feat(ai): support custom reasoning fields (#38227)
This commit is contained in:
parent
69c05ae3fc
commit
23483ea013
24 changed files with 306 additions and 67 deletions
|
|
@ -344,7 +344,12 @@ function modelFromLanguage(info: ModelV2.Info, language: LanguageModelV3) {
|
|||
prepareTransport: (body) => Effect.succeed(body),
|
||||
streamPrepared: (prepared) => streamLanguage(language, prepared as LanguageModelV3CallOptions),
|
||||
}
|
||||
return Model.make({ id: info.modelID ?? info.id, provider: info.providerID, route })
|
||||
return Model.make({
|
||||
id: info.modelID ?? info.id,
|
||||
provider: info.providerID,
|
||||
route,
|
||||
compatibility: info.compatibility,
|
||||
})
|
||||
}
|
||||
|
||||
function gatewayProviderOptions(modelID: ModelV2.ID, settings: Readonly<Record<string, unknown>>) {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
|||
import { Money } from "@opencode-ai/schema/money"
|
||||
import { Effect, Stream } from "effect"
|
||||
import { Config } from "../../config"
|
||||
import { ModelV2 } from "../../model"
|
||||
import { ProviderV2 } from "../../provider"
|
||||
|
||||
export const Plugin = define({
|
||||
|
|
@ -59,6 +58,8 @@ export const Plugin = define({
|
|||
if (config.family !== undefined) model.family = config.family
|
||||
if (config.name !== undefined) model.name = config.name
|
||||
if (config.modelID !== undefined) model.modelID = config.modelID
|
||||
if (config.compatibility !== undefined)
|
||||
model.compatibility = { ...model.compatibility, ...config.compatibility }
|
||||
if (config.package !== undefined) model.package = config.package
|
||||
if (config.settings !== undefined)
|
||||
model.settings = ProviderV2.mergeOverlay(model.settings, config.settings)
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ class Model extends Schema.Class<Model>("ConfigV2.Model")({
|
|||
modelID: ModelV2.ID.pipe(Schema.optional),
|
||||
family: ModelV2.Family.pipe(Schema.optional),
|
||||
name: Schema.String.pipe(Schema.optional),
|
||||
compatibility: ModelV2.Compatibility.pipe(Schema.optional),
|
||||
package: Schema.String.pipe(Schema.optional),
|
||||
...Overlays,
|
||||
capabilities: ModelV2.Capabilities.pipe(Schema.optional),
|
||||
|
|
|
|||
|
|
@ -12,6 +12,12 @@ export type VariantID = typeof VariantID.Type
|
|||
export const Family = Model.Family
|
||||
export type Family = Model.Family
|
||||
|
||||
export const ReasoningField = Model.ReasoningField
|
||||
export type ReasoningField = Model.ReasoningField
|
||||
|
||||
export const Compatibility = Model.Compatibility
|
||||
export type Compatibility = Model.Compatibility
|
||||
|
||||
export const Capabilities = Model.Capabilities
|
||||
export type Capabilities = Model.Capabilities
|
||||
|
||||
|
|
@ -25,6 +31,12 @@ export type Info = Model.Info
|
|||
|
||||
export type MutableInfo = DeepMutable<Info>
|
||||
|
||||
export function compatibility(input: unknown): Compatibility | undefined {
|
||||
if (typeof input === "string") return { reasoningField: input }
|
||||
if (typeof input !== "object" || input === null || Array.isArray(input) || !("field" in input)) return undefined
|
||||
return typeof input.field === "string" ? { reasoningField: input.field } : undefined
|
||||
}
|
||||
|
||||
export function parse(input: string): { providerID: ProviderV2.ID; modelID: ID } {
|
||||
const [providerID, ...modelID] = input.split("/")
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ type SourceModel = {
|
|||
readonly reasoning_options?: readonly ReasoningOption[]
|
||||
readonly temperature?: boolean
|
||||
readonly tool_call: boolean
|
||||
readonly interleaved?: true | { readonly field: "reasoning" | "reasoning_content" | "reasoning_details" }
|
||||
readonly interleaved?: boolean | string | { readonly field: string }
|
||||
readonly cost?: Cost
|
||||
readonly limit: { readonly context: number; readonly input?: number; readonly output: number }
|
||||
readonly modalities?: { readonly input: readonly Modality[]; readonly output: readonly Modality[] }
|
||||
|
|
@ -495,6 +495,7 @@ function modelInfo(
|
|||
modelID: ModelV2.ID.make(model.id),
|
||||
providerID,
|
||||
name: input.name ?? model.name,
|
||||
compatibility: ModelV2.compatibility(model.interleaved),
|
||||
family: model.family ? ModelV2.Family.make(model.family) : undefined,
|
||||
package: model.provider?.npm ? ProviderV2.aisdk(model.provider.npm) : undefined,
|
||||
settings: model.provider?.api ? { baseURL: model.provider.api } : undefined,
|
||||
|
|
|
|||
|
|
@ -134,6 +134,7 @@ export const OpencodePlugin = define<HttpClient.HttpClient | EventV2.Service | S
|
|||
if (config.family !== undefined) model.family = config.family
|
||||
if (config.name !== undefined) model.name = config.name
|
||||
if (config.id !== undefined) model.modelID = config.id
|
||||
model.compatibility = ModelV2.compatibility(config.interleaved) ?? model.compatibility
|
||||
if (config.provider !== undefined) {
|
||||
model.package = config.provider.npm ? ProviderV2.aisdk(config.provider.npm) : undefined
|
||||
if (config.provider.api) model.settings = { ...model.settings, baseURL: config.provider.api }
|
||||
|
|
|
|||
|
|
@ -209,14 +209,14 @@ export const fromCatalogModel = (
|
|||
return Effect.succeed(
|
||||
withDefaults(resolved, OpenAIResponses.route)
|
||||
.with({ auth: key === undefined ? Auth.none : Auth.bearer(key) })
|
||||
.model({ id: resolved.modelID ?? resolved.id }),
|
||||
.model({ id: resolved.modelID ?? resolved.id, compatibility: resolved.compatibility }),
|
||||
)
|
||||
}
|
||||
if (ProviderV2.isAISDK(resolved.package) && packageName === "@ai-sdk/anthropic") {
|
||||
return Effect.succeed(
|
||||
withDefaults(resolved, AnthropicMessages.route)
|
||||
.with({ auth: key === undefined ? Auth.none : Auth.header("x-api-key", key) })
|
||||
.model({ id: resolved.modelID ?? resolved.id }),
|
||||
.model({ id: resolved.modelID ?? resolved.id, compatibility: resolved.compatibility }),
|
||||
)
|
||||
}
|
||||
if (
|
||||
|
|
@ -227,7 +227,7 @@ export const fromCatalogModel = (
|
|||
return Effect.succeed(
|
||||
withDefaults(resolved, OpenAICompatibleChat.route)
|
||||
.with({ auth: key === undefined ? Auth.none : Auth.bearer(key) })
|
||||
.model({ id: resolved.modelID ?? resolved.id }),
|
||||
.model({ id: resolved.modelID ?? resolved.id, compatibility: resolved.compatibility }),
|
||||
)
|
||||
}
|
||||
if (ProviderV2.isAISDK(resolved.package)) {
|
||||
|
|
@ -257,8 +257,15 @@ export const fromCatalogModel = (
|
|||
limits: { context: resolved.limit.context, output: resolved.limit.output },
|
||||
}
|
||||
return yield* Effect.try({
|
||||
try: () =>
|
||||
Model.update(module.model(resolved.modelID ?? resolved.id, settings), { provider: resolved.providerID }),
|
||||
try: () => {
|
||||
const runtime = module.model(resolved.modelID ?? resolved.id, settings)
|
||||
return Model.update(runtime, {
|
||||
provider: resolved.providerID,
|
||||
compatibility: resolved.compatibility
|
||||
? { ...runtime.compatibility, ...resolved.compatibility }
|
||||
: runtime.compatibility,
|
||||
})
|
||||
},
|
||||
catch: () => unsupported(resolved),
|
||||
})
|
||||
})
|
||||
|
|
@ -302,7 +309,7 @@ const codexModel = (
|
|||
account === undefined ? Auth.none : Auth.headers({ "chatgpt-account-id": account }),
|
||||
),
|
||||
})
|
||||
.model({ id: model.modelID ?? model.id })
|
||||
.model({ id: model.modelID ?? model.id, compatibility: model.compatibility })
|
||||
}
|
||||
|
||||
const unsupported = (model: ModelV2.Info) =>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { ConfigPermissionV1 } from "./permission"
|
|||
import { ConfigProviderV1 } from "./provider"
|
||||
import { ConfigProviderOptionsV1 } from "./provider-options"
|
||||
import { ProviderV2 } from "../../provider"
|
||||
import { ModelV2 } from "../../model"
|
||||
|
||||
const keys = new Set([
|
||||
"logLevel",
|
||||
|
|
@ -278,6 +279,7 @@ function migrateModel(info: typeof ConfigProviderV1.Model.Type) {
|
|||
modelID: info.id,
|
||||
family: info.family,
|
||||
name: info.name,
|
||||
compatibility: ModelV2.compatibility(info.interleaved),
|
||||
package: info.provider?.npm ? ProviderV2.aisdk(info.provider.npm) : undefined,
|
||||
settings: info.provider?.api ? { ...settings, baseURL: info.provider.api } : settings,
|
||||
capabilities,
|
||||
|
|
|
|||
|
|
@ -16,9 +16,10 @@ export const Model = Schema.Struct({
|
|||
tool_call: Schema.optional(Schema.Boolean),
|
||||
interleaved: Schema.optional(
|
||||
Schema.Union([
|
||||
Schema.Literal(true),
|
||||
Schema.Boolean,
|
||||
Schema.String,
|
||||
Schema.Struct({
|
||||
field: Schema.Literals(["reasoning", "reasoning_content", "reasoning_details"]),
|
||||
field: Schema.String,
|
||||
}),
|
||||
]),
|
||||
),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue