@oh-my-pi/pi-catalog 15.13.1 → 15.13.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,47 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [15.13.3] - 2026-06-15
6
+
7
+ ### Added
8
+
9
+ - Added Azure OpenAI as a catalog provider (`azure`, default model `gpt-5.5`, env var `AZURE_OPENAI_API_KEY`), bundling the OpenAI-family models Azure serves over the Responses API (GPT-4/4.1/4o, GPT-5 family, o-series, Codex). Like Amazon Bedrock it is catalog-only — models ship in the bundle and become selectable once the env key is set, with the deployment base URL resolved at runtime from `AZURE_OPENAI_BASE_URL`/`AZURE_OPENAI_RESOURCE_NAME`.
10
+ - Added models.dev-backed bundled catalogs for providers that previously shipped no offline models: Hugging Face, Kilo, Moonshot, NanoGPT, Synthetic, Venice, Ollama Cloud, and the Xiaomi Token Plan regions (ams/cn/sgp). They still discover live when credentialed; the bundle is now a non-empty baseline.
11
+
12
+ ### Changed
13
+
14
+ - Updated stale provider default models to their latest bundled versions: OpenAI-family providers (`azure`, `github-copilot`, `aimlapi`) → GPT-5.5; Gemini providers (`google`, `google-gemini-cli`, `google-vertex`) → `gemini-3.1-pro-preview`; GLM providers (`zai`, `zhipu-coding-plan`) → `glm-5.2`, `cerebras` → `zai-glm-4.7`; Kimi providers (`fireworks`, `opencode-go`, `moonshot`) → `kimi-k2.7-code`, `kimi-code` → `kimi-for-coding`, `together` → `moonshotai/Kimi-K2.7-Code`; `alibaba-coding-plan` → `qwen3.7-plus`; and Claude-Sonnet defaults (`cloudflare-ai-gateway`, `cursor`, `gitlab-duo`, `kilo`, `opencode-zen`, `vercel-ai-gateway`) → Claude Opus 4.x.
15
+ - Restricted models.dev Azure discovery to OpenAI-family IDs (`gpt-`, `o1`, `o3`, `o4`, `codex`, `chatgpt`), excluding Foundry-hosted third parties (Claude/DeepSeek/Llama/Mistral/Phi) that Azure serves through non-Responses APIs.
16
+ - Detected the Azure OpenAI Responses compat surface (developer role, strict tool mode, strict tool-result pairing) by provider id as well as base URL, so bundled `azure` models whose deployment host is only known at runtime still get the right wire behavior.
17
+ - Renamed the `Qwen3-ASR-Flash` model label to `Qwen3 ASR Flash`
18
+
19
+ ### Fixed
20
+
21
+ - Fixed tool syntax selection for Gemini-family and Gemma model IDs by routing them to dedicated `gemini` and `gemma` formats instead of generic XML
22
+ - Fixed `zhipu-coding-plan` and `together` shipping no bundled models: their descriptors referenced non-existent models.dev keys (`zhipu-coding-plan`, `together`); pointed them at the real keys (`zhipuai-coding-plan`, `togetherai`) so they bundle their GLM and full catalogs respectively.
23
+ - Folded the `azure-openai-responses` API into the OpenAI Responses thinking-inference branches so Azure reasoning models (o-series, GPT-5, Codex) resolve the discrete effort vocabulary (including `xhigh`) and effort-control mode instead of falling through to generic defaults.
24
+ - Fixed `ollama-cloud` discovery inheriting an unsafe cross-provider `contextWindow`/`maxTokens` when `/api/show` returns no size metadata; it now falls back to the safe 128K context / 8K output caps.
25
+ - Dropped internal Fireworks control-plane resource ids (`accounts/fireworks/{models,routers}/…`) from the bundle; only the public request ids ship.
26
+
27
+ ## [15.13.2] - 2026-06-15
28
+
29
+ ### Added
30
+
31
+ - Added the `ToolCallSyntax` union and `FALLBACK_TOOL_SYNTAX` constant to `@oh-my-pi/pi-catalog/identity` (re-exported from `@oh-my-pi/pi-ai/grammar`).
32
+ - Added `preferredToolSyntax(modelId)` to `@oh-my-pi/pi-catalog/identity`, resolving a model's native tool-call syntax affinity from its family token (Claude→`anthropic`, GLM→`glm`, Kimi→`kimi`, Qwen→`qwen3`, DeepSeek→`deepseek`, OpenAI/gpt-oss→`harmony`, else the `xml` fallback).
33
+ - Added `flux-1-schnell-fp8` to the Fireworks serverless model catalog
34
+ - Added `gpt-oss-20b` to the Fireworks model catalog
35
+ - Added `qwen3-embedding-8b` to the Fireworks model catalog
36
+ - Added `qwen3-reranker-8b` to the Fireworks model catalog
37
+ - Added `Gemma 4 E2B IT` and `Gemma 4 E4B IT` to the Google model catalog
38
+ - Added `qwen/qwen3-asr-flash` to the Zenmux model catalog
39
+ - Added sparse `supportsTools` model metadata so providers can mark models that require in-band tool-call formatting.
40
+
41
+ ### Changed
42
+
43
+ - Kept non-tool-capable Fireworks serverless models in discovery results and marked them with `supportsTools: false` for fallback-aware handling
44
+ - Extended `modelFamilyToken(modelId)` to classify Claude/OpenAI ids the structured parser misses (older dated forms such as `claude-3-5-sonnet-20241022` and `gpt-4o`), returning `anthropic`/`openai` instead of an empty token.
45
+
5
46
  ## [15.13.1] - 2026-06-15
6
47
 
7
48
  ### Added
@@ -14,10 +14,12 @@ interface OpenAIResponsesSpecLike {
14
14
  * Build the resolved Responses-API compat record. The Responses flavor
15
15
  * deliberately differs from chat-completions: GitHub Copilot's responses
16
16
  * endpoint accepts the `developer` role, while strict tool mode is scoped to
17
- * first-party OpenAI/Azure/Copilot providers. Developer-role and prompt-cache
18
- * detection are URL-only on purpose the historical call sites never
19
- * consulted the provider id for them. The GPT-5 juice-zero hack keys on the
20
- * model name, matching the historical request-time check.
17
+ * first-party OpenAI/Azure/Copilot providers. Azure is detected by provider id
18
+ * as well as URL bundled `azure` models carry no baseUrl (the deployment host
19
+ * is per-resource, resolved at runtime) while OpenAI/Copilot developer-role
20
+ * and prompt-cache detection stay URL-keyed, as the historical call sites were.
21
+ * The GPT-5 juice-zero hack keys on the model name, matching the historical
22
+ * request-time check.
21
23
  */
22
24
  export declare function buildOpenAIResponsesCompat(spec: OpenAIResponsesSpecLike): ResolvedOpenAIResponsesCompat;
23
25
  export {};
@@ -16,6 +16,8 @@ export declare function isClaudeModelId(modelId: string): boolean;
16
16
  export declare function isAnthropicNamespacedModelId(modelId: string): boolean;
17
17
  /** Qwen family ids (substring match — Qwen SKUs have no stable prefix shape). */
18
18
  export declare function isQwenModelId(modelId: string): boolean;
19
+ /** Gemma open-weights family (`gemma-3-27b-it`, `google/gemma-4-E2B-it`, `gemma2-9b`). */
20
+ export declare function isGemmaModelId(modelId: string): boolean;
19
21
  /** DeepSeek family by id or display name (proxies often rename the id but keep the name). */
20
22
  export declare function isDeepseekModelIdOrName(value: string): boolean;
21
23
  /** Xiaomi MiMo family by id or display name. */
@@ -37,6 +39,8 @@ export declare function isMinimaxM2FamilyModelId(modelId: string): boolean;
37
39
  * and `none`.
38
40
  */
39
41
  export declare function isOpenAIGptOssModelId(modelId: string): boolean;
42
+ /** OpenAI model ids (gpt-*, o1-*, o3-*, o4-*, or prefixed with openai/). */
43
+ export declare function isOpenAIModelId(modelId: string): boolean;
40
44
  /**
41
45
  * Reasoning-capable GLM coding SKUs: glm-4.5 and up on the base / `-air` /
42
46
  * `-turbo` lines. Excludes the vision (`…v`) shape, the non-reasoning
@@ -7,3 +7,4 @@ export * from "./markers";
7
7
  export * from "./priority";
8
8
  export * from "./reference";
9
9
  export * from "./selection";
10
+ export * from "./tool-syntax";
@@ -0,0 +1,3 @@
1
+ export type ToolCallSyntax = "glm" | "hermes" | "kimi" | "xml" | "anthropic" | "deepseek" | "harmony" | "pi" | "qwen3" | "gemini" | "gemma";
2
+ export declare const FALLBACK_TOOL_SYNTAX: ToolCallSyntax;
3
+ export declare function preferredToolSyntax(modelId: string): ToolCallSyntax;
@@ -8,7 +8,7 @@
8
8
  import type { ModelManagerConfig, ProviderCatalogEntry, ProviderDescriptor } from "./descriptor-types";
9
9
  export declare const CATALOG_PROVIDERS: readonly [{
10
10
  readonly id: "aimlapi";
11
- readonly defaultModel: "gpt-4o";
11
+ readonly defaultModel: "gpt-5.5-2026-04-23";
12
12
  readonly envVars: readonly ["AIMLAPI_API_KEY"];
13
13
  readonly createModelManagerOptions: (config: ModelManagerConfig) => import("..").ModelManagerOptions<"openai-completions", unknown>;
14
14
  readonly dynamicModelsAuthoritative: true;
@@ -17,7 +17,7 @@ export declare const CATALOG_PROVIDERS: readonly [{
17
17
  };
18
18
  }, {
19
19
  readonly id: "alibaba-coding-plan";
20
- readonly defaultModel: "qwen3.5-plus";
20
+ readonly defaultModel: "qwen3.7-plus";
21
21
  readonly envVars: readonly ["ALIBABA_CODING_PLAN_API_KEY"];
22
22
  readonly createModelManagerOptions: (config: ModelManagerConfig) => import("..").ModelManagerOptions<"openai-completions", unknown>;
23
23
  readonly catalogDiscovery: {
@@ -30,9 +30,13 @@ export declare const CATALOG_PROVIDERS: readonly [{
30
30
  readonly id: "anthropic";
31
31
  readonly defaultModel: "claude-opus-4-8";
32
32
  readonly createModelManagerOptions: (config: ModelManagerConfig) => import("..").ModelManagerOptions<"anthropic-messages", unknown>;
33
+ }, {
34
+ readonly id: "azure";
35
+ readonly defaultModel: "gpt-5.5";
36
+ readonly envVars: readonly ["AZURE_OPENAI_API_KEY"];
33
37
  }, {
34
38
  readonly id: "cerebras";
35
- readonly defaultModel: "zai-glm-4.6";
39
+ readonly defaultModel: "zai-glm-4.7";
36
40
  readonly envVars: readonly ["CEREBRAS_API_KEY"];
37
41
  readonly createModelManagerOptions: (config: ModelManagerConfig) => import("..").ModelManagerOptions<"openai-completions", unknown>;
38
42
  readonly catalogDiscovery: {
@@ -40,7 +44,7 @@ export declare const CATALOG_PROVIDERS: readonly [{
40
44
  };
41
45
  }, {
42
46
  readonly id: "cloudflare-ai-gateway";
43
- readonly defaultModel: "claude-sonnet-4-5";
47
+ readonly defaultModel: "anthropic/claude-opus-4-8";
44
48
  readonly envVars: readonly ["CLOUDFLARE_AI_GATEWAY_API_KEY"];
45
49
  readonly createModelManagerOptions: (config: ModelManagerConfig) => import("..").ModelManagerOptions<"anthropic-messages", unknown>;
46
50
  readonly catalogDiscovery: {
@@ -48,7 +52,7 @@ export declare const CATALOG_PROVIDERS: readonly [{
48
52
  };
49
53
  }, {
50
54
  readonly id: "cursor";
51
- readonly defaultModel: "claude-sonnet-4-6";
55
+ readonly defaultModel: "claude-4.6-opus-high";
52
56
  readonly envVars: readonly ["CURSOR_ACCESS_TOKEN"];
53
57
  readonly createModelManagerOptions: (config: ModelManagerConfig) => import("..").ModelManagerOptions<"cursor-agent", unknown>;
54
58
  readonly catalogDiscovery: {
@@ -71,7 +75,7 @@ export declare const CATALOG_PROVIDERS: readonly [{
71
75
  readonly createModelManagerOptions: (config: ModelManagerConfig) => import("..").ModelManagerOptions<"openai-completions", unknown>;
72
76
  }, {
73
77
  readonly id: "fireworks";
74
- readonly defaultModel: "kimi-k2.6";
78
+ readonly defaultModel: "kimi-k2.7-code";
75
79
  readonly envVars: readonly ["FIREWORKS_API_KEY"];
76
80
  readonly createModelManagerOptions: (config: ModelManagerConfig) => import("..").ModelManagerOptions<"openai-completions", unknown>;
77
81
  readonly catalogDiscovery: {
@@ -79,16 +83,16 @@ export declare const CATALOG_PROVIDERS: readonly [{
79
83
  };
80
84
  }, {
81
85
  readonly id: "github-copilot";
82
- readonly defaultModel: "gpt-4o";
86
+ readonly defaultModel: "gpt-5.5";
83
87
  readonly envVars: readonly ["COPILOT_GITHUB_TOKEN"];
84
88
  readonly createModelManagerOptions: (config: ModelManagerConfig) => import("..").ModelManagerOptions<import("..").Api, unknown>;
85
89
  }, {
86
90
  readonly id: "gitlab-duo";
87
- readonly defaultModel: "duo-chat-sonnet-4-5";
91
+ readonly defaultModel: "duo-chat-opus-4-6";
88
92
  readonly envVars: readonly ["GITLAB_TOKEN"];
89
93
  }, {
90
94
  readonly id: "google";
91
- readonly defaultModel: "gemini-2.5-pro";
95
+ readonly defaultModel: "gemini-3.1-pro-preview";
92
96
  readonly envVars: readonly ["GEMINI_API_KEY"];
93
97
  readonly createModelManagerOptions: (config: ModelManagerConfig) => import("..").ModelManagerOptions<"google-generative-ai", unknown>;
94
98
  }, {
@@ -97,11 +101,11 @@ export declare const CATALOG_PROVIDERS: readonly [{
97
101
  readonly specialModelManager: true;
98
102
  }, {
99
103
  readonly id: "google-gemini-cli";
100
- readonly defaultModel: "gemini-2.5-pro";
104
+ readonly defaultModel: "gemini-3.1-pro-preview";
101
105
  readonly specialModelManager: true;
102
106
  }, {
103
107
  readonly id: "google-vertex";
104
- readonly defaultModel: "gemini-3-pro-preview";
108
+ readonly defaultModel: "gemini-3.1-pro-preview";
105
109
  readonly createModelManagerOptions: (config: ModelManagerConfig) => import("..").ModelManagerOptions<import("..").Api, unknown>;
106
110
  readonly allowUnauthenticated: true;
107
111
  }, {
@@ -119,7 +123,7 @@ export declare const CATALOG_PROVIDERS: readonly [{
119
123
  };
120
124
  }, {
121
125
  readonly id: "kilo";
122
- readonly defaultModel: "anthropic/claude-sonnet-4.5";
126
+ readonly defaultModel: "anthropic/claude-opus-4.8";
123
127
  readonly envVars: readonly ["KILO_API_KEY"];
124
128
  readonly createModelManagerOptions: (config: ModelManagerConfig) => import("..").ModelManagerOptions<"openai-completions", unknown>;
125
129
  readonly catalogDiscovery: {
@@ -128,7 +132,7 @@ export declare const CATALOG_PROVIDERS: readonly [{
128
132
  };
129
133
  }, {
130
134
  readonly id: "kimi-code";
131
- readonly defaultModel: "kimi-k2.5";
135
+ readonly defaultModel: "kimi-for-coding";
132
136
  readonly createModelManagerOptions: (config: ModelManagerConfig) => import("..").ModelManagerOptions<"openai-completions", unknown>;
133
137
  readonly catalogDiscovery: {
134
138
  readonly label: "Kimi Code";
@@ -168,7 +172,7 @@ export declare const CATALOG_PROVIDERS: readonly [{
168
172
  readonly createModelManagerOptions: (config: ModelManagerConfig) => import("..").ModelManagerOptions<"openai-completions", unknown>;
169
173
  }, {
170
174
  readonly id: "moonshot";
171
- readonly defaultModel: "kimi-k2.5";
175
+ readonly defaultModel: "kimi-k2.7-code";
172
176
  readonly envVars: readonly ["MOONSHOT_API_KEY"];
173
177
  readonly createModelManagerOptions: (config: ModelManagerConfig) => import("..").ModelManagerOptions<"openai-completions", unknown>;
174
178
  readonly catalogDiscovery: {
@@ -217,12 +221,12 @@ export declare const CATALOG_PROVIDERS: readonly [{
217
221
  readonly specialModelManager: true;
218
222
  }, {
219
223
  readonly id: "opencode-go";
220
- readonly defaultModel: "kimi-k2.5";
224
+ readonly defaultModel: "kimi-k2.7-code";
221
225
  readonly envVars: readonly ["OPENCODE_API_KEY"];
222
226
  readonly createModelManagerOptions: (config: ModelManagerConfig) => import("..").ModelManagerOptions<import("..").Api, unknown>;
223
227
  }, {
224
228
  readonly id: "opencode-zen";
225
- readonly defaultModel: "claude-sonnet-4-6";
229
+ readonly defaultModel: "claude-opus-4-8";
226
230
  readonly envVars: readonly ["OPENCODE_API_KEY"];
227
231
  readonly createModelManagerOptions: (config: ModelManagerConfig) => import("..").ModelManagerOptions<import("..").Api, unknown>;
228
232
  }, {
@@ -262,7 +266,7 @@ export declare const CATALOG_PROVIDERS: readonly [{
262
266
  };
263
267
  }, {
264
268
  readonly id: "together";
265
- readonly defaultModel: "moonshotai/Kimi-K2.5";
269
+ readonly defaultModel: "moonshotai/Kimi-K2.7-Code";
266
270
  readonly envVars: readonly ["TOGETHER_API_KEY"];
267
271
  readonly createModelManagerOptions: (config: ModelManagerConfig) => import("..").ModelManagerOptions<"openai-completions", unknown>;
268
272
  readonly catalogDiscovery: {
@@ -279,7 +283,7 @@ export declare const CATALOG_PROVIDERS: readonly [{
279
283
  };
280
284
  }, {
281
285
  readonly id: "vercel-ai-gateway";
282
- readonly defaultModel: "anthropic/claude-sonnet-4-6";
286
+ readonly defaultModel: "anthropic/claude-opus-4.8";
283
287
  readonly envVars: readonly ["AI_GATEWAY_API_KEY"];
284
288
  readonly createModelManagerOptions: (config: ModelManagerConfig) => import("..").ModelManagerOptions<"anthropic-messages", unknown>;
285
289
  readonly catalogDiscovery: {
@@ -353,7 +357,7 @@ export declare const CATALOG_PROVIDERS: readonly [{
353
357
  readonly createModelManagerOptions: (config: ModelManagerConfig) => import("..").ModelManagerOptions<"openai-completions", unknown>;
354
358
  }, {
355
359
  readonly id: "zai";
356
- readonly defaultModel: "glm-5.1";
360
+ readonly defaultModel: "glm-5.2";
357
361
  readonly envVars: readonly ["ZAI_API_KEY"];
358
362
  readonly createModelManagerOptions: (config: ModelManagerConfig) => import("..").ModelManagerOptions<"anthropic-messages", unknown>;
359
363
  readonly catalogDiscovery: {
@@ -369,7 +373,7 @@ export declare const CATALOG_PROVIDERS: readonly [{
369
373
  };
370
374
  }, {
371
375
  readonly id: "zhipu-coding-plan";
372
- readonly defaultModel: "glm-5.1";
376
+ readonly defaultModel: "glm-5.2";
373
377
  readonly envVars: readonly ["ZHIPU_API_KEY"];
374
378
  readonly createModelManagerOptions: (config: ModelManagerConfig) => import("..").ModelManagerOptions<"openai-completions", unknown>;
375
379
  readonly catalogDiscovery: {
@@ -371,6 +371,13 @@ export interface Model<TApi extends Api = Api> {
371
371
  baseUrl: string;
372
372
  reasoning: boolean;
373
373
  input: ("text" | "image")[];
374
+ /**
375
+ * Native provider tool-call support. `false` is the only unsupported signal:
376
+ * `true` and `undefined` both mean callers may use native tools. Catalog and
377
+ * discovery sources should set this sparsely when an upstream explicitly
378
+ * reports that native tool calling is unsupported.
379
+ */
380
+ supportsTools?: boolean;
374
381
  cost: {
375
382
  input: number;
376
383
  output: number;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-catalog",
4
- "version": "15.13.1",
4
+ "version": "15.13.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": "Can Boluk",
@@ -34,11 +34,11 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "@bufbuild/protobuf": "^2.12.0",
37
- "@oh-my-pi/pi-utils": "15.13.1",
37
+ "@oh-my-pi/pi-utils": "15.13.3",
38
38
  "zod": "^4"
39
39
  },
40
40
  "devDependencies": {
41
- "@oh-my-pi/pi-ai": "15.13.1",
41
+ "@oh-my-pi/pi-ai": "15.13.3",
42
42
  "@types/bun": "^1.3.14"
43
43
  },
44
44
  "engines": {
@@ -301,29 +301,28 @@ interface OpenAIResponsesSpecLike {
301
301
  * Build the resolved Responses-API compat record. The Responses flavor
302
302
  * deliberately differs from chat-completions: GitHub Copilot's responses
303
303
  * endpoint accepts the `developer` role, while strict tool mode is scoped to
304
- * first-party OpenAI/Azure/Copilot providers. Developer-role and prompt-cache
305
- * detection are URL-only on purpose the historical call sites never
306
- * consulted the provider id for them. The GPT-5 juice-zero hack keys on the
307
- * model name, matching the historical request-time check.
304
+ * first-party OpenAI/Azure/Copilot providers. Azure is detected by provider id
305
+ * as well as URL bundled `azure` models carry no baseUrl (the deployment host
306
+ * is per-resource, resolved at runtime) while OpenAI/Copilot developer-role
307
+ * and prompt-cache detection stay URL-keyed, as the historical call sites were.
308
+ * The GPT-5 juice-zero hack keys on the model name, matching the historical
309
+ * request-time check.
308
310
  */
309
311
  export function buildOpenAIResponsesCompat(spec: OpenAIResponsesSpecLike): ResolvedOpenAIResponsesCompat {
310
312
  const baseUrl = spec.baseUrl ?? "";
313
+ const isAzure = modelMatchesHost({ provider: spec.provider, baseUrl }, "azureOpenAI");
311
314
  const compat: ResolvedOpenAIResponsesCompat = {
312
- supportsDeveloperRole:
313
- hostMatchesUrl(baseUrl, "openai") ||
314
- hostMatchesUrl(baseUrl, "azureOpenAI") ||
315
- hostMatchesUrl(baseUrl, "githubCopilot"),
315
+ supportsDeveloperRole: isAzure || hostMatchesUrl(baseUrl, "openai") || hostMatchesUrl(baseUrl, "githubCopilot"),
316
316
  supportsStrictMode:
317
317
  spec.provider === "openai" ||
318
- spec.provider === "azure" ||
318
+ isAzure ||
319
319
  spec.provider === "github-copilot" ||
320
- hostMatchesUrl(baseUrl, "openai") ||
321
- hostMatchesUrl(baseUrl, "azureOpenAI"),
320
+ hostMatchesUrl(baseUrl, "openai"),
322
321
  supportsReasoningEffort: true,
323
322
  supportsLongPromptCacheRetention: hostMatchesUrl(baseUrl, "openai"),
324
323
  // Azure OpenAI and GitHub Copilot Responses paths require tool results
325
324
  // to strictly match prior tool calls when building Responses inputs.
326
- strictResponsesPairing: hostMatchesUrl(baseUrl, "azureOpenAI") || spec.provider === "github-copilot",
325
+ strictResponsesPairing: isAzure || spec.provider === "github-copilot",
327
326
  requiresJuiceZeroHack: spec.name.toLowerCase().startsWith("gpt-5"),
328
327
  reasoningEffortMap: {},
329
328
  };
@@ -41,6 +41,11 @@ export function isQwenModelId(modelId: string): boolean {
41
41
  return modelId.toLowerCase().includes("qwen");
42
42
  }
43
43
 
44
+ /** Gemma open-weights family (`gemma-3-27b-it`, `google/gemma-4-E2B-it`, `gemma2-9b`). */
45
+ export function isGemmaModelId(modelId: string): boolean {
46
+ return /(^|\/)gemma[-.]?\d/i.test(modelId);
47
+ }
48
+
44
49
  /** DeepSeek family by id or display name (proxies often rename the id but keep the name). */
45
50
  export function isDeepseekModelIdOrName(value: string): boolean {
46
51
  return value.toLowerCase().includes("deepseek");
@@ -78,6 +83,11 @@ export function isOpenAIGptOssModelId(modelId: string): boolean {
78
83
  return /(^|\/)gpt-oss[-:]/i.test(modelId);
79
84
  }
80
85
 
86
+ /** OpenAI model ids (gpt-*, o1-*, o3-*, o4-*, or prefixed with openai/). */
87
+ export function isOpenAIModelId(modelId: string): boolean {
88
+ return /(^|\/)(gpt|o1|o3|o4)[-.]/i.test(modelId) || modelId.toLowerCase().includes("openai/");
89
+ }
90
+
81
91
  /**
82
92
  * Reasoning-capable GLM coding SKUs: glm-4.5 and up on the base / `-air` /
83
93
  * `-turbo` lines. Excludes the vision (`…v`) shape, the non-reasoning
@@ -114,12 +124,15 @@ export function isGlmVisionModelId(modelId: string): boolean {
114
124
  export function modelFamilyToken(modelId: string): string {
115
125
  const parsed = parseKnownModel(modelId);
116
126
  if (parsed.family !== "unknown") return parsed.family;
127
+ if (isClaudeModelId(modelId) || isAnthropicNamespacedModelId(modelId)) return "anthropic";
128
+ if (isOpenAIModelId(modelId)) return "openai";
117
129
  if (isKimiModelId(modelId)) return "kimi";
118
130
  if (isQwenModelId(modelId)) return "qwen";
119
131
  if (isMinimaxM2FamilyModelId(modelId)) return "minimax";
120
132
  if (isOpenAIGptOssModelId(modelId)) return "gpt-oss";
121
133
  if (isDeepseekModelIdOrName(modelId)) return "deepseek";
122
134
  if (isMimoModelIdOrName(modelId)) return "mimo";
135
+ if (isGemmaModelId(modelId)) return "gemma";
123
136
  if (parseGlmModel(bareModelId(modelId))) return "glm";
124
137
  return "";
125
138
  }
@@ -7,3 +7,4 @@ export * from "./markers";
7
7
  export * from "./priority";
8
8
  export * from "./reference";
9
9
  export * from "./selection";
10
+ export * from "./tool-syntax";
@@ -0,0 +1,40 @@
1
+ import { modelFamilyToken } from "./family";
2
+
3
+ export type ToolCallSyntax =
4
+ | "glm"
5
+ | "hermes"
6
+ | "kimi"
7
+ | "xml"
8
+ | "anthropic"
9
+ | "deepseek"
10
+ | "harmony"
11
+ | "pi"
12
+ | "qwen3"
13
+ | "gemini"
14
+ | "gemma";
15
+
16
+ export const FALLBACK_TOOL_SYNTAX: ToolCallSyntax = "xml";
17
+
18
+ export function preferredToolSyntax(modelId: string): ToolCallSyntax {
19
+ switch (modelFamilyToken(modelId)) {
20
+ case "anthropic":
21
+ return "anthropic";
22
+ case "glm":
23
+ return "glm";
24
+ case "gemini":
25
+ return "gemini";
26
+ case "gemma":
27
+ return "gemma";
28
+ case "kimi":
29
+ return "kimi";
30
+ case "qwen":
31
+ return "qwen3";
32
+ case "deepseek":
33
+ return "deepseek";
34
+ case "openai":
35
+ case "gpt-oss":
36
+ return "harmony";
37
+ default:
38
+ return FALLBACK_TOOL_SYNTAX;
39
+ }
40
+ }
@@ -219,7 +219,7 @@ export function deriveThinking<TApi extends Api>(spec: ModelSpec<TApi>, compat:
219
219
  * through other request fields.
220
220
  */
221
221
  function omitsWireReasoningEffort(api: Api, compat: CompatOf<Api>): boolean {
222
- if (api !== "openai-responses" && api !== "openai-codex-responses") {
222
+ if (api !== "openai-responses" && api !== "openai-codex-responses" && api !== "azure-openai-responses") {
223
223
  return false;
224
224
  }
225
225
  return (compat as ResolvedOpenAIResponsesCompat | undefined)?.supportsReasoningEffort === false;
@@ -426,7 +426,11 @@ function inferFallbackEfforts<TApi extends Api>(spec: ModelSpec<TApi>, compat: C
426
426
  return DEFAULT_REASONING_EFFORTS;
427
427
  }
428
428
  // OpenAI Responses APIs encode discrete effort levels, including xhigh.
429
- if (spec.api === "openai-responses" || spec.api === "openai-codex-responses") {
429
+ if (
430
+ spec.api === "openai-responses" ||
431
+ spec.api === "openai-codex-responses" ||
432
+ spec.api === "azure-openai-responses"
433
+ ) {
430
434
  return DEFAULT_REASONING_EFFORTS_WITH_XHIGH;
431
435
  }
432
436
  return DEFAULT_REASONING_EFFORTS;