@linxiraos/pi-catalog 1.1.27 → 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 (100) hide show
  1. package/CHANGELOG.md +4 -17
  2. package/dist/types/compat/auth-ids.d.ts +2 -2
  3. package/dist/types/compat/judgment-questions.d.ts +7 -0
  4. package/dist/types/compat/prompt-cache-lookback.d.ts +7 -0
  5. package/dist/types/compat/provider-ids.d.ts +1 -1
  6. package/dist/types/compat/reasoning-carry.d.ts +21 -0
  7. package/dist/types/compat/request-size.d.ts +7 -0
  8. package/dist/types/compat/taxonomy.d.ts +0 -10
  9. package/dist/types/compat/types.d.ts +4 -0
  10. package/dist/types/discovery/antigravity.d.ts +13 -3
  11. package/dist/types/model-manager.d.ts +16 -0
  12. package/dist/types/provider-models/account-access.d.ts +11 -0
  13. package/dist/types/provider-models/google.d.ts +23 -1
  14. package/dist/types/provider-models/openai-compat.d.ts +19 -1
  15. package/dist/types/provider-models/special.d.ts +2 -0
  16. package/dist/types/types.d.ts +106 -20
  17. package/dist/types/wire/cloudflare-ai-gateway.d.ts +0 -10
  18. package/dist/types/wire/cloudflare.d.ts +10 -0
  19. package/dist/types/wire/factory-droid.d.ts +1 -1
  20. package/package.json +4 -4
  21. package/src/build.ts +39 -35
  22. package/src/compat/auth-ids.ts +4 -0
  23. package/src/compat/axes.ts +37 -3
  24. package/src/compat/cascade.ts +6 -1
  25. package/src/compat/judgment-questions.ts +12 -0
  26. package/src/compat/prompt-cache-lookback.ts +23 -0
  27. package/src/compat/provider-ids.d.ts +1 -1
  28. package/src/compat/provider-ids.ts +2 -0
  29. package/src/compat/reasoning-carry.ts +64 -0
  30. package/src/compat/request-size.ts +25 -0
  31. package/src/compat/resolve.ts +16 -2
  32. package/src/compat/rules/README.md +12 -2
  33. package/src/compat/rules/auth/_order.kdl +2 -2
  34. package/src/compat/rules/auth/cloudflare-workers-ai.kdl +9 -0
  35. package/src/compat/rules/auth/coralbricks.kdl +13 -0
  36. package/src/compat/rules/auth/minimax-code-cn.kdl +1 -1
  37. package/src/compat/rules/auth/minimax-code.kdl +1 -1
  38. package/src/compat/rules/auth/openai-codex.kdl +1 -1
  39. package/src/compat/rules/auth/stencil.kdl +1 -1
  40. package/src/compat/rules/classes/anthropic.kdl +87 -1
  41. package/src/compat/rules/classes/deepseek.kdl +6 -0
  42. package/src/compat/rules/classes/glm.kdl +16 -7
  43. package/src/compat/rules/classes/kimi.kdl +8 -0
  44. package/src/compat/rules/classes/minimax.kdl +31 -1
  45. package/src/compat/rules/classes/openai.kdl +5 -3
  46. package/src/compat/rules/classes/qwen.kdl +2 -1
  47. package/src/compat/rules/providers/abliteration.kdl +7 -0
  48. package/src/compat/rules/providers/anthropic.kdl +13 -0
  49. package/src/compat/rules/providers/apple.kdl +1 -0
  50. package/src/compat/rules/providers/cloudflare-workers-ai.kdl +34 -0
  51. package/src/compat/rules/providers/coralbricks.kdl +62 -0
  52. package/src/compat/rules/providers/cursor.kdl +22 -0
  53. package/src/compat/rules/providers/devin.kdl +2 -2
  54. package/src/compat/rules/providers/factory-droid.kdl +77 -13
  55. package/src/compat/rules/providers/github-copilot.kdl +29 -0
  56. package/src/compat/rules/providers/groq.kdl +21 -0
  57. package/src/compat/rules/providers/judgment-questions.kdl +53 -0
  58. package/src/compat/rules/providers/litellm.kdl +4 -1
  59. package/src/compat/rules/providers/minimax-code-cn.kdl +10 -5
  60. package/src/compat/rules/providers/minimax-code.kdl +10 -5
  61. package/src/compat/rules/providers/mistral.kdl +25 -1
  62. package/src/compat/rules/providers/moonshot.kdl +11 -0
  63. package/src/compat/rules/providers/openai-codex.kdl +17 -13
  64. package/src/compat/rules/providers/openai.kdl +22 -0
  65. package/src/compat/rules/providers/opencode-zen.kdl +20 -0
  66. package/src/compat/rules/providers/openrouter.kdl +6 -0
  67. package/src/compat/rules/providers/zhipu-coding-plan.kdl +10 -0
  68. package/src/compat/rules/runtime/behavior.kdl +7 -4
  69. package/src/compat/rules/taxonomy/_collapse.kdl +18 -0
  70. package/src/compat/rules.json +1 -1
  71. package/src/compat/taxonomy.d.ts +0 -10
  72. package/src/compat/taxonomy.ts +3 -3
  73. package/src/compat/types.d.ts +4 -0
  74. package/src/compat/types.ts +4 -0
  75. package/src/discovery/antigravity.d.ts +13 -3
  76. package/src/discovery/antigravity.ts +33 -17
  77. package/src/discovery/cursor.ts +2 -1
  78. package/src/discovery/devin.ts +20 -2
  79. package/src/model-cache.ts +113 -47
  80. package/src/model-manager.d.ts +16 -0
  81. package/src/model-manager.ts +107 -41
  82. package/src/model-tokenizer.ts +3 -0
  83. package/src/models.json +1 -1
  84. package/src/provider-models/account-access.d.ts +11 -0
  85. package/src/provider-models/account-access.ts +30 -0
  86. package/src/provider-models/cache-provider-id.ts +3 -2
  87. package/src/provider-models/descriptors.ts +4 -0
  88. package/src/provider-models/google.d.ts +23 -1
  89. package/src/provider-models/google.ts +70 -14
  90. package/src/provider-models/openai-compat.d.ts +19 -1
  91. package/src/provider-models/openai-compat.ts +215 -31
  92. package/src/provider-models/special.d.ts +2 -0
  93. package/src/provider-models/special.ts +15 -18
  94. package/src/types.d.ts +106 -20
  95. package/src/types.ts +149 -17
  96. package/src/wire/cloudflare-ai-gateway.d.ts +0 -10
  97. package/src/wire/cloudflare-ai-gateway.ts +0 -36
  98. package/src/wire/cloudflare.ts +40 -0
  99. package/src/wire/factory-droid.d.ts +1 -1
  100. package/src/wire/factory-droid.ts +1 -1
@@ -0,0 +1,11 @@
1
+ import type { Api, ModelSpec } from "../types";
2
+ /**
3
+ * Merge per-account discovery catalogs into one list, deduped by model id.
4
+ *
5
+ * The first catalog to list an id supplies its spec; {@link ModelSpec.accountAccess}
6
+ * is merged from every catalog listing that id, so credential selection can
7
+ * route account-gated models to the accounts that serve them. Used by
8
+ * multi-account discovery for Codex and Antigravity, whose rosters differ per
9
+ * account.
10
+ */
11
+ export declare function unionAccountCatalogs<TApi extends Api>(catalogs: readonly (readonly ModelSpec<TApi>[])[]): ModelSpec<TApi>[];
@@ -0,0 +1,30 @@
1
+ import type { Api, ModelSpec } from "../types";
2
+
3
+ /**
4
+ * Merge per-account discovery catalogs into one list, deduped by model id.
5
+ *
6
+ * The first catalog to list an id supplies its spec; {@link ModelSpec.accountAccess}
7
+ * is merged from every catalog listing that id, so credential selection can
8
+ * route account-gated models to the accounts that serve them. Used by
9
+ * multi-account discovery for Codex and Antigravity, whose rosters differ per
10
+ * account.
11
+ */
12
+ export function unionAccountCatalogs<TApi extends Api>(
13
+ catalogs: readonly (readonly ModelSpec<TApi>[])[],
14
+ ): ModelSpec<TApi>[] {
15
+ const byId = new Map<string, ModelSpec<TApi>>();
16
+ for (const catalog of catalogs) {
17
+ for (const model of catalog) {
18
+ const existing = byId.get(model.id);
19
+ if (!existing) {
20
+ byId.set(model.id, model);
21
+ } else if (model.accountAccess) {
22
+ byId.set(model.id, {
23
+ ...existing,
24
+ accountAccess: { ...existing.accountAccess, ...model.accountAccess },
25
+ });
26
+ }
27
+ }
28
+ }
29
+ return [...byId.values()];
30
+ }
@@ -108,11 +108,12 @@ export function resolveModelCacheProviderId(providerId: string, options: ModelCa
108
108
  // authoritative model rows.
109
109
  // v3 invalidates zero-price rows written before rich lanes were priced
110
110
  // from the KDL rate card; v4 keys the scope on the token's stable
111
- // account subject instead of the rotating JWT.
111
+ // account subject instead of the rotating JWT; v5 drops rows that still
112
+ // mark the account default `isProviderDefault`, which startup now honors.
112
113
  const baseUrl = (options.baseUrl ?? CURSOR_DEFAULT_BASE_URL).replace(/\/+$/, "");
113
114
  const apiKey = options.apiKey ?? "";
114
115
  const scope = `${cursorCredentialSubject(apiKey) ?? apiKey}\u0000${baseUrl}`;
115
- return `cursor:rich-models-v4:${Bun.hash(scope).toString(36)}`;
116
+ return `cursor:rich-models-v5:${Bun.hash(scope).toString(36)}`;
116
117
  }
117
118
  case "charm-hyper": {
118
119
  // Discovery is authoritative for this gateway, so a warm cache is served
@@ -29,6 +29,7 @@ import {
29
29
  clinePassModelManagerOptions,
30
30
  cloudflareAiGatewayModelManagerOptions,
31
31
  commandCodeModelManagerOptions,
32
+ coralbricksModelManagerOptions,
32
33
  coreWeaveModelManagerOptions,
33
34
  deepinfraModelManagerOptions,
34
35
  deepseekModelManagerOptions,
@@ -78,6 +79,7 @@ import {
78
79
  zhipuCodingPlanModelManagerOptions,
79
80
  } from "./openai-compat";
80
81
  import {
82
+ cloudflareWorkersAiModelManagerOptions,
81
83
  cursorModelManagerOptions,
82
84
  devinModelManagerOptions,
83
85
  factoryDroidModelManagerOptions,
@@ -104,7 +106,9 @@ const MODEL_MANAGER_FACTORIES: Readonly<Partial<Record<KnownProvider, ModelManag
104
106
  cerebras: config => cerebrasModelManagerOptions(config),
105
107
  "charm-hyper": config => charmHyperModelManagerOptions(config),
106
108
  "cloudflare-ai-gateway": config => cloudflareAiGatewayModelManagerOptions(config),
109
+ "cloudflare-workers-ai": () => cloudflareWorkersAiModelManagerOptions(),
107
110
  commandcode: config => commandCodeModelManagerOptions(config),
111
+ coralbricks: config => coralbricksModelManagerOptions(config),
108
112
  cursor: config => cursorModelManagerOptions(config),
109
113
  deepinfra: config => deepinfraModelManagerOptions(config),
110
114
  deepseek: config => deepseekModelManagerOptions(config),
@@ -11,8 +11,30 @@ export interface GoogleVertexModelManagerConfig {
11
11
  signal?: AbortSignal;
12
12
  fetch?: FetchImpl;
13
13
  }
14
+ /** One Antigravity OAuth account whose `fetchAvailableModels` roster discovery reads. */
15
+ export interface GoogleAntigravityAccount {
16
+ /** OAuth access token used for `Authorization: Bearer ...`. */
17
+ accessToken: string;
18
+ /**
19
+ * Credential identity recorded in {@link ModelSpec.accountAccess} for every
20
+ * model this account serves (see `oauthAccountKey` in `@linxiraos/pi-ai`).
21
+ * When any account lacks one, no model records per-account access.
22
+ */
23
+ accountKey?: string;
24
+ }
14
25
  export interface GoogleAntigravityModelManagerConfig {
15
- oauthToken?: string;
26
+ /**
27
+ * Resolves every configured Antigravity account at discovery time. Rosters
28
+ * are plan-scoped (Claude 5.5 is served to some accounts only) and
29
+ * authoritative, so each account's roster is fetched and the results are
30
+ * unioned; reading one account's roster would prune models only its
31
+ * siblings serve (#14924).
32
+ *
33
+ * Returns `null` to abort discovery (e.g. an account's credential failed to
34
+ * refresh), keeping the previous/bundled catalog instead of caching a
35
+ * partial one.
36
+ */
37
+ resolveAccounts?: () => Promise<readonly GoogleAntigravityAccount[] | null>;
16
38
  endpoint?: string;
17
39
  fetch?: FetchImpl;
18
40
  }
@@ -1,3 +1,4 @@
1
+ import { logger } from "@linxiraos/pi-utils";
1
2
  import { reviewedCollapseTable } from "../compat/collapse";
2
3
  import { classifyModel } from "../compat/taxonomy";
3
4
  import { providerEntry } from "../compat/providers";
@@ -5,7 +6,8 @@ import { fetchAntigravityDiscoveryModels } from "../discovery/antigravity";
5
6
  import { fetchGeminiModels } from "../discovery/gemini";
6
7
  import { fetchGeminiCliQuotaModels } from "../discovery/gemini-cli";
7
8
  import type { ModelManagerOptions } from "../model-manager";
8
- import type { FetchImpl } from "../types";
9
+ import type { FetchImpl, ModelSpec } from "../types";
10
+ import { unionAccountCatalogs } from "./account-access";
9
11
 
10
12
  export interface GoogleModelManagerConfig {
11
13
  apiKey?: string;
@@ -20,8 +22,31 @@ export interface GoogleVertexModelManagerConfig {
20
22
  fetch?: FetchImpl;
21
23
  }
22
24
 
25
+ /** One Antigravity OAuth account whose `fetchAvailableModels` roster discovery reads. */
26
+ export interface GoogleAntigravityAccount {
27
+ /** OAuth access token used for `Authorization: Bearer ...`. */
28
+ accessToken: string;
29
+ /**
30
+ * Credential identity recorded in {@link ModelSpec.accountAccess} for every
31
+ * model this account serves (see `oauthAccountKey` in `@linxiraos/pi-ai`).
32
+ * When any account lacks one, no model records per-account access.
33
+ */
34
+ accountKey?: string;
35
+ }
36
+
23
37
  export interface GoogleAntigravityModelManagerConfig {
24
- oauthToken?: string;
38
+ /**
39
+ * Resolves every configured Antigravity account at discovery time. Rosters
40
+ * are plan-scoped (Claude 5.5 is served to some accounts only) and
41
+ * authoritative, so each account's roster is fetched and the results are
42
+ * unioned; reading one account's roster would prune models only its
43
+ * siblings serve (#14924).
44
+ *
45
+ * Returns `null` to abort discovery (e.g. an account's credential failed to
46
+ * refresh), keeping the previous/bundled catalog instead of caching a
47
+ * partial one.
48
+ */
49
+ resolveAccounts?: () => Promise<readonly GoogleAntigravityAccount[] | null>;
25
50
  endpoint?: string;
26
51
  fetch?: FetchImpl;
27
52
  }
@@ -71,18 +96,49 @@ export function googleVertexModelManagerOptions(_config?: GoogleVertexModelManag
71
96
  export function googleAntigravityModelManagerOptions(
72
97
  config?: GoogleAntigravityModelManagerConfig,
73
98
  ): ModelManagerOptions<"google-gemini-cli"> {
74
- const token = config?.oauthToken;
99
+ const resolveAccounts = config?.resolveAccounts;
75
100
  return {
76
101
  providerId: "google-antigravity",
77
102
  dynamicModelsAuthoritative: providerEntry("google-antigravity")?.dynamicModelsAuthoritative === true,
78
- ...(token
103
+ ...(resolveAccounts
79
104
  ? {
80
- fetchDynamicModels: () =>
81
- fetchAntigravityDiscoveryModels({
82
- token,
83
- endpoint: config?.endpoint,
84
- fetcher: toDiscoveryFetch(config?.fetch),
85
- }),
105
+ fetchDynamicModels: async () => {
106
+ const accounts = await resolveAccounts();
107
+ if (!accounts || accounts.length === 0) return null;
108
+ const fetcher = toDiscoveryFetch(config?.fetch);
109
+ const rosters = await Promise.all(
110
+ accounts.map(async account => ({
111
+ accountKey: account.accountKey,
112
+ result: await fetchAntigravityDiscoveryModels({
113
+ token: account.accessToken,
114
+ endpoint: config?.endpoint,
115
+ fetcher,
116
+ }),
117
+ })),
118
+ );
119
+ const catalogs: { accountKey: string | undefined; models: ModelSpec<"google-gemini-cli">[] }[] = [];
120
+ for (const { accountKey, result } of rosters) {
121
+ // A transient failure would leave the union partial; keep the previous catalog.
122
+ if (!result) return null;
123
+ if (result.rejectedStatus !== undefined) {
124
+ logger.warn("Antigravity model discovery skipped an account whose credential was rejected", {
125
+ accountKey,
126
+ status: result.rejectedStatus,
127
+ });
128
+ continue;
129
+ }
130
+ catalogs.push({ accountKey, models: result.models });
131
+ }
132
+ if (catalogs.length === 0) return null;
133
+ const tagAccess = catalogs.every(catalog => catalog.accountKey !== undefined);
134
+ return unionAccountCatalogs(
135
+ catalogs.map(({ accountKey, models }) =>
136
+ tagAccess && accountKey !== undefined
137
+ ? models.map(model => ({ ...model, accountAccess: { [accountKey]: {} } }))
138
+ : models,
139
+ ),
140
+ );
141
+ },
86
142
  }
87
143
  : undefined),
88
144
  };
@@ -103,19 +159,19 @@ export function googleGeminiCliModelManagerOptions(
103
159
  if (collapseTable === undefined) {
104
160
  throw new Error("missing reviewed collapse table for google-gemini-cli");
105
161
  }
106
- const models = await fetchAntigravityDiscoveryModels({
162
+ const result = await fetchAntigravityDiscoveryModels({
107
163
  token,
108
164
  fetcher,
109
- collapseTable: collapseTable,
165
+ collapseTable,
110
166
  });
111
167
  // Antigravity's fetchAvailableModels is unreachable for
112
168
  // credentials without Antigravity entitlement (Code Assist
113
169
  // Standard returns HTTP 403). Fall back to the account's own
114
170
  // retrieveUserQuota list on Cloud Code Assist.
115
- if (models === null) {
171
+ if (result === null || result.rejectedStatus !== undefined) {
116
172
  return fetchGeminiCliQuotaModels({ token, projectId: config?.projectId, endpoint, fetcher });
117
173
  }
118
- return models
174
+ return result.models
119
175
  .filter(m => classifyModel("google-gemini-cli", m.id, { lenient: true }).class === "gemini")
120
176
  .map(m => ({
121
177
  ...m,
@@ -69,7 +69,7 @@ export interface OpenAIModelManagerConfig {
69
69
  baseUrl?: string;
70
70
  fetch?: FetchImpl;
71
71
  }
72
- export declare function openaiModelManagerOptions(config?: OpenAIModelManagerConfig): ModelManagerOptions<"openai-responses">;
72
+ export declare function openaiModelManagerOptions(config?: OpenAIModelManagerConfig): ModelManagerOptions<Api>;
73
73
  /**
74
74
  * Re-derive the generated pro-reasoning aliases (`gpt-5.6-*-pro`) for the
75
75
  * first-party `openai` gpt-5.6 rows. Each alias inherits the base row's
@@ -154,6 +154,19 @@ export interface DeepinfraModelManagerConfig {
154
154
  * model availability at runtime is still gated on credentials by the registry.
155
155
  */
156
156
  export declare function deepinfraModelManagerOptions(config?: DeepinfraModelManagerConfig): ModelManagerOptions<"openai-completions">;
157
+ export declare const CORALBRICKS_BASE_URL = "https://inference.coralbricks.ai/v1";
158
+ /** CoralBricks OpenAI-compatible discovery configuration. */
159
+ export interface CoralbricksModelManagerConfig {
160
+ apiKey?: string;
161
+ baseUrl?: string;
162
+ fetch?: FetchImpl;
163
+ }
164
+ /**
165
+ * Builds CoralBricks' model-discovery manager. `/v1/models` is key-protected
166
+ * (401 without a bearer key), so a keyless config serves only the bundled
167
+ * reviewed seed rows; with a key, live rows are authoritative over the bundle.
168
+ */
169
+ export declare function coralbricksModelManagerOptions(config?: CoralbricksModelManagerConfig): ModelManagerOptions<"openai-completions">;
157
170
  export interface XaiModelManagerConfig {
158
171
  apiKey?: string;
159
172
  baseUrl?: string;
@@ -339,6 +352,11 @@ export interface OpenRouterModelManagerConfig {
339
352
  baseUrl?: string;
340
353
  fetch?: FetchImpl;
341
354
  }
355
+ /**
356
+ * OpenRouter's Decisions API lives at `/api/alpha`, a sibling of the `/api/v1`
357
+ * chat root; derive it so a custom gateway base URL keeps both aligned.
358
+ */
359
+ export declare function openrouterDecisionsBaseUrl(chatBaseUrl: string): string;
342
360
  export declare function openrouterModelManagerOptions(config?: OpenRouterModelManagerConfig): ModelManagerOptions<Api>;
343
361
  export interface ZenMuxModelManagerConfig {
344
362
  apiKey?: string;
@@ -1071,8 +1071,8 @@ export interface OpenAIModelManagerConfig {
1071
1071
  fetch?: FetchImpl;
1072
1072
  }
1073
1073
 
1074
- export function openaiModelManagerOptions(config?: OpenAIModelManagerConfig): ModelManagerOptions<"openai-responses"> {
1075
- return createOpenAICompatibleModelManagerOptions({
1074
+ export function openaiModelManagerOptions(config?: OpenAIModelManagerConfig): ModelManagerOptions<Api> {
1075
+ const options = createOpenAICompatibleModelManagerOptions({
1076
1076
  api: "openai-responses",
1077
1077
  providerId: "openai",
1078
1078
  defaultBaseUrl: OPENAI_API_BASE_URL,
@@ -1081,6 +1081,30 @@ export function openaiModelManagerOptions(config?: OpenAIModelManagerConfig): Mo
1081
1081
  filterModel: (_entry, model, references) => isLikelyOpenAIResponsesModelId(model.id, references),
1082
1082
  mapModel: mapWithBundledReference,
1083
1083
  });
1084
+ const fetchDiscovered = options.fetchDynamicModels;
1085
+ if (!fetchDiscovered) return options;
1086
+ return {
1087
+ ...options,
1088
+ fetchDynamicModels: async () => appendUnbundledSeedModels("openai", await fetchDiscovered()),
1089
+ };
1090
+ }
1091
+
1092
+ /**
1093
+ * Append the authored seed rows that neither discovery nor the bundled catalog
1094
+ * carries yet: a seed added since the last models.json regeneration (the
1095
+ * Decisions judge row) reaches runtime through a successful discovery. Seeds
1096
+ * already bundled keep resolving through the bundled slice, and the static
1097
+ * catalog (and with it the cache fingerprint) stays the bundled one.
1098
+ */
1099
+ function appendUnbundledSeedModels(
1100
+ providerId: GeneratedProvider,
1101
+ discovered: readonly ModelSpec<Api>[] | null,
1102
+ ): readonly ModelSpec<Api>[] | null {
1103
+ if (!discovered) return null;
1104
+ const known = new Set(getBundledModels(providerId).map(model => model.id));
1105
+ for (const model of discovered) known.add(model.id);
1106
+ const seeds = seedModels(providerId).filter(seed => !known.has(seed.id));
1107
+ return seeds.length === 0 ? discovered : [...discovered, ...seeds.map(seed => ({ ...seed }))];
1084
1108
  }
1085
1109
 
1086
1110
  /**
@@ -1618,10 +1642,171 @@ export function deepinfraModelManagerOptions(
1618
1642
  return {
1619
1643
  providerId: "deepinfra",
1620
1644
  dynamicModelsAuthoritative: true,
1645
+ // `vision`/`vlm` tags are the whole truth for modality on this host.
1646
+ dynamicInputAuthoritative: true,
1621
1647
  fetchDynamicModels: () => fetchDeepinfraModels({ baseUrl, apiKey, fetch: config?.fetch, references }),
1622
1648
  };
1623
1649
  }
1624
1650
 
1651
+ // ---------------------------------------------------------------------------
1652
+ // CoralBricks
1653
+ // ---------------------------------------------------------------------------
1654
+
1655
+ export const CORALBRICKS_BASE_URL = "https://inference.coralbricks.ai/v1";
1656
+
1657
+ /** CoralBricks OpenAI-compatible discovery configuration. */
1658
+ export interface CoralbricksModelManagerConfig {
1659
+ apiKey?: string;
1660
+ baseUrl?: string;
1661
+ fetch?: FetchImpl;
1662
+ }
1663
+
1664
+ /**
1665
+ * The CoralBricks-specific fields of a `GET /v1/models` row: per-million USD
1666
+ * pricing and the capability flags Coral documents as authoritative
1667
+ * (https://www.coralbricks.ai/docs.md).
1668
+ */
1669
+ interface CoralbricksModelRecord extends OpenAICompatibleModelRecord {
1670
+ context_length?: unknown;
1671
+ pricing?: unknown;
1672
+ supports_chat?: unknown;
1673
+ supports_image_input?: unknown;
1674
+ supports_tools?: unknown;
1675
+ supports_reasoning?: unknown;
1676
+ reasoning?: unknown;
1677
+ }
1678
+
1679
+ /**
1680
+ * Read one live per-million price; a missing or negative field falls back to
1681
+ * the bundled reference. An explicit `0` is a real price: the manager keeps it
1682
+ * because `coralbricksModelManagerOptions` declares `dynamicCostAuthoritative`.
1683
+ */
1684
+ function coralRate(value: unknown, fallback: number): number {
1685
+ const parsed = toNumber(value);
1686
+ return parsed !== undefined && parsed >= 0 ? parsed : fallback;
1687
+ }
1688
+
1689
+ /**
1690
+ * Map one CoralBricks catalog row to a chat model spec; non-chat rows
1691
+ * (`supports_chat: false`) are dropped. Pricing arrives in Coral's own
1692
+ * per-million field names; `cached_input_per_m` is $0 on every model and a
1693
+ * missing field falls back to the bundled reference.
1694
+ * Live reasoning capabilities and controls override the bundled fallback.
1695
+ * The endpoint publishes no output cap, so `maxTokens` keeps its reference
1696
+ * value rather than being invented from the context window.
1697
+ */
1698
+ function mapCoralbricksModel(
1699
+ entry: CoralbricksModelRecord,
1700
+ defaults: ModelSpec<"openai-completions">,
1701
+ reference: ModelSpec<"openai-completions"> | undefined,
1702
+ ): ModelSpec<"openai-completions"> | null {
1703
+ if (entry.supports_chat === false) {
1704
+ return null;
1705
+ }
1706
+ const pricing = isRecord(entry.pricing) ? entry.pricing : {};
1707
+ // A bundled reference may lend metadata, but its runner kind is not
1708
+ // evidence the chat roster advertised it.
1709
+ const { kind: _inheritedKind, ...chatReference } = reference ?? {};
1710
+ const hasReasoningFlag = typeof entry.supports_reasoning === "boolean";
1711
+ const reasoning = hasReasoningFlag
1712
+ ? entry.supports_reasoning === true
1713
+ : (reference?.reasoning ?? defaults.reasoning);
1714
+ const controls = isRecord(entry.reasoning) ? entry.reasoning : undefined;
1715
+ const wireEfforts = controls?.supported_efforts;
1716
+ let thinking = reasoning ? reference?.thinking : undefined;
1717
+ let compat = reference?.compat;
1718
+ if (reasoning && Array.isArray(wireEfforts)) {
1719
+ const efforts = THINKING_EFFORTS.filter(effort => wireEfforts.includes(effort));
1720
+ // `none` is the server's off default, not an Effort (nor `minimal`).
1721
+ const defaultLevel = efforts.find(effort => effort === controls?.default_effort);
1722
+ thinking =
1723
+ efforts.length > 0
1724
+ ? {
1725
+ mode: "effort",
1726
+ efforts,
1727
+ ...(defaultLevel !== undefined && { defaultLevel }),
1728
+ ...(typeof controls?.mandatory === "boolean" && { requiresEffort: controls.mandatory }),
1729
+ }
1730
+ : undefined;
1731
+ }
1732
+ if (hasReasoningFlag || Array.isArray(wireEfforts)) {
1733
+ // An explicit empty/unknown vocabulary must not regrow a guessed dial
1734
+ // from identity or KDL. Missing legacy metadata still uses the reference.
1735
+ compat = { ...compat, trustExplicitThinkingOnly: true };
1736
+ }
1737
+ if (reasoning && controls) {
1738
+ if (controls.mandatory === true || controls.disable === null) {
1739
+ compat = { ...compat, reasoningDisableMode: "lowest-effort" };
1740
+ } else if (isRecord(controls.disable) && controls.disable.reasoning_effort === "none") {
1741
+ compat = { ...compat, reasoningDisableMode: "none-effort" };
1742
+ }
1743
+ }
1744
+ const input: ("text" | "image")[] =
1745
+ entry.supports_image_input === true
1746
+ ? ["text", "image"]
1747
+ : entry.supports_image_input === false
1748
+ ? ["text"]
1749
+ : (reference?.input ?? defaults.input);
1750
+ return {
1751
+ ...defaults,
1752
+ ...chatReference,
1753
+ id: defaults.id,
1754
+ name: reference?.name ?? defaults.name,
1755
+ api: defaults.api,
1756
+ provider: defaults.provider,
1757
+ baseUrl: defaults.baseUrl,
1758
+ reasoning,
1759
+ thinking,
1760
+ compat,
1761
+ input,
1762
+ ...(typeof entry.supports_tools === "boolean" ? { supportsTools: entry.supports_tools } : {}),
1763
+ cost: {
1764
+ input: coralRate(pricing.input_per_m, reference?.cost.input ?? 0),
1765
+ output: coralRate(pricing.output_per_m, reference?.cost.output ?? 0),
1766
+ cacheRead: coralRate(pricing.cached_input_per_m, reference?.cost.cacheRead ?? 0),
1767
+ cacheWrite: coralRate(pricing.cache_write_per_m, reference?.cost.cacheWrite ?? 0),
1768
+ },
1769
+ contextWindow: toPositiveNumber(entry.context_length, reference?.contextWindow ?? null),
1770
+ maxTokens: reference?.maxTokens ?? null,
1771
+ };
1772
+ }
1773
+
1774
+ /**
1775
+ * Builds CoralBricks' model-discovery manager. `/v1/models` is key-protected
1776
+ * (401 without a bearer key), so a keyless config serves only the bundled
1777
+ * reviewed seed rows; with a key, live rows are authoritative over the bundle.
1778
+ */
1779
+ export function coralbricksModelManagerOptions(
1780
+ config?: CoralbricksModelManagerConfig,
1781
+ ): ModelManagerOptions<"openai-completions"> {
1782
+ const apiKey = config?.apiKey;
1783
+ const baseUrl = config?.baseUrl ?? CORALBRICKS_BASE_URL;
1784
+ const references = createBundledReferenceMap<"openai-completions">("coralbricks");
1785
+ return {
1786
+ providerId: "coralbricks",
1787
+ dynamicModelsAuthoritative: true,
1788
+ dynamicReasoningAuthoritative: true,
1789
+ // `supports_image_input` is the row's whole truth for modality (Coral
1790
+ // answers unsupported content with `400 unsupported_content_type`).
1791
+ dynamicInputAuthoritative: true,
1792
+ // Coral's `pricing` block is the deployment tariff; an explicit live `0`
1793
+ // (a free model or no cache-write charge) must not revert to the bundle.
1794
+ dynamicCostAuthoritative: true,
1795
+ ...(apiKey && {
1796
+ fetchDynamicModels: () =>
1797
+ fetchOpenAICompatibleModels({
1798
+ api: "openai-completions",
1799
+ provider: "coralbricks",
1800
+ baseUrl,
1801
+ apiKey,
1802
+ fetch: config?.fetch,
1803
+ mapModel: (entry, defaults) =>
1804
+ mapCoralbricksModel(entry as CoralbricksModelRecord, defaults, references.get(defaults.id)),
1805
+ }),
1806
+ }),
1807
+ };
1808
+ }
1809
+
1625
1810
  // ---------------------------------------------------------------------------
1626
1811
  // 6. xAI
1627
1812
  // ---------------------------------------------------------------------------
@@ -3138,8 +3323,8 @@ function openCodeModelManagerOptions(
3138
3323
  },
3139
3324
  },
3140
3325
  ...(apiKey && {
3141
- fetchDynamicModels: () =>
3142
- fetchOpenAICompatibleModels<Api>({
3326
+ fetchDynamicModels: async () => {
3327
+ const discovered = await fetchOpenAICompatibleModels<Api>({
3143
3328
  api: "openai-completions",
3144
3329
  provider: providerId,
3145
3330
  baseUrl: discoveryBaseUrl,
@@ -3197,7 +3382,12 @@ function openCodeModelManagerOptions(
3197
3382
  };
3198
3383
  },
3199
3384
  fetch: config?.fetch,
3200
- }),
3385
+ });
3386
+ if (!discovered) return null;
3387
+ const seeds = seedModels(providerId).map(seed => ({ ...seed, baseUrl: basePath }));
3388
+ const seedIds = new Set(seeds.map(seed => seed.id));
3389
+ return [...discovered.filter(model => !seedIds.has(model.id)), ...seeds];
3390
+ },
3201
3391
  }),
3202
3392
  };
3203
3393
  }
@@ -3290,8 +3480,9 @@ export interface OpenRouterModelManagerConfig {
3290
3480
  * OpenRouter's Decisions API lives at `/api/alpha`, a sibling of the `/api/v1`
3291
3481
  * chat root; derive it so a custom gateway base URL keeps both aligned.
3292
3482
  */
3293
- function openrouterDecisionsBaseUrl(chatBaseUrl: string): string {
3294
- return chatBaseUrl.endsWith("/v1") ? `${chatBaseUrl.slice(0, -"/v1".length)}/alpha` : `${chatBaseUrl}/alpha`;
3483
+ export function openrouterDecisionsBaseUrl(chatBaseUrl: string): string {
3484
+ const base = chatBaseUrl.replace(/\/+$/g, "");
3485
+ return base.endsWith("/v1") ? `${base.slice(0, -"/v1".length)}/alpha` : `${base}/alpha`;
3295
3486
  }
3296
3487
 
3297
3488
  function mapOpenRouterThinking(entry: OpenAICompatibleModelRecord): ThinkingConfig | undefined {
@@ -4264,6 +4455,7 @@ export function syntheticModelManagerOptions(
4264
4455
  return {
4265
4456
  providerId: "synthetic",
4266
4457
  dynamicModelsAuthoritative: true,
4458
+ dynamicReasoningAuthoritative: true,
4267
4459
  ...(apiKey && {
4268
4460
  fetchDynamicModels: () =>
4269
4461
  fetchOpenAICompatibleModels({
@@ -6187,7 +6379,7 @@ function parseCopilotTokenPriceTier(value: unknown): CopilotTokenPriceTier | und
6187
6379
  return undefined;
6188
6380
  }
6189
6381
  return {
6190
- contextMax: toNumber(value.context_max),
6382
+ contextMax: toNumber(value.max_prompt_tokens) ?? toNumber(value.context_max),
6191
6383
  inputPrice: toNumber(value.input_price),
6192
6384
  outputPrice: toNumber(value.output_price),
6193
6385
  cachePrice: toNumber(value.cache_price),
@@ -6303,6 +6495,9 @@ export function githubCopilotModelManagerOptions(config?: GithubCopilotModelMana
6303
6495
  return {
6304
6496
  providerId: "github-copilot",
6305
6497
  cacheProviderId: resolveModelCacheProviderId("github-copilot", { apiKey: rawApiKey, baseUrl }),
6498
+ // Copilot discovery pre-applies the correct image fallback for omitted
6499
+ // `supports.vision`; the live row's modality is authoritative.
6500
+ dynamicInputAuthoritative: true,
6306
6501
  dropCachedModelIdsOnStaticMismatch: COPILOT_CACHE_INVALIDATED_MODEL_IDS,
6307
6502
  // COPILOT_API_HEADERS are compile-time wire identity constants, not
6308
6503
  // credentials. The cache omits all request headers for
@@ -6373,12 +6568,12 @@ export function githubCopilotModelManagerOptions(config?: GithubCopilotModelMana
6373
6568
  ? ["text"]
6374
6569
  : (reference?.input ?? defaults.input);
6375
6570
  // With COPILOT_API_HEADERS the served window is the long-context
6376
- // ceiling; the default tier ends at token_prices.default.context_max
6377
- // prompt tokens. Cap the base entry to the default tier — the long
6378
- // tier is the opt-in `-1m` sibling below. On tiered rows
6379
- // max_prompt_tokens is the default lane's prompt budget, and the
6380
- // billed default ceiling can overlap the long lane (#13912), so the
6381
- // tighter of the two bounds the base entry.
6571
+ // ceiling; the default tier reports its prompt boundary in
6572
+ // token_prices.default.max_prompt_tokens (or legacy context_max).
6573
+ // Cap the base entry to the default tier — the long tier is
6574
+ // the opt-in `-1m` sibling below. On tiered legacy rows the
6575
+ // model-wide max_prompt_tokens may be tighter than the billed
6576
+ // default ceiling (#13912), so use the smaller bound.
6382
6577
  const tokenPrices = extractCopilotTokenPrices(entry);
6383
6578
  const billedDefaultMax = tokenPrices.defaultTier?.contextMax;
6384
6579
  const tieredPromptBudget =
@@ -7132,23 +7327,12 @@ const MODELS_DEV_PROVIDER_DESCRIPTORS_CODING_PLANS: readonly ModelsDevProviderDe
7132
7327
  allowsSyntheticReasoningContentForToolCalls: false,
7133
7328
  },
7134
7329
  }),
7135
- // --- MiniMax Coding Plan ---
7136
- openAiCompletionsDescriptor("minimax-coding-plan", "minimax-code", "https://api.minimax.io/v1", {
7137
- compat: {
7138
- supportsStore: false,
7139
- supportsDeveloperRole: false,
7140
- supportsReasoningEffort: false,
7141
- reasoningContentField: "reasoning_content",
7142
- },
7143
- }),
7144
- openAiCompletionsDescriptor("minimax-cn-coding-plan", "minimax-code-cn", "https://api.minimaxi.com/v1", {
7145
- compat: {
7146
- supportsStore: false,
7147
- supportsDeveloperRole: false,
7148
- supportsReasoningEffort: false,
7149
- reasoningContentField: "reasoning_content",
7150
- },
7151
- }),
7330
+ // --- MiniMax Token Plan ---
7331
+ // MiniMax documents its Anthropic-compatible API as the recommended
7332
+ // protocol: signed thinking blocks, `output_config.effort` depth control,
7333
+ // and prompt-cache usage reporting.
7334
+ anthropicMessagesDescriptor("minimax-coding-plan", "minimax-code", "https://api.minimax.io/anthropic"),
7335
+ anthropicMessagesDescriptor("minimax-cn-coding-plan", "minimax-code-cn", "https://api.minimaxi.com/anthropic"),
7152
7336
  // --- Alibaba Coding Plan ---
7153
7337
  openAiCompletionsDescriptor(
7154
7338
  "alibaba-coding-plan",
@@ -65,6 +65,8 @@ export declare function devinModelManagerOptions(config?: DevinModelManagerConfi
65
65
  export declare function factoryDroidModelManagerOptions(config?: FactoryDroidModelDiscoveryOptions): ModelManagerOptions<"factory-droid-agent">;
66
66
  export declare function localModelManagerOptions(): ModelManagerOptions<"local-inference">;
67
67
  export declare function webModelManagerOptions(): ModelManagerOptions<"web-search">;
68
+ /** Cloudflare Workers AI judges: the authored Clef rows; the account id resolves per request. */
69
+ export declare function cloudflareWorkersAiModelManagerOptions(): ModelManagerOptions<"workers-ai-systemone">;
68
70
  /** Credentials and endpoint overrides for the TypeSafe catalog manager. */
69
71
  export interface TypeSafeModelManagerConfig {
70
72
  apiKey?: string;