refactor(core): remove ai sdk option fields (#30581)
This commit is contained in:
parent
7f8412ec3e
commit
1520b0de20
57 changed files with 825 additions and 686 deletions
|
|
@ -5,6 +5,7 @@ import { ConfigAgentV1 } from "./agent"
|
|||
import { ConfigMCPV1 } from "./mcp"
|
||||
import { ConfigPermissionV1 } from "./permission"
|
||||
import { ConfigProviderV1 } from "./provider"
|
||||
import { ConfigProviderOptionsV1 } from "./provider-options"
|
||||
|
||||
const keys = new Set([
|
||||
"logLevel",
|
||||
|
|
@ -99,7 +100,7 @@ function agents(info: typeof ConfigV1.Info.Type) {
|
|||
...Object.entries(info.mode ?? {}).map(([name, agent]) => [name, { ...agent, mode: "primary" as const }] as const),
|
||||
]
|
||||
if (!entries.length) return undefined
|
||||
return Object.fromEntries(entries.map(([name, agent]) => [name, migrateAgent(agent)]))
|
||||
return Object.fromEntries(entries.flatMap(([name, agent]) => (agent ? [[name, migrateAgent(agent)]] : [])))
|
||||
}
|
||||
|
||||
function migrateAgent(info: ConfigAgentV1.Info) {
|
||||
|
|
@ -111,7 +112,7 @@ function migrateAgent(info: ConfigAgentV1.Info) {
|
|||
return {
|
||||
model: info.model,
|
||||
variant: info.variant,
|
||||
options: Object.keys(body).length ? { body } : undefined,
|
||||
request: Object.keys(body).length ? { body } : undefined,
|
||||
system: info.prompt,
|
||||
description: info.description,
|
||||
mode: info.mode,
|
||||
|
|
@ -160,22 +161,27 @@ function providers(info?: Readonly<Record<string, ConfigProviderV1.Info>>) {
|
|||
}
|
||||
|
||||
function migrateProvider(info: ConfigProviderV1.Info) {
|
||||
const lowerer = ConfigProviderOptionsV1.get(info.npm)
|
||||
const options = lowerer.provider(info.options ?? {})
|
||||
return {
|
||||
name: info.name,
|
||||
env: info.env,
|
||||
endpoint: info.npm && {
|
||||
type: "aisdk" as const,
|
||||
package: info.npm,
|
||||
url: info.api ?? (typeof info.options?.baseURL === "string" ? info.options.baseURL : undefined),
|
||||
},
|
||||
options: info.options && { body: info.options },
|
||||
api: info.npm
|
||||
? {
|
||||
type: "aisdk" as const,
|
||||
package: info.npm,
|
||||
url: info.api ?? options.url,
|
||||
settings: options.settings ?? {},
|
||||
}
|
||||
: undefined,
|
||||
request: info.options && { headers: options.headers, body: options.body },
|
||||
models:
|
||||
info.models &&
|
||||
Object.fromEntries(Object.entries(info.models).map(([name, model]) => [name, migrateModel(model)])),
|
||||
Object.fromEntries(Object.entries(info.models).map(([name, model]) => [name, migrateModel(model, info.npm)])),
|
||||
}
|
||||
}
|
||||
|
||||
function migrateModel(info: typeof ConfigProviderV1.Model.Type) {
|
||||
function migrateModel(info: typeof ConfigProviderV1.Model.Type, packageName?: string) {
|
||||
const costs = info.cost && [
|
||||
{
|
||||
input: info.cost.input,
|
||||
|
|
@ -197,16 +203,33 @@ function migrateModel(info: typeof ConfigProviderV1.Model.Type) {
|
|||
info.tool_call !== undefined || info.modalities?.input !== undefined || info.modalities?.output !== undefined
|
||||
? { tools: info.tool_call ?? false, input: info.modalities?.input ?? [], output: info.modalities?.output ?? [] }
|
||||
: undefined
|
||||
const lowerer = ConfigProviderOptionsV1.get(info.provider?.npm ?? packageName)
|
||||
return {
|
||||
api_id: info.id,
|
||||
family: info.family,
|
||||
name: info.name,
|
||||
endpoint: info.provider?.npm && { type: "aisdk" as const, package: info.provider.npm, url: info.provider.api },
|
||||
api: info.provider?.npm
|
||||
? { type: "aisdk" as const, package: info.provider.npm, url: info.provider.api, settings: {} }
|
||||
: undefined,
|
||||
capabilities,
|
||||
options: (info.headers || info.options) && { headers: info.headers, body: info.options },
|
||||
variants: info.variants && Object.entries(info.variants).map(([id, options]) => ({ id, body: options })),
|
||||
request: (info.headers || info.options) && {
|
||||
headers: info.headers,
|
||||
body: info.options && lowerer.request(info.options),
|
||||
},
|
||||
variants:
|
||||
info.variants &&
|
||||
Object.entries(info.variants).map(([id, options]) => ({ id, body: lowerer.request(options) })),
|
||||
cost: costs,
|
||||
disabled: info.status === "deprecated" ? true : undefined,
|
||||
limit: info.limit,
|
||||
limit:
|
||||
info.limit && {
|
||||
context: int(info.limit.context),
|
||||
input: info.limit.input === undefined ? undefined : int(info.limit.input),
|
||||
output: int(info.limit.output),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function int(value: number) {
|
||||
return Math.max(Number.MIN_SAFE_INTEGER, Math.min(Number.MAX_SAFE_INTEGER, Math.trunc(value)))
|
||||
}
|
||||
|
|
|
|||
210
packages/core/src/v1/config/provider-options.ts
Normal file
210
packages/core/src/v1/config/provider-options.ts
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
export * as ConfigProviderOptionsV1 from "./provider-options"
|
||||
|
||||
type Options = Readonly<Record<string, unknown>>
|
||||
|
||||
export interface ProviderResult {
|
||||
readonly headers?: Record<string, string>
|
||||
readonly body?: Record<string, unknown>
|
||||
readonly url?: string
|
||||
readonly settings?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface Lowerer {
|
||||
readonly provider: (options: Options) => ProviderResult
|
||||
readonly request: (options: Options) => Record<string, unknown>
|
||||
}
|
||||
|
||||
export function get(packageName?: string): Lowerer {
|
||||
const key = packageName ?? ""
|
||||
return Object.hasOwn(lowerers, key) ? lowerers[key]! : raw
|
||||
}
|
||||
|
||||
const raw: Lowerer = {
|
||||
provider(options) {
|
||||
return { body: clone(options) }
|
||||
},
|
||||
request: clone,
|
||||
}
|
||||
|
||||
const openai: Lowerer = {
|
||||
provider(options) {
|
||||
return {
|
||||
url: string(options.baseURL),
|
||||
headers: compact({
|
||||
Authorization: bearer(options.apiKey),
|
||||
"OpenAI-Organization": string(options.organization),
|
||||
"OpenAI-Project": string(options.project),
|
||||
...headers(options.headers),
|
||||
}),
|
||||
body: body(options.body),
|
||||
settings: omit(options, ["apiKey", "baseURL", "organization", "project", "headers", "body"]),
|
||||
}
|
||||
},
|
||||
request: snake,
|
||||
}
|
||||
|
||||
const anthropic: Lowerer = {
|
||||
provider(options) {
|
||||
return {
|
||||
url: string(options.baseURL),
|
||||
headers: compact({
|
||||
"x-api-key": string(options.apiKey),
|
||||
Authorization: options.authToken ? bearer(options.authToken) : undefined,
|
||||
...headers(options.headers),
|
||||
}),
|
||||
body: body(options.body),
|
||||
settings: omit(options, ["apiKey", "authToken", "baseURL", "headers", "body"]),
|
||||
}
|
||||
},
|
||||
request(options) {
|
||||
const result = snake(options)
|
||||
if (options.effort !== undefined || options.taskBudget !== undefined) {
|
||||
result.output_config = compactUnknown({ effort: options.effort, task_budget: options.taskBudget })
|
||||
delete result.effort
|
||||
delete result.task_budget
|
||||
}
|
||||
if (isRecord(options.metadata) && options.metadata.userId !== undefined) {
|
||||
result.metadata = { ...options.metadata, user_id: options.metadata.userId }
|
||||
delete (result.metadata as Record<string, unknown>).userId
|
||||
}
|
||||
return result
|
||||
},
|
||||
}
|
||||
|
||||
const google: Lowerer = {
|
||||
provider(options) {
|
||||
return {
|
||||
url: string(options.baseURL),
|
||||
headers: compact({ "x-goog-api-key": string(options.apiKey), ...headers(options.headers) }),
|
||||
body: body(options.body),
|
||||
settings: omit(options, ["apiKey", "baseURL", "headers", "body"]),
|
||||
}
|
||||
},
|
||||
request(options) {
|
||||
const generationConfig = pick(options, ["thinkingConfig", "responseModalities", "mediaResolution", "imageConfig"])
|
||||
return {
|
||||
...omit(options, ["thinkingConfig", "responseModalities", "mediaResolution", "imageConfig"]),
|
||||
...(Object.keys(generationConfig).length ? { generationConfig } : {}),
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const azure: Lowerer = {
|
||||
provider(options) {
|
||||
return {
|
||||
url: string(options.baseURL),
|
||||
headers: compact({ "api-key": string(options.apiKey), ...headers(options.headers) }),
|
||||
body: body(options.body),
|
||||
settings: omit(options, ["apiKey", "baseURL", "headers", "body"]),
|
||||
}
|
||||
},
|
||||
request: openai.request,
|
||||
}
|
||||
|
||||
const bedrock: Lowerer = {
|
||||
provider(options) {
|
||||
return direct(options)
|
||||
},
|
||||
request(options) {
|
||||
return { additionalModelRequestFields: clone(options) }
|
||||
},
|
||||
}
|
||||
|
||||
const openaiCompatible: Lowerer = {
|
||||
provider(options) {
|
||||
return { ...direct(options, ["baseURL"]), url: string(options.baseURL) }
|
||||
},
|
||||
request(options) {
|
||||
const result = clone(options)
|
||||
if (options.reasoningEffort !== undefined) {
|
||||
result.reasoning_effort = options.reasoningEffort
|
||||
delete result.reasoningEffort
|
||||
}
|
||||
return result
|
||||
},
|
||||
}
|
||||
|
||||
const lowerers: Readonly<Record<string, Lowerer>> = {
|
||||
"@ai-sdk/openai": openai,
|
||||
"@ai-sdk/anthropic": anthropic,
|
||||
"@ai-sdk/google-vertex/anthropic": anthropic,
|
||||
"@ai-sdk/google": google,
|
||||
"@ai-sdk/google-vertex": google,
|
||||
"@ai-sdk/azure": azure,
|
||||
"@ai-sdk/amazon-bedrock": bedrock,
|
||||
"@ai-sdk/openai-compatible": openaiCompatible,
|
||||
"@ai-sdk/cerebras": openaiCompatible,
|
||||
"@ai-sdk/deepinfra": openaiCompatible,
|
||||
"@ai-sdk/groq": openaiCompatible,
|
||||
"@ai-sdk/mistral": openaiCompatible,
|
||||
"@ai-sdk/togetherai": openaiCompatible,
|
||||
"@ai-sdk/xai": openaiCompatible,
|
||||
"@openrouter/ai-sdk-provider": openaiCompatible,
|
||||
"ai-gateway-provider": openaiCompatible,
|
||||
"venice-ai-sdk-provider": openaiCompatible,
|
||||
}
|
||||
|
||||
function direct(options: Options, extraKeys: ReadonlyArray<string> = []): ProviderResult {
|
||||
return {
|
||||
headers: headers(options.headers),
|
||||
body: body(options.body),
|
||||
settings: omit(options, ["headers", "body", ...extraKeys]),
|
||||
}
|
||||
}
|
||||
|
||||
function body(input: unknown) {
|
||||
if (!isRecord(input)) return undefined
|
||||
return { ...input }
|
||||
}
|
||||
|
||||
function snake(options: Options) {
|
||||
return Object.fromEntries(Object.entries(options).map(([key, value]) => [snakeKey(key), snakeValue(value)]))
|
||||
}
|
||||
|
||||
function snakeValue(value: unknown): unknown {
|
||||
if (Array.isArray(value)) return value.map(snakeValue)
|
||||
if (!isRecord(value)) return value
|
||||
return Object.fromEntries(Object.entries(value).map(([key, value]) => [snakeKey(key), snakeValue(value)]))
|
||||
}
|
||||
|
||||
function snakeKey(key: string) {
|
||||
return key.replace(/[A-Z]/g, (match) => "_" + match.toLowerCase())
|
||||
}
|
||||
|
||||
function clone(options: Options) {
|
||||
return { ...options }
|
||||
}
|
||||
|
||||
function omit(options: Options, keys: ReadonlyArray<string>) {
|
||||
return Object.fromEntries(Object.entries(options).filter(([key]) => !keys.includes(key)))
|
||||
}
|
||||
|
||||
function pick(options: Options, keys: ReadonlyArray<string>) {
|
||||
return Object.fromEntries(Object.entries(options).filter(([key]) => keys.includes(key)))
|
||||
}
|
||||
|
||||
function headers(input: unknown) {
|
||||
if (!isRecord(input)) return undefined
|
||||
return Object.fromEntries(Object.entries(input).filter((entry): entry is [string, string] => typeof entry[1] === "string"))
|
||||
}
|
||||
|
||||
function compact(input: Record<string, string | undefined>) {
|
||||
const entries = Object.entries(input).filter((entry): entry is [string, string] => entry[1] !== undefined)
|
||||
return entries.length ? Object.fromEntries(entries) : undefined
|
||||
}
|
||||
|
||||
function compactUnknown(input: Record<string, unknown>) {
|
||||
return Object.fromEntries(Object.entries(input).filter((entry) => entry[1] !== undefined))
|
||||
}
|
||||
|
||||
function string(input: unknown) {
|
||||
return typeof input === "string" && input ? input : undefined
|
||||
}
|
||||
|
||||
function bearer(input: unknown) {
|
||||
return typeof input === "string" && input ? `Bearer ${input}` : undefined
|
||||
}
|
||||
|
||||
function isRecord(input: unknown): input is Record<string, unknown> {
|
||||
return typeof input === "object" && input !== null && !Array.isArray(input)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue