@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/dist/response.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
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():
|
|
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
|
|
60
|
-
if (
|
|
72
|
+
const item = value[0];
|
|
73
|
+
if (item === undefined)
|
|
61
74
|
return;
|
|
62
|
-
const index = field(
|
|
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(
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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(
|
|
111
|
+
const finishReason = field(item, "finishReason");
|
|
84
112
|
if (finishReason !== undefined) {
|
|
85
|
-
|
|
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.
|
|
88
|
-
events.push({ type: "finish", reason: finishReason === "STOP" ? "stop" : "length" });
|
|
117
|
+
semantics.setFinish(reason);
|
|
89
118
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if (
|
|
108
|
-
throw new ResponseSemanticError("Antigravity returned
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
299
|
-
|
|
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 {};
|