Browse Source

add provider_list

Dax Raad 9 months ago
parent
commit
55a6fcdd3f

+ 6 - 0
js/bun.lock

@@ -12,9 +12,11 @@
         "cac": "^6.7.14",
         "clipanion": "^4.0.0-rc.4",
         "diff": "^8.0.2",
+        "env-paths": "^3.0.0",
         "hono": "^4.7.10",
         "hono-openapi": "^0.4.8",
         "jsdom": "^26.1.0",
+        "remeda": "^2.22.3",
         "ts-lsp-client": "^1.0.3",
         "turndown": "^7.2.0",
         "vscode-jsonrpc": "^8.2.1",
@@ -170,6 +172,8 @@
 
     "entities": ["[email protected]", "", {}, "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw=="],
 
+    "env-paths": ["[email protected]", "", {}, "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A=="],
+
     "environment": ["[email protected]", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="],
 
     "es-toolkit": ["[email protected]", "", {}, "sha512-OT3AxczYYd3W50bCj4V0hKoOAfqIy9tof0leNQYekEDxVKir3RTVTJOLij7VAe6fsCNsGhC0JqIkURpMXTCSEA=="],
@@ -290,6 +294,8 @@
 
     "real-require": ["[email protected]", "", {}, "sha512-r/H9MzAWtrv8aSVjPCMFpDMl5q66GqtmmRkRjpHTsp4zBAa+snZyiQNlMONiUmEJcsnaw0wCauJ2GWODr/aFkg=="],
 
+    "remeda": ["[email protected]", "", { "dependencies": { "type-fest": "^4.40.1" } }, "sha512-Ka6965m9Zu9OLsysWxVf3jdJKmp6+PKzDv7HWHinEevf0JOJ9y02YpjiC/sKxRpCqGhVyvm1U+0YIj+E6DMgKw=="],
+
     "restore-cursor": ["[email protected]", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg=="],
 
     "rfdc": ["[email protected]", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="],

+ 2 - 0
js/package.json

@@ -28,9 +28,11 @@
     "cac": "^6.7.14",
     "clipanion": "^4.0.0-rc.4",
     "diff": "^8.0.2",
+    "env-paths": "^3.0.0",
     "hono": "^4.7.10",
     "hono-openapi": "^0.4.8",
     "jsdom": "^26.1.0",
+    "remeda": "^2.22.3",
     "ts-lsp-client": "^1.0.3",
     "turndown": "^7.2.0",
     "vscode-jsonrpc": "^8.2.1",

+ 22 - 13
js/src/app/config.ts

@@ -1,25 +1,34 @@
 import path from "node:path";
 import { Log } from "../util/log";
 import { z } from "zod";
+import { LLM } from "../llm/llm";
 
 export namespace Config {
   const log = Log.create({ service: "config" });
 
+  export const Model = z.object({
+    name: z.string().optional(),
+    cost: z.object({
+      input: z.number(),
+      inputCached: z.number(),
+      output: z.number(),
+      outputCached: z.number(),
+    }),
+    contextWindow: z.number(),
+    maxTokens: z.number(),
+    attachment: z.boolean(),
+  });
+  export type Model = z.output<typeof Model>;
+
+  export const Provider = z.object({
+    options: z.record(z.string(), z.any()).optional(),
+    models: z.record(z.string(), Model).optional(),
+  });
+  export type Provider = z.output<typeof Provider>;
+
   export const Info = z
     .object({
-      providers: z
-        .object({
-          anthropic: z
-            .object({
-              apiKey: z.string().optional(),
-              headers: z.record(z.string(), z.string()).optional(),
-              baseURL: z.string().optional(),
-            })
-            .strict()
-            .optional(),
-        })
-        .strict()
-        .optional(),
+      providers: z.record(z.string(), Provider).optional(),
     })
     .strict();
 

+ 25 - 0
js/src/bun/index.ts

@@ -0,0 +1,25 @@
+import path from "node:path";
+import { Log } from "../util/log";
+export namespace BunProc {
+  const log = Log.create({ service: "bun" });
+
+  export function run(
+    cmd: string[],
+    options?: Bun.SpawnOptions.OptionsObject<any, any, any>,
+  ) {
+    const root = path.resolve(process.cwd(), process.argv0);
+    log.info("running", {
+      cmd: [root, ...cmd],
+      options,
+    });
+    const result = Bun.spawnSync([root, ...cmd], {
+      ...options,
+      argv0: "bun",
+      env: {
+        ...process.env,
+        ...options?.env,
+      },
+    });
+    return result;
+  }
+}

+ 20 - 0
js/src/global/index.ts

@@ -0,0 +1,20 @@
+import envpaths from "env-paths";
+import fs from "fs/promises";
+const paths = envpaths("opencode", {
+  suffix: "",
+});
+
+await Promise.all([
+  fs.mkdir(paths.config, { recursive: true }),
+  fs.mkdir(paths.cache, { recursive: true }),
+]);
+
+export namespace Global {
+  export function config() {
+    return paths.config;
+  }
+
+  export function cache() {
+    return paths.cache;
+  }
+}

+ 14 - 3
js/src/index.ts

@@ -7,6 +7,7 @@ import { Session } from "./session/session";
 import cac from "cac";
 import { Share } from "./share/share";
 import { Storage } from "./storage/storage";
+import { LLM } from "./llm/llm";
 
 const cli = cac("opencode");
 
@@ -90,9 +91,19 @@ cli
         }
       });
 
-      const result = await Session.chat(session.id, {
-        type: "text",
-        text: message.join(" "),
+      const providers = await LLM.providers();
+      const providerID = Object.keys(providers)[0];
+      const modelID = Object.keys(providers[providerID].info.models!)[0];
+      const result = await Session.chat({
+        sessionID: session.id,
+        providerID,
+        modelID,
+        parts: [
+          {
+            type: "text",
+            text: message.join(" "),
+          },
+        ],
       });
 
       for (const part of result.parts) {

+ 82 - 27
js/src/llm/llm.ts

@@ -1,9 +1,13 @@
 import { App } from "../app";
 import { Log } from "../util/log";
+import { mergeDeep } from "remeda";
+import path from "node:path";
 
-import { createAnthropic } from "@ai-sdk/anthropic";
 import type { LanguageModel, Provider } from "ai";
 import { NoSuchModelError } from "ai";
+import type { Config } from "../app/config";
+import { BunProc } from "../bun";
+import { Global } from "../global";
 
 export namespace LLM {
   const log = Log.create({ service: "llm" });
@@ -14,17 +18,67 @@ export namespace LLM {
     }
   }
 
+  const NATIVE_PROVIDERS: Record<string, Config.Provider> = {
+    anthropic: {
+      models: {
+        "claude-sonnet-4-20250514": {
+          name: "Claude 4 Sonnet",
+          cost: {
+            input: 3.0,
+            inputCached: 3.75,
+            output: 15.0,
+            outputCached: 0.3,
+          },
+          contextWindow: 200000,
+          maxTokens: 50000,
+          attachment: true,
+        },
+      },
+    },
+  };
+
+  const AUTODETECT: Record<string, string[]> = {
+    anthropic: ["ANTHROPIC_API_KEY"],
+  };
+
   const state = App.state("llm", async (app) => {
-    const providers: Provider[] = [];
-
-    if (process.env["ANTHROPIC_API_KEY"] || app.config.providers?.anthropic) {
-      log.info("loaded anthropic");
-      const provider = createAnthropic({
-        apiKey: app.config.providers?.anthropic?.apiKey,
-        baseURL: app.config.providers?.anthropic?.baseURL,
-        headers: app.config.providers?.anthropic?.headers,
-      });
-      providers.push(provider);
+    const providers: Record<
+      string,
+      {
+        info: Config.Provider;
+        instance: Provider;
+      }
+    > = {};
+
+    const list = mergeDeep(NATIVE_PROVIDERS, app.config.providers ?? {});
+
+    for (const [providerID, providerInfo] of Object.entries(list)) {
+      if (
+        !app.config.providers?.[providerID] &&
+        !AUTODETECT[providerID]?.some((env) => process.env[env])
+      )
+        continue;
+      const dir = path.join(
+        Global.cache(),
+        `node_modules`,
+        `@ai-sdk`,
+        providerID,
+      );
+      if (!(await Bun.file(path.join(dir, "package.json")).exists())) {
+        BunProc.run(["add", "--exact", `@ai-sdk/${providerID}@alpha`], {
+          cwd: Global.cache(),
+        });
+      }
+      const mod = await import(
+        path.join(Global.cache(), `node_modules`, `@ai-sdk`, providerID)
+      );
+      const fn = mod[Object.keys(mod).find((key) => key.startsWith("create"))!];
+      const loaded = fn(providerInfo.options);
+      log.info("loaded", { provider: providerID });
+      providers[providerID] = {
+        info: providerInfo,
+        instance: loaded,
+      };
     }
 
     return {
@@ -37,23 +91,24 @@ export namespace LLM {
     return state().then((state) => state.providers);
   }
 
-  export async function findModel(model: string) {
+  export async function findModel(providerID: string, modelID: string) {
+    const key = `${providerID}/${modelID}`;
     const s = await state();
-    if (s.models.has(model)) {
-      return s.models.get(model)!;
-    }
-    log.info("loading", { model });
-    for (const provider of s.providers) {
-      try {
-        const match = provider.languageModel(model);
-        log.info("found", { model });
-        s.models.set(model, match);
-        return match;
-      } catch (e) {
-        if (e instanceof NoSuchModelError) continue;
-        throw e;
-      }
+    if (s.models.has(key)) return s.models.get(key)!;
+    const provider = s.providers[providerID];
+    if (!provider) throw new ModelNotFoundError(modelID);
+    log.info("loading", {
+      providerID,
+      modelID,
+    });
+    try {
+      const match = provider.instance.languageModel(modelID);
+      log.info("found", { providerID, modelID });
+      s.models.set(key, match);
+      return match;
+    } catch (e) {
+      if (e instanceof NoSuchModelError) throw new ModelNotFoundError(modelID);
+      throw e;
     }
-    throw new ModelNotFoundError(model);
   }
 }

+ 0 - 0
js/src/llm/models/anthropic.ts


+ 1 - 0
js/src/llm/models/index.ts

@@ -0,0 +1 @@
+export * as anthropic from "./anthropic";

+ 11 - 0
js/src/llm/models/model.ts

@@ -0,0 +1,11 @@
+export interface ModelInfo {
+  cost: {
+    input: number;
+    inputCached: number;
+    output: number;
+    outputCached: number;
+  };
+  contextWindow: number;
+  maxTokens: number;
+  attachment: boolean;
+}

+ 34 - 1
js/src/server/server.ts

@@ -7,11 +7,18 @@ import { Session } from "../session/session";
 import { resolver, validator as zValidator } from "hono-openapi/zod";
 import { z } from "zod";
 import "zod-openapi/extend";
+import { Config } from "../app/config";
+import { LLM } from "../llm/llm";
 
 const SessionInfo = Session.Info.openapi({
   ref: "Session.Info",
 });
 
+const ProviderInfo = Config.Provider.openapi({
+  ref: "Provider.Info",
+});
+type ProviderInfo = z.output<typeof ProviderInfo>;
+
 export namespace Server {
   const log = Log.create({ service: "server" });
   const PORT = 16713;
@@ -156,14 +163,40 @@ export namespace Server {
           "json",
           z.object({
             sessionID: z.string(),
+            providerID: z.string(),
+            modelID: z.string(),
             parts: z.custom<Session.Message["parts"]>(),
           }),
         ),
         async (c) => {
           const body = c.req.valid("json");
-          const msg = await Session.chat(body.sessionID, ...body.parts);
+          const msg = await Session.chat(body);
           return c.json(msg);
         },
+      )
+      .post(
+        "/provider_list",
+        describeRoute({
+          description: "List all providers",
+          responses: {
+            200: {
+              description: "List of providers",
+              content: {
+                "application/json": {
+                  schema: resolver(z.record(z.string(), ProviderInfo)),
+                },
+              },
+            },
+          },
+        }),
+        async (c) => {
+          const providers = await LLM.providers();
+          const result: Record<string, ProviderInfo> = {};
+          for (const [providerID, provider] of Object.entries(providers)) {
+            result[providerID] = provider.info;
+          }
+          return c.json(result);
+        },
       );
 
     return result;

+ 29 - 29
js/src/session/session.ts

@@ -127,19 +127,19 @@ export namespace Session {
     }
   }
 
-  export async function chat(
-    sessionID: string,
-    ...parts: UIMessagePart<UIDataTypes>[]
-  ) {
-    const model = await LLM.findModel("claude-sonnet-4-20250514");
-    const session = await get(sessionID);
-    const l = log.clone().tag("session", sessionID);
+  export async function chat(input: {
+    sessionID: string;
+    providerID: string;
+    modelID: string;
+    parts: UIMessagePart<UIDataTypes>[];
+  }) {
+    const l = log.clone().tag("session", input.sessionID);
     l.info("chatting");
-
-    const msgs = await messages(sessionID);
+    const model = await LLM.findModel(input.providerID, input.modelID);
+    const msgs = await messages(input.sessionID);
     async function write(msg: Message) {
       return Storage.writeJSON(
-        "session/message/" + sessionID + "/" + msg.id,
+        "session/message/" + input.sessionID + "/" + msg.id,
         msg,
       );
     }
@@ -155,7 +155,7 @@ export namespace Session {
           },
         ],
         metadata: {
-          sessionID,
+          sessionID: input.sessionID,
           time: {
             created: Date.now(),
           },
@@ -171,7 +171,7 @@ export namespace Session {
         });
       }
       msgs.push(system);
-      state().messages.set(sessionID, msgs);
+      state().messages.set(input.sessionID, msgs);
       generateText({
         messages: convertToModelMessages([
           {
@@ -185,12 +185,12 @@ export namespace Session {
           },
           {
             role: "user",
-            parts,
+            parts: input.parts,
           },
         ]),
         model,
       }).then((result) => {
-        return Session.update(sessionID, (draft) => {
+        return Session.update(input.sessionID, (draft) => {
           draft.title = result.text;
         });
       });
@@ -199,21 +199,33 @@ export namespace Session {
     const msg: Message = {
       role: "user",
       id: Identifier.ascending("message"),
-      parts,
+      parts: input.parts,
       metadata: {
         time: {
           created: Date.now(),
         },
-        sessionID,
+        sessionID: input.sessionID,
         tool: {},
       },
     };
     msgs.push(msg);
     await write(msg);
 
+    const next: Message = {
+      id: Identifier.ascending("message"),
+      role: "assistant",
+      parts: [],
+      metadata: {
+        time: {
+          created: Date.now(),
+        },
+        sessionID: input.sessionID,
+        tool: {},
+      },
+    };
     const result = streamText({
       onStepFinish: (step) => {
-        update(sessionID, (draft) => {
+        update(input.sessionID, (draft) => {
           draft.tokens.input += step.usage.inputTokens || 0;
           draft.tokens.output += step.usage.outputTokens || 0;
           draft.tokens.reasoning += step.usage.reasoningTokens || 0;
@@ -225,18 +237,6 @@ export namespace Session {
       tools,
       model,
     });
-    const next: Message = {
-      id: Identifier.ascending("message"),
-      role: "assistant",
-      parts: [],
-      metadata: {
-        time: {
-          created: Date.now(),
-        },
-        sessionID,
-        tool: {},
-      },
-    };
 
     msgs.push(next);
     let text: TextUIPart | undefined;

+ 89 - 1
pkg/client/gen/openapi.json

@@ -167,16 +167,46 @@
                   "sessionID": {
                     "type": "string"
                   },
+                  "providerID": {
+                    "type": "string"
+                  },
+                  "modelID": {
+                    "type": "string"
+                  },
                   "parts": {}
                 },
                 "required": [
-                  "sessionID"
+                  "sessionID",
+                  "providerID",
+                  "modelID"
                 ]
               }
             }
           }
         }
       }
+    },
+    "/provider_list": {
+      "post": {
+        "responses": {
+          "200": {
+            "description": "List of providers",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "type": "object",
+                  "additionalProperties": {
+                    "$ref": "#/components/schemas/Provider.Info"
+                  }
+                }
+              }
+            }
+          }
+        },
+        "operationId": "postProvider_list",
+        "parameters": [],
+        "description": "List all providers"
+      }
     }
   },
   "components": {
@@ -219,6 +249,64 @@
           "title",
           "tokens"
         ]
+      },
+      "Provider.Info": {
+        "type": "object",
+        "properties": {
+          "options": {
+            "type": "object",
+            "additionalProperties": {}
+          },
+          "models": {
+            "type": "object",
+            "additionalProperties": {
+              "type": "object",
+              "properties": {
+                "name": {
+                  "type": "string"
+                },
+                "cost": {
+                  "type": "object",
+                  "properties": {
+                    "input": {
+                      "type": "number"
+                    },
+                    "inputCached": {
+                      "type": "number"
+                    },
+                    "output": {
+                      "type": "number"
+                    },
+                    "outputCached": {
+                      "type": "number"
+                    }
+                  },
+                  "required": [
+                    "input",
+                    "inputCached",
+                    "output",
+                    "outputCached"
+                  ]
+                },
+                "contextWindow": {
+                  "type": "number"
+                },
+                "maxTokens": {
+                  "type": "number"
+                },
+                "attachment": {
+                  "type": "boolean"
+                }
+              },
+              "required": [
+                "cost",
+                "contextWindow",
+                "maxTokens",
+                "attachment"
+              ]
+            }
+          }
+        }
       }
     }
   }

+ 123 - 2
pkg/client/generated-client.go

@@ -14,6 +14,23 @@ import (
 	"strings"
 )
 
+// ProviderInfo defines model for Provider.Info.
+type ProviderInfo struct {
+	Models *map[string]struct {
+		Attachment    bool    `json:"attachment"`
+		ContextWindow float32 `json:"contextWindow"`
+		Cost          struct {
+			Input        float32 `json:"input"`
+			InputCached  float32 `json:"inputCached"`
+			Output       float32 `json:"output"`
+			OutputCached float32 `json:"outputCached"`
+		} `json:"cost"`
+		MaxTokens float32 `json:"maxTokens"`
+		Name      *string `json:"name,omitempty"`
+	} `json:"models,omitempty"`
+	Options *map[string]interface{} `json:"options,omitempty"`
+}
+
 // SessionInfo defines model for Session.Info.
 type SessionInfo struct {
 	Id      string  `json:"id"`
@@ -28,8 +45,10 @@ type SessionInfo struct {
 
 // PostSessionChatJSONBody defines parameters for PostSessionChat.
 type PostSessionChatJSONBody struct {
-	Parts     *interface{} `json:"parts,omitempty"`
-	SessionID string       `json:"sessionID"`
+	ModelID    string       `json:"modelID"`
+	Parts      *interface{} `json:"parts,omitempty"`
+	ProviderID string       `json:"providerID"`
+	SessionID  string       `json:"sessionID"`
 }
 
 // PostSessionMessagesJSONBody defines parameters for PostSessionMessages.
@@ -124,6 +143,9 @@ func WithRequestEditorFn(fn RequestEditorFn) ClientOption {
 
 // The interface specification for the client above.
 type ClientInterface interface {
+	// PostProviderList request
+	PostProviderList(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error)
+
 	// PostSessionChatWithBody request with any body
 	PostSessionChatWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
 
@@ -146,6 +168,18 @@ type ClientInterface interface {
 	PostSessionShare(ctx context.Context, body PostSessionShareJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)
 }
 
+func (c *Client) PostProviderList(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) {
+	req, err := NewPostProviderListRequest(c.Server)
+	if err != nil {
+		return nil, err
+	}
+	req = req.WithContext(ctx)
+	if err := c.applyEditors(ctx, req, reqEditors); err != nil {
+		return nil, err
+	}
+	return c.Client.Do(req)
+}
+
 func (c *Client) PostSessionChatWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) {
 	req, err := NewPostSessionChatRequestWithBody(c.Server, contentType, body)
 	if err != nil {
@@ -242,6 +276,33 @@ func (c *Client) PostSessionShare(ctx context.Context, body PostSessionShareJSON
 	return c.Client.Do(req)
 }
 
+// NewPostProviderListRequest generates requests for PostProviderList
+func NewPostProviderListRequest(server string) (*http.Request, error) {
+	var err error
+
+	serverURL, err := url.Parse(server)
+	if err != nil {
+		return nil, err
+	}
+
+	operationPath := fmt.Sprintf("/provider_list")
+	if operationPath[0] == '/' {
+		operationPath = "." + operationPath
+	}
+
+	queryURL, err := serverURL.Parse(operationPath)
+	if err != nil {
+		return nil, err
+	}
+
+	req, err := http.NewRequest("POST", queryURL.String(), nil)
+	if err != nil {
+		return nil, err
+	}
+
+	return req, nil
+}
+
 // NewPostSessionChatRequest calls the generic PostSessionChat builder with application/json body
 func NewPostSessionChatRequest(server string, body PostSessionChatJSONRequestBody) (*http.Request, error) {
 	var bodyReader io.Reader
@@ -459,6 +520,9 @@ func WithBaseURL(baseURL string) ClientOption {
 
 // ClientWithResponsesInterface is the interface specification for the client with responses above.
 type ClientWithResponsesInterface interface {
+	// PostProviderListWithResponse request
+	PostProviderListWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*PostProviderListResponse, error)
+
 	// PostSessionChatWithBodyWithResponse request with any body
 	PostSessionChatWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostSessionChatResponse, error)
 
@@ -481,6 +545,28 @@ type ClientWithResponsesInterface interface {
 	PostSessionShareWithResponse(ctx context.Context, body PostSessionShareJSONRequestBody, reqEditors ...RequestEditorFn) (*PostSessionShareResponse, error)
 }
 
+type PostProviderListResponse struct {
+	Body         []byte
+	HTTPResponse *http.Response
+	JSON200      *map[string]ProviderInfo
+}
+
+// Status returns HTTPResponse.Status
+func (r PostProviderListResponse) Status() string {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.Status
+	}
+	return http.StatusText(0)
+}
+
+// StatusCode returns HTTPResponse.StatusCode
+func (r PostProviderListResponse) StatusCode() int {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.StatusCode
+	}
+	return 0
+}
+
 type PostSessionChatResponse struct {
 	Body         []byte
 	HTTPResponse *http.Response
@@ -599,6 +685,15 @@ func (r PostSessionShareResponse) StatusCode() int {
 	return 0
 }
 
+// PostProviderListWithResponse request returning *PostProviderListResponse
+func (c *ClientWithResponses) PostProviderListWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*PostProviderListResponse, error) {
+	rsp, err := c.PostProviderList(ctx, reqEditors...)
+	if err != nil {
+		return nil, err
+	}
+	return ParsePostProviderListResponse(rsp)
+}
+
 // PostSessionChatWithBodyWithResponse request with arbitrary body returning *PostSessionChatResponse
 func (c *ClientWithResponses) PostSessionChatWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostSessionChatResponse, error) {
 	rsp, err := c.PostSessionChatWithBody(ctx, contentType, body, reqEditors...)
@@ -668,6 +763,32 @@ func (c *ClientWithResponses) PostSessionShareWithResponse(ctx context.Context,
 	return ParsePostSessionShareResponse(rsp)
 }
 
+// ParsePostProviderListResponse parses an HTTP response from a PostProviderListWithResponse call
+func ParsePostProviderListResponse(rsp *http.Response) (*PostProviderListResponse, error) {
+	bodyBytes, err := io.ReadAll(rsp.Body)
+	defer func() { _ = rsp.Body.Close() }()
+	if err != nil {
+		return nil, err
+	}
+
+	response := &PostProviderListResponse{
+		Body:         bodyBytes,
+		HTTPResponse: rsp,
+	}
+
+	switch {
+	case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
+		var dest map[string]ProviderInfo
+		if err := json.Unmarshal(bodyBytes, &dest); err != nil {
+			return nil, err
+		}
+		response.JSON200 = &dest
+
+	}
+
+	return response, nil
+}
+
 // ParsePostSessionChatResponse parses an HTTP response from a PostSessionChatWithResponse call
 func ParsePostSessionChatResponse(rsp *http.Response) (*PostSessionChatResponse, error) {
 	bodyBytes, err := io.ReadAll(rsp.Body)