mirror of
https://github.com/anomalyco/opencode.git
synced 2026-09-08 12:34:40 +00:00
fix(core): preserve legacy Console reasoning variants (#46586)
This commit is contained in:
parent
e297da82ae
commit
cff1d0fe01
2 changed files with 161 additions and 2 deletions
|
|
@ -163,7 +163,11 @@ export const OpencodePlugin = define<HttpClient.HttpClient | Bus.Service | Scope
|
|||
existing.settings = Provider.mergeOverlay(existing.settings, withoutCredentials(variant.settings))
|
||||
if (variant.headers !== undefined)
|
||||
existing.headers = Provider.mergeHeaders(existing.headers, variant.headers)
|
||||
if (variant.body !== undefined) existing.body = Provider.mergeOverlay(existing.body, variant.body)
|
||||
if (variant.body !== undefined)
|
||||
existing.body = Provider.mergeOverlay(
|
||||
existing.body,
|
||||
variantBody(variant.body, model.package ?? item.package ?? source?.provider.package),
|
||||
)
|
||||
}
|
||||
if (config.cost !== undefined)
|
||||
model.cost = (Array.isArray(config.cost) ? config.cost : [config.cost]).map((cost) => ({
|
||||
|
|
@ -232,6 +236,18 @@ function fetchProviders(http: HttpClient.HttpClient, value: Credential.Value) {
|
|||
)
|
||||
}
|
||||
|
||||
function variantBody(body: Readonly<Record<string, unknown>>, packageName: string | undefined) {
|
||||
if (packageName !== Provider.aisdk("@ai-sdk/openai")) return body
|
||||
const { reasoningEffort, reasoningSummary, ...native } = body
|
||||
const reasoning = {
|
||||
...(typeof reasoningEffort === "string" ? { effort: reasoningEffort } : {}),
|
||||
...(typeof reasoningSummary === "string" ? { summary: reasoningSummary } : {}),
|
||||
}
|
||||
if (Object.keys(reasoning).length === 0) return body
|
||||
// Existing Console variants stored SDK options here before V2 consumed raw bodies.
|
||||
return Provider.mergeOverlay({ reasoning }, native)
|
||||
}
|
||||
|
||||
function withoutCredentials<Value>(body: Readonly<Record<string, Value>> | undefined) {
|
||||
return (
|
||||
body &&
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
import { describe, expect } from "bun:test"
|
||||
import { LLM } from "@opencode-ai/ai"
|
||||
import { LLMClient, RequestExecutor } from "@opencode-ai/ai/route"
|
||||
import { Money } from "@opencode-ai/schema/money"
|
||||
import { Effect } from "effect"
|
||||
import { Effect, Layer, Stream } from "effect"
|
||||
import { Catalog } from "@opencode-ai/core/catalog"
|
||||
import { Credential } from "@opencode-ai/core/credential"
|
||||
import { Integration } from "@opencode-ai/core/integration"
|
||||
import { Model } from "@opencode-ai/core/model"
|
||||
import { ModelResolver } from "@opencode-ai/core/model-resolver"
|
||||
import { Plugin } from "@opencode-ai/core/plugin"
|
||||
import { PluginHost } from "@opencode-ai/core/plugin/host"
|
||||
import { OpencodePlugin } from "@opencode-ai/core/plugin/provider/opencode"
|
||||
|
|
@ -376,6 +379,146 @@ describe("OpencodePlugin", () => {
|
|||
),
|
||||
)
|
||||
|
||||
it.live("normalizes legacy Console OpenAI variant bodies without changing native bodies", () =>
|
||||
Effect.acquireUseRelease(
|
||||
Effect.sync(() => {
|
||||
const variants = [
|
||||
{
|
||||
id: "legacy",
|
||||
body: { reasoningEffort: "high", reasoningSummary: "auto" },
|
||||
expected: { reasoning: { effort: "high", summary: "auto" } },
|
||||
},
|
||||
{
|
||||
id: "effort-only",
|
||||
body: { reasoningEffort: "low" },
|
||||
expected: { reasoning: { effort: "low" } },
|
||||
},
|
||||
{
|
||||
id: "summary-only",
|
||||
body: { reasoningSummary: "detailed" },
|
||||
expected: { reasoning: { summary: "detailed" } },
|
||||
},
|
||||
{
|
||||
id: "native",
|
||||
body: { reasoning: { effort: "high", summary: "auto" } },
|
||||
expected: { reasoning: { effort: "high", summary: "auto" } },
|
||||
},
|
||||
{
|
||||
id: "mixed",
|
||||
body: {
|
||||
reasoningEffort: "low",
|
||||
reasoningSummary: "auto",
|
||||
reasoning: { effort: "high", summary: "detailed" },
|
||||
},
|
||||
expected: { reasoning: { effort: "high", summary: "detailed" } },
|
||||
},
|
||||
{ id: "plain", body: {}, expected: {} },
|
||||
]
|
||||
const models = {
|
||||
astra: {
|
||||
modelID: "api-astra",
|
||||
variants: variants.map((variant) => ({
|
||||
id: variant.id,
|
||||
headers: { "x-variant": variant.id },
|
||||
body: {
|
||||
...variant.body,
|
||||
include: ["reasoning.encrypted_content"],
|
||||
metadata: { custom: "unchanged" },
|
||||
},
|
||||
})),
|
||||
},
|
||||
}
|
||||
const requests: { body: unknown; variant: string | null }[] = []
|
||||
return {
|
||||
variants,
|
||||
requests,
|
||||
server: Bun.serve({
|
||||
port: 0,
|
||||
fetch: async (request) => {
|
||||
if (new URL(request.url).pathname === "/responses") {
|
||||
requests.push({ body: await request.json(), variant: request.headers.get("x-variant") })
|
||||
return new Response('data: {"type":"response.completed","response":{"id":"resp_test"}}\n\n', {
|
||||
headers: { "content-type": "text/event-stream" },
|
||||
})
|
||||
}
|
||||
return Response.json({
|
||||
providers: {
|
||||
remote: {
|
||||
canonical: "openai",
|
||||
package: "aisdk:@ai-sdk/openai",
|
||||
settings: { baseURL: new URL(request.url).origin },
|
||||
models: {
|
||||
...models,
|
||||
override: { ...models.astra, package: "aisdk:@ai-sdk/openai-compatible" },
|
||||
},
|
||||
},
|
||||
compatible: {
|
||||
canonical: "openai",
|
||||
package: "aisdk:@ai-sdk/openai-compatible",
|
||||
models,
|
||||
},
|
||||
},
|
||||
})
|
||||
},
|
||||
}),
|
||||
}
|
||||
}),
|
||||
({ variants, requests, server }) =>
|
||||
Effect.gen(function* () {
|
||||
const credentials = yield* Credential.Service
|
||||
const catalog = yield* Catalog.Service
|
||||
const credential = Credential.Key.make({
|
||||
type: "key",
|
||||
key: "secret",
|
||||
metadata: { server: server.url.origin },
|
||||
})
|
||||
yield* credentials.create({ integrationID: Integration.ID.make("opencode"), value: credential })
|
||||
yield* addPlugin()
|
||||
const model = required(yield* catalog.model.get(Provider.ID.make("remote"), Model.ID.make("astra")))
|
||||
expect(model.canonical).toBe(Provider.ID.openai)
|
||||
yield* Effect.forEach(variants, (variant, index) =>
|
||||
Effect.gen(function* () {
|
||||
const projected = required(model.variants.find((item) => item.id === variant.id))
|
||||
expect(projected.body).toEqual({
|
||||
...variant.expected,
|
||||
include: ["reasoning.encrypted_content"],
|
||||
metadata: { custom: "unchanged" },
|
||||
})
|
||||
expect(projected.settings).toBeUndefined()
|
||||
const resolved = yield* ModelResolver.resolveModel(model, Model.VariantID.make(variant.id), credential)
|
||||
yield* LLMClient.stream(LLM.request({ model: resolved, prompt: "Hello" })).pipe(
|
||||
Stream.runDrain,
|
||||
Effect.provide(LLMClient.layer.pipe(Layer.provide(RequestExecutor.layer))),
|
||||
)
|
||||
expect(requests).toHaveLength(index + 1)
|
||||
const request = required(requests.at(-1))
|
||||
expect(request.variant).toBe(variant.id)
|
||||
expect(request.body).toMatchObject({
|
||||
...variant.expected,
|
||||
model: "api-astra",
|
||||
include: ["reasoning.encrypted_content"],
|
||||
metadata: { custom: "unchanged" },
|
||||
})
|
||||
expect(request.body).not.toHaveProperty("reasoningEffort")
|
||||
expect(request.body).not.toHaveProperty("reasoningSummary")
|
||||
if (variant.id === "plain") expect(request.body).not.toHaveProperty("reasoning")
|
||||
}),
|
||||
)
|
||||
const compatible = required(yield* catalog.model.get(Provider.ID.make("compatible"), Model.ID.make("astra")))
|
||||
const override = required(yield* catalog.model.get(Provider.ID.make("remote"), Model.ID.make("override")))
|
||||
for (const model of [compatible, override]) {
|
||||
expect(model.variants.find((variant) => variant.id === "legacy")?.body).toEqual({
|
||||
reasoningEffort: "high",
|
||||
reasoningSummary: "auto",
|
||||
include: ["reasoning.encrypted_content"],
|
||||
metadata: { custom: "unchanged" },
|
||||
})
|
||||
}
|
||||
}),
|
||||
({ server }) => Effect.promise(() => server.stop(true)),
|
||||
),
|
||||
)
|
||||
|
||||
it.effect("uses a public key and disables paid models without credentials", () =>
|
||||
withEnv({ OPENCODE_API_KEY: undefined }, () =>
|
||||
Effect.gen(function* () {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue