plugin.ts 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. import OpenAPIClientAxios from "openapi-client-axios";
  2. import { StoreKey } from "../constant";
  3. import { nanoid } from "nanoid";
  4. import { createPersistStore } from "../utils/store";
  5. import { getClientConfig } from "../config/client";
  6. import yaml from "js-yaml";
  7. import { adapter, getOperationId } from "../utils";
  8. import { useAccessStore } from "./access";
  9. const isApp = getClientConfig()?.isApp !== false;
  10. export type Plugin = {
  11. id: string;
  12. createdAt: number;
  13. title: string;
  14. version: string;
  15. content: string;
  16. builtin: boolean;
  17. authType?: string;
  18. authLocation?: string;
  19. authHeader?: string;
  20. authToken?: string;
  21. };
  22. export type FunctionToolItem = {
  23. type: string;
  24. function: {
  25. name: string;
  26. description?: string;
  27. parameters: Object;
  28. };
  29. };
  30. type FunctionToolServiceItem = {
  31. api: OpenAPIClientAxios;
  32. length: number;
  33. tools: FunctionToolItem[];
  34. funcs: Record<string, Function>;
  35. };
  36. export const FunctionToolService = {
  37. tools: {} as Record<string, FunctionToolServiceItem>,
  38. add(plugin: Plugin, replace = false) {
  39. if (!replace && this.tools[plugin.id]) return this.tools[plugin.id];
  40. const headerName = (
  41. plugin?.authType == "custom" ? plugin?.authHeader : "Authorization"
  42. ) as string;
  43. const tokenValue =
  44. plugin?.authType == "basic"
  45. ? `Basic ${plugin?.authToken}`
  46. : plugin?.authType == "bearer"
  47. ? `Bearer ${plugin?.authToken}`
  48. : plugin?.authToken;
  49. const authLocation = plugin?.authLocation || "header";
  50. const definition = yaml.load(plugin.content) as any;
  51. const serverURL = definition?.servers?.[0]?.url;
  52. const baseURL = !isApp ? "/api/proxy" : serverURL;
  53. const headers: Record<string, string | undefined> = {
  54. "X-Base-URL": !isApp ? serverURL : undefined,
  55. };
  56. if (authLocation == "header") {
  57. headers[headerName] = tokenValue;
  58. }
  59. // try using openaiApiKey for Dalle3 Plugin.
  60. if (!tokenValue && plugin.id === "dalle3") {
  61. const openaiApiKey = useAccessStore.getState().openaiApiKey;
  62. if (openaiApiKey) {
  63. headers[headerName] = `Bearer ${openaiApiKey}`;
  64. }
  65. }
  66. const api = new OpenAPIClientAxios({
  67. definition: yaml.load(plugin.content) as any,
  68. axiosConfigDefaults: {
  69. adapter: (window.__TAURI__ ? adapter : ["xhr"]) as any,
  70. baseURL,
  71. headers,
  72. },
  73. });
  74. try {
  75. api.initSync();
  76. } catch (e) {}
  77. const operations = api.getOperations();
  78. return (this.tools[plugin.id] = {
  79. api,
  80. length: operations.length,
  81. tools: operations.map((o) => {
  82. // @ts-ignore
  83. const parameters = o?.requestBody?.content["application/json"]
  84. ?.schema || {
  85. type: "object",
  86. properties: {},
  87. };
  88. if (!parameters["required"]) {
  89. parameters["required"] = [];
  90. }
  91. if (o.parameters instanceof Array) {
  92. o.parameters.forEach((p) => {
  93. // @ts-ignore
  94. if (p?.in == "query" || p?.in == "path") {
  95. // const name = `${p.in}__${p.name}`
  96. // @ts-ignore
  97. const name = p?.name;
  98. parameters["properties"][name] = {
  99. // @ts-ignore
  100. type: p.schema.type,
  101. // @ts-ignore
  102. description: p.description,
  103. };
  104. // @ts-ignore
  105. if (p.required) {
  106. parameters["required"].push(name);
  107. }
  108. }
  109. });
  110. }
  111. return {
  112. type: "function",
  113. function: {
  114. name: getOperationId(o),
  115. description: o.description || o.summary,
  116. parameters: parameters,
  117. },
  118. } as FunctionToolItem;
  119. }),
  120. funcs: operations.reduce((s, o) => {
  121. // @ts-ignore
  122. s[getOperationId(o)] = function (args) {
  123. const parameters: Record<string, any> = {};
  124. if (o.parameters instanceof Array) {
  125. o.parameters.forEach((p) => {
  126. // @ts-ignore
  127. parameters[p?.name] = args[p?.name];
  128. // @ts-ignore
  129. delete args[p?.name];
  130. });
  131. }
  132. if (authLocation == "query") {
  133. parameters[headerName] = tokenValue;
  134. } else if (authLocation == "body") {
  135. args[headerName] = tokenValue;
  136. }
  137. // @ts-ignore if o.operationId is null, then using o.path and o.method
  138. return api.client.paths[o.path][o.method](
  139. parameters,
  140. args,
  141. api.axiosConfigDefaults,
  142. );
  143. };
  144. return s;
  145. }, {}),
  146. });
  147. },
  148. get(id: string) {
  149. return this.tools[id];
  150. },
  151. };
  152. export const createEmptyPlugin = () =>
  153. ({
  154. id: nanoid(),
  155. title: "",
  156. version: "1.0.0",
  157. content: "",
  158. builtin: false,
  159. createdAt: Date.now(),
  160. }) as Plugin;
  161. export const DEFAULT_PLUGIN_STATE = {
  162. plugins: {} as Record<string, Plugin>,
  163. };
  164. export const usePluginStore = createPersistStore(
  165. { ...DEFAULT_PLUGIN_STATE },
  166. (set, get) => ({
  167. create(plugin?: Partial<Plugin>) {
  168. const plugins = get().plugins;
  169. const id = plugin?.id || nanoid();
  170. plugins[id] = {
  171. ...createEmptyPlugin(),
  172. ...plugin,
  173. id,
  174. builtin: false,
  175. };
  176. set(() => ({ plugins }));
  177. get().markUpdate();
  178. return plugins[id];
  179. },
  180. updatePlugin(id: string, updater: (plugin: Plugin) => void) {
  181. const plugins = get().plugins;
  182. const plugin = plugins[id];
  183. if (!plugin) return;
  184. const updatePlugin = { ...plugin };
  185. updater(updatePlugin);
  186. plugins[id] = updatePlugin;
  187. FunctionToolService.add(updatePlugin, true);
  188. set(() => ({ plugins }));
  189. get().markUpdate();
  190. },
  191. delete(id: string) {
  192. const plugins = get().plugins;
  193. delete plugins[id];
  194. set(() => ({ plugins }));
  195. get().markUpdate();
  196. },
  197. getAsTools(ids: string[]) {
  198. const plugins = get().plugins;
  199. const selected = (ids || [])
  200. .map((id) => plugins[id])
  201. .filter((i) => i)
  202. .map((p) => FunctionToolService.add(p));
  203. return [
  204. // @ts-ignore
  205. selected.reduce((s, i) => s.concat(i.tools), []),
  206. selected.reduce((s, i) => Object.assign(s, i.funcs), {}),
  207. ];
  208. },
  209. get(id?: string) {
  210. return get().plugins[id ?? 1145141919810];
  211. },
  212. getAll() {
  213. return Object.values(get().plugins).sort(
  214. (a, b) => b.createdAt - a.createdAt,
  215. );
  216. },
  217. }),
  218. {
  219. name: StoreKey.Plugin,
  220. version: 1,
  221. onRehydrateStorage(state) {
  222. // Skip store rehydration on server side
  223. if (typeof window === "undefined") {
  224. return;
  225. }
  226. fetch("./plugins.json")
  227. .then((res) => res.json())
  228. .then((res) => {
  229. Promise.all(
  230. res.map((item: any) =>
  231. // skip get schema
  232. state.get(item.id)
  233. ? item
  234. : fetch(item.schema)
  235. .then((res) => res.text())
  236. .then((content) => ({
  237. ...item,
  238. content,
  239. }))
  240. .catch((e) => item),
  241. ),
  242. ).then((builtinPlugins: any) => {
  243. builtinPlugins
  244. .filter((item: any) => item?.content)
  245. .forEach((item: any) => {
  246. const plugin = state.create(item);
  247. state.updatePlugin(plugin.id, (plugin) => {
  248. const tool = FunctionToolService.add(plugin, true);
  249. plugin.title = tool.api.definition.info.title;
  250. plugin.version = tool.api.definition.info.version;
  251. plugin.builtin = true;
  252. });
  253. });
  254. });
  255. });
  256. },
  257. },
  258. );