@benjamolina/pi-antigravity-guard 0.3.0 → 0.4.1
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/README.md +28 -10
- package/dist/catalog.d.ts +57 -1
- package/dist/catalog.js +47 -22
- package/dist/context.d.ts +17 -1
- package/dist/context.js +97 -31
- package/dist/response.d.ts +31 -6
- package/dist/response.js +117 -49
- package/dist/stream.d.ts +32 -4
- package/dist/stream.js +115 -15
- package/dist/tool-context.d.ts +29 -0
- package/dist/tool-context.js +216 -0
- package/dist/tool-contract.d.ts +17 -0
- package/dist/tool-contract.js +65 -0
- package/dist/tool-schema.d.ts +7 -0
- package/dist/tool-schema.js +125 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,11 +12,35 @@ Install the package in Pi, then run:
|
|
|
12
12
|
|
|
13
13
|
Choose browser login, or choose manual login and paste the complete callback URL. The loopback callback uses fixed port `51121`; browser callback failures, remote shells, and port conflicts can use the manual callback-URL flow.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
## Text registration is not tool enablement
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
All seven rows below are registered for text. The exact `antigravity-gemini-3.8-flash` `off` route is enabled for tools from recorded direct evidence; every other production route is **not enabled for tools**, so its context with a declaration, assistant tool call, or tool result fails locally before any network request. Text-only requests retain their released behavior.
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
| Public ID | Exposed Pi levels | Tool state | Admission |
|
|
20
|
+
|---|---|---|---|
|
|
21
|
+
| `antigravity-gemini-3.8-flash` | off, low, medium, high | Enabled (off) | pi-json-tool-loop/gemini-3.8-flash-off-v1 |
|
|
22
|
+
| `antigravity-gemini-3.7-flash` | off, low, medium, high | Disabled | missing-direct-evidence |
|
|
23
|
+
| `antigravity-gemini-3.6-flash` | off, low, medium, high | Disabled | missing-direct-evidence |
|
|
24
|
+
| `antigravity-gemini-3.1-pro` | off, low, high | Disabled | missing-direct-evidence |
|
|
25
|
+
| `antigravity-claude-sonnet-4.6` | off, high | Disabled | claude-continuity-unproven |
|
|
26
|
+
| `antigravity-claude-opus-4.6-thinking` | off, high | Disabled | claude-continuity-unproven |
|
|
27
|
+
| `antigravity-gpt-oss-120b` | off, medium | Disabled | missing-direct-evidence |
|
|
28
|
+
|
|
29
|
+
`off` is available for every row even though it is omitted from Pi's level map; omitted reasoning also selects that row's `off` route. Unsupported levels are not advertised and are rejected before transport. `antigravity-gemini-3.5-flash` is not registered or advertised as supported because its recorded HTTP-200 response lacks the strict terminal metadata required for admission. A future `fixture-qualified` route remains disabled for ordinary tool use; only separately authorized direct validation for the exact route may make it enabled.
|
|
30
|
+
|
|
31
|
+
Claude Sonnet and Claude Opus remain disabled until declaration, call, result replay, thinking-signature, resume, interruption, and continuation evidence is recorded for each exact route. Only Gemini 3.8 Flash `off` is enabled by this package release.
|
|
32
|
+
|
|
33
|
+
## Opt-in direct tool-loop validation
|
|
34
|
+
|
|
35
|
+
Maintainers can run `npm run build:pi` followed by `npx tsx scripts/pi-tool-loop-probe.ts --live --expect-enabled` to verify the directly admitted Gemini 3.8 Flash `off` route. The isolated runner invokes Pi in `--mode json`, passes the deterministic non-terminating `pi_evidence_echo` prompt positionally, drains its event stream, and completes after Pi emits `agent_end` and the child exits. It stores only the passing route and sanitized assertions; it never stores message contents, tool details, names, IDs, arguments, credentials, headers, or raw provider output. Other routes remain fail-closed.
|
|
36
|
+
|
|
37
|
+
## Tool preflight boundaries
|
|
38
|
+
|
|
39
|
+
Tool-bearing requests are never silently downgraded to text. When an exact route is eventually enabled, declarations are retained in order and omitted or `auto` choice serializes as `AUTO`; explicit `none` serializes as `NONE`. Forced, required, named, unknown, and constrained-sampling choices are rejected before transport.
|
|
40
|
+
|
|
41
|
+
Schemas must use the documented strict object-rooted subset. Unsupported keywords, references, unions, lossy constraints, empty objects, and oversized input are rejected rather than cleaned. Tool results must match the exact call ID and name, remain text-only, and are replayed in assistant source-call order; image-bearing or ambiguous results are rejected. A missing persisted result uses the documented failed response only during context reconstruction.
|
|
42
|
+
|
|
43
|
+
## Text catalog limits and safety
|
|
20
44
|
|
|
21
45
|
| Public ID | Exposed Pi levels | Context / output |
|
|
22
46
|
|---|---|---:|
|
|
@@ -28,16 +52,10 @@ The provider registers only these evidence-admitted public IDs, in this order. `
|
|
|
28
52
|
| `antigravity-claude-opus-4.6-thinking` | off, high | 250,000 / 64,000 |
|
|
29
53
|
| `antigravity-gpt-oss-120b` | off, medium | 131,072 / 32,768 |
|
|
30
54
|
|
|
31
|
-
Gemini 3.8 remains compatible with the released mapping: `antigravity-gemini-3.8-flash` sends `gemini-3.8-flash-tiered`; `off` sends native `thinkingLevel: "low"` with hidden thoughts, and `low`, `medium`, and `high` send their matching native level with visible thoughts.
|
|
32
|
-
|
|
33
|
-
`antigravity-gemini-3.5-flash` is not registered or advertised as supported because its recorded HTTP-200 response lacks the strict terminal metadata required for admission.
|
|
34
|
-
|
|
35
|
-
## Limits and safety
|
|
36
|
-
|
|
37
55
|
An explicit `maxTokens` must be a positive integer no greater than the row's output limit. For a route with a positive finite thinking budget, it must also be greater than that budget. When `maxTokens` is omitted, the provider uses 4,096 unless a larger finite thinking budget needs a 1,024-token answer reserve, always bounded by the row's output limit. Custom `thinkingBudgets` are unsupported and rejected.
|
|
38
56
|
|
|
39
57
|
- Costs are reported as zero because subscription usage is unpriced, not because access is free.
|
|
40
58
|
- The catalog is static: it performs no startup or runtime model discovery, catalog synchronization, or dynamic registration.
|
|
41
|
-
- It has no account rotation, quota fallback, model substitution,
|
|
59
|
+
- It has no account rotation, quota fallback, model substitution, image support, or enabled tools beyond the directly admitted Gemini 3.8 Flash `off` route.
|
|
42
60
|
|
|
43
61
|
To remove it, disable or uninstall `@benjamolina/pi-antigravity-guard` and reload Pi. Removal does not delete Pi-managed credentials or revoke tokens.
|
package/dist/catalog.d.ts
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
export type StandardReasoningLevel = "off" | "minimal" | "low" | "medium" | "high";
|
|
2
2
|
type NativeThinkingLevel = "low" | "medium" | "high";
|
|
3
|
+
export interface ToolEvidenceRef {
|
|
4
|
+
readonly record: string;
|
|
5
|
+
readonly revision: string;
|
|
6
|
+
readonly publicModelId: string;
|
|
7
|
+
readonly reasoning: StandardReasoningLevel;
|
|
8
|
+
readonly wireModel: string;
|
|
9
|
+
}
|
|
10
|
+
export type ToolCapability = {
|
|
11
|
+
readonly state: "disabled";
|
|
12
|
+
readonly contractRevision: 1;
|
|
13
|
+
readonly reason: "missing-direct-evidence" | "stale-or-conflicting-evidence" | "claude-continuity-unproven";
|
|
14
|
+
} | {
|
|
15
|
+
readonly state: "fixture-qualified";
|
|
16
|
+
readonly contractRevision: 1;
|
|
17
|
+
readonly fixtureEvidence: ToolEvidenceRef;
|
|
18
|
+
} | {
|
|
19
|
+
readonly state: "enabled";
|
|
20
|
+
readonly contractRevision: 1;
|
|
21
|
+
readonly fixtureEvidence: ToolEvidenceRef;
|
|
22
|
+
readonly directEvidence: ToolEvidenceRef;
|
|
23
|
+
};
|
|
3
24
|
type NativeRoute = {
|
|
4
25
|
readonly wireModel: string;
|
|
5
26
|
readonly thinking: {
|
|
@@ -19,6 +40,9 @@ type IntegerBudgetRoute = {
|
|
|
19
40
|
};
|
|
20
41
|
};
|
|
21
42
|
export type GenerationRoute = NativeRoute | IntegerBudgetRoute;
|
|
43
|
+
type ConfiguredGenerationRoute = GenerationRoute & {
|
|
44
|
+
readonly tools: ToolCapability;
|
|
45
|
+
};
|
|
22
46
|
export interface CatalogEntry {
|
|
23
47
|
readonly publicId: string;
|
|
24
48
|
readonly descriptor: {
|
|
@@ -27,7 +51,7 @@ export interface CatalogEntry {
|
|
|
27
51
|
readonly contextWindow: number;
|
|
28
52
|
readonly maxTokens: number;
|
|
29
53
|
};
|
|
30
|
-
readonly routes: Readonly<Partial<Record<StandardReasoningLevel,
|
|
54
|
+
readonly routes: Readonly<Partial<Record<StandardReasoningLevel, ConfiguredGenerationRoute>>>;
|
|
31
55
|
readonly replay: {
|
|
32
56
|
readonly kind: "same-public-model" | "strip";
|
|
33
57
|
};
|
|
@@ -57,6 +81,7 @@ declare const CATALOG: readonly [{
|
|
|
57
81
|
readonly thinkingLevel: "low";
|
|
58
82
|
readonly includeThoughts: false;
|
|
59
83
|
};
|
|
84
|
+
readonly tools: ToolCapability;
|
|
60
85
|
};
|
|
61
86
|
readonly low: {
|
|
62
87
|
readonly wireModel: "gemini-3.8-flash-tiered";
|
|
@@ -65,6 +90,7 @@ declare const CATALOG: readonly [{
|
|
|
65
90
|
readonly thinkingLevel: "low";
|
|
66
91
|
readonly includeThoughts: true;
|
|
67
92
|
};
|
|
93
|
+
readonly tools: ToolCapability;
|
|
68
94
|
};
|
|
69
95
|
readonly medium: {
|
|
70
96
|
readonly wireModel: "gemini-3.8-flash-tiered";
|
|
@@ -73,6 +99,7 @@ declare const CATALOG: readonly [{
|
|
|
73
99
|
readonly thinkingLevel: "medium";
|
|
74
100
|
readonly includeThoughts: true;
|
|
75
101
|
};
|
|
102
|
+
readonly tools: ToolCapability;
|
|
76
103
|
};
|
|
77
104
|
readonly high: {
|
|
78
105
|
readonly wireModel: "gemini-3.8-flash-tiered";
|
|
@@ -81,6 +108,7 @@ declare const CATALOG: readonly [{
|
|
|
81
108
|
readonly thinkingLevel: "high";
|
|
82
109
|
readonly includeThoughts: true;
|
|
83
110
|
};
|
|
111
|
+
readonly tools: ToolCapability;
|
|
84
112
|
};
|
|
85
113
|
};
|
|
86
114
|
readonly replay: {
|
|
@@ -111,6 +139,7 @@ declare const CATALOG: readonly [{
|
|
|
111
139
|
readonly budget: 0;
|
|
112
140
|
readonly includeThoughts: false;
|
|
113
141
|
};
|
|
142
|
+
readonly tools: ToolCapability;
|
|
114
143
|
};
|
|
115
144
|
readonly low: {
|
|
116
145
|
readonly wireModel: "gemini-3.7-flash-low";
|
|
@@ -119,6 +148,7 @@ declare const CATALOG: readonly [{
|
|
|
119
148
|
readonly budget: 1000;
|
|
120
149
|
readonly includeThoughts: true;
|
|
121
150
|
};
|
|
151
|
+
readonly tools: ToolCapability;
|
|
122
152
|
};
|
|
123
153
|
readonly medium: {
|
|
124
154
|
readonly wireModel: "gemini-3.7-flash-medium";
|
|
@@ -127,6 +157,7 @@ declare const CATALOG: readonly [{
|
|
|
127
157
|
readonly budget: 4000;
|
|
128
158
|
readonly includeThoughts: true;
|
|
129
159
|
};
|
|
160
|
+
readonly tools: ToolCapability;
|
|
130
161
|
};
|
|
131
162
|
readonly high: {
|
|
132
163
|
readonly wireModel: "gemini-3.7-flash-high";
|
|
@@ -135,6 +166,7 @@ declare const CATALOG: readonly [{
|
|
|
135
166
|
readonly budget: -1;
|
|
136
167
|
readonly includeThoughts: true;
|
|
137
168
|
};
|
|
169
|
+
readonly tools: ToolCapability;
|
|
138
170
|
};
|
|
139
171
|
};
|
|
140
172
|
readonly replay: {
|
|
@@ -163,6 +195,7 @@ declare const CATALOG: readonly [{
|
|
|
163
195
|
readonly thinking: {
|
|
164
196
|
readonly kind: "omit";
|
|
165
197
|
};
|
|
198
|
+
readonly tools: ToolCapability;
|
|
166
199
|
};
|
|
167
200
|
readonly low: {
|
|
168
201
|
readonly wireModel: "gemini-3.6-flash-low";
|
|
@@ -171,6 +204,7 @@ declare const CATALOG: readonly [{
|
|
|
171
204
|
readonly budget: 1000;
|
|
172
205
|
readonly includeThoughts: true;
|
|
173
206
|
};
|
|
207
|
+
readonly tools: ToolCapability;
|
|
174
208
|
};
|
|
175
209
|
readonly medium: {
|
|
176
210
|
readonly wireModel: "gemini-3.6-flash-medium";
|
|
@@ -179,6 +213,7 @@ declare const CATALOG: readonly [{
|
|
|
179
213
|
readonly budget: 4000;
|
|
180
214
|
readonly includeThoughts: true;
|
|
181
215
|
};
|
|
216
|
+
readonly tools: ToolCapability;
|
|
182
217
|
};
|
|
183
218
|
readonly high: {
|
|
184
219
|
readonly wireModel: "gemini-3.6-flash-high";
|
|
@@ -187,6 +222,7 @@ declare const CATALOG: readonly [{
|
|
|
187
222
|
readonly budget: -1;
|
|
188
223
|
readonly includeThoughts: true;
|
|
189
224
|
};
|
|
225
|
+
readonly tools: ToolCapability;
|
|
190
226
|
};
|
|
191
227
|
};
|
|
192
228
|
readonly replay: {
|
|
@@ -215,6 +251,7 @@ declare const CATALOG: readonly [{
|
|
|
215
251
|
readonly thinking: {
|
|
216
252
|
readonly kind: "omit";
|
|
217
253
|
};
|
|
254
|
+
readonly tools: ToolCapability;
|
|
218
255
|
};
|
|
219
256
|
readonly low: {
|
|
220
257
|
readonly wireModel: "gemini-3.1-pro-low";
|
|
@@ -223,6 +260,7 @@ declare const CATALOG: readonly [{
|
|
|
223
260
|
readonly budget: 1001;
|
|
224
261
|
readonly includeThoughts: true;
|
|
225
262
|
};
|
|
263
|
+
readonly tools: ToolCapability;
|
|
226
264
|
};
|
|
227
265
|
readonly high: {
|
|
228
266
|
readonly wireModel: "gemini-pro-agent";
|
|
@@ -231,6 +269,7 @@ declare const CATALOG: readonly [{
|
|
|
231
269
|
readonly budget: 10001;
|
|
232
270
|
readonly includeThoughts: true;
|
|
233
271
|
};
|
|
272
|
+
readonly tools: ToolCapability;
|
|
234
273
|
};
|
|
235
274
|
};
|
|
236
275
|
readonly replay: {
|
|
@@ -261,6 +300,7 @@ declare const CATALOG: readonly [{
|
|
|
261
300
|
readonly budget: 0;
|
|
262
301
|
readonly includeThoughts: false;
|
|
263
302
|
};
|
|
303
|
+
readonly tools: ToolCapability;
|
|
264
304
|
};
|
|
265
305
|
readonly high: {
|
|
266
306
|
readonly wireModel: "claude-sonnet-4-6";
|
|
@@ -269,6 +309,7 @@ declare const CATALOG: readonly [{
|
|
|
269
309
|
readonly budget: 1024;
|
|
270
310
|
readonly includeThoughts: true;
|
|
271
311
|
};
|
|
312
|
+
readonly tools: ToolCapability;
|
|
272
313
|
};
|
|
273
314
|
};
|
|
274
315
|
readonly replay: {
|
|
@@ -299,6 +340,7 @@ declare const CATALOG: readonly [{
|
|
|
299
340
|
readonly budget: 0;
|
|
300
341
|
readonly includeThoughts: false;
|
|
301
342
|
};
|
|
343
|
+
readonly tools: ToolCapability;
|
|
302
344
|
};
|
|
303
345
|
readonly high: {
|
|
304
346
|
readonly wireModel: "claude-opus-4-6-thinking";
|
|
@@ -307,6 +349,7 @@ declare const CATALOG: readonly [{
|
|
|
307
349
|
readonly budget: 1024;
|
|
308
350
|
readonly includeThoughts: true;
|
|
309
351
|
};
|
|
352
|
+
readonly tools: ToolCapability;
|
|
310
353
|
};
|
|
311
354
|
};
|
|
312
355
|
readonly replay: {
|
|
@@ -335,6 +378,7 @@ declare const CATALOG: readonly [{
|
|
|
335
378
|
readonly thinking: {
|
|
336
379
|
readonly kind: "omit";
|
|
337
380
|
};
|
|
381
|
+
readonly tools: ToolCapability;
|
|
338
382
|
};
|
|
339
383
|
readonly medium: {
|
|
340
384
|
readonly wireModel: "gpt-oss-120b-medium";
|
|
@@ -343,6 +387,7 @@ declare const CATALOG: readonly [{
|
|
|
343
387
|
readonly budget: 8192;
|
|
344
388
|
readonly includeThoughts: true;
|
|
345
389
|
};
|
|
390
|
+
readonly tools: ToolCapability;
|
|
346
391
|
};
|
|
347
392
|
};
|
|
348
393
|
readonly replay: {
|
|
@@ -356,6 +401,10 @@ declare const CATALOG: readonly [{
|
|
|
356
401
|
type Catalog = typeof CATALOG;
|
|
357
402
|
export type AntigravityPublicModelId = Catalog[number]["publicId"];
|
|
358
403
|
export type AntigravityWireModelId = Catalog[number]["routes"][keyof Catalog[number]["routes"]]["wireModel"];
|
|
404
|
+
/** Constructs deterministic capability data for hermetic serializer tests only. */
|
|
405
|
+
export declare function createEnabledToolCapability(evidence: ToolEvidenceRef): Extract<ToolCapability, {
|
|
406
|
+
readonly state: "enabled";
|
|
407
|
+
}>;
|
|
359
408
|
export declare function defineCatalog<T extends readonly {
|
|
360
409
|
readonly publicId: string;
|
|
361
410
|
readonly descriptor?: CatalogEntry["descriptor"];
|
|
@@ -367,6 +416,13 @@ export declare function defineCatalog<T extends readonly {
|
|
|
367
416
|
export declare function listCatalogEntries(): readonly CatalogEntry[];
|
|
368
417
|
export declare function getCatalogEntry(publicId: unknown): CatalogEntry | undefined;
|
|
369
418
|
export declare function resolveGenerationRoute(entry: CatalogEntry, reasoning: unknown): GenerationRoute;
|
|
419
|
+
export interface GenerationSelection {
|
|
420
|
+
readonly level: StandardReasoningLevel;
|
|
421
|
+
readonly route: GenerationRoute;
|
|
422
|
+
readonly tools: ToolCapability;
|
|
423
|
+
}
|
|
424
|
+
export declare function resolveGenerationSelection(entry: CatalogEntry, reasoning: unknown): GenerationSelection;
|
|
425
|
+
export declare function resolveToolCapability(capability: ToolCapability, publicModelId: string, reasoning: StandardReasoningLevel, wireModel: string): ToolCapability;
|
|
370
426
|
export declare function toPiModelDescriptor(entry: CatalogEntry): {
|
|
371
427
|
id: string;
|
|
372
428
|
name: string;
|
package/dist/catalog.js
CHANGED
|
@@ -8,10 +8,10 @@ const CATALOG = freeze(defineCatalog([{
|
|
|
8
8
|
maxTokens: 65_536,
|
|
9
9
|
},
|
|
10
10
|
routes: {
|
|
11
|
-
off: { wireModel: "gemini-3.8-flash-tiered", thinking: { kind: "native-level", thinkingLevel: "low", includeThoughts: false } },
|
|
12
|
-
low: { wireModel: "gemini-3.8-flash-tiered", thinking: { kind: "native-level", thinkingLevel: "low", includeThoughts: true } },
|
|
13
|
-
medium: { wireModel: "gemini-3.8-flash-tiered", thinking: { kind: "native-level", thinkingLevel: "medium", includeThoughts: true } },
|
|
14
|
-
high: { wireModel: "gemini-3.8-flash-tiered", thinking: { kind: "native-level", thinkingLevel: "high", includeThoughts: true } },
|
|
11
|
+
off: { wireModel: "gemini-3.8-flash-tiered", thinking: { kind: "native-level", thinkingLevel: "low", includeThoughts: false }, tools: enabled({ record: "pi-json-tool-loop", revision: "gemini-3.8-flash-off-v1", publicModelId: "antigravity-gemini-3.8-flash", reasoning: "off", wireModel: "gemini-3.8-flash-tiered" }) },
|
|
12
|
+
low: { wireModel: "gemini-3.8-flash-tiered", thinking: { kind: "native-level", thinkingLevel: "low", includeThoughts: true }, tools: disabled("missing-direct-evidence") },
|
|
13
|
+
medium: { wireModel: "gemini-3.8-flash-tiered", thinking: { kind: "native-level", thinkingLevel: "medium", includeThoughts: true }, tools: disabled("missing-direct-evidence") },
|
|
14
|
+
high: { wireModel: "gemini-3.8-flash-tiered", thinking: { kind: "native-level", thinkingLevel: "high", includeThoughts: true }, tools: disabled("missing-direct-evidence") },
|
|
15
15
|
},
|
|
16
16
|
replay: { kind: "same-public-model" },
|
|
17
17
|
response: { kind: "gemini-envelope", family: "gemini" },
|
|
@@ -24,10 +24,10 @@ const CATALOG = freeze(defineCatalog([{
|
|
|
24
24
|
maxTokens: 65_536,
|
|
25
25
|
},
|
|
26
26
|
routes: {
|
|
27
|
-
off: { wireModel: "gemini-3.7-flash-low", thinking: { kind: "budget", budget: 0, includeThoughts: false } },
|
|
28
|
-
low: { wireModel: "gemini-3.7-flash-low", thinking: { kind: "budget", budget: 1000, includeThoughts: true } },
|
|
29
|
-
medium: { wireModel: "gemini-3.7-flash-medium", thinking: { kind: "budget", budget: 4000, includeThoughts: true } },
|
|
30
|
-
high: { wireModel: "gemini-3.7-flash-high", thinking: { kind: "budget", budget: -1, includeThoughts: true } },
|
|
27
|
+
off: { wireModel: "gemini-3.7-flash-low", thinking: { kind: "budget", budget: 0, includeThoughts: false }, tools: disabled("missing-direct-evidence") },
|
|
28
|
+
low: { wireModel: "gemini-3.7-flash-low", thinking: { kind: "budget", budget: 1000, includeThoughts: true }, tools: disabled("missing-direct-evidence") },
|
|
29
|
+
medium: { wireModel: "gemini-3.7-flash-medium", thinking: { kind: "budget", budget: 4000, includeThoughts: true }, tools: disabled("missing-direct-evidence") },
|
|
30
|
+
high: { wireModel: "gemini-3.7-flash-high", thinking: { kind: "budget", budget: -1, includeThoughts: true }, tools: disabled("missing-direct-evidence") },
|
|
31
31
|
},
|
|
32
32
|
replay: { kind: "strip" },
|
|
33
33
|
response: { kind: "gemini-envelope", family: "gemini" },
|
|
@@ -40,10 +40,10 @@ const CATALOG = freeze(defineCatalog([{
|
|
|
40
40
|
maxTokens: 65_536,
|
|
41
41
|
},
|
|
42
42
|
routes: {
|
|
43
|
-
off: { wireModel: "gemini-3.6-flash-low", thinking: { kind: "omit" } },
|
|
44
|
-
low: { wireModel: "gemini-3.6-flash-low", thinking: { kind: "budget", budget: 1000, includeThoughts: true } },
|
|
45
|
-
medium: { wireModel: "gemini-3.6-flash-medium", thinking: { kind: "budget", budget: 4000, includeThoughts: true } },
|
|
46
|
-
high: { wireModel: "gemini-3.6-flash-high", thinking: { kind: "budget", budget: -1, includeThoughts: true } },
|
|
43
|
+
off: { wireModel: "gemini-3.6-flash-low", thinking: { kind: "omit" }, tools: disabled("missing-direct-evidence") },
|
|
44
|
+
low: { wireModel: "gemini-3.6-flash-low", thinking: { kind: "budget", budget: 1000, includeThoughts: true }, tools: disabled("missing-direct-evidence") },
|
|
45
|
+
medium: { wireModel: "gemini-3.6-flash-medium", thinking: { kind: "budget", budget: 4000, includeThoughts: true }, tools: disabled("missing-direct-evidence") },
|
|
46
|
+
high: { wireModel: "gemini-3.6-flash-high", thinking: { kind: "budget", budget: -1, includeThoughts: true }, tools: disabled("missing-direct-evidence") },
|
|
47
47
|
},
|
|
48
48
|
replay: { kind: "strip" },
|
|
49
49
|
response: { kind: "gemini-envelope", family: "gemini" },
|
|
@@ -56,9 +56,9 @@ const CATALOG = freeze(defineCatalog([{
|
|
|
56
56
|
maxTokens: 65_535,
|
|
57
57
|
},
|
|
58
58
|
routes: {
|
|
59
|
-
off: { wireModel: "gemini-3.1-pro-low", thinking: { kind: "omit" } },
|
|
60
|
-
low: { wireModel: "gemini-3.1-pro-low", thinking: { kind: "budget", budget: 1001, includeThoughts: true } },
|
|
61
|
-
high: { wireModel: "gemini-pro-agent", thinking: { kind: "budget", budget: 10001, includeThoughts: true } },
|
|
59
|
+
off: { wireModel: "gemini-3.1-pro-low", thinking: { kind: "omit" }, tools: disabled("missing-direct-evidence") },
|
|
60
|
+
low: { wireModel: "gemini-3.1-pro-low", thinking: { kind: "budget", budget: 1001, includeThoughts: true }, tools: disabled("missing-direct-evidence") },
|
|
61
|
+
high: { wireModel: "gemini-pro-agent", thinking: { kind: "budget", budget: 10001, includeThoughts: true }, tools: disabled("missing-direct-evidence") },
|
|
62
62
|
},
|
|
63
63
|
replay: { kind: "strip" },
|
|
64
64
|
response: { kind: "gemini-envelope", family: "gemini" },
|
|
@@ -71,8 +71,8 @@ const CATALOG = freeze(defineCatalog([{
|
|
|
71
71
|
maxTokens: 64_000,
|
|
72
72
|
},
|
|
73
73
|
routes: {
|
|
74
|
-
off: { wireModel: "claude-sonnet-4-6", thinking: { kind: "budget", budget: 0, includeThoughts: false } },
|
|
75
|
-
high: { wireModel: "claude-sonnet-4-6", thinking: { kind: "budget", budget: 1024, includeThoughts: true } },
|
|
74
|
+
off: { wireModel: "claude-sonnet-4-6", thinking: { kind: "budget", budget: 0, includeThoughts: false }, tools: disabled("claude-continuity-unproven") },
|
|
75
|
+
high: { wireModel: "claude-sonnet-4-6", thinking: { kind: "budget", budget: 1024, includeThoughts: true }, tools: disabled("claude-continuity-unproven") },
|
|
76
76
|
},
|
|
77
77
|
replay: { kind: "strip" },
|
|
78
78
|
response: { kind: "gemini-envelope", family: "claude" },
|
|
@@ -85,8 +85,8 @@ const CATALOG = freeze(defineCatalog([{
|
|
|
85
85
|
maxTokens: 64_000,
|
|
86
86
|
},
|
|
87
87
|
routes: {
|
|
88
|
-
off: { wireModel: "claude-opus-4-6-thinking", thinking: { kind: "budget", budget: 0, includeThoughts: false } },
|
|
89
|
-
high: { wireModel: "claude-opus-4-6-thinking", thinking: { kind: "budget", budget: 1024, includeThoughts: true } },
|
|
88
|
+
off: { wireModel: "claude-opus-4-6-thinking", thinking: { kind: "budget", budget: 0, includeThoughts: false }, tools: disabled("claude-continuity-unproven") },
|
|
89
|
+
high: { wireModel: "claude-opus-4-6-thinking", thinking: { kind: "budget", budget: 1024, includeThoughts: true }, tools: disabled("claude-continuity-unproven") },
|
|
90
90
|
},
|
|
91
91
|
replay: { kind: "strip" },
|
|
92
92
|
response: { kind: "gemini-envelope", family: "claude" },
|
|
@@ -99,13 +99,23 @@ const CATALOG = freeze(defineCatalog([{
|
|
|
99
99
|
maxTokens: 32_768,
|
|
100
100
|
},
|
|
101
101
|
routes: {
|
|
102
|
-
off: { wireModel: "gpt-oss-120b-medium", thinking: { kind: "omit" } },
|
|
103
|
-
medium: { wireModel: "gpt-oss-120b-medium", thinking: { kind: "budget", budget: 8192, includeThoughts: true } },
|
|
102
|
+
off: { wireModel: "gpt-oss-120b-medium", thinking: { kind: "omit" }, tools: disabled("missing-direct-evidence") },
|
|
103
|
+
medium: { wireModel: "gpt-oss-120b-medium", thinking: { kind: "budget", budget: 8192, includeThoughts: true }, tools: disabled("missing-direct-evidence") },
|
|
104
104
|
},
|
|
105
105
|
replay: { kind: "strip" },
|
|
106
106
|
response: { kind: "gemini-envelope", family: "gpt-oss" },
|
|
107
107
|
}]));
|
|
108
108
|
const lookup = new Map(CATALOG.map((entry) => [entry.publicId, entry]));
|
|
109
|
+
function disabled(reason) {
|
|
110
|
+
return { state: "disabled", contractRevision: 1, reason };
|
|
111
|
+
}
|
|
112
|
+
function enabled(evidence) {
|
|
113
|
+
return { state: "enabled", contractRevision: 1, fixtureEvidence: evidence, directEvidence: evidence };
|
|
114
|
+
}
|
|
115
|
+
/** Constructs deterministic capability data for hermetic serializer tests only. */
|
|
116
|
+
export function createEnabledToolCapability(evidence) {
|
|
117
|
+
return freeze({ state: "enabled", contractRevision: 1, fixtureEvidence: evidence, directEvidence: evidence });
|
|
118
|
+
}
|
|
109
119
|
function freeze(value) {
|
|
110
120
|
if (typeof value === "object" && value !== null) {
|
|
111
121
|
Object.freeze(value);
|
|
@@ -147,10 +157,25 @@ export function getCatalogEntry(publicId) {
|
|
|
147
157
|
return typeof publicId === "string" ? lookup.get(publicId) : undefined;
|
|
148
158
|
}
|
|
149
159
|
export function resolveGenerationRoute(entry, reasoning) {
|
|
160
|
+
return resolveGenerationSelection(entry, reasoning).route;
|
|
161
|
+
}
|
|
162
|
+
export function resolveGenerationSelection(entry, reasoning) {
|
|
150
163
|
const level = reasoning === undefined || reasoning === "off" ? "off" : reasoning;
|
|
151
164
|
if (typeof level !== "string" || !entry.routes[level])
|
|
152
165
|
throw new Error("Unsupported reasoning level");
|
|
153
|
-
|
|
166
|
+
const typedLevel = level;
|
|
167
|
+
const configured = entry.routes[typedLevel];
|
|
168
|
+
const { tools, ...route } = configured;
|
|
169
|
+
return { level: typedLevel, route, tools: resolveToolCapability(tools, entry.publicId, typedLevel, route.wireModel) };
|
|
170
|
+
}
|
|
171
|
+
export function resolveToolCapability(capability, publicModelId, reasoning, wireModel) {
|
|
172
|
+
if (capability.state === "disabled")
|
|
173
|
+
return capability;
|
|
174
|
+
const evidence = capability.state === "enabled" ? [capability.fixtureEvidence, capability.directEvidence] : [capability.fixtureEvidence];
|
|
175
|
+
if (capability.contractRevision !== 1 || evidence.some((item) => item.publicModelId !== publicModelId || item.reasoning !== reasoning || item.wireModel !== wireModel)) {
|
|
176
|
+
return freeze(disabled("stale-or-conflicting-evidence"));
|
|
177
|
+
}
|
|
178
|
+
return capability;
|
|
154
179
|
}
|
|
155
180
|
export function toPiModelDescriptor(entry) {
|
|
156
181
|
return {
|
package/dist/context.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { Context, Model, SimpleStreamOptions } from "@earendil-works/pi-ai";
|
|
2
|
+
import { type GenerationSelection } from "./catalog.ts";
|
|
3
|
+
import { type ToolReplayDiagnostics } from "./tool-context.ts";
|
|
2
4
|
interface Part {
|
|
3
5
|
text: string;
|
|
4
6
|
thought?: true;
|
|
@@ -6,7 +8,12 @@ interface Part {
|
|
|
6
8
|
}
|
|
7
9
|
interface Content {
|
|
8
10
|
role: "user" | "model";
|
|
9
|
-
parts:
|
|
11
|
+
parts: object[];
|
|
12
|
+
}
|
|
13
|
+
interface GeminiFunctionDeclaration {
|
|
14
|
+
readonly name: string;
|
|
15
|
+
readonly description: string;
|
|
16
|
+
readonly parametersJsonSchema: object;
|
|
10
17
|
}
|
|
11
18
|
export interface GenerationRequest {
|
|
12
19
|
project: string;
|
|
@@ -16,6 +23,14 @@ export interface GenerationRequest {
|
|
|
16
23
|
systemInstruction?: {
|
|
17
24
|
parts: Part[];
|
|
18
25
|
};
|
|
26
|
+
tools?: {
|
|
27
|
+
functionDeclarations: readonly GeminiFunctionDeclaration[];
|
|
28
|
+
}[];
|
|
29
|
+
toolConfig?: {
|
|
30
|
+
functionCallingConfig: {
|
|
31
|
+
mode: "AUTO" | "NONE";
|
|
32
|
+
};
|
|
33
|
+
};
|
|
19
34
|
generationConfig: {
|
|
20
35
|
temperature: number;
|
|
21
36
|
maxOutputTokens: number;
|
|
@@ -41,5 +56,6 @@ export interface SerializeTextContextInput {
|
|
|
41
56
|
}
|
|
42
57
|
export declare class ContextSerializationError extends Error {
|
|
43
58
|
}
|
|
59
|
+
export declare function serializeContext(input: SerializeTextContextInput, injectedSelection?: GenerationSelection, onDiagnostics?: (diagnostics: ToolReplayDiagnostics) => void): GenerationRequest;
|
|
44
60
|
export declare function serializeTextContext(input: SerializeTextContextInput): GenerationRequest;
|
|
45
61
|
export {};
|
package/dist/context.js
CHANGED
|
@@ -1,10 +1,47 @@
|
|
|
1
|
-
import { getCatalogEntry,
|
|
1
|
+
import { getCatalogEntry, resolveGenerationSelection } from "./catalog.js";
|
|
2
|
+
import { hasToolContext, prepareToolContext, replayToolHistory } from "./tool-context.js";
|
|
2
3
|
const MAX_TEXT_BYTES = 8 * 1024 * 1024;
|
|
3
4
|
const PROVIDER = "antigravity-guard";
|
|
4
5
|
const RESERVED_HEADERS = new Set(["authorization", "host", "content-type", "content-length"]);
|
|
5
6
|
const THINKING_LEVELS = ["low", "medium", "high"];
|
|
6
7
|
export class ContextSerializationError extends Error {
|
|
7
8
|
}
|
|
9
|
+
export function serializeContext(input, injectedSelection, onDiagnostics) {
|
|
10
|
+
const context = input.context;
|
|
11
|
+
if (!hasToolContext(context))
|
|
12
|
+
return serializeTextContext(input);
|
|
13
|
+
const options = input.options;
|
|
14
|
+
const entry = isRecord(input.model) ? getCatalogEntry(field(input.model, "id", true)) : undefined;
|
|
15
|
+
if (!entry || !isRecord(context))
|
|
16
|
+
return serializeTextContext(input);
|
|
17
|
+
const selected = resolveGenerationSelection(entry, isRecord(options) ? option(options, "reasoning") : undefined);
|
|
18
|
+
const selection = injectedSelection ?? selected;
|
|
19
|
+
if (selection.level !== selected.level || selection.route.wireModel !== selected.route.wireModel)
|
|
20
|
+
fail("Invalid tool capability selection.");
|
|
21
|
+
if (selection.tools.state !== "enabled")
|
|
22
|
+
fail(capabilityError(entry.publicId, selection.level, selection.tools));
|
|
23
|
+
const prepared = prepareToolContext(field(context, "tools") ?? [], isRecord(options) ? field(options, "toolChoice") : undefined);
|
|
24
|
+
const { tools: _tools, messages, ...textContext } = context;
|
|
25
|
+
const { toolChoice: _choice, ...textOptions } = isRecord(options) ? options : {};
|
|
26
|
+
const text = serializeTextContext({ ...input, context: { ...textContext, messages: [{ role: "user", content: "placeholder", timestamp: 0 }] }, options: textOptions });
|
|
27
|
+
const sameModel = (message) => field(message, "role") === "assistant" && entry.replay.kind === "same-public-model" && isSameProviderAndModel(message, entry.publicId);
|
|
28
|
+
const signedToolReplay = geminiRequiresSignedToolReplay(selection.route.wireModel) ? {
|
|
29
|
+
requireSignedToolCalls: true,
|
|
30
|
+
isSameModel: sameModel,
|
|
31
|
+
toolCallSignature: (part, message) => sameModel(message) ? validThoughtSignature(field(part, "thoughtSignature")) : undefined,
|
|
32
|
+
} : undefined;
|
|
33
|
+
const contents = replayToolHistory(isDenseArray(messages), (part, message) => messagePart(part, field(message, "role") === "assistant", sameModel(message)), onDiagnostics, signedToolReplay, prepared?.declarations.length ?? 0);
|
|
34
|
+
const { systemInstruction, generationConfig } = text.request;
|
|
35
|
+
return { ...text, request: {
|
|
36
|
+
contents,
|
|
37
|
+
...(systemInstruction ? { systemInstruction } : {}),
|
|
38
|
+
...(prepared ? {
|
|
39
|
+
tools: [{ functionDeclarations: prepared.declarations.map(({ name, description, parameters }) => ({ name, description, parametersJsonSchema: parameters })) }],
|
|
40
|
+
...(prepared.mode === "NONE" ? { toolConfig: { functionCallingConfig: { mode: "NONE" } } } : {}),
|
|
41
|
+
} : {}),
|
|
42
|
+
generationConfig,
|
|
43
|
+
} };
|
|
44
|
+
}
|
|
8
45
|
export function serializeTextContext(input) {
|
|
9
46
|
try {
|
|
10
47
|
if (!isRecord(input))
|
|
@@ -17,7 +54,10 @@ export function serializeTextContext(input) {
|
|
|
17
54
|
const entry = isRecord(model) ? getCatalogEntry(field(model, "id", true)) : undefined;
|
|
18
55
|
if (!isRecord(context) || !entry || typeof project !== "string" || typeof requestId !== "string")
|
|
19
56
|
fail("Invalid text context.");
|
|
20
|
-
const
|
|
57
|
+
const selection = resolveGenerationSelection(entry, option(options, "reasoning"));
|
|
58
|
+
const route = selection.route;
|
|
59
|
+
if (isToolBearingContext(context) && selection.tools.state !== "enabled")
|
|
60
|
+
fail(capabilityError(entry.publicId, selection.level, selection.tools));
|
|
21
61
|
const systemPrompt = field(context, "systemPrompt");
|
|
22
62
|
const tools = field(context, "tools");
|
|
23
63
|
if (systemPrompt !== undefined && typeof systemPrompt !== "string")
|
|
@@ -62,42 +102,64 @@ export function serializeTextContext(input) {
|
|
|
62
102
|
fail("Invalid text context.");
|
|
63
103
|
}
|
|
64
104
|
}
|
|
105
|
+
function capabilityError(publicId, reasoning, capability) {
|
|
106
|
+
const reason = capability.state === "disabled" ? capability.reason : capability.state;
|
|
107
|
+
return `PI_TOOL_CAPABILITY_NOT_ENABLED: ${publicId}/${reasoning} is ${capability.state} (${reason}).`;
|
|
108
|
+
}
|
|
109
|
+
function isToolBearingContext(context) {
|
|
110
|
+
const tools = field(context, "tools");
|
|
111
|
+
if (tools !== undefined && isDenseArray(tools).length > 0)
|
|
112
|
+
return true;
|
|
113
|
+
const messages = isDenseArray(field(context, "messages", true));
|
|
114
|
+
for (const message of messages) {
|
|
115
|
+
if (!isRecord(message))
|
|
116
|
+
continue;
|
|
117
|
+
if (field(message, "role") === "toolResult")
|
|
118
|
+
return true;
|
|
119
|
+
const content = field(message, "content");
|
|
120
|
+
if (!Array.isArray(content))
|
|
121
|
+
continue;
|
|
122
|
+
for (const part of content)
|
|
123
|
+
if (isRecord(part) && field(part, "type") === "toolCall")
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
65
128
|
function messageParts(content, assistant, sameProviderAndModel) {
|
|
66
129
|
if (typeof content === "string")
|
|
67
130
|
return content ? [{ text: content }] : fail("A text conversation is required.");
|
|
68
131
|
const parts = isDenseArray(content);
|
|
69
132
|
if (!parts.length)
|
|
70
133
|
fail("A text conversation is required.");
|
|
71
|
-
return parts.map((part) =>
|
|
72
|
-
|
|
134
|
+
return parts.map((part) => messagePart(isRecord(part) ? part : fail("Only text context is supported by this provider."), assistant, sameProviderAndModel));
|
|
135
|
+
}
|
|
136
|
+
function messagePart(part, assistant, sameProviderAndModel) {
|
|
137
|
+
const type = field(part, "type", true);
|
|
138
|
+
if (type === "text") {
|
|
139
|
+
const text = field(part, "text", true);
|
|
140
|
+
if (typeof text !== "string")
|
|
73
141
|
fail("Only text context is supported by this provider.");
|
|
74
|
-
const
|
|
75
|
-
if (
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (typeof text !== "string" || !text)
|
|
96
|
-
fail("Only text context is supported by this provider.");
|
|
97
|
-
return { text };
|
|
98
|
-
}
|
|
99
|
-
fail("Only text context is supported by this provider.");
|
|
100
|
-
});
|
|
142
|
+
const thoughtSignature = sameProviderAndModel ? validThoughtSignature(field(part, "textSignature")) : undefined;
|
|
143
|
+
if (!text && !thoughtSignature)
|
|
144
|
+
fail("Only text context is supported by this provider.");
|
|
145
|
+
return { text, ...(thoughtSignature ? { thoughtSignature } : {}) };
|
|
146
|
+
}
|
|
147
|
+
if (type === "thinking" && assistant && sameProviderAndModel) {
|
|
148
|
+
const text = field(part, "thinking", true);
|
|
149
|
+
if (typeof text !== "string")
|
|
150
|
+
fail("Only text context is supported by this provider.");
|
|
151
|
+
const thoughtSignature = validThoughtSignature(field(part, "thinkingSignature"));
|
|
152
|
+
if (!text && !thoughtSignature)
|
|
153
|
+
fail("Only text context is supported by this provider.");
|
|
154
|
+
return { thought: true, text, ...(thoughtSignature ? { thoughtSignature } : {}) };
|
|
155
|
+
}
|
|
156
|
+
if (type === "thinking" && assistant) {
|
|
157
|
+
const text = field(part, "thinking", true);
|
|
158
|
+
if (typeof text !== "string" || !text)
|
|
159
|
+
fail("Only text context is supported by this provider.");
|
|
160
|
+
return { text };
|
|
161
|
+
}
|
|
162
|
+
fail("Only text context is supported by this provider.");
|
|
101
163
|
}
|
|
102
164
|
function isSameProviderAndModel(message, publicId) {
|
|
103
165
|
return field(message, "provider") === PROVIDER && field(message, "model") === publicId;
|
|
@@ -119,6 +181,10 @@ function resolveOutputTokens(maxTokens, maxAllowed, thinking) {
|
|
|
119
181
|
}
|
|
120
182
|
return thinking.kind === "budget" && thinking.budget > 0 ? Math.max(4096, thinking.budget + 1024) : 4096;
|
|
121
183
|
}
|
|
184
|
+
function geminiRequiresSignedToolReplay(wireModel) {
|
|
185
|
+
const match = /^gemini-(\d+)(?:[.-])/.exec(wireModel);
|
|
186
|
+
return match !== null && Number(match[1]) >= 3;
|
|
187
|
+
}
|
|
122
188
|
function validThoughtSignature(value) {
|
|
123
189
|
if (typeof value !== "string" || !value || value.length % 4 !== 0)
|
|
124
190
|
return undefined;
|