From 37be46db40a6d1af8a9bc7836f3b78ff1bf4dab3 Mon Sep 17 00:00:00 2001 From: maphew <486200+maphew@users.noreply.github.com> Date: Fri, 28 Aug 2026 18:26:24 +0000 Subject: [PATCH 01/12] fix(cli): gate OpenAI-only reasoning effort tiers to gpt-5 family --- .changeset/grok-xhigh-effort-gate.md | 5 ++ packages/opencode/src/provider/transform.ts | 12 +++++ .../opencode/test/provider/transform.test.ts | 53 +++++++++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 .changeset/grok-xhigh-effort-gate.md diff --git a/.changeset/grok-xhigh-effort-gate.md b/.changeset/grok-xhigh-effort-gate.md new file mode 100644 index 000000000000..ad0ee340dca0 --- /dev/null +++ b/.changeset/grok-xhigh-effort-gate.md @@ -0,0 +1,5 @@ +--- +"@kilocode/cli": patch +--- + +Stop offering OpenAI-only `none`/`xhigh` reasoning-effort variants for non-OpenAI models routed through the OpenAI provider npm (e.g. Grok on an OpenAI-compatible base URL), which the upstream provider rejects with a 400. Only gpt-5-family models now receive the rollout-gated tiers. diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index d2cfaa6b89ad..56f1bd51f875 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -683,6 +683,11 @@ const OPENAI_XHIGH_EFFORT_RELEASE_DATE = "2025-12-04" // "gpt-5", "gpt-5-nano", "gpt-5.4", "openai/gpt-5.4-codex". // Anchored to start-of-string or "/" so it doesn't false-match "gpt-50" or "gpt-5o". const GPT5_FAMILY_RE = /(?:^|\/)gpt-5(?:[.-]|$)/ +// kilocode_change start - like GPT5_FAMILY_RE, but also accepts a "." prefix so +// provider-prefixed ids like Bedrock Mantle's "openai.gpt-5.5" count as gpt-5 +// family. Still excludes lookalikes such as "gpt-50" and "gpt-5o". +const GPT5_FAMILY_PREFIX_RE = /(?:^|\/|\.)gpt-5(?:[.-]|$)/ +// kilocode_change end const GPT5_VERSION_RE = /(?:^|\/)gpt-5[.-](\d+)(?:[.-]|$)/ const GPT5_PRO_RE = /(?:^|\/)gpt-5[.-]?pro(?:[.-]|$)/ const GPT5_VERSIONED_PRO_RE = /(?:^|\/)gpt-5[.-]\d+[.-]pro(?:[.-]|$)/ @@ -727,6 +732,13 @@ function openaiReasoningEfforts(apiId: string, releaseDate: string) { // GPT-5.1 replaced GPT-5's `minimal` effort with `none`; GPT-5.2+ // additionally accepts `xhigh`. Model pages list the supported subset. if (versionedEfforts) return versionedEfforts + // kilocode_change start - `none` and `xhigh` are OpenAI tiers gated on + // OpenAI's rollout dates. They apply only to gpt-5-family models: non-OpenAI + // models routed through @ai-sdk/openai on a compatible base URL (e.g. Grok + // via api.x.ai) reject them, which surfaced as offering an unusable `xhigh` + // variant (issue #13342). + if (!GPT5_FAMILY_PREFIX_RE.test(id)) return WIDELY_SUPPORTED_EFFORTS + // kilocode_change end const efforts = [...WIDELY_SUPPORTED_EFFORTS] if (GPT5_FAMILY_RE.test(id)) efforts.unshift("minimal") if (releaseDate >= OPENAI_NONE_EFFORT_RELEASE_DATE) efforts.unshift("none") diff --git a/packages/opencode/test/provider/transform.test.ts b/packages/opencode/test/provider/transform.test.ts index abcacebf6aa3..48d3578cab3e 100644 --- a/packages/opencode/test/provider/transform.test.ts +++ b/packages/opencode/test/provider/transform.test.ts @@ -5457,6 +5457,59 @@ describe("ProviderTransform.variants", () => { const result = ProviderTransform.variants(model) expect(Object.keys(result)).toEqual(["low", "medium", "high"]) }) + + // kilocode_change start - non-OpenAI models on an OpenAI-compatible base URL + // must not inherit OpenAI's rollout-gated tiers (issue #13342) + test("grok-4.6 does not get OpenAI rollout-gated none/xhigh tiers", () => { + const model = createMockModel({ + id: "grok-4.6", + providerID: "xai", + api: { + id: "grok-4.6", + url: "https://api.x.ai", + npm: "@ai-sdk/openai", + }, + release_date: "2026-08-12", + }) + const result = ProviderTransform.variants(model) + expect(Object.keys(result)).toEqual(["low", "medium", "high"]) + expect(result.medium).toEqual({ + reasoningEffort: "medium", + reasoningSummary: "auto", + include: ["reasoning.encrypted_content"], + }) + }) + + test("unknown reasoning models on OpenAI npm keep only widely supported efforts", () => { + const model = createMockModel({ + id: "my-compatible-model", + providerID: "custom", + api: { + id: "my-compatible-model", + url: "https://api.custom.com", + npm: "@ai-sdk/openai", + }, + release_date: "2026-08-12", + }) + const result = ProviderTransform.variants(model) + expect(Object.keys(result)).toEqual(["low", "medium", "high"]) + }) + + test("gpt-5 family still receives rollout-gated tiers on a dot-prefixed id", () => { + const model = createMockModel({ + id: "openai.gpt-5.5", + providerID: "amazon-bedrock", + api: { + id: "openai.gpt-5.5", + url: "https://bedrock-mantle.us-east-2.api.aws/openai/v1", + npm: "@ai-sdk/amazon-bedrock/mantle", + }, + release_date: "2026-04-23", + }) + const result = ProviderTransform.variants(model) + expect(Object.keys(result)).toEqual(["none", "low", "medium", "high", "xhigh"]) + }) + // kilocode_change end }) describe("@ai-sdk/amazon-bedrock/mantle", () => { From 5bc05ff3e30ba0e6adf7ce173e13d14dbd6f71f5 Mon Sep 17 00:00:00 2001 From: maphew <486200+maphew@users.noreply.github.com> Date: Fri, 28 Aug 2026 21:49:49 +0000 Subject: [PATCH 02/12] fix(cli): extract gpt-5 tier gate to Kilo mirror, cover hyphen-prefixed ids --- .../src/kilocode/provider/transform.ts | 23 ++++ packages/opencode/src/provider/transform.ts | 16 +-- .../test/kilocode/provider/transform.test.ts | 118 ++++++++++++++++++ .../opencode/test/provider/transform.test.ts | 53 -------- 4 files changed, 146 insertions(+), 64 deletions(-) create mode 100644 packages/opencode/src/kilocode/provider/transform.ts create mode 100644 packages/opencode/test/kilocode/provider/transform.test.ts diff --git a/packages/opencode/src/kilocode/provider/transform.ts b/packages/opencode/src/kilocode/provider/transform.ts new file mode 100644 index 000000000000..a3ba1c11ef5c --- /dev/null +++ b/packages/opencode/src/kilocode/provider/transform.ts @@ -0,0 +1,23 @@ +// kilocode_change - new file +// Kilo-specific reasoning-effort gate for shared ProviderTransform. +// +// OpenAI rolled out the `none` and `xhigh` reasoning_effort tiers on fixed +// dates. Only gpt-5-family models accept them: non-OpenAI models routed +// through @ai-sdk/openai on a compatible base URL (e.g. Grok via api.x.ai) +// reject them, which surfaced as offering an unusable `xhigh` variant +// (issue #13342). + +const GPT5_FAMILY_PREFIX_RE = /(?:^|\/|\.|-)gpt-5(?:[.-]|$)/ + +/** + * True when the model id is a gpt-5 family member that may receive OpenAI's + * rollout-gated reasoning tiers (`none`, `xhigh`). + * + * Matches bare ("gpt-5.5"), slash-prefixed ("openai/gpt-5.6"), dot-prefixed + * ("openai.gpt-5.5", Bedrock Mantle), and hyphen-prefixed deployment ids + * ("azure-openai--gpt-5.4", SAP AI Core) while still excluding lookalikes + * such as "gpt-50" and "gpt-5o". + */ +export function gpt5FamilyReasoningTiers(id: string) { + return GPT5_FAMILY_PREFIX_RE.test(id.toLowerCase()) +} diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 56f1bd51f875..4d0948fcfe8e 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -7,6 +7,7 @@ import { iife } from "@/util/iife" import { kiloProviderOptions } from "@/kilocode/provider-options" import { isLing } from "@/kilocode/model-match" // kilocode_change import { reasoningSummary } from "@/kilocode/provider/reasoning-summary" // kilocode_change +import { gpt5FamilyReasoningTiers } from "@/kilocode/provider/transform" // kilocode_change type Modality = NonNullable["input"][number] @@ -683,11 +684,6 @@ const OPENAI_XHIGH_EFFORT_RELEASE_DATE = "2025-12-04" // "gpt-5", "gpt-5-nano", "gpt-5.4", "openai/gpt-5.4-codex". // Anchored to start-of-string or "/" so it doesn't false-match "gpt-50" or "gpt-5o". const GPT5_FAMILY_RE = /(?:^|\/)gpt-5(?:[.-]|$)/ -// kilocode_change start - like GPT5_FAMILY_RE, but also accepts a "." prefix so -// provider-prefixed ids like Bedrock Mantle's "openai.gpt-5.5" count as gpt-5 -// family. Still excludes lookalikes such as "gpt-50" and "gpt-5o". -const GPT5_FAMILY_PREFIX_RE = /(?:^|\/|\.)gpt-5(?:[.-]|$)/ -// kilocode_change end const GPT5_VERSION_RE = /(?:^|\/)gpt-5[.-](\d+)(?:[.-]|$)/ const GPT5_PRO_RE = /(?:^|\/)gpt-5[.-]?pro(?:[.-]|$)/ const GPT5_VERSIONED_PRO_RE = /(?:^|\/)gpt-5[.-]\d+[.-]pro(?:[.-]|$)/ @@ -732,12 +728,10 @@ function openaiReasoningEfforts(apiId: string, releaseDate: string) { // GPT-5.1 replaced GPT-5's `minimal` effort with `none`; GPT-5.2+ // additionally accepts `xhigh`. Model pages list the supported subset. if (versionedEfforts) return versionedEfforts - // kilocode_change start - `none` and `xhigh` are OpenAI tiers gated on - // OpenAI's rollout dates. They apply only to gpt-5-family models: non-OpenAI - // models routed through @ai-sdk/openai on a compatible base URL (e.g. Grok - // via api.x.ai) reject them, which surfaced as offering an unusable `xhigh` - // variant (issue #13342). - if (!GPT5_FAMILY_PREFIX_RE.test(id)) return WIDELY_SUPPORTED_EFFORTS + // kilocode_change start - `none`/`xhigh` are OpenAI rollout tiers only gpt-5-family + // models accept; non-OpenAI models on an OpenAI-compatible base URL (e.g. Grok + // via api.x.ai) reject them (issue #13342). See gpt5FamilyReasoningTiers. + if (!gpt5FamilyReasoningTiers(id)) return WIDELY_SUPPORTED_EFFORTS // kilocode_change end const efforts = [...WIDELY_SUPPORTED_EFFORTS] if (GPT5_FAMILY_RE.test(id)) efforts.unshift("minimal") diff --git a/packages/opencode/test/kilocode/provider/transform.test.ts b/packages/opencode/test/kilocode/provider/transform.test.ts new file mode 100644 index 000000000000..a5d5d3c397a3 --- /dev/null +++ b/packages/opencode/test/kilocode/provider/transform.test.ts @@ -0,0 +1,118 @@ +import { describe, expect, test } from "bun:test" +import { ProviderTransform } from "@/provider/transform" + +// Kilo-specific reasoning-effort gate regression coverage (issue #13342). +// +// OpenAI's `none`/`xhigh` reasoning_effort tiers are rollout-gated and only +// gpt-5-family models accept them. Non-OpenAI models routed through the OpenAI +// npm on a compatible base URL (e.g. Grok via api.x.ai) reject them, so Kilo +// must not offer those tiers to non-gpt-5 models. See +// src/kilocode/provider/transform.ts. + +function model(overrides: Record = {}): any { + return { + id: "test/test-model", + providerID: "test", + api: { + id: "test-model", + url: "https://api.test.com", + npm: "@ai-sdk/openai", + }, + name: "Test Model", + capabilities: { + temperature: true, + reasoning: true, + attachment: true, + toolcall: true, + input: { text: true, audio: false, image: true, video: false, pdf: false }, + output: { text: true, audio: false, image: false, video: false, pdf: false }, + interleaved: false, + }, + cost: { + input: 0.001, + output: 0.002, + cache: { read: 0.0001, write: 0.0002 }, + }, + limit: { + context: 200_000, + output: 64_000, + }, + status: "active", + options: {}, + headers: {}, + release_date: "2024-01-01", + ...overrides, + } +} + +describe("ProviderTransform.variants - gpt-5 rollout-tier gate", () => { + test("grok-4.6 on the OpenAI npm does not get OpenAI rollout-gated none/xhigh tiers", () => { + const result = ProviderTransform.variants( + model({ + id: "grok-4.6", + providerID: "xai", + api: { id: "grok-4.6", url: "https://api.x.ai", npm: "@ai-sdk/openai" }, + release_date: "2026-08-12", + }), + ) + expect(Object.keys(result)).toEqual(["low", "medium", "high"]) + expect(result.medium).toEqual({ + reasoningEffort: "medium", + reasoningSummary: "auto", + include: ["reasoning.encrypted_content"], + }) + }) + + test("unknown reasoning models on the OpenAI npm keep only widely supported efforts", () => { + const result = ProviderTransform.variants( + model({ + id: "my-compatible-model", + providerID: "custom", + api: { id: "my-compatible-model", url: "https://api.custom.com", npm: "@ai-sdk/openai" }, + release_date: "2026-08-12", + }), + ) + expect(Object.keys(result)).toEqual(["low", "medium", "high"]) + }) + + test("gpt-5 lookalikes stay out of the gpt-5 family", () => { + for (const id of ["gpt-50", "gpt-5o"]) { + const result = ProviderTransform.variants( + model({ + id, + api: { id, url: "https://api.openai.com", npm: "@ai-sdk/openai" }, + release_date: "2026-08-12", + }), + ) + expect(Object.keys(result)).toEqual(["low", "medium", "high"]) + } + }) + + test("dot-prefixed gpt-5 id (Bedrock Mantle) keeps rollout-gated tiers", () => { + const result = ProviderTransform.variants( + model({ + id: "openai.gpt-5.5", + providerID: "amazon-bedrock", + api: { + id: "openai.gpt-5.5", + url: "https://bedrock-mantle.us-east-2.api.aws/openai/v1", + npm: "@ai-sdk/amazon-bedrock/mantle", + }, + release_date: "2026-04-23", + }), + ) + expect(Object.keys(result)).toEqual(["none", "low", "medium", "high", "xhigh"]) + }) + + test("hyphen-prefixed gpt-5 deployment id (SAP AI Core) keeps rollout-gated tiers", () => { + const result = ProviderTransform.variants( + model({ + id: "azure-openai--gpt-5.4", + providerID: "sap-ai-core", + api: { id: "azure-openai--gpt-5.4", url: "https://api.ai.sap", npm: "@jerome-benoit/sap-ai-provider-v2" }, + release_date: "2026-01-15", + }), + ) + expect(Object.keys(result)).toEqual(["none", "low", "medium", "high", "xhigh"]) + }) +}) diff --git a/packages/opencode/test/provider/transform.test.ts b/packages/opencode/test/provider/transform.test.ts index 48d3578cab3e..abcacebf6aa3 100644 --- a/packages/opencode/test/provider/transform.test.ts +++ b/packages/opencode/test/provider/transform.test.ts @@ -5457,59 +5457,6 @@ describe("ProviderTransform.variants", () => { const result = ProviderTransform.variants(model) expect(Object.keys(result)).toEqual(["low", "medium", "high"]) }) - - // kilocode_change start - non-OpenAI models on an OpenAI-compatible base URL - // must not inherit OpenAI's rollout-gated tiers (issue #13342) - test("grok-4.6 does not get OpenAI rollout-gated none/xhigh tiers", () => { - const model = createMockModel({ - id: "grok-4.6", - providerID: "xai", - api: { - id: "grok-4.6", - url: "https://api.x.ai", - npm: "@ai-sdk/openai", - }, - release_date: "2026-08-12", - }) - const result = ProviderTransform.variants(model) - expect(Object.keys(result)).toEqual(["low", "medium", "high"]) - expect(result.medium).toEqual({ - reasoningEffort: "medium", - reasoningSummary: "auto", - include: ["reasoning.encrypted_content"], - }) - }) - - test("unknown reasoning models on OpenAI npm keep only widely supported efforts", () => { - const model = createMockModel({ - id: "my-compatible-model", - providerID: "custom", - api: { - id: "my-compatible-model", - url: "https://api.custom.com", - npm: "@ai-sdk/openai", - }, - release_date: "2026-08-12", - }) - const result = ProviderTransform.variants(model) - expect(Object.keys(result)).toEqual(["low", "medium", "high"]) - }) - - test("gpt-5 family still receives rollout-gated tiers on a dot-prefixed id", () => { - const model = createMockModel({ - id: "openai.gpt-5.5", - providerID: "amazon-bedrock", - api: { - id: "openai.gpt-5.5", - url: "https://bedrock-mantle.us-east-2.api.aws/openai/v1", - npm: "@ai-sdk/amazon-bedrock/mantle", - }, - release_date: "2026-04-23", - }) - const result = ProviderTransform.variants(model) - expect(Object.keys(result)).toEqual(["none", "low", "medium", "high", "xhigh"]) - }) - // kilocode_change end }) describe("@ai-sdk/amazon-bedrock/mantle", () => { From ee7eff3a82a204a915cb4775689b17a861c3b4b1 Mon Sep 17 00:00:00 2001 From: maphew <486200+maphew@users.noreply.github.com> Date: Sat, 29 Aug 2026 03:49:07 +0000 Subject: [PATCH 03/12] fix(cli): exempt native Azure from gpt-5 tier gate, drop stray marker --- .../src/kilocode/provider/transform.ts | 1 - packages/opencode/src/provider/transform.ts | 9 ++++--- .../test/kilocode/provider/transform.test.ts | 24 +++++++++++++++++++ 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/packages/opencode/src/kilocode/provider/transform.ts b/packages/opencode/src/kilocode/provider/transform.ts index a3ba1c11ef5c..2b6d1121a76d 100644 --- a/packages/opencode/src/kilocode/provider/transform.ts +++ b/packages/opencode/src/kilocode/provider/transform.ts @@ -1,4 +1,3 @@ -// kilocode_change - new file // Kilo-specific reasoning-effort gate for shared ProviderTransform. // // OpenAI rolled out the `none` and `xhigh` reasoning_effort tiers on fixed diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 4d0948fcfe8e..a4c4b01fc610 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -716,7 +716,10 @@ function gpt5ChatReasoningEfforts(apiId: string) { // Computes the reasoning_effort tiers an OpenAI (or OpenAI-compatible upstream // routed through it, e.g. cf-ai-gateway) model exposes. Effort order: weakest // to strongest. -function openaiReasoningEfforts(apiId: string, releaseDate: string) { +// kilocode_change start - isFirstPartyOpenAI marks transports that only serve first-party OpenAI +// models (native Azure), where the deployment/model key may be opaque and the family gate must not apply +function openaiReasoningEfforts(apiId: string, releaseDate: string, isFirstPartyOpenAI = false) { + // kilocode_change end const id = apiId.toLowerCase() if (id.includes("deep-research")) return ["medium"] const chatEfforts = gpt5ChatReasoningEfforts(id) @@ -731,7 +734,7 @@ function openaiReasoningEfforts(apiId: string, releaseDate: string) { // kilocode_change start - `none`/`xhigh` are OpenAI rollout tiers only gpt-5-family // models accept; non-OpenAI models on an OpenAI-compatible base URL (e.g. Grok // via api.x.ai) reject them (issue #13342). See gpt5FamilyReasoningTiers. - if (!gpt5FamilyReasoningTiers(id)) return WIDELY_SUPPORTED_EFFORTS + if (!isFirstPartyOpenAI && !gpt5FamilyReasoningTiers(id)) return WIDELY_SUPPORTED_EFFORTS // kilocode_change end const efforts = [...WIDELY_SUPPORTED_EFFORTS] if (GPT5_FAMILY_RE.test(id)) efforts.unshift("minimal") @@ -1083,7 +1086,7 @@ export function variants(model: Provider.Model): Record [ + openaiReasoningEfforts(id, model.release_date, true).map((effort) => [ // kilocode_change - native Azure serves first-party OpenAI only effort, { reasoningEffort: effort, diff --git a/packages/opencode/test/kilocode/provider/transform.test.ts b/packages/opencode/test/kilocode/provider/transform.test.ts index a5d5d3c397a3..274b753d0680 100644 --- a/packages/opencode/test/kilocode/provider/transform.test.ts +++ b/packages/opencode/test/kilocode/provider/transform.test.ts @@ -115,4 +115,28 @@ describe("ProviderTransform.variants - gpt-5 rollout-tier gate", () => { ) expect(Object.keys(result)).toEqual(["none", "low", "medium", "high", "xhigh"]) }) + + test("native azure with an opaque deployment key keeps rollout-gated tiers", () => { + const result = ProviderTransform.variants( + model({ + id: "production", + providerID: "azure", + api: { id: "production", url: "", npm: "@ai-sdk/azure" }, + release_date: "2026-08-01", + }), + ) + expect(Object.keys(result)).toEqual(["none", "low", "medium", "high", "xhigh"]) + }) + + test("native azure with a gpt-5.5 model key and separate deployment id keeps rollout-gated tiers", () => { + const result = ProviderTransform.variants( + model({ + id: "gpt-5.5", + providerID: "azure", + api: { id: "my-gpt-5-5-deployment", url: "", npm: "@ai-sdk/azure" }, + release_date: "2026-08-01", + }), + ) + expect(Object.keys(result)).toEqual(["none", "low", "medium", "high", "xhigh"]) + }) }) From 2fd2735c36607018b32245a07992ced290a236c3 Mon Sep 17 00:00:00 2001 From: matt wilkie Date: Sun, 27 Sep 2026 04:07:24 -0700 Subject: [PATCH 04/12] test(cli): widen timeouts for flaky prompt tests on loaded CI --- packages/opencode/test/session/prompt.test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/opencode/test/session/prompt.test.ts b/packages/opencode/test/session/prompt.test.ts index 836c33d01f82..522358845daf 100644 --- a/packages/opencode/test/session/prompt.test.ts +++ b/packages/opencode/test/session/prompt.test.ts @@ -1835,7 +1835,7 @@ raceNoLLMServer.instance( const firstCreate = yield* Deferred.make() processorCreateStarted.push(firstCreate) const first = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) - yield* awaitWithTimeout(Deferred.await(firstCreate), "processor.create did not start for first turn") + yield* awaitWithTimeout(Deferred.await(firstCreate), "processor.create did not start for first turn", "10 seconds") yield* prompt.cancel(chat.id) const firstExit = yield* Fiber.await(first) @@ -1861,7 +1861,7 @@ raceNoLLMServer.instance( const secondCreate = yield* Deferred.make() processorCreateStarted.push(secondCreate) const second = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) - yield* awaitWithTimeout(Deferred.await(secondCreate), "processor.create did not start for second turn") + yield* awaitWithTimeout(Deferred.await(secondCreate), "processor.create did not start for second turn", "10 seconds") yield* prompt.cancel(chat.id) const secondExit = yield* Fiber.await(second) @@ -1896,7 +1896,7 @@ raceNoLLMServer.instance( } }), { config: cfg }, - 10_000, + 20_000, ) noLLMServer.instance( @@ -2214,7 +2214,7 @@ it.instance( yield* prompt.cancel(chat.id) yield* Fiber.await(fiber) }), - 10_000, + 20_000, ) noLLMServer.instance("assertNotBusy succeeds when idle", () => @@ -2243,7 +2243,7 @@ it.instance( const fiber = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) yield* llm.wait(1) - yield* waitForBusy(chat.id) + yield* waitForBusy(chat.id, "10 seconds") const exit = yield* prompt.shell({ sessionID: chat.id, agent: "build", command: "echo hi" }).pipe(Effect.exit) expect(Exit.isFailure(exit)).toBe(true) @@ -3366,7 +3366,7 @@ it.instance( Effect.promise(() => Suggestion.list()).pipe( Effect.map((items) => items.find((item) => item.sessionID === chat.id)), ), - "timed out waiting for suggestion request", + "timed out waiting for suggestion request", "10 seconds", ) yield* Effect.promise(() => Suggestion.accept({ requestID: request.id, index: 0 })) From 143a59586b9102478927cd20169780beb212337c Mon Sep 17 00:00:00 2001 From: matt wilkie Date: Sun, 27 Sep 2026 04:18:30 -0700 Subject: [PATCH 05/12] test(cli): annotate test timeout changes with kilocode_change markers --- packages/opencode/test/session/prompt.test.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/opencode/test/session/prompt.test.ts b/packages/opencode/test/session/prompt.test.ts index 522358845daf..fb6e5eb4cbf4 100644 --- a/packages/opencode/test/session/prompt.test.ts +++ b/packages/opencode/test/session/prompt.test.ts @@ -1835,7 +1835,7 @@ raceNoLLMServer.instance( const firstCreate = yield* Deferred.make() processorCreateStarted.push(firstCreate) const first = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) - yield* awaitWithTimeout(Deferred.await(firstCreate), "processor.create did not start for first turn", "10 seconds") + yield* awaitWithTimeout(Deferred.await(firstCreate), "processor.create did not start for first turn", "10 seconds") // kilocode_change yield* prompt.cancel(chat.id) const firstExit = yield* Fiber.await(first) @@ -1861,7 +1861,7 @@ raceNoLLMServer.instance( const secondCreate = yield* Deferred.make() processorCreateStarted.push(secondCreate) const second = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) - yield* awaitWithTimeout(Deferred.await(secondCreate), "processor.create did not start for second turn", "10 seconds") + yield* awaitWithTimeout(Deferred.await(secondCreate), "processor.create did not start for second turn", "10 seconds") // kilocode_change yield* prompt.cancel(chat.id) const secondExit = yield* Fiber.await(second) @@ -1896,8 +1896,8 @@ raceNoLLMServer.instance( } }), { config: cfg }, - 20_000, -) + 20_000, // kilocode_change +) // kilocode_change noLLMServer.instance( "cancel finalizes subtask tool state", @@ -2214,7 +2214,7 @@ it.instance( yield* prompt.cancel(chat.id) yield* Fiber.await(fiber) }), - 20_000, + 20_000, // kilocode_change ) noLLMServer.instance("assertNotBusy succeeds when idle", () => @@ -2243,7 +2243,7 @@ it.instance( const fiber = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) yield* llm.wait(1) - yield* waitForBusy(chat.id, "10 seconds") + yield* waitForBusy(chat.id, "10 seconds") // kilocode_change const exit = yield* prompt.shell({ sessionID: chat.id, agent: "build", command: "echo hi" }).pipe(Effect.exit) expect(Exit.isFailure(exit)).toBe(true) @@ -3366,7 +3366,7 @@ it.instance( Effect.promise(() => Suggestion.list()).pipe( Effect.map((items) => items.find((item) => item.sessionID === chat.id)), ), - "timed out waiting for suggestion request", "10 seconds", + "timed out waiting for suggestion request", "10 seconds", // kilocode_change ) yield* Effect.promise(() => Suggestion.accept({ requestID: request.id, index: 0 })) From 22d75a3842a821b64f30daa4e505a15882805c6d Mon Sep 17 00:00:00 2001 From: matt wilkie Date: Mon, 28 Sep 2026 13:25:20 -0700 Subject: [PATCH 06/12] fix(cli): gate custom-provider fallback rollout tiers to gpt-5 family --- .../src/kilocode/provider/provider.ts | 11 ++++++++- .../provider-reasoning-options.test.ts | 23 ++++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/kilocode/provider/provider.ts b/packages/opencode/src/kilocode/provider/provider.ts index e0ae755451ed..5f84502eba53 100644 --- a/packages/opencode/src/kilocode/provider/provider.ts +++ b/packages/opencode/src/kilocode/provider/provider.ts @@ -16,6 +16,7 @@ import { Effect, Schema } from "effect" import type { LanguageModelV3 } from "@ai-sdk/provider" import { mapValues, omit, pickBy } from "remeda" import { reasoningSummary } from "./reasoning-summary" +import { gpt5FamilyReasoningTiers } from "./transform" import type { Provider } from "@/provider/provider" import type { Auth } from "@/auth" import type { Config } from "@/config/config" @@ -102,6 +103,7 @@ export function patchConfigModel(cfg: any, existing: any) { const CUSTOM_PROVIDER_PACKAGES = new Set(["@ai-sdk/openai-compatible", "@ai-sdk/openai", "@ai-sdk/anthropic"]) const FALLBACK_EFFORTS = ["none", "low", "medium", "high", "xhigh", "max"] +const WIDELY_SUPPORTED_FALLBACK_EFFORTS = ["low", "medium", "high"] type Variants = NonNullable type Generate = (model: Provider.Model) => Variants @@ -113,8 +115,15 @@ export function customProviderVariants(model: Provider.Model, npm: unknown, gene if (Object.keys(variants).length > 0) return variants if (!model.capabilities.reasoning || !supported) return variants + // The gpt-5 rollout tiers (`none`, `xhigh`) are only accepted by gpt-5-family + // models. A custom provider id that never reaches openaiReasoningEfforts + // (e.g. qwen/deepseek/minimax/glm/kimi short-circuited to {} in variants()) + // must not regain those tiers through this fallback (issue #13342). + const efforts = + npm === "@ai-sdk/openai" && !gpt5FamilyReasoningTiers(model.api.id) ? WIDELY_SUPPORTED_FALLBACK_EFFORTS : FALLBACK_EFFORTS + return Object.fromEntries( - FALLBACK_EFFORTS.map((effort) => { + efforts.map((effort) => { if (npm === "@ai-sdk/anthropic") { return [effort, effort === "none" ? { thinking: { type: "disabled" } } : { effort }] } diff --git a/packages/opencode/test/kilocode/provider-reasoning-options.test.ts b/packages/opencode/test/kilocode/provider-reasoning-options.test.ts index 06f4fd952cbe..87ba7a64a9d7 100644 --- a/packages/opencode/test/kilocode/provider-reasoning-options.test.ts +++ b/packages/opencode/test/kilocode/provider-reasoning-options.test.ts @@ -142,8 +142,9 @@ describe("ProviderTransform.reasoningVariants - models.dev reasoning_options", ( describe("custom provider fallback reasoning efforts", () => { const efforts = ["none", "low", "medium", "high", "xhigh", "max"] + const gated = ["low", "medium", "high"] - for (const npm of ["@ai-sdk/openai-compatible", "@ai-sdk/openai", "@ai-sdk/anthropic"]) { + for (const npm of ["@ai-sdk/openai-compatible", "@ai-sdk/anthropic"]) { test(`${npm} exposes broad efforts after heuristics fail`, () => { const model = mockModel({ id: "qwen-custom", api: { id: "qwen-custom", url: "https://api.test.com", npm } }) const generated = ProviderTransform.variants({ ...model, variants: {} }) @@ -162,6 +163,26 @@ describe("custom provider fallback reasoning efforts", () => { }) } + test("@ai-sdk/openai gates rollout tiers for non-gpt-5 ids (issue #13342)", () => { + const npm = "@ai-sdk/openai" + const model = mockModel({ id: "qwen-custom", api: { id: "qwen-custom", url: "https://api.test.com", npm } }) + expect(ProviderTransform.variants({ ...model, variants: {} })).toEqual({}) + + const result = customProviderVariants(model, npm, ProviderTransform.variants) + expect(Object.keys(result)).toEqual(gated) + expect(result.low?.reasoningEffort).toBe("low") + expect(result.high?.reasoningEffort).toBe("high") + }) + + test("@ai-sdk/openai keeps rollout tiers for gpt-5 ids", () => { + const npm = "@ai-sdk/openai" + const model = mockModel({ id: "gpt-5.5", api: { id: "gpt-5.5", url: "https://api.test.com", npm } }) + const result = customProviderVariants(model, npm, () => ({})) + expect(Object.keys(result)).toEqual(efforts) + expect(result.none?.reasoningEffort).toBe("none") + expect(result.xhigh?.reasoningEffort).toBe("xhigh") + }) + for (const id of ["glm-custom", "kimi-custom", "minimax-custom"]) { test(`openai-compatible custom provider uses broad efforts for ${id}`, () => { const npm = "@ai-sdk/openai-compatible" From 97db6665c06807cd22bfdf5eae3d69bb0617ce80 Mon Sep 17 00:00:00 2001 From: maphew <486200+maphew@users.noreply.github.com> Date: Sun, 11 Oct 2026 04:53:10 +0000 Subject: [PATCH 07/12] fix(cli): treat providerID openai as first-party, drop unrelated timeout scope --- packages/opencode/src/provider/transform.ts | 4 +++- .../test/kilocode/provider/transform.test.ts | 24 +++++++++++++++++++ packages/opencode/test/session/prompt.test.ts | 14 +++++------ 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index a4c4b01fc610..170f155ba10a 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -1110,7 +1110,9 @@ export function variants(model: Provider.Model): Record [ effort, diff --git a/packages/opencode/test/kilocode/provider/transform.test.ts b/packages/opencode/test/kilocode/provider/transform.test.ts index 274b753d0680..225a0a28736d 100644 --- a/packages/opencode/test/kilocode/provider/transform.test.ts +++ b/packages/opencode/test/kilocode/provider/transform.test.ts @@ -139,4 +139,28 @@ describe("ProviderTransform.variants - gpt-5 rollout-tier gate", () => { ) expect(Object.keys(result)).toEqual(["none", "low", "medium", "high", "xhigh"]) }) + + test("first-party openai with a post-cutoff id keeps rollout tiers via release-date logic", () => { + const result = ProviderTransform.variants( + model({ + id: "gpt-6", + providerID: "openai", + api: { id: "gpt-6", url: "https://api.openai.com", npm: "@ai-sdk/openai" }, + release_date: "2027-01-01", + }), + ) + expect(Object.keys(result)).toEqual(["none", "low", "medium", "high", "xhigh"]) + }) + + test("third-party openai-compatible with a post-cutoff id stays gated to widely supported efforts", () => { + const result = ProviderTransform.variants( + model({ + id: "gpt-6", + providerID: "custom", + api: { id: "gpt-6", url: "https://api.custom.com", npm: "@ai-sdk/openai" }, + release_date: "2027-01-01", + }), + ) + expect(Object.keys(result)).toEqual(["low", "medium", "high"]) + }) }) diff --git a/packages/opencode/test/session/prompt.test.ts b/packages/opencode/test/session/prompt.test.ts index fb6e5eb4cbf4..836c33d01f82 100644 --- a/packages/opencode/test/session/prompt.test.ts +++ b/packages/opencode/test/session/prompt.test.ts @@ -1835,7 +1835,7 @@ raceNoLLMServer.instance( const firstCreate = yield* Deferred.make() processorCreateStarted.push(firstCreate) const first = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) - yield* awaitWithTimeout(Deferred.await(firstCreate), "processor.create did not start for first turn", "10 seconds") // kilocode_change + yield* awaitWithTimeout(Deferred.await(firstCreate), "processor.create did not start for first turn") yield* prompt.cancel(chat.id) const firstExit = yield* Fiber.await(first) @@ -1861,7 +1861,7 @@ raceNoLLMServer.instance( const secondCreate = yield* Deferred.make() processorCreateStarted.push(secondCreate) const second = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) - yield* awaitWithTimeout(Deferred.await(secondCreate), "processor.create did not start for second turn", "10 seconds") // kilocode_change + yield* awaitWithTimeout(Deferred.await(secondCreate), "processor.create did not start for second turn") yield* prompt.cancel(chat.id) const secondExit = yield* Fiber.await(second) @@ -1896,8 +1896,8 @@ raceNoLLMServer.instance( } }), { config: cfg }, - 20_000, // kilocode_change -) // kilocode_change + 10_000, +) noLLMServer.instance( "cancel finalizes subtask tool state", @@ -2214,7 +2214,7 @@ it.instance( yield* prompt.cancel(chat.id) yield* Fiber.await(fiber) }), - 20_000, // kilocode_change + 10_000, ) noLLMServer.instance("assertNotBusy succeeds when idle", () => @@ -2243,7 +2243,7 @@ it.instance( const fiber = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild) yield* llm.wait(1) - yield* waitForBusy(chat.id, "10 seconds") // kilocode_change + yield* waitForBusy(chat.id) const exit = yield* prompt.shell({ sessionID: chat.id, agent: "build", command: "echo hi" }).pipe(Effect.exit) expect(Exit.isFailure(exit)).toBe(true) @@ -3366,7 +3366,7 @@ it.instance( Effect.promise(() => Suggestion.list()).pipe( Effect.map((items) => items.find((item) => item.sessionID === chat.id)), ), - "timed out waiting for suggestion request", "10 seconds", // kilocode_change + "timed out waiting for suggestion request", ) yield* Effect.promise(() => Suggestion.accept({ requestID: request.id, index: 0 })) From f0b445b6be4e2b779bdb046b467db8bfe4488eeb Mon Sep 17 00:00:00 2001 From: matt wilkie Date: Sat, 17 Oct 2026 07:46:11 -0700 Subject: [PATCH 08/12] fix(cli): respect effective endpoint in first-party reasoning-tier exemption --- .../src/kilocode/provider/transform.ts | 36 +++++++++++++ packages/opencode/src/provider/provider.ts | 27 ++++++++-- packages/opencode/src/provider/transform.ts | 10 ++-- .../test/kilocode/provider/transform.test.ts | 52 +++++++++++++++++++ 4 files changed, 117 insertions(+), 8 deletions(-) diff --git a/packages/opencode/src/kilocode/provider/transform.ts b/packages/opencode/src/kilocode/provider/transform.ts index 2b6d1121a76d..9e16eb3a6f59 100644 --- a/packages/opencode/src/kilocode/provider/transform.ts +++ b/packages/opencode/src/kilocode/provider/transform.ts @@ -20,3 +20,39 @@ const GPT5_FAMILY_PREFIX_RE = /(?:^|\/|\.|-)gpt-5(?:[.-]|$)/ export function gpt5FamilyReasoningTiers(id: string) { return GPT5_FAMILY_PREFIX_RE.test(id.toLowerCase()) } + +type EndpointModel = { + providerID: string + api: { url?: string } + options?: Record +} + +/** + * True when providerID "openai" genuinely serves first-party OpenAI models. + * A baseURL override to a third party (e.g. xAI) under that id serves + * non-OpenAI models that reject the rollout-gated tiers (issue #13342), so the + * exemption follows the effective endpoint, not the id alone. Model options + * shadow provider options, matching resolveSDK/getModel precedence; with no + * endpoint information the first-party default is preserved for forward + * compatibility. + */ +export function isFirstPartyOpenAIEndpoint(model: EndpointModel, options: Record): boolean { + if (model.providerID !== "openai") return false + const override = + model.options?.["providerEndpointOverride"] ?? + model.options?.["endpoint"] ?? + model.options?.["baseURL"] ?? + options["providerEndpointOverride"] ?? + options["endpoint"] ?? + options["baseURL"] + const endpoint = typeof override === "string" && override !== "" ? override : model.api.url + if (typeof endpoint !== "string" || endpoint === "") return true + let host: string + try { + host = new URL(endpoint).hostname.toLowerCase() + } catch { + // Unexpanded config placeholders ({env:...}, ${...}) cannot be judged. + return true + } + return host === "openai.com" || host.endsWith(".openai.com") +} diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 73e63769e56b..cd47a0cd2efd 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -50,6 +50,7 @@ import { wrapFirstByte, } from "@/kilocode/provider/provider" import * as ModelsRefresh from "@/kilocode/provider/models-refresh" +import { gpt5FamilyReasoningTiers, isFirstPartyOpenAIEndpoint } from "@/kilocode/provider/transform" // kilocode_change - endpoint-aware reasoning-tier gating import { bedrockAuth, providerKey, vertexAuth, vertexCredentials, vertexOptions } from "@/kilocode/provider/cloud-auth" // kilocode_change end import { ProviderError } from "./error" @@ -1635,10 +1636,13 @@ const layer = Layer.effect( ...patchKiloConfigModel(model, existingModel), // kilocode_change } // kilocode_change start + // Effective endpoint for reasoning-tier gating: provider options shadowed by + // model options, matching resolveSDK/getModel precedence. + const endpointOptions = { ...parsed.options, ...parsedModel.options } const baseGenerate = (m: typeof parsedModel) => existingModel?.api.npm === m.api.npm - ? (existingModel.variants ?? ProviderTransform.variants(m)) - : ProviderTransform.variants(m) + ? (existingModel.variants ?? ProviderTransform.variants(m, endpointOptions)) + : ProviderTransform.variants(m, endpointOptions) const generated = customProviderVariants( parsedModel, model.provider?.npm ?? provider.npm, @@ -1801,9 +1805,24 @@ const layer = Layer.effect( ) delete provider.models[modelID] - if (model.variants === undefined) { - model.variants = mapValues(ProviderTransform.variants(model), (v) => v) + // kilocode_change start - effective endpoint for reasoning-tier gating + // (provider options shadowed by model options, matching resolveSDK/getModel + // precedence). Variants baked at catalog time predate endpoint overrides, so a + // rerouted provider keeps a stale first-party exemption: none/xhigh on a + // non-gpt-5 id could only come from that exemption, which the override + // invalidates (issue #13342). Regenerate under the effective endpoint; + // explicit config variants re-apply below. + const endpointOptions = { ...provider.options, ...model.options } + if ( + model.variants === undefined || + (model.api.npm === "@ai-sdk/openai" && + (model.variants.none !== undefined || model.variants.xhigh !== undefined) && + !gpt5FamilyReasoningTiers(model.api.id) && + !isFirstPartyOpenAIEndpoint(model, endpointOptions)) + ) { + model.variants = mapValues(ProviderTransform.variants(model, endpointOptions), (v) => v) } + // kilocode_change end const configVariants = configProvider?.models?.[modelID]?.variants if (configVariants && model.variants) { diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 170f155ba10a..a08eb2e507d9 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -7,7 +7,7 @@ import { iife } from "@/util/iife" import { kiloProviderOptions } from "@/kilocode/provider-options" import { isLing } from "@/kilocode/model-match" // kilocode_change import { reasoningSummary } from "@/kilocode/provider/reasoning-summary" // kilocode_change -import { gpt5FamilyReasoningTiers } from "@/kilocode/provider/transform" // kilocode_change +import { gpt5FamilyReasoningTiers, isFirstPartyOpenAIEndpoint } from "@/kilocode/provider/transform" // kilocode_change type Modality = NonNullable["input"][number] @@ -858,7 +858,7 @@ function googleThinkingVariants(model: Provider.Model): Record> { +export function variants(model: Provider.Model, options: Record = {}): Record> { // kilocode_change start if ( ["@kilocode/kilo-gateway", "@ai-sdk/openai-compatible"].includes(model.api.npm) && @@ -1111,8 +1111,10 @@ export function variants(model: Provider.Model): Record [ effort, diff --git a/packages/opencode/test/kilocode/provider/transform.test.ts b/packages/opencode/test/kilocode/provider/transform.test.ts index 225a0a28736d..64197a331963 100644 --- a/packages/opencode/test/kilocode/provider/transform.test.ts +++ b/packages/opencode/test/kilocode/provider/transform.test.ts @@ -163,4 +163,56 @@ describe("ProviderTransform.variants - gpt-5 rollout-tier gate", () => { ) expect(Object.keys(result)).toEqual(["low", "medium", "high"]) }) + + test("openai id with an xAI baseURL override loses rollout tiers for non-GPT models", () => { + const result = ProviderTransform.variants( + model({ + id: "grok-4.6", + providerID: "openai", + api: { id: "grok-4.6", url: "https://api.openai.com", npm: "@ai-sdk/openai" }, + release_date: "2026-08-12", + }), + { baseURL: "https://api.x.ai/v1" }, + ) + expect(Object.keys(result)).toEqual(["low", "medium", "high"]) + }) + + test("openai id with a first-party baseURL keeps rollout tiers", () => { + const result = ProviderTransform.variants( + model({ + id: "gpt-6", + providerID: "openai", + api: { id: "gpt-6", url: "https://api.openai.com", npm: "@ai-sdk/openai" }, + release_date: "2027-01-01", + }), + { baseURL: "https://api.openai.com/v1" }, + ) + expect(Object.keys(result)).toEqual(["none", "low", "medium", "high", "xhigh"]) + }) + + test("openai id with a third-party catalog api url stays gated without an override", () => { + const result = ProviderTransform.variants( + model({ + id: "grok-4.6", + providerID: "openai", + api: { id: "grok-4.6", url: "https://api.x.ai/v1", npm: "@ai-sdk/openai" }, + release_date: "2026-08-12", + }), + ) + expect(Object.keys(result)).toEqual(["low", "medium", "high"]) + }) + + test("model-level baseURL shadows a first-party provider baseURL", () => { + const result = ProviderTransform.variants( + model({ + id: "grok-4.6", + providerID: "openai", + api: { id: "grok-4.6", url: "https://api.openai.com", npm: "@ai-sdk/openai" }, + options: { baseURL: "https://api.x.ai/v1" }, + release_date: "2026-08-12", + }), + { baseURL: "https://api.openai.com/v1" }, + ) + expect(Object.keys(result)).toEqual(["low", "medium", "high"]) + }) }) From 835cb35c57b8fa4a4d0c580d8731b2b9d5ad81fd Mon Sep 17 00:00:00 2001 From: matt wilkie Date: Sat, 17 Oct 2026 07:56:25 -0700 Subject: [PATCH 09/12] fix(cli): annotate variants signature for upstream check --- packages/opencode/src/provider/transform.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index a08eb2e507d9..d712fc565d79 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -858,7 +858,12 @@ function googleThinkingVariants(model: Provider.Model): Record = {}): Record> { +// kilocode_change start - options carries the effective endpoint for reasoning-tier gating +export function variants( + model: Provider.Model, + options: Record = {}, +): Record> { +// kilocode_change end // kilocode_change start if ( ["@kilocode/kilo-gateway", "@ai-sdk/openai-compatible"].includes(model.api.npm) && From 7a60c95cca4e77fc119ccffeb3617fefa05bac9b Mon Sep 17 00:00:00 2001 From: matt wilkie Date: Thu, 22 Oct 2026 13:42:08 -0700 Subject: [PATCH 10/12] fix(provider): gate reasoning tiers when the endpoint cannot be parsed --- .../opencode/src/kilocode/provider/transform.ts | 6 ++++-- .../test/kilocode/provider/transform.test.ts | 13 +++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/kilocode/provider/transform.ts b/packages/opencode/src/kilocode/provider/transform.ts index 9e16eb3a6f59..eb449f5414c3 100644 --- a/packages/opencode/src/kilocode/provider/transform.ts +++ b/packages/opencode/src/kilocode/provider/transform.ts @@ -51,8 +51,10 @@ export function isFirstPartyOpenAIEndpoint(model: EndpointModel, options: Record try { host = new URL(endpoint).hostname.toLowerCase() } catch { - // Unexpanded config placeholders ({env:...}, ${...}) cannot be judged. - return true + // A configured but unexpanded endpoint ({env:...}, ${...}) cannot be + // proven first-party. Gate conservatively: offering tiers the endpoint may + // reject is worse than omitting tiers a first-party endpoint would accept. + return false } return host === "openai.com" || host.endsWith(".openai.com") } diff --git a/packages/opencode/test/kilocode/provider/transform.test.ts b/packages/opencode/test/kilocode/provider/transform.test.ts index 64197a331963..a7eec99ee6bd 100644 --- a/packages/opencode/test/kilocode/provider/transform.test.ts +++ b/packages/opencode/test/kilocode/provider/transform.test.ts @@ -215,4 +215,17 @@ describe("ProviderTransform.variants - gpt-5 rollout-tier gate", () => { ) expect(Object.keys(result)).toEqual(["low", "medium", "high"]) }) + + test("unexpanded baseURL placeholder is gated conservatively", () => { + const result = ProviderTransform.variants( + model({ + id: "grok-4.6", + providerID: "openai", + api: { id: "grok-4.6", url: "https://api.openai.com", npm: "@ai-sdk/openai" }, + release_date: "2026-08-12", + }), + { baseURL: "https://{env:XAI_HOST}/v1" }, + ) + expect(Object.keys(result)).toEqual(["low", "medium", "high"]) + }) }) From d880cf703d43cfa9a54e9209938adefca0e59b52 Mon Sep 17 00:00:00 2001 From: matt wilkie Date: Mon, 26 Oct 2026 07:38:53 -0700 Subject: [PATCH 11/12] chore(ci): retrigger checks after unrelated serve IPv6 timing failure From ce4e5770c2c907acdf2dd66bf451b72ddfd3481e Mon Sep 17 00:00:00 2001 From: matt wilkie Date: Tue, 27 Oct 2026 07:29:58 -0700 Subject: [PATCH 12/12] chore(ci): retrigger unit tests after runner timing failures