@superdurable/dex-agents 0.1.0 → 0.2.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 (149) hide show
  1. package/README.md +1568 -62
  2. package/dist/src/agent-built-in-tools.d.ts +81 -0
  3. package/dist/src/agent-built-in-tools.js +246 -0
  4. package/dist/src/agent-built-in-tools.js.map +1 -0
  5. package/dist/src/agent-context-compaction.d.ts +108 -0
  6. package/dist/src/agent-context-compaction.js +39 -0
  7. package/dist/src/agent-context-compaction.js.map +1 -0
  8. package/dist/src/agent-framework-runner.d.ts +456 -16
  9. package/dist/src/agent-framework-runner.js +4 -3
  10. package/dist/src/agent-framework-runner.js.map +1 -1
  11. package/dist/src/agent-plans.d.ts +48 -0
  12. package/dist/src/agent-plans.js +86 -0
  13. package/dist/src/agent-plans.js.map +1 -0
  14. package/dist/src/agent-session-cancellation-steps.d.ts +28 -0
  15. package/dist/src/agent-session-cancellation-steps.js +154 -0
  16. package/dist/src/agent-session-cancellation-steps.js.map +1 -0
  17. package/dist/src/agent-session-codecs.d.ts +65 -4
  18. package/dist/src/agent-session-codecs.js +342 -8
  19. package/dist/src/agent-session-codecs.js.map +1 -1
  20. package/dist/src/agent-session-flow.d.ts +295 -27
  21. package/dist/src/agent-session-flow.js +368 -21
  22. package/dist/src/agent-session-flow.js.map +1 -1
  23. package/dist/src/agent-session-persistence.d.ts +81 -4
  24. package/dist/src/agent-session-persistence.js +33 -2
  25. package/dist/src/agent-session-persistence.js.map +1 -1
  26. package/dist/src/agent-session-settings.d.ts +58 -0
  27. package/dist/src/agent-session-settings.js +81 -0
  28. package/dist/src/agent-session-settings.js.map +1 -0
  29. package/dist/src/agent-session-step-options.d.ts +27 -0
  30. package/dist/src/agent-session-step-options.js +39 -0
  31. package/dist/src/agent-session-step-options.js.map +1 -0
  32. package/dist/src/agent-session-step-types.d.ts +20 -0
  33. package/dist/src/agent-session-step-types.js +28 -0
  34. package/dist/src/agent-session-step-types.js.map +1 -0
  35. package/dist/src/agent-session-steps.d.ts +101 -8
  36. package/dist/src/agent-session-steps.js +936 -197
  37. package/dist/src/agent-session-steps.js.map +1 -1
  38. package/dist/src/agent-session-tracer.d.ts +61 -0
  39. package/dist/src/agent-session-tracer.js +608 -0
  40. package/dist/src/agent-session-tracer.js.map +1 -0
  41. package/dist/src/agent-session-transitions.d.ts +189 -26
  42. package/dist/src/agent-session-transitions.js +680 -32
  43. package/dist/src/agent-session-transitions.js.map +1 -1
  44. package/dist/src/agent-session-values.d.ts +690 -29
  45. package/dist/src/agent-session-values.js +39 -4
  46. package/dist/src/agent-session-values.js.map +1 -1
  47. package/dist/src/agent-session-wait-steps.d.ts +53 -0
  48. package/dist/src/agent-session-wait-steps.js +389 -0
  49. package/dist/src/agent-session-wait-steps.js.map +1 -0
  50. package/dist/src/agent-tracing-options.d.ts +107 -0
  51. package/dist/src/agent-tracing-options.js +20 -0
  52. package/dist/src/agent-tracing-options.js.map +1 -0
  53. package/dist/src/ai-sdk/ai-sdk-model-client.d.ts +40 -0
  54. package/dist/src/ai-sdk/ai-sdk-model-client.js +235 -0
  55. package/dist/src/ai-sdk/ai-sdk-model-client.js.map +1 -0
  56. package/dist/src/ai-sdk/index.d.ts +1 -0
  57. package/dist/src/ai-sdk/index.js +9 -0
  58. package/dist/src/ai-sdk/index.js.map +1 -0
  59. package/dist/src/index.d.ts +9 -3
  60. package/dist/src/index.js +7 -2
  61. package/dist/src/index.js.map +1 -1
  62. package/dist/src/mcp/index.d.ts +3 -0
  63. package/dist/src/mcp/index.js +10 -0
  64. package/dist/src/mcp/index.js.map +1 -0
  65. package/dist/src/mcp/mcp-resource-and-prompt-tools.d.ts +29 -0
  66. package/dist/src/mcp/mcp-resource-and-prompt-tools.js +185 -0
  67. package/dist/src/mcp/mcp-resource-and-prompt-tools.js.map +1 -0
  68. package/dist/src/mcp/mcp-server-config.d.ts +86 -0
  69. package/dist/src/mcp/mcp-server-config.js +38 -0
  70. package/dist/src/mcp/mcp-server-config.js.map +1 -0
  71. package/dist/src/mcp/mcp-tool-source.d.ts +470 -0
  72. package/dist/src/mcp/mcp-tool-source.js +1204 -0
  73. package/dist/src/mcp/mcp-tool-source.js.map +1 -0
  74. package/dist/src/stepwise/agent-definition.d.ts +158 -0
  75. package/dist/src/stepwise/agent-definition.js +9 -0
  76. package/dist/src/stepwise/agent-definition.js.map +1 -0
  77. package/dist/src/stepwise/function-tool.d.ts +130 -0
  78. package/dist/src/stepwise/function-tool.js +75 -0
  79. package/dist/src/stepwise/function-tool.js.map +1 -0
  80. package/dist/src/stepwise/index.d.ts +7 -0
  81. package/dist/src/stepwise/index.js +12 -0
  82. package/dist/src/stepwise/index.js.map +1 -0
  83. package/dist/src/stepwise/json-schema-validation.d.ts +9 -0
  84. package/dist/src/stepwise/json-schema-validation.js +141 -0
  85. package/dist/src/stepwise/json-schema-validation.js.map +1 -0
  86. package/dist/src/stepwise/model-client.d.ts +194 -0
  87. package/dist/src/stepwise/model-client.js +9 -0
  88. package/dist/src/stepwise/model-client.js.map +1 -0
  89. package/dist/src/stepwise/scripted-model-client.d.ts +99 -0
  90. package/dist/src/stepwise/scripted-model-client.js +156 -0
  91. package/dist/src/stepwise/scripted-model-client.js.map +1 -0
  92. package/dist/src/stepwise/stepwise-agent-runner.d.ts +189 -0
  93. package/dist/src/stepwise/stepwise-agent-runner.js +804 -0
  94. package/dist/src/stepwise/stepwise-agent-runner.js.map +1 -0
  95. package/dist/src/stepwise/stepwise-agent-session-flow.d.ts +35 -0
  96. package/dist/src/stepwise/stepwise-agent-session-flow.js +56 -0
  97. package/dist/src/stepwise/stepwise-agent-session-flow.js.map +1 -0
  98. package/dist/src/stepwise/stepwise-input-items.d.ts +12 -0
  99. package/dist/src/stepwise/stepwise-input-items.js +172 -0
  100. package/dist/src/stepwise/stepwise-input-items.js.map +1 -0
  101. package/dist/src/stepwise/tool-source.d.ts +103 -0
  102. package/dist/src/stepwise/tool-source.js +9 -0
  103. package/dist/src/stepwise/tool-source.js.map +1 -0
  104. package/dist/src/testing/agent-session-test-client.d.ts +65 -8
  105. package/dist/src/testing/agent-session-test-client.js +95 -22
  106. package/dist/src/testing/agent-session-test-client.js.map +1 -1
  107. package/dist/src/testing/index.d.ts +1 -0
  108. package/dist/src/testing/index.js +1 -0
  109. package/dist/src/testing/index.js.map +1 -1
  110. package/dist/src/testing/private-dex-engine.d.ts +9 -0
  111. package/dist/src/testing/private-dex-engine.js +9 -1
  112. package/dist/src/testing/private-dex-engine.js.map +1 -1
  113. package/dist/src/testing/scripted-model-client.d.ts +1 -0
  114. package/dist/src/testing/scripted-model-client.js +10 -0
  115. package/dist/src/testing/scripted-model-client.js.map +1 -0
  116. package/dist/src/ui-stream/agent-event-json.d.ts +30 -0
  117. package/dist/src/ui-stream/agent-event-json.js +64 -0
  118. package/dist/src/ui-stream/agent-event-json.js.map +1 -0
  119. package/dist/src/ui-stream/agent-event-sse.d.ts +154 -0
  120. package/dist/src/ui-stream/agent-event-sse.js +159 -0
  121. package/dist/src/ui-stream/agent-event-sse.js.map +1 -0
  122. package/dist/src/ui-stream/agent-event-stream-positions.d.ts +42 -0
  123. package/dist/src/ui-stream/agent-event-stream-positions.js +153 -0
  124. package/dist/src/ui-stream/agent-event-stream-positions.js.map +1 -0
  125. package/dist/src/ui-stream/agent-session-record-reader.d.ts +72 -0
  126. package/dist/src/ui-stream/agent-session-record-reader.js +241 -0
  127. package/dist/src/ui-stream/agent-session-record-reader.js.map +1 -0
  128. package/dist/src/ui-stream/agent-ui-message-stream-responses.d.ts +219 -0
  129. package/dist/src/ui-stream/agent-ui-message-stream-responses.js +440 -0
  130. package/dist/src/ui-stream/agent-ui-message-stream-responses.js.map +1 -0
  131. package/dist/src/ui-stream/agent-ui-message-stream.d.ts +138 -0
  132. package/dist/src/ui-stream/agent-ui-message-stream.js +681 -0
  133. package/dist/src/ui-stream/agent-ui-message-stream.js.map +1 -0
  134. package/dist/src/ui-stream/agent-ui-message-types.d.ts +238 -0
  135. package/dist/src/ui-stream/agent-ui-message-types.js +57 -0
  136. package/dist/src/ui-stream/agent-ui-message-types.js.map +1 -0
  137. package/dist/src/ui-stream/agent-ui-messages.d.ts +66 -0
  138. package/dist/src/ui-stream/agent-ui-messages.js +220 -0
  139. package/dist/src/ui-stream/agent-ui-messages.js.map +1 -0
  140. package/dist/src/ui-stream/agent-ui-stream-cursor.d.ts +64 -0
  141. package/dist/src/ui-stream/agent-ui-stream-cursor.js +186 -0
  142. package/dist/src/ui-stream/agent-ui-stream-cursor.js.map +1 -0
  143. package/dist/src/ui-stream/index.d.ts +14 -0
  144. package/dist/src/ui-stream/index.js +22 -0
  145. package/dist/src/ui-stream/index.js.map +1 -0
  146. package/dist/src/ui-stream/server-sent-event-responses.d.ts +6 -0
  147. package/dist/src/ui-stream/server-sent-event-responses.js +34 -0
  148. package/dist/src/ui-stream/server-sent-event-responses.js.map +1 -0
  149. package/package.json +41 -5
@@ -0,0 +1,440 @@
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
+ import { setTimeout as delay } from "node:timers/promises";
9
+ import { RpcLockConflictError } from "@superdurable/dex";
10
+ import { describeToolCallOutcome, isFinalToolCallStatus, } from "../agent-session-values.js";
11
+ import { findAgentEventResumeTokenBeforeTurn, parseTurnNumber, probeAgentEventResumeToken, readAgentEventsTailResumeToken, requireAgentEventPollTimeoutMs, } from "./agent-event-stream-positions.js";
12
+ import { AgentSessionRecordReader } from "./agent-session-record-reader.js";
13
+ import { prepareAgentUIStreamStart, readAgentUIMessageChunkBatches, } from "./agent-ui-message-stream.js";
14
+ import { agentResumeTokenDataPartType, agentUIMessageStreamHeaders, } from "./agent-ui-message-types.js";
15
+ import { createAgentUIStreamCursor, decodeAgentUIStreamCursor, encodeAgentUIStreamCursor, toolCallUIStateOfRecord, } from "./agent-ui-stream-cursor.js";
16
+ import { createServerSentEventResponse, mergeHeaders, serverSentEventKeepAliveFrame, } from "./server-sent-event-responses.js";
17
+ const lockConflictRetryTimeoutMs = 5_000;
18
+ const lockConflictRetryIntervalMs = 50;
19
+ const maximumApprovalsPerRequest = 100;
20
+ const maximumToolCallIdLength = 512;
21
+ const staleApprovalText = "The approval is no longer pending.";
22
+ const assistantMessageIdPattern = /^(turn-[0-9]+)-assistant$/;
23
+ /**
24
+ * Request header name for a UI stream resume token.
25
+ *
26
+ * Send the token in this header rather than the URL: a token can reach 64 KiB, beyond many URL limits.
27
+ */
28
+ export const agentResumeTokenHeaderName = "x-agent-resume-token";
29
+ /**
30
+ * Returns a streaming AI SDK UI message stream Response for one agent turn, or 204 when there is none.
31
+ *
32
+ * Mount it as the reconnect endpoint of `useChat`, `GET <api>/<chatId>/stream` by default, with the chat ID
33
+ * as the session ID; the route must check that the caller may read that session. It writes
34
+ * {@link streamAgentUIMessageChunks} as Server-Sent Events with the protocol headers, a keep-alive
35
+ * comment after each empty long poll, and `data: [DONE]` at the end. It stops reading when the browser
36
+ * closes the response.
37
+ *
38
+ * Use the default `DefaultChatTransport`, whose resumed streams count as replays. A stream started with a
39
+ * token replays the token's turn, and one started without a token replays the active turn; either way
40
+ * its `start` part makes the browser reset the message and rebuild it, whatever it held. With
41
+ * `continueAfterResumeToken`, a stream continues exactly after the last part the browser received and
42
+ * sends no `start` part; use it only while the browser still holds that message. Before it answers 200,
43
+ * the helper reads once to check the token's Dex position; a position Dex rejects restarts the turn from
44
+ * its first retained event.
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * // Server: GET /api/chat/:id/stream
49
+ * export async function GET(request: Request, sessionId: string): Promise<Response> {
50
+ * await authorizeSession(request, sessionId);
51
+ * return createAgentUIMessageStreamResponse({
52
+ * client, flow, flowId: sessionId,
53
+ * resumeToken: request.headers.get(agentResumeTokenHeaderName) ?? undefined,
54
+ * signal: request.signal,
55
+ * });
56
+ * }
57
+ *
58
+ * // Browser
59
+ * let resumeToken: string | undefined = initial.resumeToken ?? undefined;
60
+ * const chat = useChat({
61
+ * id: sessionId,
62
+ * messages: initial.messages,
63
+ * resume: resumeToken !== undefined,
64
+ * onData: (part) => {
65
+ * if (part.type === "data-agent-resume-token") resumeToken = part.data as string;
66
+ * },
67
+ * transport: new DefaultChatTransport({
68
+ * api: "/api/chat",
69
+ * prepareReconnectToStreamRequest: ({ id }) => ({
70
+ * api: `/api/chat/${id}/stream`,
71
+ * headers: resumeToken === undefined ? {} : { "x-agent-resume-token": resumeToken },
72
+ * }),
73
+ * }),
74
+ * sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses,
75
+ * });
76
+ * ```
77
+ * @param options - Session, start position, polling, and extra headers.
78
+ * @returns 200 with the stream; 204 when the token's turn already finished, or when the turn to replay is
79
+ * not active or not on the Stream; 400 with a text body when `resumeToken` is invalid.
80
+ * @throws {RangeError} When `pollTimeoutMs` is not a positive whole number of seconds.
81
+ * @throws {DexServiceError} When reading the start position fails.
82
+ */
83
+ export async function createAgentUIMessageStreamResponse(options) {
84
+ const pollTimeoutMs = requireAgentEventPollTimeoutMs(options.pollTimeoutMs);
85
+ if (options.resumeToken !== undefined && decodeResumeTokenOrUndefined(options.resumeToken) === undefined) {
86
+ return createInvalidResumeTokenResponse(options.headers);
87
+ }
88
+ const start = await prepareAgentUIStreamStart(options);
89
+ if (start === undefined) {
90
+ return new Response(null, { status: 204, headers: mergeHeaders({}, options.headers) });
91
+ }
92
+ return createAgentUIMessageSseResponse(options, start, pollTimeoutMs, []);
93
+ }
94
+ /**
95
+ * Queues a user message and returns the AI SDK UI message stream Response of the turn it starts.
96
+ *
97
+ * Mount it as the send endpoint of `useChat`, `POST <api>` by default, when the last message in the
98
+ * request body is a user message. The chat ID in the body comes from the browser, so the route must check
99
+ * that the caller may write to that session. The helper reads the session's turn count and the Stream
100
+ * position first and then calls `send_user_message`, so the stream follows the next turn that starts
101
+ * after the message was queued. When an earlier turn is still running, the stream waits for it to end.
102
+ * When other messages were queued before this one, the stream follows the turn of the oldest of them.
103
+ *
104
+ * The session's committed state ends the stream even when no event of the turn reaches the Stream, such
105
+ * as with the Server's Stream Store disabled: once that turn ended, the stream finishes with reason
106
+ * `other`, and the application reloads the stored messages. The RPC needs a running Worker.
107
+ *
108
+ * @example
109
+ * ```ts
110
+ * // POST /api/chat
111
+ * export async function POST(request: Request): Promise<Response> {
112
+ * const { id, messages } = await request.json();
113
+ * await authorizeSession(request, id);
114
+ * const lastMessage = messages.at(-1);
115
+ * if (lastMessage.role === "assistant") {
116
+ * return resolveAgentToolApprovalsWithUIMessageStream({
117
+ * client, flow, flowId: id, approvals: readAgentToolApprovalResponses(lastMessage) });
118
+ * }
119
+ * const text = lastMessage.parts.filter((part) => part.type === "text").map((part) => part.text).join("");
120
+ * return sendAgentUserMessageWithUIMessageStream({ client, flow, flowId: id, text, signal: request.signal });
121
+ * }
122
+ * ```
123
+ * @param options - Session, message text, polling, and extra headers.
124
+ * @returns 200 with the stream; 400 with a text body when the text is blank.
125
+ * @throws {RangeError} When `pollTimeoutMs` is not a positive whole number of seconds.
126
+ * @throws {FlowNotActiveOrNotFoundError} When the session is not running.
127
+ * @throws {DexServiceError} When a read or the RPC fails.
128
+ */
129
+ export async function sendAgentUserMessageWithUIMessageStream(options) {
130
+ const pollTimeoutMs = requireAgentEventPollTimeoutMs(options.pollTimeoutMs);
131
+ const { client, flow, flowId } = options;
132
+ const [state] = await client.getAttributes(flowId, flow.agentState);
133
+ const eventResumeToken = await readAgentEventsTailResumeToken(client, flow, flowId);
134
+ const isQueued = await client.invokeRPC(flow.sendUserMessage, flowId, { text: options.text });
135
+ if (!isQueued) {
136
+ return new Response("the user message text is blank", {
137
+ status: 400,
138
+ headers: mergeHeaders({ "content-type": "text/plain; charset=utf-8" }, options.headers),
139
+ });
140
+ }
141
+ const cursor = createAgentUIStreamCursor(eventResumeToken, null, false);
142
+ // The message's turn starts after every turn that already started.
143
+ const activeTurnNumber = state?.activeTurnId == null ? 0 : (parseTurnNumber(state.activeTurnId) ?? 0);
144
+ cursor.afterTurnNumber = Math.max(state?.turnCount ?? 0, activeTurnNumber);
145
+ return createAgentUIMessageSseResponse(options, { cursor, firstMessage: undefined }, pollTimeoutMs, []);
146
+ }
147
+ /**
148
+ * Sends one tool approval decision to the session's `resolve_interrupt` RPC.
149
+ *
150
+ * Call it with each AI SDK `tool-approval-response`: its `approvalId` is the `interrupt_id`, and its
151
+ * `reason` becomes the rejection message the model sees. It retries `RpcLockConflictError`, which a call
152
+ * that races the approval Step can raise, for up to 5 seconds. It resolves any pending interrupt of the
153
+ * session, so the route must check that the caller may decide for that session. The RPC needs a running
154
+ * Worker.
155
+ *
156
+ * @example
157
+ * ```ts
158
+ * const isResolved = await resolveAgentToolApproval({
159
+ * client, flow, flowId: sessionId,
160
+ * approval: { approvalId: "call_42", approved: false, reason: "Use the sandbox account" },
161
+ * });
162
+ * ```
163
+ * @param options - Session and decision.
164
+ * @returns `true` when the interrupt was pending and is now resolved; `false` when it is unknown, already
165
+ * resolved, or past its deadline.
166
+ * @throws {RpcLockConflictError} When the lock stays busy for 5 seconds.
167
+ * @throws {FlowNotActiveOrNotFoundError} When the session is not running.
168
+ */
169
+ export async function resolveAgentToolApproval(options) {
170
+ const { approval } = options;
171
+ const deadline = Date.now() + lockConflictRetryTimeoutMs;
172
+ for (;;) {
173
+ try {
174
+ return await options.client.invokeRPC(options.flow.resolveInterrupt, options.flowId, {
175
+ interruptId: approval.approvalId,
176
+ approved: approval.approved,
177
+ rejectionMessage: approval.approved ? null : (approval.reason ?? null),
178
+ });
179
+ }
180
+ catch (error) {
181
+ if (!(error instanceof RpcLockConflictError) || Date.now() >= deadline) {
182
+ throw error;
183
+ }
184
+ }
185
+ await delay(lockConflictRetryIntervalMs);
186
+ }
187
+ }
188
+ /**
189
+ * Returns the tool approval decisions recorded on an AI SDK assistant message.
190
+ *
191
+ * `useChat` sets a tool part to `approval-responded` when the application calls
192
+ * `addToolApprovalResponse`, then sends the message list with `sendAutomaticallyWhen`. Pass the last
193
+ * message of that request body. The message comes from the browser, so every field is checked; parts
194
+ * that are not well-formed responded approvals are skipped.
195
+ *
196
+ * Each decision is bound to the message's turn when the message ID is `<turnId>-assistant`, as the agent UI
197
+ * streams and snapshots name assistant messages; a message with another ID gives unbound decisions.
198
+ *
199
+ * @param message - An AI SDK `UIMessage`, typically the last message of a `useChat` request body.
200
+ * @returns One decision per `approval-responded` tool part, in part order; empty for anything else.
201
+ */
202
+ export function readAgentToolApprovalResponses(message) {
203
+ if (!isRecord(message) || !Array.isArray(message["parts"])) {
204
+ return [];
205
+ }
206
+ const messageId = message["id"];
207
+ const turnId = typeof messageId === "string" ? assistantMessageIdPattern.exec(messageId)?.[1] : undefined;
208
+ const approvals = [];
209
+ for (const part of message["parts"]) {
210
+ if (!isRecord(part) || part["state"] !== "approval-responded" || !isToolPartType(part["type"])) {
211
+ continue;
212
+ }
213
+ const approval = part["approval"];
214
+ if (!isRecord(approval) || typeof approval["id"] !== "string" || typeof approval["approved"] !== "boolean") {
215
+ continue;
216
+ }
217
+ const reason = approval["reason"];
218
+ approvals.push({
219
+ approvalId: approval["id"],
220
+ approved: approval["approved"],
221
+ ...(typeof reason === "string" ? { reason } : {}),
222
+ ...(turnId === undefined ? {} : { turnId }),
223
+ });
224
+ }
225
+ return approvals;
226
+ }
227
+ /**
228
+ * Sends tool approval decisions and returns the AI SDK UI message stream Response that continues the turn.
229
+ *
230
+ * Mount it in the send endpoint of `useChat` for requests whose last message is an assistant message,
231
+ * which `sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses` sends after the
232
+ * application answered every approval. The decisions and the chat ID come from the browser, so the route
233
+ * must check that the caller may decide for that session. Only interrupts that are pending in the
234
+ * session's active turn are resolved.
235
+ *
236
+ * The stream continues the same assistant message. A decision that is no longer pending, because another
237
+ * client or a deadline settled the call or its turn ended, gets the call's known outcome, or an error
238
+ * part when none is known, so the browser leaves `approval-responded` and does not send the decision
239
+ * again. Decisions bound to another turn than the active one, such as from a stale browser tab, are all
240
+ * stale, and the stream then ends after their outcomes. With `resumeToken`, the latest `data-agent-resume-token`, the stream continues exactly after the
241
+ * paused stream. Without one it reads the Stream position and the active turn's tool calls before
242
+ * sending the decisions. The RPCs need a running Worker.
243
+ *
244
+ * @example
245
+ * ```ts
246
+ * return resolveAgentToolApprovalsWithUIMessageStream({
247
+ * client, flow, flowId: id, approvals: readAgentToolApprovalResponses(messages.at(-1)) });
248
+ * ```
249
+ * @param options - Session, decisions, optional resume token, polling, and extra headers.
250
+ * @returns 200 with the stream, which holds only the stale decisions' outcomes and a `finish` part when
251
+ * no turn is active; 400 with a text body when `resumeToken` is invalid.
252
+ * @throws {RangeError} When `pollTimeoutMs` is not a positive whole number of seconds.
253
+ * @throws {RpcLockConflictError} When a decision's lock stays busy for 5 seconds.
254
+ * @throws {DexServiceError} When a read or an RPC fails.
255
+ */
256
+ export async function resolveAgentToolApprovalsWithUIMessageStream(options) {
257
+ const pollTimeoutMs = requireAgentEventPollTimeoutMs(options.pollTimeoutMs);
258
+ const { client, flow, flowId } = options;
259
+ let clientCursor;
260
+ if (options.resumeToken !== undefined) {
261
+ clientCursor = decodeResumeTokenOrUndefined(options.resumeToken);
262
+ if (clientCursor === undefined) {
263
+ return createInvalidResumeTokenResponse(options.headers);
264
+ }
265
+ }
266
+ // Position before snapshot: events may repeat the snapshot, but none goes missing.
267
+ const tailResumeToken = await readAgentEventsTailResumeToken(client, flow, flowId);
268
+ const snapshot = await client.invokeRPC(flow.getAgentSnapshot, flowId);
269
+ const activeTurnId = snapshot.state?.activeTurnId ?? null;
270
+ // Decisions from an older turn's message settle that message only; the active turn goes elsewhere.
271
+ const isActiveTurnRequest = options.approvals.every((approval) => approval.turnId === undefined || approval.turnId === activeTurnId);
272
+ let start;
273
+ if (!isActiveTurnRequest) {
274
+ const finishedCursor = createAgentUIStreamCursor(tailResumeToken, null, true);
275
+ finishedCursor.isTurnFinished = true;
276
+ start = { cursor: finishedCursor, firstMessage: undefined };
277
+ }
278
+ else if (clientCursor !== undefined && clientCursor.turnId === activeTurnId && activeTurnId !== null) {
279
+ // The browser keeps its message here, so a rejected position falls back to the tail, never a replay.
280
+ const probe = await probeAgentEventResumeToken(client, flow, flowId, clientCursor.eventResumeToken);
281
+ if (probe.isAccepted) {
282
+ start = { cursor: clientCursor, firstMessage: probe.firstMessage };
283
+ }
284
+ }
285
+ start ??= {
286
+ cursor: await createActiveTurnContinuationCursor(client, flow, flowId, tailResumeToken, activeTurnId, snapshot.toolCalls),
287
+ firstMessage: undefined,
288
+ };
289
+ const cursor = start.cursor;
290
+ const pendingInterruptIds = new Set(snapshot.pendingInterrupts
291
+ .filter((interrupt) => snapshot.toolCalls.some((toolCall) => toolCall.toolCallId === interrupt.toolCallId &&
292
+ toolCall.turnId === activeTurnId &&
293
+ toolCall.status === "awaiting_approval"))
294
+ .map((interrupt) => interrupt.interruptId));
295
+ const staleApprovals = [];
296
+ for (const approval of selectUniqueApprovals(options.approvals)) {
297
+ const isResolved = isActiveTurnRequest &&
298
+ pendingInterruptIds.has(approval.approvalId) &&
299
+ (await resolveAgentToolApproval({ client, flow, flowId, approval }));
300
+ if (isResolved) {
301
+ // The interrupt_resolved event follows; until then the call must not count as waiting.
302
+ cursor.toolCalls.set(approval.approvalId, "approval_resolved");
303
+ }
304
+ else {
305
+ staleApprovals.push(approval);
306
+ }
307
+ }
308
+ const staleOutcomeChunks = await readStaleApprovalOutcomes(new AgentSessionRecordReader(client, flow, flowId), activeTurnId, snapshot.toolCalls, staleApprovals, cursor);
309
+ return createAgentUIMessageSseResponse(options, start, pollTimeoutMs, staleOutcomeChunks);
310
+ }
311
+ /** Keeps the first well-formed decision per approval ID, up to the per-request limit. */
312
+ function selectUniqueApprovals(approvals) {
313
+ const selected = new Map();
314
+ for (const approval of approvals.slice(0, maximumApprovalsPerRequest)) {
315
+ const approvalId = approval.approvalId;
316
+ if (typeof approvalId !== "string" ||
317
+ approvalId === "" ||
318
+ approvalId.length > maximumToolCallIdLength ||
319
+ approvalId.includes("/") ||
320
+ typeof approval.approved !== "boolean" ||
321
+ selected.has(approvalId)) {
322
+ continue;
323
+ }
324
+ selected.set(approvalId, approval);
325
+ }
326
+ return [...selected.values()];
327
+ }
328
+ /**
329
+ * Returns the parts that settle the decisions `resolve_interrupt` did not accept, and records them in the
330
+ * cursor. A call still open in the active turn gets none: its own events follow.
331
+ */
332
+ async function readStaleApprovalOutcomes(records, activeTurnId, activeTurnToolCalls, staleApprovals, cursor) {
333
+ const chunks = [];
334
+ let storedToolResults;
335
+ for (const approval of staleApprovals) {
336
+ // A tool approval's interrupt ID is its tool call ID.
337
+ const toolCallId = approval.approvalId;
338
+ const isActiveTurnToolCall = cursor.turnId === activeTurnId && activeTurnToolCalls.some((toolCall) => toolCall.toolCallId === toolCallId);
339
+ const record = activeTurnId !== null && isActiveTurnToolCall
340
+ ? await records.readToolCall(activeTurnId, toolCallId)
341
+ : undefined;
342
+ if (record !== undefined && !isFinalToolCallStatus(record.status)) {
343
+ cursor.toolCalls.set(toolCallId, "approval_resolved");
344
+ continue;
345
+ }
346
+ cursor.toolCalls.set(toolCallId, "finished");
347
+ if (record !== undefined) {
348
+ if (record.status === "succeeded") {
349
+ chunks.push({ type: "tool-output-available", toolCallId, output: record.output ?? null, dynamic: true });
350
+ }
351
+ else if (record.status === "rejected") {
352
+ chunks.push({ type: "tool-output-denied", toolCallId });
353
+ }
354
+ else {
355
+ chunks.push({ type: "tool-output-error", toolCallId, errorText: describeToolCallOutcome(record), dynamic: true });
356
+ }
357
+ continue;
358
+ }
359
+ storedToolResults ??= await records.readRecentStoredToolResults();
360
+ const storedToolOutput = [...storedToolResults]
361
+ .reverse()
362
+ .find((result) => result.toolCallId === toolCallId && (approval.turnId === undefined || result.turnId === approval.turnId))?.toolOutput;
363
+ chunks.push(storedToolOutput === undefined
364
+ ? { type: "tool-output-error", toolCallId, errorText: staleApprovalText, dynamic: true }
365
+ : { type: "tool-output-available", toolCallId, output: storedToolOutput, dynamic: true });
366
+ }
367
+ return chunks;
368
+ }
369
+ /**
370
+ * Returns a cursor at the Stream tail that continues the active turn the browser already shows. It records
371
+ * where the turn started, so a reconnect with its token replays the whole turn.
372
+ */
373
+ async function createActiveTurnContinuationCursor(client, flow, flowId, tailResumeToken, activeTurnId, toolCalls) {
374
+ const cursor = createAgentUIStreamCursor(tailResumeToken, activeTurnId, true);
375
+ if (activeTurnId === null) {
376
+ cursor.isTurnFinished = true;
377
+ return cursor;
378
+ }
379
+ cursor.turnStartResumeToken =
380
+ (await findAgentEventResumeTokenBeforeTurn(client, flow, flowId, activeTurnId)) ?? null;
381
+ for (const toolCall of toolCalls) {
382
+ cursor.toolCalls.set(toolCall.toolCallId, toolCallUIStateOfRecord(toolCall));
383
+ }
384
+ return cursor;
385
+ }
386
+ function createAgentUIMessageSseResponse(options, start, pollTimeoutMs, leadingChunks) {
387
+ return createServerSentEventResponse(formatAgentUIMessageSseFrames(options, start, pollTimeoutMs, leadingChunks), agentUIMessageStreamHeaders, options.headers);
388
+ }
389
+ async function* formatAgentUIMessageSseFrames(options, start, pollTimeoutMs, leadingChunks) {
390
+ const cursor = start.cursor;
391
+ if (leadingChunks.length > 0) {
392
+ const resumeTokenChunk = {
393
+ type: agentResumeTokenDataPartType,
394
+ data: encodeAgentUIStreamCursor(cursor),
395
+ transient: true,
396
+ };
397
+ yield [...leadingChunks, resumeTokenChunk].map(formatUIMessageChunkFrame).join("");
398
+ }
399
+ if (cursor.isTurnFinished) {
400
+ yield formatUIMessageChunkFrame({ type: "finish", finishReason: "other" });
401
+ }
402
+ else {
403
+ for await (const chunks of readAgentUIMessageChunkBatches(options, start, pollTimeoutMs)) {
404
+ if (chunks === undefined) {
405
+ yield serverSentEventKeepAliveFrame;
406
+ }
407
+ else if (chunks.length > 0) {
408
+ yield chunks.map(formatUIMessageChunkFrame).join("");
409
+ }
410
+ }
411
+ }
412
+ yield "data: [DONE]\n\n";
413
+ }
414
+ function formatUIMessageChunkFrame(chunk) {
415
+ return `data: ${JSON.stringify(chunk)}\n\n`;
416
+ }
417
+ function decodeResumeTokenOrUndefined(resumeToken) {
418
+ try {
419
+ return decodeAgentUIStreamCursor(resumeToken);
420
+ }
421
+ catch (error) {
422
+ if (error instanceof TypeError) {
423
+ return undefined;
424
+ }
425
+ throw error;
426
+ }
427
+ }
428
+ function createInvalidResumeTokenResponse(headers) {
429
+ return new Response("the agent UI stream resume token is invalid", {
430
+ status: 400,
431
+ headers: mergeHeaders({ "content-type": "text/plain; charset=utf-8" }, headers),
432
+ });
433
+ }
434
+ function isToolPartType(type) {
435
+ return type === "dynamic-tool" || (typeof type === "string" && type.startsWith("tool-"));
436
+ }
437
+ function isRecord(value) {
438
+ return typeof value === "object" && value !== null && !Array.isArray(value);
439
+ }
440
+ //# sourceMappingURL=agent-ui-message-stream-responses.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-ui-message-stream-responses.js","sourceRoot":"","sources":["../../../src/ui-stream/agent-ui-message-stream-responses.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,138 @@
1
+ import type { Client, StreamMessage } from "@superdurable/dex";
2
+ import type { AgentFrameworkRunner, DeferredToolOutcome } from "../agent-framework-runner.js";
3
+ import type { AgentSessionFlow } from "../agent-session-flow.js";
4
+ import { type AgentEvent } from "../agent-session-values.js";
5
+ import { type AgentUIMessageChunk } from "./agent-ui-message-types.js";
6
+ import { type AgentUIStreamCursor } from "./agent-ui-stream-cursor.js";
7
+ /**
8
+ * Selects the agent session and Stream position of an AI SDK UI message stream.
9
+ *
10
+ * Pass it to {@link streamAgentUIMessageChunks} or {@link createAgentUIMessageStreamResponse}. The Client's
11
+ * Registry must contain `flow`, the same object the Worker registered.
12
+ */
13
+ export interface AgentUIMessageStreamOptions {
14
+ /** Client used for every Stream read, Attribute read, and RPC. The caller keeps ownership. */
15
+ readonly client: Client;
16
+ /** The agent session Flow, such as the value an adapter factory returns. */
17
+ readonly flow: AgentSessionFlow<AgentFrameworkRunner<DeferredToolOutcome>>;
18
+ /**
19
+ * Session ID: the Flow ID of the agent session. The value usually comes from the browser, so the route
20
+ * must check that the caller may read this session before it streams.
21
+ */
22
+ readonly flowId: string;
23
+ /**
24
+ * UI stream resume token from the latest `data-agent-resume-token` part, or from
25
+ * {@link readAgentSessionUIMessages}. It is not a Dex Stream resume token; pass it back unchanged.
26
+ *
27
+ * By default the stream replays the token's turn from its first retained event, starting with a `start`
28
+ * part. Omit the token to replay the session's active turn instead.
29
+ */
30
+ readonly resumeToken?: string | undefined;
31
+ /**
32
+ * Whether a stream started with `resumeToken` continues exactly after the last part the browser received,
33
+ * without a `start` part, instead of replaying the turn. Defaults to `false`.
34
+ *
35
+ * Pass `true` only while the browser still holds the parts the token continues: with `useChat`, while
36
+ * `status` is `"error"` after a dropped connection. After a clean close or a page reload the browser
37
+ * starts from an empty message, so a continuation would show only the rest of the turn.
38
+ */
39
+ readonly continueAfterResumeToken?: boolean;
40
+ /**
41
+ * Long-poll duration of one `AgentEvents` read, in milliseconds. It must be a whole number of seconds.
42
+ * After each empty poll the stream checks whether the turn ended without its end event, whether the
43
+ * session Flow closed, and whether the turn waits only for approvals. Defaults to 10,000.
44
+ */
45
+ readonly pollTimeoutMs?: number;
46
+ /**
47
+ * Whether the stream finishes with reason `tool-calls` when the turn can only continue after
48
+ * `resolve_interrupt`. `useChat` sends tool approvals only after its response stream ended, so keep the
49
+ * default `true` with `sendAutomaticallyWhen`. Pass `false` when the application resolves approvals
50
+ * through its own endpoint and keeps one stream open for the whole turn.
51
+ */
52
+ readonly finishWhenAwaitingApproval?: boolean;
53
+ /** Ends the stream before its next Stream read once aborted, such as the request's `signal`. */
54
+ readonly signal?: AbortSignal | undefined;
55
+ }
56
+ /**
57
+ * Translates one agent turn from the session's `AgentEvents` Stream into AI SDK UI message stream parts.
58
+ *
59
+ * The parts follow one turn. Without a resume token they replay the session's active turn from its start.
60
+ * With one they replay the token's turn, or continue it exactly with `continueAfterResumeToken`. The
61
+ * generator returns after the turn completes (`finish` with `stop`), fails (`error`, then `finish` with
62
+ * `error`), ends through steering or `cancel_turn` (the `turn_steered` or `turn_cancelled` event data part, then
63
+ * `finish` with `other`), or waits only for tool approvals (`finish` with `tool-calls`). It also returns when the turn
64
+ * ended without its end event on the Stream, or the session Flow closed (`finish` with `other`); the
65
+ * session's committed state decides this, so the stream ends even when the Stream lost every event. On
66
+ * `other`, reload the stored messages with {@link readAgentSessionUIMessages}.
67
+ *
68
+ * Every `RunModel` Step execution is one AI SDK step. When a new attempt of the same Step execution starts,
69
+ * for example after a Worker crash, the generator emits `reset-step`, so the browser drops the partial
70
+ * text of the interrupted attempt. Tool calls become dynamic tool parts. A tool call that needs approval
71
+ * gets a `tool-approval-request` whose `approvalId` is the `interrupt_id` for `resolve_interrupt`. A
72
+ * `question` interrupt is not an approval: its `interrupt_raised` and `interrupt_resolved` events arrive as
73
+ * agent event data parts, and the browser answers it with `answer_question`. A
74
+ * transient `data-agent-resume-token` part follows every part; send the latest one back to resume.
75
+ *
76
+ * The generator reads tool arguments and results from the committed `AgentToolCalls` records, waiting up
77
+ * to 3 seconds for the commit that follows each event, and from the stored messages once the turn deleted
78
+ * its records. It also waits up to 3 seconds for a turn's end to commit before it finishes. Events are
79
+ * best effort: a part whose event the Stream lost or trimmed is missing until the stored messages replace
80
+ * the streamed message.
81
+ *
82
+ * Stop in the browser only closes the HTTP response, and the turn keeps running in Dex. To stop the turn
83
+ * durably, the application's stop route calls the session's `cancel_turn` RPC.
84
+ *
85
+ * @example
86
+ * ```ts
87
+ * import { createUIMessageStreamResponse } from "ai";
88
+ * import { streamAgentUIMessageChunks } from "@superdurable/dex-agents/ui-stream";
89
+ *
90
+ * export async function GET(request: Request, sessionId: string): Promise<Response> {
91
+ * const chunks = streamAgentUIMessageChunks({
92
+ * client, flow, flowId: sessionId,
93
+ * resumeToken: request.headers.get("x-agent-resume-token") ?? undefined, signal: request.signal,
94
+ * });
95
+ * return createUIMessageStreamResponse({ stream: ReadableStream.from(chunks) });
96
+ * }
97
+ * ```
98
+ * @param options - Session, start position, and polling.
99
+ * @returns An async generator of UI message stream parts. It yields nothing when the token's turn already
100
+ * finished, or when the stream would replay a turn that is not active or not on the Stream.
101
+ * @throws {TypeError} When `resumeToken` is not a UI stream resume token.
102
+ * @throws {RangeError} When `pollTimeoutMs` is not a positive whole number of seconds.
103
+ * @throws {DexServiceError} When a Stream read, Attribute read, or RPC fails.
104
+ */
105
+ export declare function streamAgentUIMessageChunks(options: AgentUIMessageStreamOptions): AsyncGenerator<AgentUIMessageChunk, void, undefined>;
106
+ /** Where a stream starts, with the first event already read when one was waiting. */
107
+ export interface AgentUIStreamStart {
108
+ readonly cursor: AgentUIStreamCursor;
109
+ readonly firstMessage: StreamMessage<AgentEvent> | undefined;
110
+ }
111
+ /**
112
+ * Resolves where a stream starts and checks the Dex resume token with one short read.
113
+ *
114
+ * A token that Dex rejects, such as a forged one, restarts the stream from the turn's first retained
115
+ * event, which the Server lists itself.
116
+ * @returns The start, or `undefined` when there is nothing to stream.
117
+ * @throws {TypeError} When the resume token is invalid.
118
+ */
119
+ export declare function prepareAgentUIStreamStart(options: AgentUIMessageStreamOptions): Promise<AgentUIStreamStart | undefined>;
120
+ /** Checks a cursor's Dex resume token, replacing a rejected one with the turn's listed start. */
121
+ export declare function probeAgentUIStreamStart(client: Client, flow: AgentSessionFlow<AgentFrameworkRunner<DeferredToolOutcome>>, flowId: string, cursor: AgentUIStreamCursor): Promise<AgentUIStreamStart | undefined>;
122
+ /**
123
+ * Returns the cursor where a stream starts, or `undefined` when there is nothing to stream.
124
+ * @throws {TypeError} When the resume token is invalid.
125
+ */
126
+ export declare function resolveAgentUIStreamStartCursor(options: AgentUIMessageStreamOptions): Promise<AgentUIStreamCursor | undefined>;
127
+ /**
128
+ * Returns a cursor that replays a turn from its first retained event, or `undefined` when none is retained.
129
+ * @param knownTurnStartResumeToken - The turn's start position recorded in a cursor, or `null` to search.
130
+ */
131
+ export declare function createTurnReplayCursor(client: Client, flow: AgentSessionFlow<AgentFrameworkRunner<DeferredToolOutcome>>, flowId: string, turnId: string, knownTurnStartResumeToken: string | null): Promise<AgentUIStreamCursor | undefined>;
132
+ /**
133
+ * Reads events from the cursor and yields the parts of each one.
134
+ * Yields `undefined` after an empty long poll, so a response can send a keep-alive comment.
135
+ */
136
+ export declare function readAgentUIMessageChunkBatches(options: AgentUIMessageStreamOptions, start: AgentUIStreamStart, pollTimeoutMs: number): AsyncGenerator<AgentUIMessageChunk[] | undefined, void, undefined>;
137
+ /** Parses tool arguments JSON text; text that is not JSON stays a string. */
138
+ export declare function parseToolArguments(toolArguments: string | null | undefined): unknown;