@gajae-code/ai 0.18.10 → 0.18.12

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.
Files changed (75) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/README.md +1 -1
  3. package/dist/types/auth-broker/wire-schemas.d.ts +30 -0
  4. package/dist/types/auth-storage.d.ts +80 -8
  5. package/dist/types/bedrock-claude-cache-policy.d.ts +1 -1
  6. package/dist/types/model-cache.d.ts +6 -4
  7. package/dist/types/model-manager.d.ts +2 -0
  8. package/dist/types/openai-completions-compat.d.ts +2 -1
  9. package/dist/types/providers/anthropic.d.ts +10 -0
  10. package/dist/types/providers/aws-credential-config.d.ts +5 -0
  11. package/dist/types/providers/google-auth.d.ts +7 -0
  12. package/dist/types/providers/google-gemini-headers.d.ts +10 -5
  13. package/dist/types/providers/kiro-api-key.d.ts +5 -0
  14. package/dist/types/providers/openai-chat-server-schema.d.ts +2 -1
  15. package/dist/types/providers/openai-completions.d.ts +1 -1
  16. package/dist/types/providers/openai-responses-shared.d.ts +2 -1
  17. package/dist/types/stream.d.ts +33 -1
  18. package/dist/types/types.d.ts +26 -3
  19. package/dist/types/usage.d.ts +3 -0
  20. package/dist/types/utils/discovery/antigravity.d.ts +11 -0
  21. package/dist/types/utils/idle-iterator.d.ts +3 -2
  22. package/dist/types/utils/oauth/types.d.ts +9 -0
  23. package/package.json +4 -4
  24. package/src/auth-broker/remote-store.ts +62 -11
  25. package/src/auth-broker/wire-schemas.ts +2 -0
  26. package/src/auth-gateway/server.ts +3 -2
  27. package/src/auth-storage.ts +647 -194
  28. package/src/bedrock-claude-cache-policy.d.ts +1 -1
  29. package/src/bedrock-claude-cache-policy.ts +2 -2
  30. package/src/model-cache.ts +45 -10
  31. package/src/model-manager.ts +18 -6
  32. package/src/model-thinking.ts +37 -7
  33. package/src/models.json +2931 -1670
  34. package/src/openai-completions-compat.d.ts +2 -1
  35. package/src/openai-completions-compat.ts +5 -0
  36. package/src/provider-models/google.ts +7 -1
  37. package/src/provider-models/openai-compat.ts +26 -0
  38. package/src/providers/amazon-bedrock.ts +20 -11
  39. package/src/providers/anthropic.d.ts +10 -0
  40. package/src/providers/anthropic.ts +139 -4
  41. package/src/providers/aws-credential-config.d.ts +5 -0
  42. package/src/providers/aws-credential-config.ts +29 -1
  43. package/src/providers/google-auth.d.ts +7 -0
  44. package/src/providers/google-auth.ts +165 -4
  45. package/src/providers/google-gemini-headers.d.ts +10 -5
  46. package/src/providers/google-gemini-headers.ts +11 -6
  47. package/src/providers/google-vertex.ts +2 -4
  48. package/src/providers/kiro-api-key.d.ts +5 -0
  49. package/src/providers/kiro-api-key.ts +379 -68
  50. package/src/providers/ollama.ts +19 -9
  51. package/src/providers/openai-anthropic-shim.ts +1 -0
  52. package/src/providers/openai-chat-server-schema.ts +1 -1
  53. package/src/providers/openai-chat-server.ts +14 -2
  54. package/src/providers/openai-codex-responses.ts +21 -14
  55. package/src/providers/openai-completions.d.ts +1 -1
  56. package/src/providers/openai-completions.ts +65 -4
  57. package/src/providers/openai-responses-server.ts +10 -1
  58. package/src/providers/openai-responses-shared.d.ts +2 -1
  59. package/src/providers/openai-responses-shared.ts +42 -1
  60. package/src/providers/openai-responses.ts +51 -10
  61. package/src/providers/pi-native-server.ts +1 -1
  62. package/src/stream.d.ts +33 -1
  63. package/src/stream.ts +107 -13
  64. package/src/types.d.ts +26 -3
  65. package/src/types.ts +55 -2
  66. package/src/usage/claude.ts +4 -2
  67. package/src/usage.ts +3 -0
  68. package/src/utils/discovery/antigravity.ts +144 -2
  69. package/src/utils/idle-iterator.d.ts +3 -2
  70. package/src/utils/idle-iterator.ts +30 -10
  71. package/src/utils/oauth/anthropic.ts +23 -22
  72. package/src/utils/oauth/api-key-validation.ts +34 -11
  73. package/src/utils/oauth/types.d.ts +9 -0
  74. package/src/utils/oauth/types.ts +9 -0
  75. package/src/utils/parse-bind.d.ts +35 -0
@@ -10,7 +10,7 @@ export type BedrockClaudeGeneration = {
10
10
  * - family-first (3.x era): anthropic.claude-3-5-haiku-20241022-v1:0
11
11
  * - kind-first (4+ era): anthropic.claude-opus-4-20250514-v1:0,
12
12
  * anthropic.claude-haiku-4-5-20251001-v1:0
13
- * Cross-region profiles (us./eu./au./jp./apac./global. prefixes) and inference-profile
13
+ * Cross-region profiles (us./eu./au./jp./apac./global./in. prefixes) and inference-profile
14
14
  * ARNs keep the canonical model id as their final path segment.
15
15
  */
16
16
  export declare function parseBedrockClaudeGeneration(id: string): BedrockClaudeGeneration | undefined;
@@ -20,13 +20,13 @@ function extractBedrockModelId(id: string): string | undefined {
20
20
  * - family-first (3.x era): anthropic.claude-3-5-haiku-20241022-v1:0
21
21
  * - kind-first (4+ era): anthropic.claude-opus-4-20250514-v1:0,
22
22
  * anthropic.claude-haiku-4-5-20251001-v1:0
23
- * Cross-region profiles (us./eu./au./jp./apac./global. prefixes) and inference-profile
23
+ * Cross-region profiles (us./eu./au./jp./apac./global./in. prefixes) and inference-profile
24
24
  * ARNs keep the canonical model id as their final path segment.
25
25
  */
26
26
  export function parseBedrockClaudeGeneration(id: string): BedrockClaudeGeneration | undefined {
27
27
  const modelId = extractBedrockModelId(id);
28
28
  if (modelId === undefined) return undefined;
29
- const prefix = "(?:(?:us|eu|au|jp|apac|global)\\.)?anthropic\\.claude-";
29
+ const prefix = "(?:(?:us|eu|au|jp|apac|global|in)\\.)?anthropic\\.claude-";
30
30
  const component = "(?:0|[1-9]\\d?)";
31
31
  const suffix = "(?:-(?:[a-z][a-z0-9]*(?::[a-z0-9]+)?|\\d{8}))*";
32
32
  const familyFirst = new RegExp(`^${prefix}([1-9]\\d?)(?:-(${component}))?-([a-z][a-z0-9]*)${suffix}$`).exec(modelId);
@@ -6,7 +6,7 @@ import { Database } from "bun:sqlite";
6
6
  import { getModelDbPath } from "@gajae-code/utils/dirs";
7
7
  import type { Api, Model } from "./types";
8
8
 
9
- const CACHE_SCHEMA_VERSION = 5;
9
+ const CACHE_SCHEMA_VERSION = 6;
10
10
 
11
11
  interface CacheRow {
12
12
  provider_id: string;
@@ -16,6 +16,7 @@ interface CacheRow {
16
16
  static_fingerprint: string;
17
17
  dynamic_model_ids: string | null;
18
18
  dynamic_model_provenance: string | null;
19
+ discovery_user_agent: string | null;
19
20
  models: string;
20
21
  }
21
22
 
@@ -38,6 +39,8 @@ interface CacheEntry<TApi extends Api = Api> {
38
39
  /** IDs returned by the authoritative dynamic provider catalog, when retained. */
39
40
  dynamicModelIds: string[] | undefined;
40
41
  dynamicModelProvenance: string | undefined;
42
+ /** User-Agent used during discovery; invalidates cache if it changes. */
43
+ discoveryUserAgent: string | undefined;
41
44
  }
42
45
 
43
46
  let sharedDb: Database | null = null;
@@ -63,6 +66,7 @@ function getDb(dbPath?: string): Database {
63
66
  static_fingerprint TEXT NOT NULL DEFAULT '',
64
67
  dynamic_model_ids TEXT,
65
68
  dynamic_model_provenance TEXT,
69
+ discovery_user_agent TEXT,
66
70
  models TEXT NOT NULL
67
71
  )
68
72
  `);
@@ -84,7 +88,13 @@ export function closeModelCache(dbPath?: string): boolean {
84
88
  }
85
89
 
86
90
  function migrateCacheSchema(db: Database): void {
87
- const columns = db.prepare("PRAGMA table_info(model_cache)").all() as TableInfoRow[];
91
+ const tableInfo = db.query("PRAGMA table_info(model_cache)");
92
+ let columns: TableInfoRow[];
93
+ try {
94
+ columns = tableInfo.all() as TableInfoRow[];
95
+ } finally {
96
+ tableInfo.finalize();
97
+ }
88
98
  if (!columns.some(column => column.name === "static_fingerprint")) {
89
99
  db.run("ALTER TABLE model_cache ADD COLUMN static_fingerprint TEXT NOT NULL DEFAULT ''");
90
100
  }
@@ -94,7 +104,10 @@ function migrateCacheSchema(db: Database): void {
94
104
  if (!columns.some(column => column.name === "dynamic_model_provenance")) {
95
105
  db.run("ALTER TABLE model_cache ADD COLUMN dynamic_model_provenance TEXT");
96
106
  }
97
- db.run("UPDATE model_cache SET version = ? WHERE version IN (2, 3, 4)", [CACHE_SCHEMA_VERSION]);
107
+ if (!columns.some(column => column.name === "discovery_user_agent")) {
108
+ db.run("ALTER TABLE model_cache ADD COLUMN discovery_user_agent TEXT");
109
+ }
110
+ db.run("UPDATE model_cache SET version = ? WHERE version IN (2, 3, 4, 5)", [CACHE_SCHEMA_VERSION]);
98
111
  }
99
112
 
100
113
  export function readModelCache<TApi extends Api>(
@@ -102,13 +115,24 @@ export function readModelCache<TApi extends Api>(
102
115
  ttlMs: number,
103
116
  now: () => number,
104
117
  dbPath?: string,
118
+ expectedDiscoveryUserAgent?: string,
105
119
  ): CacheEntry<TApi> | null {
106
120
  try {
107
121
  const db = getDb(dbPath);
108
- const row = db.query<CacheRow, [string]>("SELECT * FROM model_cache WHERE provider_id = ?").get(providerId);
122
+ const select = db.query<CacheRow, [string]>("SELECT * FROM model_cache WHERE provider_id = ?");
123
+ let row: CacheRow | null;
124
+ try {
125
+ row = select.get(providerId);
126
+ } finally {
127
+ select.finalize();
128
+ }
109
129
  if (!row || row.version !== CACHE_SCHEMA_VERSION) {
110
130
  return null;
111
131
  }
132
+ // Invalidate cache if discovery User-Agent has changed
133
+ if (expectedDiscoveryUserAgent !== undefined && row.discovery_user_agent !== expectedDiscoveryUserAgent) {
134
+ return null;
135
+ }
112
136
  const models = JSON.parse(row.models) as Model<TApi>[];
113
137
  const ageMs = now() - row.updated_at;
114
138
  const fresh = Number.isFinite(ageMs) && ageMs >= 0 && ageMs <= ttlMs;
@@ -120,6 +144,7 @@ export function readModelCache<TApi extends Api>(
120
144
  staticFingerprint: row.static_fingerprint ?? "",
121
145
  dynamicModelIds: row.dynamic_model_ids === null ? undefined : (JSON.parse(row.dynamic_model_ids) as string[]),
122
146
  dynamicModelProvenance: row.dynamic_model_provenance ?? undefined,
147
+ discoveryUserAgent: row.discovery_user_agent ?? undefined,
123
148
  };
124
149
  } catch {
125
150
  return null;
@@ -135,12 +160,13 @@ export function writeModelCache<TApi extends Api>(
135
160
  dbPath?: string,
136
161
  dynamicModelIds?: readonly string[],
137
162
  dynamicModelProvenance?: string,
163
+ discoveryUserAgent?: string,
138
164
  ): void {
139
165
  try {
140
166
  const db = getDb(dbPath);
141
167
  db.run(
142
- `INSERT OR REPLACE INTO model_cache (provider_id, version, updated_at, authoritative, static_fingerprint, dynamic_model_ids, dynamic_model_provenance, models)
143
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
168
+ `INSERT OR REPLACE INTO model_cache (provider_id, version, updated_at, authoritative, static_fingerprint, dynamic_model_ids, dynamic_model_provenance, discovery_user_agent, models)
169
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
144
170
  [
145
171
  providerId,
146
172
  CACHE_SCHEMA_VERSION,
@@ -149,6 +175,7 @@ export function writeModelCache<TApi extends Api>(
149
175
  staticFingerprint,
150
176
  dynamicModelIds === undefined ? null : JSON.stringify(dynamicModelIds),
151
177
  dynamicModelProvenance ?? null,
178
+ discoveryUserAgent ?? null,
152
179
  JSON.stringify(models),
153
180
  ],
154
181
  );
@@ -166,11 +193,12 @@ export function insertModelCacheIfAbsent<TApi extends Api>(
166
193
  dbPath?: string,
167
194
  dynamicModelIds?: readonly string[],
168
195
  dynamicModelProvenance?: string,
196
+ discoveryUserAgent?: string,
169
197
  ): boolean {
170
198
  try {
171
199
  const result = getDb(dbPath).run(
172
- `INSERT INTO model_cache (provider_id, version, updated_at, authoritative, static_fingerprint, dynamic_model_ids, dynamic_model_provenance, models)
173
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
200
+ `INSERT INTO model_cache (provider_id, version, updated_at, authoritative, static_fingerprint, dynamic_model_ids, dynamic_model_provenance, discovery_user_agent, models)
201
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
174
202
  ON CONFLICT(provider_id) DO NOTHING`,
175
203
  [
176
204
  providerId,
@@ -180,6 +208,7 @@ export function insertModelCacheIfAbsent<TApi extends Api>(
180
208
  staticFingerprint,
181
209
  dynamicModelIds === undefined ? null : JSON.stringify(dynamicModelIds),
182
210
  dynamicModelProvenance ?? null,
211
+ discoveryUserAgent ?? null,
183
212
  JSON.stringify(models),
184
213
  ],
185
214
  );
@@ -195,6 +224,7 @@ export function updateModelCacheIfUnchanged<TApi extends Api>(
195
224
  expectedDynamicModelIds: readonly string[] | undefined,
196
225
  expectedDynamicModelProvenance: string | undefined,
197
226
  expectedModels: readonly Model<TApi>[],
227
+ expectedDiscoveryUserAgent: string | undefined,
198
228
  updatedAt: number,
199
229
  models: Model<TApi>[],
200
230
  authoritative: boolean,
@@ -202,30 +232,35 @@ export function updateModelCacheIfUnchanged<TApi extends Api>(
202
232
  dbPath?: string,
203
233
  dynamicModelIds?: readonly string[],
204
234
  dynamicModelProvenance?: string,
235
+ discoveryUserAgent?: string,
205
236
  ): boolean {
206
237
  try {
207
238
  const expectedIds = expectedDynamicModelIds === undefined ? null : JSON.stringify(expectedDynamicModelIds);
208
239
  const provenance = expectedDynamicModelProvenance ?? null;
240
+ const expectedUserAgent = expectedDiscoveryUserAgent ?? null;
209
241
  const nextIds = dynamicModelIds === undefined ? null : JSON.stringify(dynamicModelIds);
210
242
  const nextProvenance = dynamicModelProvenance ?? null;
211
243
  const expectedModelsJson = JSON.stringify(expectedModels);
212
244
  const result = getDb(dbPath).run(
213
245
  `UPDATE model_cache
214
246
  SET updated_at = ?, authoritative = ?, static_fingerprint = ?, dynamic_model_ids = ?,
215
- dynamic_model_provenance = ?, models = ?
247
+ dynamic_model_provenance = ?, discovery_user_agent = ?, models = ?
216
248
  WHERE provider_id = ? AND updated_at = ?
217
- AND dynamic_model_ids IS ? AND dynamic_model_provenance IS ? AND models = ?`,
249
+ AND dynamic_model_ids IS ? AND dynamic_model_provenance IS ?
250
+ AND discovery_user_agent IS ? AND models = ?`,
218
251
  [
219
252
  updatedAt,
220
253
  authoritative ? 1 : 0,
221
254
  staticFingerprint,
222
255
  nextIds,
223
256
  nextProvenance,
257
+ discoveryUserAgent ?? null,
224
258
  JSON.stringify(models),
225
259
  providerId,
226
260
  expectedUpdatedAt,
227
261
  expectedIds,
228
262
  provenance,
263
+ expectedUserAgent,
229
264
  expectedModelsJson,
230
265
  ],
231
266
  );
@@ -60,6 +60,8 @@ export interface ModelManagerOptions<TApi extends Api = Api, TModelsDevPayload =
60
60
  canPublishCache?: () => boolean;
61
61
  /** Credential-and-endpoint identity required to reuse dynamic catalog IDs. */
62
62
  cacheDynamicModelProvenance?: string;
63
+ /** Discovery User-Agent (e.g., for Antigravity): cache invalidates on change. */
64
+ discoveryUserAgent?: string;
63
65
  }
64
66
 
65
67
  /**
@@ -146,7 +148,13 @@ export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPa
146
148
  const provenance = options.cacheDynamicModelProvenance;
147
149
  const now = options.now ?? Date.now;
148
150
  const ttlMs = options.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
149
- const legacyCache = readModelCache<TApi>(options.providerId, ttlMs, now, options.cacheDbPath);
151
+ const legacyCache = readModelCache<TApi>(
152
+ options.providerId,
153
+ ttlMs,
154
+ now,
155
+ options.cacheDbPath,
156
+ options.discoveryUserAgent,
157
+ );
150
158
  if (
151
159
  strategy !== "online-if-uncached" ||
152
160
  typeof options.fetchDynamicModels !== "function" ||
@@ -185,7 +193,7 @@ async function resolveProviderModelsUncoalesced<TApi extends Api = Api, TModelsD
185
193
  const staticModels = passModelList<TApi>(
186
194
  options.staticModels ?? getBundledModels(options.providerId as GeneratedProvider),
187
195
  );
188
- const cache = readModelCache<TApi>(options.providerId, ttlMs, now, dbPath);
196
+ const cache = readModelCache<TApi>(options.providerId, ttlMs, now, dbPath, options.discoveryUserAgent);
189
197
  const dynamicFetcher = options.fetchDynamicModels;
190
198
  const hasDynamicFetcher = typeof dynamicFetcher === "function";
191
199
  const cacheDynamicModelIdsCurrent =
@@ -262,6 +270,7 @@ async function resolveProviderModelsUncoalesced<TApi extends Api = Api, TModelsD
262
270
  dbPath,
263
271
  cache.dynamicModelIds,
264
272
  cache.dynamicModelProvenance,
273
+ options.discoveryUserAgent,
265
274
  );
266
275
  }
267
276
  return {
@@ -315,6 +324,7 @@ async function resolveProviderModelsUncoalesced<TApi extends Api = Api, TModelsD
315
324
  dbPath,
316
325
  dynamicModels.map(model => model.id),
317
326
  options.cacheDynamicModelProvenance,
327
+ options.discoveryUserAgent,
318
328
  );
319
329
  }
320
330
  } else {
@@ -322,10 +332,9 @@ async function resolveProviderModelsUncoalesced<TApi extends Api = Api, TModelsD
322
332
  // stale state remains visible while retry backoff still applies. Re-read
323
333
  // the current row and trust only that row's provenance and dynamic IDs,
324
334
  // not the initial cacheProvenanceMismatch snapshot. A concurrent writer
325
- // may have replaced the provider row; if the latest row is unbound or
326
- // belongs to another context, use no latest fallback and do not
327
- // overwrite or downgrade it.
328
- const latestCache = readModelCache<TApi>(options.providerId, ttlMs, now, dbPath);
335
+ // may have replaced the provider row. This reread filters mismatched
336
+ // discovery User-Agents, while the checks below enforce dynamic provenance.
337
+ const latestCache = readModelCache<TApi>(options.providerId, ttlMs, now, dbPath, options.discoveryUserAgent);
329
338
  const latestCacheMatchesCurrentContext =
330
339
  options.cacheDynamicModelProvenance === undefined
331
340
  ? latestCache !== null && latestCache.dynamicModelIds === undefined
@@ -353,6 +362,7 @@ async function resolveProviderModelsUncoalesced<TApi extends Api = Api, TModelsD
353
362
  latestCache.dynamicModelIds,
354
363
  latestCache.dynamicModelProvenance,
355
364
  latestCache.models,
365
+ latestCache.discoveryUserAgent,
356
366
  now(),
357
367
  snapshotModels,
358
368
  false,
@@ -360,6 +370,7 @@ async function resolveProviderModelsUncoalesced<TApi extends Api = Api, TModelsD
360
370
  dbPath,
361
371
  options.cacheDynamicModelProvenance === undefined ? undefined : [],
362
372
  options.cacheDynamicModelProvenance,
373
+ options.discoveryUserAgent,
363
374
  );
364
375
  if (!updated) cacheModels = [];
365
376
  } else if (latestCache == null) {
@@ -372,6 +383,7 @@ async function resolveProviderModelsUncoalesced<TApi extends Api = Api, TModelsD
372
383
  dbPath,
373
384
  options.cacheDynamicModelProvenance === undefined ? undefined : [],
374
385
  options.cacheDynamicModelProvenance,
386
+ options.discoveryUserAgent,
375
387
  );
376
388
  if (!inserted) cacheModels = [];
377
389
  }
@@ -95,7 +95,7 @@ type SemVer = {
95
95
  patch: number;
96
96
  };
97
97
 
98
- type GeminiKind = "pro" | "flash";
98
+ type GeminiKind = "pro" | "flash" | "argon";
99
99
  type AnthropicKind = "opus" | "sonnet" | "fable";
100
100
  type OpenAIVariant =
101
101
  | "astra"
@@ -328,6 +328,23 @@ export function modelSupportsReasoningControl<TApi extends Api>(
328
328
  export function applyGeneratedModelPolicies(models: ApiModel<Api>[]): void {
329
329
  for (let index = 0; index < models.length; index++) {
330
330
  const source = models[index]!;
331
+ const inputCost = nonNegativeFinitePrice(source.cost.input);
332
+ const outputCost = nonNegativeFinitePrice(source.cost.output);
333
+ const cacheReadCost = nonNegativeFinitePrice(source.cost.cacheRead);
334
+ const cacheWriteCost = nonNegativeFinitePrice(source.cost.cacheWrite);
335
+ if (
336
+ inputCost !== source.cost.input ||
337
+ outputCost !== source.cost.output ||
338
+ cacheReadCost !== source.cost.cacheRead ||
339
+ cacheWriteCost !== source.cost.cacheWrite
340
+ ) {
341
+ source.cost = {
342
+ input: inputCost,
343
+ output: outputCost,
344
+ cacheRead: cacheReadCost,
345
+ cacheWrite: cacheWriteCost,
346
+ };
347
+ }
331
348
  if (source.provider === "xai" && (source.id === "grok-4.5" || source.id === "grok-4.6")) {
332
349
  source.reasoning = true;
333
350
  }
@@ -359,6 +376,10 @@ export function applyGeneratedModelPolicies(models: ApiModel<Api>[]): void {
359
376
  }
360
377
  }
361
378
 
379
+ function nonNegativeFinitePrice(value: number): number {
380
+ return Number.isFinite(value) && value >= 0 ? value : 0;
381
+ }
382
+
362
383
  /**
363
384
  * Link OpenAI model variants to their context promotion targets.
364
385
  *
@@ -729,6 +750,17 @@ function applyGeneratedModelPolicy(model: ApiModel<Api>): void {
729
750
  ) {
730
751
  model.contextWindow = 1_000_000;
731
752
  }
753
+ // Google Gemini 4 Argon supports up to 65,536 output tokens across provider
754
+ // aliases. Strip catalog provider prefixes and OpenRouter's batch suffix.
755
+ // https://ai.google.dev/gemini-api/docs/models/gemini-4-argon
756
+ const canonicalId = model.id
757
+ .toLowerCase()
758
+ .split("/")
759
+ .at(-1)
760
+ ?.replace(/:batch$/, "");
761
+ if (canonicalId === "gemini-4-argon") {
762
+ model.maxTokens = Math.min(model.maxTokens, 65_536);
763
+ }
732
764
  }
733
765
 
734
766
  function scrubGeneratedModelName(name: string): string {
@@ -979,6 +1011,8 @@ function inferGeminiSupportedEfforts(model: GeminiModel): readonly Effort[] {
979
1011
  if (model.kind === "pro") {
980
1012
  return GEMINI_3_PRO_EFFORTS;
981
1013
  }
1014
+ // Gemini 3.7+ Flash and Gemini 4 Argon accept low/medium/high only.
1015
+ // https://ai.google.dev/gemini-api/docs/models/gemini-4-argon
982
1016
  if (semverGte(model.version, "3.7")) {
983
1017
  return GEMINI_3_7_FLASH_EFFORTS;
984
1018
  }
@@ -1054,11 +1088,7 @@ function inferThinkingControlMode<TApi extends Api>(
1054
1088
  case "google-generative-ai":
1055
1089
  case "google-gemini-cli":
1056
1090
  case "google-vertex":
1057
- return parsedModel.family === "gemini" &&
1058
- semverGte(parsedModel.version, "3.0") &&
1059
- parsedModel.version.major === 3
1060
- ? "google-level"
1061
- : "budget";
1091
+ return parsedModel.family === "gemini" && semverGte(parsedModel.version, "3.0") ? "google-level" : "budget";
1062
1092
 
1063
1093
  case "anthropic-messages":
1064
1094
  if (parsedModel.family === "anthropic") {
@@ -1104,7 +1134,7 @@ function parseGeminiModel(modelId: string): GeminiModel | null {
1104
1134
  if (modelId.endsWith(GEMINI_SUFFIX)) {
1105
1135
  modelId = modelId.slice(0, -GEMINI_SUFFIX.length);
1106
1136
  }
1107
- const match = /gemini-(\d+(?:\.\d+){0,2})-(pro|flash)\b/.exec(modelId);
1137
+ const match = /gemini-(\d+(?:\.\d+){0,2})-(pro|flash|argon)\b/.exec(modelId);
1108
1138
  if (!match) {
1109
1139
  return null;
1110
1140
  }