@superdurable/dex-agents 0.0.0-stage → 0.1.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 (57) hide show
  1. package/LICENSE +105 -0
  2. package/README.md +475 -2
  3. package/dist/src/agent-event-writer.d.ts +26 -0
  4. package/dist/src/agent-event-writer.js +110 -0
  5. package/dist/src/agent-event-writer.js.map +1 -0
  6. package/dist/src/agent-framework-runner.d.ts +368 -0
  7. package/dist/src/agent-framework-runner.js +81 -0
  8. package/dist/src/agent-framework-runner.js.map +1 -0
  9. package/dist/src/agent-session-codecs.d.ts +44 -0
  10. package/dist/src/agent-session-codecs.js +355 -0
  11. package/dist/src/agent-session-codecs.js.map +1 -0
  12. package/dist/src/agent-session-flow.d.ts +148 -0
  13. package/dist/src/agent-session-flow.js +276 -0
  14. package/dist/src/agent-session-flow.js.map +1 -0
  15. package/dist/src/agent-session-persistence.d.ts +56 -0
  16. package/dist/src/agent-session-persistence.js +43 -0
  17. package/dist/src/agent-session-persistence.js.map +1 -0
  18. package/dist/src/agent-session-steps.d.ts +86 -0
  19. package/dist/src/agent-session-steps.js +533 -0
  20. package/dist/src/agent-session-steps.js.map +1 -0
  21. package/dist/src/agent-session-transitions.d.ts +61 -0
  22. package/dist/src/agent-session-transitions.js +202 -0
  23. package/dist/src/agent-session-transitions.js.map +1 -0
  24. package/dist/src/agent-session-values.d.ts +337 -0
  25. package/dist/src/agent-session-values.js +127 -0
  26. package/dist/src/agent-session-values.js.map +1 -0
  27. package/dist/src/index.d.ts +4 -0
  28. package/dist/src/index.js +12 -0
  29. package/dist/src/index.js.map +1 -0
  30. package/dist/src/testing/agent-session-test-client.d.ts +148 -0
  31. package/dist/src/testing/agent-session-test-client.js +217 -0
  32. package/dist/src/testing/agent-session-test-client.js.map +1 -0
  33. package/dist/src/testing/agent-test-environment.d.ts +52 -0
  34. package/dist/src/testing/agent-test-environment.js +86 -0
  35. package/dist/src/testing/agent-test-environment.js.map +1 -0
  36. package/dist/src/testing/agent-worker-process.d.ts +91 -0
  37. package/dist/src/testing/agent-worker-process.js +150 -0
  38. package/dist/src/testing/agent-worker-process.js.map +1 -0
  39. package/dist/src/testing/child-process-group.d.ts +33 -0
  40. package/dist/src/testing/child-process-group.js +164 -0
  41. package/dist/src/testing/child-process-group.js.map +1 -0
  42. package/dist/src/testing/coordination.d.ts +99 -0
  43. package/dist/src/testing/coordination.js +155 -0
  44. package/dist/src/testing/coordination.js.map +1 -0
  45. package/dist/src/testing/index.d.ts +7 -0
  46. package/dist/src/testing/index.js +15 -0
  47. package/dist/src/testing/index.js.map +1 -0
  48. package/dist/src/testing/polling.d.ts +31 -0
  49. package/dist/src/testing/polling.js +51 -0
  50. package/dist/src/testing/polling.js.map +1 -0
  51. package/dist/src/testing/private-dex-engine.d.ts +92 -0
  52. package/dist/src/testing/private-dex-engine.js +215 -0
  53. package/dist/src/testing/private-dex-engine.js.map +1 -0
  54. package/dist/src/testing/step-history.d.ts +40 -0
  55. package/dist/src/testing/step-history.js +59 -0
  56. package/dist/src/testing/step-history.js.map +1 -0
  57. package/package.json +50 -4
@@ -0,0 +1,110 @@
1
+ // Copyright (c) 2026 Super Durable, Inc.
2
+ //
3
+ // Licensed under the Sustainable Use License 1.0.
4
+ // You may not use this file except in compliance with the License.
5
+ // See the LICENSE file in the repository root.
6
+ //
7
+ // SPDX-License-Identifier: LicenseRef-Sustainable-Use-1.0
8
+ const deltaFlushIntervalMs = 100;
9
+ const deltaFlushCharacters = 1024;
10
+ export class StreamAgentEventWriter {
11
+ context;
12
+ stream;
13
+ turnId;
14
+ pendingDeltaType;
15
+ pendingDeltaText = "";
16
+ deltaFlushTimer;
17
+ isClosed = false;
18
+ constructor(context, stream, turnId) {
19
+ this.context = context;
20
+ this.stream = stream;
21
+ this.turnId = turnId;
22
+ }
23
+ get attempt() {
24
+ return this.context.attempt;
25
+ }
26
+ writeEvent(type, payload) {
27
+ if (this.isClosed) {
28
+ return;
29
+ }
30
+ this.flushPendingDelta();
31
+ this.writeStreamMessage(type, payload);
32
+ }
33
+ writeTextDelta(text) {
34
+ this.writeDelta("text_delta", text);
35
+ }
36
+ writeReasoningDelta(text) {
37
+ this.writeDelta("reasoning_delta", text);
38
+ }
39
+ writeToolCallStarted(toolCallId, toolName) {
40
+ this.writeEvent("tool_call_started", { tool_call_id: toolCallId, tool_name: toolName });
41
+ }
42
+ writeToolCallFinished(toolCallId, toolName, status) {
43
+ this.writeEvent("tool_call_finished", {
44
+ tool_call_id: toolCallId,
45
+ tool_name: toolName,
46
+ status,
47
+ });
48
+ }
49
+ /** Flushes buffered deltas and drops later writes; call before the handler returns. */
50
+ close() {
51
+ if (this.isClosed) {
52
+ return;
53
+ }
54
+ this.flushPendingDelta();
55
+ this.isClosed = true;
56
+ }
57
+ writeDelta(type, text) {
58
+ if (this.isClosed || text.length === 0) {
59
+ return;
60
+ }
61
+ if (this.pendingDeltaType !== type) {
62
+ this.flushPendingDelta();
63
+ this.pendingDeltaType = type;
64
+ }
65
+ this.pendingDeltaText += text;
66
+ if (this.pendingDeltaText.length >= deltaFlushCharacters) {
67
+ this.flushPendingDelta();
68
+ return;
69
+ }
70
+ this.deltaFlushTimer ??= setTimeout(() => this.flushPendingDelta(), deltaFlushIntervalMs);
71
+ }
72
+ flushPendingDelta() {
73
+ if (this.deltaFlushTimer !== undefined) {
74
+ clearTimeout(this.deltaFlushTimer);
75
+ this.deltaFlushTimer = undefined;
76
+ }
77
+ const type = this.pendingDeltaType;
78
+ const text = this.pendingDeltaText;
79
+ this.pendingDeltaType = undefined;
80
+ this.pendingDeltaText = "";
81
+ if (type === undefined || text.length === 0 || this.isClosed) {
82
+ return;
83
+ }
84
+ this.writeStreamMessage(type, { text });
85
+ }
86
+ writeStreamMessage(type, payload) {
87
+ this.stream.write(this.context, {
88
+ type,
89
+ turnId: this.turnId,
90
+ attempt: this.context.attempt,
91
+ stepExecutionId: this.context.stepExecutionId,
92
+ payload: stringifyWithSortedKeys(payload),
93
+ });
94
+ }
95
+ }
96
+ /** Serializes with sorted object keys, as the Python core does, so payload text is deterministic. */
97
+ export function stringifyWithSortedKeys(value) {
98
+ const text = JSON.stringify(value, (_key, nestedValue) => {
99
+ if (typeof nestedValue !== "object" || nestedValue === null || Array.isArray(nestedValue)) {
100
+ return nestedValue;
101
+ }
102
+ const record = nestedValue;
103
+ return Object.fromEntries(Object.keys(record).sort().map((key) => [key, record[key]]));
104
+ });
105
+ if (text === undefined) {
106
+ throw new TypeError("event payload cannot be serialized as JSON");
107
+ }
108
+ return text;
109
+ }
110
+ //# sourceMappingURL=agent-event-writer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-event-writer.js","sourceRoot":"","sources":["../../src/agent-event-writer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,368 @@
1
+ import type { AgentMessage, AgentToolCall, AgentToolCallStatus, AgentUsage, FrameworkMessage, ToolEffect } from "./agent-session-values.js";
2
+ /**
3
+ * Per-tool override of the effect and approval defaults.
4
+ *
5
+ * Pass policies by tool name in {@link AgentSessionOptions.toolPolicies}. A policy wins over
6
+ * framework metadata such as MCP annotations and over {@link AgentSessionOptions.defaultToolEffect}.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * const toolPolicies = {
11
+ * search_orders: { effect: "read" },
12
+ * refund_order: { effect: "idempotent_write", requiresApproval: true },
13
+ * } satisfies Record<string, ToolPolicy>;
14
+ * ```
15
+ */
16
+ export interface ToolPolicy {
17
+ /** Effect of the tool. */
18
+ readonly effect: ToolEffect;
19
+ /**
20
+ * Whether every call waits for `resolve_interrupt` before it runs. When omitted, calls with effect
21
+ * `non_idempotent_write` or `unclassified` require approval and the others do not.
22
+ */
23
+ readonly requiresApproval?: boolean;
24
+ }
25
+ /** Effective policy of one tool, returned by {@link resolveToolPolicy}. */
26
+ export interface ResolvedToolPolicy {
27
+ /** Effective effect after policies, framework metadata, and the session default. */
28
+ readonly effect: ToolEffect;
29
+ /** Effective approval requirement. */
30
+ readonly requiresApproval: boolean;
31
+ }
32
+ /**
33
+ * Returns the effective policy of one tool.
34
+ *
35
+ * Runners normally call {@link AgentTurnContext.resolveToolPolicy}. Call this function when an adapter
36
+ * classifies tools outside a turn, for example while building framework tool wrappers. Precedence: the
37
+ * tool's {@link ToolPolicy}, then `frameworkEffect`, then `defaultToolEffect`.
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * const policy = resolveToolPolicy("refund_order", flow.options.toolPolicies, flow.options.defaultToolEffect);
42
+ * const label = policy.requiresApproval ? "needs approval" : "runs without approval";
43
+ * ```
44
+ * @param toolName - Tool name as the model calls it.
45
+ * @param toolPolicies - Explicit policies by tool name.
46
+ * @param defaultToolEffect - Effect of a tool without a policy or framework metadata.
47
+ * @param frameworkEffect - Effect declared by framework metadata, or `undefined`.
48
+ * @returns The effective policy.
49
+ */
50
+ export declare function resolveToolPolicy(toolName: string, toolPolicies: Readonly<Record<string, ToolPolicy>>, defaultToolEffect: ToolEffect, frameworkEffect?: ToolEffect): ResolvedToolPolicy;
51
+ /**
52
+ * Writes best-effort live events to the session's `AgentEvents` Stream.
53
+ *
54
+ * Every event carries {@link turnId} and the Step {@link attempt}. Payload objects are written as JSON
55
+ * with sorted keys. Consecutive `text_delta` or `reasoning_delta` writes are coalesced for up to 100
56
+ * milliseconds or 1,024 characters before they reach the Stream. Writes after the handler returns are
57
+ * dropped. Stream delivery is best effort: a retried turn writes its events again under a new attempt,
58
+ * so a UI rebuilds durable state with `get_agent_snapshot`.
59
+ */
60
+ export interface AgentEventWriter {
61
+ /** Turn stamped on every event, or `null` outside a turn. */
62
+ readonly turnId: string | null;
63
+ /** One-based attempt of the Step that owns this writer. */
64
+ readonly attempt: number;
65
+ /**
66
+ * Writes one event with a JSON object payload.
67
+ *
68
+ * Prefer the typed methods for standard types. For an adapter-specific type, use an adapter prefix,
69
+ * such as `"vercel_ai.step_finished"`, so it never collides with an {@link AgentEventType}.
70
+ * @param type - Event type stored in `AgentEvent.type`.
71
+ * @param payload - JSON-compatible object stored as `AgentEvent.payload` text.
72
+ * @throws {TypeError} When the payload cannot be serialized as JSON.
73
+ */
74
+ writeEvent(type: string, payload: Readonly<Record<string, unknown>>): void;
75
+ /**
76
+ * Writes visible assistant text as a `text_delta` event.
77
+ * @param text - Text chunk; empty chunks are ignored.
78
+ */
79
+ writeTextDelta(text: string): void;
80
+ /**
81
+ * Writes a provider-authored reasoning summary chunk as a `reasoning_delta` event.
82
+ * Never label ordinary text or hidden chain of thought as reasoning.
83
+ * @param text - Text chunk; empty chunks are ignored.
84
+ */
85
+ writeReasoningDelta(text: string): void;
86
+ /**
87
+ * Writes a `tool_call_started` event. The session writes it for every tool Step; a runner writes it
88
+ * only for a tool its framework cannot defer and runs inside `runModelStep`.
89
+ * @param toolCallId - Framework tool call ID.
90
+ * @param toolName - Tool name.
91
+ */
92
+ writeToolCallStarted(toolCallId: string, toolName: string): void;
93
+ /**
94
+ * Writes a `tool_call_finished` event.
95
+ * @param toolCallId - Framework tool call ID.
96
+ * @param toolName - Tool name.
97
+ * @param status - `succeeded` or `failed` for a call the runner ran itself, or the final status the
98
+ * session recorded.
99
+ */
100
+ writeToolCallFinished(toolCallId: string, toolName: string, status: AgentToolCallStatus): void;
101
+ }
102
+ /**
103
+ * Everything a runner needs for one model call of a turn, inside the `RunModel` Step.
104
+ *
105
+ * Each `RunModel` Step makes at most one model call. The first `RunModel` of a turn starts from the
106
+ * stored messages. Every later one resumes from {@link frameworkRunState} after the tool calls of the
107
+ * previous model response reached final records. A crash or thrown error reruns only this model call
108
+ * from the same committed state. Do not retain the context after `runModelStep` settles.
109
+ */
110
+ export interface AgentTurnContext {
111
+ /** Flow ID, which is the session ID. */
112
+ readonly flowId: string;
113
+ /** Turn ID, `turn-<n>`. */
114
+ readonly turnId: string;
115
+ /**
116
+ * One-based `RunModel` attempt. A rerun after a Worker crash may keep the same attempt when the
117
+ * crashed attempt sent no output, so do not rely on it to detect reruns.
118
+ */
119
+ readonly attempt: number;
120
+ /**
121
+ * Every stored session message in ascending sequence order, including the user message that
122
+ * started this turn. Replay each `frameworkItem` to the framework unchanged.
123
+ */
124
+ readonly messages: readonly AgentMessage[];
125
+ /**
126
+ * Framework run state from this turn's previous {@link ToolCallsRequested} outcome, or `null` for
127
+ * the turn's first model call.
128
+ */
129
+ readonly frameworkRunState: string | null;
130
+ /** Whether this model call continues the turn after tool calls, that is, `frameworkRunState` is set. */
131
+ readonly isResuming: boolean;
132
+ /**
133
+ * Tool call records of this turn, in deferral order; every record is final. Give the framework the
134
+ * recorded result of each call from the previous model response instead of calling the tool. Use
135
+ * {@link describeToolCallOutcome} when the framework needs result text.
136
+ */
137
+ readonly toolCalls: readonly AgentToolCall[];
138
+ /** Events writer bound to this turn and attempt. */
139
+ readonly events: AgentEventWriter;
140
+ /** Signal aborted when Dex cancels the attempt; pass it to the model call. */
141
+ readonly cancellationSignal: AbortSignal;
142
+ /** Absolute deadline of the current attempt, or `undefined` when Dex supplied none. */
143
+ readonly deadline: Date | undefined;
144
+ /**
145
+ * Returns the effective policy of a tool; see {@link resolveToolPolicy} for the precedence.
146
+ * Pass it unchanged in {@link DeferredToolCall.policy}.
147
+ * @param toolName - Tool name as the model calls it.
148
+ * @param frameworkEffect - Effect declared by framework metadata, for example from
149
+ * {@link toolEffectFromMcpToolAnnotations}, or `undefined`.
150
+ * @returns The effective policy.
151
+ */
152
+ resolveToolPolicy(toolName: string, frameworkEffect?: ToolEffect): ResolvedToolPolicy;
153
+ /**
154
+ * Returns this turn's record for a tool call ID.
155
+ * @param toolCallId - Framework tool call ID.
156
+ * @returns The record, or `undefined` when the turn never requested that call.
157
+ */
158
+ recordedToolCall(toolCallId: string): AgentToolCall | undefined;
159
+ /**
160
+ * Records a heartbeat for the current attempt.
161
+ *
162
+ * The session already records one every 20 seconds while `runModelStep` is pending. Call it only
163
+ * from a long synchronous loop that could otherwise starve the event loop.
164
+ * @returns A Promise that resolves when the Worker accepted the heartbeat.
165
+ */
166
+ heartbeat(): Promise<void>;
167
+ }
168
+ /** One tool call the model requested, returned in {@link ToolCallsRequested}; it runs in its own Step. */
169
+ export interface DeferredToolCall {
170
+ /**
171
+ * Framework tool call ID. It must be non-empty, contain no `/`, and be unique within the turn;
172
+ * frameworks may reuse IDs across turns.
173
+ */
174
+ readonly toolCallId: string;
175
+ /** Tool name. */
176
+ readonly toolName: string;
177
+ /** Tool arguments as JSON text. */
178
+ readonly arguments: string;
179
+ /** Policy from {@link AgentTurnContext.resolveToolPolicy}; it decides approval and the tool Step. */
180
+ readonly policy: ResolvedToolPolicy;
181
+ }
182
+ /** Outcome of a model call that produced the turn's final answer. */
183
+ export interface TurnCompleted {
184
+ /** Discriminator. */
185
+ readonly kind: "completed";
186
+ /**
187
+ * Every message the turn produced after its user message, across all of its model calls, in order:
188
+ * model output, tool requests, tool results, and the final answer. The session appends them in one
189
+ * commit and deletes the turn's tool call records.
190
+ */
191
+ readonly newMessages: readonly FrameworkMessage[];
192
+ /** Usage of this model call only; the session adds it to the cumulative usage. */
193
+ readonly usage: AgentUsage;
194
+ }
195
+ /** Outcome of a model call that requested tool calls. */
196
+ export interface ToolCallsRequested {
197
+ /** Discriminator. */
198
+ readonly kind: "tool_calls_requested";
199
+ /**
200
+ * Non-empty serialized framework state from which the next `RunModel` continues the turn, including
201
+ * the messages this turn has produced so far. The session stores no partial messages; it keeps this
202
+ * value opaquely in `FrameworkRunState`.
203
+ */
204
+ readonly frameworkRunState: string;
205
+ /**
206
+ * Every tool call of the model response, at least one. Each runs in its own Step, in parallel. Do not
207
+ * repeat a call that already has a record in {@link AgentTurnContext.toolCalls}; the session rejects
208
+ * that as a runner defect.
209
+ */
210
+ readonly toolCalls: readonly DeferredToolCall[];
211
+ /** Usage of this model call only. */
212
+ readonly usage: AgentUsage;
213
+ }
214
+ /** Result of {@link AgentFrameworkRunner.runModelStep}. */
215
+ export type AgentTurnOutcome = TurnCompleted | ToolCallsRequested;
216
+ /** One tool call that a tool Step asks the runner to run. */
217
+ export interface DeferredToolExecution {
218
+ /** Flow ID, which is the session ID. */
219
+ readonly flowId: string;
220
+ /** The call's record; its `effect` selected `CallTool` or `CallSingleAttemptTool`. */
221
+ readonly toolCall: AgentToolCall;
222
+ /**
223
+ * Stable key `<flowId>/<turnId>/<toolCallId>`, identical across attempts of the call. Pass it to
224
+ * services that deduplicate requests; `CallTool` retries an `idempotent_write` call with the same
225
+ * key. The turn ID keeps keys distinct when a framework reuses tool call IDs across turns.
226
+ */
227
+ readonly idempotencyKey: string;
228
+ /**
229
+ * One-based attempt of the tool Step. `read` and `idempotent_write` calls get up to three attempts;
230
+ * other calls get one.
231
+ */
232
+ readonly attempt: number;
233
+ /** Events writer bound to the turn and attempt. */
234
+ readonly events: AgentEventWriter;
235
+ /** Signal aborted when Dex cancels the attempt. */
236
+ readonly cancellationSignal: AbortSignal;
237
+ /** Absolute deadline of the current attempt, or `undefined`. */
238
+ readonly deadline: Date | undefined;
239
+ /**
240
+ * Records a heartbeat. The session already records one every 20 seconds.
241
+ * @returns A Promise that resolves when the Worker accepted the heartbeat.
242
+ */
243
+ heartbeat(): Promise<void>;
244
+ }
245
+ /** Successful result of one deferred tool call. */
246
+ export interface ToolExecutionResult {
247
+ /** Output text recorded on the call and shown to the model. */
248
+ readonly output: string;
249
+ }
250
+ /**
251
+ * Tool failure that the model should see, thrown from {@link AgentFrameworkRunner.executeDeferredToolCall}.
252
+ *
253
+ * The session records the call as `failed` with this error's message and does not retry it. Throw any
254
+ * other error for an unexpected failure: `CallTool` retries it and, once its attempts run out, records
255
+ * `failed` for a `read` tool and `outcome_unknown` for a write; `CallSingleAttemptTool` records
256
+ * `outcome_unknown` at once, because the write may have taken effect.
257
+ *
258
+ * @example
259
+ * ```ts
260
+ * if (response.status === 404) {
261
+ * throw new ToolExecutionError(`order ${orderId} does not exist`);
262
+ * }
263
+ * ```
264
+ */
265
+ export declare class ToolExecutionError extends Error {
266
+ /**
267
+ * Creates a tool failure.
268
+ * @param message - Error text recorded on the call and shown to the model.
269
+ * @param options - Standard error options, such as `cause`.
270
+ */
271
+ constructor(message: string, options?: ErrorOptions);
272
+ }
273
+ /**
274
+ * Framework-specific logic that the shared agent session Flow calls.
275
+ *
276
+ * An adapter package implements one runner per framework and passes it to
277
+ * {@link createAgentSessionFlow}. The Flow owns durability; the runner only translates between
278
+ * the framework and the session values.
279
+ *
280
+ * Contract:
281
+ * - `runModelStep` makes at most one model call. It must stop before running any tool the framework
282
+ * can defer, and return every requested tool call in {@link ToolCallsRequested}. Each call then runs
283
+ * in its own Step: `CallTool` for `read` and `idempotent_write`, `CallSingleAttemptTool` for the
284
+ * other effects, after `AwaitToolApproval` when the policy requires approval.
285
+ * - When `frameworkRunState` is set, `runModelStep` restores the framework and gives it the recorded
286
+ * result of every call from the previous response before making the next model call.
287
+ * - A tool the framework cannot defer runs inside `runModelStep` and repeats when the model call
288
+ * reruns; the adapter documents such tools as at-least-once.
289
+ * - `runModelStep` and `executeDeferredToolCall` must not keep state between calls; a later call may
290
+ * run in another Worker process.
291
+ * - Throwing from `runModelStep` retries the model call, up to 3 attempts within 30 minutes; then the
292
+ * turn fails, the session records `last_turn_error`, and it waits for the next user message.
293
+ *
294
+ * @example
295
+ * ```ts
296
+ * class EchoRunner implements AgentFrameworkRunner {
297
+ * public buildUserMessage(text: string): FrameworkMessage {
298
+ * return { role: "user", text, toolCallId: null, toolName: null, toolArguments: null,
299
+ * toolOutput: null, frameworkItem: JSON.stringify({ role: "user", content: text }) };
300
+ * }
301
+ * public async runModelStep(turn: AgentTurnContext): Promise<AgentTurnOutcome> {
302
+ * const last = turn.messages.at(-1)?.text ?? "";
303
+ * turn.events.writeTextDelta(last);
304
+ * return { kind: "completed", usage: emptyAgentUsage(), newMessages: [{ role: "assistant", text: last,
305
+ * toolCallId: null, toolName: null, toolArguments: null, toolOutput: null,
306
+ * frameworkItem: JSON.stringify({ role: "assistant", content: last }) }] };
307
+ * }
308
+ * public async executeDeferredToolCall(): Promise<ToolExecutionResult> {
309
+ * throw new ToolExecutionError("this agent has no tools");
310
+ * }
311
+ * }
312
+ * ```
313
+ */
314
+ export interface AgentFrameworkRunner {
315
+ /**
316
+ * Converts user text into the framework's user message.
317
+ *
318
+ * The session calls it when it consumes a user message and stores the result before `runModelStep`.
319
+ * It must be synchronous, deterministic, and free of side effects.
320
+ *
321
+ * @param text - User message text, never blank.
322
+ * @returns The framework message, with `role` `"user"`.
323
+ */
324
+ buildUserMessage(text: string): FrameworkMessage;
325
+ /**
326
+ * Makes the turn's next model call, at most one.
327
+ *
328
+ * @param turn - Turn context; see {@link AgentTurnContext}.
329
+ * @returns {@link TurnCompleted} with every new message of the turn, or {@link ToolCallsRequested}
330
+ * with the framework run state and every tool call of the model response.
331
+ * @throws Any error to retry the model call; after the last attempt the turn fails.
332
+ */
333
+ runModelStep(turn: AgentTurnContext): Promise<AgentTurnOutcome>;
334
+ /**
335
+ * Runs the real tool for one tool call, in `CallTool` or `CallSingleAttemptTool`.
336
+ *
337
+ * @param call - Deferred call to run.
338
+ * @returns The tool output text.
339
+ * @throws {ToolExecutionError} For a failure the model should see; the call is recorded as `failed`.
340
+ * @throws Any other error for an unexpected failure; after the last attempt the record becomes
341
+ * `failed` for a `read` tool and `outcome_unknown` for a write. See {@link ToolExecutionError}.
342
+ */
343
+ executeDeferredToolCall(call: DeferredToolExecution): Promise<ToolExecutionResult>;
344
+ }
345
+ /** MCP tool annotations that classify a tool's side effects. */
346
+ export interface McpToolAnnotations {
347
+ /** MCP `readOnlyHint`: the tool does not modify its environment. */
348
+ readonly readOnlyHint?: boolean;
349
+ /** MCP `idempotentHint`: repeated calls with the same arguments have no additional effect. */
350
+ readonly idempotentHint?: boolean;
351
+ /** MCP `destructiveHint`: the tool may perform destructive updates. */
352
+ readonly destructiveHint?: boolean;
353
+ }
354
+ /**
355
+ * Maps MCP tool annotations to a framework effect for {@link AgentTurnContext.resolveToolPolicy}.
356
+ *
357
+ * Annotations are hints from the tool server. Pass the result only for servers the application
358
+ * trusts; otherwise leave the effect to policies and the session default.
359
+ *
360
+ * @example
361
+ * ```ts
362
+ * const policy = turn.resolveToolPolicy(tool.name, toolEffectFromMcpToolAnnotations(tool.annotations ?? {}));
363
+ * ```
364
+ * @param annotations - The tool's MCP annotations.
365
+ * @returns `read` when `readOnlyHint` is `true`; `idempotent_write` when `idempotentHint` is `true`
366
+ * and `destructiveHint` is not `true`; otherwise `undefined`, so the session default applies.
367
+ */
368
+ export declare function toolEffectFromMcpToolAnnotations(annotations: McpToolAnnotations): ToolEffect | undefined;
@@ -0,0 +1,81 @@
1
+ // Copyright (c) 2026 Super Durable, Inc.
2
+ //
3
+ // Licensed under the Sustainable Use License 1.0.
4
+ // You may not use this file except in compliance with the License.
5
+ // See the LICENSE file in the repository root.
6
+ //
7
+ // SPDX-License-Identifier: LicenseRef-Sustainable-Use-1.0
8
+ /**
9
+ * Returns the effective policy of one tool.
10
+ *
11
+ * Runners normally call {@link AgentTurnContext.resolveToolPolicy}. Call this function when an adapter
12
+ * classifies tools outside a turn, for example while building framework tool wrappers. Precedence: the
13
+ * tool's {@link ToolPolicy}, then `frameworkEffect`, then `defaultToolEffect`.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * const policy = resolveToolPolicy("refund_order", flow.options.toolPolicies, flow.options.defaultToolEffect);
18
+ * const label = policy.requiresApproval ? "needs approval" : "runs without approval";
19
+ * ```
20
+ * @param toolName - Tool name as the model calls it.
21
+ * @param toolPolicies - Explicit policies by tool name.
22
+ * @param defaultToolEffect - Effect of a tool without a policy or framework metadata.
23
+ * @param frameworkEffect - Effect declared by framework metadata, or `undefined`.
24
+ * @returns The effective policy.
25
+ */
26
+ export function resolveToolPolicy(toolName, toolPolicies, defaultToolEffect, frameworkEffect) {
27
+ const policy = Object.hasOwn(toolPolicies, toolName) ? toolPolicies[toolName] : undefined;
28
+ const effect = policy?.effect ?? frameworkEffect ?? defaultToolEffect;
29
+ const requiresApproval = policy?.requiresApproval ?? (effect === "non_idempotent_write" || effect === "unclassified");
30
+ return { effect, requiresApproval };
31
+ }
32
+ /**
33
+ * Tool failure that the model should see, thrown from {@link AgentFrameworkRunner.executeDeferredToolCall}.
34
+ *
35
+ * The session records the call as `failed` with this error's message and does not retry it. Throw any
36
+ * other error for an unexpected failure: `CallTool` retries it and, once its attempts run out, records
37
+ * `failed` for a `read` tool and `outcome_unknown` for a write; `CallSingleAttemptTool` records
38
+ * `outcome_unknown` at once, because the write may have taken effect.
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * if (response.status === 404) {
43
+ * throw new ToolExecutionError(`order ${orderId} does not exist`);
44
+ * }
45
+ * ```
46
+ */
47
+ export class ToolExecutionError extends Error {
48
+ /**
49
+ * Creates a tool failure.
50
+ * @param message - Error text recorded on the call and shown to the model.
51
+ * @param options - Standard error options, such as `cause`.
52
+ */
53
+ constructor(message, options) {
54
+ super(message, options);
55
+ this.name = "ToolExecutionError";
56
+ }
57
+ }
58
+ /**
59
+ * Maps MCP tool annotations to a framework effect for {@link AgentTurnContext.resolveToolPolicy}.
60
+ *
61
+ * Annotations are hints from the tool server. Pass the result only for servers the application
62
+ * trusts; otherwise leave the effect to policies and the session default.
63
+ *
64
+ * @example
65
+ * ```ts
66
+ * const policy = turn.resolveToolPolicy(tool.name, toolEffectFromMcpToolAnnotations(tool.annotations ?? {}));
67
+ * ```
68
+ * @param annotations - The tool's MCP annotations.
69
+ * @returns `read` when `readOnlyHint` is `true`; `idempotent_write` when `idempotentHint` is `true`
70
+ * and `destructiveHint` is not `true`; otherwise `undefined`, so the session default applies.
71
+ */
72
+ export function toolEffectFromMcpToolAnnotations(annotations) {
73
+ if (annotations.readOnlyHint === true) {
74
+ return "read";
75
+ }
76
+ if (annotations.idempotentHint === true && annotations.destructiveHint !== true) {
77
+ return "idempotent_write";
78
+ }
79
+ return undefined;
80
+ }
81
+ //# sourceMappingURL=agent-framework-runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-framework-runner.js","sourceRoot":"","sources":["../../src/agent-framework-runner.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,EAAE;AACF,kDAAkD;AAClD,mEAAmE;AACnE,+CAA+C;AAC/C,EAAE;AACF,0DAA0D;AA2C1D;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,iBAAiB,CAC/B,QAAgB,EAChB,YAAkD,EAClD,iBAA6B,EAC7B,eAA4B;IAE5B,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC1F,MAAM,MAAM,GAAG,MAAM,EAAE,MAAM,IAAI,eAAe,IAAI,iBAAiB,CAAC;IACtE,MAAM,gBAAgB,GACpB,MAAM,EAAE,gBAAgB,IAAI,CAAC,MAAM,KAAK,sBAAsB,IAAI,MAAM,KAAK,cAAc,CAAC,CAAC;IAC/F,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC;AACtC,CAAC;AAiND;;;;;;;;;;;;;;GAcG;AACH,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAC3C;;;;OAIG;IACH,YAAmB,OAAe,EAAE,OAAsB;QACxD,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IACnC,CAAC;CACF;AAqFD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,gCAAgC,CAC9C,WAA+B;IAE/B,IAAI,WAAW,CAAC,YAAY,KAAK,IAAI,EAAE,CAAC;QACtC,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,IAAI,WAAW,CAAC,cAAc,KAAK,IAAI,IAAI,WAAW,CAAC,eAAe,KAAK,IAAI,EAAE,CAAC;QAChF,OAAO,kBAAkB,CAAC;IAC5B,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
@@ -0,0 +1,44 @@
1
+ import { type Codec } from "@superdurable/dex";
2
+ import type { AgentEvent, AgentInterrupt, AgentInterruptResolution, AgentMessage, AgentMessagesPage, AgentSessionStartInput, AgentSnapshot, AgentState, AgentToolCall, AgentUsage, AgentUserMessage, ListAgentMessagesInput, ResolveInterruptInput, ToolCallCompleted } from "./agent-session-values.js";
3
+ export declare function encodeAgentSessionStartInput(value: AgentSessionStartInput): unknown;
4
+ export declare function decodeAgentSessionStartInput(value: unknown): AgentSessionStartInput;
5
+ export declare function encodeAgentState(value: AgentState): unknown;
6
+ export declare function decodeAgentState(value: unknown): AgentState;
7
+ export declare function encodeAgentMessage(value: AgentMessage): unknown;
8
+ export declare function decodeAgentMessage(value: unknown): AgentMessage;
9
+ export declare function encodeAgentToolCall(value: AgentToolCall): unknown;
10
+ export declare function decodeAgentToolCall(value: unknown): AgentToolCall;
11
+ export declare function encodeAgentInterrupt(value: AgentInterrupt): unknown;
12
+ export declare function decodeAgentInterrupt(value: unknown): AgentInterrupt;
13
+ export declare function encodeAgentInterruptResolution(value: AgentInterruptResolution): unknown;
14
+ export declare function decodeAgentInterruptResolution(value: unknown): AgentInterruptResolution;
15
+ export declare function encodeAgentUsage(value: AgentUsage): unknown;
16
+ export declare function decodeAgentUsage(value: unknown): AgentUsage;
17
+ export declare function encodeAgentUserMessage(value: AgentUserMessage): unknown;
18
+ export declare function decodeAgentUserMessage(value: unknown): AgentUserMessage;
19
+ export declare function encodeToolCallCompleted(value: ToolCallCompleted): unknown;
20
+ export declare function decodeToolCallCompleted(value: unknown): ToolCallCompleted;
21
+ export declare function encodeAgentEvent(value: AgentEvent): unknown;
22
+ export declare function decodeAgentEvent(value: unknown): AgentEvent;
23
+ export declare function encodeResolveInterruptInput(value: ResolveInterruptInput): unknown;
24
+ export declare function decodeResolveInterruptInput(value: unknown): ResolveInterruptInput;
25
+ export declare function encodeAgentSnapshot(value: AgentSnapshot): unknown;
26
+ export declare function decodeAgentSnapshot(value: unknown): AgentSnapshot;
27
+ export declare function encodeListAgentMessagesInput(value: ListAgentMessagesInput): unknown;
28
+ export declare function decodeListAgentMessagesInput(value: unknown): ListAgentMessagesInput;
29
+ export declare function encodeAgentMessagesPage(value: AgentMessagesPage): unknown;
30
+ export declare function decodeAgentMessagesPage(value: unknown): AgentMessagesPage;
31
+ export declare const agentSessionStartInputCodec: Codec<AgentSessionStartInput>;
32
+ export declare const agentStateCodec: Codec<AgentState>;
33
+ export declare const agentMessageCodec: Codec<AgentMessage>;
34
+ export declare const agentToolCallCodec: Codec<AgentToolCall>;
35
+ export declare const agentInterruptListCodec: Codec<readonly AgentInterrupt[]>;
36
+ export declare const agentInterruptResolutionCodec: Codec<AgentInterruptResolution>;
37
+ export declare const agentUsageCodec: Codec<AgentUsage>;
38
+ export declare const agentUserMessageCodec: Codec<AgentUserMessage>;
39
+ export declare const toolCallCompletedCodec: Codec<ToolCallCompleted>;
40
+ export declare const agentEventCodec: Codec<AgentEvent>;
41
+ export declare const resolveInterruptInputCodec: Codec<ResolveInterruptInput>;
42
+ export declare const agentSnapshotCodec: Codec<AgentSnapshot>;
43
+ export declare const listAgentMessagesInputCodec: Codec<ListAgentMessagesInput>;
44
+ export declare const agentMessagesPageCodec: Codec<AgentMessagesPage>;