@tangle-network/sandbox 0.30.1 → 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.
@@ -1,3 +1,4 @@
1
+ import { z } from "zod";
1
2
  //#region src/openai/hooks.ts
2
3
  /**
3
4
  * Sequential hook composer. Hooks run in registration order; `block` and
@@ -379,6 +380,101 @@ var InMemoryResponseStore = class {
379
380
  }
380
381
  };
381
382
  //#endregion
383
+ //#region src/openai/terminal.ts
384
+ const SUCCESS_OUTCOMES = new Set([
385
+ "success",
386
+ "succeeded",
387
+ "completed",
388
+ "done"
389
+ ]);
390
+ const ResponseErrorCodeSchema = z.enum([
391
+ "empty_image_file",
392
+ "failed_to_download_image",
393
+ "image_content_policy_violation",
394
+ "image_file_not_found",
395
+ "image_file_too_large",
396
+ "image_parse_error",
397
+ "image_too_large",
398
+ "image_too_small",
399
+ "invalid_base64_image",
400
+ "invalid_image",
401
+ "invalid_image_format",
402
+ "invalid_image_mode",
403
+ "invalid_image_url",
404
+ "invalid_prompt",
405
+ "rate_limit_exceeded",
406
+ "server_error",
407
+ "unsupported_image_media_type",
408
+ "vector_store_timeout"
409
+ ]);
410
+ const OPENAI_MISSING_TERMINAL_REASON = "stream ended without a terminal event";
411
+ const OPENAI_MALFORMED_TERMINAL_REASON = "malformed terminal event";
412
+ const OpenAITerminalEventSchema = z.object({
413
+ type: z.enum(["done", "error"]),
414
+ outcome: z.string().optional(),
415
+ status: z.string().optional(),
416
+ success: z.boolean().optional(),
417
+ error: z.string().optional(),
418
+ message: z.string().optional()
419
+ }).passthrough();
420
+ function responseError(reason) {
421
+ const normalized = reason.trim().toLowerCase().replace(/[\s-]+/g, "_");
422
+ const parsedCode = ResponseErrorCodeSchema.safeParse(normalized);
423
+ return {
424
+ code: parsedCode.success ? parsedCode.data : normalized === "rate_limit" || normalized === "rate_limited" ? "rate_limit_exceeded" : normalized === "invalid_prompt_format" ? "invalid_prompt" : "server_error",
425
+ message: reason
426
+ };
427
+ }
428
+ function failure(reason) {
429
+ return {
430
+ status: "failed",
431
+ error: responseError(reason),
432
+ reason
433
+ };
434
+ }
435
+ function terminalOutcome(event) {
436
+ if (event.outcome?.trim()) return event.outcome.trim().toLowerCase();
437
+ if (event.status?.trim()) return event.status.trim().toLowerCase();
438
+ if (event.success === false) return "failure";
439
+ return "success";
440
+ }
441
+ function terminalReason(event, fallback) {
442
+ if (event.error && event.error.length > 0) return event.error;
443
+ if (event.message && event.message.length > 0) return event.message;
444
+ return fallback;
445
+ }
446
+ /**
447
+ * Classify one top-level OpenAI-adapter event.
448
+ *
449
+ * `TangleSSEEvent` is not the nested `SandboxEvent` protocol. This function
450
+ * reads only fields on the supplied event itself.
451
+ */
452
+ function classifyOpenAITerminal(event) {
453
+ if (event.type !== "done" && event.type !== "error") return null;
454
+ const parsed = OpenAITerminalEventSchema.safeParse(event);
455
+ if (!parsed.success) return failure(OPENAI_MALFORMED_TERMINAL_REASON);
456
+ if (parsed.data.type === "done") {
457
+ const outcome = terminalOutcome(parsed.data);
458
+ return SUCCESS_OUTCOMES.has(outcome) ? {
459
+ status: "completed",
460
+ error: null
461
+ } : failure(terminalReason(parsed.data, outcome));
462
+ }
463
+ return failure(terminalReason(parsed.data, "stream error"));
464
+ }
465
+ /**
466
+ * Classify a complete collected stream. Any failure takes precedence.
467
+ */
468
+ function classifyOpenAITerminalEvents(events) {
469
+ let terminal = null;
470
+ for (const event of events) {
471
+ const current = classifyOpenAITerminal(event);
472
+ if (!current) continue;
473
+ if (current.status === "failed" || terminal === null) terminal = current;
474
+ }
475
+ return terminal;
476
+ }
477
+ //#endregion
382
478
  //#region src/openai/runs.ts
383
479
  const TERMINAL_STATUSES = new Set([
384
480
  "completed",
@@ -425,21 +521,6 @@ function readToolCallFromFrame(event) {
425
521
  requiresAction: false
426
522
  };
427
523
  }
428
- function readTerminal(event) {
429
- if (event.type === "done") {
430
- const outcome = typeof event.outcome === "string" && event.outcome || "success";
431
- if (outcome === "success" || outcome === "completed" || outcome === "succeeded" || outcome === "done") return { status: "completed" };
432
- return {
433
- status: "failed",
434
- reason: typeof event.error === "string" ? event.error : outcome
435
- };
436
- }
437
- if (event.type === "error") return {
438
- status: "failed",
439
- reason: typeof event.message === "string" && event.message || typeof event.error === "string" && event.error || "stream error"
440
- };
441
- return null;
442
- }
443
524
  /**
444
525
  * Internal queue helper: an async iterable backed by a bounded
445
526
  * pending-list with a settle promise that resolves whenever a new
@@ -649,7 +730,7 @@ var Run = class {
649
730
  if (handled === "requires_action") return;
650
731
  continue;
651
732
  }
652
- const terminal = readTerminal(event);
733
+ const terminal = classifyOpenAITerminal(event);
653
734
  if (terminal) {
654
735
  if (terminal.status === "completed") {
655
736
  this.transition("completed");
@@ -658,7 +739,7 @@ var Run = class {
658
739
  this.transition("failed");
659
740
  this.queue.push({
660
741
  type: "failed",
661
- reason: terminal.reason ?? "unknown failure"
742
+ reason: terminal.reason
662
743
  });
663
744
  }
664
745
  this.queue.close();
@@ -666,8 +747,11 @@ var Run = class {
666
747
  }
667
748
  }
668
749
  if (!isTerminal(this._status)) {
669
- this.transition("completed");
670
- this.queue.push({ type: "completed" });
750
+ this.transition("failed");
751
+ this.queue.push({
752
+ type: "failed",
753
+ reason: OPENAI_MISSING_TERMINAL_REASON
754
+ });
671
755
  this.queue.close();
672
756
  }
673
757
  } catch (err) {
@@ -1038,6 +1122,43 @@ function usageFromTokens(usage) {
1038
1122
  output_tokens_details: { reasoning_tokens: 0 }
1039
1123
  };
1040
1124
  }
1125
+ function terminalResponse(event, ctx, terminal) {
1126
+ const tokens = event.type === "done" ? readUsage(event) : null;
1127
+ return {
1128
+ id: ctx.runId,
1129
+ object: "response",
1130
+ created_at: ctx.createdAt,
1131
+ output_text: "",
1132
+ error: terminal.error,
1133
+ incomplete_details: null,
1134
+ instructions: null,
1135
+ metadata: null,
1136
+ model: ctx.modelId,
1137
+ output: [],
1138
+ parallel_tool_calls: false,
1139
+ temperature: null,
1140
+ tool_choice: "auto",
1141
+ tools: [],
1142
+ top_p: null,
1143
+ status: terminal.status,
1144
+ usage: tokens ? usageFromTokens(tokens) : emptyResponseUsage()
1145
+ };
1146
+ }
1147
+ function terminalResponseEvent(event, ctx, extras) {
1148
+ const terminal = classifyOpenAITerminal(event);
1149
+ if (!terminal) return null;
1150
+ const response = terminalResponse(event, ctx, terminal);
1151
+ if (terminal.status === "failed") return {
1152
+ type: "response.failed",
1153
+ sequence_number: nextSeq(extras),
1154
+ response
1155
+ };
1156
+ return {
1157
+ type: "response.completed",
1158
+ sequence_number: nextSeq(extras),
1159
+ response
1160
+ };
1161
+ }
1041
1162
  function sandboxEventToResponsesEvent(event, ctx) {
1042
1163
  const extras = getResponsesExtras(ctx);
1043
1164
  switch (event.type) {
@@ -1089,48 +1210,8 @@ function sandboxEventToResponsesEvent(event, ctx) {
1089
1210
  sequence_number: nextSeq(extras)
1090
1211
  };
1091
1212
  }
1092
- case "error": {
1093
- const outputIndex = extras.messageOutputIndex ?? 0;
1094
- return {
1095
- type: "response.output_item.done",
1096
- item: {
1097
- type: "message",
1098
- id: `msg_${ctx.runId}`,
1099
- role: "assistant",
1100
- status: "incomplete",
1101
- content: []
1102
- },
1103
- output_index: outputIndex,
1104
- sequence_number: nextSeq(extras)
1105
- };
1106
- }
1107
- case "done": {
1108
- const tokens = readUsage(event);
1109
- const usage = tokens ? usageFromTokens(tokens) : emptyResponseUsage();
1110
- return {
1111
- type: "response.completed",
1112
- sequence_number: nextSeq(extras),
1113
- response: {
1114
- id: ctx.runId,
1115
- object: "response",
1116
- created_at: ctx.createdAt,
1117
- output_text: "",
1118
- error: null,
1119
- incomplete_details: null,
1120
- instructions: null,
1121
- metadata: null,
1122
- model: ctx.modelId,
1123
- output: [],
1124
- parallel_tool_calls: false,
1125
- temperature: null,
1126
- tool_choice: "auto",
1127
- tools: [],
1128
- top_p: null,
1129
- status: "completed",
1130
- usage
1131
- }
1132
- };
1133
- }
1213
+ case "error":
1214
+ case "done": return terminalResponseEvent(event, ctx, extras);
1134
1215
  default: return null;
1135
1216
  }
1136
1217
  }
@@ -1705,11 +1786,10 @@ function normalizeResponsesWebSearchForTangleRouter(req, options = {}) {
1705
1786
  } : req;
1706
1787
  }
1707
1788
  function readNativeWebSearchParameters(tool) {
1708
- const input = tool;
1709
1789
  const result = {};
1710
- const searchContextSize = input.search_context_size ?? input.searchContextSize;
1790
+ const searchContextSize = ("search_context_size" in tool ? tool.search_context_size : void 0) ?? ("searchContextSize" in tool ? tool.searchContextSize : void 0);
1711
1791
  if (searchContextSize === "low" || searchContextSize === "medium" || searchContextSize === "high") result.search_context_size = searchContextSize;
1712
- const userLocation = input.user_location ?? input.userLocation;
1792
+ const userLocation = ("user_location" in tool ? tool.user_location : void 0) ?? ("userLocation" in tool ? tool.userLocation : void 0);
1713
1793
  if (userLocation && typeof userLocation === "object") result.user_location = userLocation;
1714
1794
  return result;
1715
1795
  }
@@ -1726,7 +1806,7 @@ function buildResponseShell(args) {
1726
1806
  object: "response",
1727
1807
  created_at: args.createdAt,
1728
1808
  output_text: outputText,
1729
- error: null,
1809
+ error: args.terminal?.error ?? null,
1730
1810
  incomplete_details: null,
1731
1811
  instructions: args.instructions ?? null,
1732
1812
  metadata: null,
@@ -1737,7 +1817,7 @@ function buildResponseShell(args) {
1737
1817
  tool_choice: "auto",
1738
1818
  tools: [],
1739
1819
  top_p: null,
1740
- status: "completed",
1820
+ status: args.terminal?.status ?? "completed",
1741
1821
  usage: args.usage,
1742
1822
  previous_response_id: args.previousResponseId ?? null
1743
1823
  };
@@ -1757,4 +1837,4 @@ function createTranslatorContext(init) {
1757
1837
  };
1758
1838
  }
1759
1839
  //#endregion
1760
- export { EmbeddingValidationError, HookChain, InMemoryResponseStore, ResponsesValidationError, Run, actionRateLimitHook, assembleResponseOutput, auditLogHook, buildResponseShell, costCapHook, createTranslatorContext, destructiveActionGuardHook, egressPolicyHook, normalizeResponsesWebSearchForTangleRouter, openaiMessagesToSandboxInput, outcomeToFinishReason, resolvePreviousResponseId, runEvents, sandboxEventToChatChunk, sandboxEventToCompletionChunk, sandboxEventToResponsesEvent, screenshotRedactionHook, usageFromEvents, validateEmbeddingRequest, validateResponsesRequest };
1840
+ export { EmbeddingValidationError, HookChain, InMemoryResponseStore, ResponsesValidationError, Run, actionRateLimitHook, assembleResponseOutput, auditLogHook, buildResponseShell, classifyOpenAITerminal, classifyOpenAITerminalEvents, costCapHook, createTranslatorContext, destructiveActionGuardHook, egressPolicyHook, normalizeResponsesWebSearchForTangleRouter, openaiMessagesToSandboxInput, outcomeToFinishReason, resolvePreviousResponseId, runEvents, sandboxEventToChatChunk, sandboxEventToCompletionChunk, sandboxEventToResponsesEvent, screenshotRedactionHook, usageFromEvents, validateEmbeddingRequest, validateResponsesRequest };
@@ -1,5 +1,5 @@
1
1
  import { a as NetworkError, c as QuotaError, f as TimeoutError, l as SandboxError, m as parseErrorResponse, n as CapabilityError, p as ValidationError, u as ServerError } from "./errors-C6kn-3zt.js";
2
- import { AgentExactRunControlRefSchema, agentProfileSchema, harnessTypeSchema } from "@tangle-network/agent-interface";
2
+ import { AgentExactRunControlRefSchema, AgentExecutionOutcomeSchema, DurablePlanSchema, InteractionRequestSchema, agentProfileSchema, harnessTypeSchema } from "@tangle-network/agent-interface";
3
3
  import { z } from "zod";
4
4
  //#region ../../../packages/runtime-contracts/dist/backend-types.js
5
5
  /**
@@ -57,6 +57,392 @@ function parseBackendType(value) {
57
57
  return value;
58
58
  }
59
59
  //#endregion
60
+ //#region src/agent-run-outcome-inspection.ts
61
+ const HUB_APPROVAL_REQUIRED = "HUB_APPROVAL_REQUIRED";
62
+ const MAX_SERIALIZED_TOOL_VALUE_BYTES = 4 * 1024 * 1024;
63
+ function parseInteraction(value) {
64
+ const parsed = InteractionRequestSchema.safeParse(value);
65
+ return parsed.success ? parsed.data : void 0;
66
+ }
67
+ function parsePlan(value) {
68
+ const parsed = DurablePlanSchema.safeParse(value);
69
+ return parsed.success ? parsed.data : void 0;
70
+ }
71
+ function parseToolInvocations(value) {
72
+ if (!Array.isArray(value)) return void 0;
73
+ const result = [];
74
+ for (const entry of value) {
75
+ const tool = plainRecord(entry);
76
+ const toolName = readString(tool?.toolName);
77
+ if (!tool || !toolName || tool.isError !== void 0 && typeof tool.isError !== "boolean") return;
78
+ const invocation = { toolName };
79
+ const input = tool.input ?? tool.arguments;
80
+ if (input !== void 0) {
81
+ const copied = copyJsonValue(input);
82
+ if (!copied.success) return void 0;
83
+ invocation.input = copied.value;
84
+ }
85
+ if (tool.result !== void 0) {
86
+ const copied = copyJsonValue(tool.result);
87
+ if (!copied.success) return void 0;
88
+ invocation.result = copied.value;
89
+ }
90
+ if (typeof tool.isError === "boolean") invocation.isError = tool.isError;
91
+ result.push(invocation);
92
+ }
93
+ return result;
94
+ }
95
+ function inspectToolOutcomes(toolInvocations) {
96
+ let approval;
97
+ for (const tool of toolInvocations ?? []) {
98
+ if (tool.isError !== true) continue;
99
+ const inspection = inspectToolResult(tool.result);
100
+ if (!inspection.approvalCode) continue;
101
+ if (approval) return { malformed: true };
102
+ approval = { message: inspection.message ?? "Hub action requires approval" };
103
+ if (inspection.approvalId) approval.approvalId = inspection.approvalId;
104
+ if (inspection.connectionId) approval.connectionId = inspection.connectionId;
105
+ }
106
+ const result = { malformed: false };
107
+ if (approval) result.approval = approval;
108
+ return result;
109
+ }
110
+ function inspectToolResult(value) {
111
+ let root = value;
112
+ if (typeof value === "string") try {
113
+ root = JSON.parse(value);
114
+ } catch {
115
+ return { approvalCode: false };
116
+ }
117
+ const record = plainRecord(root);
118
+ if (!record) return { approvalCode: false };
119
+ const structuredContent = plainRecord(record.structuredContent);
120
+ const envelope = record.code === HUB_APPROVAL_REQUIRED ? record : structuredContent?.code === HUB_APPROVAL_REQUIRED ? structuredContent : void 0;
121
+ if (!envelope) return { approvalCode: false };
122
+ const approval = plainRecord(plainRecord(envelope.details)?.approval) ?? plainRecord(envelope.approval);
123
+ const result = { approvalCode: true };
124
+ const message = readString(envelope.message);
125
+ const approvalId = readString(approval?.id);
126
+ const connectionId = readString(approval?.connectionId);
127
+ if (message) result.message = message;
128
+ if (approvalId) result.approvalId = approvalId;
129
+ if (connectionId) result.connectionId = connectionId;
130
+ return result;
131
+ }
132
+ /** Copy a provider value into a bounded JSON-only value safe for public output. */
133
+ function copyJsonValue(value) {
134
+ try {
135
+ const serialized = JSON.stringify(value);
136
+ if (serialized === void 0 || new TextEncoder().encode(serialized).byteLength > MAX_SERIALIZED_TOOL_VALUE_BYTES) return { success: false };
137
+ return {
138
+ success: true,
139
+ value: JSON.parse(serialized)
140
+ };
141
+ } catch {
142
+ return { success: false };
143
+ }
144
+ }
145
+ function questionFromInteraction(request) {
146
+ return {
147
+ questionId: request.id,
148
+ questions: request.answerSpec.fields.map((field) => field.type === "select" ? {
149
+ question: field.label,
150
+ options: field.options.map((option) => {
151
+ const value = { label: option.label };
152
+ if (option.description) value.description = option.description;
153
+ return value;
154
+ }),
155
+ multiSelect: field.multi === true
156
+ } : { question: field.label })
157
+ };
158
+ }
159
+ function normalizeStatus(value) {
160
+ return readString(value)?.toLowerCase().replaceAll("-", "_");
161
+ }
162
+ function readString(value) {
163
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
164
+ }
165
+ function readError(value) {
166
+ if (!value) return void 0;
167
+ const direct = readString(value.error);
168
+ if (direct) return direct;
169
+ return readString(plainRecord(value.error)?.message) ?? readString(value.message);
170
+ }
171
+ function plainRecord(value) {
172
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
173
+ }
174
+ //#endregion
175
+ //#region src/agent-run-outcome.ts
176
+ const FAILURE_STATUSES = new Set([
177
+ "canceled",
178
+ "cancelled",
179
+ "error",
180
+ "errored",
181
+ "failed",
182
+ "failure",
183
+ "timed_out",
184
+ "timeout"
185
+ ]);
186
+ const SUCCESS_STATUSES = new Set([
187
+ "completed",
188
+ "done",
189
+ "ok",
190
+ "success"
191
+ ]);
192
+ const WAIT_STATUSES = new Map([
193
+ ["awaiting_interaction", "interaction"],
194
+ ["awaiting_plan_decision", "plan"],
195
+ ["awaiting_question", "question"],
196
+ ["blocked_on_approval", "approval"]
197
+ ]);
198
+ /** Track one run and settle its outcome only after its event stream ends. */
199
+ function createAgentRunOutcomeTracker() {
200
+ const state = createOutcomeState();
201
+ let result;
202
+ return {
203
+ observe(event) {
204
+ if (result) throw new Error("Agent run outcome tracker is already finished");
205
+ observeEvent(state, event);
206
+ },
207
+ finish() {
208
+ result ??= settleOutcome(state);
209
+ return result;
210
+ }
211
+ };
212
+ }
213
+ /** Settle a complete in-memory event sequence with the streaming tracker. */
214
+ function deriveAgentRunOutcome(events) {
215
+ const tracker = createAgentRunOutcomeTracker();
216
+ for (const event of events) tracker.observe(event);
217
+ return tracker.finish();
218
+ }
219
+ /** Settle one completed result record without treating live result frames as terminal. */
220
+ function deriveAgentResultOutcome(result) {
221
+ const state = createOutcomeState();
222
+ observeOutcomeFrame(state, "result", result, true);
223
+ return settleOutcome(state);
224
+ }
225
+ function createOutcomeState() {
226
+ return {
227
+ terminalReached: false,
228
+ terminalStatuses: /* @__PURE__ */ new Set(),
229
+ malformed: false
230
+ };
231
+ }
232
+ function observeEvent(state, event) {
233
+ try {
234
+ if (!event || typeof event !== "object") {
235
+ state.malformed = true;
236
+ return;
237
+ }
238
+ const type = typeof event.type === "string" ? event.type : "";
239
+ const data = plainRecord(event.data);
240
+ if (!type || !data) {
241
+ state.malformed = true;
242
+ return;
243
+ }
244
+ if (type === "error") {
245
+ state.runError ??= readError(data) ?? "Agent execution failed";
246
+ state.runErrorCode ??= readString(data.code);
247
+ }
248
+ if (type === "interaction") observeInteraction(state, data);
249
+ if (type === "interaction.cancel") observeInteractionCancel(state, data);
250
+ if (type === "plan.submitted") observePlan(state, data);
251
+ if (type === "result") observeOutcomeFrame(state, type, data, false);
252
+ if (type === "done") observeOutcomeFrame(state, type, data, true);
253
+ } catch {
254
+ state.malformed = true;
255
+ }
256
+ }
257
+ function observeInteraction(state, data) {
258
+ const request = parseInteraction(data.request);
259
+ if (!request) {
260
+ state.malformed = true;
261
+ return;
262
+ }
263
+ retainInteraction(state, request);
264
+ }
265
+ function observeInteractionCancel(state, data) {
266
+ const id = readString(data.id);
267
+ if (!id) {
268
+ state.malformed = true;
269
+ return;
270
+ }
271
+ if (state.interaction?.id === id) state.interaction = void 0;
272
+ }
273
+ function observePlan(state, data) {
274
+ const plan = parsePlan(data.plan);
275
+ if (!plan) {
276
+ state.malformed = true;
277
+ return;
278
+ }
279
+ retainPlan(state, plan);
280
+ }
281
+ function observeOutcomeFrame(state, type, data, terminal) {
282
+ if (terminal) state.terminalReached = true;
283
+ if (data.success !== void 0 && typeof data.success !== "boolean") state.malformed = true;
284
+ if (typeof data.success === "boolean") {
285
+ if (state.terminalSuccess !== void 0 && state.terminalSuccess !== data.success) state.malformed = true;
286
+ state.terminalSuccess = data.success;
287
+ }
288
+ if (type === "done" || data.outcome !== void 0) {
289
+ if (!AgentExecutionOutcomeSchema.safeParse(data.outcome).success) state.malformed = true;
290
+ }
291
+ const outcome = plainRecord(data.outcome);
292
+ const nestedResult = plainRecord(data.result);
293
+ for (const value of [
294
+ data.status,
295
+ outcome?.type,
296
+ outcome?.status,
297
+ nestedResult?.status
298
+ ]) {
299
+ if (value === void 0) continue;
300
+ const status = normalizeStatus(value);
301
+ if (status) state.terminalStatuses.add(status);
302
+ else state.malformed = true;
303
+ }
304
+ state.terminalError ??= readError(data) ?? readError(outcome) ?? readError(nestedResult);
305
+ state.terminalErrorCode ??= readString(data.errorCode) ?? readString(data.code) ?? readString(outcome?.errorCode) ?? readString(outcome?.code) ?? readString(nestedResult?.errorCode) ?? readString(nestedResult?.code);
306
+ const interactionCandidate = data.interaction ?? nestedResult?.interaction;
307
+ if (interactionCandidate !== void 0) {
308
+ const interaction = parseInteraction(interactionCandidate);
309
+ if (interaction) retainInteraction(state, interaction);
310
+ else state.malformed = true;
311
+ }
312
+ const planCandidate = data.plan ?? (outcome?.type === "awaiting_plan_decision" ? outcome.plan : void 0) ?? (nestedResult?.status === "awaiting_plan_decision" ? nestedResult.plan : void 0);
313
+ if (planCandidate !== void 0) {
314
+ const plan = parsePlan(planCandidate);
315
+ if (plan) retainPlan(state, plan);
316
+ else state.malformed = true;
317
+ }
318
+ const invocationCandidate = data.toolInvocations ?? nestedResult?.toolInvocations;
319
+ if (invocationCandidate !== void 0) {
320
+ const invocations = parseToolInvocations(invocationCandidate);
321
+ if (invocations) retainToolInvocations(state, invocations);
322
+ else state.malformed = true;
323
+ }
324
+ const toolOutcome = inspectToolOutcomes(state.toolInvocations);
325
+ if (toolOutcome.malformed) state.malformed = true;
326
+ state.approval = toolOutcome.approval;
327
+ }
328
+ function settleOutcome(state) {
329
+ const common = outcomeDetails(state);
330
+ if (state.runError) return failed(state.runError, state.runErrorCode, common);
331
+ const statusGroups = classifyStatuses(state.terminalStatuses);
332
+ if (statusGroups.failure) return failed(state.terminalError ?? `Agent execution ended with status ${statusGroups.failure}`, state.terminalErrorCode, common);
333
+ if (!state.terminalReached) return failed("Agent stream ended without a terminal event", void 0, common);
334
+ if (state.malformed || statusGroups.unknown || statusGroups.conflicting) return failed("Agent terminal outcome is malformed", void 0, common);
335
+ const wait = selectWait(state);
336
+ if (wait.error) return failed(wait.error, void 0, common);
337
+ if (statusGroups.wait && statusGroups.wait !== wait.kind) return failed("Agent terminal wait does not match its response request", void 0, common);
338
+ if (state.terminalSuccess === false && !wait.kind) return failed(state.terminalError ?? "Agent execution failed", state.terminalErrorCode, common);
339
+ if (state.terminalError && !statusGroups.wait) return failed(state.terminalError, state.terminalErrorCode, common);
340
+ if (wait.kind) return waitingOutcome(wait.kind, state, common);
341
+ return {
342
+ success: true,
343
+ status: "success",
344
+ ...common
345
+ };
346
+ }
347
+ function classifyStatuses(statuses) {
348
+ let failure;
349
+ let wait;
350
+ let success = false;
351
+ let unknown = false;
352
+ let conflicting = false;
353
+ for (const status of statuses) if (FAILURE_STATUSES.has(status)) failure ??= status;
354
+ else if (SUCCESS_STATUSES.has(status)) success = true;
355
+ else if (WAIT_STATUSES.has(status)) {
356
+ const next = WAIT_STATUSES.get(status);
357
+ if (!next) continue;
358
+ if (wait && wait !== next) conflicting = true;
359
+ wait = next;
360
+ } else unknown = true;
361
+ if (failure && (wait || success)) conflicting = true;
362
+ return {
363
+ failure,
364
+ wait,
365
+ success,
366
+ unknown,
367
+ conflicting
368
+ };
369
+ }
370
+ function selectWait(state) {
371
+ const kinds = [];
372
+ if (state.plan) kinds.push("plan");
373
+ if (state.approval) kinds.push("approval");
374
+ if (state.interaction) kinds.push(state.interaction.kind === "question" ? "question" : "interaction");
375
+ if (kinds.length > 1) return { error: "Agent terminal outcome contains conflicting response requests" };
376
+ return { kind: kinds[0] };
377
+ }
378
+ function waitingOutcome(kind, state, common) {
379
+ if (kind === "plan") return {
380
+ success: false,
381
+ status: "awaiting_plan_decision",
382
+ error: "Agent is awaiting a durable plan decision",
383
+ ...common
384
+ };
385
+ if (kind === "approval") return {
386
+ success: false,
387
+ status: "blocked_on_approval",
388
+ error: state.approval?.message ?? "Agent is awaiting approval",
389
+ ...common
390
+ };
391
+ if (kind === "question") return {
392
+ success: false,
393
+ status: "awaiting_question",
394
+ error: "Agent is awaiting an answer",
395
+ ...common
396
+ };
397
+ return {
398
+ success: false,
399
+ status: "awaiting_interaction",
400
+ error: "Agent is awaiting a response",
401
+ ...common
402
+ };
403
+ }
404
+ function outcomeDetails(state) {
405
+ const question = state.interaction?.kind === "question" ? questionFromInteraction(state.interaction) : void 0;
406
+ const details = {};
407
+ if (state.toolInvocations) details.toolInvocations = state.toolInvocations;
408
+ if (state.approval) details.approval = state.approval;
409
+ if (state.interaction) details.interaction = state.interaction;
410
+ if (question) details.question = question;
411
+ if (state.plan) details.plan = state.plan;
412
+ return details;
413
+ }
414
+ function failed(error, errorCode, common) {
415
+ const outcome = {
416
+ success: false,
417
+ status: "failed",
418
+ error,
419
+ ...common
420
+ };
421
+ if (errorCode) outcome.errorCode = errorCode;
422
+ return outcome;
423
+ }
424
+ function retainPlan(state, plan) {
425
+ if (state.plan && (state.plan.id !== plan.id || state.plan.revision !== plan.revision || state.plan.title !== plan.title || state.plan.body !== plan.body || state.plan.submittedAt !== plan.submittedAt)) {
426
+ state.malformed = true;
427
+ return;
428
+ }
429
+ state.plan = plan;
430
+ }
431
+ function retainInteraction(state, interaction) {
432
+ if (state.interaction && state.interaction.requestDigest !== interaction.requestDigest) {
433
+ state.malformed = true;
434
+ return;
435
+ }
436
+ state.interaction = interaction;
437
+ }
438
+ function retainToolInvocations(state, invocations) {
439
+ if (state.toolInvocations && JSON.stringify(state.toolInvocations) !== JSON.stringify(invocations)) {
440
+ state.malformed = true;
441
+ return;
442
+ }
443
+ state.toolInvocations = invocations;
444
+ }
445
+ //#endregion
60
446
  //#region src/backend-config.ts
61
447
  function assertCurrentBackendType(value, field = "backend.type") {
62
448
  const parsed = backendTypeSchema.safeParse(value);
@@ -825,4 +1211,4 @@ function normalizeSessionInfo(raw) {
825
1211
  };
826
1212
  }
827
1213
  //#endregion
828
- export { uploadChunked as a, combineAbortSignals as c, applyRequestedModel as d, assertCurrentBackendType as f, parseBackendType as h, sessionHeaders as i, encodePromptForWire as l, backendTypeSchema as m, normalizeSessionInfo as n, SANDBOX_PROXY_REQUEST_MAX_BYTES as o, normalizeRuntimeBackendConfig as p, appendSessionIdQuery as r, FILE_DECODED_WRITE_MAX_BYTES as s, SandboxRuntimeApi as t, encodeTextForWire as u };
1214
+ export { backendTypeSchema as _, uploadChunked as a, combineAbortSignals as c, applyRequestedModel as d, assertCurrentBackendType as f, deriveAgentRunOutcome as g, deriveAgentResultOutcome as h, sessionHeaders as i, encodePromptForWire as l, createAgentRunOutcomeTracker as m, normalizeSessionInfo as n, SANDBOX_PROXY_REQUEST_MAX_BYTES as o, normalizeRuntimeBackendConfig as p, appendSessionIdQuery as r, FILE_DECODED_WRITE_MAX_BYTES as s, SandboxRuntimeApi as t, encodeTextForWire as u, parseBackendType as v };