repo_description: ✨ Light and Fast AI Assistant. Support: Web | iOS | MacOS | Android |  Linux | Windows
repo_topics: calclaude chatgpt claude cross-platform desktop fe gemini gemini-pro gemini-server gemini-ultra gpt-4o groq nextjs ollama react tauri tauri-app vercel webui
code: import OpenAPIClientAxios from "openapi-client-axios";
  import { StoreKey } from "../constant";
  import { nanoid } from "nanoid";
  import { createPersistStore } from "../utils/store";
  import { getClientConfig } from "../config/client";
  import yaml from "js-yaml";
  import { adapter, getOperationId } from "../utils";
  import { useAccessStore } from "./access";
  
  const isApp = getClientConfig()?.isApp !== false;
  
  export type Plugin = {
    id: string;
    createdAt: number;
    title: string;
    version: string;
    content: string;
    builtin: boolean;
    authType?: string;
    authLocation?: string;
    authHeader?: string;
    authToken?: string;
  };
  
  export type FunctionToolItem = {
    type: string;
    function: {
      name: string;
      description?: string;
      parameters: Object;
    };
  };
  
  type FunctionToolServiceItem = {
    api: OpenAPIClientAxios;
    length: number;
    tools: FunctionToolItem[];
    funcs: Record<string, Function>;
  };
  
  export const FunctionToolService = {
    tools: {} as Record<string, FunctionToolServiceItem>,
    add(plugin: Plugin, replace = false) {
      if (!replace && this.tools[plugin.id]) return this.tools[plugin.id];
      const headerName = (
        plugin?.authType == "custom" ? plugin?.authHeader : "Authorization"
      ) as string;
      const tokenValue =
        plugin?.authType == "basic"
          ? `Basic ${plugin?.authToken}`
          : plugin?.authType == "bearer"
          ? `Bearer ${plugin?.authToken}`
          : plugin?.authToken;
      const authLocation = plugin?.authLocation || "header";
      const definition = yaml.load(plugin.content) as any;
      const serverURL = definition?.servers?.[0]?.url;
      const baseURL = !isApp ? "/api/proxy" : serverURL;
      const headers: Record<string, string | undefined> = {
        "X-Base-URL": !isApp ? serverURL : undefined,
      };
      if (authLocation == "header") {
        headers[headerName] = tokenValue;
      }
      // try using openaiApiKey for Dalle3 Plugin.
      if (!tokenValue && plugin.id === "dalle3") {
        const openaiApiKey = useAccessStore.getState().openaiApiKey;
        if (openaiApiKey) {
          headers[headerName] = `Bearer ${openaiApiKey}`;
        }
      }
      const api = new OpenAPIClientAxios({
        definition: yaml.load(plugin.content) as any,
        axiosConfigDefaults: {
          adapter: (window.__TAURI__ ? adapter : ["xhr"]) as any,
          baseURL,
          headers,
        },
      });
      try {
        api.initSync();
      } catch (e) {}
      const operations = api.getOperations();
      return (this.tools[plugin.id] = {
        api,
        length: operations.length,
        tools: operations.map((o) => {
          // @ts-ignore
          const parameters = o?.requestBody?.content["application/json"]
            ?.schema || {
            type: "object",
            properties: {},
          };
          if (!parameters["required"]) {
            parameters["required"] = [];
          }
          if (o.parameters instanceof Array) {
            o.parameters.forEach((p) => {
              // @ts-ignore
              if (p?.in == "query" || p?.in == "path") {
                // const name = `${p.in}__${p.name}`
                // @ts-ignore
                const name = p?.name;
                parameters["properties"][name] = {
                  // @ts-ignore
                  type: p.schema.type,
                  // @ts-ignore
                  description: p.description,
                };
                // @ts-ignore
                if (p.required) {
                  parameters["required"].push(name);
                }
              }
            });
          }
          return {
            type: "function",
            function: {
              name: getOperationId(o),
              description: o.description || o.summary,
              parameters: parameters,
            },
          } as FunctionToolItem;
        }),
        funcs: operations.reduce((s, o) => {
          // @ts-ignore
          s[getOperationId(o)] = function (args) {
            const parameters: Record<string, any> = {};
            if (o.parameters instanceof Array) {
              o.parameters.forEach((p) => {
                // @ts-ignore
                parameters[p?.name] = args[p?.name];
                // @ts-ignore
                delete args[p?.name];
              });
            }
            if (authLocation == "query") {
              parameters[headerName] = tokenValue;
            } else if (authLocation == "body") {
              args[headerName] = tokenValue;
            }
            // @ts-ignore if o.operationId is null, then using o.path and o.method
            return api.client.paths[o.path][o.method](
              parameters,
              args,
              api.axiosConfigDefaults,
            );
          };
          return s;
        }, {}),
      });
    },
    get(id: string) {
      return this.tools[id];
    },
  };
  
  export const createEmptyPlugin = () =>
    ({
      id: nanoid(),
      title: "",
      version: "1.0.0",
      content: "",
      builtin: false,
      createdAt: Date.now(),
    }) as Plugin;
  
  export const DEFAULT_PLUGIN_STATE = {
    plugins: {} as Record<string, Plugin>,
  };
  
  export const usePluginStore = createPersistStore(
    { ...DEFAULT_PLUGIN_STATE },
  
    (set, get) => ({
      create(plugin?: Partial<Plugin>) {
        const plugins = get().plugins;
        const id = plugin?.id || nanoid();
        plugins[id] = {
          ...createEmptyPlugin(),
          ...plugin,
          id,
          builtin: false,
        };
  
        set(() => ({ plugins }));
        get().markUpdate();
  
        return plugins[id];
      },
      updatePlugin(id: string, updater: (plugin: Plugin) => void) {
        const plugins = get().plugins;
        const plugin = plugins[id];
        if (!plugin) return;
        const updatePlugin = { ...plugin };
        updater(updatePlugin);
        plugins[id] = updatePlugin;
        FunctionToolService.add(updatePlugin, true);
        set(() => ({ plugins }));
        get().markUpdate();
      },
      delete(id: string) {
        const plugins = get().plugins;
        delete plugins[id];
        set(() => ({ plugins }));
        get().markUpdate();
      },
  
      getAsTools(ids: string[]) {
        const plugins = get().plugins;
        const selected = (ids || [])
          .map((id) => plugins[id])
          .filter((i) => i)
          .map((p) => FunctionToolService.add(p));
        return [
          // @ts-ignore
          selected.reduce((s, i) => s.concat(i.tools), []),
          selected.reduce((s, i) => Object.assign(s, i.funcs), {}),
        ];
      },
      get(id?: string) {
        return get().plugins[id ?? 1145141919810];
      },
      getAll() {
        return Object.values(get().plugins).sort(
          (a, b) => b.createdAt - a.createdAt,
        );
      },
    }),
    {
      name: StoreKey.Plugin,
      version: 1,
      onRehydrateStorage(state) {
        // Skip store rehydration on server side
        if (typeof window === "undefined") {
          return;
        }
  
        fetch("./plugins.json")
          .then((res) => res.json())
          .then((res) => {
            Promise.all(
              res.map((item: any) =>
                // skip get schema
                state.get(item.id)
                  ? item
                  : fetch(item.schema)
                      .then((res) => res.text())
                      .then((content) => ({
                        ...item,
                        content,
                      }))
                      .catch((e) => item),
              ),
            ).then((builtinPlugins: any) => {
              builtinPlugins
                .filter((item: any) => item?.content)
                .forEach((item: any) => {
                  const plugin = state.create(item);
                  state.updatePlugin(plugin.id, (plugin) => {
                    const tool = FunctionToolService.add(plugin, true);
                    plugin.title = tool.api.definition.info.title;
                    plugin.version = tool.api.definition.info.version;
                    plugin.builtin = true;
                  });
                });
            });
          });
      },
    },
  );
  