@agen-ai/agent-runtime 0.4.3 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +297 -10
- package/dist/adapterValidation.js +38 -4
- package/dist/contractErrors.d.ts +2 -2
- package/dist/contractErrors.js +8 -1
- package/dist/executionInput/index.d.ts +3 -0
- package/dist/executionInput/index.js +4 -0
- package/dist/executionInput/lifetime.d.ts +47 -0
- package/dist/executionInput/lifetime.js +241 -0
- package/dist/executionInput/types.d.ts +61 -0
- package/dist/executionInput/types.js +0 -0
- package/dist/executionInput/validation.d.ts +25 -0
- package/dist/executionInput/validation.js +178 -0
- package/dist/generation/cleanup.d.ts +15 -0
- package/dist/generation/cleanup.js +108 -0
- package/dist/generation/deadline.d.ts +16 -0
- package/dist/generation/deadline.js +81 -0
- package/dist/generation/decoding.d.ts +4 -0
- package/dist/generation/decoding.js +59 -0
- package/dist/generation/errors.d.ts +29 -0
- package/dist/generation/errors.js +39 -0
- package/dist/generation/generateObject.d.ts +3 -0
- package/dist/generation/generateObject.js +148 -0
- package/dist/generation/index.d.ts +5 -0
- package/dist/generation/index.js +8 -0
- package/dist/generation/preflight.d.ts +25 -0
- package/dist/generation/preflight.js +48 -0
- package/dist/generation/requests.d.ts +17 -0
- package/dist/generation/requests.js +117 -0
- package/dist/generation/types.d.ts +22 -0
- package/dist/generation/types.js +0 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +7 -1
- package/dist/nativeContinuations/admission.js +1 -0
- package/dist/outputValidation.d.ts +2 -0
- package/dist/outputValidation.js +29 -1
- package/dist/sessionInteractions/lifetime.d.ts +1 -0
- package/dist/sessionInteractions/lifetime.js +4 -0
- package/dist/sessionInteractions/request.d.ts +1 -0
- package/dist/sessionInteractions/request.js +3 -0
- package/dist/sessionValidation.d.ts +13 -3
- package/dist/sessionValidation.js +80 -6
- package/dist/sessions.d.ts +7 -1
- package/dist/structuredOutput/admission.d.ts +16 -0
- package/dist/structuredOutput/admission.js +89 -0
- package/dist/structuredOutput/candidate.d.ts +22 -0
- package/dist/structuredOutput/candidate.js +64 -0
- package/dist/structuredOutput/validation.d.ts +8 -0
- package/dist/structuredOutput/validation.js +26 -0
- package/dist/testing/conformance.d.ts +20 -1
- package/dist/testing/conformance.js +103 -3
- package/dist/testing/fakeProvider.js +5 -4
- package/dist/turnExecution/execution.d.ts +9 -1
- package/dist/turnExecution/execution.js +88 -7
- package/dist/turnExecution/terminalBuffer.d.ts +9 -0
- package/dist/turnExecution/terminalBuffer.js +38 -0
- 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:
|
|
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:
|
|
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 =
|
|
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({
|
|
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
|
+
"version": "0.4.4",
|
|
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.
|
|
38
|
+
"@agen-ai/agent-protocol": "^0.4.4"
|
|
35
39
|
},
|
|
36
40
|
"devDependencies": {
|
|
37
41
|
"tsup": "^8.5.0"
|