refactor(core): isolate AI SDK native mappings (#39761)

This commit is contained in:
Aiden Cline 2026-07-30 18:38:29 -05:00 committed by GitHub
parent 7814568ba0
commit cc1289048e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 67 additions and 26 deletions

View file

@ -1,6 +1,7 @@
import type { Model, ProviderOptions } from "./schema"
export interface Settings extends Readonly<Record<string, unknown>> {
readonly baseURL?: string
readonly headers?: Readonly<Record<string, string>>
readonly body?: Readonly<Record<string, unknown>>
readonly limits?: {

View file

@ -0,0 +1,59 @@
export * as AISDKNative from "./aisdk-native"
export interface Mapping {
readonly package: string
readonly settings: Readonly<Record<string, unknown>>
}
export function map(packageName: string | undefined, settings: Readonly<Record<string, unknown>>): Mapping | undefined {
const baseSettings = mapBaseSettings(settings)
switch (packageName) {
case "@ai-sdk/google":
return {
package: "@opencode-ai/ai/providers/google",
settings: {
...baseSettings,
...mapAPIKey(settings),
...mapProviderOptions("gemini", settings),
},
}
case "@openrouter/ai-sdk-provider":
return {
package: "@opencode-ai/ai/providers/openrouter",
settings: {
...baseSettings,
...mapAPIKey(settings),
...mapProviderOptions("openrouter", settings),
},
}
case "@ai-sdk/xai":
return {
package: "@opencode-ai/ai/providers/xai",
settings: {
...baseSettings,
...mapAPIKey(settings),
...mapProviderOptions("xai", settings),
},
}
}
}
function mapBaseSettings(settings: Readonly<Record<string, unknown>>) {
return {
...(typeof settings.baseURL === "string" ? { baseURL: settings.baseURL } : {}),
}
}
function mapAPIKey(settings: Readonly<Record<string, unknown>>) {
return typeof settings.apiKey === "string" ? { apiKey: settings.apiKey } : {}
}
function mapProviderOptions(namespace: string, settings: Readonly<Record<string, unknown>>) {
const values = Object.fromEntries(
Object.entries(settings).filter(
([key]) => !["apiKey", "authToken", "baseURL", "chunkTimeout", "fetch", "timeout"].includes(key),
),
)
if (Object.keys(values).length === 0) return {}
return { providerOptions: { [namespace]: values } }
}

View file

@ -12,6 +12,7 @@ import { Auth, type AnyRoute } from "@opencode-ai/ai/route"
import { Context, Effect, Layer, Schema } from "effect"
import { produce } from "immer"
import { AISDK } from "./aisdk"
import { AISDKNative } from "./aisdk-native"
import { Catalog } from "./catalog"
import { Credential } from "./credential"
import { Integration } from "./integration"
@ -182,8 +183,10 @@ export const fromCatalogModel = (
.model({ id: resolved.modelID ?? resolved.id, compatibility: resolved.compatibility }),
)
}
const native = Provider.isAISDK(resolved.package) ? nativePackage(packageName) : resolved.package
if (Provider.isAISDK(resolved.package) && !native) {
const configured = { ...resolved.settings, ...credential?.metadata }
const mapping = Provider.isAISDK(resolved.package) ? AISDKNative.map(packageName, configured) : undefined
const native = mapping?.package ?? resolved.package
if (Provider.isAISDK(resolved.package) && !mapping) {
if (!dependencies?.loadAISDK) return Effect.fail(unsupported(resolved))
const runtime = produce(resolved, (draft) => {
draft.settings = Provider.mergeOverlay(draft.settings, {
@ -201,15 +204,13 @@ export const fromCatalogModel = (
const module = yield* (dependencies?.loadPackage ?? Provider.loadPackage)(specifier).pipe(
Effect.mapError(() => unsupported(resolved)),
)
const configured = { ...resolved.settings, ...credential?.metadata }
const providerOptions = nativeProviderOptions(packageName, configured)
const mapped = mapping?.settings ?? configured
const settings = {
...(credential ? withoutNativeAuthSettings(configured) : configured),
...(credential ? withoutNativeAuthSettings(mapped) : mapped),
...nativeCredentialSettings(specifier, credential),
headers: resolved.headers,
body: resolved.body,
limits: { context: resolved.limit.context, output: resolved.limit.output },
...(providerOptions ? { providerOptions } : {}),
}
return yield* Effect.try({
try: () => {
@ -226,26 +227,6 @@ export const fromCatalogModel = (
})
}
const nativePackage = (packageName: string | undefined) => {
if (packageName === "@ai-sdk/google") return "@opencode-ai/ai/providers/google"
if (packageName === "@openrouter/ai-sdk-provider") return "@opencode-ai/ai/providers/openrouter"
if (packageName === "@ai-sdk/xai") return "@opencode-ai/ai/providers/xai"
return undefined
}
const nativeProviderOptions = (packageName: string | undefined, settings: Readonly<Record<string, unknown>>) => {
const values = Object.fromEntries(
Object.entries(settings).filter(
([key]) => !["apiKey", "authToken", "baseURL", "chunkTimeout", "fetch", "timeout"].includes(key),
),
)
if (Object.keys(values).length === 0) return undefined
if (packageName === "@ai-sdk/google") return { gemini: values }
if (packageName === "@openrouter/ai-sdk-provider") return { openrouter: values }
if (packageName === "@ai-sdk/xai") return { xai: values }
return undefined
}
const isNativeOpenAI = (packageName: string | undefined) =>
packageName === "@opencode-ai/ai/providers/openai" ||
packageName?.startsWith("@opencode-ai/ai/providers/openai/") === true