@agen-ai/agent-runtime 0.3.1 → 0.4.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 (37) hide show
  1. package/README.md +176 -25
  2. package/dist/adapterValidation.d.ts +1 -1
  3. package/dist/adapterValidation.js +162 -95
  4. package/dist/contractErrors.d.ts +15 -2
  5. package/dist/contractErrors.js +39 -1
  6. package/dist/hostBindings/index.d.ts +4 -0
  7. package/dist/hostBindings/index.js +9 -0
  8. package/dist/hostBindings/lifetime.d.ts +25 -0
  9. package/dist/hostBindings/lifetime.js +147 -0
  10. package/dist/hostBindings/opening.d.ts +12 -0
  11. package/dist/hostBindings/opening.js +24 -0
  12. package/dist/hostBindings/schema.d.ts +9 -0
  13. package/dist/hostBindings/schema.js +169 -0
  14. package/dist/hostBindings/types.d.ts +71 -0
  15. package/dist/hostBindings/types.js +0 -0
  16. package/dist/hostBindings/validation.d.ts +14 -0
  17. package/dist/hostBindings/validation.js +107 -0
  18. package/dist/index.d.ts +2 -0
  19. package/dist/index.js +6 -0
  20. package/dist/internal/objectShape.d.ts +2 -0
  21. package/dist/internal/objectShape.js +7 -0
  22. package/dist/outputValidation.js +1 -0
  23. package/dist/sessionCleanup.d.ts +9 -0
  24. package/dist/sessionCleanup.js +17 -0
  25. package/dist/sessionValidation.d.ts +12 -2
  26. package/dist/sessionValidation.js +134 -305
  27. package/dist/sessions.d.ts +28 -4
  28. package/dist/testing/conformance.d.ts +3 -3
  29. package/dist/testing/conformance.js +24 -30
  30. package/dist/testing/fakeProvider.js +56 -45
  31. package/dist/turnExecution/execution.d.ts +26 -0
  32. package/dist/turnExecution/execution.js +214 -0
  33. package/dist/turnExecution/receipts.d.ts +6 -0
  34. package/dist/turnExecution/receipts.js +39 -0
  35. package/dist/turnExecution/requests.d.ts +19 -0
  36. package/dist/turnExecution/requests.js +56 -0
  37. package/package.json +2 -2
package/README.md CHANGED
@@ -8,8 +8,10 @@ sessions; each returned session owns its binding and conversation-local operatio
8
8
 
9
9
  The runtime depends only on `@agen-ai/agent-protocol`. It has no concept of tenants, SaaS
10
10
  workspaces, assigned users, database rows, persistence sequence, visibility, billing, host boots,
11
- leases, or storage policy. A host must authorize and select an instance before calling this SPI.
12
- This source package implements Agent Protocol V9 as part of the coordinated SDK `0.3.1` contract.
11
+ distributed leases, or storage policy. A host must authorize and select an instance before calling this SPI.
12
+ This source package implements Agent Protocol V10 as part of the coordinated SDK
13
+ `0.4.0` contract.
14
+ Upgrade consumers from `0.3.1` using the coordinated release and its migration guide.
13
15
  The SDK contract version is independent of private transport and product persistence versions. This
14
16
  is intentionally one coordinated tuple rather than a deployable mixed-version graph.
15
17
 
@@ -28,7 +30,8 @@ is intentionally one coordinated tuple rather than a deployable mixed-version gr
28
30
  2. Give caller-owned instance definitions to `createAgentProviderRegistry`.
29
31
  3. Select a materialized instance by opaque `instanceId`.
30
32
  4. Create, resume, or branch a provider session through its adapter.
31
- 5. Consume each `runTurn` or `resolveRequest` output stream incrementally. Neutral `AgentEvent`
33
+ 5. Call `startTurn` and consume its single `outputs` stream incrementally. Submit human answers with
34
+ the same execution's `resolveRequest`, which returns an independent delivery receipt. Neutral `AgentEvent`
32
35
  output carries bounded provider source evidence atomically. A `request.opened` output may also
33
36
  carry separately bounded, non-truncated provider request context for continuation; lifecycle,
34
37
  authentication, artifact, and standalone diagnostic evidence remain separate output variants.
@@ -77,26 +80,73 @@ through an unknown-status update, regress it from in-progress to pending, or rev
77
80
  terminal event or status. An `item.completed` event must carry `completed`, `failed`, or `canceled`
78
81
  status. Completion-only snapshots remain valid when no earlier lifecycle event was observed.
79
82
 
80
- The host should serialize mutating operations for a given session. Separate session objects may
81
- run concurrently, so provider implementations must isolate their conversation-local state.
83
+ A session reserves one turn execution synchronously at `startTurn`. Native execution stays lazy until
84
+ its first output pull; an unused handle has no provider effects. Separate sessions run concurrently.
85
+ The execution's `outputs` has exactly one consumer and one outstanding pull. Its ownership continues
86
+ across human requests until native terminal output, entered response/control settlement, and cleanup.
82
87
 
83
88
  Materialization-scoped context usage remains monotonic only within one native provider process. A
84
89
  provider that replaces that process emits a `process.started` lifecycle output as the explicit
85
90
  boundary before reporting usage from the replacement. Validation then clears only the
86
91
  `materialization` occupancy, cumulative counters, and compaction allowance; logical `session`
87
92
  measurements remain continuous.
88
- `runTurn` and `resolveRequest` both preserve consumer backpressure: the provider does not resume
89
- until the consumer requests the next output. Each stream must end at a completed turn or exactly
90
- one newly pending request. If a consumer abandons a stream, a provider throws, or either stream
91
- ends before that stable boundary, the validated session becomes unusable and must be closed rather
92
- than retried. The validated `runTurn` input may observe `onProviderExecutionStarted`; validation
93
- invokes it exactly at candidate-port delegation, after runtime prechecks pass, and never forwards it
94
- to the candidate adapter. A delegated mutating operation that throws or returns an invalid result
95
- has the same effect. Pre-aborted operations fail with `AbortError`; close and disposal must be safe
96
- to call repeatedly. An accepted interruption of an already-waiting turn does not prove that the
97
- turn terminalized; unless the result also carries a terminal event, the validated session becomes
98
- unusable and must be closed and rematerialized. A close failure makes the session unusable while
99
- leaving close itself retryable.
93
+ `startTurn(input)` returns `{ turnId, outputs, resolveRequest }`. `outputs` preserves per-output
94
+ backpressure and stays open during `waiting_for_request`; the consumer can call
95
+ `execution.resolveRequest({ resolution })` as soon as it receives `request.opened`, before its next
96
+ pull. A later queued waiting observation cannot revive an already answered request. No response
97
+ creates or acquires another output stream.
98
+
99
+ `resolveRequest` returns one of these delivery receipts:
100
+
101
+ - `{ status: "delivered" }`: the provider-owned handler accepted the exact canonical answer or step.
102
+ This includes a mapped form-intent step whose later field answer is still pending. It does not
103
+ establish a universal transport write, model consumption, effect completion, or turn success.
104
+ - `{ status: "rejected", requestState: "pending" | "unavailable", error }`: the provider definitively
105
+ refused this answer. Only `pending` preserves that exact callback's answerability; `unavailable`
106
+ retires it. Neither value terminalizes the original output owner.
107
+ - `{ status: "delivery_uncertain", error }`: native delivery started without definitive acceptance
108
+ or refusal. Further mutation and replay are forbidden; the original output/cleanup owner remains.
109
+
110
+ Local parsing, request correlation, expiry, and pre-abort failures reject with their precise original
111
+ errors before candidate entry. Validation atomically claims an answer before invoking the optional
112
+ synchronous `onProviderExecutionStarted` observer and then the candidate. The runtime strips this
113
+ observer from candidate input. A throwing observer prevents native entry and preserves the request
114
+ only while it remains answerable. An unexpected candidate response exception carries
115
+ `AgentProviderDelegatedOperationError` with `operation: "resolve_request"` and started-execution evidence;
116
+ it also forbids further mutation. Concurrent duplicate answers cannot enter the provider twice.
117
+
118
+ The turn input has the same synchronous start observer at its lazy candidate-entry boundary, after
119
+ prechecks pass. Abort or iterator return before the first pull releases the reservation without
120
+ poisoning the session. An aborted old handle rejects on a later pull. Abandoning an entered output
121
+ consumer stops admission and closes its native owner; an incomplete stream makes the session unusable.
122
+ Interruption remains a session control: dispatch acceptance leaves the original stream open to
123
+ observe actual native cancellation completion. Its `AgentProviderTurnInterruptionResult` carries only
124
+ status and optional error; interruption cannot return turn output. A reserved turn rejects interruption
125
+ with `active_turn_mismatch`, including after iterator acquisition; the first pull must enter the provider.
126
+ Abort or return cancels a reservation locally. Admitted interruption suspends exact request admission;
127
+ definitive failed interruptions restore only the unchanged live callback after every overlapping
128
+ interruption fails. A concurrent response refusal with `requestState: "pending"` retains that callback
129
+ through suspension; an accepted interruption withdraws it regardless of response completion order.
130
+
131
+ Close first stops admission, signals cancellation, and initiates native quiescence; it then joins
132
+ entered output/response work and drains host callbacks before releasing resources. Native close is
133
+ initiated before joining acknowledgments that depend on it. A failed close keeps the stopped owner
134
+ and host binding retained while close remains retryable. Successful close and disposal are idempotent.
135
+
136
+ Ordinary answers past a request's `expiresAt` fail locally. A canonical `canceled` resolution still
137
+ checks the live handler's availability; expiry alone cannot establish delivery. Terminal observation
138
+ fences new answers even while an entered response is settling, and waiting observations cannot
139
+ reopen an older request. A host must cancel human decisions when their execution ends and join
140
+ those tasks during cleanup. Keep the session or exact opening-cleanup receipt if cleanup fails;
141
+ disposing its registry or host resources before quiescence would lose the retained owner.
142
+
143
+ `runtime.retrying` reports a retry already owned by the provider on the active turn. It preserves
144
+ turn ownership, pending requests, and per-output backpressure; it neither starts another execution
145
+ nor completes the turn. It is rejected before `turn.started`, after `turn.completed`, after a
146
+ `final_diff` terminal materialization, and from session-operation observations. The event's neutral
147
+ failure category and recovery hint describe the observed condition. A recovery hint never authorizes
148
+ replaying work or changes an uncertain execution outcome. Contract failures, delegated-operation
149
+ started evidence, and retained cleanup responsibilities remain separate from those failure values.
100
150
 
101
151
  When context usage is advertised, the validated session enforces the declared measurement scopes
102
152
  and cumulative fields across turns. Identical consecutive samples and decreasing cumulative
@@ -104,13 +154,19 @@ counters are rejected. Occupancy may decrease only after a completed advertised
104
154
  the next accepted sample consumes that allowance. Context output remains subject to the same
105
155
  per-output backpressure and terminal ordering as every other provider event.
106
156
 
107
- When `capabilities.turns.steer` is true, the session exposes `steering.steerTurn`. Steering accepts
157
+ When `capabilities.turns.steer.kind` is `supported`, the session exposes `steering.steerTurn`. Steering accepts
108
158
  the existing turn ID plus the same canonical `parts` and optional `summary` used to start a turn.
109
- It does not create or own an output stream: model output continues on the original `runTurn`
110
- iterator. The receipt is exactly `delivered`, `rejected` with a bounded neutral error, or
159
+ It requires provider entry on the first output pull; a reserved or merely acquired execution rejects
160
+ steering with `active_turn_mismatch` without calling the provider.
161
+ It does not create or own an output stream: model output continues on the original execution's
162
+ `outputs` iterator. The receipt is exactly `delivered`, `rejected` with a bounded neutral error, or
111
163
  `delivery_uncertain` with a bounded neutral error. A provider must use uncertainty when delivery
112
164
  may have started but its authoritative acknowledgement was lost; callers cannot safely replay that
113
- result. The validated runtime preserves pre-delegation validation and abort errors unchanged, while
165
+ result. A returned `delivery_uncertain` makes the validated session unusable for further mutation.
166
+ Keep its original output reader owned while it drains toward terminality; a pending request does not
167
+ end that stream or restore mutation authority. Close the unusable session to quiesce work that cannot
168
+ finish without another response, then rematerialize before further work. The validated runtime preserves pre-delegation
169
+ validation and abort errors unchanged, while
114
170
  provider-delegated steering failures throw `AgentProviderDelegatedOperationError` with the original
115
171
  cause and explicit started-execution evidence so a host can retire the unusable session. Platform
116
172
  scheduling and provider-native queue modes are intentionally outside this SPI.
@@ -128,6 +184,73 @@ identity, declared bounds, forward-only lifecycle transitions, and per-output ba
128
184
  the operation result may settle. Collaboration and generated-resource observations retain the
129
185
  same correlation and lifecycle validation at their respective ports.
130
186
 
187
+ ## Session-local host tools and instructions
188
+
189
+ A host can pass an optional `AgentSessionHostBinding` when creating, resuming or branching a
190
+ session. The binding contains tool definitions, an invocation callback, optional instructions and
191
+ an idempotent `release` callback. Its callbacks close over host authorization; model arguments do
192
+ not supply that authority. This is an additive runtime SPI. It does not enable managed delegation
193
+ or change native collaboration by itself.
194
+
195
+ The optional `@agen-ai/agent-orchestration` package sits above this SPI. It owns managed target policy,
196
+ scoped tools and generated delegation guidance; the runtime has no dependency on that package or
197
+ its explicit MCP transport. A parent's end-turn boundary leaves its session binding alive and does
198
+ not cancel independently owned children. AgenAI Workspaces preferences and Hostd production
199
+ integration remain separate product work.
200
+
201
+ An adapter declares `hostBinding` support, its `callback` or `mcp` transport, and the instruction
202
+ placements it can materialize. Missing support means unsupported. `system` and `developer`
203
+ placements require the corresponding trusted native channels; `tool_context` means tool/server
204
+ guidance. The runtime rejects unsupported requests before native session creation. Adapter authors
205
+ must reject collisions with their native tools and actually install the requested binding.
206
+
207
+ Tools use a bounded, closed JSON Schema 2020-12 subset: objects with declared properties and no
208
+ additional properties, bounded arrays and strings, finite numbers, safe integers, booleans and
209
+ null. Root schemas declare the dialect. References, remote loading, regular expressions, coercion
210
+ and executable validation are unsupported. A binding allows up to 32 tools, 64 KiB of definition
211
+ data, 32 KiB argument/result envelopes and 16 concurrent callbacks. Runtime validation precedes
212
+ callback entry and validates returned results; callback exceptions become sanitized tool errors.
213
+
214
+ Every opening needs a fresh binding object, including resume and branch. The validated opening
215
+ claims a minimally valid binding before other opening checks. After that claim, opening failures
216
+ own cleanup; malformed minimal bindings remain caller-owned and a duplicate claim cannot release
217
+ the original session's binding. `validateAgentSessionHostBinding` validates metadata without claiming it. Explicit transport
218
+ adapters can reuse `validateAgentHostToolInvocation({ binding, invocation })` against that validated
219
+ metadata and `validateAgentHostToolResult(value)` without duplicating the bounded schema interpreter.
220
+ These pure validators do not invoke callbacks, claim leases or provide lifecycle ownership.
221
+
222
+ If a provider opening fails before returning a session and native resources are still pending,
223
+ throw `createAgentProviderSessionCleanupError({ providerKey, openingInput, errors, retryCleanup })`.
224
+ `openingInput` must be the exact context object received by the create/resume/branch port. The
225
+ validated adapter adopts only that exact identity and provider key, stops new host callbacks, and
226
+ retains the original binding. Explicit cleanup retry first quiesces provider resources and then
227
+ releases the binding; propagation does not add another native cleanup attempt. Successful native
228
+ cleanup is retained when only host release needs retry. A plain or foreign cleanup error cannot
229
+ claim the current opening. Callers can use `isAgentProviderSessionCleanupErrorFor(error, input)`
230
+ against their original opening object to identify the retained responsibility.
231
+
232
+ Closing stops new invocations and aborts active callback signals, then joins provider cleanup and
233
+ callback settlement before releasing the host binding. Ending a turn does not release it. Providers
234
+ must quiesce their output/control producers before their close succeeds and clean up partial native
235
+ resources before throwing an opening error without a session. Preserve callback method receivers.
236
+ Hosts must close their sessions before disposing the instance or registry. Registry disposal retains
237
+ its existing instance ownership and does not inventory or close caller-owned session bindings.
238
+
239
+ Cleanup failure leaves authority closed and cleanup retryable. An opened session retries through
240
+ `session.close`. If opening fails and cleanup also fails, catch
241
+ `AgentProviderSessionCleanupError` and check
242
+ `isAgentProviderSessionCleanupErrorFor(error, originalOpeningInput)` before assigning its retained
243
+ handle to an opening. Use a distinct input object for each opening and preserve the exact object
244
+ passed to create, resume or branch; copied inputs
245
+ and constructed errors do not match the runtime's private ownership evidence. This also covers
246
+ invalid candidates without host tools. `AgentProviderHostBindingCleanupError` remains the compatible
247
+ subclass for failures involving a claimed host binding. A provider can propagate another opening's
248
+ error, so `instanceof` alone does not establish ownership. Call the matching error's single-flight
249
+ `retryCleanup()` to finish retained cleanup. It closes the exact rejected session before releasing
250
+ the binding; a completed close is not repeated when only host release failed. Keep this error and
251
+ its diagnostic causes on the trusted host boundary. Never serialize its handle or raw diagnostics
252
+ into a model tool result.
253
+
131
254
  ## Minimal driver
132
255
 
133
256
  ```ts
@@ -178,6 +301,19 @@ The runtime distinguishes programmer/contract failures from provider failures:
178
301
  Provider-native exceptions should be normalized or safely wrapped at the provider boundary. Do
179
302
  not attach credentials, raw prompts, product identities, or unbounded output to public errors.
180
303
 
304
+ Use `AgentError.category`, `code` and `recovery` to describe an expected operational condition.
305
+ They do not replace `AgentProviderContractError`, delegated-operation start evidence, or cleanup
306
+ exceptions. A `runtime.error` observation is not a terminal event; `runtime.retrying` keeps the same
307
+ turn active. Render a definitive failed turn from its `turn.completed` result instead of counting
308
+ each error observation as another failure. A transient classification or a quota reset never proves
309
+ that prior tool effects can be repeated.
310
+
311
+ Cancellation must retain the current stream and resource owners until native settlement and callback
312
+ quiescence are established. An accepted interruption acknowledges dispatch; it does not establish
313
+ cancellation completion. If the outcome remains unknown, preserve that uncertainty, close the unusable
314
+ session and retain any failed cleanup handle for explicit retry. Successful cleanup does not settle
315
+ the uncertain operation or authorize resending it.
316
+
181
317
  ## Scoped environment observation
182
318
 
183
319
  `MaterializedAgentProviderInstance.environment` discovers a caller-selected workspace and
@@ -207,11 +343,12 @@ turn/request ordering, request resolution, steering, interruption, configuration
207
343
  close, and idempotent disposal. Unsupported operations must remain explicit discriminants and
208
344
  must not expose handlers.
209
345
 
210
- The coordinated SDK version for this source is `0.3.1`, and the source implements Agent Protocol
211
- V9. The earlier public `0.3.0` release implemented the same protocol and remains historical.
212
- Upgrade all three SDK packages together. Every materialized instance requires `environment` and
346
+ The coordinated SDK version is `0.4.0` and implements Agent Protocol V10.
347
+ The earlier public `0.3.0` and `0.3.1` releases implemented V9 and remain historical.
348
+ Keep installed packages in the seven-package SDK release family on the coordinated version; the
349
+ orchestration and ACP packages remain optional. Every materialized instance requires `environment` and
213
350
  `accountQuota`; every session requires `environment`. The former standalone `integrations`
214
- observation port is removed. The public runtime implements Agent Protocol V9 with one current
351
+ observation port is removed. The public runtime implements Agent Protocol V10 with one current
215
352
  adapter/session surface. Package versions remain independent of private transport and product
216
353
  persistence versions.
217
354
 
@@ -222,3 +359,17 @@ From the public repository root, run `pnpm check`.
222
359
  It installs tarballs into a temporary project outside the workspace, typechecks every documented
223
360
  entrypoint, runs the fake provider and parser flow, and removes the temporary directory. It never
224
361
  publishes or accesses release credentials.
362
+
363
+ The public repository also contains `examples/delegation`: a shared host composition with direct
364
+ and credentialless ACP workflows plus a separately qualified SQLite/inbox CLI. From that root,
365
+ `pnpm examples:delegation` exercises real steering, queued resume, cancellation and live approval;
366
+ `pnpm examples:delegation:acp` invokes scoped MCP tools and demonstrates a child surviving its
367
+ parent's end-turn boundary. The installed-tarball consumer runs the same source. Synthetic evidence
368
+ does not certify native agents or authorize replay of an uncertain turn.
369
+
370
+ For the smallest runnable lifecycle example, use
371
+ [`examples/live-request-output.ts`](../../examples/live-request-output.ts) in the public repository
372
+ with `pnpm examples:live-request-output`. It observes unrelated output before answering an exact
373
+ request, with one reader and joined cleanup. The public
374
+ [migration guide](../../MIGRATING-TO-0.4.md#continuous-turn-output-and-independent-answers) shows the
375
+ published-`0.3.1` before-example and an independent human-handler pattern for `0.4.0`.
@@ -1,4 +1,4 @@
1
1
  import { type AgentCapabilities } from "@agen-ai/agent-protocol";
2
2
  import type { AgentProviderAdapter } from "./sessions.js";
3
- export declare function validateAgentProviderAdapter(capabilityInput: AgentCapabilities, adapter: AgentProviderAdapter): AgentProviderAdapter;
3
+ export declare function validateAgentProviderAdapter(capabilityInput: AgentCapabilities, adapterInput: AgentProviderAdapter): AgentProviderAdapter;
4
4
  //# sourceMappingURL=adapterValidation.d.ts.map
@@ -13,6 +13,9 @@ import {
13
13
  throwIfAgentOperationAborted
14
14
  } from "./foundation.js";
15
15
  import { containsAgentControlCharacter } from "./internal/controlCharacters.js";
16
+ import { withAgentSessionHostBinding } from "./hostBindings/lifetime.js";
17
+ import { invokeAgentSessionOpening } from "./hostBindings/opening.js";
18
+ import { validateAgentSessionHostBindingSupport } from "./hostBindings/validation.js";
16
19
  import {
17
20
  validateAgentProviderOperationResult,
18
21
  validateAgentProviderOutputForContext
@@ -22,6 +25,7 @@ import {
22
25
  openIdentityCreatingAgentProviderSession,
23
26
  validateAgentProviderSession
24
27
  } from "./sessionValidation.js";
28
+ const validatedAdapters = /* @__PURE__ */ new WeakMap();
25
29
  function parseWorkingDirectory(value) {
26
30
  if (value.length < 1 || value.length > 4096 || !path.isAbsolute(value) || path.normalize(value) !== value || containsAgentControlCharacter(value)) {
27
31
  throw new TypeError(
@@ -40,8 +44,9 @@ function assertAuthenticationOutputKind(providerKey, output) {
40
44
  "Authentication may emit only authentication, lifecycle, or evidence output."
41
45
  );
42
46
  }
43
- function validateAgentProviderAdapter(capabilityInput, adapter) {
47
+ function validateAgentProviderAdapter(capabilityInput, adapterInput) {
44
48
  const capabilities = parseAgentCapabilities(capabilityInput);
49
+ const adapter = validatedAdapters.get(adapterInput) ?? adapterInput;
45
50
  if (adapter === null || typeof adapter !== "object" || typeof adapter.createSession !== "function" || adapter.resumption === null || typeof adapter.resumption !== "object" || !["supported", "unsupported"].includes(adapter.resumption.kind) || adapter.resumption.kind === "supported" && typeof adapter.resumption.resumeSession !== "function" || adapter.branching === null || typeof adapter.branching !== "object" || !["through_turn", "unsupported"].includes(adapter.branching.kind) || adapter.branching.kind === "through_turn" && typeof adapter.branching.branchSession !== "function" || adapter.authentication === null || typeof adapter.authentication !== "object" || !["supported", "unsupported"].includes(adapter.authentication.kind) || adapter.authentication.kind === "supported" && (typeof adapter.authentication.start !== "function" || typeof adapter.authentication.cancel !== "function")) {
46
51
  throwAgentProviderContractError(
47
52
  capabilities.providerKey,
@@ -57,83 +62,127 @@ function validateAgentProviderAdapter(capabilityInput, adapter) {
57
62
  );
58
63
  }
59
64
  const declaredResumption = adapter.resumption;
65
+ let hostBinding;
66
+ try {
67
+ hostBinding = validateAgentSessionHostBindingSupport(adapter.hostBinding);
68
+ } catch {
69
+ return throwAgentProviderContractError(
70
+ capabilities.providerKey,
71
+ "invalid_adapter",
72
+ "Provider declared invalid session host binding support."
73
+ );
74
+ }
60
75
  const resumption = declaredResumption.kind === "unsupported" ? Object.freeze({ kind: "unsupported" }) : Object.freeze({
61
76
  kind: "supported",
62
- resumeSession: async (resumeInput) => {
63
- throwIfAgentOperationAborted(resumeInput.signal);
64
- const parsed = parseAgentSessionOpenInput({
65
- operation: "resume",
66
- sessionId: resumeInput.sessionId,
67
- binding: resumeInput.binding,
68
- configuration: resumeInput.configuration
69
- });
70
- if (parsed.operation !== "resume") {
71
- throw new TypeError("Expected a resume session input.");
72
- }
73
- assertAgentSessionConfigurationSupported(
74
- capabilities,
75
- parsed.configuration
76
- );
77
- const workingDirectory = parseWorkingDirectory(
78
- resumeInput.workingDirectory
79
- );
80
- const binding = parsed.binding;
81
- const candidate = await declaredResumption.resumeSession({
82
- sessionId: parsed.sessionId,
83
- workingDirectory,
84
- binding,
85
- configuration: parsed.configuration,
86
- ...resumeInput.signal === void 0 ? {} : { signal: resumeInput.signal }
87
- });
88
- try {
89
- return validateAgentProviderSession({
77
+ resumeSession: async (resumeInput) => withAgentSessionHostBinding({
78
+ providerKey: capabilities.providerKey,
79
+ binding: resumeInput.hostBinding,
80
+ openingInput: resumeInput,
81
+ support: hostBinding,
82
+ open: async (lease) => {
83
+ throwIfAgentOperationAborted(resumeInput.signal);
84
+ const parsed = parseAgentSessionOpenInput({
85
+ operation: "resume",
86
+ sessionId: resumeInput.sessionId,
87
+ binding: resumeInput.binding,
88
+ configuration: resumeInput.configuration
89
+ });
90
+ if (parsed.operation !== "resume") {
91
+ throw new TypeError("Expected a resume session input.");
92
+ }
93
+ assertAgentSessionConfigurationSupported(
90
94
  capabilities,
91
- sessionId: parsed.sessionId,
92
- candidate,
93
- expectedBinding: binding
95
+ parsed.configuration
96
+ );
97
+ const workingDirectory = parseWorkingDirectory(
98
+ resumeInput.workingDirectory
99
+ );
100
+ const binding = parsed.binding;
101
+ const candidate = await invokeAgentSessionOpening({
102
+ providerKey: capabilities.providerKey,
103
+ openingInput: resumeInput,
104
+ lease,
105
+ open: (context) => declaredResumption.resumeSession(context),
106
+ candidateInput: {
107
+ sessionId: parsed.sessionId,
108
+ workingDirectory,
109
+ binding,
110
+ configuration: parsed.configuration,
111
+ ...lease === void 0 ? {} : { hostBinding: lease.binding },
112
+ ...resumeInput.signal === void 0 ? {} : { signal: resumeInput.signal }
113
+ }
94
114
  });
95
- } catch (error) {
96
- return closeRejectedAgentProviderSession(candidate, error);
115
+ try {
116
+ return validateAgentProviderSession({
117
+ capabilities,
118
+ sessionId: parsed.sessionId,
119
+ candidate,
120
+ expectedBinding: binding,
121
+ ...lease === void 0 ? {} : { hostBindingLease: lease }
122
+ });
123
+ } catch (error) {
124
+ return closeRejectedAgentProviderSession(
125
+ candidate,
126
+ error,
127
+ lease === void 0 ? void 0 : { providerKey: capabilities.providerKey, lease },
128
+ { providerKey: capabilities.providerKey, input: resumeInput }
129
+ );
130
+ }
97
131
  }
98
- }
132
+ })
99
133
  });
100
134
  const declaredBranching = adapter.branching;
101
135
  const branching = declaredBranching.kind === "unsupported" ? Object.freeze({ kind: "unsupported" }) : Object.freeze({
102
136
  kind: "through_turn",
103
- branchSession: async (branchInput) => {
104
- throwIfAgentOperationAborted(branchInput.signal);
105
- const parsed = parseAgentSessionOpenInput({
106
- operation: "branch",
107
- sessionId: branchInput.sessionId,
108
- source: branchInput.source,
109
- configuration: branchInput.configuration
110
- });
111
- if (parsed.operation !== "branch") {
112
- throw new TypeError("Expected a branch session input.");
113
- }
114
- assertAgentSessionConfigurationSupported(
115
- capabilities,
116
- parsed.configuration
117
- );
118
- const workingDirectory = parseWorkingDirectory(
119
- branchInput.workingDirectory
120
- );
121
- const sourceBinding = parsed.source.binding;
122
- return openIdentityCreatingAgentProviderSession({
123
- capabilities,
124
- sessionId: parsed.sessionId,
125
- observer: branchInput.onBindingCreated,
126
- sourceBinding,
127
- open: (observer) => declaredBranching.branchSession({
137
+ branchSession: async (branchInput) => withAgentSessionHostBinding({
138
+ providerKey: capabilities.providerKey,
139
+ binding: branchInput.hostBinding,
140
+ openingInput: branchInput,
141
+ support: hostBinding,
142
+ open: async (lease) => {
143
+ throwIfAgentOperationAborted(branchInput.signal);
144
+ const parsed = parseAgentSessionOpenInput({
145
+ operation: "branch",
146
+ sessionId: branchInput.sessionId,
147
+ source: branchInput.source,
148
+ configuration: branchInput.configuration
149
+ });
150
+ if (parsed.operation !== "branch") {
151
+ throw new TypeError("Expected a branch session input.");
152
+ }
153
+ assertAgentSessionConfigurationSupported(
154
+ capabilities,
155
+ parsed.configuration
156
+ );
157
+ const workingDirectory = parseWorkingDirectory(
158
+ branchInput.workingDirectory
159
+ );
160
+ const sourceBinding = parsed.source.binding;
161
+ return openIdentityCreatingAgentProviderSession({
162
+ capabilities,
128
163
  sessionId: parsed.sessionId,
129
- workingDirectory,
130
- source: parsed.source,
131
- configuration: parsed.configuration,
132
- onBindingCreated: observer,
133
- ...branchInput.signal === void 0 ? {} : { signal: branchInput.signal }
134
- })
135
- });
136
- }
164
+ observer: branchInput.onBindingCreated,
165
+ sourceBinding,
166
+ openingInput: branchInput,
167
+ ...lease === void 0 ? {} : { hostBindingLease: lease },
168
+ open: (observer) => invokeAgentSessionOpening({
169
+ providerKey: capabilities.providerKey,
170
+ openingInput: branchInput,
171
+ lease,
172
+ open: (context) => declaredBranching.branchSession(context),
173
+ candidateInput: {
174
+ sessionId: parsed.sessionId,
175
+ workingDirectory,
176
+ source: parsed.source,
177
+ configuration: parsed.configuration,
178
+ onBindingCreated: observer,
179
+ ...lease === void 0 ? {} : { hostBinding: lease.binding },
180
+ ...branchInput.signal === void 0 ? {} : { signal: branchInput.signal }
181
+ }
182
+ })
183
+ });
184
+ }
185
+ })
137
186
  });
138
187
  const declaredAuthentication = adapter.authentication;
139
188
  const authenticationFlows = capabilities.authentication.kind === "supported" ? capabilities.authentication.flows : [];
@@ -204,38 +253,56 @@ function validateAgentProviderAdapter(capabilityInput, adapter) {
204
253
  return result;
205
254
  }
206
255
  });
207
- return Object.freeze({
208
- createSession: async (createInput) => {
209
- throwIfAgentOperationAborted(createInput.signal);
210
- const parsed = parseAgentSessionOpenInput({
211
- operation: "create",
212
- sessionId: createInput.sessionId,
213
- configuration: createInput.configuration
214
- });
215
- assertAgentSessionConfigurationSupported(
216
- capabilities,
217
- parsed.configuration
218
- );
219
- const workingDirectory = parseWorkingDirectory(
220
- createInput.workingDirectory
221
- );
222
- return openIdentityCreatingAgentProviderSession({
223
- capabilities,
224
- sessionId: parsed.sessionId,
225
- observer: createInput.onBindingCreated,
226
- open: (observer) => adapter.createSession({
256
+ const validated = Object.freeze({
257
+ hostBinding,
258
+ createSession: async (createInput) => withAgentSessionHostBinding({
259
+ providerKey: capabilities.providerKey,
260
+ binding: createInput.hostBinding,
261
+ openingInput: createInput,
262
+ support: hostBinding,
263
+ open: async (lease) => {
264
+ throwIfAgentOperationAborted(createInput.signal);
265
+ const parsed = parseAgentSessionOpenInput({
266
+ operation: "create",
267
+ sessionId: createInput.sessionId,
268
+ configuration: createInput.configuration
269
+ });
270
+ assertAgentSessionConfigurationSupported(
271
+ capabilities,
272
+ parsed.configuration
273
+ );
274
+ const workingDirectory = parseWorkingDirectory(
275
+ createInput.workingDirectory
276
+ );
277
+ return openIdentityCreatingAgentProviderSession({
278
+ capabilities,
227
279
  sessionId: parsed.sessionId,
228
- workingDirectory,
229
- configuration: parsed.configuration,
230
- onBindingCreated: observer,
231
- ...createInput.signal === void 0 ? {} : { signal: createInput.signal }
232
- })
233
- });
234
- },
280
+ observer: createInput.onBindingCreated,
281
+ openingInput: createInput,
282
+ ...lease === void 0 ? {} : { hostBindingLease: lease },
283
+ open: (observer) => invokeAgentSessionOpening({
284
+ providerKey: capabilities.providerKey,
285
+ openingInput: createInput,
286
+ lease,
287
+ open: (context) => adapter.createSession(context),
288
+ candidateInput: {
289
+ sessionId: parsed.sessionId,
290
+ workingDirectory,
291
+ configuration: parsed.configuration,
292
+ onBindingCreated: observer,
293
+ ...lease === void 0 ? {} : { hostBinding: lease.binding },
294
+ ...createInput.signal === void 0 ? {} : { signal: createInput.signal }
295
+ }
296
+ })
297
+ });
298
+ }
299
+ }),
235
300
  resumption,
236
301
  branching,
237
302
  authentication
238
303
  });
304
+ validatedAdapters.set(validated, adapter);
305
+ return validated;
239
306
  }
240
307
  export {
241
308
  validateAgentProviderAdapter
@@ -1,12 +1,25 @@
1
1
  import type { AgentProviderKey } from "@agen-ai/agent-protocol";
2
- export declare const AGENT_PROVIDER_CONTRACT_ERROR_CODES: readonly ["capability_port_mismatch", "invalid_adapter", "invalid_session", "invalid_binding", "binding_callback_missing", "binding_callback_repeated", "binding_callback_mismatch", "resume_binding_mismatch", "branch_binding_reused", "session_closed", "session_unusable", "concurrent_turn", "active_turn_mismatch", "invalid_operation_result", "invalid_inventory", "invalid_collaboration_transition", "input_operation_mismatch", "output_session_mismatch", "output_turn_mismatch", "output_authentication_attempt_mismatch", "output_operation_mismatch", "output_collaboration_mismatch", "output_resource_mismatch", "output_capability_mismatch", "input_capability_mismatch", "invalid_turn_sequence", "request_resolution_mismatch", "configuration_key_unsupported", "configuration_value_unsupported", "request_pending"];
2
+ import type { MaybePromise } from "./foundation.js";
3
+ export declare const AGENT_PROVIDER_CONTRACT_ERROR_CODES: readonly ["capability_port_mismatch", "invalid_adapter", "invalid_session", "invalid_binding", "binding_callback_missing", "binding_callback_repeated", "binding_callback_mismatch", "resume_binding_mismatch", "branch_binding_reused", "session_closed", "session_unusable", "concurrent_turn", "active_turn_mismatch", "invalid_operation_result", "invalid_inventory", "invalid_collaboration_transition", "input_operation_mismatch", "output_session_mismatch", "output_turn_mismatch", "output_authentication_attempt_mismatch", "output_operation_mismatch", "output_collaboration_mismatch", "output_resource_mismatch", "output_capability_mismatch", "input_capability_mismatch", "invalid_turn_sequence", "request_resolution_mismatch", "configuration_key_unsupported", "configuration_value_unsupported", "request_pending", "invalid_host_binding", "host_binding_unsupported", "host_binding_reused", "host_binding_closed", "host_tool_name_conflict", "host_tool_input_invalid", "host_tool_result_invalid", "host_tool_capacity_exhausted"];
3
4
  export type AgentProviderContractErrorCode = (typeof AGENT_PROVIDER_CONTRACT_ERROR_CODES)[number];
4
- export type AgentProviderDelegatedOperation = "steer_turn" | "invoke_operation" | "apply_configuration" | "spawn_collaboration" | "control_collaboration";
5
+ export type AgentProviderDelegatedOperation = "resolve_request" | "steer_turn" | "invoke_operation" | "apply_configuration" | "spawn_collaboration" | "control_collaboration";
5
6
  export declare class AgentProviderContractError extends Error {
6
7
  readonly providerKey: AgentProviderKey;
7
8
  readonly code: AgentProviderContractErrorCode;
8
9
  constructor(providerKey: AgentProviderKey, code: AgentProviderContractErrorCode, message: string);
9
10
  }
11
+ export declare class AgentProviderSessionCleanupError extends AggregateError {
12
+ readonly providerKey: AgentProviderKey;
13
+ readonly retryCleanup: () => Promise<void>;
14
+ constructor(input: Readonly<{
15
+ providerKey: AgentProviderKey;
16
+ errors: readonly unknown[];
17
+ retryCleanup: () => MaybePromise<void>;
18
+ }>);
19
+ }
20
+ export declare class AgentProviderHostBindingCleanupError extends AgentProviderSessionCleanupError {
21
+ constructor(input: ConstructorParameters<typeof AgentProviderSessionCleanupError>[0]);
22
+ }
10
23
  export declare class AgentProviderDelegatedOperationError extends Error {
11
24
  readonly providerKey: AgentProviderKey;
12
25
  readonly operation: AgentProviderDelegatedOperation;