@linxiraos/pi-catalog 1.1.26 → 1.2.0

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 (128) hide show
  1. package/CHANGELOG.md +4 -6
  2. package/dist/types/compat/auth-ids.d.ts +2 -2
  3. package/dist/types/compat/collapse.d.ts +7 -0
  4. package/dist/types/compat/image-tokenization.d.ts +52 -0
  5. package/dist/types/compat/judgment-questions.d.ts +7 -0
  6. package/dist/types/compat/prompt-cache-lookback.d.ts +7 -0
  7. package/dist/types/compat/provider-ids.d.ts +1 -1
  8. package/dist/types/compat/reasoning-carry.d.ts +21 -0
  9. package/dist/types/compat/request-size.d.ts +7 -0
  10. package/dist/types/compat/taxonomy.d.ts +0 -10
  11. package/dist/types/compat/types.d.ts +4 -0
  12. package/dist/types/discovery/antigravity.d.ts +13 -3
  13. package/dist/types/hosts.d.ts +4 -0
  14. package/dist/types/model-cache.d.ts +6 -0
  15. package/dist/types/model-manager.d.ts +16 -0
  16. package/dist/types/provider-models/account-access.d.ts +11 -0
  17. package/dist/types/provider-models/google.d.ts +23 -1
  18. package/dist/types/provider-models/openai-compat.d.ts +19 -1
  19. package/dist/types/provider-models/special.d.ts +8 -0
  20. package/dist/types/types.d.ts +134 -23
  21. package/dist/types/wire/cloudflare-ai-gateway.d.ts +0 -10
  22. package/dist/types/wire/cloudflare.d.ts +10 -0
  23. package/dist/types/wire/factory-droid.d.ts +1 -1
  24. package/dist/types/wire/gemini-headers.d.ts +6 -3
  25. package/package.json +4 -4
  26. package/src/build.ts +39 -35
  27. package/src/compat/auth-ids.ts +6 -0
  28. package/src/compat/axes.ts +43 -5
  29. package/src/compat/cascade.ts +6 -1
  30. package/src/compat/collapse.d.ts +7 -0
  31. package/src/compat/collapse.ts +11 -0
  32. package/src/compat/image-tokenization.ts +166 -0
  33. package/src/compat/judgment-questions.ts +12 -0
  34. package/src/compat/prompt-cache-lookback.ts +23 -0
  35. package/src/compat/provider-ids.d.ts +1 -1
  36. package/src/compat/provider-ids.ts +3 -0
  37. package/src/compat/reasoning-carry.ts +64 -0
  38. package/src/compat/request-size.ts +25 -0
  39. package/src/compat/resolve.ts +36 -25
  40. package/src/compat/rules/README.md +13 -3
  41. package/src/compat/rules/auth/_order.kdl +2 -2
  42. package/src/compat/rules/auth/cloudflare-workers-ai.kdl +9 -0
  43. package/src/compat/rules/auth/coralbricks.kdl +13 -0
  44. package/src/compat/rules/auth/minimax-code-cn.kdl +1 -1
  45. package/src/compat/rules/auth/minimax-code.kdl +1 -1
  46. package/src/compat/rules/auth/openai-codex.kdl +1 -1
  47. package/src/compat/rules/auth/snowflake.kdl +11 -0
  48. package/src/compat/rules/auth/stencil.kdl +1 -1
  49. package/src/compat/rules/classes/anthropic.kdl +188 -7
  50. package/src/compat/rules/classes/deepseek.kdl +11 -3
  51. package/src/compat/rules/classes/gemini.kdl +12 -0
  52. package/src/compat/rules/classes/glm.kdl +17 -8
  53. package/src/compat/rules/classes/kimi.kdl +15 -0
  54. package/src/compat/rules/classes/meta.kdl +4 -4
  55. package/src/compat/rules/classes/minimax.kdl +31 -1
  56. package/src/compat/rules/classes/openai.kdl +114 -4
  57. package/src/compat/rules/classes/qwen.kdl +2 -1
  58. package/src/compat/rules/providers/abliteration.kdl +7 -0
  59. package/src/compat/rules/providers/amazon-bedrock.kdl +0 -7
  60. package/src/compat/rules/providers/anthropic.kdl +13 -0
  61. package/src/compat/rules/providers/apple.kdl +1 -0
  62. package/src/compat/rules/providers/cloudflare-workers-ai.kdl +34 -0
  63. package/src/compat/rules/providers/coralbricks.kdl +62 -0
  64. package/src/compat/rules/providers/cursor.kdl +28 -8
  65. package/src/compat/rules/providers/devin.kdl +2 -2
  66. package/src/compat/rules/providers/factory-droid.kdl +77 -13
  67. package/src/compat/rules/providers/github-copilot.kdl +29 -0
  68. package/src/compat/rules/providers/google-antigravity.kdl +8 -1
  69. package/src/compat/rules/providers/groq.kdl +21 -0
  70. package/src/compat/rules/providers/image-tokenization.kdl +42 -0
  71. package/src/compat/rules/providers/judgment-questions.kdl +53 -0
  72. package/src/compat/rules/providers/litellm.kdl +4 -1
  73. package/src/compat/rules/providers/meta.kdl +3 -3
  74. package/src/compat/rules/providers/minimax-code-cn.kdl +10 -5
  75. package/src/compat/rules/providers/minimax-code.kdl +10 -5
  76. package/src/compat/rules/providers/mistral.kdl +25 -1
  77. package/src/compat/rules/providers/moonshot.kdl +11 -0
  78. package/src/compat/rules/providers/muse-code.kdl +7 -0
  79. package/src/compat/rules/providers/ollama.kdl +3 -0
  80. package/src/compat/rules/providers/openai-codex.kdl +18 -13
  81. package/src/compat/rules/providers/openai.kdl +22 -0
  82. package/src/compat/rules/providers/opencode-go.kdl +11 -0
  83. package/src/compat/rules/providers/opencode-zen.kdl +20 -0
  84. package/src/compat/rules/providers/openrouter.kdl +6 -0
  85. package/src/compat/rules/providers/snowflake.kdl +135 -0
  86. package/src/compat/rules/providers/tool-result-images.kdl +16 -0
  87. package/src/compat/rules/providers/zhipu-coding-plan.kdl +10 -0
  88. package/src/compat/rules/runtime/behavior.kdl +8 -5
  89. package/src/compat/rules/taxonomy/_collapse.kdl +52 -2
  90. package/src/compat/rules/taxonomy/deepseek.kdl +4 -0
  91. package/src/compat/rules/taxonomy/kimi.kdl +5 -0
  92. package/src/compat/rules.json +1 -1
  93. package/src/compat/taxonomy.d.ts +0 -10
  94. package/src/compat/taxonomy.ts +3 -3
  95. package/src/compat/types.d.ts +4 -0
  96. package/src/compat/types.ts +4 -0
  97. package/src/discovery/antigravity.d.ts +13 -3
  98. package/src/discovery/antigravity.ts +33 -17
  99. package/src/discovery/cursor.ts +28 -4
  100. package/src/discovery/devin.ts +20 -2
  101. package/src/hosts.d.ts +4 -0
  102. package/src/hosts.ts +2 -0
  103. package/src/model-cache.d.ts +6 -0
  104. package/src/model-cache.ts +123 -47
  105. package/src/model-manager.d.ts +16 -0
  106. package/src/model-manager.ts +107 -41
  107. package/src/model-tokenizer.ts +3 -0
  108. package/src/models.json +1 -1
  109. package/src/provider-models/account-access.d.ts +11 -0
  110. package/src/provider-models/account-access.ts +30 -0
  111. package/src/provider-models/cache-provider-id.ts +13 -6
  112. package/src/provider-models/cline-pass.ts +33 -0
  113. package/src/provider-models/descriptors.ts +4 -0
  114. package/src/provider-models/google.d.ts +23 -1
  115. package/src/provider-models/google.ts +72 -14
  116. package/src/provider-models/openai-compat.d.ts +19 -1
  117. package/src/provider-models/openai-compat.ts +281 -42
  118. package/src/provider-models/special.d.ts +8 -0
  119. package/src/provider-models/special.ts +26 -22
  120. package/src/types.d.ts +134 -23
  121. package/src/types.ts +183 -20
  122. package/src/wire/cloudflare-ai-gateway.d.ts +0 -10
  123. package/src/wire/cloudflare-ai-gateway.ts +0 -36
  124. package/src/wire/cloudflare.ts +40 -0
  125. package/src/wire/factory-droid.d.ts +1 -1
  126. package/src/wire/factory-droid.ts +1 -1
  127. package/src/wire/gemini-headers.d.ts +6 -3
  128. package/src/wire/gemini-headers.ts +39 -21
@@ -1,13 +1,3 @@
1
- /**
2
- * Runtime model-identity taxonomy: classifies wire model ids into
3
- * `(class, family, revision)` ranks, applies reviewed identity overrides, and
4
- * exposes the collapse/discovery vocabularies.
5
- *
6
- * Faithful port of the o2 reference (`taxonomy.rs` / `classify.rs`):
7
- * matchers rank `exact(4) > bounded(3) > namespace(2) > prefix(1) > glob(0)`
8
- * with token byte length as tiebreak; equal cross-class or cross-family ranks
9
- * throw unless classification is `lenient` (discovery normalization).
10
- */
11
1
  import type { Effort } from "../effort";
12
2
  import type { ModelIdentity } from "./types";
13
3
  /** Two classes, families, or reviewed override patterns tie for one model id. */
@@ -8,6 +8,7 @@
8
8
  * with token byte length as tiebreak; equal cross-class or cross-family ranks
9
9
  * throw unless classification is `lenient` (discovery normalization).
10
10
  */
11
+ import { LRUCache } from "@linxiraos/pi-utils/lru";
11
12
  import type { Effort } from "../effort";
12
13
  import { globMatch } from "./cascade";
13
14
  import { formatRevision, parseRevisionPrefix } from "./revision";
@@ -433,8 +434,8 @@ export function stripThinkingVariantSuffix(model: string): string | undefined {
433
434
  * @throws AmbiguousIdentityError on equal-rank cross-class, cross-family, or
434
435
  * reviewed-pattern matches unless `opts.lenient`.
435
436
  */
436
- const classifyMemo = new Map<string, ModelIdentity>();
437
- const CLASSIFY_MEMO_MAX = 4096;
437
+ // LRU sized above the bundled catalog so catalog-wide passes stay hot (a full `clear()` thrashed them).
438
+ const classifyMemo = new LRUCache<string, ModelIdentity>({ max: 16384 });
438
439
 
439
440
  function classifyMemoKey(provider: string, modelId: string, lenient: boolean): string {
440
441
  return `${provider.length}:${provider}${modelId.length}:${modelId}${lenient ? 1 : 0}`;
@@ -446,7 +447,6 @@ export function classifyModel(provider: string, modelId: string, opts?: Classify
446
447
  const cached = classifyMemo.get(key);
447
448
  if (cached !== undefined) return { ...cached };
448
449
  const identity = classifyModelUncached(provider, modelId, opts);
449
- if (classifyMemo.size >= CLASSIFY_MEMO_MAX) classifyMemo.clear();
450
450
  classifyMemo.set(key, Object.freeze(identity));
451
451
  return { ...identity };
452
452
  }
@@ -593,6 +593,8 @@ export interface CompiledSeedModel {
593
593
  baseUrl: string;
594
594
  reasoning: boolean;
595
595
  input: ("text" | "image")[];
596
+ /** Wire model id sent upstream when it differs from the row id (`request-model-id`). */
597
+ requestModelId?: string;
596
598
  supportsTools?: boolean;
597
599
  cost: TokenCost;
598
600
  contextWindow: number | null;
@@ -622,6 +624,8 @@ export interface CompiledProvider {
622
624
  id: string;
623
625
  /** Preferred model id when no explicit selection is made. */
624
626
  defaultModel: string;
627
+ /** Whether the provider participates in automatic default selection (defaults to true). */
628
+ automaticDefault?: boolean;
625
629
  /** Env vars consulted, in order, for the runtime API-key fallback. */
626
630
  envVars?: string[];
627
631
  /** The runtime creates a model manager even without a valid API key. */
@@ -625,6 +625,8 @@ export interface CompiledSeedModel {
625
625
  baseUrl: string;
626
626
  reasoning: boolean;
627
627
  input: ("text" | "image")[];
628
+ /** Wire model id sent upstream when it differs from the row id (`request-model-id`). */
629
+ requestModelId?: string;
628
630
  supportsTools?: boolean;
629
631
  cost: TokenCost;
630
632
  contextWindow: number | null;
@@ -656,6 +658,8 @@ export interface CompiledProvider {
656
658
  id: string;
657
659
  /** Preferred model id when no explicit selection is made. */
658
660
  defaultModel: string;
661
+ /** Whether the provider participates in automatic default selection (defaults to true). */
662
+ automaticDefault?: boolean;
659
663
  /** Env vars consulted, in order, for the runtime API-key fallback. */
660
664
  envVars?: string[];
661
665
  /** The runtime creates a model manager even without a valid API key. */
@@ -63,13 +63,23 @@ export interface FetchAntigravityDiscoveryModelsOptions {
63
63
  */
64
64
  collapseTable?: VariantCollapseTable;
65
65
  }
66
+ /**
67
+ * A complete account roster, or a definitive 401/403 credential rejection.
68
+ * `null` from {@link fetchAntigravityDiscoveryModels} instead means a transient
69
+ * failure or invalid response, which must not replace an authoritative catalog.
70
+ */
71
+ export interface AntigravityModelDiscoveryResult {
72
+ models: ModelSpec<"google-gemini-cli">[];
73
+ rejectedStatus?: 401 | 403;
74
+ }
66
75
  /**
67
76
  * Fetches discoverable Antigravity models and normalizes them into canonical model entries.
68
77
  *
69
- * Returns `null` on network/payload/auth failures.
70
- * Returns `[]` only when the endpoint responds successfully with no usable models.
78
+ * Returns `null` on network, server, or payload failures; a 401/403 is reported
79
+ * separately so multi-account discovery can skip a rejected credential.
80
+ * A successful empty roster has `models: []` without `rejectedStatus`.
71
81
  */
72
- export declare function fetchAntigravityDiscoveryModels(options: FetchAntigravityDiscoveryModelsOptions): Promise<ModelSpec<"google-gemini-cli">[] | null>;
82
+ export declare function fetchAntigravityDiscoveryModels(options: FetchAntigravityDiscoveryModelsOptions): Promise<AntigravityModelDiscoveryResult | null>;
73
83
  /** Advertised image model and serving endpoint for one Antigravity account. */
74
84
  export interface AntigravityImageModel {
75
85
  id: string;
@@ -153,19 +153,29 @@ export interface FetchAntigravityDiscoveryModelsOptions {
153
153
  collapseTable?: VariantCollapseTable;
154
154
  }
155
155
 
156
+ /**
157
+ * A complete account roster, or a definitive 401/403 credential rejection.
158
+ * `null` from {@link fetchAntigravityDiscoveryModels} instead means a transient
159
+ * failure or invalid response, which must not replace an authoritative catalog.
160
+ */
161
+ export interface AntigravityModelDiscoveryResult {
162
+ models: ModelSpec<"google-gemini-cli">[];
163
+ rejectedStatus?: 401 | 403;
164
+ }
165
+
156
166
  /**
157
167
  * Fetches discoverable Antigravity models and normalizes them into canonical model entries.
158
168
  *
159
- * Returns `null` on network/payload/auth failures.
160
- * Returns `[]` only when the endpoint responds successfully with no usable models.
169
+ * Returns `null` on network, server, or payload failures; a 401/403 is reported
170
+ * separately so multi-account discovery can skip a rejected credential.
171
+ * A successful empty roster has `models: []` without `rejectedStatus`.
161
172
  */
162
173
  export async function fetchAntigravityDiscoveryModels(
163
174
  options: FetchAntigravityDiscoveryModelsOptions,
164
- ): Promise<ModelSpec<"google-gemini-cli">[] | null> {
175
+ ): Promise<AntigravityModelDiscoveryResult | null> {
165
176
  const discovered = await fetchAntigravityDiscoveryResponse(options);
166
- if (!discovered) {
167
- return null;
168
- }
177
+ if (!discovered) return null;
178
+ if ("rejectedStatus" in discovered) return { models: [], rejectedStatus: discovered.rejectedStatus };
169
179
 
170
180
  const models: ModelSpec<"google-gemini-cli">[] = [];
171
181
  const apiModels = discovered.payload.models;
@@ -208,7 +218,7 @@ export async function fetchAntigravityDiscoveryModels(
208
218
  options.collapseTable === undefined ? undefined : { table: options.collapseTable },
209
219
  );
210
220
  collapsed.sort((a, b) => a.name.localeCompare(b.name) || a.id.localeCompare(b.id));
211
- return collapsed;
221
+ return { models: collapsed };
212
222
  }
213
223
 
214
224
  /** Advertised image model and serving endpoint for one Antigravity account. */
@@ -222,14 +232,14 @@ export async function fetchAntigravityImageModel(
222
232
  options: FetchAntigravityDiscoveryModelsOptions,
223
233
  ): Promise<AntigravityImageModel | null> {
224
234
  const discovered = await fetchAntigravityDiscoveryResponse(options);
225
- const id = discovered?.payload.imageGenerationModelIds?.find(modelId => modelId.length > 0);
226
- return id && discovered ? { id, endpoint: discovered.endpoint } : null;
235
+ if (!discovered || "rejectedStatus" in discovered) return null;
236
+ const id = discovered.payload.imageGenerationModelIds?.find(modelId => modelId.length > 0);
237
+ return id ? { id, endpoint: discovered.endpoint } : null;
227
238
  }
228
239
 
229
- interface AntigravityDiscoveryResponse {
230
- payload: AntigravityDiscoveryApiResponse;
231
- endpoint: string;
232
- }
240
+ type AntigravityDiscoveryResponse =
241
+ | { payload: AntigravityDiscoveryApiResponse; endpoint: string }
242
+ | { rejectedStatus: 401 | 403 };
233
243
 
234
244
  async function fetchAntigravityDiscoveryResponse(
235
245
  options: FetchAntigravityDiscoveryModelsOptions,
@@ -243,6 +253,8 @@ async function fetchAntigravityDiscoveryResponse(
243
253
  ? [trimTrailingSlashes(options.endpoint)]
244
254
  : DEFAULT_ANTIGRAVITY_DISCOVERY_ENDPOINTS.map(trimTrailingSlashes);
245
255
 
256
+ let rejectedStatus: 401 | 403 | undefined;
257
+ let transientFailure = false;
246
258
  for (const endpoint of endpoints) {
247
259
  let response: Response;
248
260
  try {
@@ -257,10 +269,14 @@ async function fetchAntigravityDiscoveryResponse(
257
269
  signal: options.signal,
258
270
  });
259
271
  } catch {
272
+ transientFailure = true;
260
273
  continue;
261
274
  }
262
275
 
263
276
  if (!response.ok) {
277
+ if (response.status === 401) rejectedStatus = 401;
278
+ else if (response.status === 403) rejectedStatus = 403;
279
+ else transientFailure = true;
264
280
  continue;
265
281
  }
266
282
 
@@ -268,16 +284,16 @@ async function fetchAntigravityDiscoveryResponse(
268
284
  try {
269
285
  payload = await response.json();
270
286
  } catch {
287
+ transientFailure = true;
271
288
  continue;
272
289
  }
273
290
 
274
291
  const parsed = parseAntigravityDiscoveryResponse(payload);
275
- if (parsed) {
276
- return { payload: parsed, endpoint };
277
- }
292
+ if (parsed) return { payload: parsed, endpoint };
293
+ transientFailure = true;
278
294
  }
279
295
 
280
- return null;
296
+ return rejectedStatus !== undefined && !transientFailure ? { rejectedStatus } : null;
281
297
  }
282
298
 
283
299
  function parseAntigravityDiscoveryResponse(value: unknown): AntigravityDiscoveryApiResponse | null {
@@ -1,6 +1,6 @@
1
1
  import * as http2 from "node:http2";
2
2
  import { type } from "@linxiraos/pi-omptype";
3
- import { collapseVariants, type EffortVariantFamily } from "../compat/collapse";
3
+ import { collapseVariants, type EffortVariantFamily, reviewedVariantFamilyId } from "../compat/collapse";
4
4
  import { compareRevision, parseRevision } from "../compat/revision";
5
5
  import { resolveCatalogAxes, resolveModelPolicy } from "../compat/resolve";
6
6
  import { classifyModel } from "../compat/taxonomy";
@@ -577,14 +577,15 @@ function normalizeRichCursorModels(
577
577
  const first = entries[0];
578
578
  if (!first) continue;
579
579
  const dimensions = cursorLaneParameters(first.parameters);
580
- const laneId = cursorLaneId(
580
+ const richLaneId = cursorLaneId(
581
581
  baseId,
582
582
  dimensions,
583
583
  parameterDefaults,
584
584
  first.variant?.isMaxMode === true,
585
585
  claimedLaneIds,
586
586
  );
587
- const laneName = cursorLaneName(details, laneId, baseId);
587
+ const laneName = cursorLaneName(details, richLaneId, baseId);
588
+ const laneId = reviewedCursorLaneId(entries, claimedLaneIds) ?? richLaneId;
588
589
  normalized.push(
589
590
  ...buildRichCursorLane(
590
591
  details,
@@ -706,6 +707,28 @@ function cursorLaneId(
706
707
  return candidate;
707
708
  }
708
709
 
710
+ /**
711
+ * The reviewed collapse family owning a lane's legacy slugs, when exactly one
712
+ * family claims any of them (slugs no family lists, e.g. a newly added tier,
713
+ * stay in the lane). `AvailableModels` names Grok 4.5/4.6 `grok-4.6` while its
714
+ * slugs are `cursor-grok-4.6-*`; reusing the family id keeps the lane id equal
715
+ * to the `GetUsableModels`-only fallback and the bundled catalog, so a selector
716
+ * survives whichever RPC answered.
717
+ */
718
+ function reviewedCursorLaneId(entries: readonly NormalizedRichVariant[], claimed: Set<string>): string | undefined {
719
+ let familyId: string | undefined;
720
+ for (const entry of entries) {
721
+ const slug = entry.variant?.legacySlug?.trim();
722
+ const owner = slug ? reviewedVariantFamilyId("cursor", slug) : undefined;
723
+ if (owner === undefined) continue;
724
+ if (familyId !== undefined && owner !== familyId) return undefined;
725
+ familyId = owner;
726
+ }
727
+ if (familyId === undefined || claimed.has(familyId)) return undefined;
728
+ claimed.add(familyId);
729
+ return familyId;
730
+ }
731
+
709
732
  function sanitizeCursorLanePart(value: string): string {
710
733
  return value
711
734
  .toLowerCase()
@@ -859,7 +882,8 @@ function buildRichCursorLane(
859
882
  cursorRequiresDataRetention: details.requiresDataRetention,
860
883
  cursorSupportsAgent: details.supportsAgent,
861
884
  cursorSupportsSandboxing: details.supportsSandboxing,
862
- isProviderDefault,
885
+ // Not marked `isProviderDefault`: Cursor keeps its bundled startup default, so
886
+ // the account default only picks the lane's default wire id and the badge.
863
887
  isRecommended: isProviderDefault || details.defaultOn,
864
888
  description:
865
889
  variant?.tagline ??
@@ -9,6 +9,7 @@ import {
9
9
  type ClientModelConfig,
10
10
  DisplayOption,
11
11
  GetCliModelConfigsRequestSchema,
12
+ type GetCliModelConfigsResponse,
12
13
  GetCliModelConfigsResponseSchema,
13
14
  type Metadata,
14
15
  MetadataSchema,
@@ -346,7 +347,7 @@ export async function fetchDevinModels(
346
347
  GetCliModelConfigsResponseSchema,
347
348
  new Uint8Array(await response.arrayBuffer()),
348
349
  );
349
- return decoded ? normalizeDevinModels(decoded.clientModelConfigs, options.baseUrl) : null;
350
+ return decoded ? normalizeDevinModels(decoded, options.baseUrl) : null;
350
351
  } catch {
351
352
  return null;
352
353
  }
@@ -497,9 +498,12 @@ function routeDevinFusionLead(spec: ModelSpec<"devin-agent">, lead: ModelSpec<"d
497
498
  }
498
499
 
499
500
  function normalizeDevinModels(
500
- configs: readonly ClientModelConfig[],
501
+ response: GetCliModelConfigsResponse,
501
502
  baseUrlOverride: string | undefined,
502
503
  ): ModelSpec<"devin-agent">[] {
504
+ const configs = response.clientModelConfigs;
505
+ // The model the native client starts this account on (plan-dependent).
506
+ const accountDefaultUid = response.defaultOverrideModelConfig?.modelUid.trim();
503
507
  const baseUrl = baseUrlOverride ?? DEVIN_DEFAULT_BASE_URL;
504
508
  const specs: ModelSpec<"devin-agent">[] = [];
505
509
  const seen = new Set<string>();
@@ -539,6 +543,7 @@ function normalizeDevinModels(
539
543
  if (lead === null) continue;
540
544
  const spec = devinModelSpec(config, uid, baseUrl, isAssignModelRouter);
541
545
  if (lead !== undefined) routeDevinFusionLead(spec, devinModelSpec(liveConfigs.get(lead)!, lead, baseUrl, false));
546
+ if (uid === accountDefaultUid) spec.isProviderDefault = true;
542
547
  specs.push(spec);
543
548
  // A router is a server-side dispatcher, not an effort tier: it stays a
544
549
  // standalone model even when upstream files it under a family.
@@ -554,5 +559,18 @@ function normalizeDevinModels(
554
559
  const families = devinDynamicFamilies(lanes.values());
555
560
  const dynamic = families.length > 0 ? collapseVariants(specs, { table: { families } }) : specs;
556
561
  const collapsed = collapseVariants(dynamic);
562
+ // The account default outranks its family's own default lane when an effort
563
+ // routes to it, from server metadata or the static table: the family starts
564
+ // on that wire uid and effort. Other lanes (a no-thinking `off` default) keep
565
+ // the family's own default.
566
+ const accountFamily = collapsed.find(spec => spec.isProviderDefault && spec.thinking?.effortRouting);
567
+ const accountEffort = THINKING_EFFORTS.find(
568
+ effort => accountFamily?.thinking?.effortRouting?.[effort] === accountDefaultUid,
569
+ );
570
+ if (accountFamily?.thinking && accountEffort !== undefined) {
571
+ accountFamily.thinking = { ...accountFamily.thinking, defaultLevel: accountEffort };
572
+ if (accountFamily.id === accountDefaultUid) delete accountFamily.requestModelId;
573
+ else accountFamily.requestModelId = accountDefaultUid;
574
+ }
557
575
  return collapsed.sort((a, b) => a.id.localeCompare(b.id));
558
576
  }
package/src/hosts.d.ts CHANGED
@@ -15,6 +15,10 @@ export declare const KNOWN_HOSTS: {
15
15
  readonly providers: readonly ["openai"];
16
16
  readonly urlMarkers: readonly ["api.openai.com"];
17
17
  };
18
+ /** URL-only: a Codex provider rerouted through a proxy does not imply the subscription backend's capabilities. */
19
+ readonly openaiCodex: {
20
+ readonly urlMarkers: readonly ["chatgpt.com/backend-api", "chat.openai.com/backend-api"];
21
+ };
18
22
  readonly azureOpenAI: {
19
23
  readonly providers: readonly ["azure"];
20
24
  readonly urlMarkers: readonly [".openai.azure.com", "azure.com/openai", "models.inference.ai.azure.com"];
package/src/hosts.ts CHANGED
@@ -25,6 +25,8 @@ interface HostClassSpec {
25
25
 
26
26
  export const KNOWN_HOSTS = {
27
27
  openai: { providers: ["openai"], urlMarkers: ["api.openai.com"] },
28
+ /** URL-only: a Codex provider rerouted through a proxy does not imply the subscription backend's capabilities. */
29
+ openaiCodex: { urlMarkers: ["chatgpt.com/backend-api", "chat.openai.com/backend-api"] },
28
30
  azureOpenAI: {
29
31
  providers: ["azure"],
30
32
  urlMarkers: [".openai.azure.com", "azure.com/openai", "models.inference.ai.azure.com"],
@@ -35,6 +35,12 @@ export interface CacheEntry<TApi extends Api = Api> {
35
35
  */
36
36
  staticFingerprint: string;
37
37
  }
38
+ /**
39
+ * Closes the shared handle to the default `<agent-dir>/models.db`; the next
40
+ * default-path access reopens it. Call before deleting an agent directory the
41
+ * cache was opened under — Windows cannot remove a database that is still open.
42
+ */
43
+ export declare function closeModelCache(): void;
38
44
  export declare function readModelCache<TApi extends Api>(providerId: string, ttlMs: number, now: () => number, dbPath?: string): CacheEntry<TApi> | null;
39
45
  /**
40
46
  * Persist a provider snapshot.