plugin.ts 5.9 KB

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