@bitkyc08/opencodex 2.15.1-preview.20260814 → 2.16.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.
- package/gui/dist/assets/{index-1U3HI8uT.js → index-CZwbOse7.js} +1 -1
- package/gui/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/adapters/base.ts +2 -0
- package/src/adapters/cursor/effort-map.ts +4 -5
- package/src/adapters/cursor/request-builder.ts +1 -1
- package/src/adapters/google-antigravity-replay.ts +9 -1
- package/src/adapters/kiro-thinking.ts +8 -0
- package/src/adapters/kiro.ts +45 -42
- package/src/adapters/openai-chat.ts +5 -2
- package/src/adapters/openai-responses.ts +5 -1
- package/src/cli/dispatch.ts +6 -3
- package/src/cli/index.ts +1 -0
- package/src/generated/compatibility-version.json +56 -32
- package/src/generated/model-metadata.ts +1 -1
- package/src/integrations/config-io.ts +119 -1
- package/src/integrations/omp-yaml-source.ts +6 -1
- package/src/integrations/serialize.ts +80 -1
- package/src/integrations/state.ts +37 -6
- package/src/integrations/writer.ts +11 -3
- package/src/lab/automation/orchestrator.ts +19 -0
- package/src/lib/lab-activation.ts +161 -0
- package/src/lib/lab-passive-linker-registration.ts +26 -0
- package/src/lib/optional-shutdown-hooks.ts +57 -0
- package/src/lib/translator-budget.ts +34 -0
- package/src/oauth/index.ts +3 -0
- package/src/providers/antigravity-models.ts +156 -36
- package/src/providers/model-rename-migration.ts +54 -1
- package/src/providers/registry.ts +1 -1
- package/src/routing/compatibility/assemble.ts +21 -107
- package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
- package/src/routing/compatibility/provider-slot.ts +56 -0
- package/src/server/index.ts +8 -17
- package/src/server/lifecycle.ts +5 -3
- package/src/server/management/routing-profile-routes.ts +9 -1
- package/src/server/management-api.ts +37 -6
- package/src/server/passive-route-linker.ts +66 -0
- package/src/server/responses/core.ts +20 -20
- package/src/types.ts +10 -0
- package/src/usage/expected-prices.ts +13 -0
|
@@ -71,6 +71,40 @@ export interface TranslatorBudget {
|
|
|
71
71
|
|
|
72
72
|
const retainedEventOwnership = new WeakMap<object, { budget: TranslatorBudget; bytes: number }>();
|
|
73
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Charge one event appended to an incrementally materialized adapter-event batch.
|
|
76
|
+
* The newest event owns the closing array bracket; moving that byte from the old
|
|
77
|
+
* tail keeps in-order release accounting equal to the still-retained JSON array.
|
|
78
|
+
*/
|
|
79
|
+
export function retainTranslatedEvent<T extends object>(
|
|
80
|
+
event: T,
|
|
81
|
+
budget: TranslatorBudget,
|
|
82
|
+
previousTail?: object,
|
|
83
|
+
): void {
|
|
84
|
+
if (retainedEventOwnership.has(event)) {
|
|
85
|
+
throw new Error("translated event is already retained");
|
|
86
|
+
}
|
|
87
|
+
if (previousTail === event) {
|
|
88
|
+
throw new Error("incremental translated event tail must be a distinct object");
|
|
89
|
+
}
|
|
90
|
+
const previousOwnership = previousTail === undefined
|
|
91
|
+
? undefined
|
|
92
|
+
: retainedEventOwnership.get(previousTail);
|
|
93
|
+
if (
|
|
94
|
+
previousTail !== undefined
|
|
95
|
+
&& (!previousOwnership || previousOwnership.budget !== budget || previousOwnership.bytes < 2)
|
|
96
|
+
) {
|
|
97
|
+
throw new Error("incremental translated event tail is not retained by this budget");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const serializedBytes = Buffer.byteLength(JSON.stringify(event));
|
|
101
|
+
budget.chargeRetained(serializedBytes + (previousTail === undefined ? 2 : 1), {
|
|
102
|
+
kind: "retained_collectors",
|
|
103
|
+
});
|
|
104
|
+
if (previousOwnership) previousOwnership.bytes -= 1;
|
|
105
|
+
retainedEventOwnership.set(event, { budget, bytes: serializedBytes + 2 });
|
|
106
|
+
}
|
|
107
|
+
|
|
74
108
|
/**
|
|
75
109
|
* Charge a materialized adapter-event batch and attach its lease to the events themselves.
|
|
76
110
|
* A copied event array (for example terminal-guard collection) preserves the event objects, so
|
package/src/oauth/index.ts
CHANGED
|
@@ -879,6 +879,9 @@ function isLegacyCommandCodeStaticCatalog(provider: OcxProviderConfig): boolean
|
|
|
879
879
|
}
|
|
880
880
|
|
|
881
881
|
function isLegacyAntigravityStaticCatalog(provider: OcxProviderConfig): boolean {
|
|
882
|
+
// A fingerprint of the shape version 1 actually shipped, NOT of the current registry.
|
|
883
|
+
// These literals must stay frozen as the model list moves on: matching them is how we
|
|
884
|
+
// know the row is the untouched v1 seed rather than a user's own selection.
|
|
882
885
|
return provider.liveModels === false
|
|
883
886
|
&& provider.adapter === "google"
|
|
884
887
|
&& provider.baseUrl === "https://daily-cloudcode-pa.googleapis.com"
|
|
@@ -6,14 +6,43 @@ import { isValidModelDiscoveryModelId, MODEL_DISCOVERY_MAX_MODELS } from "./mode
|
|
|
6
6
|
// CLI resolves labels against. The ids below separate CCA wire ids, collapsed picker entries,
|
|
7
7
|
// and hidden compatibility aliases for saved selections. The CCA envelope's `model` field must
|
|
8
8
|
// receive the wire id (for example "Gemini 3.1 Pro (High)" => gemini-pro-agent), while the
|
|
9
|
-
// picker exposes collapsed base models only when CCA returns every known tier;
|
|
10
|
-
// returned wire
|
|
9
|
+
// picker exposes collapsed known base models only when CCA returns every known tier; unknown
|
|
10
|
+
// returned wire ids remain visible so they stay directly routable.
|
|
11
11
|
|
|
12
12
|
// ── Wire IDs (what CCA :fetchAvailableModels returns) ──
|
|
13
|
+
|
|
14
|
+
/** Current Antigravity Flash generation. */
|
|
15
|
+
const GEMINI_FLASH_CURRENT = "gemini-3.7-flash";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Retired Flash ids → the reasoning tier they used to encode.
|
|
19
|
+
*
|
|
20
|
+
* Google pulls the previous Flash model from CCA almost immediately when the next
|
|
21
|
+
* one ships, so a saved selection cannot keep pointing at it. A flat alias would
|
|
22
|
+
* strand the user's tier: `resolveAntigravityEffortWireModel` rule 1 treats any
|
|
23
|
+
* alias as "the suffix IS the effort" and deliberately sends no thinkingConfig, so
|
|
24
|
+
* `gemini-3.6-flash-high` would silently become an untiered 3.7 call. Carrying the
|
|
25
|
+
* level here preserves what the user actually chose.
|
|
26
|
+
*
|
|
27
|
+
* 3.7 exposes tiers through `thinkingLevel` on ONE wire id rather than through
|
|
28
|
+
* suffixed wire ids, which is why the mapping is id → level and not id → id.
|
|
29
|
+
*/
|
|
30
|
+
const RETIRED_FLASH_TIERS: Record<string, string> = {
|
|
31
|
+
// 3.6 generation.
|
|
32
|
+
"gemini-3.6-flash": "medium", // bare base carried a medium default
|
|
33
|
+
"gemini-3.6-flash-low": "low",
|
|
34
|
+
"gemini-3.6-flash-medium": "medium",
|
|
35
|
+
"gemini-3.6-flash-high": "high",
|
|
36
|
+
// 3.5 generation — these already pointed at 3.6 wire ids, which are now dead too.
|
|
37
|
+
"gemini-3.5-flash-extra-low": "low",
|
|
38
|
+
"gemini-3.5-flash-low": "medium",
|
|
39
|
+
"gemini-3.5-flash-mid": "medium",
|
|
40
|
+
"gemini-3.5-flash-high": "high",
|
|
41
|
+
"gemini-3-flash-agent": "high",
|
|
42
|
+
};
|
|
43
|
+
|
|
13
44
|
const ANTIGRAVITY_WIRE_MODELS = [
|
|
14
|
-
"gemini-3.
|
|
15
|
-
"gemini-3.6-flash-medium",
|
|
16
|
-
"gemini-3.6-flash-high",
|
|
45
|
+
"gemini-3.7-flash",
|
|
17
46
|
"gemini-3.1-pro-low",
|
|
18
47
|
"gemini-pro-agent",
|
|
19
48
|
"gemini-3.1-flash-image",
|
|
@@ -23,9 +52,6 @@ const ANTIGRAVITY_WIRE_MODELS = [
|
|
|
23
52
|
];
|
|
24
53
|
|
|
25
54
|
const ANTIGRAVITY_PICKER_MODEL_BY_WIRE_ID: Record<string, string> = {
|
|
26
|
-
"gemini-3.6-flash-low": "gemini-3.6-flash",
|
|
27
|
-
"gemini-3.6-flash-medium": "gemini-3.6-flash",
|
|
28
|
-
"gemini-3.6-flash-high": "gemini-3.6-flash",
|
|
29
55
|
"gemini-3.1-pro-low": "gemini-3.1-pro",
|
|
30
56
|
"gemini-pro-agent": "gemini-3.1-pro",
|
|
31
57
|
};
|
|
@@ -37,11 +63,46 @@ const ANTIGRAVITY_WIRE_IDS_BY_PICKER_MODEL: Record<string, string[]> = Object.en
|
|
|
37
63
|
return out;
|
|
38
64
|
}, {});
|
|
39
65
|
|
|
66
|
+
const ANTIGRAVITY_DISCOVERY_EFFORTS = ["low", "medium", "high"] as const;
|
|
67
|
+
|
|
68
|
+
function pickerModelIdForDiscoveredWireId(
|
|
69
|
+
wireId: string,
|
|
70
|
+
available: ReadonlyMap<string, Record<string, unknown>>,
|
|
71
|
+
): string {
|
|
72
|
+
const explicitPickerId = Object.hasOwn(ANTIGRAVITY_PICKER_MODEL_BY_WIRE_ID, wireId)
|
|
73
|
+
? ANTIGRAVITY_PICKER_MODEL_BY_WIRE_ID[wireId]
|
|
74
|
+
: undefined;
|
|
75
|
+
if (explicitPickerId) {
|
|
76
|
+
const requiredWireIds = Object.hasOwn(ANTIGRAVITY_WIRE_IDS_BY_PICKER_MODEL, explicitPickerId)
|
|
77
|
+
? ANTIGRAVITY_WIRE_IDS_BY_PICKER_MODEL[explicitPickerId] ?? []
|
|
78
|
+
: [];
|
|
79
|
+
if (requiredWireIds.every(id => available.has(id))) return explicitPickerId;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// CCA uses a single `-tiered` row for models whose effort levels ride on the
|
|
83
|
+
// request's thinkingLevel field. Keep this generic so new tiered models do not
|
|
84
|
+
// require another provider-specific ID mapping.
|
|
85
|
+
if (wireId.endsWith("-tiered")) {
|
|
86
|
+
const baseId = wireId.slice(0, -"-tiered".length);
|
|
87
|
+
if (isKnownAntigravityPickerModelId(baseId)) return baseId;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const effortMatch = /^(.*)-(low|medium|high)$/.exec(wireId);
|
|
91
|
+
if (effortMatch) {
|
|
92
|
+
const baseId = effortMatch[1]!;
|
|
93
|
+
if (isKnownAntigravityPickerModelId(baseId)
|
|
94
|
+
&& ANTIGRAVITY_DISCOVERY_EFFORTS.every(effort => available.has(`${baseId}-${effort}`))) {
|
|
95
|
+
return baseId;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return wireId;
|
|
99
|
+
}
|
|
100
|
+
|
|
40
101
|
// ── Effort ladders per collapsed base model ──
|
|
41
102
|
// Gemini models: effort → wire model suffix (official agy UI pattern).
|
|
42
103
|
// Claude Opus: effort → thinkingConfig.thinkingLevel (CLIProxyAPI proven pattern).
|
|
43
104
|
export const ANTIGRAVITY_MODEL_EFFORTS: Record<string, string[]> = {
|
|
44
|
-
"gemini-3.
|
|
105
|
+
"gemini-3.7-flash": ["low", "medium", "high"],
|
|
45
106
|
"gemini-3.1-pro": ["low", "high"],
|
|
46
107
|
"claude-sonnet-4-6": ["low", "medium", "high", "max"],
|
|
47
108
|
"claude-opus-4-6-thinking": ["low", "medium", "high", "max"],
|
|
@@ -49,11 +110,6 @@ export const ANTIGRAVITY_MODEL_EFFORTS: Record<string, string[]> = {
|
|
|
49
110
|
|
|
50
111
|
// ── Effort → wire model map for Gemini base models ──
|
|
51
112
|
const ANTIGRAVITY_EFFORT_WIRE_MAP: Record<string, Record<string, string>> = {
|
|
52
|
-
"gemini-3.6-flash": {
|
|
53
|
-
low: "gemini-3.6-flash-low",
|
|
54
|
-
medium: "gemini-3.6-flash-medium",
|
|
55
|
-
high: "gemini-3.6-flash-high",
|
|
56
|
-
},
|
|
57
113
|
"gemini-3.1-pro": {
|
|
58
114
|
low: "gemini-3.1-pro-low",
|
|
59
115
|
high: "gemini-pro-agent",
|
|
@@ -62,11 +118,20 @@ const ANTIGRAVITY_EFFORT_WIRE_MAP: Record<string, Record<string, string>> = {
|
|
|
62
118
|
|
|
63
119
|
// ── Default effort per Gemini base model ──
|
|
64
120
|
const ANTIGRAVITY_DEFAULT_EFFORT: Record<string, string> = {
|
|
65
|
-
"gemini-3.6-flash": "medium",
|
|
66
121
|
"gemini-3.1-pro": "high",
|
|
67
122
|
};
|
|
68
123
|
|
|
69
|
-
|
|
124
|
+
/**
|
|
125
|
+
* Gemini base models whose efforts ride on `thinkingLevel` against a single wire id
|
|
126
|
+
* instead of suffixed wire ids, with the level applied when the caller names none.
|
|
127
|
+
*/
|
|
128
|
+
const ANTIGRAVITY_THINKING_LEVEL_MODELS: Record<string, string> = {
|
|
129
|
+
"gemini-3.7-flash": "medium",
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// `minimal` is deliberately absent: Google documents it as unsupported for the current
|
|
133
|
+
// Flash generation, where it is an error rather than a quieter tier.
|
|
134
|
+
const ANTIGRAVITY_THINKING_LEVELS = new Set(["low", "medium", "high"]);
|
|
70
135
|
|
|
71
136
|
function resolveAntigravityThinkingLevel(effort: string): string | undefined {
|
|
72
137
|
if (effort === "xhigh" || effort === "max" || effort === "ultra") return "high";
|
|
@@ -83,16 +148,17 @@ const ANTIGRAVITY_VISIBLE_MODEL_ALIASES: Record<string, string> = {
|
|
|
83
148
|
// Wire suffix IDs are identity aliases — they resolve to themselves so saved configs
|
|
84
149
|
// with explicit suffixes (e.g. gemini-3.6-flash-low) continue to work.
|
|
85
150
|
const ANTIGRAVITY_COMPATIBILITY_MODEL_ALIASES: Record<string, string> = {
|
|
86
|
-
"gemini-3.6-flash-low": "gemini-3.6-flash-low",
|
|
87
|
-
"gemini-3.6-flash-medium": "gemini-3.6-flash-medium",
|
|
88
|
-
"gemini-3.6-flash-high": "gemini-3.6-flash-high",
|
|
89
151
|
"gemini-3.1-pro-low": "gemini-3.1-pro-low",
|
|
90
152
|
"gemini-pro-agent": "gemini-pro-agent",
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
153
|
+
// ── Retired Flash generations ──
|
|
154
|
+
// Google takes the previous Antigravity Flash model offline almost immediately
|
|
155
|
+
// once its successor ships, so 3.6 (and the 3.5 ids that used to land on it)
|
|
156
|
+
// route to 3.7. These stay in the alias map — not only in the tier map below —
|
|
157
|
+
// because `parseAntigravityAvailableModels` uses THIS map to keep a stale CCA
|
|
158
|
+
// payload from republishing a dead wire id as a picker row.
|
|
159
|
+
...Object.fromEntries(
|
|
160
|
+
Object.keys(RETIRED_FLASH_TIERS).map(retired => [retired, GEMINI_FLASH_CURRENT]),
|
|
161
|
+
),
|
|
96
162
|
};
|
|
97
163
|
|
|
98
164
|
export const ANTIGRAVITY_MODEL_ALIASES: Record<string, string> = {
|
|
@@ -102,7 +168,7 @@ export const ANTIGRAVITY_MODEL_ALIASES: Record<string, string> = {
|
|
|
102
168
|
|
|
103
169
|
// Picker-visible: collapsed base models only.
|
|
104
170
|
export const ANTIGRAVITY_MODELS = [
|
|
105
|
-
|
|
171
|
+
GEMINI_FLASH_CURRENT,
|
|
106
172
|
"gemini-3.1-pro",
|
|
107
173
|
"gemini-3.1-flash-image",
|
|
108
174
|
"claude-sonnet-4-6",
|
|
@@ -110,11 +176,13 @@ export const ANTIGRAVITY_MODELS = [
|
|
|
110
176
|
"gpt-oss-120b-medium",
|
|
111
177
|
];
|
|
112
178
|
|
|
179
|
+
function isKnownAntigravityPickerModelId(value: string): boolean {
|
|
180
|
+
return isValidModelDiscoveryModelId(value) && ANTIGRAVITY_MODELS.includes(value);
|
|
181
|
+
}
|
|
182
|
+
|
|
113
183
|
// Context windows from the upstream `:fetchAvailableModels` maxTokens per model.
|
|
114
184
|
const ANTIGRAVITY_WIRE_MODEL_CONTEXT_WINDOWS: Record<string, number> = {
|
|
115
|
-
"gemini-3.
|
|
116
|
-
"gemini-3.6-flash-medium": 1_048_576,
|
|
117
|
-
"gemini-3.6-flash-high": 1_048_576,
|
|
185
|
+
"gemini-3.7-flash": 1_048_576,
|
|
118
186
|
"gemini-3.1-pro-low": 1_048_576,
|
|
119
187
|
"gemini-pro-agent": 1_048_576,
|
|
120
188
|
"gemini-3.1-flash-image": 1_048_576,
|
|
@@ -125,7 +193,7 @@ const ANTIGRAVITY_WIRE_MODEL_CONTEXT_WINDOWS: Record<string, number> = {
|
|
|
125
193
|
|
|
126
194
|
export const ANTIGRAVITY_MODEL_CONTEXT_WINDOWS: Record<string, number> = {
|
|
127
195
|
// Collapsed base IDs — explicit entries for the picker.
|
|
128
|
-
"gemini-3.
|
|
196
|
+
"gemini-3.7-flash": 1_048_576,
|
|
129
197
|
"gemini-3.1-pro": 1_048_576,
|
|
130
198
|
// Wire IDs and aliases via derivation.
|
|
131
199
|
...ANTIGRAVITY_WIRE_MODEL_CONTEXT_WINDOWS,
|
|
@@ -138,7 +206,11 @@ export const ANTIGRAVITY_MODEL_CONTEXT_WINDOWS: Record<string, number> = {
|
|
|
138
206
|
};
|
|
139
207
|
|
|
140
208
|
export const ANTIGRAVITY_MODEL_INPUT_MODALITIES: Record<string, string[]> = {
|
|
141
|
-
|
|
209
|
+
// Google documents 3.7 Flash as also accepting video, audio and PDF, but this proxy
|
|
210
|
+
// carries only text and image parts (`OcxImageContent`, src/types.ts) and the Codex
|
|
211
|
+
// catalog normalizes `input_modalities` against a closed enum. Advertising a modality
|
|
212
|
+
// the wire cannot carry would be a promise we break at request time.
|
|
213
|
+
"gemini-3.7-flash": ["text", "image"],
|
|
142
214
|
"gemini-3.1-pro": ["text", "image"],
|
|
143
215
|
"gemini-3.1-flash-image": ["text", "image"],
|
|
144
216
|
"claude-sonnet-4-6": ["text", "image"],
|
|
@@ -189,6 +261,7 @@ export function parseAntigravityAvailableModels(
|
|
|
189
261
|
if (!Array.isArray(modelIds)) return null;
|
|
190
262
|
for (const id of modelIds) {
|
|
191
263
|
if (!isValidModelDiscoveryModelId(id)
|
|
264
|
+
|| !Object.hasOwn(models, id)
|
|
192
265
|
|| !antigravityRecord(models[id])
|
|
193
266
|
|| ids.length >= limit) return null;
|
|
194
267
|
ids.push(id);
|
|
@@ -202,6 +275,19 @@ export function parseAntigravityAvailableModels(
|
|
|
202
275
|
if (ids.length >= limit) return null;
|
|
203
276
|
ids.push("gemini-3.1-flash-image");
|
|
204
277
|
}
|
|
278
|
+
// Newer CCA responses identify tiered Flash models through this index instead of
|
|
279
|
+
// adding their synthetic wire ids to agentModelSorts.
|
|
280
|
+
const tieredModelIds = antigravityRecord(body.tieredModelIds);
|
|
281
|
+
const flashTieredIds = tieredModelIds?.flash;
|
|
282
|
+
if (Array.isArray(flashTieredIds)) {
|
|
283
|
+
for (const id of flashTieredIds) {
|
|
284
|
+
if (!isValidModelDiscoveryModelId(id)
|
|
285
|
+
|| !Object.hasOwn(models, id)
|
|
286
|
+
|| !antigravityRecord(models[id])
|
|
287
|
+
|| ids.length >= limit) return null;
|
|
288
|
+
ids.push(id);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
205
291
|
|
|
206
292
|
const available = new Map<string, Record<string, unknown>>();
|
|
207
293
|
for (const wireId of ids) {
|
|
@@ -209,17 +295,17 @@ export function parseAntigravityAvailableModels(
|
|
|
209
295
|
if (!info || available.has(wireId)) continue;
|
|
210
296
|
// Legacy compatibility aliases are deliberately routed to newer wire ids for saved
|
|
211
297
|
// selections. They are not safe as independently discovered picker rows.
|
|
212
|
-
|
|
298
|
+
const alias = Object.hasOwn(ANTIGRAVITY_MODEL_ALIASES, wireId)
|
|
299
|
+
? ANTIGRAVITY_MODEL_ALIASES[wireId]
|
|
300
|
+
: undefined;
|
|
301
|
+
if (alias && alias !== wireId) continue;
|
|
213
302
|
available.set(wireId, info);
|
|
214
303
|
}
|
|
215
304
|
|
|
216
305
|
const out: AntigravityAvailableModel[] = [];
|
|
217
306
|
const seen = new Set<string>();
|
|
218
307
|
for (const [wireId, info] of available) {
|
|
219
|
-
const
|
|
220
|
-
const completePickerSet = pickerId !== undefined
|
|
221
|
-
&& ANTIGRAVITY_WIRE_IDS_BY_PICKER_MODEL[pickerId]!.every(id => available.has(id));
|
|
222
|
-
const id = completePickerSet ? pickerId! : wireId;
|
|
308
|
+
const id = pickerModelIdForDiscoveredWireId(wireId, available);
|
|
223
309
|
if (seen.has(id)) continue;
|
|
224
310
|
seen.add(id);
|
|
225
311
|
out.push({
|
|
@@ -232,7 +318,9 @@ export function parseAntigravityAvailableModels(
|
|
|
232
318
|
}
|
|
233
319
|
|
|
234
320
|
export function resolveAntigravityWireModelId(modelId: string): string {
|
|
235
|
-
return ANTIGRAVITY_MODEL_ALIASES
|
|
321
|
+
return Object.hasOwn(ANTIGRAVITY_MODEL_ALIASES, modelId)
|
|
322
|
+
? ANTIGRAVITY_MODEL_ALIASES[modelId]
|
|
323
|
+
: modelId;
|
|
236
324
|
}
|
|
237
325
|
|
|
238
326
|
/**
|
|
@@ -244,6 +332,11 @@ export function isAntigravitySuffixModelId(modelId: string): boolean {
|
|
|
244
332
|
return !(ANTIGRAVITY_MODELS as string[]).includes(modelId);
|
|
245
333
|
}
|
|
246
334
|
|
|
335
|
+
/** The reasoning tier a retired Flash id used to encode, if it is one. */
|
|
336
|
+
export function retiredAntigravityFlashTier(modelId: string): string | undefined {
|
|
337
|
+
return Object.hasOwn(RETIRED_FLASH_TIERS, modelId) ? RETIRED_FLASH_TIERS[modelId] : undefined;
|
|
338
|
+
}
|
|
339
|
+
|
|
247
340
|
/**
|
|
248
341
|
* Resolve a picker-visible base model + optional reasoning effort to the CCA wire model ID.
|
|
249
342
|
*
|
|
@@ -258,11 +351,32 @@ export function resolveAntigravityEffortWireModel(
|
|
|
258
351
|
modelId: string,
|
|
259
352
|
effort?: string,
|
|
260
353
|
): { wireModelId: string; thinkingLevel?: string } {
|
|
354
|
+
// Rule 0: retired Flash id — Google has taken the wire id offline, so route to the
|
|
355
|
+
// current generation and carry the tier the retired id encoded. This runs BEFORE the
|
|
356
|
+
// suffix check because those ids are aliases, and rule 1 would drop the tier.
|
|
357
|
+
const retiredTier = retiredAntigravityFlashTier(modelId);
|
|
358
|
+
if (retiredTier) {
|
|
359
|
+
return {
|
|
360
|
+
wireModelId: GEMINI_FLASH_CURRENT,
|
|
361
|
+
thinkingLevel: effort ? resolveAntigravityThinkingLevel(effort) ?? retiredTier : retiredTier,
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
261
365
|
// Rule 1: suffix/compat alias — suffix IS the effort.
|
|
262
366
|
if (isAntigravitySuffixModelId(modelId)) {
|
|
263
367
|
return { wireModelId: resolveAntigravityWireModelId(modelId) };
|
|
264
368
|
}
|
|
265
369
|
|
|
370
|
+
// Rule 1b: single-wire-id Gemini model whose tiers ride on thinkingLevel. Without
|
|
371
|
+
// this the model falls to rule 5 and silently loses reasoning control entirely.
|
|
372
|
+
const defaultLevel = ANTIGRAVITY_THINKING_LEVEL_MODELS[modelId];
|
|
373
|
+
if (defaultLevel) {
|
|
374
|
+
return {
|
|
375
|
+
wireModelId: modelId,
|
|
376
|
+
thinkingLevel: effort ? resolveAntigravityThinkingLevel(effort) ?? defaultLevel : defaultLevel,
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
266
380
|
// Rule 2/3: mapped Gemini base model.
|
|
267
381
|
const effortMap = ANTIGRAVITY_EFFORT_WIRE_MAP[modelId];
|
|
268
382
|
if (effortMap) {
|
|
@@ -301,6 +415,12 @@ const ANTIGRAVITY_USAGE_BASE_BY_ID: Record<string, string> = (() => {
|
|
|
301
415
|
// If alias is itself a base/wire already mapped, keep that mapping.
|
|
302
416
|
else if (rev[wire]) rev[alias] = rev[wire]!;
|
|
303
417
|
}
|
|
418
|
+
// Retired ids keep their OWN identity for usage aggregation, overriding the alias
|
|
419
|
+
// pass above. Routing sends new 3.6 calls to 3.7, but a usage row written months ago
|
|
420
|
+
// records a model the user actually called: relabelling it would move historical spend
|
|
421
|
+
// onto a model that did not exist then, and away from the 3.6 price row that still
|
|
422
|
+
// prices it correctly. Retirement changes what we CALL, not what we RECORD.
|
|
423
|
+
for (const retired of Object.keys(RETIRED_FLASH_TIERS)) rev[retired] = retired;
|
|
304
424
|
// Visible aliases that only appear in ANTIGRAVITY_VISIBLE_MODEL_ALIASES are already
|
|
305
425
|
// included via ANTIGRAVITY_MODEL_ALIASES. Identity bases without effort maps remain.
|
|
306
426
|
return rev;
|
|
@@ -26,6 +26,22 @@ export interface ModelRename {
|
|
|
26
26
|
to: string;
|
|
27
27
|
/** Why the vendor retired it, for the startup warning and future readers. */
|
|
28
28
|
reason: string;
|
|
29
|
+
/**
|
|
30
|
+
* Drop the retired key from `modelReasoningEffortMap` instead of renaming it.
|
|
31
|
+
*
|
|
32
|
+
* Renaming preserves the VALUE, which is right for records whose values describe the
|
|
33
|
+
* model — a context window or an effort ladder survives a rename — and wrong for the
|
|
34
|
+
* one record whose values are themselves wire ids. An effort map saved as
|
|
35
|
+
* `high -> gemini-3.6-flash-high` would keep naming a dead wire id under the new key,
|
|
36
|
+
* and the adapter maps effort BEFORE resolving CCA routing, so that value arrives as
|
|
37
|
+
* an unrecognised effort and silently degrades to the default tier.
|
|
38
|
+
*
|
|
39
|
+
* Only that one record is dropped. Emptying the others would be worse than the bug:
|
|
40
|
+
* catalog enrichment treats an existing `{}` as "already populated" and will not
|
|
41
|
+
* restore the registry's records, so the migrated user would keep routing correctly
|
|
42
|
+
* but lose the reasoning picker entirely.
|
|
43
|
+
*/
|
|
44
|
+
dropReasoningEffortMap?: boolean;
|
|
29
45
|
}
|
|
30
46
|
|
|
31
47
|
/**
|
|
@@ -46,6 +62,22 @@ export const MODEL_RENAMES: readonly ModelRename[] = [
|
|
|
46
62
|
to: "qwen3.8-max",
|
|
47
63
|
reason: "Alibaba shipped Qwen3.8-Max as stable and documents the preview endpoint as liable to be taken offline once preview concludes",
|
|
48
64
|
},
|
|
65
|
+
// Antigravity Flash generations. Google takes the previous Flash model off Cloud Code
|
|
66
|
+
// Assist almost immediately when the next ships, so a saved 3.6 (or older 3.5) id is a
|
|
67
|
+
// dead selection rather than a merely outdated one. Routing already redirects these ids
|
|
68
|
+
// at request time; this migration repairs the saved config so the picker, the allowlist
|
|
69
|
+
// and the capability maps stop naming a model the backend no longer serves.
|
|
70
|
+
...(["gemini-3.6-flash", "gemini-3.6-flash-low", "gemini-3.6-flash-medium", "gemini-3.6-flash-high",
|
|
71
|
+
"gemini-3.5-flash-extra-low", "gemini-3.5-flash-low", "gemini-3.5-flash-mid", "gemini-3.5-flash-high",
|
|
72
|
+
"gemini-3-flash-agent"] as const).map(from => ({
|
|
73
|
+
provider: "google-antigravity",
|
|
74
|
+
from,
|
|
75
|
+
to: "gemini-3.7-flash",
|
|
76
|
+
reason: "Google retires the previous Antigravity Flash generation from Cloud Code Assist when its successor ships, so the saved id no longer resolves to a live model",
|
|
77
|
+
// The retired Flash tiers were wire ids, so any saved per-model record keyed by one
|
|
78
|
+
// may also hold one as a value. 3.7 expresses tiers as thinkingLevel names instead.
|
|
79
|
+
dropReasoningEffortMap: true,
|
|
80
|
+
})),
|
|
49
81
|
];
|
|
50
82
|
|
|
51
83
|
/** Provider fields that key metadata by model id. */
|
|
@@ -61,6 +93,12 @@ const MODEL_KEYED_RECORDS = [
|
|
|
61
93
|
/** Provider fields that are flat lists of model ids. */
|
|
62
94
|
const MODEL_ID_LISTS = [
|
|
63
95
|
"models",
|
|
96
|
+
// A retired id left here is worse than a stale label: `filterCatalogModels` treats
|
|
97
|
+
// `selectedModels` as an exact-match allowlist, so a user who allowlisted only the
|
|
98
|
+
// retired model gets NO replacement row at all — the model silently vanishes from
|
|
99
|
+
// their catalog instead of being renamed. OAuth reconciliation does not cover this
|
|
100
|
+
// field, so the rename has to.
|
|
101
|
+
"selectedModels",
|
|
64
102
|
"noVisionModels",
|
|
65
103
|
"noReasoningModels",
|
|
66
104
|
"noTemperatureModels",
|
|
@@ -102,6 +140,19 @@ function renameInRecord(value: unknown, from: string, to: string): Record<string
|
|
|
102
140
|
return next;
|
|
103
141
|
}
|
|
104
142
|
|
|
143
|
+
/** Drop the retired key entirely, leaving any entry already saved under the new id. */
|
|
144
|
+
function dropFromRecord(value: unknown, from: string): Record<string, unknown> | null {
|
|
145
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
146
|
+
const record = value as Record<string, unknown>;
|
|
147
|
+
if (!(from in record)) return null;
|
|
148
|
+
const next: Record<string, unknown> = {};
|
|
149
|
+
for (const [key, entry] of Object.entries(record)) {
|
|
150
|
+
if (key === from) continue;
|
|
151
|
+
next[key] = entry;
|
|
152
|
+
}
|
|
153
|
+
return next;
|
|
154
|
+
}
|
|
155
|
+
|
|
105
156
|
/**
|
|
106
157
|
* `provider/model` rows in the top-level `disabledModels` list.
|
|
107
158
|
*
|
|
@@ -179,7 +230,9 @@ export function projectModelRenames(
|
|
|
179
230
|
touched = true;
|
|
180
231
|
}
|
|
181
232
|
for (const field of MODEL_KEYED_RECORDS) {
|
|
182
|
-
const next =
|
|
233
|
+
const next = rename.dropReasoningEffortMap && field === "modelReasoningEffortMap"
|
|
234
|
+
? dropFromRecord(row[field], rename.from)
|
|
235
|
+
: renameInRecord(row[field], rename.from, rename.to);
|
|
183
236
|
if (!next) continue;
|
|
184
237
|
row[field] = next;
|
|
185
238
|
touched = true;
|
|
@@ -1451,7 +1451,7 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
1451
1451
|
// 2026-07-10: defaultModel is frozen pending Vertex-specific Tier-2 evidence; Gemini API
|
|
1452
1452
|
// evidence from ai.google.dev does not establish Vertex publisher availability.
|
|
1453
1453
|
{ id: "google-vertex", label: "Google Vertex AI", adapter: "google", baseUrl: "https://aiplatform.googleapis.com", authKind: "key", dashboardUrl: "https://console.cloud.google.com/vertex-ai", defaultModel: "gemini-3-pro", googleMode: "vertex", jawcodeBundle: "google", extraMetadataAliases: ["gemini-vertex"] },
|
|
1454
|
-
{ id: "google-antigravity", label: "Google Antigravity", adapter: "google", baseUrl: "https://daily-cloudcode-pa.googleapis.com", authKind: "oauth", dashboardUrl: "https://antigravity.google", models: ANTIGRAVITY_MODELS, liveModels: true, defaultModel: "gemini-3.
|
|
1454
|
+
{ id: "google-antigravity", label: "Google Antigravity", adapter: "google", baseUrl: "https://daily-cloudcode-pa.googleapis.com", authKind: "oauth", dashboardUrl: "https://antigravity.google", models: ANTIGRAVITY_MODELS, liveModels: true, defaultModel: "gemini-3.7-flash", modelContextWindows: ANTIGRAVITY_MODEL_CONTEXT_WINDOWS, modelInputModalities: ANTIGRAVITY_MODEL_INPUT_MODALITIES, modelReasoningEfforts: ANTIGRAVITY_MODEL_EFFORTS, googleMode: "cloud-code-assist", jawcodeBundle: "google", extraMetadataAliases: ["antigravity", "gemini-antigravity"] },
|
|
1455
1455
|
{ id: "azure-openai", label: "Azure OpenAI", adapter: "azure-openai", baseUrl: "https://{resource}.openai.azure.com/openai", authKind: "key", featured: true, dashboardUrl: "https://portal.azure.com" },
|
|
1456
1456
|
{ id: "ollama", label: "Ollama (local)", adapter: "openai-chat", baseUrl: "http://localhost:11434/v1", authKind: "local", allowPrivateNetworkByDefault: true, allowBaseUrlOverride: true, featured: true, note: "Local — key usually blank" },
|
|
1457
1457
|
{ id: "vllm", label: "vLLM (local)", adapter: "openai-chat", baseUrl: "http://localhost:8000/v1", authKind: "local", allowPrivateNetworkByDefault: true, allowBaseUrlOverride: true, featured: true, note: "Local — key usually blank" },
|
|
@@ -5,73 +5,22 @@ import type { PolicyCandidateEvidence } from "../evaluator";
|
|
|
5
5
|
import { policyCandidateHealthEvidence } from "../health";
|
|
6
6
|
import type { NormalizedRoutingProfile } from "../profile";
|
|
7
7
|
import { quotaEvidenceForCandidate } from "../quota";
|
|
8
|
-
import {
|
|
9
|
-
compatibilitySuiteKey,
|
|
10
|
-
loadCompatibilityCatalogSnapshot,
|
|
11
|
-
type CompatibilityCatalogSnapshot,
|
|
12
|
-
} from "./catalog";
|
|
13
|
-
import { findVerdictForSuite, loadCompatibilityEvidenceSnapshot } from "./reader";
|
|
14
|
-
import {
|
|
15
|
-
resolvePolicyCompatibilitySubjects,
|
|
16
|
-
type ResolvedPolicyCompatibilitySubjects,
|
|
17
|
-
} from "./subject";
|
|
18
|
-
import type { CandidateCompatibilityEvidence } from "./types";
|
|
8
|
+
import { resolveCompatibilityEvidenceProvider, type CoreEvidenceOptions } from "./provider-slot";
|
|
19
9
|
|
|
20
10
|
export type RoutedProviderResolver = (
|
|
21
11
|
providerName: string,
|
|
22
12
|
provider: OcxProviderConfig,
|
|
23
13
|
) => OcxProviderConfig;
|
|
24
14
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
resolved: ResolvedPolicyCompatibilitySubjects | undefined,
|
|
35
|
-
snapshot: ReturnType<typeof loadCompatibilityEvidenceSnapshot>,
|
|
36
|
-
catalog: CompatibilityCatalogSnapshot,
|
|
37
|
-
profile: NonNullable<NormalizedRoutingProfile["compatibility"]>,
|
|
38
|
-
): CandidateCompatibilityEvidence {
|
|
39
|
-
const subjectIds = resolved?.subjectIds ?? {};
|
|
40
|
-
const suites: CandidateCompatibilityEvidence["suites"] = [];
|
|
41
|
-
|
|
42
|
-
for (const requirement of profile.requiredSuites) {
|
|
43
|
-
const subjectId = subjectIds[requirement.evidenceLayer];
|
|
44
|
-
if (!subjectId) continue;
|
|
45
|
-
const metadata = catalog.get(compatibilitySuiteKey(requirement.evidenceLayer, requirement.suiteId));
|
|
46
|
-
if (!metadata) continue;
|
|
47
|
-
const row = findVerdictForSuite(
|
|
48
|
-
snapshot,
|
|
49
|
-
subjectId,
|
|
50
|
-
requirement.evidenceLayer,
|
|
51
|
-
requirement.suiteId,
|
|
52
|
-
metadata.suiteVersion,
|
|
53
|
-
metadata.suiteManifestDigest,
|
|
54
|
-
);
|
|
55
|
-
if (!row) continue;
|
|
56
|
-
suites.push({
|
|
57
|
-
subjectId,
|
|
58
|
-
suiteId: row.suiteId,
|
|
59
|
-
evidenceLayer: requirement.evidenceLayer,
|
|
60
|
-
suiteVersion: row.suiteVersion,
|
|
61
|
-
suiteManifestDigest: row.suiteManifestDigest,
|
|
62
|
-
verdict: row.verdict,
|
|
63
|
-
asOf: row.asOf,
|
|
64
|
-
maxAgeMs: metadata.maxAgeMs,
|
|
65
|
-
notes: row.notes,
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return {
|
|
70
|
-
subjectIds: { ...subjectIds },
|
|
71
|
-
projectionAvailable: snapshot.projectionAvailable,
|
|
72
|
-
suites,
|
|
73
|
-
};
|
|
74
|
-
}
|
|
15
|
+
/**
|
|
16
|
+
* Options the core assembler needs. Provider-specific test seams (subject resolution,
|
|
17
|
+
* catalog and projection loading) belong to the compatibility provider, not here -- keeping
|
|
18
|
+
* them out is what stops the core assembler from naming Lab-backed contracts.
|
|
19
|
+
*
|
|
20
|
+
* The provider reads its own seams off the same object, so callers may still pass a
|
|
21
|
+
* `LabCompatibilityProviderOptions`; that type extends this one.
|
|
22
|
+
*/
|
|
23
|
+
export type AssemblePolicyEvidenceOptions = CoreEvidenceOptions;
|
|
75
24
|
|
|
76
25
|
/**
|
|
77
26
|
* Assemble production policy candidate evidence including compatibility snapshots.
|
|
@@ -89,55 +38,20 @@ export function assemblePolicyCandidateEvidence(
|
|
|
89
38
|
const hasCompatibilityRequirements = Boolean(
|
|
90
39
|
compatibilityPolicy && compatibilityPolicy.requiredSuites.length > 0,
|
|
91
40
|
);
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const loadCatalog = options.loadCatalogSnapshot ?? loadCompatibilityCatalogSnapshot;
|
|
103
|
-
const loadEvidence = options.loadEvidenceSnapshot ?? loadCompatibilityEvidenceSnapshot;
|
|
104
|
-
catalog = loadCatalog(compatibilityPolicy.requiredSuites);
|
|
105
|
-
const subjectIds = new Set<string>();
|
|
106
|
-
|
|
107
|
-
for (const candidate of profile.candidates) {
|
|
108
|
-
const provider = config.providers[candidate.provider];
|
|
109
|
-
if (!provider) continue;
|
|
110
|
-
try {
|
|
111
|
-
const routed = options.routedProviderConfig(candidate.provider, provider);
|
|
112
|
-
const resolved = resolveSubjects(
|
|
113
|
-
config,
|
|
114
|
-
candidate.provider,
|
|
115
|
-
candidate.model,
|
|
116
|
-
routed,
|
|
117
|
-
options.configDir,
|
|
118
|
-
);
|
|
119
|
-
resolvedByCandidate.set(`${candidate.provider}/${candidate.model}`, resolved);
|
|
120
|
-
for (const subjectId of Object.values(resolved.subjectIds)) {
|
|
121
|
-
if (subjectId) subjectIds.add(subjectId);
|
|
122
|
-
}
|
|
123
|
-
} catch {
|
|
124
|
-
// Subject construction failure is handled per required layer as unknown.
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
snapshot = loadEvidence([...subjectIds], options.configDir);
|
|
129
|
-
}
|
|
41
|
+
// Compatibility evidence is supplied by an opt-in subsystem. With no provider registered
|
|
42
|
+
// -- every install without compatibility-gated profiles -- the evaluator sees no
|
|
43
|
+
// compatibility evidence and scores on capability, health, quota, and cost exactly as it
|
|
44
|
+
// did before compatibility policy existed.
|
|
45
|
+
const compatibilityProvider = hasCompatibilityRequirements && compatibilityPolicy
|
|
46
|
+
? resolveCompatibilityEvidenceProvider()
|
|
47
|
+
: null;
|
|
48
|
+
const compatibilityByCandidate = compatibilityProvider && compatibilityPolicy
|
|
49
|
+
? compatibilityProvider(config, profile, compatibilityPolicy, options)
|
|
50
|
+
: null;
|
|
130
51
|
|
|
131
52
|
return profile.candidates.map(candidate => {
|
|
132
53
|
const key = `${candidate.provider}/${candidate.model}`;
|
|
133
|
-
const compatibility =
|
|
134
|
-
? attachCompatibilityEvidence(
|
|
135
|
-
resolvedByCandidate.get(key),
|
|
136
|
-
snapshot,
|
|
137
|
-
catalog,
|
|
138
|
-
compatibilityPolicy,
|
|
139
|
-
)
|
|
140
|
-
: undefined;
|
|
54
|
+
const compatibility = compatibilityByCandidate?.get(key);
|
|
141
55
|
|
|
142
56
|
return {
|
|
143
57
|
provider: candidate.provider,
|