@oh-my-pi/pi-catalog 18.0.0 → 18.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,27 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.0.2] - 2026-08-23
6
+
7
+ ### Fixed
8
+
9
+ - Fixed OpenRouter auxiliary requests (e.g. session-title generation) failing with `400 Reasoning is mandatory for this endpoint and cannot be disabled` on mandatory-reasoning models such as `stealth/ox-alpha`. Live discovery now honors the endpoint's `reasoning.mandatory` flag, clamping thinking-off to the lowest supported effort instead of sending `reasoning: { enabled: false }` ([#9415](https://github.com/can1357/oh-my-pi/issues/9415)).
10
+
11
+ ## [18.0.1] - 2026-08-23
12
+
13
+ ### Added
14
+
15
+ - Fixed `google-gemini-cli` model refresh returning only bundled models for Gemini Code Assist Standard accounts, whose credential is not authorized for the Antigravity `fetchAvailableModels` endpoint (HTTP 403). Discovery now falls back to the account's own `retrieveUserQuota` list on Cloud Code Assist, surfacing models such as `gemini-3.5-flash` ([#9315](https://github.com/can1357/oh-my-pi/issues/9315)).
16
+ - Added Amazon Bedrock guardrail metadata to model definitions for Converse requests.
17
+
18
+ ### Fixed
19
+
20
+ - Fixed `opencode-go/ox-alpha-free` sending `reasoning_effort: "xhigh"` for the top thinking tier, which the OpenCode Go gateway rejects; the model now uses the gateway's wire-exact `low`/`high`/`max` ladder with mandatory thinking so `--thinking max` reaches the real max tier ([#9349](https://github.com/can1357/oh-my-pi/issues/9349)).
21
+ - Fixed Venice-hosted Qwen models (e.g. `venice/qwen3-6-35b-a3b`) failing with `400 Invalid request parameters`. Reasoning levels now use the accepted OpenAI-style `reasoning_effort` field, while Thinking Off sends Venice's explicit `venice_parameters.disable_thinking` flag ([#9345](https://github.com/can1357/oh-my-pi/issues/9345)).
22
+ - Fixed gateway-first OpenCode Zen and Go models missing context, output, image, and reasoning metadata by enriching live discovery from the current stencil catalog ([#9272](https://github.com/can1357/oh-my-pi/issues/9272)).
23
+ - Fixed `opencode-go/deepseek-v4-flash` exposing the generic `minimal`/`low`/`medium`/`high`/`xhigh` thinking ladder instead of DeepSeek V4's real `low`/`high`/`max` tiers. The model is pinned to the Responses transport (the Go gateway serves it only at `/responses`), which the DeepSeek effort branch did not admit, so it fell through to the default ladder; the branch now covers the `openai-responses` transport like every other host ([#9134](https://github.com/can1357/oh-my-pi/issues/9134)).
24
+ - Fixed protobuf map decoding corrupting entries when a key is `__proto__`, which dropped that argument and replayed spurious numeric arguments ([#9394](https://github.com/can1357/oh-my-pi/issues/9394)).
25
+
5
26
  ## [18.0.0] - 2026-08-22
6
27
 
7
28
  ### Added
@@ -0,0 +1,33 @@
1
+ import type { ModelSpec } from "../types.js";
2
+ import { type VariantCollapseTable } from "../variant-collapse.js";
3
+ /**
4
+ * Options for the Gemini CLI quota-based discovery fallback.
5
+ */
6
+ export interface FetchGeminiCliQuotaModelsOptions {
7
+ /** OAuth access token sent as `Authorization: Bearer <token>`. */
8
+ token: string;
9
+ /** Cloud Code Assist endpoint. Defaults to `https://cloudcode-pa.googleapis.com`. */
10
+ endpoint?: string;
11
+ /** Pre-resolved GCP project id; otherwise discovered via `loadCodeAssist`. */
12
+ projectId?: string;
13
+ /** Optional abort signal for request cancellation. */
14
+ signal?: AbortSignal;
15
+ /** Optional fetch implementation override for tests. */
16
+ fetcher?: typeof fetch;
17
+ /** Effort-tier collapse table applied to the discovered list. */
18
+ collapseTable?: VariantCollapseTable;
19
+ }
20
+ /**
21
+ * Discovers the Gemini models available to a `google-gemini-cli` credential via
22
+ * the account's own `retrieveUserQuota` endpoint on Cloud Code Assist.
23
+ *
24
+ * This is the fallback for accounts whose credential is not authorized for the
25
+ * Antigravity `fetchAvailableModels` endpoint (e.g. Gemini Code Assist Standard
26
+ * tiers, which return HTTP 403 there). Quota buckets carry only model ids, so
27
+ * metadata is filled from the bundled catalog where the id is known and
28
+ * synthesized with Gemini CLI defaults otherwise.
29
+ *
30
+ * Returns `null` on network/payload/auth failure (the caller keeps the bundled
31
+ * catalog). Returns `[]` when the quota response lists no usable Gemini models.
32
+ */
33
+ export declare function fetchGeminiCliQuotaModels(options: FetchGeminiCliQuotaModelsOptions): Promise<ModelSpec<"google-gemini-cli">[] | null>;
@@ -1,6 +1,7 @@
1
1
  export * from "./antigravity.js";
2
2
  export * from "./codex.js";
3
3
  export * from "./gemini.js";
4
+ export * from "./gemini-cli.js";
4
5
  export * from "./gitlab-duo-workflow.js";
5
6
  export * from "./openai-compatible.js";
6
7
  export * from "./protobuf.js";
@@ -111,6 +111,11 @@ export declare const KNOWN_HOSTS: {
111
111
  readonly providers: readonly ["nvidia"];
112
112
  readonly urlMarkers: readonly ["integrate.api.nvidia.com"];
113
113
  };
114
+ /** Venice AI (`api.venice.ai`). OpenAI-compatible; drives reasoning via top-level `reasoning_effort` (and `venice_parameters.disable_thinking`), and rejects DashScope's top-level `enable_thinking` with a 400 (`additionalProperties: false` request schema). */
115
+ readonly venice: {
116
+ readonly providers: readonly ["venice"];
117
+ readonly urlMarkers: readonly ["api.venice.ai"];
118
+ };
114
119
  readonly moonshotNative: {
115
120
  readonly providers: readonly ["moonshot", "kimi-code"];
116
121
  readonly urlMarkers: readonly ["api.moonshot.ai", "api.kimi.com"];
@@ -18,6 +18,8 @@ export interface GoogleAntigravityModelManagerConfig {
18
18
  }
19
19
  export interface GoogleGeminiCliModelManagerConfig {
20
20
  oauthToken?: string;
21
+ /** GCP project id required by Workspace/Standard credentials for quota discovery. */
22
+ projectId?: string;
21
23
  endpoint?: string;
22
24
  fetch?: FetchImpl;
23
25
  }
@@ -122,7 +122,7 @@ export interface Usage {
122
122
  };
123
123
  }
124
124
  export type OpenAIReasoningFormat = "openai" | "openrouter" | "zai" | "kimi" | "qwen" | "qwen-chat-template";
125
- export type OpenAIReasoningDisableMode = "omit" | "lowest-effort" | "none-effort" | "openrouter-enabled-false" | "zai-thinking-disabled" | "qwen-enable-thinking-false" | "qwen-template-false";
125
+ export type OpenAIReasoningDisableMode = "omit" | "lowest-effort" | "none-effort" | "openrouter-enabled-false" | "venice-disable-thinking" | "zai-thinking-disabled" | "qwen-enable-thinking-false" | "qwen-template-false";
126
126
  export type OpenAIStreamMarkupHealingPattern = "kimi" | "dsml" | "qwen" | "thinking";
127
127
  /**
128
128
  * Compatibility settings for openai-completions API.
@@ -852,6 +852,18 @@ export interface Model<TApi extends Api = Api> {
852
852
  * `options.isOAuth = true` for the underlying provider call.
853
853
  */
854
854
  isOAuth?: boolean;
855
+ /**
856
+ * Amazon Bedrock Guardrail id or ARN attached to every Converse request for
857
+ * this model. Set from `providers.amazon-bedrock.guardrailIdentifier`; the
858
+ * streaming layer forwards it as `options.guardrailIdentifier` so accounts
859
+ * that gate `bedrock:InvokeModel*` on the `bedrock:GuardrailIdentifier`
860
+ * condition key stop returning an explicit deny.
861
+ */
862
+ guardrailIdentifier?: string;
863
+ /** Bedrock guardrail version. Defaults to `"DRAFT"` at request time when unset. */
864
+ guardrailVersion?: string;
865
+ /** Bedrock guardrail trace verbosity. */
866
+ guardrailTrace?: "enabled" | "disabled" | "enabled_full";
855
867
  }
856
868
  /**
857
869
  * A model as authored by configs, bundled catalogs, and discovery — the input
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-catalog",
4
- "version": "18.0.0",
4
+ "version": "18.0.3",
5
5
  "description": "Model catalog for omp: bundled model database, provider discovery descriptors, model identity, classification, and equivalence",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Stencil Labs, Inc.",
@@ -34,11 +34,11 @@
34
34
  "gen:proto": "bun scripts/generate-protocols.ts"
35
35
  },
36
36
  "dependencies": {
37
- "@oh-my-pi/omptype": "18.0.0",
38
- "@oh-my-pi/pi-utils": "18.0.0"
37
+ "@oh-my-pi/omptype": "18.0.3",
38
+ "@oh-my-pi/pi-utils": "18.0.3"
39
39
  },
40
40
  "devDependencies": {
41
- "@oh-my-pi/pi-ai": "18.0.0",
41
+ "@oh-my-pi/pi-ai": "18.0.3",
42
42
  "@types/bun": "^1.3.14"
43
43
  },
44
44
  "engines": {
@@ -312,6 +312,7 @@ export function buildOpenAICompat(spec: ModelSpec<"openai-completions">): Resolv
312
312
  modelMatchesHost(hostModel, "anthropic") || isClaudeModelId(spec.id) || isAnthropicNamespacedModelId(spec.id);
313
313
  const isAlibaba = modelMatchesHost(hostModel, "alibabaDashscope");
314
314
  const isNvidiaNim = modelMatchesHost(hostModel, "nvidia");
315
+ const isVenice = modelMatchesHost(hostModel, "venice");
315
316
  const isQwen = isQwenModelId(spec.id);
316
317
  // DeepSeek V4 (and other reasoning-capable DeepSeek models) reject follow-up requests in
317
318
  // thinking mode unless prior assistant tool-call turns include `reasoning_content`. The
@@ -467,7 +468,7 @@ export function buildOpenAICompat(spec: ModelSpec<"openai-completions">): Resolv
467
468
  ? "openrouter"
468
469
  : isQwen && (isNvidiaNim || provider === "vllm")
469
470
  ? "qwen-chat-template"
470
- : isQwen && isFireworks
471
+ : isQwen && (isFireworks || isVenice)
471
472
  ? "openai"
472
473
  : isAlibaba || isQwen
473
474
  ? "qwen"
@@ -528,7 +529,7 @@ export function buildOpenAICompat(spec: ModelSpec<"openai-completions">): Resolv
528
529
  // (issue #2299).
529
530
  thinkingFormat,
530
531
  kimiApiFormat: undefined,
531
- reasoningDisableMode: resolveReasoningDisableMode(thinkingFormat),
532
+ reasoningDisableMode: isVenice ? "venice-disable-thinking" : resolveReasoningDisableMode(thinkingFormat),
532
533
  omitReasoningEffort: false,
533
534
  includeEncryptedReasoning: true,
534
535
  filterReasoningHistory: isOpenRouter && isAnthropicModel,
@@ -648,7 +649,9 @@ export function buildOpenAICompat(spec: ModelSpec<"openai-completions">): Resolv
648
649
  ? "omit"
649
650
  : isDirectDeepseekReasoning
650
651
  ? "zai-thinking-disabled"
651
- : resolveReasoningDisableMode(compat.thinkingFormat);
652
+ : isVenice
653
+ ? "venice-disable-thinking"
654
+ : resolveReasoningDisableMode(compat.thinkingFormat);
652
655
  }
653
656
  if (spec.compat?.omitReasoningEffort === undefined && !compat.supportsReasoningEffort) {
654
657
  compat.omitReasoningEffort = true;
@@ -666,7 +669,9 @@ export function buildOpenAICompat(spec: ModelSpec<"openai-completions">): Resolv
666
669
  const variant: ResolvedOpenAICompat = { ...compat };
667
670
  applyCompatOverrides(variant, whenThinkingPolicy);
668
671
  if (whenThinkingPolicy.reasoningDisableMode === undefined) {
669
- variant.reasoningDisableMode = resolveReasoningDisableMode(variant.thinkingFormat);
672
+ variant.reasoningDisableMode = isVenice
673
+ ? "venice-disable-thinking"
674
+ : resolveReasoningDisableMode(variant.thinkingFormat);
670
675
  }
671
676
  if (whenThinkingPolicy.omitReasoningEffort === undefined && !variant.supportsReasoningEffort) {
672
677
  variant.omitReasoningEffort = true;
@@ -0,0 +1,198 @@
1
+ import { type } from "@oh-my-pi/omptype";
2
+ import type { FetchImpl } from "@oh-my-pi/pi-utils";
3
+ import { parseGeminiModel, semverGte } from "../identity/classify";
4
+ import { isGeminiModelId } from "../identity/family";
5
+ import { createBundledReferenceMap } from "../provider-models/bundled-references";
6
+ import type { ModelSpec } from "../types";
7
+ import { discoveryFetch } from "../utils";
8
+ import {
9
+ collapseEffortVariants,
10
+ GEMINI_CLI_VARIANT_COLLAPSE_TABLE,
11
+ type VariantCollapseTable,
12
+ } from "../variant-collapse";
13
+ import { getGeminiCliHeaders } from "../wire/gemini-headers";
14
+
15
+ const DEFAULT_ENDPOINT = "https://cloudcode-pa.googleapis.com";
16
+ const LOAD_CODE_ASSIST_PATH = "/v1internal:loadCodeAssist";
17
+ const RETRIEVE_USER_QUOTA_PATH = "/v1internal:retrieveUserQuota";
18
+
19
+ // All current Gemini CLI models ship a 1M-token context and 65,536-token
20
+ // output ceiling; used only for quota-listed ids the bundled catalog does not
21
+ // already describe. Ids present in the bundle keep their real limits.
22
+ const DEFAULT_CONTEXT_WINDOW = 1_048_576;
23
+ const DEFAULT_MAX_TOKENS = 65_536;
24
+
25
+ /** Gemini generations that expose thinking on Cloud Code Assist. */
26
+ const REASONING_MIN_VERSION = "2.5";
27
+
28
+ const LoadCodeAssistResponseSchema = type({
29
+ "cloudaicompanionProject?": type("unknown").pipe(value => {
30
+ if (typeof value === "string") return value;
31
+ if (value && typeof value === "object" && "id" in value && typeof value.id === "string") {
32
+ return value.id;
33
+ }
34
+ return undefined;
35
+ }),
36
+ });
37
+
38
+ const QuotaBucketSchema = type({
39
+ "modelId?": type("unknown").pipe(value => (typeof value === "string" ? value : undefined)),
40
+ });
41
+
42
+ const RetrieveUserQuotaResponseSchema = type({
43
+ "buckets?": type("unknown").pipe(value => {
44
+ if (!Array.isArray(value)) return undefined;
45
+ const buckets: Array<{ modelId?: string }> = [];
46
+ for (const bucket of value) {
47
+ const parsed = QuotaBucketSchema(bucket);
48
+ if (!(parsed instanceof type.errors)) {
49
+ buckets.push(parsed);
50
+ }
51
+ }
52
+ return buckets;
53
+ }),
54
+ });
55
+
56
+ /**
57
+ * Options for the Gemini CLI quota-based discovery fallback.
58
+ */
59
+ export interface FetchGeminiCliQuotaModelsOptions {
60
+ /** OAuth access token sent as `Authorization: Bearer <token>`. */
61
+ token: string;
62
+ /** Cloud Code Assist endpoint. Defaults to `https://cloudcode-pa.googleapis.com`. */
63
+ endpoint?: string;
64
+ /** Pre-resolved GCP project id; otherwise discovered via `loadCodeAssist`. */
65
+ projectId?: string;
66
+ /** Optional abort signal for request cancellation. */
67
+ signal?: AbortSignal;
68
+ /** Optional fetch implementation override for tests. */
69
+ fetcher?: typeof fetch;
70
+ /** Effort-tier collapse table applied to the discovered list. */
71
+ collapseTable?: VariantCollapseTable;
72
+ }
73
+
74
+ /**
75
+ * Discovers the Gemini models available to a `google-gemini-cli` credential via
76
+ * the account's own `retrieveUserQuota` endpoint on Cloud Code Assist.
77
+ *
78
+ * This is the fallback for accounts whose credential is not authorized for the
79
+ * Antigravity `fetchAvailableModels` endpoint (e.g. Gemini Code Assist Standard
80
+ * tiers, which return HTTP 403 there). Quota buckets carry only model ids, so
81
+ * metadata is filled from the bundled catalog where the id is known and
82
+ * synthesized with Gemini CLI defaults otherwise.
83
+ *
84
+ * Returns `null` on network/payload/auth failure (the caller keeps the bundled
85
+ * catalog). Returns `[]` when the quota response lists no usable Gemini models.
86
+ */
87
+ export async function fetchGeminiCliQuotaModels(
88
+ options: FetchGeminiCliQuotaModelsOptions,
89
+ ): Promise<ModelSpec<"google-gemini-cli">[] | null> {
90
+ const fetcher = discoveryFetch(options.fetcher);
91
+ const endpoint = (options.endpoint?.trim() || DEFAULT_ENDPOINT).replace(/\/+$/, "");
92
+ const headers = {
93
+ Authorization: `Bearer ${options.token}`,
94
+ "Content-Type": "application/json",
95
+ ...getGeminiCliHeaders(),
96
+ };
97
+
98
+ const projectId = options.projectId ?? (await loadProjectId(fetcher, endpoint, headers, options.signal));
99
+
100
+ let response: Response;
101
+ try {
102
+ response = await fetcher(`${endpoint}${RETRIEVE_USER_QUOTA_PATH}`, {
103
+ method: "POST",
104
+ headers,
105
+ body: JSON.stringify(projectId ? { project: projectId } : {}),
106
+ signal: options.signal,
107
+ });
108
+ } catch {
109
+ return null;
110
+ }
111
+
112
+ if (!response.ok) {
113
+ return null;
114
+ }
115
+
116
+ let payload: unknown;
117
+ try {
118
+ payload = await response.json();
119
+ } catch {
120
+ return null;
121
+ }
122
+
123
+ const parsed = RetrieveUserQuotaResponseSchema(payload);
124
+ if (parsed instanceof type.errors) {
125
+ return null;
126
+ }
127
+
128
+ const seen = new Set<string>();
129
+ const models: ModelSpec<"google-gemini-cli">[] = [];
130
+ const bundled = createBundledReferenceMap<"google-gemini-cli">("google-gemini-cli");
131
+
132
+ for (const bucket of parsed.buckets ?? []) {
133
+ const modelId = bucket.modelId?.trim();
134
+ if (!modelId || seen.has(modelId) || !isGeminiModelId(modelId)) {
135
+ continue;
136
+ }
137
+ seen.add(modelId);
138
+
139
+ const reference = bundled.get(modelId);
140
+ if (reference) {
141
+ models.push({ ...reference, baseUrl: endpoint });
142
+ continue;
143
+ }
144
+
145
+ const parsedId = parseGeminiModel(modelId);
146
+ models.push({
147
+ id: modelId,
148
+ name: modelId,
149
+ api: "google-gemini-cli",
150
+ provider: "google-gemini-cli",
151
+ baseUrl: endpoint,
152
+ reasoning: parsedId ? semverGte(parsedId.version, REASONING_MIN_VERSION) : false,
153
+ input: ["text", "image"],
154
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
155
+ contextWindow: DEFAULT_CONTEXT_WINDOW,
156
+ maxTokens: DEFAULT_MAX_TOKENS,
157
+ });
158
+ }
159
+
160
+ const collapsed = collapseEffortVariants(models, options.collapseTable ?? GEMINI_CLI_VARIANT_COLLAPSE_TABLE);
161
+ collapsed.sort((a, b) => a.name.localeCompare(b.name) || a.id.localeCompare(b.id));
162
+ return collapsed;
163
+ }
164
+
165
+ async function loadProjectId(
166
+ fetcher: FetchImpl,
167
+ endpoint: string,
168
+ headers: Record<string, string>,
169
+ signal: AbortSignal | undefined,
170
+ ): Promise<string | undefined> {
171
+ let response: Response;
172
+ try {
173
+ response = await fetcher(`${endpoint}${LOAD_CODE_ASSIST_PATH}`, {
174
+ method: "POST",
175
+ headers,
176
+ body: JSON.stringify({
177
+ metadata: { ideType: "IDE_UNSPECIFIED", platform: "PLATFORM_UNSPECIFIED", pluginType: "GEMINI" },
178
+ }),
179
+ signal,
180
+ });
181
+ } catch {
182
+ return undefined;
183
+ }
184
+
185
+ if (!response.ok) {
186
+ return undefined;
187
+ }
188
+
189
+ let payload: unknown;
190
+ try {
191
+ payload = await response.json();
192
+ } catch {
193
+ return undefined;
194
+ }
195
+
196
+ const parsed = LoadCodeAssistResponseSchema(payload);
197
+ return parsed instanceof type.errors ? undefined : parsed.cloudaicompanionProject;
198
+ }
@@ -1,6 +1,7 @@
1
1
  export * from "./antigravity";
2
2
  export * from "./codex";
3
3
  export * from "./gemini";
4
+ export * from "./gemini-cli";
4
5
  export * from "./gitlab-duo-workflow";
5
6
  export * from "./openai-compatible";
6
7
  export * from "./protobuf";
@@ -374,7 +374,7 @@ function compileMapField(desc: MapFieldDesc): CompiledField {
374
374
  return {
375
375
  number,
376
376
  initDefault(message) {
377
- Reflect.set(message, name, {});
377
+ Reflect.set(message, name, Object.create(null));
378
378
  },
379
379
  encode(message, writer) {
380
380
  const input = Reflect.get(message, name);
@@ -425,7 +425,7 @@ function compileMapField(desc: MapFieldDesc): CompiledField {
425
425
  toJson(message, output) {
426
426
  const input = Reflect.get(message, name);
427
427
  if (!isMessageObject(input)) return;
428
- const mapOutput: { [key: string]: JsonValue } = {};
428
+ const mapOutput: { [key: string]: JsonValue } = Object.create(null);
429
429
  for (const entryKey in input) {
430
430
  mapOutput[entryKey] = valCodec.toJson(input[entryKey]);
431
431
  }
@@ -685,7 +685,7 @@ function arrayField(message: object, name: string): unknown[] {
685
685
  function mapField(message: object, name: string): Record<string, unknown> {
686
686
  const value = Reflect.get(message, name);
687
687
  if (isRecord(value)) return value;
688
- const map: Record<string, unknown> = {};
688
+ const map: Record<string, unknown> = Object.create(null);
689
689
  Reflect.set(message, name, map);
690
690
  return map;
691
691
  }
package/src/hosts.ts CHANGED
@@ -61,6 +61,8 @@ export const KNOWN_HOSTS = {
61
61
  qwenPortal: { providers: ["qwen-portal"], urlMarkers: ["portal.qwen.ai"] },
62
62
  /** NVIDIA NIM (`integrate.api.nvidia.com`). Qwen NIM endpoints take `chat_template_kwargs.enable_thinking`, never top-level `enable_thinking`. */
63
63
  nvidia: { providers: ["nvidia"], urlMarkers: ["integrate.api.nvidia.com"] },
64
+ /** Venice AI (`api.venice.ai`). OpenAI-compatible; drives reasoning via top-level `reasoning_effort` (and `venice_parameters.disable_thinking`), and rejects DashScope's top-level `enable_thinking` with a 400 (`additionalProperties: false` request schema). */
65
+ venice: { providers: ["venice"], urlMarkers: ["api.venice.ai"] },
64
66
  moonshotNative: { providers: ["moonshot", "kimi-code"], urlMarkers: ["api.moonshot.ai", "api.kimi.com"] },
65
67
  /** Google AI Studio's OpenAI-compatible shim (`/v1beta/openai`) — a subset of chat-completions; rejects `store` with a 400. Native Gemini uses `google-generative-ai` api instead. */
66
68
  googleAistudio: { providers: [], urlMarkers: ["generativelanguage.googleapis.com"] },
@@ -33,6 +33,7 @@ import {
33
33
  isMinimaxM2FamilyModelId,
34
34
  isMinimaxM3FamilyModelId,
35
35
  isOpenAIGptOssModelId,
36
+ isQwenModelId,
36
37
  supportsAdaptiveThinkingDisplay,
37
38
  } from "./identity/family";
38
39
  import type {
@@ -186,7 +187,9 @@ function fillThinkingWireDefaults<TApi extends Api>(
186
187
  supportsAdaptiveThinkingDisplay(spec.id);
187
188
  const needsRequiresEffort =
188
189
  thinking.requiresEffort === undefined &&
189
- (impliesMandatoryReasoning(parsed, spec.id) || isQwenTemplateReasoningEffortCompat(compat));
190
+ (impliesMandatoryReasoning(parsed, spec.id) ||
191
+ isQwenTemplateReasoningEffortCompat(compat) ||
192
+ isOpenCodeGatewayOxAlphaModel(spec));
190
193
  const needsDefaultLevel =
191
194
  thinking.defaultLevel === undefined && (isKimiK3ModelId(spec.id) || isGlm53ReasoningEffortModelId(spec.id));
192
195
  if (!effortsChanged && !shouldReplaceEffortMap && !needsDisplay && !needsRequiresEffort && !needsDefaultLevel) {
@@ -239,7 +242,11 @@ export function deriveThinking<TApi extends Api>(spec: ModelSpec<TApi>, compat:
239
242
  ) {
240
243
  config.supportsDisplay = true;
241
244
  }
242
- if (impliesMandatoryReasoning(parsed, spec.id) || isQwenTemplateReasoningEffortCompat(compat)) {
245
+ if (
246
+ impliesMandatoryReasoning(parsed, spec.id) ||
247
+ isQwenTemplateReasoningEffortCompat(compat) ||
248
+ isOpenCodeGatewayOxAlphaModel(spec)
249
+ ) {
243
250
  config.requiresEffort = true;
244
251
  }
245
252
  return config;
@@ -359,6 +366,9 @@ function getModelDefinedEfforts<TApi extends Api>(
359
366
  if (isKimiK3ModelId(spec.id)) {
360
367
  return LOW_HIGH_MAX_REASONING_EFFORTS;
361
368
  }
369
+ if (isOpenCodeGatewayOxAlphaModel(spec)) {
370
+ return LOW_HIGH_MAX_REASONING_EFFORTS;
371
+ }
362
372
  if (isSakanaFuguReasoningModel(spec)) {
363
373
  return HIGH_MAX_REASONING_EFFORTS;
364
374
  }
@@ -391,9 +401,15 @@ function getModelDefinedEfforts<TApi extends Api>(
391
401
  return QWEN38_TEMPLATE_REASONING_EFFORTS;
392
402
  }
393
403
  if (
394
- (isOpenAICompatReasoningApi(spec.api) || (spec.api === "ollama-chat" && spec.provider === "ollama-cloud")) &&
404
+ (isOpenAICompatReasoningApi(spec.api) ||
405
+ spec.api === "openai-responses" ||
406
+ (spec.api === "ollama-chat" && spec.provider === "ollama-cloud")) &&
395
407
  isDeepseekReasoningModel(spec)
396
408
  ) {
409
+ // The DeepSeek V4 effort ladder is a model property, not a transport one:
410
+ // `opencode-go/deepseek-v4-flash` is pinned to `openai-responses` (the Go
411
+ // gateway serves it only at /responses), yet carries the same wire-exact
412
+ // low/high/max scale — so the Responses transport is admitted here too.
397
413
  // DeepSeek V4 (Flash and Pro) accepts the wire-exact low/high/max ladder
398
414
  // on every first-party/aggregator host — the direct API, aggregators, and
399
415
  // Ollama Cloud alike (medium/xhigh fold into high, max is a real wire
@@ -538,6 +554,22 @@ function isSakanaFuguReasoningModel<TApi extends Api>(spec: ModelSpec<TApi>): bo
538
554
  return spec.provider === "sakana" && /^fugu(?:$|-)/i.test(spec.id);
539
555
  }
540
556
 
557
+ /**
558
+ * "Ox Alpha" stealth models on the OpenCode gateways (`opencode-go` /
559
+ * `opencode-zen`) reason through the wire-exact `low`/`high`/`max` ladder with
560
+ * mandatory thinking: the gateway rejects `minimal`/`medium`/`xhigh`
561
+ * (`[1210] ... please use low, high, or max`), the same dialect it already
562
+ * serves for GLM-5.3 and Kimi K3. Other hosts proxying an `ox-alpha` SKU
563
+ * (Kilo, NanoGPT, Venice, OpenRouter) expose their own vocabularies and are
564
+ * left untouched. See issue #9349.
565
+ */
566
+ function isOpenCodeGatewayOxAlphaModel<TApi extends Api>(spec: ModelSpec<TApi>): boolean {
567
+ return (
568
+ (spec.provider === "opencode-go" || spec.provider === "opencode-zen") &&
569
+ /(?:^|\/)ox-alpha(?:-|$)/i.test(bareModelId(spec.id))
570
+ );
571
+ }
572
+
541
573
  function isDeepseekReasoningModel<TApi extends Api>(spec: ModelSpec<TApi>): boolean {
542
574
  if (!spec.reasoning) return false;
543
575
  const lowerId = spec.id.toLowerCase();
@@ -660,6 +692,13 @@ function inferFallbackEfforts<TApi extends Api>(spec: ModelSpec<TApi>, compat: C
660
692
  }
661
693
  if (isOpenAICompatReasoningApi(spec.api)) {
662
694
  const resolved = compat as ResolvedOpenAICompat;
695
+ if (
696
+ resolved.thinkingFormat === "openai" &&
697
+ modelMatchesHost({ provider: spec.provider, baseUrl: spec.baseUrl ?? "" }, "venice") &&
698
+ isQwenModelId(spec.id)
699
+ ) {
700
+ return DEFAULT_REASONING_EFFORTS;
701
+ }
663
702
  if (resolved.thinkingFormat === "openai" && resolved.supportsReasoningEffort) {
664
703
  return DEFAULT_REASONING_EFFORTS_WITH_XHIGH;
665
704
  }