@agen-ai/agent-runtime 0.4.3 → 0.4.5

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 (56) hide show
  1. package/README.md +297 -10
  2. package/dist/adapterValidation.js +38 -4
  3. package/dist/contractErrors.d.ts +2 -2
  4. package/dist/contractErrors.js +8 -1
  5. package/dist/executionInput/index.d.ts +3 -0
  6. package/dist/executionInput/index.js +4 -0
  7. package/dist/executionInput/lifetime.d.ts +47 -0
  8. package/dist/executionInput/lifetime.js +241 -0
  9. package/dist/executionInput/types.d.ts +61 -0
  10. package/dist/executionInput/types.js +0 -0
  11. package/dist/executionInput/validation.d.ts +25 -0
  12. package/dist/executionInput/validation.js +178 -0
  13. package/dist/generation/cleanup.d.ts +15 -0
  14. package/dist/generation/cleanup.js +108 -0
  15. package/dist/generation/deadline.d.ts +16 -0
  16. package/dist/generation/deadline.js +81 -0
  17. package/dist/generation/decoding.d.ts +4 -0
  18. package/dist/generation/decoding.js +59 -0
  19. package/dist/generation/errors.d.ts +29 -0
  20. package/dist/generation/errors.js +39 -0
  21. package/dist/generation/generateObject.d.ts +3 -0
  22. package/dist/generation/generateObject.js +148 -0
  23. package/dist/generation/index.d.ts +5 -0
  24. package/dist/generation/index.js +8 -0
  25. package/dist/generation/preflight.d.ts +25 -0
  26. package/dist/generation/preflight.js +48 -0
  27. package/dist/generation/requests.d.ts +17 -0
  28. package/dist/generation/requests.js +117 -0
  29. package/dist/generation/types.d.ts +22 -0
  30. package/dist/generation/types.js +0 -0
  31. package/dist/index.d.ts +3 -0
  32. package/dist/index.js +7 -1
  33. package/dist/nativeContinuations/admission.js +1 -0
  34. package/dist/outputValidation.d.ts +2 -0
  35. package/dist/outputValidation.js +29 -1
  36. package/dist/sessionInteractions/lifetime.d.ts +1 -0
  37. package/dist/sessionInteractions/lifetime.js +4 -0
  38. package/dist/sessionInteractions/request.d.ts +1 -0
  39. package/dist/sessionInteractions/request.js +3 -0
  40. package/dist/sessionValidation.d.ts +13 -3
  41. package/dist/sessionValidation.js +80 -6
  42. package/dist/sessions.d.ts +7 -1
  43. package/dist/structuredOutput/admission.d.ts +16 -0
  44. package/dist/structuredOutput/admission.js +89 -0
  45. package/dist/structuredOutput/candidate.d.ts +22 -0
  46. package/dist/structuredOutput/candidate.js +64 -0
  47. package/dist/structuredOutput/validation.d.ts +8 -0
  48. package/dist/structuredOutput/validation.js +26 -0
  49. package/dist/testing/conformance.d.ts +20 -1
  50. package/dist/testing/conformance.js +103 -3
  51. package/dist/testing/fakeProvider.js +5 -4
  52. package/dist/turnExecution/execution.d.ts +9 -1
  53. package/dist/turnExecution/execution.js +88 -7
  54. package/dist/turnExecution/terminalBuffer.d.ts +9 -0
  55. package/dist/turnExecution/terminalBuffer.js +38 -0
  56. package/package.json +6 -2
@@ -1,4 +1,4 @@
1
- import { type AgentCollaborationSpawnInput, type AgentConfigurationSelectionInput, type AgentEnvironmentId, type AgentGeneratedResourceId, type AgentOperationInvocation, type AgentRequest, type AgentRequestResolution, type AgentSessionBinding, type AgentSessionBranchSource, type AgentSessionConfiguration, type AgentSessionId, type AgentTurnId, type AgentTurnInputContent } from "@agen-ai/agent-protocol";
1
+ import { type AgentCollaborationSpawnInput, type AgentJsonObject, type AgentOutputSchema, type AgentConfigurationSelectionInput, type AgentEnvironmentId, type AgentGeneratedResourceId, type AgentOperationInvocation, type AgentRequest, type AgentRequestResolution, type AgentSessionBinding, type AgentSessionBranchSource, type AgentSessionConfiguration, type AgentSessionId, type AgentTurnId, type AgentTurnInputContent } from "@agen-ai/agent-protocol";
2
2
  import type { AgentProviderDriver, AgentProviderInstanceDefinition } from "../providerDriver.js";
3
3
  import type { AgentProviderStartTurnInput } from "../sessions.js";
4
4
  export interface AgentProviderConformanceScenario {
@@ -23,6 +23,25 @@ export interface AgentProviderConformanceScenario {
23
23
  }>) => AgentRequestResolution | undefined | Promise<AgentRequestResolution | undefined>;
24
24
  readonly branchSource?: (binding: AgentSessionBinding, turnId: AgentTurnId) => AgentSessionBranchSource;
25
25
  readonly steeringInput?: AgentTurnInputContent;
26
+ readonly structuredOutput?: AgentStructuredOutputConformanceScenario;
27
+ }
28
+ export interface AgentStructuredOutputConformanceScenario {
29
+ readonly sessionId: AgentSessionId;
30
+ readonly plainSessionId: AgentSessionId;
31
+ readonly schema: AgentOutputSchema;
32
+ readonly alternateSchema: AgentOutputSchema;
33
+ /** Turn scope uses schema then alternateSchema; session scope uses schema for both turns. */
34
+ readonly turns: readonly [
35
+ Readonly<{
36
+ turn: Omit<AgentProviderStartTurnInput, "output">;
37
+ expectedValue: AgentJsonObject;
38
+ }>,
39
+ Readonly<{
40
+ turn: Omit<AgentProviderStartTurnInput, "output">;
41
+ expectedValue: AgentJsonObject;
42
+ }>
43
+ ];
44
+ readonly plainTurn: Omit<AgentProviderStartTurnInput, "output">;
26
45
  }
27
46
  export interface AgentProviderConformanceReport {
28
47
  readonly providerKey: string;
@@ -1,3 +1,4 @@
1
+ import { isDeepStrictEqual } from "node:util";
1
2
  import {
2
3
  matchesAgentSessionBinding,
3
4
  parseAgentEnvironmentId,
@@ -5,6 +6,7 @@ import {
5
6
  parseAgentTurnId
6
7
  } from "@agen-ai/agent-protocol";
7
8
  import { isAgentOperationAbortError } from "../foundation.js";
9
+ import { AgentProviderContractError } from "../contractErrors.js";
8
10
  import {
9
11
  createAgentProviderRegistry,
10
12
  AgentProviderRegistryError
@@ -61,7 +63,7 @@ async function advanceToStartedTurn(session, input) {
61
63
  throw error;
62
64
  }
63
65
  }
64
- async function collectRemainingOutputs(started, resolutionFor, beforeClose) {
66
+ async function collectRemainingOutputs(started, resolutionFor, beforeClose, closeOnSuccess = true) {
65
67
  const requests = /* @__PURE__ */ new Map();
66
68
  const tasks = [];
67
69
  let acceptingRequests = true;
@@ -119,7 +121,6 @@ async function collectRemainingOutputs(started, resolutionFor, beforeClose) {
119
121
  requests.delete(event.payload.requestId);
120
122
  } else if (event.type === "turn.completed") {
121
123
  abortRequests();
122
- return;
123
124
  }
124
125
  }
125
126
  })();
@@ -134,7 +135,7 @@ async function collectRemainingOutputs(started, resolutionFor, beforeClose) {
134
135
  }
135
136
  acceptingRequests = false;
136
137
  abortRequests();
137
- const closing = started.session.close({ reason: failures.size > 0 ? "contract_rejected" : "idle" });
138
+ const closing = failures.size > 0 || closeOnSuccess ? started.session.close({ reason: failures.size > 0 ? "contract_rejected" : "idle" }) : Promise.resolve();
138
139
  const [closed, read, inspected, returned] = await Promise.allSettled([closing, reader, checked, started.iterator.return?.()]);
139
140
  if (closed?.status === "rejected") failures.add(closed.reason);
140
141
  for (const result of [read, inspected]) {
@@ -163,6 +164,99 @@ function hasStableTurnBoundary(events) {
163
164
  );
164
165
  return boundary?.type === "turn.completed" || boundary?.type === "turn.state_changed" && boundary.payload.state === "waiting_for_request";
165
166
  }
167
+ async function requireStructuredRejection(operation, code) {
168
+ let rejected = false;
169
+ try {
170
+ await operation();
171
+ } catch (error) {
172
+ requireCheck(
173
+ error instanceof AgentProviderContractError && error.code === code,
174
+ "structured_output",
175
+ "Structured output rejection did not preserve its admission classification."
176
+ );
177
+ rejected = true;
178
+ }
179
+ requireCheck(rejected, "structured_output", "Unsupported structured output was admitted.");
180
+ }
181
+ async function checkStructuredOutput(input) {
182
+ const { adapter, capability, scenario } = input;
183
+ const schema = scenario.structuredOutput?.schema ?? {
184
+ type: "object",
185
+ properties: { title: { type: "string" } },
186
+ required: ["title"],
187
+ additionalProperties: false
188
+ };
189
+ const request = { kind: "json_schema", schema };
190
+ if (capability.kind === "unsupported") {
191
+ await requireStructuredRejection(async () => {
192
+ const unexpected = await adapter.createSession({
193
+ sessionId: scenario.abortedSessionId,
194
+ workingDirectory: scenario.workingDirectory,
195
+ configuration: scenario.configuration,
196
+ onBindingCreated() {
197
+ },
198
+ output: request
199
+ });
200
+ await unexpected.close({ reason: "contract_rejected" });
201
+ }, "structured_output_unsupported");
202
+ await requireStructuredRejection(() => input.plainSession.startTurn({ ...scenario.turn, output: request }), "structured_output_unsupported");
203
+ return ["structured_output_unsupported"];
204
+ }
205
+ const fixture = scenario.structuredOutput;
206
+ requireCheck(fixture !== void 0, "structured_output", "Supported structured output requires two schema/result fixtures and an ordinary turn.");
207
+ let bound;
208
+ let plain;
209
+ let failure;
210
+ const open = (sessionId, bind) => adapter.createSession({
211
+ sessionId,
212
+ workingDirectory: scenario.workingDirectory,
213
+ configuration: scenario.configuration,
214
+ onBindingCreated() {
215
+ },
216
+ ...bind ? { output: request } : {}
217
+ });
218
+ try {
219
+ bound = await open(fixture.sessionId, capability.scope === "session");
220
+ for (const [index, turnFixture] of fixture.turns.entries()) {
221
+ const selected = { kind: "json_schema", schema: index === 0 ? schema : fixture.alternateSchema };
222
+ if (capability.scope === "session") {
223
+ await requireStructuredRejection(() => bound.startTurn({ ...turnFixture.turn, output: selected }), "structured_output_scope_mismatch");
224
+ }
225
+ const started2 = await advanceToStartedTurn(bound, { ...turnFixture.turn, ...capability.scope === "turn" ? { output: selected } : {} });
226
+ const observed2 = await collectRemainingOutputs(started2, scenario.resolutionFor, void 0, false);
227
+ const terminal2 = semanticEvents(observed2.outputs).at(-1);
228
+ requireCheck(
229
+ terminal2?.type === "turn.completed" && terminal2.payload.outcome === "completed" && terminal2.payload.structuredOutput.kind === "json" && isDeepStrictEqual(terminal2.payload.structuredOutput.value, turnFixture.expectedValue),
230
+ "structured_output",
231
+ "Structured turn did not finish with the expected schema-valid native value."
232
+ );
233
+ }
234
+ if (capability.scope === "session") {
235
+ await bound.close({ reason: "idle" });
236
+ plain = await open(fixture.plainSessionId, false);
237
+ } else plain = bound;
238
+ const started = await advanceToStartedTurn(plain, fixture.plainTurn);
239
+ const observed = await collectRemainingOutputs(started, scenario.resolutionFor, void 0, false);
240
+ const terminal = semanticEvents(observed.outputs).at(-1);
241
+ requireCheck(
242
+ terminal?.type === "turn.completed" && terminal.payload.outcome === "completed" && terminal.payload.structuredOutput.kind === "not_requested",
243
+ "structured_output_lifetime",
244
+ "An ordinary turn inherited a structured output binding."
245
+ );
246
+ return ["structured_output", "structured_output_lifetime"];
247
+ } catch (error) {
248
+ failure = { error };
249
+ throw error;
250
+ } finally {
251
+ const settled = await Promise.allSettled([bound?.close({ reason: "idle" }), plain === bound ? void 0 : plain?.close({ reason: "idle" })]);
252
+ const failures = settled.flatMap((result) => result.status === "rejected" ? [result.reason] : []);
253
+ if (failures.length > 0) throw new AggregateError(
254
+ failure ? [failure.error, ...failures] : failures,
255
+ "Structured conformance cleanup failed.",
256
+ failure ? { cause: failure.error } : void 0
257
+ );
258
+ }
259
+ }
166
260
  async function runAgentProviderConformance(scenario) {
167
261
  const checks = [];
168
262
  try {
@@ -337,6 +431,12 @@ async function runAgentProviderConformance(scenario) {
337
431
  }
338
432
  }
339
433
  checks.push("environment_session");
434
+ checks.push(...await checkStructuredOutput({
435
+ adapter: instance.adapter,
436
+ capability: instance.capabilities.turns.structuredOutput,
437
+ plainSession: created,
438
+ scenario
439
+ }));
340
440
  const aborted = new AbortController();
341
441
  aborted.abort(new DOMException("Conformance abort.", "AbortError"));
342
442
  try {
@@ -44,10 +44,11 @@ function defaultCapabilities(providerKey) {
44
44
  }
45
45
  };
46
46
  return parseAgentCapabilities({
47
- protocolVersion: 12,
47
+ protocolVersion: 13,
48
48
  providerKey,
49
49
  sessions: { create: true, resume: true, branch: { kind: "through_turn" } },
50
50
  turns: {
51
+ structuredOutput: { kind: "unsupported" },
51
52
  interactionModes: ["default"],
52
53
  interrupt: true,
53
54
  steer: { kind: "supported", input }
@@ -166,7 +167,7 @@ function sessionBinding(state, sessionId) {
166
167
  };
167
168
  }
168
169
  function eventBase(sessionId, turnId, occurredAt) {
169
- return { protocolVersion: 12, sessionId, turnId, occurredAt };
170
+ return { protocolVersion: 13, sessionId, turnId, occurredAt };
170
171
  }
171
172
  function fakeEnvironmentSnapshot(environmentId) {
172
173
  return parseAgentEnvironmentSnapshot({
@@ -293,7 +294,7 @@ function fakeSession(input) {
293
294
  yield createAgentEventOutput({
294
295
  ...eventBase(input.context.sessionId, turnInput.turnId, input.now()),
295
296
  type: "turn.completed",
296
- payload: { outcome: "canceled", reason: "Fake provider interrupted." }
297
+ payload: { structuredOutput: { kind: "not_requested" }, outcome: "canceled", reason: "Fake provider interrupted." }
297
298
  });
298
299
  return;
299
300
  }
@@ -353,7 +354,7 @@ function fakeSession(input) {
353
354
  yield createAgentEventOutput({
354
355
  ...eventBase(input.context.sessionId, turnInput.turnId, occurredAt),
355
356
  type: "turn.completed",
356
- payload: { outcome: interruptedTurns.has(turnInput.turnId) ? "canceled" : "completed", reason: "Fake provider completed." }
357
+ payload: { structuredOutput: { kind: "not_requested" }, outcome: interruptedTurns.has(turnInput.turnId) ? "canceled" : "completed", reason: "Fake provider completed." }
357
358
  });
358
359
  };
359
360
  const close = async () => {
@@ -1,4 +1,6 @@
1
1
  import { type AgentProviderKey, type AgentTurnId } from "@agen-ai/agent-protocol";
2
+ import type { AgentExecutionInputSupport } from "../executionInput/types.js";
3
+ import { type createExecutionInputCensus } from "../executionInput/lifetime.js";
2
4
  import type { AgentProviderOutput } from "../outputs.js";
3
5
  import type { AgentSessionAdmission } from "../sessionAdmission.js";
4
6
  import type { AgentProviderStartTurnInput, AgentProviderTurnExecution } from "../sessions.js";
@@ -6,13 +8,19 @@ import { type AgentTurnRequestAdmission, type PendingAgentRequest, type Suspende
6
8
  export interface ValidatedAgentTurnExecution {
7
9
  readonly execution: AgentProviderTurnExecution;
8
10
  readonly hasEntered: () => boolean;
9
- readonly stop: (reason: Error) => void;
11
+ readonly stop: (reason: Error, inputReason?: "closed" | "interrupted" | "native_failed") => void;
12
+ readonly closeInputAdmission: () => void;
10
13
  readonly join: () => Promise<void>;
11
14
  readonly suspendRequests: () => SuspendedAgentTurnRequests;
12
15
  readonly completed: Promise<void>;
13
16
  }
14
17
  type ExecutionOptions = Readonly<{
15
18
  providerKey: AgentProviderKey;
19
+ executionInputSupport?: AgentExecutionInputSupport;
20
+ executionInputCensus: ReturnType<typeof createExecutionInputCensus>;
21
+ ownsExecution: () => boolean;
22
+ requireInputContext: () => void;
23
+ structuredOutputBound: boolean;
16
24
  pendingRequests: Map<string, PendingAgentRequest>;
17
25
  sessionAdmission: AgentSessionAdmission;
18
26
  inFlightOperations: Set<Promise<void>>;
@@ -5,8 +5,11 @@ import {
5
5
  import { AgentProviderContractError, AgentProviderDelegatedOperationError, throwAgentProviderContractError } from "../contractErrors.js";
6
6
  import { throwIfAgentOperationAborted } from "../foundation.js";
7
7
  import { hasExactOwnKeys } from "../internal/objectShape.js";
8
+ import { validateAgentExecutionInputPort } from "../executionInput/validation.js";
9
+ import { createExecutionInputOwner } from "../executionInput/lifetime.js";
8
10
  import { validateAgentRequestResolutionResult } from "./receipts.js";
9
11
  import { createAgentTurnRequestAdmission } from "./requests.js";
12
+ import { createAgentTurnTerminalBuffer } from "./terminalBuffer.js";
10
13
  function createValidatedAgentTurnExecution(input) {
11
14
  return createExecution(input, { kind: "caller", input: input.turnInput, start: input.start });
12
15
  }
@@ -17,14 +20,17 @@ function createExecution(input, origin) {
17
20
  const turnId = origin.kind === "caller" ? origin.input.turnId : origin.turnId;
18
21
  const callerSignal = origin.kind === "caller" ? origin.input.signal : void 0;
19
22
  const lifetime = new AbortController();
23
+ let inputOwner;
20
24
  const validateCandidate = (candidate) => {
21
- if (candidate === null || typeof candidate !== "object" || candidate.turnId !== turnId || !hasExactOwnKeys(candidate, ["turnId", "outputs", "resolveRequest"]) || typeof candidate.resolveRequest !== "function" || !candidate.outputs || typeof candidate.outputs[Symbol.asyncIterator] !== "function") {
25
+ if (candidate === null || typeof candidate !== "object" || candidate.turnId !== turnId || !hasExactOwnKeys(candidate, ["turnId", "outputs", "resolveRequest", ...Object.hasOwn(candidate, "input") ? ["input"] : []]) || typeof candidate.resolveRequest !== "function" || !candidate.outputs || typeof candidate.outputs[Symbol.asyncIterator] !== "function") {
22
26
  throwAgentProviderContractError(
23
27
  input.providerKey,
24
28
  origin.kind === "native" ? "invalid_native_continuation" : "invalid_operation_result",
25
29
  "Provider returned an invalid turn execution."
26
30
  );
27
31
  }
32
+ const port = validateAgentExecutionInputPort(candidate.input, input.executionInputSupport, input.providerKey);
33
+ if (port?.kind === "supported") inputOwner?.attach(port);
28
34
  return candidate;
29
35
  };
30
36
  let state;
@@ -54,6 +60,8 @@ function createExecution(input, origin) {
54
60
  resolveDisposition = resolve;
55
61
  });
56
62
  let source;
63
+ let sourceExhausted = false;
64
+ let terminalTail;
57
65
  const requests = createAgentTurnRequestAdmission({
58
66
  providerKey: input.providerKey,
59
67
  sessionAdmission: input.sessionAdmission,
@@ -61,6 +69,40 @@ function createExecution(input, origin) {
61
69
  canRestore: () => state.kind === "entered" && !input.isTerminal(),
62
70
  canRestoreRequest: input.canRestoreRequest
63
71
  });
72
+ let resolvingRequests = 0;
73
+ if (origin.kind === "caller" && input.executionInputSupport?.kind === "supported") {
74
+ inputOwner = createExecutionInputOwner({
75
+ providerKey: input.providerKey,
76
+ turnId,
77
+ support: input.executionInputSupport,
78
+ census: input.executionInputCensus,
79
+ signal: lifetime.signal,
80
+ fence: input.retire,
81
+ requireAdmission() {
82
+ input.requireUsable();
83
+ if (state.kind !== "entered" || !input.ownsExecution() || input.isTerminal()) {
84
+ throwAgentProviderContractError(
85
+ input.providerKey,
86
+ "active_turn_mismatch",
87
+ "Execution input does not identify the exact active entered execution."
88
+ );
89
+ }
90
+ if (input.structuredOutputBound) throwAgentProviderContractError(
91
+ input.providerKey,
92
+ "structured_output_unsupported",
93
+ "Schema-bound execution cannot admit additional input."
94
+ );
95
+ input.requireInputContext();
96
+ if (requests.hasUnclaimedRequest() || resolvingRequests > 0) {
97
+ throwAgentProviderContractError(
98
+ input.providerKey,
99
+ "request_pending",
100
+ "Execution input cannot enter while a human request or its response remains unsettled."
101
+ );
102
+ }
103
+ }
104
+ });
105
+ }
64
106
  let acquired = false;
65
107
  let pulling = false;
66
108
  let returnPromise;
@@ -75,8 +117,11 @@ function createExecution(input, origin) {
75
117
  return new DOMException("The turn execution was stopped.", "AbortError");
76
118
  };
77
119
  const detachAbort = () => callerSignal?.removeEventListener("abort", abort);
78
- const stop = (reason) => {
120
+ const stop = (reason, inputReason = "native_failed") => {
79
121
  if (state.kind === "settled" || state.kind === "stopped") return;
122
+ inputOwner?.closeAdmission(inputReason);
123
+ terminalTail?.clear();
124
+ terminalTail = void 0;
80
125
  const entered = dispatched || state.kind === "entered" || state.kind === "entering";
81
126
  state = { kind: "stopped", reason };
82
127
  requests.withdraw();
@@ -97,7 +142,7 @@ function createExecution(input, origin) {
97
142
  } catch (error) {
98
143
  if (error instanceof Error) reason = error;
99
144
  }
100
- stop(reason);
145
+ stop(reason, "interrupted");
101
146
  };
102
147
  callerSignal?.addEventListener("abort", abort, { once: true });
103
148
  const track = (operation) => {
@@ -105,6 +150,7 @@ function createExecution(input, origin) {
105
150
  void operation.then(() => nativeOperations.delete(operation), () => nativeOperations.delete(operation));
106
151
  return operation;
107
152
  };
153
+ const pullNative = () => track(Promise.resolve().then(() => state.kind === "entered" ? sourceExhausted ? { done: true, value: void 0 } : state.iterator.next() : origin.kind === "native" && state.kind === "settled" ? { done: true, value: void 0 } : Promise.reject(abortError())));
108
154
  let joinPromise;
109
155
  const join = () => {
110
156
  if (joinPromise) return joinPromise;
@@ -113,6 +159,7 @@ function createExecution(input, origin) {
113
159
  await Promise.allSettled([...nativeOperations, ...input.inFlightOperations]);
114
160
  }
115
161
  if (source?.return) await source.return();
162
+ await inputOwner?.join();
116
163
  })();
117
164
  joinPromise = attempt;
118
165
  void attempt.catch(() => {
@@ -150,6 +197,7 @@ function createExecution(input, origin) {
150
197
  settle = resolve;
151
198
  });
152
199
  input.inFlightOperations.add(settlement);
200
+ resolvingRequests += 1;
153
201
  try {
154
202
  try {
155
203
  requestInput.onProviderExecutionStarted?.();
@@ -174,6 +222,7 @@ function createExecution(input, origin) {
174
222
  if (result.status === "delivery_uncertain") input.retire();
175
223
  return result;
176
224
  } finally {
225
+ resolvingRequests -= 1;
177
226
  input.inFlightOperations.delete(settlement);
178
227
  settle();
179
228
  }
@@ -186,6 +235,12 @@ function createExecution(input, origin) {
186
235
  let removeAbort = () => {
187
236
  };
188
237
  try {
238
+ if (terminalTail) {
239
+ throwIfAgentOperationAborted(lifetime.signal);
240
+ const trailing = terminalTail.take();
241
+ if (trailing !== void 0) return { done: false, value: trailing };
242
+ terminalTail = void 0;
243
+ }
189
244
  if (state.kind === "parked") {
190
245
  ready();
191
246
  await dispatch;
@@ -224,7 +279,7 @@ function createExecution(input, origin) {
224
279
  }
225
280
  }
226
281
  if (state.kind !== "entered") throw abortError();
227
- const nativePull = track(Promise.resolve().then(() => state.kind === "entered" ? state.iterator.next() : origin.kind === "native" && state.kind === "settled" ? { done: true, value: void 0 } : Promise.reject(abortError())));
282
+ const nativePull = pullNative();
228
283
  const stopped = new Promise((_, reject) => {
229
284
  const onAbort = () => reject(abortError());
230
285
  lifetime.signal.addEventListener("abort", onAbort, { once: true });
@@ -236,6 +291,24 @@ function createExecution(input, origin) {
236
291
  if (origin.kind === "native" && currentState().kind === "settled") throwAgentProviderContractError(input.providerKey, "invalid_native_continuation", "A no-work execution emitted native output.");
237
292
  observedOutput = true;
238
293
  const output = input.observe(result.value, requests);
294
+ if (inputOwner && output.kind === "event" && output.event.type === "turn.completed") {
295
+ inputOwner.closeAdmission();
296
+ const buffer = createAgentTurnTerminalBuffer(input.providerKey, output);
297
+ const exhaustSource = (async () => {
298
+ for (; ; ) {
299
+ const trailing = await Promise.race([pullNative(), stopped]);
300
+ throwIfAgentOperationAborted(lifetime.signal);
301
+ if (trailing.done) {
302
+ sourceExhausted = true;
303
+ return;
304
+ }
305
+ buffer.append(input.observe(trailing.value, requests));
306
+ }
307
+ })();
308
+ await Promise.race([inputOwner.drain(exhaustSource), stopped]);
309
+ throwIfAgentOperationAborted(lifetime.signal);
310
+ terminalTail = buffer;
311
+ }
239
312
  return { done: false, value: output };
240
313
  }
241
314
  while (input.inFlightOperations.size > 0) {
@@ -247,7 +320,7 @@ function createExecution(input, origin) {
247
320
  requests.withdraw();
248
321
  state = { kind: "settled" };
249
322
  detachAbort();
250
- input.release();
323
+ if (!inputOwner?.retainsOwnership) input.release();
251
324
  complete();
252
325
  return { done: true, value: void 0 };
253
326
  } catch (error) {
@@ -256,6 +329,8 @@ function createExecution(input, origin) {
256
329
  void input.abandon().catch(() => {
257
330
  });
258
331
  }
332
+ if (inputOwner?.failed) void input.abandon().catch(() => {
333
+ });
259
334
  throw error;
260
335
  } finally {
261
336
  removeAbort();
@@ -268,7 +343,7 @@ function createExecution(input, origin) {
268
343
  if (returnPromise) return returnPromise;
269
344
  if (state.kind === "settled") return Promise.resolve({ done: true, value: void 0 });
270
345
  const entered = dispatched || state.kind === "entered" || source !== void 0;
271
- stop(new DOMException("The turn output consumer returned.", "AbortError"));
346
+ stop(new DOMException("The turn output consumer returned.", "AbortError"), "closed");
272
347
  if (entered && origin.kind === "native") {
273
348
  void input.abandon().catch(() => {
274
349
  });
@@ -285,10 +360,16 @@ function createExecution(input, origin) {
285
360
  }
286
361
  });
287
362
  return {
288
- execution: Object.freeze({ turnId, outputs, resolveRequest }),
363
+ execution: Object.freeze({
364
+ turnId,
365
+ outputs,
366
+ resolveRequest,
367
+ ...inputOwner === void 0 ? origin.kind === "native" && input.executionInputSupport?.kind === "supported" ? { input: Object.freeze({ kind: "unsupported" }) } : {} : { input: inputOwner.port }
368
+ }),
289
369
  hasEntered: () => dispatched && state.kind === "parked" || state.kind === "entering" || state.kind === "entered",
290
370
  stop,
291
371
  join,
372
+ closeInputAdmission: () => inputOwner?.closeAdmission(),
292
373
  suspendRequests: requests.suspend,
293
374
  completed,
294
375
  readerReady,
@@ -0,0 +1,9 @@
1
+ import type { AgentProviderKey } from "@agen-ai/agent-protocol";
2
+ import type { AgentProviderOutput } from "../outputs.js";
3
+ /** Holds validated observations in native order until the terminal and its input ownership can publish. */
4
+ export declare function createAgentTurnTerminalBuffer(providerKey: AgentProviderKey, terminal: AgentProviderOutput): {
5
+ append(output: AgentProviderOutput): void;
6
+ take(): AgentProviderOutput | undefined;
7
+ clear(): void;
8
+ };
9
+ //# sourceMappingURL=terminalBuffer.d.ts.map
@@ -0,0 +1,38 @@
1
+ import { throwAgentProviderContractError } from "../contractErrors.js";
2
+ const MAX_TRAILING_OUTPUTS = 32;
3
+ const MAX_RETAINED_BYTES = 16 * 1024 * 1024;
4
+ function retainedOutputBytes(output) {
5
+ if (output.kind === "artifact" && output.candidate.source.kind === "bytes") {
6
+ const metadata = { ...output, candidate: { ...output.candidate, source: { kind: "bytes" } } };
7
+ return output.candidate.source.bytes.byteLength + Buffer.byteLength(JSON.stringify(metadata), "utf8");
8
+ }
9
+ return Buffer.byteLength(JSON.stringify(output), "utf8");
10
+ }
11
+ function createAgentTurnTerminalBuffer(providerKey, terminal) {
12
+ const trailing = [];
13
+ let retainedBytes = retainedOutputBytes(terminal);
14
+ return {
15
+ append(output) {
16
+ const bytes = retainedOutputBytes(output);
17
+ if (trailing.length >= MAX_TRAILING_OUTPUTS || retainedBytes + bytes > MAX_RETAINED_BYTES) {
18
+ throwAgentProviderContractError(
19
+ providerKey,
20
+ "invalid_operation_result",
21
+ "Provider exceeded bounded terminal output capacity."
22
+ );
23
+ }
24
+ trailing.push(output);
25
+ retainedBytes += bytes;
26
+ },
27
+ take() {
28
+ return trailing.shift();
29
+ },
30
+ clear() {
31
+ trailing.length = 0;
32
+ retainedBytes = 0;
33
+ }
34
+ };
35
+ }
36
+ export {
37
+ createAgentTurnTerminalBuffer
38
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agen-ai/agent-runtime",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
4
4
  "private": false,
5
5
  "description": "Provider-neutral driver, instance, adapter, session, output, and conformance contracts for coding-agent runtimes.",
6
6
  "type": "module",
@@ -28,10 +28,14 @@
28
28
  "./environment": {
29
29
  "types": "./dist/environment/index.d.ts",
30
30
  "import": "./dist/environment/index.js"
31
+ },
32
+ "./generation": {
33
+ "types": "./dist/generation/index.d.ts",
34
+ "import": "./dist/generation/index.js"
31
35
  }
32
36
  },
33
37
  "dependencies": {
34
- "@agen-ai/agent-protocol": "^0.4.3"
38
+ "@agen-ai/agent-protocol": "^0.4.5"
35
39
  },
36
40
  "devDependencies": {
37
41
  "tsup": "^8.5.0"