@benjamolina/pi-antigravity-guard 0.2.0 → 0.4.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.
@@ -0,0 +1,188 @@
1
+ const ANSWER_RESERVE = 1024;
2
+ const CATALOG = freeze(defineCatalog([{
3
+ publicId: "antigravity-gemini-3.8-flash",
4
+ descriptor: {
5
+ name: "Gemini 3.8 Flash (Antigravity, text only)",
6
+ thinkingLevelMap: { minimal: null, low: "low", medium: "medium", high: "high" },
7
+ contextWindow: 1_048_576,
8
+ maxTokens: 65_536,
9
+ },
10
+ routes: {
11
+ off: { wireModel: "gemini-3.8-flash-tiered", thinking: { kind: "native-level", thinkingLevel: "low", includeThoughts: false }, tools: disabled("missing-direct-evidence") },
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
+ },
16
+ replay: { kind: "same-public-model" },
17
+ response: { kind: "gemini-envelope", family: "gemini" },
18
+ }, {
19
+ publicId: "antigravity-gemini-3.7-flash",
20
+ descriptor: {
21
+ name: "Gemini 3.7 Flash (Antigravity, text only)",
22
+ thinkingLevelMap: { minimal: null, low: "low", medium: "medium", high: "high" },
23
+ contextWindow: 1_048_576,
24
+ maxTokens: 65_536,
25
+ },
26
+ routes: {
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
+ },
32
+ replay: { kind: "strip" },
33
+ response: { kind: "gemini-envelope", family: "gemini" },
34
+ }, {
35
+ publicId: "antigravity-gemini-3.6-flash",
36
+ descriptor: {
37
+ name: "Gemini 3.6 Flash (Antigravity, text only)",
38
+ thinkingLevelMap: { minimal: null, low: "low", medium: "medium", high: "high" },
39
+ contextWindow: 1_048_576,
40
+ maxTokens: 65_536,
41
+ },
42
+ routes: {
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
+ },
48
+ replay: { kind: "strip" },
49
+ response: { kind: "gemini-envelope", family: "gemini" },
50
+ }, {
51
+ publicId: "antigravity-gemini-3.1-pro",
52
+ descriptor: {
53
+ name: "Gemini 3.1 Pro (Antigravity, text only)",
54
+ thinkingLevelMap: { minimal: null, low: "low", medium: null, high: "high" },
55
+ contextWindow: 1_048_576,
56
+ maxTokens: 65_535,
57
+ },
58
+ routes: {
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
+ },
63
+ replay: { kind: "strip" },
64
+ response: { kind: "gemini-envelope", family: "gemini" },
65
+ }, {
66
+ publicId: "antigravity-claude-sonnet-4.6",
67
+ descriptor: {
68
+ name: "Claude Sonnet 4.6 (Antigravity, text only)",
69
+ thinkingLevelMap: { minimal: null, low: null, medium: null, high: "high" },
70
+ contextWindow: 250_000,
71
+ maxTokens: 64_000,
72
+ },
73
+ routes: {
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
+ },
77
+ replay: { kind: "strip" },
78
+ response: { kind: "gemini-envelope", family: "claude" },
79
+ }, {
80
+ publicId: "antigravity-claude-opus-4.6-thinking",
81
+ descriptor: {
82
+ name: "Claude Opus 4.6 Thinking (Antigravity, text only)",
83
+ thinkingLevelMap: { minimal: null, low: null, medium: null, high: "high" },
84
+ contextWindow: 250_000,
85
+ maxTokens: 64_000,
86
+ },
87
+ routes: {
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
+ },
91
+ replay: { kind: "strip" },
92
+ response: { kind: "gemini-envelope", family: "claude" },
93
+ }, {
94
+ publicId: "antigravity-gpt-oss-120b",
95
+ descriptor: {
96
+ name: "GPT-OSS 120B (Antigravity, text only)",
97
+ thinkingLevelMap: { minimal: null, low: null, medium: "medium", high: null },
98
+ contextWindow: 131_072,
99
+ maxTokens: 32_768,
100
+ },
101
+ routes: {
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
+ },
105
+ replay: { kind: "strip" },
106
+ response: { kind: "gemini-envelope", family: "gpt-oss" },
107
+ }]));
108
+ const lookup = new Map(CATALOG.map((entry) => [entry.publicId, entry]));
109
+ function disabled(reason) {
110
+ return { state: "disabled", contractRevision: 1, reason };
111
+ }
112
+ /** Constructs deterministic capability data for hermetic serializer tests only. */
113
+ export function createEnabledToolCapability(evidence) {
114
+ return freeze({ state: "enabled", contractRevision: 1, fixtureEvidence: evidence, directEvidence: evidence });
115
+ }
116
+ function freeze(value) {
117
+ if (typeof value === "object" && value !== null) {
118
+ Object.freeze(value);
119
+ for (const child of Object.values(value))
120
+ freeze(child);
121
+ }
122
+ return value;
123
+ }
124
+ export function defineCatalog(entries) {
125
+ const identities = new Set();
126
+ for (const entry of entries) {
127
+ if (identities.has(entry.publicId))
128
+ throw new Error(`Duplicate public model ID: ${entry.publicId}`);
129
+ identities.add(entry.publicId);
130
+ if (!Object.keys(entry.routes).length)
131
+ throw new Error(`Model has no routes: ${entry.publicId}`);
132
+ if (!entry.descriptor)
133
+ continue;
134
+ for (const [level, route] of Object.entries(entry.routes)) {
135
+ if (!route.thinking)
136
+ throw new Error(`Route has no thinking policy: ${entry.publicId}/${level}`);
137
+ if (level !== "off" && !Object.hasOwn(entry.descriptor.thinkingLevelMap, level)) {
138
+ throw new Error(`Route is outside descriptor map: ${entry.publicId}/${level}`);
139
+ }
140
+ if (level !== "off" && entry.descriptor.thinkingLevelMap[level] === null) {
141
+ throw new Error(`Route is hidden by descriptor: ${entry.publicId}/${level}`);
142
+ }
143
+ if (route.thinking.kind === "budget" && route.thinking.budget > 0 && Number.isFinite(route.thinking.budget) && route.thinking.budget + ANSWER_RESERVE > entry.descriptor.maxTokens) {
144
+ throw new Error(`Finite thinking budget does not leave answer reserve: ${entry.publicId}/${level}`);
145
+ }
146
+ }
147
+ }
148
+ return entries;
149
+ }
150
+ export function listCatalogEntries() {
151
+ return CATALOG;
152
+ }
153
+ export function getCatalogEntry(publicId) {
154
+ return typeof publicId === "string" ? lookup.get(publicId) : undefined;
155
+ }
156
+ export function resolveGenerationRoute(entry, reasoning) {
157
+ return resolveGenerationSelection(entry, reasoning).route;
158
+ }
159
+ export function resolveGenerationSelection(entry, reasoning) {
160
+ const level = reasoning === undefined || reasoning === "off" ? "off" : reasoning;
161
+ if (typeof level !== "string" || !entry.routes[level])
162
+ throw new Error("Unsupported reasoning level");
163
+ const typedLevel = level;
164
+ const configured = entry.routes[typedLevel];
165
+ const { tools, ...route } = configured;
166
+ return { level: typedLevel, route, tools: resolveToolCapability(tools, entry.publicId, typedLevel, route.wireModel) };
167
+ }
168
+ export function resolveToolCapability(capability, publicModelId, reasoning, wireModel) {
169
+ if (capability.state === "disabled")
170
+ return capability;
171
+ const evidence = capability.state === "enabled" ? [capability.fixtureEvidence, capability.directEvidence] : [capability.fixtureEvidence];
172
+ if (capability.contractRevision !== 1 || evidence.some((item) => item.publicModelId !== publicModelId || item.reasoning !== reasoning || item.wireModel !== wireModel)) {
173
+ return freeze(disabled("stale-or-conflicting-evidence"));
174
+ }
175
+ return capability;
176
+ }
177
+ export function toPiModelDescriptor(entry) {
178
+ return {
179
+ id: entry.publicId,
180
+ name: entry.descriptor.name,
181
+ reasoning: true,
182
+ thinkingLevelMap: { ...entry.descriptor.thinkingLevelMap },
183
+ input: ["text"],
184
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
185
+ contextWindow: entry.descriptor.contextWindow,
186
+ maxTokens: entry.descriptor.maxTokens,
187
+ };
188
+ }
package/dist/context.d.ts CHANGED
@@ -1,7 +1,5 @@
1
1
  import type { Context, Model, SimpleStreamOptions } from "@earendil-works/pi-ai";
2
- declare const WIRE_MODEL = "gemini-3.8-flash-tiered";
3
- declare const THINKING_LEVELS: readonly ["low", "medium", "high"];
4
- type ThinkingLevel = typeof THINKING_LEVELS[number];
2
+ import { type GenerationSelection } from "./catalog.ts";
5
3
  interface Part {
6
4
  text: string;
7
5
  thought?: true;
@@ -9,24 +7,34 @@ interface Part {
9
7
  }
10
8
  interface Content {
11
9
  role: "user" | "model";
12
- parts: Part[];
13
- }
14
- interface ThinkingConfig {
15
- thinkingLevel: ThinkingLevel;
16
- includeThoughts: boolean;
10
+ parts: object[];
17
11
  }
18
12
  export interface GenerationRequest {
19
13
  project: string;
20
- model: typeof WIRE_MODEL;
14
+ model: string;
21
15
  request: {
22
16
  contents: Content[];
23
17
  systemInstruction?: {
24
18
  parts: Part[];
25
19
  };
20
+ tools?: {
21
+ functionDeclarations: unknown;
22
+ }[];
23
+ toolConfig?: {
24
+ functionCallingConfig: {
25
+ mode: "AUTO" | "NONE";
26
+ };
27
+ };
26
28
  generationConfig: {
27
29
  temperature: number;
28
30
  maxOutputTokens: number;
29
- thinkingConfig: ThinkingConfig;
31
+ thinkingConfig?: {
32
+ thinkingLevel: "low" | "medium" | "high";
33
+ includeThoughts: boolean;
34
+ } | {
35
+ thinkingBudget: number;
36
+ includeThoughts: boolean;
37
+ };
30
38
  };
31
39
  };
32
40
  requestType: "agent";
@@ -42,5 +50,6 @@ export interface SerializeTextContextInput {
42
50
  }
43
51
  export declare class ContextSerializationError extends Error {
44
52
  }
53
+ export declare function serializeContext(input: SerializeTextContextInput, injectedSelection?: GenerationSelection, onRecovery?: (recoveryCount: number) => void): GenerationRequest;
45
54
  export declare function serializeTextContext(input: SerializeTextContextInput): GenerationRequest;
46
55
  export {};
package/dist/context.js CHANGED
@@ -1,12 +1,38 @@
1
+ import { getCatalogEntry, resolveGenerationSelection } from "./catalog.js";
2
+ import { hasToolContext, prepareToolContext, replayToolHistory } from "./tool-context.js";
1
3
  const MAX_TEXT_BYTES = 8 * 1024 * 1024;
2
- const MAX_OUTPUT_TOKENS = 65_536;
3
4
  const PROVIDER = "antigravity-guard";
4
- const PUBLIC_MODEL = "antigravity-gemini-3.8-flash";
5
- const WIRE_MODEL = "gemini-3.8-flash-tiered";
6
5
  const RESERVED_HEADERS = new Set(["authorization", "host", "content-type", "content-length"]);
7
6
  const THINKING_LEVELS = ["low", "medium", "high"];
8
7
  export class ContextSerializationError extends Error {
9
8
  }
9
+ export function serializeContext(input, injectedSelection, onRecovery) {
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 contents = replayToolHistory(isDenseArray(messages), (part, message) => messagePart(part, field(message, "role") === "assistant", field(message, "role") === "assistant" && entry.replay.kind === "same-public-model" && isSameProviderAndModel(message, entry.publicId)), onRecovery);
28
+ const { systemInstruction, generationConfig } = text.request;
29
+ return { ...text, request: {
30
+ contents,
31
+ ...(systemInstruction ? { systemInstruction } : {}),
32
+ ...(prepared ? { tools: [{ functionDeclarations: prepared.declarations }], toolConfig: { functionCallingConfig: { mode: prepared.mode } } } : {}),
33
+ generationConfig,
34
+ } };
35
+ }
10
36
  export function serializeTextContext(input) {
11
37
  try {
12
38
  if (!isRecord(input))
@@ -16,8 +42,13 @@ export function serializeTextContext(input) {
16
42
  const options = field(input, "options");
17
43
  const project = field(input, "project", true);
18
44
  const requestId = field(input, "requestId", true);
19
- if (!isRecord(context) || !isRecord(model) || field(model, "id", true) !== PUBLIC_MODEL || typeof project !== "string" || typeof requestId !== "string")
45
+ const entry = isRecord(model) ? getCatalogEntry(field(model, "id", true)) : undefined;
46
+ if (!isRecord(context) || !entry || typeof project !== "string" || typeof requestId !== "string")
20
47
  fail("Invalid text context.");
48
+ const selection = resolveGenerationSelection(entry, option(options, "reasoning"));
49
+ const route = selection.route;
50
+ if (isToolBearingContext(context) && selection.tools.state !== "enabled")
51
+ fail(capabilityError(entry.publicId, selection.level, selection.tools));
21
52
  const systemPrompt = field(context, "systemPrompt");
22
53
  const tools = field(context, "tools");
23
54
  if (systemPrompt !== undefined && typeof systemPrompt !== "string")
@@ -36,7 +67,7 @@ export function serializeTextContext(input) {
36
67
  if (role !== "user" && role !== "assistant")
37
68
  fail("Unsupported context role for this text-only provider.");
38
69
  const assistant = role === "assistant";
39
- const parts = messageParts(field(message, "content", true), assistant, assistant && isSameProviderAndModel(message));
70
+ const parts = messageParts(field(message, "content", true), assistant, assistant && entry.replay.kind === "same-public-model" && isSameProviderAndModel(message, entry.publicId));
40
71
  textBytes += parts.reduce((total, part) => total + byteLength(part.text), 0);
41
72
  if (textBytes > MAX_TEXT_BYTES)
42
73
  fail("Text context is too large.");
@@ -47,10 +78,13 @@ export function serializeTextContext(input) {
47
78
  const systemInstruction = systemPrompt === undefined ? undefined : { parts: [{ text: systemPrompt }] };
48
79
  const temperature = option(options, "temperature");
49
80
  const maxTokens = option(options, "maxTokens");
50
- return { project, model: WIRE_MODEL, request: { contents, ...(systemInstruction ? { systemInstruction } : {}), generationConfig: {
81
+ return { project, model: route.wireModel, request: { contents, ...(systemInstruction ? { systemInstruction } : {}), generationConfig: {
51
82
  temperature: typeof temperature === "number" ? temperature : 1,
52
- maxOutputTokens: typeof maxTokens === "number" ? maxTokens : 4096,
53
- thinkingConfig: resolveThinkingConfig(options),
83
+ maxOutputTokens: resolveOutputTokens(maxTokens, entry.descriptor.maxTokens, route.thinking),
84
+ ...(() => {
85
+ const thinkingConfig = serializeThinkingConfig(route.thinking);
86
+ return thinkingConfig ? { thinkingConfig } : {};
87
+ })(),
54
88
  } }, requestType: "agent", userAgent: "antigravity", requestId };
55
89
  }
56
90
  catch (error) {
@@ -59,64 +93,97 @@ export function serializeTextContext(input) {
59
93
  fail("Invalid text context.");
60
94
  }
61
95
  }
96
+ function capabilityError(publicId, reasoning, capability) {
97
+ const reason = capability.state === "disabled" ? capability.reason : capability.state;
98
+ return `PI_TOOL_CAPABILITY_NOT_ENABLED: ${publicId}/${reasoning} is ${capability.state} (${reason}).`;
99
+ }
100
+ function isToolBearingContext(context) {
101
+ const tools = field(context, "tools");
102
+ if (tools !== undefined && isDenseArray(tools).length > 0)
103
+ return true;
104
+ const messages = isDenseArray(field(context, "messages", true));
105
+ for (const message of messages) {
106
+ if (!isRecord(message))
107
+ continue;
108
+ if (field(message, "role") === "toolResult")
109
+ return true;
110
+ const content = field(message, "content");
111
+ if (!Array.isArray(content))
112
+ continue;
113
+ for (const part of content)
114
+ if (isRecord(part) && field(part, "type") === "toolCall")
115
+ return true;
116
+ }
117
+ return false;
118
+ }
62
119
  function messageParts(content, assistant, sameProviderAndModel) {
63
120
  if (typeof content === "string")
64
121
  return content ? [{ text: content }] : fail("A text conversation is required.");
65
122
  const parts = isDenseArray(content);
66
123
  if (!parts.length)
67
124
  fail("A text conversation is required.");
68
- return parts.map((part) => {
69
- if (!isRecord(part))
125
+ return parts.map((part) => messagePart(isRecord(part) ? part : fail("Only text context is supported by this provider."), assistant, sameProviderAndModel));
126
+ }
127
+ function messagePart(part, assistant, sameProviderAndModel) {
128
+ const type = field(part, "type", true);
129
+ if (type === "text") {
130
+ const text = field(part, "text", true);
131
+ if (typeof text !== "string")
70
132
  fail("Only text context is supported by this provider.");
71
- const type = field(part, "type", true);
72
- if (type === "text") {
73
- const text = field(part, "text", true);
74
- if (typeof text !== "string")
75
- fail("Only text context is supported by this provider.");
76
- const thoughtSignature = sameProviderAndModel ? validThoughtSignature(field(part, "textSignature")) : undefined;
77
- if (!text && !thoughtSignature)
78
- fail("Only text context is supported by this provider.");
79
- return { text, ...(thoughtSignature ? { thoughtSignature } : {}) };
80
- }
81
- if (type === "thinking" && assistant && sameProviderAndModel) {
82
- const text = field(part, "thinking", true);
83
- if (typeof text !== "string")
84
- fail("Only text context is supported by this provider.");
85
- const thoughtSignature = validThoughtSignature(field(part, "thinkingSignature"));
86
- if (!text && !thoughtSignature)
87
- fail("Only text context is supported by this provider.");
88
- return { thought: true, text, ...(thoughtSignature ? { thoughtSignature } : {}) };
89
- }
90
- if (type === "thinking" && assistant) {
91
- const text = field(part, "thinking", true);
92
- if (typeof text !== "string" || !text)
93
- fail("Only text context is supported by this provider.");
94
- return { text };
95
- }
96
- fail("Only text context is supported by this provider.");
97
- });
133
+ const thoughtSignature = sameProviderAndModel ? validThoughtSignature(field(part, "textSignature")) : undefined;
134
+ if (!text && !thoughtSignature)
135
+ fail("Only text context is supported by this provider.");
136
+ return { text, ...(thoughtSignature ? { thoughtSignature } : {}) };
137
+ }
138
+ if (type === "thinking" && assistant && sameProviderAndModel) {
139
+ const text = field(part, "thinking", true);
140
+ if (typeof text !== "string")
141
+ fail("Only text context is supported by this provider.");
142
+ const thoughtSignature = validThoughtSignature(field(part, "thinkingSignature"));
143
+ if (!text && !thoughtSignature)
144
+ fail("Only text context is supported by this provider.");
145
+ return { thought: true, text, ...(thoughtSignature ? { thoughtSignature } : {}) };
146
+ }
147
+ if (type === "thinking" && assistant) {
148
+ const text = field(part, "thinking", true);
149
+ if (typeof text !== "string" || !text)
150
+ fail("Only text context is supported by this provider.");
151
+ return { text };
152
+ }
153
+ fail("Only text context is supported by this provider.");
98
154
  }
99
- function isSameProviderAndModel(message) {
100
- return field(message, "provider") === PROVIDER && field(message, "model") === PUBLIC_MODEL;
155
+ function isSameProviderAndModel(message, publicId) {
156
+ return field(message, "provider") === PROVIDER && field(message, "model") === publicId;
157
+ }
158
+ function serializeThinkingConfig(thinking) {
159
+ if (thinking.kind === "native-level")
160
+ return { thinkingLevel: thinking.thinkingLevel, includeThoughts: thinking.includeThoughts };
161
+ if (thinking.kind === "budget")
162
+ return { thinkingBudget: thinking.budget, includeThoughts: thinking.includeThoughts };
163
+ return undefined;
164
+ }
165
+ function resolveOutputTokens(maxTokens, maxAllowed, thinking) {
166
+ if (typeof maxTokens === "number") {
167
+ if (maxTokens > maxAllowed)
168
+ fail(`maxTokens must be a positive integer no greater than ${maxAllowed}.`);
169
+ if (thinking.kind === "budget" && thinking.budget > 0 && maxTokens <= thinking.budget)
170
+ fail("maxTokens must exceed the selected thinking budget.");
171
+ return maxTokens;
172
+ }
173
+ return thinking.kind === "budget" && thinking.budget > 0 ? Math.max(4096, thinking.budget + 1024) : 4096;
101
174
  }
102
175
  function validThoughtSignature(value) {
103
176
  if (typeof value !== "string" || !value || value.length % 4 !== 0)
104
177
  return undefined;
105
178
  return /^[A-Za-z0-9+/]+={0,2}$/.test(value) ? value : undefined;
106
179
  }
107
- function resolveThinkingConfig(options) {
108
- const reasoning = option(options, "reasoning");
109
- if (isThinkingLevel(reasoning))
110
- return { thinkingLevel: reasoning, includeThoughts: true };
111
- return { thinkingLevel: "low", includeThoughts: false };
112
- }
113
180
  function validateOptions(options) {
114
181
  if (options === undefined)
115
182
  return;
116
183
  if (!isRecord(options))
117
184
  fail("Invalid generation options.");
118
185
  const reasoning = option(options, "reasoning"), budgets = option(options, "thinkingBudgets"), deferred = option(options, "deferred"), toolChoice = option(options, "toolChoice"), sampling = option(options, "samplingParams"), temperature = option(options, "temperature"), maxTokens = option(options, "maxTokens"), headers = option(options, "headers");
119
- if (reasoning !== undefined && reasoning !== "off" && !isThinkingLevel(reasoning))
186
+ if (reasoning !== undefined && reasoning !== "off" && !isThinkingLevel(reasoning) && reasoning !== "minimal")
120
187
  fail("Unsupported reasoning level.");
121
188
  if (budgets !== undefined)
122
189
  fail("Thinking budgets are not supported.");
@@ -128,7 +195,7 @@ function validateOptions(options) {
128
195
  fail("Custom generation options are not supported.");
129
196
  if (temperature !== undefined && (typeof temperature !== "number" || !Number.isFinite(temperature) || temperature < 0 || temperature > 2))
130
197
  fail("Temperature must be finite and between 0 and 2.");
131
- if (maxTokens !== undefined && (typeof maxTokens !== "number" || !Number.isInteger(maxTokens) || maxTokens <= 0 || maxTokens > MAX_OUTPUT_TOKENS))
198
+ if (maxTokens !== undefined && (typeof maxTokens !== "number" || !Number.isInteger(maxTokens) || maxTokens <= 0 || maxTokens > 65_536))
132
199
  fail("maxTokens must be a positive integer no greater than 65536.");
133
200
  if (headers !== undefined && !isRecord(headers))
134
201
  fail("Invalid generation options.");
package/dist/provider.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { ANTIGRAVITY_ENDPOINTS } from "@benjamolina/antigravity-guard-core";
2
+ import { listCatalogEntries, toPiModelDescriptor } from "./catalog.js";
2
3
  import { createPiOAuthLifecycle } from "./oauth.js";
3
4
  import { createPiLifecycleStream, executeStreamTransport } from "./stream.js";
4
5
  const PROVIDER = "antigravity-guard";
5
6
  const API = "antigravity-guard-sse";
6
- const MODEL = "antigravity-gemini-3.8-flash";
7
7
  const INVALID_CREDENTIALS = "Antigravity credentials are invalid. Run /login antigravity-guard.";
8
8
  export function registerAntigravityProvider(pi) {
9
9
  const oauth = createPiOAuthLifecycle({ fetch: globalThis.fetch, now: Date.now });
@@ -11,16 +11,7 @@ export function registerAntigravityProvider(pi) {
11
11
  name: "Antigravity Guard",
12
12
  baseUrl: ANTIGRAVITY_ENDPOINTS.daily,
13
13
  api: API,
14
- models: [{
15
- id: MODEL,
16
- name: "Gemini 3.8 Flash (Antigravity, text only)",
17
- reasoning: true,
18
- thinkingLevelMap: { minimal: null, low: "low", medium: "medium", high: "high" },
19
- input: ["text"],
20
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
21
- contextWindow: 1_048_576,
22
- maxTokens: 65_536,
23
- }],
14
+ models: listCatalogEntries().map(toPiModelDescriptor),
24
15
  oauth: {
25
16
  name: "Antigravity Guard",
26
17
  isSubscription: true,
@@ -1,4 +1,5 @@
1
- export type ResponseSemantic = TextSemantic | ThinkingSemantic | FinishSemantic | UsageSemantic;
1
+ import type { JsonObject } from "./tool-contract.ts";
2
+ export type ResponseSemantic = TextSemantic | ThinkingSemantic | ToolCallSemantic | FinishSemantic | UsageSemantic;
2
3
  export interface TextSemantic {
3
4
  type: "text";
4
5
  text: string;
@@ -9,9 +10,18 @@ export interface ThinkingSemantic {
9
10
  thinking: string;
10
11
  signature?: string;
11
12
  }
13
+ export interface ToolCallSemantic {
14
+ type: "toolCall";
15
+ callIndex: number;
16
+ id: string;
17
+ name: string;
18
+ arguments: JsonObject;
19
+ argumentsJson: string;
20
+ signature?: string;
21
+ }
12
22
  export interface FinishSemantic {
13
23
  type: "finish";
14
- reason: "stop" | "length";
24
+ reason: "stop" | "length" | "toolUse";
15
25
  }
16
26
  export interface UsageSemantic {
17
27
  type: "usage";
@@ -22,16 +32,31 @@ export interface UsageSemantic {
22
32
  reasoning: number;
23
33
  total: number;
24
34
  }
35
+ export type ToolResponsePolicy = {
36
+ kind: "reject";
37
+ } | {
38
+ kind: "accept";
39
+ declaredNames: ReadonlySet<string>;
40
+ };
25
41
  export declare class ResponseSemanticError extends Error {
26
42
  }
27
43
  export declare class ResponseSemantics {
44
+ readonly policy: ToolResponsePolicy;
28
45
  private finished;
29
- private content;
30
46
  private done;
47
+ private content;
48
+ private calls;
49
+ private readonly ids;
50
+ private pendingFinish;
31
51
  private usage;
52
+ constructor(policy?: ToolResponsePolicy);
32
53
  push(record: string): ResponseSemantic[];
33
- finish(): void;
34
- addContent(text: string): void;
35
- addFinish(): void;
54
+ finish(): FinishSemantic;
36
55
  private updateUsage;
56
+ addContent(text: string): void;
57
+ addCall(): void;
58
+ setFinish(reason: FinishSemantic["reason"]): void;
59
+ hasId(id: string): boolean;
60
+ addId(id: string): void;
61
+ callIndex(): number;
37
62
  }