refactor(llm): colocate provider package logic

This commit is contained in:
Shoubhit Dash
2026-06-24 23:10:45 +05:30
parent 2f85f4744d
commit beefda1f8f
15 changed files with 95 additions and 129 deletions
+6 -8
View File
@@ -16,18 +16,16 @@
"./provider": "./src/provider.ts",
"./providers": "./src/providers/index.ts",
"./provider-package": "./src/provider-package.ts",
"./providers/amazon-bedrock": "./src/provider-packages/amazon-bedrock.ts",
"./providers/anthropic": "./src/provider-packages/anthropic-messages.ts",
"./providers/anthropic/messages": "./src/provider-packages/anthropic-messages.ts",
"./providers/amazon-bedrock": "./src/providers/amazon-bedrock.ts",
"./providers/anthropic": "./src/providers/anthropic.ts",
"./providers/azure": "./src/providers/azure.ts",
"./providers/cloudflare": "./src/providers/cloudflare.ts",
"./providers/github-copilot": "./src/providers/github-copilot.ts",
"./providers/google": "./src/providers/google.ts",
"./providers/openai": "./src/provider-packages/openai-responses.ts",
"./providers/openai/responses": "./src/provider-packages/openai-responses.ts",
"./providers/openai/chat": "./src/provider-packages/openai-chat.ts",
"./providers/openai-compatible": "./src/provider-packages/openai-compatible-chat.ts",
"./providers/openai-compatible/chat": "./src/provider-packages/openai-compatible-chat.ts",
"./providers/openai": "./src/providers/openai.ts",
"./providers/openai/responses": "./src/providers/openai/responses.ts",
"./providers/openai/chat": "./src/providers/openai/chat.ts",
"./providers/openai-compatible": "./src/providers/openai-compatible.ts",
"./providers/openai-compatible-profile": "./src/providers/openai-compatible-profile.ts",
"./providers/openrouter": "./src/providers/openrouter.ts",
"./providers/xai": "./src/providers/xai.ts",
@@ -1,28 +0,0 @@
import type { ProviderPackage } from "../provider-package"
import * as AmazonBedrock from "../providers/amazon-bedrock"
import type { BedrockCredentials } from "../protocols/bedrock-converse"
import { defaults } from "./shared"
export interface Settings extends ProviderPackage.Settings {
readonly apiKey?: string
readonly auth?: "bearer" | "sigv4"
readonly baseURL?: string
readonly credentials?: BedrockCredentials
readonly region?: string
readonly topP?: number
}
export const model: ProviderPackage.Definition<Settings>["model"] = (id, settings) => {
if (settings.auth === "bearer" && settings.apiKey === undefined)
throw new Error("Amazon Bedrock bearer auth requires apiKey")
if (settings.auth === "sigv4" && settings.apiKey !== undefined)
throw new Error("Amazon Bedrock SigV4 auth does not accept apiKey")
return AmazonBedrock.configure({
...defaults(settings),
apiKey: settings.auth === "sigv4" ? undefined : settings.apiKey,
baseURL: settings.baseURL,
credentials: settings.credentials,
generation: settings.topP === undefined ? undefined : { topP: settings.topP },
region: settings.region,
}).model(id)
}
@@ -1,15 +0,0 @@
import type { ProviderPackage } from "../provider-package"
import * as Anthropic from "../providers/anthropic"
import { defaults } from "./shared"
export interface Settings extends ProviderPackage.Settings {
readonly apiKey?: string
readonly baseURL?: string
}
export const model: ProviderPackage.Definition<Settings>["model"] = (id, settings) =>
Anthropic.configure({
...defaults(settings),
apiKey: settings.apiKey,
baseURL: settings.baseURL,
}).model(id)
@@ -1,20 +0,0 @@
import type { ProviderPackage } from "../provider-package"
import * as OpenAI from "../providers/openai"
import type { OpenAIProviderOptionsInput } from "../providers/openai-options"
import { defaults } from "./shared"
export interface Settings extends ProviderPackage.Settings {
readonly apiKey?: string
readonly baseURL?: string
readonly queryParams?: Readonly<Record<string, string>>
readonly providerOptions?: OpenAIProviderOptionsInput
}
export const model: ProviderPackage.Definition<Settings>["model"] = (id, settings) =>
OpenAI.configure({
...defaults(settings),
apiKey: settings.apiKey,
baseURL: settings.baseURL,
queryParams: settings.queryParams === undefined ? undefined : { ...settings.queryParams },
providerOptions: settings.providerOptions,
}).chat(id)
@@ -1,17 +0,0 @@
import type { ProviderPackage } from "../provider-package"
import * as OpenAICompatible from "../providers/openai-compatible"
import { defaults } from "./shared"
export interface Settings extends ProviderPackage.Settings {
readonly apiKey?: string
readonly baseURL: string
readonly provider?: string
}
export const model: ProviderPackage.Definition<Settings>["model"] = (id, settings) =>
OpenAICompatible.configure({
...defaults(settings),
apiKey: settings.apiKey,
baseURL: settings.baseURL,
provider: settings.provider,
}).model(id)
@@ -1,25 +0,0 @@
import type { ProviderPackage } from "../provider-package"
import * as OpenAI from "../providers/openai"
import type { OpenAIProviderOptionsInput } from "../providers/openai-options"
import { defaults } from "./shared"
export interface Settings extends ProviderPackage.Settings {
readonly apiKey?: string
readonly baseURL?: string
readonly queryParams?: Readonly<Record<string, string>>
readonly transport?: "http" | "websocket"
readonly providerOptions?: OpenAIProviderOptionsInput
}
export const model: ProviderPackage.Definition<Settings>["model"] = (id, settings) => {
const provider = OpenAI.configure({
...defaults(settings),
apiKey: settings.apiKey,
baseURL: settings.baseURL,
queryParams: settings.queryParams === undefined ? undefined : { ...settings.queryParams },
providerOptions: settings.providerOptions,
})
if (settings.transport === undefined || settings.transport === "http") return provider.responses(id)
if (settings.transport === "websocket") return provider.responsesWebSocket(id)
throw new Error(`Unsupported OpenAI Responses transport: ${String(settings.transport)}`)
}
@@ -1,8 +0,0 @@
import type { RouteDefaultsInput } from "../route/client"
import type { ProviderPackage } from "../provider-package"
export const defaults = (settings: ProviderPackage.Settings): RouteDefaultsInput => ({
headers: settings.headers === undefined ? undefined : { ...settings.headers },
limits: settings.limits,
http: settings.body === undefined ? undefined : { body: { ...settings.body } },
})
+26 -1
View File
@@ -1,5 +1,6 @@
import type { RouteDefaultsInput } from "../route/client"
import { Auth } from "../route/auth"
import type { ProviderPackage } from "../provider-package"
import { ProviderID, type ModelID } from "../schema"
import * as BedrockConverse from "../protocols/bedrock-converse"
import type { BedrockCredentials } from "../protocols/bedrock-converse"
@@ -15,6 +16,15 @@ export type Config = RouteDefaultsInput & {
/** Override the computed `https://bedrock-runtime.<region>.amazonaws.com` URL. */
readonly baseURL?: string
}
export interface Settings extends ProviderPackage.Settings {
readonly apiKey?: string
readonly auth?: "bearer" | "sigv4"
readonly baseURL?: string
readonly credentials?: BedrockCredentials
readonly region?: string
readonly topP?: number
}
export const routes = [BedrockConverse.route]
const bedrockBaseURL = (region: string) => `https://bedrock-runtime.${region}.amazonaws.com`
@@ -40,4 +50,19 @@ export const configure = (input: Config = {}) => {
}
export const provider = configure()
export const model = provider.model
export const model: ProviderPackage.Definition<Settings>["model"] = (id, settings) => {
if (settings.auth === "bearer" && settings.apiKey === undefined)
throw new Error("Amazon Bedrock bearer auth requires apiKey")
if (settings.auth === "sigv4" && settings.apiKey !== undefined)
throw new Error("Amazon Bedrock SigV4 auth does not accept apiKey")
return configure({
apiKey: settings.auth === "sigv4" ? undefined : settings.apiKey,
baseURL: settings.baseURL,
credentials: settings.credentials,
generation: settings.topP === undefined ? undefined : { topP: settings.topP },
headers: settings.headers === undefined ? undefined : { ...settings.headers },
http: settings.body === undefined ? undefined : { body: { ...settings.body } },
limits: settings.limits,
region: settings.region,
}).model(id)
}
+14 -1
View File
@@ -1,6 +1,7 @@
import type { RouteDefaultsInput } from "../route/client"
import { Auth } from "../route/auth"
import type { ProviderAuthOption } from "../route/auth-options"
import type { ProviderPackage } from "../provider-package"
import { ProviderID, type ModelID } from "../schema"
import * as AnthropicMessages from "../protocols/anthropic-messages"
@@ -10,6 +11,11 @@ export const routes = [AnthropicMessages.route]
export type Config = RouteDefaultsInput & ProviderAuthOption<"optional"> & { readonly baseURL?: string }
export interface Settings extends ProviderPackage.Settings {
readonly apiKey?: string
readonly baseURL?: string
}
const auth = (options: ProviderAuthOption<"optional">) => {
if ("auth" in options && options.auth) return options.auth
return Auth.optional("apiKey" in options ? options.apiKey : undefined, "apiKey")
@@ -32,4 +38,11 @@ export const configure = (input: Config = {}) => {
}
export const provider = configure()
export const model = provider.model
export const model: ProviderPackage.Definition<Settings>["model"] = (id, settings) =>
configure({
apiKey: settings.apiKey,
baseURL: settings.baseURL,
headers: settings.headers === undefined ? undefined : { ...settings.headers },
http: settings.body === undefined ? undefined : { body: { ...settings.body } },
limits: settings.limits,
}).model(id)
@@ -2,6 +2,7 @@ import { ProviderID, type ModelID } from "../schema"
import * as OpenAICompatibleChat from "../protocols/openai-compatible-chat"
import type { RouteDefaultsInput } from "../route/client"
import { AuthOptions, type ProviderAuthOption } from "../route/auth-options"
import type { ProviderPackage } from "../provider-package"
import { profiles, type OpenAICompatibleProfile } from "./openai-compatible-profile"
export const id = ProviderID.make("openai-compatible")
@@ -12,6 +13,12 @@ type GenericModelOptions = RouteDefaultsInput &
readonly baseURL: string
}
export interface Settings extends ProviderPackage.Settings {
readonly apiKey?: string
readonly baseURL: string
readonly provider?: string
}
export type FamilyModelOptions = RouteDefaultsInput &
ProviderAuthOption<"optional"> & {
readonly baseURL?: string
@@ -56,6 +63,16 @@ export const provider = {
configure,
}
export const model: ProviderPackage.Definition<Settings>["model"] = (id, settings) =>
configure({
apiKey: settings.apiKey,
baseURL: settings.baseURL,
headers: settings.headers === undefined ? undefined : { ...settings.headers },
http: settings.body === undefined ? undefined : { body: { ...settings.body } },
limits: settings.limits,
provider: settings.provider,
}).model(id)
export const baseten = define(profiles.baseten)
export const cerebras = define(profiles.cerebras)
export const deepinfra = define(profiles.deepinfra)
+28 -1
View File
@@ -1,5 +1,6 @@
import { AuthOptions, type ProviderAuthOption } from "../route/auth-options"
import type { Route, RouteDefaultsInput } from "../route/client"
import type { ProviderPackage } from "../provider-package"
import { ProviderID, type ModelID } from "../schema"
import * as OpenAIChat from "../protocols/openai-chat"
import * as OpenAIResponses from "../protocols/openai-responses"
@@ -21,6 +22,14 @@ export type Config = RouteDefaultsInput &
readonly providerOptions?: OpenAIProviderOptionsInput
}
export interface Settings extends ProviderPackage.Settings {
readonly apiKey?: string
readonly baseURL?: string
readonly queryParams?: Readonly<Record<string, string>>
readonly transport?: "http" | "websocket"
readonly providerOptions?: OpenAIProviderOptionsInput
}
const auth = (options: ProviderAuthOption<"optional">) => AuthOptions.bearer(options, "OPENAI_API_KEY")
const defaults = (input: Config) => {
@@ -57,7 +66,25 @@ export const configure = (input: Config = {}) => {
export const provider = configure()
export const model = provider.model
const config = (settings: Settings): Config => ({
apiKey: settings.apiKey,
baseURL: settings.baseURL,
headers: settings.headers === undefined ? undefined : { ...settings.headers },
http: settings.body === undefined ? undefined : { body: { ...settings.body } },
limits: settings.limits,
providerOptions: settings.providerOptions,
queryParams: settings.queryParams === undefined ? undefined : { ...settings.queryParams },
})
export const model: ProviderPackage.Definition<Settings>["model"] = (id, settings) => {
const configured = configure(config(settings))
if (settings.transport === undefined || settings.transport === "http") return configured.responses(id)
if (settings.transport === "websocket") return configured.responsesWebSocket(id)
throw new Error(`Unsupported OpenAI Responses transport: ${String(settings.transport)}`)
}
export const chatModel: ProviderPackage.Definition<Settings>["model"] = (id, settings) =>
configure(config(settings)).chat(id)
export const responses = provider.responses
export const responsesWebSocket = provider.responsesWebSocket
export const chat = provider.chat
@@ -0,0 +1,2 @@
export { chatModel as model } from "../openai"
export type { Settings } from "../openai"
@@ -0,0 +1,2 @@
export { model } from "../openai"
export type { Settings } from "../openai"
-1
View File
@@ -30,7 +30,6 @@ describe("public exports", () => {
test("provider barrels expose user-facing facades", () => {
expect(OpenAI.model).toBeFunction()
expect(OpenAI.provider.model).toBe(OpenAI.model)
expect(OpenAI.provider.responses).toBe(OpenAI.responses)
expect(OpenAI.provider.responsesWebSocket).toBe(OpenAI.responsesWebSocket)
expect(OpenAI.configure({ apiKey: "fixture" }).responses).toBeFunction()
@@ -8,16 +8,12 @@ describe("provider package entrypoints", () => {
import("@opencode-ai/llm/providers/openai/responses"),
import("@opencode-ai/llm/providers/openai/chat"),
import("@opencode-ai/llm/providers/anthropic"),
import("@opencode-ai/llm/providers/anthropic/messages"),
import("@opencode-ai/llm/providers/openai-compatible"),
import("@opencode-ai/llm/providers/openai-compatible/chat"),
import("@opencode-ai/llm/providers/amazon-bedrock"),
])
for (const module of modules) expect(module.model).toBeFunction()
expect(modules[0].model).toBe(modules[1].model)
expect(modules[3].model).toBe(modules[4].model)
expect(modules[5].model).toBe(modules[6].model)
})
test("maps package settings onto the executable model", () => {