plugin.ts 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. import OpenAPIClientAxios from "openapi-client-axios";
  2. import { getLang, Lang } from "../locales";
  3. import { StoreKey } from "../constant";
  4. import { nanoid } from "nanoid";
  5. import { createPersistStore } from "../utils/store";
  6. import yaml from "js-yaml";
  7. export type Plugin = {
  8. id: string;
  9. createdAt: number;
  10. title: string;
  11. version: string;
  12. content: string;
  13. builtin: boolean;
  14. authType?: string;
  15. authHeader?: string;
  16. authToken?: string;
  17. usingProxy?: boolean;
  18. };
  19. export type FunctionToolItem = {
  20. type: string;
  21. function: {
  22. name: string;
  23. description?: string;
  24. parameters: Object;
  25. };
  26. };
  27. type FunctionToolServiceItem = {
  28. api: OpenAPIClientAxios;
  29. length: number;
  30. tools: FunctionToolItem[];
  31. funcs: Record<string, Function>;
  32. };
  33. export const FunctionToolService = {
  34. tools: {} as Record<string, FunctionToolServiceItem>,
  35. add(plugin: Plugin, replace = false) {
  36. if (!replace && this.tools[plugin.id]) return this.tools[plugin.id];
  37. const headerName = (
  38. plugin?.authType == "custom" ? plugin?.authHeader : "Authorization"
  39. ) as string;
  40. const tokenValue =
  41. plugin?.authType == "basic"
  42. ? `Basic ${plugin?.authToken}`
  43. : plugin?.authType == "bearer"
  44. ? ` Bearer ${plugin?.authToken}`
  45. : plugin?.authToken;
  46. const definition = yaml.load(plugin.content) as any;
  47. const serverURL = definition?.servers?.[0]?.url;
  48. const baseURL = !!plugin?.usingProxy ? "/api/proxy" : serverURL;
  49. const api = new OpenAPIClientAxios({
  50. definition: yaml.load(plugin.content) as any,
  51. axiosConfigDefaults: {
  52. baseURL,
  53. headers: {
  54. // 'Cache-Control': 'no-cache',
  55. // 'Content-Type': 'application/json', // TODO
  56. [headerName]: tokenValue,
  57. "X-Base-URL": !!plugin?.usingProxy ? serverURL : undefined,
  58. },
  59. },
  60. });
  61. try {
  62. api.initSync();
  63. } catch (e) {}
  64. const operations = api.getOperations();
  65. return (this.tools[plugin.id] = {
  66. api,
  67. length: operations.length,
  68. tools: operations.map((o) => {
  69. // @ts-ignore
  70. const parameters = o?.requestBody?.content["application/json"]
  71. ?.schema || {
  72. type: "object",
  73. properties: {},
  74. };
  75. if (!parameters["required"]) {
  76. parameters["required"] = [];
  77. }
  78. if (o.parameters instanceof Array) {
  79. o.parameters.forEach((p) => {
  80. // @ts-ignore
  81. if (p?.in == "query" || p?.in == "path") {
  82. // const name = `${p.in}__${p.name}`
  83. // @ts-ignore
  84. const name = p?.name;
  85. parameters["properties"][name] = {
  86. // @ts-ignore
  87. type: p.schema.type,
  88. // @ts-ignore
  89. description: p.description,
  90. };
  91. // @ts-ignore
  92. if (p.required) {
  93. parameters["required"].push(name);
  94. }
  95. }
  96. });
  97. }
  98. return {
  99. type: "function",
  100. function: {
  101. name: o.operationId,
  102. description: o.description || o.summary,
  103. parameters: parameters,
  104. },
  105. } as FunctionToolItem;
  106. }),
  107. funcs: operations.reduce((s, o) => {
  108. // @ts-ignore
  109. s[o.operationId] = function (args) {
  110. const argument = [];
  111. if (o.parameters instanceof Array) {
  112. o.parameters.forEach((p) => {
  113. // @ts-ignore
  114. argument.push(args[p?.name]);
  115. // @ts-ignore
  116. delete args[p?.name];
  117. });
  118. } else {
  119. argument.push(null);
  120. }
  121. argument.push(args);
  122. // @ts-ignore
  123. return api.client[o.operationId].apply(null, argument);
  124. };
  125. return s;
  126. }, {}),
  127. });
  128. },
  129. get(id: string) {
  130. return this.tools[id];
  131. },
  132. };
  133. export const createEmptyPlugin = () =>
  134. ({
  135. id: nanoid(),
  136. title: "",
  137. version: "1.0.0",
  138. content: "",
  139. builtin: false,
  140. createdAt: Date.now(),
  141. }) as Plugin;
  142. export const DEFAULT_PLUGIN_STATE = {
  143. plugins: {} as Record<string, Plugin>,
  144. };
  145. export const usePluginStore = createPersistStore(
  146. { ...DEFAULT_PLUGIN_STATE },
  147. (set, get) => ({
  148. create(plugin?: Partial<Plugin>) {
  149. const plugins = get().plugins;
  150. const id = nanoid();
  151. plugins[id] = {
  152. ...createEmptyPlugin(),
  153. ...plugin,
  154. id,
  155. builtin: false,
  156. };
  157. set(() => ({ plugins }));
  158. get().markUpdate();
  159. return plugins[id];
  160. },
  161. updatePlugin(id: string, updater: (plugin: Plugin) => void) {
  162. const plugins = get().plugins;
  163. const plugin = plugins[id];
  164. if (!plugin) return;
  165. const updatePlugin = { ...plugin };
  166. updater(updatePlugin);
  167. plugins[id] = updatePlugin;
  168. FunctionToolService.add(updatePlugin, true);
  169. set(() => ({ plugins }));
  170. get().markUpdate();
  171. },
  172. delete(id: string) {
  173. const plugins = get().plugins;
  174. delete plugins[id];
  175. set(() => ({ plugins }));
  176. get().markUpdate();
  177. },
  178. getAsTools(ids: string[]) {
  179. const plugins = get().plugins;
  180. const selected = ids
  181. .map((id) => plugins[id])
  182. .filter((i) => i)
  183. .map((p) => FunctionToolService.add(p));
  184. return [
  185. // @ts-ignore
  186. selected.reduce((s, i) => s.concat(i.tools), []),
  187. selected.reduce((s, i) => Object.assign(s, i.funcs), {}),
  188. ];
  189. },
  190. get(id?: string) {
  191. return get().plugins[id ?? 1145141919810];
  192. },
  193. getAll() {
  194. return Object.values(get().plugins).sort(
  195. (a, b) => b.createdAt - a.createdAt,
  196. );
  197. },
  198. }),
  199. {
  200. name: StoreKey.Plugin,
  201. version: 1,
  202. },
  203. );