mirror of
https://github.com/anomalyco/opencode.git
synced 2026-09-19 07:16:26 +00:00
Compare commits
2
Commits
prepaint
...
hook-audit
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cbd911368e | ||
|
|
8bfb247854 |
+4
-4
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"nodeModules": {
|
||||
"x86_64-linux": "sha256-TRfKunG6/UE8rQFyRkx/pX+pe7GT542IJWLEKcFFfAY=",
|
||||
"aarch64-linux": "sha256-JxCYBQoHeJVuEMEe4Ef5f6UxxUCAuhPo96jqMT047Fc=",
|
||||
"aarch64-darwin": "sha256-UEgjeQMivNC7JVsLEDlNbuqp9LJH84f9nHgLHTZ2zDI=",
|
||||
"x86_64-darwin": "sha256-jEs/Oadjf2LVAinY0xyFna0V+NTwoCKXiXmQ83OchF8="
|
||||
"x86_64-linux": "sha256-W2QBi7/jtbvLp3Gj+nR49+wqVgCB0qfnQ30C0PQOjO4=",
|
||||
"aarch64-linux": "sha256-R4YKo8JUvKd77QYehTNpj/65oodeLBQ8mdxLzBLrFdw=",
|
||||
"aarch64-darwin": "sha256-7v2ABLG6zsIMSzsYu06BWEqbLL4HOeKyZFBx/gx87SI=",
|
||||
"x86_64-darwin": "sha256-emKRoQILMzB5ko45dtMPuZGV2pAyszcwzKUh9ey9JZM="
|
||||
}
|
||||
}
|
||||
|
||||
@@ -456,6 +456,10 @@ export type V2EventServerConnected = {
|
||||
data: {}
|
||||
}
|
||||
|
||||
export type ModelSettings = { compaction?: ProviderCompaction } & { [x: string]: any }
|
||||
|
||||
export type ConfigModelSettings = { compaction?: ProviderCompaction } & { [x: string]: JsonValue | null }
|
||||
|
||||
export type ProviderSettings = {
|
||||
timeout?: number | false
|
||||
chunkTimeout?: number
|
||||
@@ -1656,19 +1660,19 @@ export type SessionInboxMove = {
|
||||
payload: SessionInboxMovePayload
|
||||
}
|
||||
|
||||
export type ModelVariant = {
|
||||
id: string
|
||||
settings?: ModelSettings
|
||||
headers?: { [x: string]: string }
|
||||
body?: { [x: string]: any }
|
||||
}
|
||||
|
||||
export type ProviderRequest = {
|
||||
settings: ProviderSettings
|
||||
headers: { [x: string]: string }
|
||||
body: { [x: string]: any }
|
||||
}
|
||||
|
||||
export type ModelVariant = {
|
||||
id: string
|
||||
settings?: ProviderSettings
|
||||
headers?: { [x: string]: string }
|
||||
body?: { [x: string]: any }
|
||||
}
|
||||
|
||||
export type ProviderInfo = {
|
||||
id: string
|
||||
canonical?: string
|
||||
@@ -1898,7 +1902,7 @@ export type ModelInfo = {
|
||||
name: string
|
||||
compatibility?: ModelCompatibility
|
||||
package?: string
|
||||
settings?: ProviderSettings
|
||||
settings?: ModelSettings
|
||||
headers?: { [x: string]: string }
|
||||
body?: { [x: string]: any }
|
||||
capabilities: ModelCapabilities
|
||||
@@ -2107,13 +2111,13 @@ export type ConfigEntry =
|
||||
name?: string
|
||||
compatibility?: ModelCompatibility
|
||||
package?: string
|
||||
settings?: ConfigProviderSettings
|
||||
settings?: ConfigModelSettings
|
||||
headers?: { [x: string]: string }
|
||||
body?: { [x: string]: JsonValue }
|
||||
capabilities?: ModelCapabilities
|
||||
variants?: Array<{
|
||||
id: string
|
||||
settings?: ConfigProviderSettings
|
||||
settings?: ConfigModelSettings
|
||||
headers?: { [x: string]: string }
|
||||
body?: { [x: string]: JsonValue }
|
||||
}>
|
||||
|
||||
@@ -34,7 +34,7 @@ import { Auth, Endpoint, RequestExecutor, type AnyRoute } from "@opencode/ai/rou
|
||||
import { ProviderShared } from "@opencode/ai/protocols/shared"
|
||||
import { Cause, Context, Effect, Layer, Option, Schema, Scope, Stream } from "effect"
|
||||
import { makeParser } from "effect/unstable/encoding/Sse"
|
||||
import type { ID, Info } from "./model.js"
|
||||
import type { ID, RuntimeInfo } from "./model.js"
|
||||
import { Provider } from "./provider.js"
|
||||
import { State } from "./state.js"
|
||||
|
||||
@@ -46,14 +46,14 @@ type ToolResultContent = Extract<AssistantContent[number], { type: "tool-result"
|
||||
const decodeJson = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Unknown))
|
||||
|
||||
export interface SDKEvent {
|
||||
readonly model: Info
|
||||
readonly model: RuntimeInfo
|
||||
readonly package: string
|
||||
readonly options: Record<string, any>
|
||||
sdk?: SDK
|
||||
}
|
||||
|
||||
export interface LanguageEvent {
|
||||
readonly model: Info
|
||||
readonly model: RuntimeInfo
|
||||
readonly sdk: SDK
|
||||
readonly options: Record<string, any>
|
||||
language?: LanguageModelV3
|
||||
@@ -116,7 +116,7 @@ function wrapSSE(res: Response, ms: number, ctl: AbortController) {
|
||||
})
|
||||
}
|
||||
|
||||
function prepareOptions(model: Info, pkg: string) {
|
||||
function prepareOptions(model: RuntimeInfo, pkg: string) {
|
||||
const projected = mapBodyToProviderOptions(model, pkg)
|
||||
const options: Record<string, any> = {
|
||||
name: model.canonical ?? model.providerID,
|
||||
@@ -182,8 +182,8 @@ export interface Interface {
|
||||
}
|
||||
readonly runSDK: (event: SDKEvent) => Effect.Effect<SDKEvent>
|
||||
readonly runLanguage: (event: LanguageEvent) => Effect.Effect<LanguageEvent>
|
||||
readonly language: (model: Info) => Effect.Effect<LanguageModelV3, InitError>
|
||||
readonly model: (model: Info) => Effect.Effect<LanguageModel, InitError>
|
||||
readonly language: (model: RuntimeInfo) => Effect.Effect<LanguageModelV3, InitError>
|
||||
readonly model: (model: RuntimeInfo) => Effect.Effect<LanguageModel, InitError>
|
||||
}
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/AISDK") {}
|
||||
@@ -302,7 +302,7 @@ export const locationLayer = Layer.effect(
|
||||
}),
|
||||
)
|
||||
|
||||
function modelFromLanguage(info: Info, language: LanguageModelV3) {
|
||||
function modelFromLanguage(info: RuntimeInfo, language: LanguageModelV3) {
|
||||
const packageName = Provider.packageName(info.package!)
|
||||
const projected = mapBodyToProviderOptions(info, packageName)
|
||||
const providerID = info.canonical ?? info.providerID
|
||||
@@ -399,7 +399,7 @@ function requestSettings(settings: Readonly<Record<string, unknown>> | undefined
|
||||
return Object.keys(result).length === 0 ? undefined : result
|
||||
}
|
||||
|
||||
function mapBodyToProviderOptions(model: Info, packageName: string) {
|
||||
function mapBodyToProviderOptions(model: RuntimeInfo, packageName: string) {
|
||||
const settings = requestSettings(model.settings)
|
||||
const pro = Schema.is(Schema.Struct({ mode: Schema.Literal("pro") }))(model.body?.reasoning)
|
||||
const forceReasoning =
|
||||
|
||||
@@ -8,6 +8,7 @@ import { AISDK } from "./aisdk.js"
|
||||
import { Credential } from "./credential.js"
|
||||
import { Integration } from "./integration.js"
|
||||
import { Capabilities, ID, Info, Model, Ref, VariantID } from "./model.js"
|
||||
import type { RuntimeInfo } from "./model.js"
|
||||
import { Npm } from "@opencode/util/npm"
|
||||
import { Provider } from "./provider.js"
|
||||
|
||||
@@ -117,9 +118,9 @@ export interface Resolved {
|
||||
readonly cost: Info["cost"]
|
||||
/** Catalog token limits used by Core for context management. */
|
||||
readonly limit: Info["limit"]
|
||||
/** Model policy overrides the provider policy; omitted means local compaction. */
|
||||
/** Model policy overrides the provider policy; omitted means summary compaction. */
|
||||
readonly compaction?: Provider.Compaction
|
||||
/** Model transport overrides the provider transport; omitted means HTTP. */
|
||||
/** Provider transport policy; omitted means HTTP. */
|
||||
readonly transport?: Provider.Transport
|
||||
}
|
||||
|
||||
@@ -149,7 +150,7 @@ export const withVariant = (
|
||||
variant
|
||||
? {
|
||||
...model,
|
||||
settings: Provider.mergeOverlay(model.settings, variant.settings),
|
||||
settings: Provider.mergeOverlay(model.settings, Provider.modelSettings(variant.settings)),
|
||||
headers: Provider.mergeHeaders(model.headers, variant.headers),
|
||||
body: Provider.mergeOverlay(model.body, variant.body),
|
||||
}
|
||||
@@ -159,11 +160,11 @@ export const withVariant = (
|
||||
|
||||
export interface Dependencies {
|
||||
readonly loadPackage?: (specifier: string) => Effect.Effect<Provider.ProviderPackage, Provider.LoadError>
|
||||
readonly loadAISDK?: (model: Info) => Effect.Effect<LanguageModel, AISDK.InitError>
|
||||
readonly loadAISDK?: (model: RuntimeInfo) => Effect.Effect<LanguageModel, AISDK.InitError>
|
||||
}
|
||||
|
||||
export const fromCatalogModel = (
|
||||
model: Info,
|
||||
model: RuntimeInfo,
|
||||
credential?: Credential.Value,
|
||||
dependencies?: Dependencies,
|
||||
): Effect.Effect<
|
||||
@@ -191,7 +192,7 @@ export const fromCatalogModel = (
|
||||
)
|
||||
|
||||
const resolveCatalogModel = Effect.fn("ModelResolver.resolveCatalogModel")(function* (
|
||||
model: Info,
|
||||
model: RuntimeInfo,
|
||||
credential?: Credential.Value,
|
||||
dependencies?: Dependencies,
|
||||
) {
|
||||
@@ -248,7 +249,7 @@ const resolveCatalogModel = Effect.fn("ModelResolver.resolveCatalogModel")(funct
|
||||
})
|
||||
})
|
||||
|
||||
function prepareRuntimeModel(model: Info, credential: Credential.Value | undefined) {
|
||||
function prepareRuntimeModel(model: RuntimeInfo, credential: Credential.Value | undefined) {
|
||||
if (model.settings?.apiKey !== "" && (credential?.type !== "key" || credential.metadata === undefined)) return model
|
||||
return {
|
||||
...model,
|
||||
@@ -260,7 +261,7 @@ function prepareRuntimeModel(model: Info, credential: Credential.Value | undefin
|
||||
}
|
||||
|
||||
function validateProviderVariables(
|
||||
model: Info,
|
||||
model: RuntimeInfo,
|
||||
resolved: LanguageModel,
|
||||
): Effect.Effect<LanguageModel, UnresolvedProviderVariablesError> {
|
||||
const baseURL = resolved.route.endpoint.baseURL
|
||||
@@ -270,7 +271,7 @@ function validateProviderVariables(
|
||||
}
|
||||
|
||||
function prepareProviderSettings(
|
||||
model: Info,
|
||||
model: RuntimeInfo,
|
||||
settings: Readonly<Record<string, unknown>>,
|
||||
): Effect.Effect<Readonly<Record<string, unknown>>, UnresolvedProviderVariablesError> {
|
||||
const baseURL = settings.baseURL
|
||||
@@ -280,14 +281,14 @@ function prepareProviderSettings(
|
||||
)
|
||||
}
|
||||
|
||||
function prepareProviderURL(model: Info, baseURL: string): Effect.Effect<string, UnresolvedProviderVariablesError> {
|
||||
function prepareProviderURL(model: RuntimeInfo, baseURL: string): Effect.Effect<string, UnresolvedProviderVariablesError> {
|
||||
if (!baseURL.includes("${")) return Effect.succeed(baseURL)
|
||||
const prepared = baseURL.replace(/\$\{([^}]+)\}/g, (placeholder, name: string) => process.env[name] ?? placeholder)
|
||||
const failure = unresolvedProviderVariables(model, prepared)
|
||||
return failure ? Effect.fail(failure) : Effect.succeed(prepared)
|
||||
}
|
||||
|
||||
function unresolvedProviderVariables(model: Info, baseURL: string) {
|
||||
function unresolvedProviderVariables(model: RuntimeInfo, baseURL: string) {
|
||||
const variables = new Set(Array.from(baseURL.matchAll(/\$\{([^}]+)\}/g), (match) => match[1]))
|
||||
if (variables.size === 0) return
|
||||
return new UnresolvedProviderVariablesError({
|
||||
@@ -310,14 +311,14 @@ const nativeCredentialSettings = (specifier: string, credential: Credential.Valu
|
||||
return { apiKey: credential.access }
|
||||
}
|
||||
|
||||
const unsupported = (model: Info) =>
|
||||
const unsupported = (model: RuntimeInfo) =>
|
||||
new UnsupportedPackageError({
|
||||
providerID: model.providerID,
|
||||
modelID: model.id,
|
||||
package: model.package ?? "unknown",
|
||||
})
|
||||
|
||||
const initialization = (model: Info, phase: InitializationPhase, cause: unknown) =>
|
||||
const initialization = (model: RuntimeInfo, phase: InitializationPhase, cause: unknown) =>
|
||||
new ModelInitializationError({
|
||||
providerID: model.providerID,
|
||||
modelID: model.id,
|
||||
@@ -359,7 +360,11 @@ export const layer = Layer.effect(
|
||||
provider?.integrationID ?? Integration.ID.make(selected.providerID),
|
||||
)
|
||||
const credential = connection ? yield* integrations.connection.resolve(connection) : undefined
|
||||
const runtimeInfo = yield* withVariant(selected, variant)
|
||||
const selectedVariant = yield* withVariant(selected, variant)
|
||||
const runtimeInfo: RuntimeInfo = {
|
||||
...selectedVariant,
|
||||
settings: Provider.mergeOverlay(provider?.settings, Provider.modelSettings(selectedVariant.settings)),
|
||||
}
|
||||
const model = yield* fromCatalogModel(runtimeInfo, credential, {
|
||||
loadPackage: (specifier) => Provider.loadPackage(specifier, npm),
|
||||
loadAISDK: (model) => aisdk.model(model),
|
||||
@@ -382,7 +387,7 @@ export const layer = Layer.effect(
|
||||
cost: selected.cost,
|
||||
limit: selected.limit,
|
||||
compaction: runtimeInfo.settings?.compaction,
|
||||
transport: runtimeInfo.settings?.transport,
|
||||
transport: provider?.settings?.transport,
|
||||
}
|
||||
})
|
||||
return Service.of({
|
||||
@@ -406,7 +411,7 @@ export const layer = Layer.effect(
|
||||
}),
|
||||
)
|
||||
|
||||
function hasConfiguredAuth(model: Info) {
|
||||
function hasConfiguredAuth(model: RuntimeInfo) {
|
||||
return [model.settings?.apiKey, model.settings?.authToken, model.settings?.accessToken].some(
|
||||
(value) => typeof value === "string" && value !== "",
|
||||
)
|
||||
|
||||
@@ -36,6 +36,9 @@ export type Ref = typeof Ref.Type
|
||||
export const Info = Model.Info
|
||||
export type Info = Model.Info
|
||||
|
||||
/** Effective provider and model settings used only while constructing a runtime model. */
|
||||
export type RuntimeInfo = Omit<Info, "settings"> & { readonly settings?: Provider.Settings }
|
||||
|
||||
export type MutableInfo = DeepMutable<Info>
|
||||
|
||||
export { Event } from "@opencode/schema/model"
|
||||
@@ -188,7 +191,10 @@ const layer = Layer.effect(
|
||||
...model,
|
||||
...(provider?.canonical === undefined ? {} : { canonical: provider.canonical }),
|
||||
package: model.package ?? provider?.package,
|
||||
settings: Provider.mergeOverlay(provider?.settings, model.settings),
|
||||
settings: Provider.mergeOverlay(
|
||||
Provider.modelSettings(provider?.settings),
|
||||
Provider.modelSettings(model.settings),
|
||||
),
|
||||
headers: Provider.mergeHeaders(provider?.headers, model.headers),
|
||||
body: Provider.mergeOverlay(provider?.body, model.body),
|
||||
} satisfies Info
|
||||
|
||||
@@ -7,7 +7,6 @@ import { App } from "../../app.js"
|
||||
import { Bus } from "../../bus.js"
|
||||
import { Credential } from "../../credential.js"
|
||||
import { Integration } from "../../integration.js"
|
||||
import { Model } from "../../model.js"
|
||||
import { Provider } from "../../provider.js"
|
||||
import { iife } from "../../util/iife.js"
|
||||
import { which } from "../../util/which.js"
|
||||
@@ -140,16 +139,18 @@ export const AzurePlugin = define({
|
||||
)
|
||||
continue
|
||||
const resourceName = resolveResourceName(item.provider.settings, loaded.resource)
|
||||
if (resourceName)
|
||||
evt.update(item.provider.id, (provider) => {
|
||||
provider.settings = {
|
||||
...provider.settings,
|
||||
resourceName,
|
||||
...(typeof provider.settings?.baseURL === "string"
|
||||
? { baseURL: expandResourceName(provider.settings.baseURL, resourceName) }
|
||||
: {}),
|
||||
}
|
||||
})
|
||||
const websocket = responsesWebSocketCapable(item.provider)
|
||||
if (!resourceName && !websocket) continue
|
||||
evt.update(item.provider.id, (provider) => {
|
||||
provider.settings = {
|
||||
...provider.settings,
|
||||
...(resourceName === undefined ? {} : { resourceName }),
|
||||
...(websocket ? { transport: provider.settings?.transport ?? "websocket" } : {}),
|
||||
...(resourceName !== undefined && typeof provider.settings?.baseURL === "string"
|
||||
? { baseURL: expandResourceName(provider.settings.baseURL, resourceName) }
|
||||
: {}),
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
yield* ctx.model.transform((models) => {
|
||||
@@ -167,10 +168,6 @@ export const AzurePlugin = define({
|
||||
draft.settings.baseURL,
|
||||
resolveResourceName(draft.settings, resourceName) ?? resourceName,
|
||||
)
|
||||
if (responsesWebSocketCapable(item.provider, draft))
|
||||
draft.settings = Provider.mergeOverlay(draft.settings, {
|
||||
transport: item.provider.settings?.transport ?? "websocket",
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -242,9 +239,9 @@ function expandResourceName(baseURL: string, resourceName: string) {
|
||||
.replaceAll("${AZURE_COGNITIVE_SERVICES_RESOURCE_NAME}", resourceName)
|
||||
}
|
||||
|
||||
function responsesWebSocketCapable(provider: Provider.Info, model: Model.Info) {
|
||||
if ((model.package ?? provider.package) !== "@opencode/ai/providers/azure/responses") return false
|
||||
const settings = Provider.mergeOverlay(provider.settings, model.settings)
|
||||
function responsesWebSocketCapable(provider: Provider.Info) {
|
||||
if (provider.package !== "@opencode/ai/providers/azure/responses") return false
|
||||
const settings = provider.settings
|
||||
if (settings?.useDeploymentBasedUrls === true) return false
|
||||
if (settings?.apiVersion !== undefined && settings.apiVersion !== "v1") return false
|
||||
if (typeof settings?.baseURL !== "string") return true
|
||||
|
||||
@@ -254,10 +254,13 @@ export const OpenAIPlugin = define({
|
||||
yield* ctx.provider.transform((providers) => {
|
||||
const item = providers.get(Provider.ID.openai)
|
||||
if (!item) return
|
||||
if (!chatgpt) return
|
||||
const account = chatgpt.metadata?.accountID
|
||||
const account = chatgpt?.metadata?.accountID
|
||||
providers.update(item.provider.id, (provider) => {
|
||||
provider.settings = Provider.mergeOverlay(provider.settings, { baseURL: codexBaseURL })
|
||||
provider.settings = Provider.mergeOverlay(provider.settings, {
|
||||
transport: provider.settings?.transport ?? "websocket",
|
||||
...(chatgpt ? { baseURL: codexBaseURL } : {}),
|
||||
})
|
||||
if (!chatgpt) return
|
||||
provider.headers = Provider.mergeHeaders(provider.headers, {
|
||||
originator: "opencode",
|
||||
"x-codex-beta-features": "remote_compaction_v2",
|
||||
@@ -270,9 +273,6 @@ export const OpenAIPlugin = define({
|
||||
// ChatGPT-plan tokens only authorize codex-eligible models, and the
|
||||
// subscription covers usage, so hide the rest and zero the cost.
|
||||
models.update(model.providerID, model.id, (draft) => {
|
||||
draft.settings = Provider.mergeOverlay(draft.settings, {
|
||||
transport: models.provider.get(model.providerID)?.provider.settings?.transport ?? "websocket",
|
||||
})
|
||||
if (!chatgpt) return
|
||||
if (Schema.is(Schema.Struct({ mode: Schema.Literal("pro") }))(draft.body?.reasoning)) {
|
||||
draft.enabled = false
|
||||
|
||||
@@ -95,14 +95,13 @@ export const XAIPlugin = define({
|
||||
editor.method.update(device(ctx.app))
|
||||
editor.method.update({ integrationID: "xai", method: { type: "key", label: "Manually enter API Key" } })
|
||||
})
|
||||
yield* ctx.model.transform((models) => {
|
||||
for (const model of models.list(providerID)) {
|
||||
models.update(providerID, model.id, (draft) => {
|
||||
draft.settings = Provider.mergeOverlay(draft.settings, {
|
||||
transport: models.provider.get(providerID)?.provider.settings?.transport ?? "websocket",
|
||||
})
|
||||
yield* ctx.provider.transform((providers) => {
|
||||
if (!providers.get(providerID)) return
|
||||
providers.update(providerID, (provider) => {
|
||||
provider.settings = Provider.mergeOverlay(provider.settings, {
|
||||
transport: provider.settings?.transport ?? "websocket",
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -134,11 +134,16 @@ export const loadPackage = Effect.fn("Provider.loadPackage")(function* (input: s
|
||||
|
||||
/** opencode settings consumed in Core; native packages never receive them. */
|
||||
const CORE_KEYS = ["chunkTimeout", "compaction", "fetch", "timeout", "transport"] as const
|
||||
const PROVIDER_ONLY_KEYS = ["chunkTimeout", "timeout", "transport"] as const
|
||||
|
||||
export function nativeSettings(settings: Settings): Settings {
|
||||
return Struct.omit(settings, CORE_KEYS)
|
||||
}
|
||||
|
||||
export function modelSettings(settings: Settings | undefined) {
|
||||
return settings && Struct.omit(settings, PROVIDER_ONLY_KEYS)
|
||||
}
|
||||
|
||||
export function mergeOverlay(
|
||||
base: Readonly<Record<string, unknown>> | undefined,
|
||||
overlay: Readonly<Record<string, unknown>> | undefined,
|
||||
|
||||
@@ -27,14 +27,15 @@ import { testEffect } from "./lib/effect"
|
||||
|
||||
const it = testEffect(AISDK.locationLayer)
|
||||
|
||||
const model = (packageName: string, settings: Record<string, unknown> = {}) =>
|
||||
Model.Info.make({
|
||||
const model = (packageName: string, settings: Provider.Settings = {}): Model.RuntimeInfo => ({
|
||||
...Model.Info.make({
|
||||
...Model.Info.default(Provider.ID.make("test-provider"), Model.ID.make("catalog-model")),
|
||||
modelID: Model.ID.make("api-model"),
|
||||
package: Provider.aisdk(packageName),
|
||||
settings,
|
||||
limit: { context: 100, output: 20 },
|
||||
})
|
||||
}),
|
||||
settings,
|
||||
})
|
||||
|
||||
const streamModel = (events: ReadonlyArray<LanguageModelV3StreamPart>): LanguageModelV3 => ({
|
||||
specificationVersion: "v3",
|
||||
|
||||
@@ -511,7 +511,7 @@ describe("Provider and Model", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("resolves provider and model overlay merges", () =>
|
||||
it.effect("keeps settings scoped while resolving request overlay merges", () =>
|
||||
Effect.gen(function* () {
|
||||
const providers = yield* Provider.Service
|
||||
const models = yield* Model.Service
|
||||
@@ -533,6 +533,7 @@ describe("Provider and Model", () => {
|
||||
})
|
||||
|
||||
const model = required(yield* models.get(providerID, modelID))
|
||||
expect((yield* providers.get(providerID))?.settings).toEqual({ provider: true, shared: "provider" })
|
||||
expect(model.settings).toEqual({ provider: true, shared: "model", model: true })
|
||||
expect(model.headers).toEqual({ provider: "provider", shared: "model", model: "model" })
|
||||
expect(model.body).toEqual({ provider: true, shared: "model", model: true })
|
||||
|
||||
@@ -37,6 +37,7 @@ describe("ConfigProviderPlugin.Plugin", () => {
|
||||
it.effect("inherits the provider compaction setting with model overrides and rejects unsupported routes", () =>
|
||||
Effect.gen(function* () {
|
||||
const models = yield* Model.Service
|
||||
const providers = yield* Provider.Service
|
||||
yield* addPlugin([
|
||||
new Document({
|
||||
type: "document",
|
||||
@@ -63,6 +64,7 @@ describe("ConfigProviderPlugin.Plugin", () => {
|
||||
expect(native.settings?.compaction).toEqual({ type: "native" })
|
||||
expect(local.settings?.compaction).toEqual({ type: "summary" })
|
||||
expect(defaultModel.settings?.compaction).toBeUndefined()
|
||||
expect((yield* providers.get(Provider.ID.make("custom")))?.settings?.compaction).toEqual({ type: "native" })
|
||||
yield* ModelResolver.fromCatalogModel(native)
|
||||
yield* ModelResolver.fromCatalogModel(local)
|
||||
yield* ModelResolver.fromCatalogModel(defaultModel)
|
||||
@@ -73,8 +75,9 @@ describe("ConfigProviderPlugin.Plugin", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("inherits the provider websocket policy with model overrides", () =>
|
||||
it.effect("keeps the provider websocket policy out of model settings", () =>
|
||||
Effect.gen(function* () {
|
||||
const providers = yield* Provider.Service
|
||||
const models = yield* Model.Service
|
||||
yield* addPlugin([
|
||||
new Document({
|
||||
@@ -83,8 +86,12 @@ describe("ConfigProviderPlugin.Plugin", () => {
|
||||
providers: {
|
||||
custom: {
|
||||
package: "@opencode/ai/providers/openai/responses",
|
||||
settings: { transport: "http" },
|
||||
models: { inherited: {}, override: { settings: { transport: "websocket" } } },
|
||||
settings: { transport: "http", timeout: 100, chunkTimeout: 200, shared: "provider" },
|
||||
models: {
|
||||
inherited: {
|
||||
settings: { transport: "websocket", timeout: 1, chunkTimeout: 2, model: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
default: { package: "@opencode/ai/providers/openai/responses", models: { untouched: {} } },
|
||||
},
|
||||
@@ -92,10 +99,9 @@ describe("ConfigProviderPlugin.Plugin", () => {
|
||||
}),
|
||||
])
|
||||
const inherited = required(yield* models.get(Provider.ID.make("custom"), Model.ID.make("inherited")))
|
||||
const override = required(yield* models.get(Provider.ID.make("custom"), Model.ID.make("override")))
|
||||
const untouched = required(yield* models.get(Provider.ID.make("default"), Model.ID.make("untouched")))
|
||||
expect(inherited.settings?.transport).toBe("http")
|
||||
expect(override.settings?.transport).toBe("websocket")
|
||||
expect((yield* providers.get(Provider.ID.make("custom")))?.settings?.transport).toBe("http")
|
||||
expect(inherited.settings).toEqual({ shared: "provider", model: true })
|
||||
expect(untouched.settings?.transport).toBeUndefined()
|
||||
}),
|
||||
)
|
||||
@@ -106,7 +112,7 @@ describe("ConfigProviderPlugin.Plugin", () => {
|
||||
{ id: "azure", model: "gpt-5.6-sol", package: "@opencode/ai/providers/azure/responses", plugin: AzurePlugin },
|
||||
{ id: "custom-azure", model: "deployment", package: "@opencode/ai/providers/azure/responses", plugin: AzurePlugin },
|
||||
]) {
|
||||
it.live(`provider transport overrides ${builtin.id} defaults while model overrides still win`, () =>
|
||||
it.live(`configured provider transport overrides ${builtin.id} defaults`, () =>
|
||||
Effect.gen(function* () {
|
||||
const providers = yield* Provider.Service
|
||||
const models = yield* Model.Service
|
||||
@@ -122,7 +128,8 @@ describe("ConfigProviderPlugin.Plugin", () => {
|
||||
editor.models.update(providerID, modelID, () => {})
|
||||
})
|
||||
yield* builtin.plugin.effect(host)
|
||||
expect((yield* models.get(providerID, modelID))?.settings?.transport).toBe("websocket")
|
||||
expect((yield* providers.get(providerID))?.settings?.transport).toBe("websocket")
|
||||
expect((yield* models.get(providerID, modelID))?.settings?.transport).toBeUndefined()
|
||||
|
||||
yield* addPlugin([
|
||||
new Document({
|
||||
@@ -131,15 +138,16 @@ describe("ConfigProviderPlugin.Plugin", () => {
|
||||
providers: {
|
||||
[builtin.id]: {
|
||||
settings: { transport: "http" },
|
||||
models: { override: { modelID: builtin.model, settings: { transport: "websocket" } } },
|
||||
models: { override: { modelID: builtin.model } },
|
||||
},
|
||||
},
|
||||
}),
|
||||
}),
|
||||
])
|
||||
|
||||
expect((yield* models.get(providerID, modelID))?.settings?.transport).toBe("http")
|
||||
expect((yield* models.get(providerID, Model.ID.make("override")))?.settings?.transport).toBe("websocket")
|
||||
expect((yield* providers.get(providerID))?.settings?.transport).toBe("http")
|
||||
expect((yield* models.get(providerID, modelID))?.settings?.transport).toBeUndefined()
|
||||
expect((yield* models.get(providerID, Model.ID.make("override")))?.settings?.transport).toBeUndefined()
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -434,7 +434,7 @@ describe("AzurePlugin", () => {
|
||||
),
|
||||
)
|
||||
|
||||
it.effect("marks only Azure v1 Responses deployments as WebSocket capable", () =>
|
||||
it.effect("stores the Azure Responses WebSocket preference on the provider", () =>
|
||||
withEnv({ AZURE_RESOURCE_NAME: undefined, AZURE_COGNITIVE_SERVICES_RESOURCE_NAME: undefined }, () =>
|
||||
Effect.gen(function* () {
|
||||
const catalog = yield* Provider.Service
|
||||
@@ -472,9 +472,8 @@ describe("AzurePlugin", () => {
|
||||
|
||||
yield* addPlugin()
|
||||
|
||||
const responses = required(yield* service.get(Provider.ID.azure, models.responses))
|
||||
expect(responses.settings?.transport).toBe("websocket")
|
||||
for (const modelID of [models.chat, models.preview, models.deploymentURL, models.gateway, models.nonAzure]) {
|
||||
expect((yield* catalog.get(Provider.ID.azure))?.settings?.transport).toBe("websocket")
|
||||
for (const modelID of [models.responses, models.chat, models.preview, models.deploymentURL, models.gateway, models.nonAzure]) {
|
||||
const model = required(yield* service.get(Provider.ID.azure, modelID))
|
||||
expect(model.settings?.transport).toBeUndefined()
|
||||
}
|
||||
|
||||
@@ -205,7 +205,8 @@ describe("OpenAIPlugin", () => {
|
||||
expect(model.package).toBe("@opencode/ai/providers/openai")
|
||||
expect(model.enabled).toBe(true)
|
||||
expect(model.limit).toEqual({ context: 1_050_000, input: 922_000, output: 128_000 })
|
||||
expect(model.settings?.transport).toBe("websocket")
|
||||
expect(provider.settings?.transport).toBe("websocket")
|
||||
expect(model.settings?.transport).toBeUndefined()
|
||||
expect(direct.headers).not.toHaveProperty("originator")
|
||||
expect(direct.baseURL).toBe("https://api.openai.com/v1")
|
||||
expect(provider.headers).not.toHaveProperty("x-codex-beta-features")
|
||||
|
||||
@@ -83,8 +83,10 @@ describe("XAIPlugin", () => {
|
||||
|
||||
yield* addPlugin()
|
||||
|
||||
const provider = yield* providers.get(providerID)
|
||||
const model = yield* models.get(providerID, Model.ID.make("grok-4.6"))
|
||||
expect(model?.settings?.transport).toBe("websocket")
|
||||
expect(provider?.settings?.transport).toBe("websocket")
|
||||
expect(model?.settings?.transport).toBeUndefined()
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -42,4 +42,19 @@ describe("Provider", () => {
|
||||
reasoningEffort: "high",
|
||||
})
|
||||
})
|
||||
|
||||
test("inherits shared and loose settings without provider-only policies", () => {
|
||||
expect(
|
||||
Provider.modelSettings({
|
||||
timeout: 60_000,
|
||||
chunkTimeout: 30_000,
|
||||
transport: "websocket",
|
||||
compaction: { type: "native" },
|
||||
reasoningEffort: "high",
|
||||
}),
|
||||
).toEqual({
|
||||
compaction: { type: "native" },
|
||||
reasoningEffort: "high",
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -13251,6 +13251,27 @@
|
||||
"required": ["input", "output"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Config.Model.Settings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"compaction": {
|
||||
"$ref": "#/components/schemas/Provider.Compaction"
|
||||
}
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"anyOf": [
|
||||
{},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"Config.ModelEncoded": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -13270,7 +13291,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"settings": {
|
||||
"$ref": "#/components/schemas/Config.Provider.Settings"
|
||||
"$ref": "#/components/schemas/Config.Model.Settings"
|
||||
},
|
||||
"headers": {
|
||||
"type": "object",
|
||||
@@ -13293,7 +13314,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"settings": {
|
||||
"$ref": "#/components/schemas/Config.Provider.Settings"
|
||||
"$ref": "#/components/schemas/Config.Model.Settings"
|
||||
},
|
||||
"headers": {
|
||||
"type": "object",
|
||||
@@ -15508,7 +15529,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"settings": {
|
||||
"$ref": "#/components/schemas/Provider.Settings"
|
||||
"$ref": "#/components/schemas/Model.Settings"
|
||||
},
|
||||
"headers": {
|
||||
"type": "object",
|
||||
@@ -15614,6 +15635,20 @@
|
||||
"required": ["id", "providerID"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Model.Settings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"compaction": {
|
||||
"$ref": "#/components/schemas/Provider.Compaction"
|
||||
}
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": {}
|
||||
}
|
||||
]
|
||||
},
|
||||
"Model.Variant": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -15621,7 +15656,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"settings": {
|
||||
"$ref": "#/components/schemas/Provider.Settings"
|
||||
"$ref": "#/components/schemas/Model.Settings"
|
||||
},
|
||||
"headers": {
|
||||
"type": "object",
|
||||
|
||||
@@ -17,6 +17,14 @@ export const Settings = Schema.StructWithRest(
|
||||
).annotate({ identifier: "Config.Provider.Settings" })
|
||||
export type Settings = typeof Settings.Type
|
||||
|
||||
export const ModelSettings = Schema.StructWithRest(
|
||||
Schema.Struct({
|
||||
compaction: Provider.Compaction.pipe(optional),
|
||||
}),
|
||||
[Schema.Record(Schema.String, Schema.UndefinedOr(Schema.Json))],
|
||||
).annotate({ identifier: "Config.Model.Settings" })
|
||||
export type ModelSettings = typeof ModelSettings.Type
|
||||
|
||||
const JsonRecord = Schema.Record(Schema.String, Schema.Json)
|
||||
|
||||
export const Overlays = {
|
||||
@@ -25,6 +33,12 @@ export const Overlays = {
|
||||
body: JsonRecord.pipe(optional),
|
||||
}
|
||||
|
||||
const ModelOverlays = {
|
||||
settings: ModelSettings.pipe(optional),
|
||||
headers: Schema.Record(Schema.String, Schema.String).pipe(optional),
|
||||
body: JsonRecord.pipe(optional),
|
||||
}
|
||||
|
||||
export class Request extends Schema.Class<Request>("Config.Provider.Request")({
|
||||
headers: Overlays.headers,
|
||||
body: Overlays.body,
|
||||
@@ -57,11 +71,11 @@ class Model extends Schema.Class<Model>("Config.Model")({
|
||||
name: Schema.String.pipe(optional),
|
||||
compatibility: Compatibility.pipe(optional),
|
||||
package: Schema.String.pipe(optional),
|
||||
...Overlays,
|
||||
...ModelOverlays,
|
||||
capabilities: Capabilities.pipe(optional),
|
||||
variants: Schema.Struct({
|
||||
id: VariantID,
|
||||
...Overlays,
|
||||
...ModelOverlays,
|
||||
}).pipe(Schema.Array, optional),
|
||||
cost: Schema.Union([Cost, Cost.pipe(Schema.Array)]).pipe(optional),
|
||||
disabled: Schema.Boolean.pipe(optional),
|
||||
|
||||
@@ -56,6 +56,21 @@ export const MaxTokensField = Schema.Literals(["max_completion_tokens", "max_tok
|
||||
})
|
||||
export type MaxTokensField = typeof MaxTokensField.Type
|
||||
|
||||
export const Settings = Schema.StructWithRest(
|
||||
Schema.Struct({
|
||||
compaction: Provider.Compaction.pipe(optional),
|
||||
}),
|
||||
// Provider packages may define arbitrary model-level options beyond OpenCode's shared compaction policy.
|
||||
[Schema.Record(Schema.String, Schema.Any)],
|
||||
).annotate({ identifier: "Model.Settings" })
|
||||
export type Settings = typeof Settings.Type
|
||||
|
||||
export const Overlays = {
|
||||
settings: Settings.pipe(optional),
|
||||
headers: Schema.Record(Schema.String, Schema.String).pipe(optional),
|
||||
body: Schema.Record(Schema.String, Schema.Any).pipe(optional),
|
||||
}
|
||||
|
||||
export interface Compatibility extends Schema.Schema.Type<typeof Compatibility> {}
|
||||
export const Compatibility = Schema.Struct({
|
||||
reasoningField: ReasoningField.pipe(optional),
|
||||
@@ -97,7 +112,7 @@ export const Cost = Schema.Struct({
|
||||
export interface Variant extends Schema.Schema.Type<typeof Variant> {}
|
||||
export const Variant = Schema.Struct({
|
||||
id: VariantID,
|
||||
...Provider.Overlays,
|
||||
...Overlays,
|
||||
}).annotate({ identifier: "Model.Variant" })
|
||||
|
||||
export interface Info extends Schema.Schema.Type<typeof Info> {}
|
||||
@@ -110,7 +125,7 @@ export const Info = Schema.Struct({
|
||||
name: Schema.String,
|
||||
compatibility: Compatibility.pipe(optional),
|
||||
package: Provider.Package.pipe(optional),
|
||||
...Provider.Overlays,
|
||||
...Overlays,
|
||||
capabilities: Capabilities,
|
||||
variants: Schema.Array(Variant),
|
||||
time: Schema.Struct({
|
||||
|
||||
@@ -169,6 +169,7 @@ describe("contract hygiene", () => {
|
||||
test("reusable public identifiers are stable and unique", () => {
|
||||
const identifiers = [
|
||||
Agent.Color,
|
||||
ConfigProvider.ModelSettings,
|
||||
ConfigProvider.Settings,
|
||||
FileSystem.Submatch,
|
||||
Form.Field,
|
||||
@@ -183,6 +184,7 @@ describe("contract hygiene", () => {
|
||||
Model.Ref,
|
||||
Model.Capabilities,
|
||||
Model.Cost,
|
||||
Model.Settings,
|
||||
Model.Variant,
|
||||
Project.Current,
|
||||
Worktree.Directory,
|
||||
@@ -239,11 +241,12 @@ describe("contract hygiene", () => {
|
||||
|
||||
expect(
|
||||
sources
|
||||
.filter((item) => item.file !== "provider.ts" && item.file !== "integration.ts")
|
||||
.filter((item) => item.file !== "provider.ts" && item.file !== "model.ts" && item.file !== "integration.ts")
|
||||
.map((item) => item.source)
|
||||
.join("\n"),
|
||||
).not.toContain("Schema.Any")
|
||||
expect(sources.find((item) => item.file === "provider.ts")?.source.match(/Schema\.Any/g)).toHaveLength(3)
|
||||
expect(sources.find((item) => item.file === "model.ts")?.source.match(/Schema\.Any/g)).toHaveLength(2)
|
||||
expect(sources.find((item) => item.file === "integration.ts")?.source.match(/Schema\.Any/g)).toHaveLength(2)
|
||||
expect(source).not.toContain("Schema.mutable")
|
||||
})
|
||||
|
||||
@@ -75,13 +75,10 @@ describe("Model.Info", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("Model.Capabilities", () => {
|
||||
test("decodes the optional transport setting", () => {
|
||||
const model = Model.Info.default(Provider.ID.openai, Model.ID.make("gpt-5.4-mini"))
|
||||
expect(Schema.encodeSync(Model.Info)({ ...model, settings: { transport: undefined } }).settings).toEqual({})
|
||||
expect(Schema.decodeUnknownSync(Model.Info)({ ...model, settings: { transport: "websocket" } }).settings).toEqual({
|
||||
transport: "websocket",
|
||||
describe("Model.Settings", () => {
|
||||
test("preserves provider-specific model options", () => {
|
||||
expect(Schema.decodeUnknownSync(Model.Settings)({ providerOption: true })).toEqual({
|
||||
providerOption: true,
|
||||
})
|
||||
expect(() => Schema.decodeUnknownSync(Model.Info)({ ...model, settings: { transport: "sse" } })).toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -13251,6 +13251,27 @@
|
||||
"required": ["input", "output"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Config.Model.Settings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"compaction": {
|
||||
"$ref": "#/components/schemas/Provider.Compaction"
|
||||
}
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"anyOf": [
|
||||
{},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"Config.ModelEncoded": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -13270,7 +13291,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"settings": {
|
||||
"$ref": "#/components/schemas/Config.Provider.Settings"
|
||||
"$ref": "#/components/schemas/Config.Model.Settings"
|
||||
},
|
||||
"headers": {
|
||||
"type": "object",
|
||||
@@ -13293,7 +13314,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"settings": {
|
||||
"$ref": "#/components/schemas/Config.Provider.Settings"
|
||||
"$ref": "#/components/schemas/Config.Model.Settings"
|
||||
},
|
||||
"headers": {
|
||||
"type": "object",
|
||||
@@ -15508,7 +15529,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"settings": {
|
||||
"$ref": "#/components/schemas/Provider.Settings"
|
||||
"$ref": "#/components/schemas/Model.Settings"
|
||||
},
|
||||
"headers": {
|
||||
"type": "object",
|
||||
@@ -15614,6 +15635,20 @@
|
||||
"required": ["id", "providerID"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Model.Settings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"compaction": {
|
||||
"$ref": "#/components/schemas/Provider.Compaction"
|
||||
}
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": {}
|
||||
}
|
||||
]
|
||||
},
|
||||
"Model.Variant": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -15621,7 +15656,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"settings": {
|
||||
"$ref": "#/components/schemas/Provider.Settings"
|
||||
"$ref": "#/components/schemas/Model.Settings"
|
||||
},
|
||||
"headers": {
|
||||
"type": "object",
|
||||
|
||||
@@ -13251,6 +13251,27 @@
|
||||
"required": ["input", "output"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Config.Model.Settings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"compaction": {
|
||||
"$ref": "#/components/schemas/Provider.Compaction"
|
||||
}
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"anyOf": [
|
||||
{},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"Config.ModelEncoded": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -13270,7 +13291,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"settings": {
|
||||
"$ref": "#/components/schemas/Config.Provider.Settings"
|
||||
"$ref": "#/components/schemas/Config.Model.Settings"
|
||||
},
|
||||
"headers": {
|
||||
"type": "object",
|
||||
@@ -13293,7 +13314,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"settings": {
|
||||
"$ref": "#/components/schemas/Config.Provider.Settings"
|
||||
"$ref": "#/components/schemas/Config.Model.Settings"
|
||||
},
|
||||
"headers": {
|
||||
"type": "object",
|
||||
@@ -15508,7 +15529,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"settings": {
|
||||
"$ref": "#/components/schemas/Provider.Settings"
|
||||
"$ref": "#/components/schemas/Model.Settings"
|
||||
},
|
||||
"headers": {
|
||||
"type": "object",
|
||||
@@ -15614,6 +15635,20 @@
|
||||
"required": ["id", "providerID"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Model.Settings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"compaction": {
|
||||
"$ref": "#/components/schemas/Provider.Compaction"
|
||||
}
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": {}
|
||||
}
|
||||
]
|
||||
},
|
||||
"Model.Variant": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -15621,7 +15656,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"settings": {
|
||||
"$ref": "#/components/schemas/Provider.Settings"
|
||||
"$ref": "#/components/schemas/Model.Settings"
|
||||
},
|
||||
"headers": {
|
||||
"type": "object",
|
||||
|
||||
@@ -506,7 +506,6 @@ headers, and model variants.
|
||||
}
|
||||
```
|
||||
|
||||
`settings.transport: "websocket"` on a provider or model selects its session
|
||||
WebSocket; a model value overrides the provider value.
|
||||
Provider `settings.transport: "websocket"` selects its session WebSocket.
|
||||
|
||||
See the [providers guide](/providers) for credentials, custom endpoints, provider packages, the WebSocket transport, and model configuration.
|
||||
|
||||
@@ -558,9 +558,6 @@ sessions.
|
||||
"providers": {
|
||||
"openai": {
|
||||
"settings": { "transport": "http" },
|
||||
"models": {
|
||||
"gpt-5.5": { "settings": { "transport": "websocket" } },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -568,9 +565,8 @@ sessions.
|
||||
|
||||
WebSocket behavior follows these rules:
|
||||
|
||||
- Built-in providers opt supported models in according to their own policy.
|
||||
- `settings.transport: "websocket"` enables a provider or model; `"http"` disables it.
|
||||
- A model value overrides its provider value.
|
||||
- Built-in providers opt supported routes in according to their own policy.
|
||||
- Provider `settings.transport: "websocket"` enables it; `"http"` disables it.
|
||||
- `"websocket"` on a route without a WebSocket channel logs a warning and falls back to HTTP.
|
||||
- OpenAI provider compaction uses the same connection.
|
||||
- xAI continues from stored responses only. With its default `store: false`, each step is sent in full over the reused
|
||||
|
||||
Reference in New Issue
Block a user