@zhivex-ai/core 0.19.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +19 -1
  2. package/dist/agent-control-plane.d.ts +4 -1
  3. package/dist/agent-control-plane.d.ts.map +1 -1
  4. package/dist/agent-control-plane.js +61 -32
  5. package/dist/agent-control-plane.js.map +1 -1
  6. package/dist/agent-evaluation.d.ts +5 -1
  7. package/dist/agent-evaluation.d.ts.map +1 -1
  8. package/dist/agent-evaluation.js +17 -0
  9. package/dist/agent-evaluation.js.map +1 -1
  10. package/dist/agent-harness.d.ts +9 -0
  11. package/dist/agent-harness.d.ts.map +1 -0
  12. package/dist/agent-harness.js +66 -0
  13. package/dist/agent-harness.js.map +1 -0
  14. package/dist/agent-state.d.ts.map +1 -1
  15. package/dist/agent-state.js +112 -0
  16. package/dist/agent-state.js.map +1 -1
  17. package/dist/agent.d.ts +24 -15
  18. package/dist/agent.d.ts.map +1 -1
  19. package/dist/agent.js +692 -39
  20. package/dist/agent.js.map +1 -1
  21. package/dist/api-stability.d.ts.map +1 -1
  22. package/dist/api-stability.js +4 -0
  23. package/dist/api-stability.js.map +1 -1
  24. package/dist/catalog.d.ts.map +1 -1
  25. package/dist/catalog.js +10 -0
  26. package/dist/catalog.js.map +1 -1
  27. package/dist/errors.d.ts +2 -2
  28. package/dist/errors.d.ts.map +1 -1
  29. package/dist/errors.js.map +1 -1
  30. package/dist/generate-object.d.ts.map +1 -1
  31. package/dist/generate-object.js +7 -2
  32. package/dist/generate-object.js.map +1 -1
  33. package/dist/generate-text.d.ts +5 -4
  34. package/dist/generate-text.d.ts.map +1 -1
  35. package/dist/generate-text.js +482 -70
  36. package/dist/generate-text.js.map +1 -1
  37. package/dist/index.d.ts +2 -1
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +1 -0
  40. package/dist/index.js.map +1 -1
  41. package/dist/mcp.d.ts +22 -3
  42. package/dist/mcp.d.ts.map +1 -1
  43. package/dist/mcp.js +164 -18
  44. package/dist/mcp.js.map +1 -1
  45. package/dist/messages.d.ts +5 -5
  46. package/dist/messages.d.ts.map +1 -1
  47. package/dist/messages.js.map +1 -1
  48. package/dist/safety-policy.d.ts.map +1 -1
  49. package/dist/safety-policy.js +2 -3
  50. package/dist/safety-policy.js.map +1 -1
  51. package/dist/stream.d.ts +4 -0
  52. package/dist/stream.d.ts.map +1 -1
  53. package/dist/stream.js +29 -0
  54. package/dist/stream.js.map +1 -1
  55. package/dist/structured-output-prompt.d.ts +6 -0
  56. package/dist/structured-output-prompt.d.ts.map +1 -0
  57. package/dist/structured-output-prompt.js +26 -0
  58. package/dist/structured-output-prompt.js.map +1 -0
  59. package/dist/tool-execution-suspension.d.ts +8 -0
  60. package/dist/tool-execution-suspension.d.ts.map +1 -0
  61. package/dist/tool-execution-suspension.js +12 -0
  62. package/dist/tool-execution-suspension.js.map +1 -0
  63. package/dist/types.d.ts +335 -28
  64. package/dist/types.d.ts.map +1 -1
  65. package/dist/ui.d.ts +5 -1
  66. package/dist/ui.d.ts.map +1 -1
  67. package/dist/ui.js +68 -2
  68. package/dist/ui.js.map +1 -1
  69. package/package.json +1 -1
package/dist/agent.js CHANGED
@@ -1,14 +1,17 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { createAgentApprovalMessage, getAgentApprovalRequests } from "./agent-approval.js";
3
3
  import { createAgentHandoffMessage } from "./agent-handoff.js";
4
+ import { createAgentExecutionEnvironmentBinding, fingerprintAgentHarness } from "./agent-harness.js";
4
5
  import { AGENT_RUN_STATE_SCHEMA_VERSION, normalizeAgentRunState } from "./agent-state.js";
5
6
  import { BoundedReplayBroadcast } from "./bounded-broadcast.js";
6
- import { ConflictError, GuardrailTriggeredError, ValidationError } from "./errors.js";
7
+ import { ConflictError, GuardrailTriggeredError, UnsupportedFeatureError, ValidationError } from "./errors.js";
7
8
  import { aggregateTokenUsage, generateText, getGenerateTextStepTiming, normalizeMessages, streamText } from "./generate-text.js";
8
- import { isCallableToolDefinition, serializeJsonValue } from "./messages.js";
9
+ import { createTextMessage, isCallableToolDefinition, serializeJsonValue } from "./messages.js";
9
10
  import { mergeAbortSignals } from "./runtime.js";
10
11
  import { evaluateAgentBudgetPreflight, getAgentBudgetStatus } from "./safety-policy.js";
11
12
  import { createSecureId } from "./secure-id.js";
13
+ import { createStructuredOutputPrompt } from "./structured-output-prompt.js";
14
+ import { ToolExecutionSuspendedError } from "./tool-execution-suspension.js";
12
15
  import { toToolSet } from "./tool-registry.js";
13
16
  import { z } from "zod";
14
17
  const randomId = createSecureId;
@@ -26,6 +29,26 @@ const joinInstructions = (...parts) => {
26
29
  const content = parts.map((part) => part?.trim()).filter((part) => Boolean(part));
27
30
  return content.length ? content.join("\n\n") : undefined;
28
31
  };
32
+ const resolveAgentOutputMode = (agent) => {
33
+ if (!agent.outputSchema) {
34
+ return undefined;
35
+ }
36
+ const requested = agent.outputMode ?? "auto";
37
+ if (requested === "native" && !agent.model.capabilities.structuredOutput) {
38
+ throw new UnsupportedFeatureError(`Model "${agent.model.provider}/${agent.model.modelId}" does not support native structured output.`);
39
+ }
40
+ return requested === "auto"
41
+ ? agent.model.capabilities.structuredOutput
42
+ ? "native"
43
+ : "prompted"
44
+ : requested;
45
+ };
46
+ const promptedOutputInstruction = (agent) => agent.outputSchema && resolveAgentOutputMode(agent) === "prompted"
47
+ ? createStructuredOutputPrompt(agent.outputSchema, {
48
+ name: agent.outputName,
49
+ description: agent.outputDescription
50
+ })
51
+ : undefined;
29
52
  const hasToolCalls = (messages) => messages.some((message) => message.parts.some((part) => part.type === "tool-call"));
30
53
  const snapshotRequest = (request, messageOffset = 0, messages = request.messages) => ({
31
54
  messageOffset,
@@ -48,9 +71,20 @@ const snapshotResponse = (response) => ({
48
71
  usage: response.usage
49
72
  });
50
73
  const countToolCalls = (messages) => messages.reduce((total, message) => total + message.parts.filter((part) => part.type === "tool-call").length, 0);
74
+ const messagePrefixLength = (prefix, messages) => {
75
+ if (prefix.length > messages.length) {
76
+ return 0;
77
+ }
78
+ for (let index = 0; index < prefix.length; index += 1) {
79
+ if (JSON.stringify(prefix[index]) !== JSON.stringify(messages[index])) {
80
+ return 0;
81
+ }
82
+ }
83
+ return prefix.length;
84
+ };
51
85
  const mapSteps = (steps, offset, toolResults) => {
52
86
  let toolResultCursor = 0;
53
- let previousMessageCount = 0;
87
+ let previousMessages = [];
54
88
  return steps.map((step, index) => {
55
89
  const response = snapshotResponse(step.response);
56
90
  const toolCallCount = countToolCalls(response.messages);
@@ -58,9 +92,9 @@ const mapSteps = (steps, offset, toolResults) => {
58
92
  toolResultCursor += toolCallCount;
59
93
  const timing = getGenerateTextStepTiming(step.request);
60
94
  const finishedAt = timing?.finishedAt ?? Date.now();
61
- const messageOffset = index === 0 ? 0 : previousMessageCount;
95
+ const messageOffset = index === 0 ? 0 : messagePrefixLength(previousMessages, step.request.messages);
62
96
  const incrementalMessages = step.request.messages.slice(messageOffset);
63
- previousMessageCount = step.request.messages.length;
97
+ previousMessages = step.request.messages;
64
98
  return {
65
99
  index: offset + index + 1,
66
100
  status: "completed",
@@ -79,6 +113,9 @@ const cloneMetadata = (...values) => {
79
113
  const toOutput = (state) => ({
80
114
  status: state.status,
81
115
  outputText: state.outputText,
116
+ finalOutput: state.status === "completed" && state.finalOutput !== undefined
117
+ ? state.finalOutput
118
+ : undefined,
82
119
  finishReason: state.finishReason,
83
120
  providerFinishReason: state.providerFinishReason,
84
121
  usage: state.usage,
@@ -90,7 +127,7 @@ const toOutput = (state) => ({
90
127
  });
91
128
  const normalizeApprovalStatus = (status) => status === "suspended" ? "waiting_approval" : status;
92
129
  const cloneState = (state) => JSON.parse(JSON.stringify(normalizeAgentRunState(state)));
93
- const createBaseState = (provider, modelId, initialMessages, maxSteps, metadata, agentId, runId, handoff, parentRunId, idempotencyKey, scope) => {
130
+ const createBaseState = (provider, modelId, initialMessages, maxSteps, metadata, agentId, runId, handoff, parentRunId, idempotencyKey, scope, outputMode, harness, executionEnvironment) => {
94
131
  const startedAt = Date.now();
95
132
  return {
96
133
  schemaVersion: AGENT_RUN_STATE_SCHEMA_VERSION,
@@ -102,6 +139,8 @@ const createBaseState = (provider, modelId, initialMessages, maxSteps, metadata,
102
139
  parentRunId: parentRunId ?? handoff?.fromRunId,
103
140
  provider,
104
141
  modelId,
142
+ harness,
143
+ executionEnvironment,
105
144
  status: "running",
106
145
  messages: initialMessages,
107
146
  steps: [],
@@ -109,7 +148,10 @@ const createBaseState = (provider, modelId, initialMessages, maxSteps, metadata,
109
148
  currentStep: 0,
110
149
  maxSteps,
111
150
  outputText: "",
151
+ outputMode,
112
152
  pendingApprovals: [],
153
+ approvalHistory: [],
154
+ compactions: [],
113
155
  metadata,
114
156
  handoff,
115
157
  startedAt,
@@ -198,7 +240,7 @@ const prepareFreshMessages = async (agent, input, runId) => {
198
240
  let messages = normalizeMessages({
199
241
  prompt: input.prompt,
200
242
  messages: input.messages,
201
- system: joinInstructions(agent.instructions, input.system)
243
+ system: joinInstructions(agent.instructions, input.system, promptedOutputInstruction(agent))
202
244
  });
203
245
  const handoffMessages = input.handoff
204
246
  ? [createAgentHandoffMessage(input.handoff), ...input.handoff.contextMessages.filter((message) => message.role !== "system")]
@@ -218,11 +260,17 @@ const prepareFreshMessages = async (agent, input, runId) => {
218
260
  memoryMessages
219
261
  };
220
262
  };
221
- const applyApprovalResponses = (messages, approvals, pendingApprovals) => {
263
+ const localApprovalResolutionPayload = (inputDigest, approve, reason) => JSON.stringify({
264
+ inputDigest,
265
+ approve,
266
+ reason: reason ?? null
267
+ });
268
+ const applyApprovalResponses = async (messages, approvals, pendingApprovals, approvalHistory = [], signer) => {
222
269
  if (!approvals?.length) {
223
270
  return {
224
271
  messages,
225
- pendingApprovals
272
+ pendingApprovals,
273
+ approvalHistory
226
274
  };
227
275
  }
228
276
  const pendingById = new Map(pendingApprovals.map((approval) => [approval.id, approval]));
@@ -235,17 +283,85 @@ const applyApprovalResponses = (messages, approvals, pendingApprovals) => {
235
283
  throw new ValidationError(`Approval request "${approval.approvalRequestId}" belongs to provider "${pending.provider}", not "${approval.provider}".`);
236
284
  }
237
285
  }
286
+ const providerApprovals = approvals.filter((approval) => {
287
+ const pending = pendingById.get(approval.approvalRequestId);
288
+ return pending?.kind === undefined || pending.kind === "provider";
289
+ });
290
+ const localResolutions = [];
291
+ const subagentResolutions = [];
292
+ for (const approval of approvals) {
293
+ const pending = pendingById.get(approval.approvalRequestId);
294
+ if (!pending) {
295
+ continue;
296
+ }
297
+ if (pending.kind === "subagent") {
298
+ subagentResolutions.push({
299
+ requestId: pending.id,
300
+ kind: "subagent",
301
+ provider: pending.provider,
302
+ approve: approval.approve,
303
+ reason: approval.reason,
304
+ toolCallId: pending.toolCallId,
305
+ childRunId: pending.childRunId,
306
+ childAgentId: pending.childAgentId,
307
+ childApprovalRequestId: pending.childApprovalRequestId,
308
+ resolvedAt: Date.now()
309
+ });
310
+ continue;
311
+ }
312
+ if (pending.kind !== "local-tool") {
313
+ continue;
314
+ }
315
+ if (signer) {
316
+ if (!pending.inputDigest || !pending.signature) {
317
+ throw new ValidationError(`Approval request "${pending.id}" is missing its required signature.`);
318
+ }
319
+ const requestSignatureValid = signer.verify
320
+ ? await signer.verify(pending.inputDigest, pending.signature)
321
+ : (await signer.sign(pending.inputDigest)) === pending.signature;
322
+ if (!requestSignatureValid) {
323
+ throw new ValidationError(`Approval request "${pending.id}" has an invalid signature.`);
324
+ }
325
+ }
326
+ const resolutionSignature = signer && pending.inputDigest
327
+ ? await signer.sign(localApprovalResolutionPayload(pending.inputDigest, approval.approve, approval.reason))
328
+ : undefined;
329
+ localResolutions.push({
330
+ requestId: pending.id,
331
+ kind: "local-tool",
332
+ provider: pending.provider,
333
+ approve: approval.approve,
334
+ reason: approval.reason,
335
+ toolCallId: pending.toolCallId,
336
+ step: pending.step,
337
+ inputDigest: pending.inputDigest,
338
+ toolVersion: pending.toolVersion,
339
+ signature: resolutionSignature,
340
+ resolvedAt: Date.now()
341
+ });
342
+ }
238
343
  return {
239
- messages: [...messages, createAgentApprovalMessage(approvals)],
240
- pendingApprovals: pendingApprovals.filter((pending) => !approvals.some((approval) => approval.approvalRequestId === pending.id))
344
+ messages: providerApprovals.length
345
+ ? [...messages, createAgentApprovalMessage(providerApprovals)]
346
+ : messages,
347
+ pendingApprovals: pendingApprovals.filter((pending) => !approvals.some((approval) => approval.approvalRequestId === pending.id)),
348
+ approvalHistory: [
349
+ ...approvalHistory.filter((existing) => !localResolutions.some((resolution) => resolution.requestId === existing.requestId) &&
350
+ !subagentResolutions.some((resolution) => resolution.requestId === existing.requestId)),
351
+ ...localResolutions,
352
+ ...subagentResolutions
353
+ ]
241
354
  };
242
355
  };
243
- const finalizeState = (state, result, newSteps, newToolResults) => {
356
+ const finalizeState = (agent, state, result, newSteps, newToolResults) => {
244
357
  const nextCurrentStep = state.currentStep + newSteps.length;
245
358
  const exhausted = nextCurrentStep >= state.maxSteps;
246
359
  const lastStep = newSteps.at(-1);
247
360
  const unresolvedToolCalls = lastStep?.response ? hasToolCalls(lastStep.response.messages) : false;
248
- const pendingApprovals = getAgentApprovalRequests(newSteps.flatMap((step) => step.response?.messages ?? []));
361
+ const pendingApprovals = [
362
+ ...(result.approvalRequests ?? []),
363
+ ...getAgentApprovalRequests(newSteps.flatMap((step) => step.response?.messages ?? []))
364
+ ];
249
365
  if (pendingApprovals.length) {
250
366
  state.status = "waiting_approval";
251
367
  state.error = undefined;
@@ -272,6 +388,23 @@ const finalizeState = (state, result, newSteps, newToolResults) => {
272
388
  state.toolResults = [...state.toolResults, ...newToolResults];
273
389
  state.currentStep = nextCurrentStep;
274
390
  state.outputText = result.text;
391
+ if (state.status === "completed") {
392
+ const terminalText = result.steps.at(-1)?.response.text ?? result.text;
393
+ if (agent.outputSchema) {
394
+ let parsedJson;
395
+ try {
396
+ parsedJson = JSON.parse(terminalText);
397
+ }
398
+ catch (error) {
399
+ throw new ValidationError("Agent final output is not valid JSON.", { cause: error });
400
+ }
401
+ const parsedOutput = agent.outputSchema.safeParse(parsedJson);
402
+ if (!parsedOutput.success) {
403
+ throw new ValidationError(`Agent final output validation failed: ${parsedOutput.error.message}`);
404
+ }
405
+ state.finalOutput = serializeJsonValue(parsedOutput.data);
406
+ }
407
+ }
275
408
  state.finishReason = result.finishReason;
276
409
  state.providerFinishReason = result.providerFinishReason;
277
410
  state.usage = aggregateTokenUsage([state.usage, result.usage]);
@@ -293,6 +426,7 @@ const defaultSubAgentToolName = (agent) => {
293
426
  const countToolCallsInSteps = (steps) => steps.reduce((total, step) => total + countToolCalls(step.response?.messages ?? []), 0);
294
427
  const countToolErrors = (toolResults) => toolResults.filter((result) => result.isError).length;
295
428
  export const createSubAgentTool = (options) => {
429
+ const runtimeState = options.runtimeState;
296
430
  const toolName = options.toolName ?? options.name ?? defaultSubAgentToolName(options.agent);
297
431
  const metadata = {
298
432
  type: "subagent"
@@ -313,7 +447,7 @@ export const createSubAgentTool = (options) => {
313
447
  schema: subAgentToolInputSchema,
314
448
  requiresApproval: options.requiresApproval,
315
449
  metadata: cloneMetadata(metadata, options.metadata),
316
- execute: async (input) => {
450
+ execute: async (input, executionContext) => {
317
451
  await options.onStart?.({
318
452
  toolName,
319
453
  childAgentId: options.agent.id,
@@ -328,14 +462,38 @@ export const createSubAgentTool = (options) => {
328
462
  if (options.parentAgentId) {
329
463
  childMetadata.parentAgentId = options.parentAgentId;
330
464
  }
331
- const output = await runAgent(options.agent, {
332
- prompt: input.prompt,
333
- system: joinInstructions(options.system, input.system),
334
- parentRunId: options.parentRunId,
335
- scope: options.scope,
336
- maxSteps: options.maxSteps,
337
- metadata: cloneMetadata(options.metadata, childMetadata)
338
- });
465
+ const toolCallId = executionContext?.toolCall.id;
466
+ const checkpoint = runtimeState?.childRuns?.find((childRun) => childRun.toolCallId === toolCallId && childRun.resumeState);
467
+ const childApprovalResponses = checkpoint
468
+ ? (runtimeState?.approvalHistory ?? [])
469
+ .filter((resolution) => resolution.kind === "subagent" &&
470
+ resolution.toolCallId === toolCallId &&
471
+ resolution.childRunId === checkpoint.runId &&
472
+ resolution.childApprovalRequestId)
473
+ .map((resolution) => ({
474
+ provider: resolution.provider,
475
+ approvalRequestId: resolution.childApprovalRequestId,
476
+ approve: resolution.approve,
477
+ reason: resolution.reason
478
+ }))
479
+ : [];
480
+ const output = checkpoint?.resumeState
481
+ ? await resumeAgent(options.agent, {
482
+ state: checkpoint.resumeState,
483
+ approvals: childApprovalResponses,
484
+ scope: options.scope,
485
+ context: executionContext?.context,
486
+ maxSteps: options.maxSteps
487
+ })
488
+ : await runAgent(options.agent, {
489
+ prompt: input.prompt,
490
+ system: joinInstructions(options.system, input.system),
491
+ parentRunId: options.parentRunId,
492
+ scope: options.scope,
493
+ context: executionContext?.context,
494
+ maxSteps: options.maxSteps,
495
+ metadata: cloneMetadata(options.metadata, childMetadata)
496
+ });
339
497
  const childRun = {
340
498
  runId: output.state.runId,
341
499
  status: output.status,
@@ -344,6 +502,9 @@ export const createSubAgentTool = (options) => {
344
502
  toolCalls: countToolCallsInSteps(output.steps),
345
503
  toolErrors: countToolErrors(output.toolResults)
346
504
  };
505
+ if (toolCallId) {
506
+ childRun.toolCallId = toolCallId;
507
+ }
347
508
  if (output.state.agentId) {
348
509
  childRun.agentId = output.state.agentId;
349
510
  }
@@ -366,7 +527,38 @@ export const createSubAgentTool = (options) => {
366
527
  if (output.state.metadata) {
367
528
  childRun.metadata = output.state.metadata;
368
529
  }
530
+ if (output.status === "waiting_approval" && output.state.pendingApprovals.length) {
531
+ childRun.resumeState = output.state;
532
+ }
369
533
  await options.onFinish?.(childRun);
534
+ if (childRun.resumeState) {
535
+ const approvals = childRun.resumeState.pendingApprovals.map((approval) => {
536
+ const id = `subapproval_${createHash("sha256")
537
+ .update(`${options.parentRunId ?? ""}\0${toolCallId ?? ""}\0${childRun.runId}\0${approval.id}`)
538
+ .digest("hex")}`;
539
+ return {
540
+ kind: "subagent",
541
+ provider: approval.provider,
542
+ id,
543
+ name: approval.name,
544
+ arguments: approval.arguments,
545
+ serverLabel: approval.serverLabel,
546
+ toolCallId,
547
+ step: executionContext?.step,
548
+ childRunId: childRun.runId,
549
+ childAgentId: childRun.agentId,
550
+ childApprovalRequestId: approval.id,
551
+ rawData: {
552
+ type: "subagent_approval_request",
553
+ childRunId: childRun.runId,
554
+ childAgentId: childRun.agentId ?? null,
555
+ childApprovalRequestId: approval.id,
556
+ approval: approval.rawData
557
+ }
558
+ };
559
+ });
560
+ throw new ToolExecutionSuspendedError(approvals);
561
+ }
370
562
  return serializeJsonValue(childRun);
371
563
  }
372
564
  };
@@ -477,7 +669,71 @@ const runGuardrails = async (agent, state, stage, guardrails, requestFactory) =>
477
669
  }
478
670
  return undefined;
479
671
  };
672
+ const bindDurableRuntime = (agent, input, state, executionEnvironment) => {
673
+ const policy = {
674
+ ...(agent.policy ?? {}),
675
+ ...(input.policy ?? {})
676
+ };
677
+ if (state.harness && !agent.harness) {
678
+ throw new ConflictError(`Agent run "${state.runId}" is bound to harness "${state.harness.id}", but the current agent has no harness binding.`);
679
+ }
680
+ if (state.harness && agent.harness) {
681
+ if (state.harness.id !== agent.harness.id ||
682
+ state.harness.version !== agent.harness.version ||
683
+ state.harness.fingerprint !== agent.harness.fingerprint) {
684
+ throw new ConflictError(`Agent run "${state.runId}" was created by a different harness fingerprint.`);
685
+ }
686
+ }
687
+ else if (!state.harness && agent.harness) {
688
+ if (!policy?.allowLegacyHarnessResume) {
689
+ throw new ConflictError(`Agent run "${state.runId}" predates harness binding; set allowLegacyHarnessResume only for an explicit migration.`);
690
+ }
691
+ state.harness = agent.harness;
692
+ }
693
+ if (state.executionEnvironment && !executionEnvironment) {
694
+ throw new ConflictError(`Agent run "${state.runId}" is bound to execution environment "${state.executionEnvironment.environmentId}".`);
695
+ }
696
+ if (state.executionEnvironment && executionEnvironment) {
697
+ if (state.executionEnvironment.environmentId !== executionEnvironment.environmentId ||
698
+ state.executionEnvironment.environmentVersion !== executionEnvironment.environmentVersion ||
699
+ state.executionEnvironment.fingerprint !== executionEnvironment.fingerprint ||
700
+ state.executionEnvironment.workspaceId !== executionEnvironment.workspaceId) {
701
+ throw new ConflictError(`Agent run "${state.runId}" was created in a different execution environment.`);
702
+ }
703
+ }
704
+ else if (!state.executionEnvironment && executionEnvironment) {
705
+ if (!policy?.allowLegacyExecutionEnvironmentResume) {
706
+ throw new ConflictError(`Agent run "${state.runId}" predates execution-environment binding; enable the explicit migration policy to resume it.`);
707
+ }
708
+ state.executionEnvironment = executionEnvironment;
709
+ }
710
+ };
711
+ const validateHarnessBinding = (binding) => {
712
+ if (!binding) {
713
+ return;
714
+ }
715
+ if (binding.schemaVersion !== 1 ||
716
+ binding.algorithm !== "sha256" ||
717
+ !binding.id ||
718
+ !binding.version ||
719
+ !/^sha256:[0-9a-f]{64}$/.test(binding.fingerprint)) {
720
+ throw new ValidationError("Agent harness binding is invalid.");
721
+ }
722
+ };
480
723
  const resolveContext = async (agent, input) => {
724
+ validateHarnessBinding(agent.harness);
725
+ const executionEnvironment = input.executionEnvironment ?? agent.executionEnvironment;
726
+ const executionEnvironmentBinding = executionEnvironment
727
+ ? createAgentExecutionEnvironmentBinding(executionEnvironment.manifest)
728
+ : undefined;
729
+ let parsedContext = input.context;
730
+ if (agent.contextSchema) {
731
+ const result = await agent.contextSchema.safeParseAsync(input.context);
732
+ if (!result.success) {
733
+ throw new ValidationError(`Invalid agent context: ${result.error.message}`);
734
+ }
735
+ parsedContext = result.data;
736
+ }
481
737
  ensureValidIdempotencyInput(input, agent.store);
482
738
  const inputScope = input.scope ?? input.handoff?.scope;
483
739
  ensureValidScope(inputScope);
@@ -494,7 +750,7 @@ const resolveContext = async (agent, input) => {
494
750
  const maxSteps = Math.max(1, input.maxSteps ?? agent.maxSteps ?? 1);
495
751
  const metadata = cloneMetadata(agent.metadata, input.metadata, input.handoff?.metadata);
496
752
  const prepared = await prepareFreshMessages(agent, input, runId);
497
- const candidate = createBaseState(agent.model.provider, agent.model.modelId, prepared.messages, maxSteps, metadata, agent.id, runId, input.handoff, input.parentRunId, input.idempotencyKey, inputScope);
753
+ const candidate = createBaseState(agent.model.provider, agent.model.modelId, prepared.messages, maxSteps, metadata, agent.id, runId, input.handoff, input.parentRunId, input.idempotencyKey, inputScope, resolveAgentOutputMode(agent), agent.harness, executionEnvironmentBinding);
498
754
  const claim = await agent.store.claimIdempotencyKey(candidate);
499
755
  if (claim.claimed) {
500
756
  return {
@@ -502,6 +758,8 @@ const resolveContext = async (agent, input) => {
502
758
  messages: prepared.messages,
503
759
  remainingSteps: maxSteps,
504
760
  memoryMessages: prepared.memoryMessages,
761
+ context: parsedContext,
762
+ executionEnvironment,
505
763
  fresh: true
506
764
  };
507
765
  }
@@ -516,8 +774,9 @@ const resolveContext = async (agent, input) => {
516
774
  ensureValidStateInput(normalizedInput);
517
775
  const metadata = cloneMetadata(agent.metadata, loadedState?.metadata, input.metadata, input.handoff?.metadata);
518
776
  if (loadedState) {
777
+ bindDurableRuntime(agent, input, loadedState, executionEnvironmentBinding);
519
778
  const maxSteps = input.maxSteps ?? loadedState.maxSteps;
520
- const resumed = applyApprovalResponses(loadedState.messages, input.approvals, loadedState.pendingApprovals);
779
+ const resumed = await applyApprovalResponses(loadedState.messages, input.approvals, loadedState.pendingApprovals, loadedState.approvalHistory, agent.toolApprovalSigner);
521
780
  return {
522
781
  state: {
523
782
  ...loadedState,
@@ -531,12 +790,15 @@ const resolveContext = async (agent, input) => {
531
790
  maxSteps,
532
791
  messages: resumed.messages,
533
792
  pendingApprovals: resumed.pendingApprovals,
793
+ approvalHistory: resumed.approvalHistory,
534
794
  metadata,
535
795
  updatedAt: Date.now()
536
796
  },
537
797
  messages: resumed.messages,
538
798
  remainingSteps: Math.max(0, maxSteps - loadedState.currentStep),
539
799
  memoryMessages: [],
800
+ context: parsedContext,
801
+ executionEnvironment,
540
802
  fresh: false
541
803
  };
542
804
  }
@@ -544,10 +806,12 @@ const resolveContext = async (agent, input) => {
544
806
  const maxSteps = Math.max(1, input.maxSteps ?? agent.maxSteps ?? 1);
545
807
  const prepared = await prepareFreshMessages(agent, input, runId);
546
808
  return {
547
- state: createBaseState(agent.model.provider, agent.model.modelId, prepared.messages, maxSteps, metadata, agent.id, runId, input.handoff, input.parentRunId, input.idempotencyKey, inputScope),
809
+ state: createBaseState(agent.model.provider, agent.model.modelId, prepared.messages, maxSteps, metadata, agent.id, runId, input.handoff, input.parentRunId, input.idempotencyKey, inputScope, resolveAgentOutputMode(agent), agent.harness, executionEnvironmentBinding),
548
810
  messages: prepared.messages,
549
811
  remainingSteps: maxSteps,
550
812
  memoryMessages: prepared.memoryMessages,
813
+ context: parsedContext,
814
+ executionEnvironment,
551
815
  fresh: true
552
816
  };
553
817
  };
@@ -566,6 +830,57 @@ const canonicalJson = (value) => {
566
830
  const durableToolCallId = (runId, step, providerToolCallId, toolName, input) => `tool_${createHash("sha256")
567
831
  .update(`${runId}\0${step}\0${providerToolCallId}\0${toolName}\0${canonicalJson(input)}`)
568
832
  .digest("hex")}`;
833
+ const wrapToolWithExecutionEnvironment = (tool, session) => {
834
+ const authorize = async (input, context, phase) => session.authorize({
835
+ manifest: session.manifest,
836
+ binding: session.binding,
837
+ tool,
838
+ toolCall: context.toolCall,
839
+ input,
840
+ context,
841
+ phase
842
+ });
843
+ const environmentGuardrail = async ({ input, context }) => {
844
+ const decision = await authorize(input, context, "preflight");
845
+ return decision.decision === "deny"
846
+ ? {
847
+ triggered: true,
848
+ reason: decision.reason,
849
+ metadata: decision.metadata
850
+ }
851
+ : undefined;
852
+ };
853
+ return {
854
+ ...tool,
855
+ approvalVersion: [
856
+ tool.approvalVersion,
857
+ `environment:${session.binding.fingerprint}`
858
+ ].filter(Boolean).join("|"),
859
+ inputGuardrails: [
860
+ environmentGuardrail,
861
+ ...(tool.inputGuardrails ?? [])
862
+ ],
863
+ execute: async (input, context) => {
864
+ if (!context) {
865
+ throw new ValidationError(`Tool "${tool.name}" requires an execution environment context.`);
866
+ }
867
+ const decision = await authorize(input, context, "execute");
868
+ if (decision.decision === "deny") {
869
+ throw new GuardrailTriggeredError("tool-input", decision.reason, { metadata: decision.metadata });
870
+ }
871
+ const request = {
872
+ manifest: session.manifest,
873
+ binding: session.binding,
874
+ tool,
875
+ toolCall: context.toolCall,
876
+ input,
877
+ context,
878
+ phase: "execute"
879
+ };
880
+ return session.execute(request, () => tool.execute(input, context));
881
+ }
882
+ };
883
+ };
569
884
  const wrapToolWithJournal = (agent, state, tool) => {
570
885
  const store = agent.store;
571
886
  if (!store?.claimToolExecution || !store.loadToolExecution || !store.completeToolExecution) {
@@ -637,14 +952,141 @@ const wrapToolWithJournal = (agent, state, tool) => {
637
952
  }
638
953
  };
639
954
  };
640
- const createGenerateOptions = (agent, state, input, messages, maxSteps, abortSignal = input.abortSignal) => {
955
+ const defaultEstimateInputTokens = (messages) => Math.ceil(new TextEncoder().encode(JSON.stringify(messages)).byteLength / 4);
956
+ const validateCompactionOptions = (options) => {
957
+ if (options.maxMessages !== undefined &&
958
+ (!Number.isSafeInteger(options.maxMessages) || options.maxMessages < 2)) {
959
+ throw new ValidationError("Agent compaction maxMessages must be an integer greater than or equal to 2.");
960
+ }
961
+ if (options.maxEstimatedInputTokens !== undefined &&
962
+ (!Number.isSafeInteger(options.maxEstimatedInputTokens) || options.maxEstimatedInputTokens < 1)) {
963
+ throw new ValidationError("Agent compaction maxEstimatedInputTokens must be a positive integer.");
964
+ }
965
+ if (options.keepRecentMessages !== undefined &&
966
+ (!Number.isSafeInteger(options.keepRecentMessages) || options.keepRecentMessages < 1)) {
967
+ throw new ValidationError("Agent compaction keepRecentMessages must be a positive integer.");
968
+ }
969
+ if (options.maxMessages === undefined && options.maxEstimatedInputTokens === undefined) {
970
+ throw new ValidationError("Agent compaction requires maxMessages or maxEstimatedInputTokens.");
971
+ }
972
+ };
973
+ const compactAgentMessages = async (options, state, messages, beforeStep, context, abortSignal) => {
974
+ validateCompactionOptions(options);
975
+ if (state.pendingApprovals.length) {
976
+ return undefined;
977
+ }
978
+ const estimateTokens = options.estimateTokens ?? defaultEstimateInputTokens;
979
+ const estimatedTokensBefore = estimateTokens(messages);
980
+ const reasons = [];
981
+ if (options.maxMessages !== undefined && messages.length > options.maxMessages) {
982
+ reasons.push("message-count");
983
+ }
984
+ if (options.maxEstimatedInputTokens !== undefined &&
985
+ estimatedTokensBefore > options.maxEstimatedInputTokens) {
986
+ reasons.push("estimated-input-tokens");
987
+ }
988
+ if (!reasons.length) {
989
+ return undefined;
990
+ }
991
+ let systemCount = 0;
992
+ while (messages[systemCount]?.role === "system") {
993
+ systemCount += 1;
994
+ }
995
+ const maxTailForMessageLimit = options.maxMessages === undefined
996
+ ? Number.POSITIVE_INFINITY
997
+ : Math.max(1, options.maxMessages - systemCount - 1);
998
+ const keepRecentMessages = Math.min(options.keepRecentMessages ?? 8, maxTailForMessageLimit);
999
+ let cut = Math.max(systemCount, messages.length - keepRecentMessages);
1000
+ while (cut > systemCount && messages[cut]?.role === "tool") {
1001
+ cut -= 1;
1002
+ }
1003
+ if (cut <= systemCount) {
1004
+ throw new ValidationError("Agent compaction cannot satisfy its limits without removing protected messages.");
1005
+ }
1006
+ const compactedMessages = structuredClone(messages.slice(systemCount, cut));
1007
+ const retainedMessages = structuredClone(messages.slice(cut));
1008
+ const sourceDigest = fingerprintAgentHarness(messages);
1009
+ const id = `cmp_${createHash("sha256")
1010
+ .update(`${state.runId}\0${beforeStep}\0${sourceDigest}`)
1011
+ .digest("hex")}`;
1012
+ const result = await options.compactor({
1013
+ runId: state.runId,
1014
+ agentId: state.agentId,
1015
+ scope: state.scope,
1016
+ beforeStep,
1017
+ context,
1018
+ messages: compactedMessages,
1019
+ retainedMessages,
1020
+ reasons,
1021
+ estimatedTokensBefore,
1022
+ sourceDigest,
1023
+ idempotencyKey: id,
1024
+ metadata: state.metadata,
1025
+ abortSignal
1026
+ });
1027
+ const summary = result.summary.trim();
1028
+ if (!summary) {
1029
+ throw new ValidationError("Agent compactor returned an empty summary.");
1030
+ }
1031
+ const compacted = [
1032
+ ...structuredClone(messages.slice(0, systemCount)),
1033
+ createTextMessage("assistant", `[Compacted prior conversation]\n${summary}`),
1034
+ ...retainedMessages
1035
+ ];
1036
+ const estimatedTokensAfter = estimateTokens(compacted);
1037
+ if (compacted.length >= messages.length || estimatedTokensAfter >= estimatedTokensBefore) {
1038
+ throw new ValidationError("Agent compaction must reduce both message count and estimated input tokens.");
1039
+ }
1040
+ if (options.maxMessages !== undefined && compacted.length > options.maxMessages) {
1041
+ throw new ValidationError("Agent compaction result still exceeds maxMessages.");
1042
+ }
1043
+ if (options.maxEstimatedInputTokens !== undefined &&
1044
+ estimatedTokensAfter > options.maxEstimatedInputTokens) {
1045
+ throw new ValidationError("Agent compaction result still exceeds maxEstimatedInputTokens.");
1046
+ }
1047
+ const resultDigest = fingerprintAgentHarness(compacted);
1048
+ return {
1049
+ messages: compacted,
1050
+ record: {
1051
+ id,
1052
+ beforeStep,
1053
+ createdAt: Date.now(),
1054
+ reasons,
1055
+ sourceDigest,
1056
+ resultDigest,
1057
+ summaryDigest: fingerprintAgentHarness(summary),
1058
+ summary,
1059
+ messageCountBefore: messages.length,
1060
+ messageCountAfter: compacted.length,
1061
+ compactedMessageCount: compactedMessages.length,
1062
+ retainedMessageCount: retainedMessages.length,
1063
+ estimatedTokensBefore,
1064
+ estimatedTokensAfter,
1065
+ usage: result.usage,
1066
+ metadata: result.metadata
1067
+ }
1068
+ };
1069
+ };
1070
+ const createGenerateOptions = (agent, state, input, messages, maxSteps, context, executionEnvironmentSession, abortSignal = input.abortSignal, onCompaction) => {
641
1071
  const tools = { ...(toToolSet(input.tools ?? agent.tools) ?? {}) };
642
1072
  for (const subagent of agent.subagents ?? []) {
643
1073
  const subagentTool = createSubAgentTool({
644
1074
  ...subagent,
1075
+ agent: {
1076
+ ...subagent.agent,
1077
+ store: subagent.agent.store ?? agent.store,
1078
+ memory: subagent.agent.memory ?? agent.memory,
1079
+ executionEnvironment: subagent.agent.executionEnvironment ??
1080
+ input.executionEnvironment ??
1081
+ agent.executionEnvironment,
1082
+ compaction: subagent.agent.compaction ?? (input.compaction === false
1083
+ ? undefined
1084
+ : input.compaction ?? agent.compaction)
1085
+ },
645
1086
  parentRunId: state.runId,
646
1087
  parentAgentId: state.agentId,
647
1088
  scope: state.scope,
1089
+ runtimeState: state,
648
1090
  onStart: async ({ toolName, childAgentId }) => {
649
1091
  await emitTelemetryEvent(agent, {
650
1092
  type: "subagent-start",
@@ -655,7 +1097,12 @@ const createGenerateOptions = (agent, state, input, messages, maxSteps, abortSig
655
1097
  });
656
1098
  },
657
1099
  onFinish: async (childRun) => {
658
- state.childRuns = [...(state.childRuns ?? []), childRun];
1100
+ state.childRuns = [
1101
+ ...(state.childRuns ?? []).filter((existing) => childRun.toolCallId
1102
+ ? existing.toolCallId !== childRun.toolCallId
1103
+ : existing.runId !== childRun.runId),
1104
+ childRun
1105
+ ];
659
1106
  await emitTelemetryEvent(agent, {
660
1107
  type: "subagent-finish",
661
1108
  runId: state.runId,
@@ -671,12 +1118,20 @@ const createGenerateOptions = (agent, state, input, messages, maxSteps, abortSig
671
1118
  }
672
1119
  for (const [name, tool] of Object.entries(tools)) {
673
1120
  if (isCallableToolDefinition(tool)) {
674
- tools[name] = wrapToolWithJournal(agent, state, tool);
1121
+ const environmentTool = executionEnvironmentSession
1122
+ ? wrapToolWithExecutionEnvironment(tool, executionEnvironmentSession)
1123
+ : tool;
1124
+ tools[name] = tool.metadata?.type === "subagent"
1125
+ ? environmentTool
1126
+ : wrapToolWithJournal(agent, state, environmentTool);
675
1127
  }
676
1128
  }
677
1129
  const finalTools = Object.keys(tools).length ? tools : undefined;
678
1130
  const budget = input.policy?.budget ?? agent.policy?.budget;
679
1131
  const runPolicy = resolveRunPolicy(agent, input);
1132
+ const compaction = input.compaction === false
1133
+ ? undefined
1134
+ : input.compaction ?? agent.compaction;
680
1135
  let checkpointState = cloneState(state);
681
1136
  let reservedToolCalls = 0;
682
1137
  const requestedMaxTokens = input.maxTokens ?? agent.maxTokens;
@@ -687,16 +1142,62 @@ const createGenerateOptions = (agent, state, input, messages, maxSteps, abortSig
687
1142
  budgetStatus?.remaining.totalTokens
688
1143
  ].filter((value) => value !== undefined);
689
1144
  const maxTokens = tokenCeilings.length ? Math.min(...tokenCeilings) : undefined;
1145
+ const requestedToolExecution = input.toolExecution ?? agent.toolExecution;
1146
+ const toolExecution = agent.subagents?.length
1147
+ ? {
1148
+ ...requestedToolExecution,
1149
+ parallel: false,
1150
+ maxConcurrency: 1
1151
+ }
1152
+ : requestedToolExecution;
690
1153
  return {
691
1154
  model: agent.model,
692
1155
  messages,
693
1156
  tools: finalTools,
694
1157
  toolChoice: input.toolChoice,
695
- toolExecution: input.toolExecution ?? agent.toolExecution,
1158
+ toolExecution,
696
1159
  toolApprovalPolicy: input.toolApprovalPolicy ?? agent.toolApprovalPolicy,
1160
+ toolApprovalSigner: agent.toolApprovalSigner,
1161
+ toolApprovalResolutions: state.approvalHistory,
1162
+ toolContext: {
1163
+ context,
1164
+ runId: state.runId,
1165
+ agentId: state.agentId,
1166
+ scope: state.scope,
1167
+ metadata: state.metadata,
1168
+ executionEnvironment: executionEnvironmentSession
1169
+ },
697
1170
  onToolApprovalDecision: async (event) => {
698
1171
  await emitToolApprovalTelemetry(agent, state, event);
699
1172
  },
1173
+ prepareModelMessages: compaction
1174
+ ? async ({ messages: activeMessages, step }) => {
1175
+ const compacted = await compactAgentMessages(compaction, checkpointState, activeMessages, step, context, abortSignal);
1176
+ if (!compacted) {
1177
+ return undefined;
1178
+ }
1179
+ checkpointState = {
1180
+ ...checkpointState,
1181
+ messages: compacted.messages,
1182
+ usage: aggregateTokenUsage([
1183
+ checkpointState.usage,
1184
+ compacted.record.usage
1185
+ ]),
1186
+ compactions: [
1187
+ ...(checkpointState.compactions ?? []).filter((existing) => existing.id !== compacted.record.id),
1188
+ compacted.record
1189
+ ],
1190
+ updatedAt: Date.now()
1191
+ };
1192
+ state.messages = compacted.messages;
1193
+ state.usage = checkpointState.usage;
1194
+ state.compactions = checkpointState.compactions;
1195
+ await persistState(agent, checkpointState, runPolicy);
1196
+ state.revision = checkpointState.revision;
1197
+ await onCompaction?.(compacted.record);
1198
+ return compacted.messages;
1199
+ }
1200
+ : undefined,
700
1201
  onBeforeModelStep: ({ step }) => {
701
1202
  if (!budget)
702
1203
  return;
@@ -711,12 +1212,19 @@ const createGenerateOptions = (agent, state, input, messages, maxSteps, abortSig
711
1212
  });
712
1213
  }
713
1214
  },
714
- onModelStep: async ({ request, response, step, toolCalls }) => {
1215
+ onModelStep: async ({ request, response, step, toolCalls, approvalRequests }) => {
715
1216
  if (!agent.store)
716
1217
  return;
717
1218
  const responseSnapshot = snapshotResponse(response);
718
- const approvals = getAgentApprovalRequests(responseSnapshot.messages);
719
- const requestOffset = Math.min(checkpointState.messages.length, request.messages.length);
1219
+ const approvals = [
1220
+ ...approvalRequests,
1221
+ ...getAgentApprovalRequests(responseSnapshot.messages)
1222
+ ];
1223
+ const crossedCompactionBoundary = checkpointState.compactions?.some((record) => record.beforeStep === step &&
1224
+ record.resultDigest === fingerprintAgentHarness(request.messages));
1225
+ const requestOffset = crossedCompactionBoundary
1226
+ ? 0
1227
+ : messagePrefixLength(checkpointState.messages, request.messages);
720
1228
  const timing = getGenerateTextStepTiming(request);
721
1229
  const finishedAt = timing?.finishedAt ?? Date.now();
722
1230
  const checkpointStep = {
@@ -787,6 +1295,14 @@ const createGenerateOptions = (agent, state, input, messages, maxSteps, abortSig
787
1295
  temperature: input.temperature ?? agent.temperature,
788
1296
  maxTokens,
789
1297
  reasoning: input.reasoning ?? agent.reasoning,
1298
+ structuredOutput: agent.outputSchema && resolveAgentOutputMode(agent) === "native"
1299
+ ? {
1300
+ schema: agent.outputSchema,
1301
+ mode: "native",
1302
+ name: agent.outputName,
1303
+ description: agent.outputDescription
1304
+ }
1305
+ : undefined,
790
1306
  providerOptions: input.providerOptions ?? agent.providerOptions,
791
1307
  abortSignal,
792
1308
  timeoutMs: input.timeoutMs,
@@ -863,6 +1379,36 @@ const createAgentAbortContext = (inputAbortSignal, policy) => {
863
1379
  isTimedOut: () => timedOut
864
1380
  };
865
1381
  };
1382
+ const acquireExecutionEnvironment = async (environment, state, context, abortSignal) => {
1383
+ if (!environment) {
1384
+ return undefined;
1385
+ }
1386
+ const expected = state.executionEnvironment;
1387
+ if (!expected) {
1388
+ throw new ConflictError(`Agent run "${state.runId}" has no durable execution-environment binding.`);
1389
+ }
1390
+ const session = await environment.acquire({
1391
+ runId: state.runId,
1392
+ agentId: state.agentId,
1393
+ scope: state.scope,
1394
+ context,
1395
+ metadata: state.metadata,
1396
+ abortSignal
1397
+ });
1398
+ const manifestBinding = createAgentExecutionEnvironmentBinding(session.manifest);
1399
+ if (session.binding.environmentId !== expected.environmentId ||
1400
+ session.binding.environmentVersion !== expected.environmentVersion ||
1401
+ session.binding.fingerprint !== expected.fingerprint ||
1402
+ session.binding.workspaceId !== expected.workspaceId ||
1403
+ manifestBinding.fingerprint !== expected.fingerprint) {
1404
+ await session.release?.({
1405
+ status: "failed",
1406
+ error: { message: "Execution environment returned a binding that differs from the durable run." }
1407
+ });
1408
+ throw new ConflictError(`Execution environment binding changed for agent run "${state.runId}".`);
1409
+ }
1410
+ return session;
1411
+ };
866
1412
  const acquireAgentExecutionLease = async (agent, state, policy) => {
867
1413
  const store = agent.store;
868
1414
  if (policy?.leaseMode === "disabled" || !store?.acquireLease || !store.renewLease || !store.releaseLease) {
@@ -997,17 +1543,26 @@ export class Agent {
997
1543
  id;
998
1544
  model;
999
1545
  instructions;
1546
+ contextSchema;
1000
1547
  tools;
1001
1548
  maxSteps;
1002
1549
  temperature;
1003
1550
  maxTokens;
1004
1551
  reasoning;
1552
+ outputSchema;
1553
+ outputMode;
1554
+ outputName;
1555
+ outputDescription;
1005
1556
  toolExecution;
1006
1557
  toolApprovalPolicy;
1558
+ toolApprovalSigner;
1007
1559
  inputGuardrails;
1008
1560
  outputGuardrails;
1009
1561
  providerOptions;
1010
1562
  subagents;
1563
+ harness;
1564
+ executionEnvironment;
1565
+ compaction;
1011
1566
  policy;
1012
1567
  metadata;
1013
1568
  store;
@@ -1023,17 +1578,26 @@ export class Agent {
1023
1578
  id: this.id,
1024
1579
  model: this.model,
1025
1580
  instructions: this.instructions,
1581
+ contextSchema: this.contextSchema,
1026
1582
  tools: this.tools,
1027
1583
  maxSteps: this.maxSteps,
1028
1584
  temperature: this.temperature,
1029
1585
  maxTokens: this.maxTokens,
1030
1586
  reasoning: this.reasoning,
1587
+ outputSchema: this.outputSchema,
1588
+ outputMode: this.outputMode,
1589
+ outputName: this.outputName,
1590
+ outputDescription: this.outputDescription,
1031
1591
  toolExecution: this.toolExecution,
1032
1592
  toolApprovalPolicy: this.toolApprovalPolicy,
1593
+ toolApprovalSigner: this.toolApprovalSigner,
1033
1594
  inputGuardrails: this.inputGuardrails,
1034
1595
  outputGuardrails: this.outputGuardrails,
1035
1596
  providerOptions: this.providerOptions,
1036
1597
  subagents: this.subagents,
1598
+ harness: this.harness,
1599
+ executionEnvironment: this.executionEnvironment,
1600
+ compaction: this.compaction,
1037
1601
  policy: this.policy,
1038
1602
  metadata: this.metadata,
1039
1603
  store: this.store,
@@ -1058,6 +1622,8 @@ export const prepareSubagentsForAgent = (agent, options = {}) => {
1058
1622
  const onTelemetryEvent = options.onTelemetryEvent ?? agent.onTelemetryEvent;
1059
1623
  const toolApprovalPolicy = options.toolApprovalPolicy ?? agent.toolApprovalPolicy;
1060
1624
  const toolExecution = options.toolExecution ?? agent.toolExecution;
1625
+ const executionEnvironment = options.executionEnvironment ?? agent.executionEnvironment;
1626
+ const compaction = options.compaction ?? agent.compaction;
1061
1627
  const defaultMetadata = cloneMetadata(agent.metadata, options.metadata);
1062
1628
  return {
1063
1629
  ...agent,
@@ -1072,6 +1638,8 @@ export const prepareSubagentsForAgent = (agent, options = {}) => {
1072
1638
  onTelemetryEvent: subagent.agent.onTelemetryEvent ?? onTelemetryEvent,
1073
1639
  toolApprovalPolicy: subagent.agent.toolApprovalPolicy ?? toolApprovalPolicy,
1074
1640
  toolExecution: subagent.agent.toolExecution ?? toolExecution,
1641
+ executionEnvironment: subagent.agent.executionEnvironment ?? executionEnvironment,
1642
+ compaction: subagent.agent.compaction ?? compaction,
1075
1643
  metadata: cloneMetadata(defaultMetadata, subagent.agent.metadata)
1076
1644
  }
1077
1645
  }))
@@ -1222,7 +1790,7 @@ export const runAgent = async (agent, input = {}) => {
1222
1790
  context.state.status = currentStatus;
1223
1791
  return toOutput(context.state);
1224
1792
  }
1225
- if (currentStatus === "waiting_approval" && context.state.pendingApprovals.length > 0 && !input.approvals?.length) {
1793
+ if (currentStatus === "waiting_approval" && context.state.pendingApprovals.length > 0) {
1226
1794
  context.state.status = currentStatus;
1227
1795
  return toOutput(context.state);
1228
1796
  }
@@ -1264,6 +1832,7 @@ export const runAgent = async (agent, input = {}) => {
1264
1832
  inputGuardrail = await runGuardrails(agent, context.state, "input", agent.inputGuardrails, () => ({
1265
1833
  runId: context.state.runId,
1266
1834
  agentId: context.state.agentId,
1835
+ context: context.context,
1267
1836
  state: cloneState(context.state),
1268
1837
  messages: context.messages,
1269
1838
  metadata: context.state.metadata
@@ -1293,10 +1862,21 @@ export const runAgent = async (agent, input = {}) => {
1293
1862
  throw error;
1294
1863
  }
1295
1864
  const abortContext = createAgentAbortContext(mergeAbortSignals(input.abortSignal, executionLease.signal), policy);
1865
+ let executionEnvironmentSession;
1866
+ let executionEnvironmentStatus = "failed";
1867
+ let executionEnvironmentError;
1868
+ try {
1869
+ executionEnvironmentSession = await acquireExecutionEnvironment(context.executionEnvironment, context.state, context.context, abortContext.signal);
1870
+ }
1871
+ catch (error) {
1872
+ await executionLease.release();
1873
+ throw error;
1874
+ }
1296
1875
  try {
1297
- const result = await withAgentPolicyTimeout(generateText(createGenerateOptions(agent, context.state, input, context.messages, context.remainingSteps, abortContext.signal)), abortContext);
1876
+ const result = await withAgentPolicyTimeout(generateText(createGenerateOptions(agent, context.state, input, context.messages, context.remainingSteps, context.context, executionEnvironmentSession, abortContext.signal)), abortContext);
1298
1877
  const cancelled = executionLease.cancelledState();
1299
1878
  if (cancelled) {
1879
+ executionEnvironmentStatus = cancelled.status;
1300
1880
  await emitRunFinishTelemetry(agent, cancelled);
1301
1881
  return toOutput(cancelled);
1302
1882
  }
@@ -1304,10 +1884,11 @@ export const runAgent = async (agent, input = {}) => {
1304
1884
  throw new ConflictError(`Agent run "${context.state.runId}" lost its worker lease.`);
1305
1885
  }
1306
1886
  const newSteps = mapSteps(result.steps, context.state.currentStep, result.toolResults);
1307
- let output = finalizeState(context.state, result, newSteps, result.toolResults);
1887
+ let output = finalizeState(agent, context.state, result, newSteps, result.toolResults);
1308
1888
  const outputGuardrail = await runGuardrails(agent, output.state, "output", agent.outputGuardrails, () => ({
1309
1889
  runId: output.state.runId,
1310
1890
  agentId: output.state.agentId,
1891
+ context: context.context,
1311
1892
  state: cloneState(output.state),
1312
1893
  output,
1313
1894
  metadata: output.state.metadata
@@ -1316,14 +1897,22 @@ export const runAgent = async (agent, input = {}) => {
1316
1897
  output = toOutput(applyGuardrailFailure(output.state, "output", outputGuardrail));
1317
1898
  }
1318
1899
  await emitFinalizedStepTelemetry(agent, output.state, newSteps);
1319
- await emitApprovalTelemetry(agent, output.state, approvalsFromEvents(newSteps.flatMap((step) => step.response?.messages ?? [])));
1900
+ await emitApprovalTelemetry(agent, output.state, [
1901
+ ...(result.approvalRequests ?? []),
1902
+ ...approvalsFromEvents(newSteps.flatMap((step) => step.response?.messages ?? []))
1903
+ ]);
1320
1904
  await persistState(agent, output.state, policy);
1321
1905
  await emitRunFinishTelemetry(agent, output.state);
1906
+ executionEnvironmentStatus = output.status;
1322
1907
  return output;
1323
1908
  }
1324
1909
  catch (error) {
1910
+ executionEnvironmentError = {
1911
+ message: error instanceof Error ? error.message : String(error)
1912
+ };
1325
1913
  const cancelled = executionLease.cancelledState();
1326
1914
  if (cancelled) {
1915
+ executionEnvironmentStatus = cancelled.status;
1327
1916
  await emitRunFinishTelemetry(agent, cancelled);
1328
1917
  return toOutput(cancelled);
1329
1918
  }
@@ -1339,6 +1928,7 @@ export const runAgent = async (agent, input = {}) => {
1339
1928
  const timedOutState = createTerminalState(durableState, status, message);
1340
1929
  await persistState(agent, timedOutState, policy);
1341
1930
  await emitRunFinishTelemetry(agent, timedOutState);
1931
+ executionEnvironmentStatus = timedOutState.status;
1342
1932
  return toOutput(timedOutState);
1343
1933
  }
1344
1934
  const durableState = agent.store
@@ -1347,9 +1937,14 @@ export const runAgent = async (agent, input = {}) => {
1347
1937
  const failedState = createFailedState(durableState, error instanceof Error ? error.message : String(error));
1348
1938
  await persistState(agent, failedState, policy);
1349
1939
  await emitRunFinishTelemetry(agent, failedState);
1940
+ executionEnvironmentStatus = failedState.status;
1350
1941
  throw error;
1351
1942
  }
1352
1943
  finally {
1944
+ await executionEnvironmentSession?.release?.({
1945
+ status: executionEnvironmentStatus,
1946
+ error: executionEnvironmentError
1947
+ });
1353
1948
  await executionLease.release();
1354
1949
  }
1355
1950
  };
@@ -1360,6 +1955,7 @@ export const streamAgent = (agent, input = {}) => {
1360
1955
  });
1361
1956
  const publish = (event, terminal = false) => broadcast.publish(event, { terminal });
1362
1957
  let activeLease;
1958
+ let activeExecutionEnvironment;
1363
1959
  const runner = (async () => {
1364
1960
  const context = await resolveContext(agent, input);
1365
1961
  const currentStatus = normalizeApprovalStatus(context.state.status);
@@ -1382,7 +1978,7 @@ export const streamAgent = (agent, input = {}) => {
1382
1978
  textStream: emptyAsyncIterable()
1383
1979
  };
1384
1980
  }
1385
- if (currentStatus === "waiting_approval" && context.state.pendingApprovals.length > 0 && !input.approvals?.length) {
1981
+ if (currentStatus === "waiting_approval" && context.state.pendingApprovals.length > 0) {
1386
1982
  context.state.status = currentStatus;
1387
1983
  broadcast.close();
1388
1984
  return {
@@ -1433,6 +2029,7 @@ export const streamAgent = (agent, input = {}) => {
1433
2029
  inputGuardrail = await runGuardrails(agent, context.state, "input", agent.inputGuardrails, () => ({
1434
2030
  runId: context.state.runId,
1435
2031
  agentId: context.state.agentId,
2032
+ context: context.context,
1436
2033
  state: cloneState(context.state),
1437
2034
  messages: context.messages,
1438
2035
  metadata: context.state.metadata
@@ -1486,11 +2083,48 @@ export const streamAgent = (agent, input = {}) => {
1486
2083
  stepIndex: context.state.currentStep + 1
1487
2084
  });
1488
2085
  const abortContext = createAgentAbortContext(mergeAbortSignals(input.abortSignal, executionLease.signal), policy);
1489
- const streamResult = streamText(createGenerateOptions(agent, context.state, input, context.messages, context.remainingSteps, abortContext.signal));
2086
+ const executionEnvironmentSession = await acquireExecutionEnvironment(context.executionEnvironment, context.state, context.context, abortContext.signal);
2087
+ activeExecutionEnvironment = executionEnvironmentSession;
2088
+ let executionEnvironmentStatus = "failed";
2089
+ let executionEnvironmentError;
2090
+ let streamResult;
2091
+ try {
2092
+ streamResult = streamText(createGenerateOptions(agent, context.state, input, context.messages, context.remainingSteps, context.context, executionEnvironmentSession, abortContext.signal, async (record) => {
2093
+ await publish({
2094
+ type: "agent-compaction",
2095
+ compaction: record
2096
+ });
2097
+ }));
2098
+ }
2099
+ catch (error) {
2100
+ executionEnvironmentError = {
2101
+ message: error instanceof Error ? error.message : String(error)
2102
+ };
2103
+ await executionEnvironmentSession?.release?.({
2104
+ status: "failed",
2105
+ error: executionEnvironmentError
2106
+ });
2107
+ activeExecutionEnvironment = undefined;
2108
+ await executionLease.release();
2109
+ throw error;
2110
+ }
1490
2111
  const approvalRequests = [];
1491
2112
  const eventRelay = (async () => {
1492
2113
  for await (const event of streamResult.eventStream) {
1493
2114
  await publish(event);
2115
+ if (event.type === "tool-approval-request") {
2116
+ approvalRequests.push(event.approval);
2117
+ await publish({
2118
+ type: "agent-approval-request",
2119
+ approval: event.approval
2120
+ });
2121
+ await emitTelemetryEvent(agent, {
2122
+ type: "approval-request",
2123
+ runId: context.state.runId,
2124
+ agentId: context.state.agentId,
2125
+ approval: event.approval
2126
+ });
2127
+ }
1494
2128
  if (event.type === "provider-data" &&
1495
2129
  typeof event.data === "object" &&
1496
2130
  event.data !== null &&
@@ -1526,6 +2160,7 @@ export const streamAgent = (agent, input = {}) => {
1526
2160
  const final = await withAgentPolicyTimeout(eventRelay.then(() => streamResult.collect()), abortContext);
1527
2161
  const cancelled = executionLease.cancelledState();
1528
2162
  if (cancelled) {
2163
+ executionEnvironmentStatus = cancelled.status;
1529
2164
  await emitRunFinishTelemetry(agent, cancelled);
1530
2165
  await publish({
1531
2166
  type: "agent-run-finish",
@@ -1541,10 +2176,11 @@ export const streamAgent = (agent, input = {}) => {
1541
2176
  throw conflict;
1542
2177
  }
1543
2178
  const newSteps = mapSteps(final.steps, context.state.currentStep, final.toolResults);
1544
- let result = finalizeState(context.state, final, newSteps, final.toolResults);
2179
+ let result = finalizeState(agent, context.state, final, newSteps, final.toolResults);
1545
2180
  const outputGuardrail = await runGuardrails(agent, result.state, "output", agent.outputGuardrails, () => ({
1546
2181
  runId: result.state.runId,
1547
2182
  agentId: result.state.agentId,
2183
+ context: context.context,
1548
2184
  state: cloneState(result.state),
1549
2185
  output: result,
1550
2186
  metadata: result.state.metadata
@@ -1574,11 +2210,16 @@ export const streamAgent = (agent, input = {}) => {
1574
2210
  state: result.state
1575
2211
  }, true);
1576
2212
  broadcast.close();
2213
+ executionEnvironmentStatus = result.status;
1577
2214
  return result;
1578
2215
  }
1579
2216
  catch (error) {
2217
+ executionEnvironmentError = {
2218
+ message: error instanceof Error ? error.message : String(error)
2219
+ };
1580
2220
  const cancelled = executionLease.cancelledState();
1581
2221
  if (cancelled) {
2222
+ executionEnvironmentStatus = cancelled.status;
1582
2223
  await emitRunFinishTelemetry(agent, cancelled);
1583
2224
  await publish({
1584
2225
  type: "agent-run-finish",
@@ -1612,6 +2253,7 @@ export const streamAgent = (agent, input = {}) => {
1612
2253
  state: timedOutState
1613
2254
  }, true);
1614
2255
  broadcast.close();
2256
+ executionEnvironmentStatus = timedOutState.status;
1615
2257
  return toOutput(timedOutState);
1616
2258
  }
1617
2259
  const durableState = agent.store
@@ -1630,9 +2272,15 @@ export const streamAgent = (agent, input = {}) => {
1630
2272
  state: failedState
1631
2273
  }, true);
1632
2274
  broadcast.close();
2275
+ executionEnvironmentStatus = failedState.status;
1633
2276
  throw error;
1634
2277
  }
1635
2278
  finally {
2279
+ await executionEnvironmentSession?.release?.({
2280
+ status: executionEnvironmentStatus,
2281
+ error: executionEnvironmentError
2282
+ });
2283
+ activeExecutionEnvironment = undefined;
1636
2284
  await executionLease.release();
1637
2285
  }
1638
2286
  })();
@@ -1641,6 +2289,11 @@ export const streamAgent = (agent, input = {}) => {
1641
2289
  textStream: streamResult.textStream
1642
2290
  };
1643
2291
  })().catch(async (error) => {
2292
+ await activeExecutionEnvironment?.release?.({
2293
+ status: "failed",
2294
+ error: { message: error instanceof Error ? error.message : String(error) }
2295
+ });
2296
+ activeExecutionEnvironment = undefined;
1644
2297
  await activeLease?.release();
1645
2298
  broadcast.fail(error);
1646
2299
  throw error;