@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.
@@ -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
  }
package/dist/response.js CHANGED
@@ -1,10 +1,18 @@
1
+ import { canonicalJson } from "./tool-contract.js";
1
2
  export class ResponseSemanticError extends Error {
2
3
  }
3
4
  export class ResponseSemantics {
5
+ policy;
4
6
  finished = false;
5
- content = false;
6
7
  done = false;
8
+ content = false;
9
+ calls = 0;
10
+ ids = new Set();
11
+ pendingFinish;
7
12
  usage = { prompt: 0, cacheRead: 0, output: 0, reasoning: 0 };
13
+ constructor(policy = { kind: "reject" }) {
14
+ this.policy = policy;
15
+ }
8
16
  push(record) {
9
17
  if (this.done)
10
18
  throw new ResponseSemanticError("Response data arrived after [DONE].");
@@ -37,9 +45,8 @@ export class ResponseSemantics {
37
45
  throw new ResponseSemanticError("Response ended without a finish reason.");
38
46
  if (!this.content)
39
47
  throw new ResponseSemanticError("Response ended without content.");
48
+ return { type: "finish", reason: this.pendingFinish };
40
49
  }
41
- addContent(text) { this.content = this.content || text.length > 0; }
42
- addFinish() { this.finished = true; }
43
50
  updateUsage(value) {
44
51
  const prompt = count(value, "promptTokenCount", this.usage.prompt);
45
52
  const cacheRead = count(value, "cachedContentTokenCount", this.usage.cacheRead);
@@ -52,74 +59,135 @@ export class ResponseSemantics {
52
59
  this.usage = { prompt, cacheRead, output, reasoning };
53
60
  return { type: "usage", input: prompt - cacheRead, output, cacheRead, cacheWrite: 0, reasoning, total: prompt + output };
54
61
  }
62
+ addContent(text) { this.content = this.content || text.length > 0; }
63
+ addCall() { this.content = true; this.calls++; }
64
+ setFinish(reason) { this.finished = true; this.pendingFinish = reason; }
65
+ hasId(id) { return this.ids.has(id); }
66
+ addId(id) { this.ids.add(id); }
67
+ callIndex() { return this.calls; }
55
68
  }
56
69
  function candidate(value, events, semantics) {
57
70
  if (!Array.isArray(value) || value.length !== 1)
58
71
  throw new ResponseSemanticError("Antigravity returned invalid candidates.");
59
- const candidate = value[0];
60
- if (candidate === undefined)
72
+ const item = value[0];
73
+ if (item === undefined)
61
74
  return;
62
- const index = field(candidate, "index");
75
+ const index = field(item, "index");
63
76
  if (index !== undefined && index !== 0)
64
77
  throw new ResponseSemanticError("Antigravity returned invalid candidates.");
65
- const content = field(candidate, "content");
78
+ const content = field(item, "content");
79
+ const local = [];
80
+ const ids = [];
81
+ let callCount = 0;
66
82
  if (content !== undefined) {
67
83
  const parts = field(content, "parts");
68
84
  if (!Array.isArray(parts))
69
85
  throw new ResponseSemanticError("Antigravity returned invalid content.");
86
+ const explicitIds = new Set();
87
+ for (const part of parts) {
88
+ const call = field(part, "functionCall");
89
+ if (call !== undefined) {
90
+ const id = field(call, "id");
91
+ if (typeof id === "string")
92
+ explicitIds.add(id);
93
+ }
94
+ }
70
95
  for (const part of parts) {
71
- const text = field(part, "text");
72
- const thought = field(part, "thought");
73
- if (typeof text !== "string" || (thought !== undefined && typeof thought !== "boolean") || Object.keys(part).some((name) => name !== "text" && name !== "thought" && name !== "thoughtSignature"))
74
- throw new ResponseSemanticError("Antigravity returned unsupported content.");
75
- const signature = validThoughtSignature(field(part, "thoughtSignature"));
76
- semantics.addContent(text);
77
- if (thought === true)
78
- events.push({ type: "thinking", thinking: text, ...(signature ? { signature } : {}) });
79
- else
80
- events.push({ type: "text", text, ...(signature ? { signature } : {}) });
96
+ if (field(part, "functionCall") !== undefined) {
97
+ const call = toolCall(part, semantics, ids, explicitIds, callCount++);
98
+ ids.push(call.id);
99
+ local.push(call);
100
+ }
101
+ else {
102
+ const text = field(part, "text");
103
+ const thought = field(part, "thought");
104
+ if (typeof text !== "string" || (thought !== undefined && typeof thought !== "boolean") || keys(part, ["text", "thought", "thoughtSignature"]))
105
+ throw new ResponseSemanticError("Antigravity returned unsupported content.");
106
+ const signature = validThoughtSignature(field(part, "thoughtSignature"));
107
+ local.push(thought === true ? { type: "thinking", thinking: text, ...(signature ? { signature } : {}) } : { type: "text", text, ...(signature ? { signature } : {}) });
108
+ }
81
109
  }
82
110
  }
83
- const finishReason = field(candidate, "finishReason");
111
+ const finishReason = field(item, "finishReason");
84
112
  if (finishReason !== undefined) {
85
- if (finishReason !== "STOP" && finishReason !== "MAX_TOKENS")
113
+ const hasCalls = semantics.callIndex() + callCount > 0;
114
+ const reason = hasCalls ? "toolUse" : finishReason === "STOP" ? "stop" : finishReason === "MAX_TOKENS" ? "length" : undefined;
115
+ if (!reason)
86
116
  throw new ResponseSemanticError("Antigravity returned an unsupported finish reason.");
87
- semantics.addFinish();
88
- events.push({ type: "finish", reason: finishReason === "STOP" ? "stop" : "length" });
117
+ semantics.setFinish(reason);
89
118
  }
90
- }
91
- function validThoughtSignature(value) {
92
- if (typeof value !== "string" || !value || value.length % 4 !== 0)
93
- return undefined;
94
- return /^[A-Za-z0-9+/]+={0,2}$/.test(value) ? value : undefined;
95
- }
96
- function validateMetadata(value) {
97
- for (const name of ["responseId", "modelVersion"]) {
98
- const metadata = field(value, name);
99
- if (metadata !== undefined && (typeof metadata !== "string" || !metadata))
100
- throw new ResponseSemanticError("Antigravity returned invalid metadata.");
119
+ for (const id of ids)
120
+ semantics.addId(id);
121
+ for (const event of local) {
122
+ if (event.type === "text")
123
+ semantics.addContent(event.text);
124
+ else if (event.type === "thinking")
125
+ semantics.addContent(event.thinking);
126
+ else
127
+ semantics.addCall();
128
+ events.push(event);
101
129
  }
102
130
  }
103
- function count(value, name, fallback) {
104
- const valueAtName = field(value, name);
105
- if (valueAtName === undefined)
106
- return fallback;
107
- if (typeof valueAtName !== "number" || !Number.isSafeInteger(valueAtName) || valueAtName < 0)
108
- throw new ResponseSemanticError("Antigravity returned invalid usage.");
109
- return valueAtName;
110
- }
111
- function parse(record) {
131
+ function toolCall(part, semantics, ids, explicitIds, offset) {
132
+ if (semantics.policy.kind !== "accept" || keys(part, ["functionCall", "thoughtSignature"]))
133
+ throw new ResponseSemanticError("Antigravity returned unsupported content.");
134
+ const call = field(part, "functionCall");
135
+ if (keys(call, ["id", "name", "args"]))
136
+ throw new ResponseSemanticError("Antigravity returned unsupported content.");
137
+ const suppliedId = field(call, "id");
138
+ const id = suppliedId === undefined ? localCallId(semantics, ids, explicitIds, offset) : suppliedId;
139
+ const name = field(call, "name");
140
+ const args = field(call, "args");
141
+ if (typeof id !== "string" || !id.trim() || typeof name !== "string" || !semantics.policy.declaredNames.has(name) || semantics.hasId(id) || ids.includes(id))
142
+ throw new ResponseSemanticError("Antigravity returned invalid function call.");
143
+ let argumentsValue;
112
144
  try {
113
- return JSON.parse(record);
145
+ const canonical = canonicalJson(args, name, "$");
146
+ if (Array.isArray(canonical) || canonical === null)
147
+ throw new Error();
148
+ argumentsValue = canonical;
114
149
  }
115
150
  catch {
116
- throw new ResponseSemanticError("Antigravity returned invalid JSON.");
151
+ throw new ResponseSemanticError("Antigravity returned invalid function call.");
117
152
  }
153
+ const argumentsJson = JSON.stringify(argumentsValue);
154
+ if (new TextEncoder().encode(argumentsJson).byteLength > 1024 * 1024 || depth(argumentsValue) > 64)
155
+ throw new ResponseSemanticError("Antigravity returned invalid function call.");
156
+ const signature = validThoughtSignature(field(part, "thoughtSignature"));
157
+ return { type: "toolCall", callIndex: semantics.callIndex() + offset, id, name, arguments: argumentsValue, argumentsJson, ...(signature ? { signature } : {}) };
118
158
  }
119
- function hasError(value) { return field(value, "error") !== undefined; }
120
- function field(value, name) {
121
- if (typeof value !== "object" || value === null || Array.isArray(value))
122
- throw new ResponseSemanticError("Antigravity returned an invalid response.");
123
- const descriptor = Object.getOwnPropertyDescriptor(value, name);
124
- return descriptor && "value" in descriptor ? descriptor.value : undefined;
159
+ function localCallId(semantics, ids, explicitIds, offset) {
160
+ let suffix = semantics.callIndex() + offset + 1;
161
+ let id = `pi-gemini-call-${suffix}`;
162
+ while (semantics.hasId(id) || ids.includes(id) || explicitIds.has(id)) {
163
+ suffix++;
164
+ id = `pi-gemini-call-${suffix}`;
165
+ }
166
+ return id;
125
167
  }
168
+ function depth(value, level = 0) {
169
+ let maximum = level;
170
+ for (const item of Object.values(value))
171
+ maximum = Math.max(maximum, depthValue(item, level + 1));
172
+ return maximum;
173
+ }
174
+ function depthValue(value, level) { return value && typeof value === "object" ? depth(value, level) : level; }
175
+ function keys(value, allowed) { return typeof value !== "object" || value === null || Array.isArray(value) || Object.keys(value).some((name) => !allowed.includes(name)); }
176
+ function validThoughtSignature(value) { return typeof value === "string" && value && value.length % 4 === 0 && /^[A-Za-z0-9+/]+={0,2}$/.test(value) ? value : undefined; }
177
+ function validateMetadata(value) { for (const name of ["responseId", "modelVersion"]) {
178
+ const metadata = field(value, name);
179
+ if (metadata !== undefined && (typeof metadata !== "string" || !metadata))
180
+ throw new ResponseSemanticError("Antigravity returned invalid metadata.");
181
+ } }
182
+ function count(value, name, fallback) { const item = field(value, name); if (item === undefined)
183
+ return fallback; if (typeof item !== "number" || !Number.isSafeInteger(item) || item < 0)
184
+ throw new ResponseSemanticError("Antigravity returned invalid usage."); return item; }
185
+ function parse(record) { try {
186
+ return JSON.parse(record);
187
+ }
188
+ catch {
189
+ throw new ResponseSemanticError("Antigravity returned invalid JSON.");
190
+ } }
191
+ function hasError(value) { return field(value, "error") !== undefined; }
192
+ function field(value, name) { if (typeof value !== "object" || value === null || Array.isArray(value))
193
+ throw new ResponseSemanticError("Antigravity returned an invalid response."); const descriptor = Object.getOwnPropertyDescriptor(value, name); return descriptor && "value" in descriptor ? descriptor.value : undefined; }
package/dist/stream.d.ts CHANGED
@@ -1,10 +1,37 @@
1
1
  import type { Context, Model, SimpleStreamOptions } from "@earendil-works/pi-ai";
2
+ import { type GenerationSelection } from "./catalog.ts";
3
+ import { type ToolReplayMode } from "./tool-context.ts";
2
4
  import type { ResponseSemantic } from "./response.ts";
3
- type StreamErrorKind = "aborted" | "access" | "model" | "quota" | "response" | "transport" | "callback";
5
+ type StreamErrorKind = "aborted" | "access" | "capability" | "model" | "quota" | "preflight" | "response" | "transport" | "callback";
6
+ export interface ToolStreamDiagnostics {
7
+ readonly publicModelId: string;
8
+ readonly reasoning: string;
9
+ readonly capabilityState: string;
10
+ readonly preflight?: "accepted" | string;
11
+ readonly preflightCategory?: string;
12
+ readonly preflightPath?: string;
13
+ readonly replayMode: ToolReplayMode;
14
+ readonly recoveryCount: number;
15
+ readonly userMessageCount: number;
16
+ readonly assistantMessageCount: number;
17
+ readonly toolResultMessageCount: number;
18
+ readonly assistantToolCallBlockCount: number;
19
+ readonly declaredToolCount: number;
20
+ readonly failure?: {
21
+ readonly kind: StreamErrorKind;
22
+ readonly status?: number;
23
+ };
24
+ }
25
+ type ToolDiagnosticSemantic = {
26
+ type: "toolDiagnostics";
27
+ details: ToolStreamDiagnostics;
28
+ };
29
+ type StreamSemantic = ResponseSemantic | ToolDiagnosticSemantic;
4
30
  export declare class StreamTransportError extends Error {
5
31
  readonly kind: StreamErrorKind;
6
32
  readonly status?: number | undefined;
7
- constructor(kind: StreamErrorKind, message: string, status?: number | undefined);
33
+ readonly details?: ToolStreamDiagnostics | undefined;
34
+ constructor(kind: StreamErrorKind, message: string, status?: number | undefined, details?: ToolStreamDiagnostics | undefined);
8
35
  }
9
36
  export interface StreamTransportInput {
10
37
  accessToken: string;
@@ -16,9 +43,10 @@ export interface StreamTransportInput {
16
43
  model: Model<string>;
17
44
  projectId: string;
18
45
  now: () => number;
19
- onSemantic: (semantic: ResponseSemantic) => void | Promise<void>;
46
+ onSemantic: (semantic: StreamSemantic) => void | Promise<void>;
20
47
  platform: string;
21
48
  requestId: string;
49
+ selection?: GenerationSelection;
22
50
  signal?: AbortSignal;
23
51
  timeoutMs?: number;
24
52
  }
@@ -27,7 +55,7 @@ export interface PiStreamLifecycleInput {
27
55
  now: () => number;
28
56
  signal?: AbortSignal;
29
57
  runTransport: (input: {
30
- onSemantic: (semantic: ResponseSemantic) => void;
58
+ onSemantic: (semantic: StreamSemantic) => void;
31
59
  signal: AbortSignal;
32
60
  }) => Promise<void>;
33
61
  }
package/dist/stream.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import { calculateCost, createAssistantMessageEventStream } from "@earendil-works/pi-ai";
2
2
  import { ANTIGRAVITY_ENDPOINTS } from "@benjamolina/antigravity-guard-core";
3
- import { getCatalogEntry } from "./catalog.js";
4
- import { serializeTextContext } from "./context.js";
3
+ import { getCatalogEntry, resolveGenerationSelection } from "./catalog.js";
4
+ import { ContextSerializationError, serializeContext } from "./context.js";
5
+ import { ToolPreflightError } from "./tool-contract.js";
6
+ import { hasToolContext } from "./tool-context.js";
5
7
  import { ResponseSemanticError, ResponseSemantics } from "./response.js";
6
8
  import { SseFrameError, SseFramer } from "./sse.js";
7
9
  const TOTAL_TIMEOUT_MS = 120_000;
@@ -15,10 +17,12 @@ const LOCAL_ERRORS = new WeakSet();
15
17
  export class StreamTransportError extends Error {
16
18
  kind;
17
19
  status;
18
- constructor(kind, message, status) {
20
+ details;
21
+ constructor(kind, message, status, details) {
19
22
  super(message);
20
23
  this.kind = kind;
21
24
  this.status = status;
25
+ this.details = details;
22
26
  }
23
27
  }
24
28
  export function createPiLifecycleStream(input) {
@@ -36,6 +40,8 @@ export function createPiLifecycleStream(input) {
36
40
  timestamp: input.now(),
37
41
  };
38
42
  let complete = false;
43
+ let sawToolCall = false;
44
+ let toolDiagnostics;
39
45
  let textStarted = false;
40
46
  let currentBlock;
41
47
  const closeBlock = () => {
@@ -49,15 +55,24 @@ export function createPiLifecycleStream(input) {
49
55
  currentBlock = undefined;
50
56
  };
51
57
  let removeAbort = () => { };
52
- const finalize = (reason, errorMessage) => {
58
+ const finalize = (reason, errorMessage, details, error) => {
53
59
  if (complete)
54
60
  return;
55
61
  complete = true;
56
62
  removeAbort();
57
- if (reason === "error" || reason === "aborted")
63
+ if (reason === "error" || reason === "aborted") {
58
64
  controller.abort();
65
+ for (let index = output.content.length - 1; index >= 0; index--) {
66
+ if (output.content[index]?.type === "toolCall")
67
+ output.content.splice(index, 1);
68
+ }
69
+ }
59
70
  output.stopReason = reason;
60
- if (reason === "stop" || reason === "length") {
71
+ const diagnostic = details ?? toolDiagnostics;
72
+ const failure = error && isLocalStreamError(error) ? diagnosticFailure(error) : undefined;
73
+ if (sawToolCall || diagnostic)
74
+ output.diagnostics = [{ type: "antigravity-guard.tools", timestamp: input.now(), details: { ...diagnostic, ...(failure ? { failure } : {}), terminal: reason } }];
75
+ if (reason === "stop" || reason === "length" || reason === "toolUse") {
61
76
  closeBlock();
62
77
  stream.push({ type: "done", reason, message: output });
63
78
  }
@@ -70,6 +85,21 @@ export function createPiLifecycleStream(input) {
70
85
  const onSemantic = (semantic) => {
71
86
  if (complete)
72
87
  return;
88
+ if (semantic.type === "toolDiagnostics") {
89
+ toolDiagnostics = semantic.details;
90
+ return;
91
+ }
92
+ if (semantic.type === "toolCall") {
93
+ sawToolCall = true;
94
+ closeBlock();
95
+ const toolCall = { type: "toolCall", id: semantic.id, name: semantic.name, arguments: semantic.arguments, ...(semantic.signature ? { thoughtSignature: semantic.signature } : {}) };
96
+ output.content.push(toolCall);
97
+ const contentIndex = output.content.length - 1;
98
+ stream.push({ type: "toolcall_start", contentIndex, partial: output });
99
+ stream.push({ type: "toolcall_delta", contentIndex, delta: semantic.argumentsJson, partial: output });
100
+ stream.push({ type: "toolcall_end", contentIndex, toolCall, partial: output });
101
+ return;
102
+ }
73
103
  if (isContentSemantic(semantic)) {
74
104
  if (!currentBlock || currentBlock.type !== semantic.type) {
75
105
  closeBlock();
@@ -124,7 +154,7 @@ export function createPiLifecycleStream(input) {
124
154
  };
125
155
  stream.push({ type: "start", partial: output });
126
156
  if (input.signal) {
127
- const abort = () => finalize("aborted");
157
+ const abort = () => finalize("aborted", undefined, undefined, streamError("aborted", "Generation was cancelled."));
128
158
  input.signal.addEventListener("abort", abort, { once: true });
129
159
  removeAbort = () => input.signal?.removeEventListener("abort", abort);
130
160
  if (input.signal.aborted)
@@ -132,7 +162,7 @@ export function createPiLifecycleStream(input) {
132
162
  }
133
163
  if (!complete)
134
164
  void Promise.resolve().then(() => input.runTransport({ onSemantic, signal })).then(() => { if (!complete)
135
- finalize(signal.aborted ? "aborted" : "error"); }, (error) => finalize(signal.aborted || input.signal?.aborted ? "aborted" : "error", isLocalStreamError(error) ? error.message : undefined));
165
+ finalize(signal.aborted ? "aborted" : "error"); }, (error) => finalize(signal.aborted || input.signal?.aborted ? "aborted" : "error", isLocalStreamError(error) ? error.message : undefined, isLocalStreamError(error) ? error.details : undefined, isLocalStreamError(error) ? error : undefined));
136
166
  return stream;
137
167
  }
138
168
  function isContentSemantic(semantic) {
@@ -143,8 +173,14 @@ export async function executeStreamTransport(input) {
143
173
  const signal = totalSignal(input);
144
174
  const headers = requestHeaders(input);
145
175
  let responseBody = null;
176
+ let selection;
177
+ let replayDiagnostics = emptyReplayDiagnostics();
146
178
  try {
147
- const original = serializeTextContext({ context: input.context, model: input.model, options: input.generationOptions, project: input.projectId, requestId: input.requestId });
179
+ const entry = getCatalogEntry(input.model.id);
180
+ selection = input.selection ?? resolveGenerationSelection(entry, input.generationOptions?.reasoning);
181
+ const original = serializeContext({ context: input.context, model: input.model, options: input.generationOptions, project: input.projectId, requestId: input.requestId }, selection, (diagnostics) => { replayDiagnostics = diagnostics; });
182
+ if (hasToolContext(input.context))
183
+ await deliver(input.onSemantic, { type: "toolDiagnostics", details: toolDiagnostics(entry.publicId, selection, replayDiagnostics, "accepted") });
148
184
  const payload = await payloadHook(input, original, signal);
149
185
  const response = await abortable(input.fetch(ENDPOINT, { method: "POST", redirect: "error", headers, body: JSON.stringify(payload), signal }), signal);
150
186
  await responseHook(input, response, signal);
@@ -155,7 +191,7 @@ export async function executeStreamTransport(input) {
155
191
  await boundedBody(response.body, signal);
156
192
  throw streamError("response", "Antigravity did not return an SSE response.");
157
193
  }
158
- await consume(response.body, signal, input.onSemantic, inactivityTimeout(input));
194
+ await consume(response.body, signal, input.onSemantic, inactivityTimeout(input), responsePolicy(original, selection));
159
195
  }
160
196
  catch (error) {
161
197
  void responseBody?.cancel().catch(() => undefined);
@@ -163,6 +199,13 @@ export async function executeStreamTransport(input) {
163
199
  throw error;
164
200
  if (signal.aborted)
165
201
  throw streamError("aborted", "Generation was cancelled.");
202
+ if (error instanceof ToolPreflightError && selection) {
203
+ const details = toolDiagnostics(getCatalogEntry(input.model.id).publicId, selection, replayDiagnostics, error.code, error.path);
204
+ throw streamError("preflight", error.message, undefined, details);
205
+ }
206
+ if (error instanceof ContextSerializationError && error.message.startsWith("PI_TOOL_CAPABILITY_NOT_ENABLED") && selection) {
207
+ throw streamError("capability", error.message, undefined, toolDiagnostics(getCatalogEntry(input.model.id).publicId, selection, replayDiagnostics, "PI_TOOL_CAPABILITY_NOT_ENABLED"));
208
+ }
166
209
  if (error instanceof SseFrameError || error instanceof ResponseSemanticError)
167
210
  throw streamError("response", "Antigravity returned an invalid stream.");
168
211
  throw streamError("transport", "Antigravity generation request failed.");
@@ -172,6 +215,51 @@ function validateInput(input) {
172
215
  if (!getCatalogEntry(input.model.id) || input.model.api !== API)
173
216
  throw streamError("response", "The selected Antigravity model or API is unsupported.");
174
217
  }
218
+ function toolDiagnostics(publicModelId, selection, replayDiagnostics, preflight, preflightPath) {
219
+ return {
220
+ publicModelId,
221
+ reasoning: selection.level,
222
+ capabilityState: selection.tools.state,
223
+ replayMode: replayDiagnostics.replayMode,
224
+ recoveryCount: replayDiagnostics.recoveryCount,
225
+ userMessageCount: replayDiagnostics.userMessageCount,
226
+ assistantMessageCount: replayDiagnostics.assistantMessageCount,
227
+ toolResultMessageCount: replayDiagnostics.toolResultMessageCount,
228
+ assistantToolCallBlockCount: replayDiagnostics.assistantToolCallBlockCount,
229
+ declaredToolCount: replayDiagnostics.declaredToolCount,
230
+ preflight,
231
+ ...(preflight === "accepted" ? {} : { preflightCategory: preflightCategory(preflight), ...(preflightPath ? { preflightPath } : {}) }),
232
+ };
233
+ }
234
+ function emptyReplayDiagnostics() {
235
+ return {
236
+ replayMode: "none",
237
+ recoveryCount: 0,
238
+ userMessageCount: 0,
239
+ assistantMessageCount: 0,
240
+ toolResultMessageCount: 0,
241
+ assistantToolCallBlockCount: 0,
242
+ declaredToolCount: 0,
243
+ };
244
+ }
245
+ function preflightCategory(code) {
246
+ if (code.includes("SCHEMA"))
247
+ return "schema";
248
+ if (code.includes("CHOICE"))
249
+ return "tool-choice";
250
+ if (code.includes("DECLARATION") || code.includes("CONSTRAINED"))
251
+ return "declaration";
252
+ if (code.includes("RESULT") || code.includes("CALL") || code.includes("HISTORY"))
253
+ return "history";
254
+ return "capability";
255
+ }
256
+ function responsePolicy(payload, selection) {
257
+ const declarations = payload.request.tools?.[0]?.functionDeclarations;
258
+ if (selection.tools.state !== "enabled" || payload.request.toolConfig?.functionCallingConfig.mode === "NONE" || !Array.isArray(declarations))
259
+ return { kind: "reject" };
260
+ const names = declarations.map((declaration) => Object.getOwnPropertyDescriptor(declaration, "name")?.value);
261
+ return names.every((name) => typeof name === "string") ? { kind: "accept", declaredNames: new Set(names) } : { kind: "reject" };
262
+ }
175
263
  async function payloadHook(input, payload, signal) {
176
264
  try {
177
265
  const replacement = await abortable(Promise.resolve().then(() => input.generationOptions?.onPayload?.(payload, input.model)), signal);
@@ -209,10 +297,10 @@ function requestHeaders(input) {
209
297
  "User-Agent": ANTIGRAVITY_USER_AGENT,
210
298
  };
211
299
  }
212
- async function consume(body, signal, onSemantic, inactivityMs) {
300
+ async function consume(body, signal, onSemantic, inactivityMs, policy) {
213
301
  const reader = body.getReader();
214
302
  const framer = new SseFramer();
215
- const semantics = new ResponseSemantics();
303
+ const semantics = new ResponseSemantics(policy);
216
304
  try {
217
305
  while (true) {
218
306
  const next = await abortable(reader.read(), AbortSignal.any([signal, AbortSignal.timeout(inactivityMs)]));
@@ -225,7 +313,7 @@ async function consume(body, signal, onSemantic, inactivityMs) {
225
313
  for (const record of framer.finish())
226
314
  for (const semantic of semantics.push(record))
227
315
  await deliver(onSemantic, semantic);
228
- semantics.finish();
316
+ await deliver(onSemantic, semantics.finish());
229
317
  }
230
318
  catch (error) {
231
319
  void reader.cancel().catch(() => undefined);
@@ -295,8 +383,20 @@ function isSse(value) { return value?.split(";", 1)[0]?.trim().toLowerCase() ===
295
383
  function inactivityTimeout(input) {
296
384
  return input.inactivityTimeoutMs === undefined ? INACTIVITY_TIMEOUT_MS : input.inactivityTimeoutMs > 0 && Number.isFinite(input.inactivityTimeoutMs) ? Math.min(input.inactivityTimeoutMs, INACTIVITY_TIMEOUT_MS) : 0;
297
385
  }
298
- function streamError(kind, message, status) {
299
- const error = new StreamTransportError(kind, message, status);
386
+ function diagnosticFailure(error) {
387
+ if (!isStreamErrorKind(error.kind))
388
+ return undefined;
389
+ const status = error.status;
390
+ return {
391
+ kind: error.kind,
392
+ ...(typeof status === "number" && Number.isInteger(status) && status >= 100 && status <= 599 ? { status } : {}),
393
+ };
394
+ }
395
+ function isStreamErrorKind(value) {
396
+ return value === "aborted" || value === "access" || value === "capability" || value === "model" || value === "quota" || value === "preflight" || value === "response" || value === "transport" || value === "callback";
397
+ }
398
+ function streamError(kind, message, status, details) {
399
+ const error = new StreamTransportError(kind, message, status, details);
300
400
  LOCAL_ERRORS.add(error);
301
401
  return error;
302
402
  }
@@ -0,0 +1,29 @@
1
+ import { type ToolDeclaration } from "./tool-schema.ts";
2
+ export interface PreparedToolContext {
3
+ readonly declarations: readonly ToolDeclaration[];
4
+ readonly mode: "AUTO" | "NONE";
5
+ }
6
+ export declare function hasToolContext(context: unknown): boolean;
7
+ export declare function prepareToolContext(tools: unknown, choice: unknown): PreparedToolContext | undefined;
8
+ type WirePart = object;
9
+ type WireContent = {
10
+ role: "user" | "model";
11
+ parts: WirePart[];
12
+ };
13
+ export type ToolReplayMode = "none" | "signed-function-response" | "unsigned-observation";
14
+ export interface ToolReplayDiagnostics {
15
+ readonly replayMode: ToolReplayMode;
16
+ readonly recoveryCount: number;
17
+ readonly userMessageCount: number;
18
+ readonly assistantMessageCount: number;
19
+ readonly toolResultMessageCount: number;
20
+ readonly assistantToolCallBlockCount: number;
21
+ readonly declaredToolCount: number;
22
+ }
23
+ export interface ToolReplayPolicy {
24
+ readonly requireSignedToolCalls: boolean;
25
+ readonly isSameModel: (message: Record<string, unknown>) => boolean;
26
+ readonly toolCallSignature: (part: Record<string, unknown>, message: Record<string, unknown>) => string | undefined;
27
+ }
28
+ export declare function replayToolHistory(messages: unknown[], serializePart: (part: Record<string, unknown>, message: Record<string, unknown>) => WirePart, onDiagnostics?: (diagnostics: ToolReplayDiagnostics) => void, policy?: ToolReplayPolicy, declaredToolCount?: number): WireContent[];
29
+ export {};