@tangle-network/sandbox 0.30.0 → 0.30.2-develop.20260820100122.dc85046
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/dist/agent/index.d.ts +3 -3
- package/dist/agent/index.js +3 -3
- package/dist/agent-run-outcome-TwIC-Ccd.d.ts +27 -0
- package/dist/{client-DyFI5GnE.d.ts → client-1OK1sjCh.d.ts} +19 -8
- package/dist/{client-BnhE_gUq.js → client-CCKEVUaD.js} +12 -2
- package/dist/core.d.ts +3 -3
- package/dist/core.js +2 -2
- package/dist/{index-ldDRk8Jh.d.ts → index-DAxioMfr.d.ts} +2 -2
- package/dist/index.d.ts +6 -5
- package/dist/index.js +5 -5
- package/dist/openai/index.d.ts +24 -2
- package/dist/openai/index.js +147 -67
- package/dist/{runtime-api-CK-4S7oA.js → runtime-api-TVLbV10b.js} +388 -2
- package/dist/runtime.d.ts +3 -2
- package/dist/runtime.js +2 -2
- package/dist/{sandbox-B49ZGQv7.d.ts → sandbox-Caev7isY.d.ts} +6 -8
- package/dist/{sandbox-BwIaQcE7.js → sandbox-DM-VD8ja.js} +207 -326
- package/dist/session-gateway/index.d.ts +56 -2
- package/dist/session-gateway/index.js +25 -3
- package/dist/tangle/index.d.ts +1 -1
- package/dist/tangle/index.js +1 -1
- package/dist/{tangle-CGzEfYFL.js → tangle-lzUyvhKp.js} +1 -1
- package/dist/{types-Qn3x7R8O.d.ts → types-D_ykdqn6.d.ts} +11 -7
- package/package.json +4 -3
|
@@ -1,10 +1,164 @@
|
|
|
1
|
-
import { a as uploadChunked, c as combineAbortSignals, d as applyRequestedModel, f as assertCurrentBackendType, i as sessionHeaders, l as encodePromptForWire, n as normalizeSessionInfo, o as SANDBOX_PROXY_REQUEST_MAX_BYTES, p as normalizeRuntimeBackendConfig, r as appendSessionIdQuery, t as SandboxRuntimeApi } from "./runtime-api-
|
|
1
|
+
import { a as uploadChunked, c as combineAbortSignals, d as applyRequestedModel, f as assertCurrentBackendType, h as deriveAgentResultOutcome, i as sessionHeaders, l as encodePromptForWire, m as createAgentRunOutcomeTracker, n as normalizeSessionInfo, o as SANDBOX_PROXY_REQUEST_MAX_BYTES, p as normalizeRuntimeBackendConfig, r as appendSessionIdQuery, t as SandboxRuntimeApi } from "./runtime-api-TVLbV10b.js";
|
|
2
2
|
import { a as NetworkError, c as QuotaError, d as StateError, f as TimeoutError, l as SandboxError, m as parseErrorResponse, n as CapabilityError, o as NotFoundError, p as ValidationError, t as AuthError, u as ServerError } from "./errors-C6kn-3zt.js";
|
|
3
3
|
import { AgentExactRunControlRefSchema, AgentRunCancellationAcknowledgementSchema, AgentRunCancellationRequestSchema, InteractionAcknowledgementSchema, InteractionBindingSchema, InteractionRequestSchema, InteractionResponseCommandSchema, InteractionResponseSchema, agentRunCancellationAcknowledgementMatchesRequest, canonicalCandidateDigest, sha256DigestSchema } from "@tangle-network/agent-interface";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { readTokenCostUsd, readTokenUsage } from "@tangle-network/agent-core";
|
|
6
|
+
//#region ../../../packages/sse-core/dist/index.js
|
|
7
|
+
/**
|
|
8
|
+
* Incremental SSE frame parser. Feed decoded text via `push()`; call
|
|
9
|
+
* `flush()` when the stream ends.
|
|
10
|
+
*/
|
|
11
|
+
var SSEFrameParser = class {
|
|
12
|
+
buffer = "";
|
|
13
|
+
frameEvent = "";
|
|
14
|
+
frameId;
|
|
15
|
+
frameDataLines = null;
|
|
16
|
+
bomChecked = false;
|
|
17
|
+
/**
|
|
18
|
+
* Append a decoded text chunk and return every frame the chunk
|
|
19
|
+
* completes. Frame state survives across calls: one frame can
|
|
20
|
+
* straddle any number of chunks.
|
|
21
|
+
*/
|
|
22
|
+
push(chunk) {
|
|
23
|
+
let text = chunk;
|
|
24
|
+
if (!this.bomChecked && text.length > 0) {
|
|
25
|
+
this.bomChecked = true;
|
|
26
|
+
if (text.charCodeAt(0) === 65279) text = text.slice(1);
|
|
27
|
+
}
|
|
28
|
+
this.buffer += text;
|
|
29
|
+
const lines = this.buffer.split("\n");
|
|
30
|
+
this.buffer = lines.pop() ?? "";
|
|
31
|
+
return this.processLines(lines);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* End-of-stream: process the unterminated trailing line, then
|
|
35
|
+
* dispatch a dangling frame that never got its blank line.
|
|
36
|
+
* Well-behaved servers end frames with "\n\n", but some close early.
|
|
37
|
+
*/
|
|
38
|
+
flush() {
|
|
39
|
+
const rest = this.buffer;
|
|
40
|
+
this.buffer = "";
|
|
41
|
+
const frames = rest.length > 0 ? this.processLines([rest]) : [];
|
|
42
|
+
const dangling = this.dispatch();
|
|
43
|
+
if (dangling) frames.push(dangling);
|
|
44
|
+
return frames;
|
|
45
|
+
}
|
|
46
|
+
processLines(lines) {
|
|
47
|
+
const frames = [];
|
|
48
|
+
for (const rawLine of lines) {
|
|
49
|
+
const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
|
|
50
|
+
if (line === "") {
|
|
51
|
+
const frame = this.dispatch();
|
|
52
|
+
if (frame) frames.push(frame);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (line.startsWith(":")) continue;
|
|
56
|
+
if (line.startsWith("data:")) {
|
|
57
|
+
let value = line.slice(5);
|
|
58
|
+
if (value.startsWith(" ")) value = value.slice(1);
|
|
59
|
+
if (this.frameDataLines === null) this.frameDataLines = [];
|
|
60
|
+
this.frameDataLines.push(value);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (line.startsWith("event:")) {
|
|
64
|
+
this.frameEvent = line.slice(6).trim();
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (line.startsWith("id:")) this.frameId = line.slice(3).trim();
|
|
68
|
+
}
|
|
69
|
+
return frames;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Dispatch the accumulated frame. A frame with no `data:` field (an
|
|
73
|
+
* `event:`/`id:`-only frame, or a bare comment) produces nothing, and
|
|
74
|
+
* its field state is consumed and reset either way.
|
|
75
|
+
*/
|
|
76
|
+
dispatch() {
|
|
77
|
+
const dataLines = this.frameDataLines;
|
|
78
|
+
const frame = dataLines ? {
|
|
79
|
+
event: this.frameEvent,
|
|
80
|
+
data: dataLines.join("\n"),
|
|
81
|
+
id: this.frameId || void 0
|
|
82
|
+
} : null;
|
|
83
|
+
this.frameEvent = "";
|
|
84
|
+
this.frameId = void 0;
|
|
85
|
+
this.frameDataLines = null;
|
|
86
|
+
return frame;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Read a streaming SSE response body into parsed frames.
|
|
91
|
+
*
|
|
92
|
+
* @throws `TypeError` when the body is missing.
|
|
93
|
+
* @throws `DOMException` with `name === "AbortError"` on cancellation —
|
|
94
|
+
* including the WHATWG-fetch variant where an aborted body resolves
|
|
95
|
+
* `done: true` instead of rejecting the read.
|
|
96
|
+
*/
|
|
97
|
+
async function* streamSSEFrames(body, options) {
|
|
98
|
+
if (!body) throw new TypeError("SSE stream has no response body");
|
|
99
|
+
const reader = body.getReader();
|
|
100
|
+
const decoder = new TextDecoder();
|
|
101
|
+
const parser = new SSEFrameParser();
|
|
102
|
+
const { signal } = options ?? {};
|
|
103
|
+
let completed = false;
|
|
104
|
+
try {
|
|
105
|
+
for (;;) {
|
|
106
|
+
if (signal?.aborted) throw new DOMException("SSE stream aborted", "AbortError");
|
|
107
|
+
const { done, value } = await reader.read();
|
|
108
|
+
if (done) {
|
|
109
|
+
if (signal?.aborted) throw new DOMException("SSE stream aborted", "AbortError");
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
for (const frame of parser.push(decoder.decode(value, { stream: true }))) yield frame;
|
|
113
|
+
}
|
|
114
|
+
for (const frame of parser.push(decoder.decode())) yield frame;
|
|
115
|
+
for (const frame of parser.flush()) yield frame;
|
|
116
|
+
completed = true;
|
|
117
|
+
} finally {
|
|
118
|
+
if (!completed) await reader.cancel().catch(() => void 0);
|
|
119
|
+
reader.releaseLock();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
//#endregion
|
|
6
123
|
//#region src/lib/sse-parser.ts
|
|
7
124
|
/**
|
|
125
|
+
* SSE Stream Parser
|
|
126
|
+
*
|
|
127
|
+
* EventSource stream parsing for SDK consumers. Framing — buffering
|
|
128
|
+
* chunk-straddling frames, LF/CRLF terminators, multi-line `data:` joins,
|
|
129
|
+
* comment heartbeats, BOM — lives in `@tangle-network/sse-core`, the one
|
|
130
|
+
* wire-format implementation shared across the repo (bundled into this
|
|
131
|
+
* package's dist, so the SDK stays dependency-free at runtime). This
|
|
132
|
+
* module owns the SDK's payload contract on top of it: JSON parsing, the
|
|
133
|
+
* `"message"` default event type, and the malformed-frame policy. Used by
|
|
134
|
+
* `Sandbox.streamBatch` and by the web dashboard's batch page; exported
|
|
135
|
+
* so downstream surfaces don't maintain their own copies.
|
|
136
|
+
*
|
|
137
|
+
* Guarantees:
|
|
138
|
+
* - Correctly buffers frames that straddle chunk boundaries.
|
|
139
|
+
* - Accepts LF- and CRLF-terminated streams interchangeably.
|
|
140
|
+
* - Joins multi-line `data:` per the spec (dispatch joins with
|
|
141
|
+
* "\n" before JSON parse).
|
|
142
|
+
* - Defaults event type to `"message"` when a frame carries
|
|
143
|
+
* `data:` but no `event:` field (SSE spec §9.2.6 step 3).
|
|
144
|
+
* - Surfaces optional `id:` so callers can implement reconnection.
|
|
145
|
+
* - Flushes a dangling event when the stream closes without a
|
|
146
|
+
* terminating blank line (well-behaved servers emit `\n\n`, but
|
|
147
|
+
* some close early).
|
|
148
|
+
* - Throws `AbortError` when the caller-supplied signal fires, so
|
|
149
|
+
* consumers can distinguish "ran to completion" from "cancelled"
|
|
150
|
+
* without peeking at signal state.
|
|
151
|
+
* - Rejects malformed JSON by default. Callers handling recoverable output
|
|
152
|
+
* streams can explicitly warn and skip one invalid frame.
|
|
153
|
+
*
|
|
154
|
+
* What it does NOT do:
|
|
155
|
+
* - CR-only (legacy Mac) line terminators. No known emitter uses
|
|
156
|
+
* them; supporting them would risk splitting payloads that
|
|
157
|
+
* legitimately contain CR.
|
|
158
|
+
* - Retry/reconnect (caller's responsibility).
|
|
159
|
+
* - Event-type filtering (caller's responsibility).
|
|
160
|
+
*/
|
|
161
|
+
/**
|
|
8
162
|
* Parse a streaming SSE response body into an async iterable of
|
|
9
163
|
* structured events.
|
|
10
164
|
*
|
|
@@ -12,81 +166,31 @@ import { readTokenCostUsd, readTokenUsage } from "@tangle-network/agent-core";
|
|
|
12
166
|
* @throws `DOMException` with `name === "AbortError"` on cancellation.
|
|
13
167
|
*/
|
|
14
168
|
async function* parseSSEStream(body, options) {
|
|
15
|
-
if (!body) throw new TypeError("SSE stream has no response body");
|
|
16
|
-
const reader = body.getReader();
|
|
17
|
-
const decoder = new TextDecoder();
|
|
18
169
|
const { signal, invalidEvent = "throw" } = options ?? {};
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
let dataLines = [];
|
|
22
|
-
let currentId = "";
|
|
23
|
-
let completed = false;
|
|
24
|
-
const flush = function* () {
|
|
25
|
-
if (dataLines.length === 0) {
|
|
26
|
-
currentEvent = "";
|
|
27
|
-
currentId = "";
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
const effectiveEvent = currentEvent || "message";
|
|
31
|
-
const joined = dataLines.join("\n");
|
|
170
|
+
for await (const frame of streamSSEFrames(body, { signal })) {
|
|
171
|
+
const effectiveEvent = frame.event || "message";
|
|
32
172
|
let data;
|
|
33
173
|
try {
|
|
34
|
-
data = JSON.parse(
|
|
174
|
+
data = JSON.parse(frame.data);
|
|
35
175
|
} catch (error) {
|
|
36
176
|
if (invalidEvent === "warn-and-skip") {
|
|
37
|
-
console.warn(`[sandbox-sdk] Malformed SSE JSON for event "${effectiveEvent}": ${
|
|
38
|
-
|
|
39
|
-
dataLines = [];
|
|
40
|
-
currentId = "";
|
|
41
|
-
return;
|
|
177
|
+
console.warn(`[sandbox-sdk] Malformed SSE JSON for event "${effectiveEvent}": ${frame.data.slice(0, 200)}`);
|
|
178
|
+
continue;
|
|
42
179
|
}
|
|
43
180
|
throw new SyntaxError(`Malformed SSE JSON for event "${effectiveEvent}"`, { cause: error });
|
|
44
181
|
}
|
|
45
182
|
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
46
183
|
if (invalidEvent === "warn-and-skip") {
|
|
47
184
|
console.warn(`[sandbox-sdk] SSE event "${effectiveEvent}" data must be a JSON object`);
|
|
48
|
-
|
|
49
|
-
dataLines = [];
|
|
50
|
-
currentId = "";
|
|
51
|
-
return;
|
|
185
|
+
continue;
|
|
52
186
|
}
|
|
53
187
|
throw new TypeError(`SSE event "${effectiveEvent}" data must be a JSON object`);
|
|
54
188
|
}
|
|
55
189
|
yield {
|
|
56
190
|
type: effectiveEvent,
|
|
57
191
|
data,
|
|
58
|
-
id:
|
|
192
|
+
id: frame.id
|
|
59
193
|
};
|
|
60
|
-
currentEvent = "";
|
|
61
|
-
dataLines = [];
|
|
62
|
-
currentId = "";
|
|
63
|
-
};
|
|
64
|
-
const processLine = (line) => {
|
|
65
|
-
if (line.startsWith("event:")) currentEvent = line.slice(6).trim();
|
|
66
|
-
else if (line.startsWith("data:")) dataLines.push(line.slice(5).trim());
|
|
67
|
-
else if (line.startsWith("id:")) currentId = line.slice(3).trim();
|
|
68
|
-
};
|
|
69
|
-
try {
|
|
70
|
-
while (true) {
|
|
71
|
-
if (signal?.aborted) throw new DOMException("SSE stream aborted", "AbortError");
|
|
72
|
-
const { done, value } = await reader.read();
|
|
73
|
-
if (done) {
|
|
74
|
-
if (signal?.aborted) throw new DOMException("SSE stream aborted", "AbortError");
|
|
75
|
-
break;
|
|
76
|
-
}
|
|
77
|
-
buffer += decoder.decode(value, { stream: true });
|
|
78
|
-
const lines = buffer.split(/\r?\n/);
|
|
79
|
-
buffer = lines.pop() ?? "";
|
|
80
|
-
for (const line of lines) if (line === "") yield* flush();
|
|
81
|
-
else processLine(line);
|
|
82
|
-
}
|
|
83
|
-
buffer += decoder.decode();
|
|
84
|
-
if (buffer.length > 0) processLine(buffer);
|
|
85
|
-
yield* flush();
|
|
86
|
-
completed = true;
|
|
87
|
-
} finally {
|
|
88
|
-
if (!completed) await reader.cancel().catch(() => void 0);
|
|
89
|
-
reader.releaseLock();
|
|
90
194
|
}
|
|
91
195
|
}
|
|
92
196
|
//#endregion
|
|
@@ -736,7 +840,7 @@ async function submitSandboxInteractionCommand(transport, sessionId, command, op
|
|
|
736
840
|
/**
|
|
737
841
|
* The text carried by a single event, or `undefined` when the event carries no
|
|
738
842
|
* text payload. Mirrors the event shapes the sidecar/OpenCode runtime emits:
|
|
739
|
-
* - `result` — the
|
|
843
|
+
* - `result` — the final response summary; text under `finalText`/`response`/
|
|
740
844
|
* `output`/`content`/`text`.
|
|
741
845
|
* - `token` — an incremental token; text under `value`/`delta`/`text`/
|
|
742
846
|
* `content`.
|
|
@@ -1907,153 +2011,6 @@ function runtimeFilesystemPath(endpoint, path) {
|
|
|
1907
2011
|
const encodedPath = segments.map(encodeURIComponent).join("/");
|
|
1908
2012
|
return `${endpoint}/${isAbsolute ? "/" : ""}${encodedPath}`;
|
|
1909
2013
|
}
|
|
1910
|
-
/**
|
|
1911
|
-
* Single source of truth for a run's outcome, shared by
|
|
1912
|
-
* `prompt`/`task`/`_sessionResult`/turn settling. A run succeeds only when it
|
|
1913
|
-
* reached a terminal event, raised no run-level error, hit no approval gate,
|
|
1914
|
-
* left no unanswered question, and recorded no failed (`isError`) tool. A
|
|
1915
|
-
* tool-only turn with no text succeeds unless the server enforces visible-output
|
|
1916
|
-
* requirements; a turn whose tool failed (even if narrated around) fails.
|
|
1917
|
-
*/
|
|
1918
|
-
function deriveOutcome(inputs) {
|
|
1919
|
-
if (inputs.plan) return {
|
|
1920
|
-
status: "awaiting_plan_decision",
|
|
1921
|
-
success: false,
|
|
1922
|
-
error: "Agent is awaiting a durable plan decision"
|
|
1923
|
-
};
|
|
1924
|
-
if (inputs.question) return {
|
|
1925
|
-
status: "awaiting_question",
|
|
1926
|
-
success: false,
|
|
1927
|
-
error: inputs.runError ?? "Agent is awaiting an answer to a question"
|
|
1928
|
-
};
|
|
1929
|
-
if (inputs.approval) return {
|
|
1930
|
-
status: "blocked_on_approval",
|
|
1931
|
-
success: false,
|
|
1932
|
-
error: inputs.runError ?? inputs.approval.message ?? "Agent is blocked on a hub approval"
|
|
1933
|
-
};
|
|
1934
|
-
if (inputs.runError) return {
|
|
1935
|
-
status: "failed",
|
|
1936
|
-
success: false,
|
|
1937
|
-
error: inputs.runError,
|
|
1938
|
-
errorCode: inputs.runErrorCode
|
|
1939
|
-
};
|
|
1940
|
-
const failedTool = inputs.toolInvocations?.find((t) => t.isError === true);
|
|
1941
|
-
if (failedTool) return {
|
|
1942
|
-
status: "failed",
|
|
1943
|
-
success: false,
|
|
1944
|
-
error: `Tool "${failedTool.toolName}" failed`
|
|
1945
|
-
};
|
|
1946
|
-
if (!inputs.terminalReached) return {
|
|
1947
|
-
status: "failed",
|
|
1948
|
-
success: false,
|
|
1949
|
-
error: "Agent stream ended without a terminal event"
|
|
1950
|
-
};
|
|
1951
|
-
return {
|
|
1952
|
-
status: "success",
|
|
1953
|
-
success: true
|
|
1954
|
-
};
|
|
1955
|
-
}
|
|
1956
|
-
function readAwaitingPlan(data) {
|
|
1957
|
-
const direct = data.plan;
|
|
1958
|
-
const outcome = data.outcome;
|
|
1959
|
-
const candidate = direct && typeof direct === "object" ? direct : outcome && typeof outcome === "object" && outcome.type === "awaiting_plan_decision" ? outcome.plan : void 0;
|
|
1960
|
-
if (!candidate || typeof candidate !== "object") return void 0;
|
|
1961
|
-
const plan = candidate;
|
|
1962
|
-
if (typeof plan.id !== "string" || typeof plan.revision !== "number" || typeof plan.body !== "string" || typeof plan.submittedAt !== "string") return;
|
|
1963
|
-
return plan;
|
|
1964
|
-
}
|
|
1965
|
-
function readPlanFromEvent(event) {
|
|
1966
|
-
return event.type === "result" || event.type === "done" || event.type === "plan.submitted" ? readAwaitingPlan(event.data) : void 0;
|
|
1967
|
-
}
|
|
1968
|
-
/** Read tool invocations off a `result` event payload. */
|
|
1969
|
-
function readToolInvocations(data) {
|
|
1970
|
-
const raw = data.toolInvocations;
|
|
1971
|
-
return Array.isArray(raw) ? raw : void 0;
|
|
1972
|
-
}
|
|
1973
|
-
/** Hub error code surfaced when a write tool hits an approval gate. */
|
|
1974
|
-
const HUB_APPROVAL_CODE = "HUB_APPROVAL_REQUIRED";
|
|
1975
|
-
/** Best-effort string view of an arbitrary tool result for signal matching. */
|
|
1976
|
-
function stringifyToolResult(result) {
|
|
1977
|
-
if (typeof result === "string") return result;
|
|
1978
|
-
try {
|
|
1979
|
-
return JSON.stringify(result) ?? "";
|
|
1980
|
-
} catch {
|
|
1981
|
-
return String(result);
|
|
1982
|
-
}
|
|
1983
|
-
}
|
|
1984
|
-
/**
|
|
1985
|
-
* Walk a tool result for an `approval` object and return its `id`/`connectionId`.
|
|
1986
|
-
* The in-sandbox hub bridge carries these in `structuredContent.details.approval`,
|
|
1987
|
-
* but the exact nesting depends on how the backend serialized the MCP result, so
|
|
1988
|
-
* search defensively. Returns `undefined` when not present — the id is then
|
|
1989
|
-
* surfaced honestly as `undefined`, never invented.
|
|
1990
|
-
*/
|
|
1991
|
-
function findApprovalDetails(value, depth = 0) {
|
|
1992
|
-
if (depth > 6 || value === null || typeof value !== "object") return void 0;
|
|
1993
|
-
const obj = value;
|
|
1994
|
-
const approval = obj.approval;
|
|
1995
|
-
if (approval && typeof approval === "object") {
|
|
1996
|
-
const a = approval;
|
|
1997
|
-
return {
|
|
1998
|
-
...typeof a.id === "string" ? { id: a.id } : {},
|
|
1999
|
-
...typeof a.connectionId === "string" ? { connectionId: a.connectionId } : {}
|
|
2000
|
-
};
|
|
2001
|
-
}
|
|
2002
|
-
for (const v of Object.values(obj)) {
|
|
2003
|
-
const found = findApprovalDetails(v, depth + 1);
|
|
2004
|
-
if (found) return found;
|
|
2005
|
-
}
|
|
2006
|
-
}
|
|
2007
|
-
/** Find a human-readable `message` string anywhere in a tool result. */
|
|
2008
|
-
function findResultMessage(value, depth = 0) {
|
|
2009
|
-
if (depth > 6 || value === null || typeof value !== "object") return void 0;
|
|
2010
|
-
const obj = value;
|
|
2011
|
-
if (typeof obj.message === "string" && obj.message.length > 0) return obj.message;
|
|
2012
|
-
for (const v of Object.values(obj)) {
|
|
2013
|
-
const found = findResultMessage(v, depth + 1);
|
|
2014
|
-
if (found) return found;
|
|
2015
|
-
}
|
|
2016
|
-
}
|
|
2017
|
-
/**
|
|
2018
|
-
* Detect a hub approval gate among a run's tool invocations: a failed tool whose
|
|
2019
|
-
* result carries `HUB_APPROVAL_REQUIRED`. Returns the approval id/connectionId
|
|
2020
|
-
* when the structured signal survived serialization, else just the message — so
|
|
2021
|
-
* the run can settle as `blocked_on_approval` even when the id is unrecoverable.
|
|
2022
|
-
*/
|
|
2023
|
-
function detectHubApproval(toolInvocations) {
|
|
2024
|
-
if (!toolInvocations) return void 0;
|
|
2025
|
-
for (const tool of toolInvocations) {
|
|
2026
|
-
if (tool.isError !== true) continue;
|
|
2027
|
-
if (!stringifyToolResult(tool.result).includes(HUB_APPROVAL_CODE)) continue;
|
|
2028
|
-
const approval = findApprovalDetails(tool.result);
|
|
2029
|
-
const message = findResultMessage(tool.result) ?? "Hub action requires approval";
|
|
2030
|
-
return {
|
|
2031
|
-
...approval?.id ? { approvalId: approval.id } : {},
|
|
2032
|
-
...approval?.connectionId ? { connectionId: approval.connectionId } : {},
|
|
2033
|
-
message
|
|
2034
|
-
};
|
|
2035
|
-
}
|
|
2036
|
-
}
|
|
2037
|
-
/** Read an `interaction` event (kind `question`) into an awaiting-question requirement. */
|
|
2038
|
-
function readQuestionEvent(data) {
|
|
2039
|
-
const request = data.request;
|
|
2040
|
-
if (!request || request.kind !== "question" || typeof request.id !== "string") return;
|
|
2041
|
-
return {
|
|
2042
|
-
questionId: request.id,
|
|
2043
|
-
questions: questionsFromAnswerSpec(request)
|
|
2044
|
-
};
|
|
2045
|
-
}
|
|
2046
|
-
/** Reconstruct the prompt/options shape from a question interaction's answerSpec. */
|
|
2047
|
-
function questionsFromAnswerSpec(request) {
|
|
2048
|
-
return request.answerSpec.fields.map((field) => field.type === "select" ? {
|
|
2049
|
-
question: field.label,
|
|
2050
|
-
options: field.options.map((o) => ({
|
|
2051
|
-
label: o.label,
|
|
2052
|
-
description: o.description
|
|
2053
|
-
})),
|
|
2054
|
-
multiSelect: field.multi === true
|
|
2055
|
-
} : { question: field.label });
|
|
2056
|
-
}
|
|
2057
2014
|
function isProfileNotCachedError(err) {
|
|
2058
2015
|
return err instanceof SandboxError && err.code === "PROFILE_NOT_CACHED";
|
|
2059
2016
|
}
|
|
@@ -2841,16 +2798,11 @@ var SandboxInstance = class SandboxInstance {
|
|
|
2841
2798
|
await this.ensureRunning();
|
|
2842
2799
|
const startTime = Date.now();
|
|
2843
2800
|
let responseText;
|
|
2844
|
-
|
|
2845
|
-
let runErrorCode;
|
|
2801
|
+
const outcomeTracker = createAgentRunOutcomeTracker();
|
|
2846
2802
|
let executionId = options?.lastEventId ? options.executionId : void 0;
|
|
2847
2803
|
let traceId;
|
|
2848
2804
|
let usage;
|
|
2849
2805
|
let costUsd;
|
|
2850
|
-
let toolInvocations;
|
|
2851
|
-
let question;
|
|
2852
|
-
let plan;
|
|
2853
|
-
let terminalReached = false;
|
|
2854
2806
|
const controller = new AbortController();
|
|
2855
2807
|
try {
|
|
2856
2808
|
const signal = options?.signal ? combineAbortSignals([options.signal, controller.signal]) : controller.signal;
|
|
@@ -2858,6 +2810,7 @@ var SandboxInstance = class SandboxInstance {
|
|
|
2858
2810
|
...options,
|
|
2859
2811
|
signal
|
|
2860
2812
|
})) {
|
|
2813
|
+
outcomeTracker.observe(event);
|
|
2861
2814
|
responseText = applySandboxEventText(responseText, event);
|
|
2862
2815
|
const eventExecutionId = event.data.executionId;
|
|
2863
2816
|
if (typeof eventExecutionId === "string" && eventExecutionId.length > 0) executionId = eventExecutionId;
|
|
@@ -2865,26 +2818,9 @@ var SandboxInstance = class SandboxInstance {
|
|
|
2865
2818
|
usage = readTokenUsage(event.data) ?? usage;
|
|
2866
2819
|
costUsd = readTokenCostUsd(event.data) ?? costUsd;
|
|
2867
2820
|
}
|
|
2868
|
-
plan = readPlanFromEvent(event) ?? plan;
|
|
2869
|
-
if (event.type === "result") toolInvocations = readToolInvocations(event.data) ?? toolInvocations;
|
|
2870
|
-
if (event.type === "result" || event.type === "done") terminalReached = true;
|
|
2871
|
-
if (event.type === "interaction") question = readQuestionEvent(event.data) ?? question;
|
|
2872
2821
|
if (event.type === "trace.id") traceId = event.data.traceId;
|
|
2873
|
-
if (event.type === "error") {
|
|
2874
|
-
runError = event.data.message;
|
|
2875
|
-
runErrorCode = event.data.code;
|
|
2876
|
-
}
|
|
2877
2822
|
}
|
|
2878
|
-
const
|
|
2879
|
-
const outcome = deriveOutcome({
|
|
2880
|
-
terminalReached,
|
|
2881
|
-
runError,
|
|
2882
|
-
runErrorCode,
|
|
2883
|
-
toolInvocations,
|
|
2884
|
-
approval,
|
|
2885
|
-
question,
|
|
2886
|
-
plan
|
|
2887
|
-
});
|
|
2823
|
+
const outcome = outcomeTracker.finish();
|
|
2888
2824
|
return {
|
|
2889
2825
|
success: outcome.success,
|
|
2890
2826
|
status: outcome.status,
|
|
@@ -2892,10 +2828,11 @@ var SandboxInstance = class SandboxInstance {
|
|
|
2892
2828
|
response: responseText,
|
|
2893
2829
|
error: outcome.error,
|
|
2894
2830
|
errorCode: outcome.errorCode,
|
|
2895
|
-
toolInvocations,
|
|
2896
|
-
approval,
|
|
2897
|
-
|
|
2898
|
-
|
|
2831
|
+
toolInvocations: outcome.toolInvocations,
|
|
2832
|
+
approval: outcome.approval,
|
|
2833
|
+
interaction: outcome.interaction,
|
|
2834
|
+
question: outcome.question,
|
|
2835
|
+
plan: outcome.plan,
|
|
2899
2836
|
traceId,
|
|
2900
2837
|
durationMs: Date.now() - startTime,
|
|
2901
2838
|
usage,
|
|
@@ -2924,15 +2861,13 @@ var SandboxInstance = class SandboxInstance {
|
|
|
2924
2861
|
* Stream events from an agent prompt.
|
|
2925
2862
|
* Use this for real-time updates during agent execution.
|
|
2926
2863
|
*
|
|
2927
|
-
* Guarantees
|
|
2928
|
-
* ends with
|
|
2929
|
-
* error
|
|
2930
|
-
*
|
|
2931
|
-
* never a thrown generator. Caller-initiated cancellation (aborting
|
|
2932
|
-
* `options.signal`) ends the stream silently with no synthetic terminal.
|
|
2864
|
+
* Guarantees terminal completion on every non-cancelled path. A clean run
|
|
2865
|
+
* emits a `result` summary and ends with `done`. A failure is an in-band
|
|
2866
|
+
* `error` followed by `done`, never a thrown generator. Caller cancellation
|
|
2867
|
+
* ends the stream silently with no synthetic terminal.
|
|
2933
2868
|
*
|
|
2934
2869
|
* Automatically reconnects via the runtime event replay endpoint if the
|
|
2935
|
-
* SSE stream drops before a terminal event (`
|
|
2870
|
+
* SSE stream drops before a terminal event (`done` or `error`) is
|
|
2936
2871
|
* received. Reconnection is transparent — replayed events that were
|
|
2937
2872
|
* already yielded (based on event ID tracking) are deduplicated.
|
|
2938
2873
|
*/
|
|
@@ -3015,7 +2950,7 @@ var SandboxInstance = class SandboxInstance {
|
|
|
3015
2950
|
try {
|
|
3016
2951
|
for await (const event of this.streamPromptInner(message, options, { forceFullProfile: profileResendAttempted })) {
|
|
3017
2952
|
yieldedThisAttempt = true;
|
|
3018
|
-
if (event.type === "
|
|
2953
|
+
if (event.type === "done") receivedTerminal = true;
|
|
3019
2954
|
if (event.type === "execution.started" && event.data.executionId) executionId = event.data.executionId;
|
|
3020
2955
|
const sid = event.data.sessionId;
|
|
3021
2956
|
if (typeof sid === "string" && sid.length > 0) sessionId = sid;
|
|
@@ -3174,7 +3109,7 @@ var SandboxInstance = class SandboxInstance {
|
|
|
3174
3109
|
if (event.type === "execution.started" && event.data?.executionId) executionId = event.data.executionId;
|
|
3175
3110
|
const eventSessionId = event.data?.sessionId;
|
|
3176
3111
|
if (typeof eventSessionId === "string" && eventSessionId.length > 0) sessionId = eventSessionId;
|
|
3177
|
-
if (event.type === "
|
|
3112
|
+
if (event.type === "done" || event.type === "error") {
|
|
3178
3113
|
receivedTerminal = true;
|
|
3179
3114
|
logStreamCheckpoint("terminal_event", {
|
|
3180
3115
|
eventType: event.type,
|
|
@@ -3244,6 +3179,7 @@ var SandboxInstance = class SandboxInstance {
|
|
|
3244
3179
|
sessionId,
|
|
3245
3180
|
executionId,
|
|
3246
3181
|
status: info.status,
|
|
3182
|
+
...info.status === "completed" ? { outcome: { type: "completed" } } : {},
|
|
3247
3183
|
replayUnavailable: true
|
|
3248
3184
|
}
|
|
3249
3185
|
};
|
|
@@ -3268,7 +3204,7 @@ var SandboxInstance = class SandboxInstance {
|
|
|
3268
3204
|
if (event.id) lastEventId = event.id;
|
|
3269
3205
|
const eventSessionId = event.data?.sessionId;
|
|
3270
3206
|
if (typeof eventSessionId === "string" && eventSessionId.length > 0) sessionId = eventSessionId;
|
|
3271
|
-
if (event.type === "
|
|
3207
|
+
if (event.type === "done" || event.type === "error") {
|
|
3272
3208
|
receivedTerminal = true;
|
|
3273
3209
|
logStreamCheckpoint("terminal_event", {
|
|
3274
3210
|
eventType: event.type,
|
|
@@ -4578,37 +4514,16 @@ var SandboxInstance = class SandboxInstance {
|
|
|
4578
4514
|
};
|
|
4579
4515
|
}
|
|
4580
4516
|
async *parseProcessLogStream(response) {
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
buffer += decoder.decode(value, { stream: true });
|
|
4592
|
-
const lines = buffer.split("\n");
|
|
4593
|
-
buffer = lines.pop() ?? "";
|
|
4594
|
-
for (const line of lines) if (line.startsWith("event:")) currentEvent = line.slice(6).trim();
|
|
4595
|
-
else if (line.startsWith("data:")) currentData = line.slice(5).trim();
|
|
4596
|
-
else if (line === "" && currentEvent && currentData) {
|
|
4597
|
-
if (currentEvent === "stdout" || currentEvent === "stderr") try {
|
|
4598
|
-
const parsed = JSON.parse(currentData);
|
|
4599
|
-
yield {
|
|
4600
|
-
type: currentEvent,
|
|
4601
|
-
data: parsed.data,
|
|
4602
|
-
timestamp: parsed.timestamp
|
|
4603
|
-
};
|
|
4604
|
-
} catch {}
|
|
4605
|
-
else if (currentEvent === "exit") return;
|
|
4606
|
-
currentEvent = "";
|
|
4607
|
-
currentData = "";
|
|
4608
|
-
}
|
|
4609
|
-
}
|
|
4610
|
-
} finally {
|
|
4611
|
-
reader.releaseLock();
|
|
4517
|
+
if (!response.body) throw new NetworkError("No response body");
|
|
4518
|
+
for await (const event of parseSSEStream(response.body, { invalidEvent: "warn-and-skip" })) {
|
|
4519
|
+
if (event.type === "exit") return;
|
|
4520
|
+
if (event.type !== "stdout" && event.type !== "stderr") continue;
|
|
4521
|
+
if (typeof event.data.data !== "string" || typeof event.data.timestamp !== "number") continue;
|
|
4522
|
+
yield {
|
|
4523
|
+
type: event.type,
|
|
4524
|
+
data: event.data.data,
|
|
4525
|
+
timestamp: event.data.timestamp
|
|
4526
|
+
};
|
|
4612
4527
|
}
|
|
4613
4528
|
}
|
|
4614
4529
|
/**
|
|
@@ -6367,7 +6282,7 @@ var SandboxInstance = class SandboxInstance {
|
|
|
6367
6282
|
}
|
|
6368
6283
|
for await (const event of this.parseSSEStream(response, opts?.signal)) {
|
|
6369
6284
|
yield event;
|
|
6370
|
-
const turnTerminal = event.type === "
|
|
6285
|
+
const turnTerminal = event.type === "done" || event.type === "error";
|
|
6371
6286
|
if ((stopOnTurnTerminal || opts?.executionId !== void 0) && turnTerminal) return;
|
|
6372
6287
|
}
|
|
6373
6288
|
}
|
|
@@ -6386,7 +6301,7 @@ var SandboxInstance = class SandboxInstance {
|
|
|
6386
6301
|
}
|
|
6387
6302
|
for await (const event of this.parseSSEStream(response)) {
|
|
6388
6303
|
yield event;
|
|
6389
|
-
if (event.type === "
|
|
6304
|
+
if (event.type === "done" || event.type === "error") return;
|
|
6390
6305
|
}
|
|
6391
6306
|
}
|
|
6392
6307
|
/** @internal — invoked by SandboxSession.result(). */
|
|
@@ -6408,20 +6323,13 @@ var SandboxInstance = class SandboxInstance {
|
|
|
6408
6323
|
}
|
|
6409
6324
|
let resultExecutionId = executionId;
|
|
6410
6325
|
let response;
|
|
6411
|
-
|
|
6412
|
-
let runErrorCode;
|
|
6326
|
+
const outcomeTracker = createAgentRunOutcomeTracker();
|
|
6413
6327
|
let traceId;
|
|
6414
6328
|
let usage;
|
|
6415
6329
|
let costUsd;
|
|
6416
|
-
|
|
6417
|
-
let question;
|
|
6418
|
-
let plan;
|
|
6419
|
-
let terminalReached = false;
|
|
6420
|
-
const events = options?.executionId ? this._executionEvents(id, executionId) : this._sessionEvents(id, {
|
|
6421
|
-
since: "0",
|
|
6422
|
-
executionId
|
|
6423
|
-
}, true);
|
|
6330
|
+
const events = this._executionEvents(id, executionId);
|
|
6424
6331
|
for await (const event of events) {
|
|
6332
|
+
outcomeTracker.observe(event);
|
|
6425
6333
|
response = applySandboxEventText(response, event);
|
|
6426
6334
|
if (event.type === "result" || event.type === "done") {
|
|
6427
6335
|
const data = event.data;
|
|
@@ -6429,33 +6337,10 @@ var SandboxInstance = class SandboxInstance {
|
|
|
6429
6337
|
usage = readTokenUsage(data) ?? usage;
|
|
6430
6338
|
costUsd = readTokenCostUsd(data) ?? costUsd;
|
|
6431
6339
|
}
|
|
6432
|
-
plan = readPlanFromEvent(event) ?? plan;
|
|
6433
|
-
if (event.type === "result") {
|
|
6434
|
-
const data = event.data;
|
|
6435
|
-
toolInvocations = readToolInvocations(data) ?? toolInvocations;
|
|
6436
|
-
}
|
|
6437
|
-
if (event.type === "interaction") question = readQuestionEvent(event.data) ?? question;
|
|
6438
6340
|
if (event.type === "trace.id") traceId = event.data.traceId;
|
|
6439
|
-
if (event.type === "error")
|
|
6440
|
-
const data = event.data;
|
|
6441
|
-
runError = data.message;
|
|
6442
|
-
runErrorCode = data.code;
|
|
6443
|
-
}
|
|
6444
|
-
if (event.type === "done" || event.type === "result") {
|
|
6445
|
-
terminalReached = true;
|
|
6446
|
-
break;
|
|
6447
|
-
}
|
|
6341
|
+
if (event.type === "done" || event.type === "error") break;
|
|
6448
6342
|
}
|
|
6449
|
-
const
|
|
6450
|
-
const outcome = deriveOutcome({
|
|
6451
|
-
terminalReached,
|
|
6452
|
-
runError,
|
|
6453
|
-
runErrorCode,
|
|
6454
|
-
toolInvocations,
|
|
6455
|
-
approval,
|
|
6456
|
-
question,
|
|
6457
|
-
plan
|
|
6458
|
-
});
|
|
6343
|
+
const outcome = outcomeTracker.finish();
|
|
6459
6344
|
return {
|
|
6460
6345
|
success: outcome.success,
|
|
6461
6346
|
status: outcome.status,
|
|
@@ -6463,10 +6348,11 @@ var SandboxInstance = class SandboxInstance {
|
|
|
6463
6348
|
response,
|
|
6464
6349
|
error: outcome.error,
|
|
6465
6350
|
errorCode: outcome.errorCode,
|
|
6466
|
-
toolInvocations,
|
|
6467
|
-
approval,
|
|
6468
|
-
|
|
6469
|
-
|
|
6351
|
+
toolInvocations: outcome.toolInvocations,
|
|
6352
|
+
approval: outcome.approval,
|
|
6353
|
+
interaction: outcome.interaction,
|
|
6354
|
+
question: outcome.question,
|
|
6355
|
+
plan: outcome.plan,
|
|
6470
6356
|
traceId,
|
|
6471
6357
|
durationMs: Date.now() - startTime,
|
|
6472
6358
|
usage,
|
|
@@ -6760,21 +6646,16 @@ function isSessionInFlight(info) {
|
|
|
6760
6646
|
return info.status === "queued" && !isSessionAwaitingFirstTurn(info);
|
|
6761
6647
|
}
|
|
6762
6648
|
function settleTurnDrive(result) {
|
|
6763
|
-
const
|
|
6764
|
-
if (plan) return {
|
|
6649
|
+
const outcome = deriveAgentResultOutcome(result);
|
|
6650
|
+
if (outcome.status === "awaiting_plan_decision" && outcome.plan) return {
|
|
6765
6651
|
state: "awaiting_plan_decision",
|
|
6766
|
-
plan,
|
|
6652
|
+
plan: outcome.plan,
|
|
6767
6653
|
result
|
|
6768
6654
|
};
|
|
6769
6655
|
const text = typeof result.text === "string" ? result.text : typeof result.response === "string" ? result.response : void 0;
|
|
6770
|
-
|
|
6771
|
-
terminalReached: true,
|
|
6772
|
-
runError: typeof result.error === "string" ? result.error : void 0,
|
|
6773
|
-
toolInvocations: Array.isArray(result.toolInvocations) ? result.toolInvocations : void 0
|
|
6774
|
-
}).status;
|
|
6775
|
-
if (status !== "success") return {
|
|
6656
|
+
if (outcome.status !== "success") return {
|
|
6776
6657
|
state: "failed",
|
|
6777
|
-
error:
|
|
6658
|
+
error: outcome.error ?? `turn ${outcome.status}`
|
|
6778
6659
|
};
|
|
6779
6660
|
const usage = readTokenUsage(result);
|
|
6780
6661
|
return {
|