@superdurable/dex-agents 0.0.0-stage → 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 (171) hide show
  1. package/LICENSE +105 -0
  2. package/README.md +1981 -2
  3. package/dist/src/agent-built-in-tools.d.ts +81 -0
  4. package/dist/src/agent-built-in-tools.js +246 -0
  5. package/dist/src/agent-built-in-tools.js.map +1 -0
  6. package/dist/src/agent-context-compaction.d.ts +108 -0
  7. package/dist/src/agent-context-compaction.js +39 -0
  8. package/dist/src/agent-context-compaction.js.map +1 -0
  9. package/dist/src/agent-event-writer.d.ts +26 -0
  10. package/dist/src/agent-event-writer.js +110 -0
  11. package/dist/src/agent-event-writer.js.map +1 -0
  12. package/dist/src/agent-framework-runner.d.ts +808 -0
  13. package/dist/src/agent-framework-runner.js +82 -0
  14. package/dist/src/agent-framework-runner.js.map +1 -0
  15. package/dist/src/agent-plans.d.ts +48 -0
  16. package/dist/src/agent-plans.js +86 -0
  17. package/dist/src/agent-plans.js.map +1 -0
  18. package/dist/src/agent-session-cancellation-steps.d.ts +28 -0
  19. package/dist/src/agent-session-cancellation-steps.js +154 -0
  20. package/dist/src/agent-session-cancellation-steps.js.map +1 -0
  21. package/dist/src/agent-session-codecs.d.ts +105 -0
  22. package/dist/src/agent-session-codecs.js +689 -0
  23. package/dist/src/agent-session-codecs.js.map +1 -0
  24. package/dist/src/agent-session-flow.d.ts +416 -0
  25. package/dist/src/agent-session-flow.js +623 -0
  26. package/dist/src/agent-session-flow.js.map +1 -0
  27. package/dist/src/agent-session-persistence.d.ts +133 -0
  28. package/dist/src/agent-session-persistence.js +74 -0
  29. package/dist/src/agent-session-persistence.js.map +1 -0
  30. package/dist/src/agent-session-settings.d.ts +58 -0
  31. package/dist/src/agent-session-settings.js +81 -0
  32. package/dist/src/agent-session-settings.js.map +1 -0
  33. package/dist/src/agent-session-step-options.d.ts +27 -0
  34. package/dist/src/agent-session-step-options.js +39 -0
  35. package/dist/src/agent-session-step-options.js.map +1 -0
  36. package/dist/src/agent-session-step-types.d.ts +20 -0
  37. package/dist/src/agent-session-step-types.js +28 -0
  38. package/dist/src/agent-session-step-types.js.map +1 -0
  39. package/dist/src/agent-session-steps.d.ts +179 -0
  40. package/dist/src/agent-session-steps.js +1272 -0
  41. package/dist/src/agent-session-steps.js.map +1 -0
  42. package/dist/src/agent-session-tracer.d.ts +61 -0
  43. package/dist/src/agent-session-tracer.js +608 -0
  44. package/dist/src/agent-session-tracer.js.map +1 -0
  45. package/dist/src/agent-session-transitions.d.ts +224 -0
  46. package/dist/src/agent-session-transitions.js +850 -0
  47. package/dist/src/agent-session-transitions.js.map +1 -0
  48. package/dist/src/agent-session-values.d.ts +998 -0
  49. package/dist/src/agent-session-values.js +162 -0
  50. package/dist/src/agent-session-values.js.map +1 -0
  51. package/dist/src/agent-session-wait-steps.d.ts +53 -0
  52. package/dist/src/agent-session-wait-steps.js +389 -0
  53. package/dist/src/agent-session-wait-steps.js.map +1 -0
  54. package/dist/src/agent-tracing-options.d.ts +107 -0
  55. package/dist/src/agent-tracing-options.js +20 -0
  56. package/dist/src/agent-tracing-options.js.map +1 -0
  57. package/dist/src/ai-sdk/ai-sdk-model-client.d.ts +40 -0
  58. package/dist/src/ai-sdk/ai-sdk-model-client.js +235 -0
  59. package/dist/src/ai-sdk/ai-sdk-model-client.js.map +1 -0
  60. package/dist/src/ai-sdk/index.d.ts +1 -0
  61. package/dist/src/ai-sdk/index.js +9 -0
  62. package/dist/src/ai-sdk/index.js.map +1 -0
  63. package/dist/src/index.d.ts +10 -0
  64. package/dist/src/index.js +17 -0
  65. package/dist/src/index.js.map +1 -0
  66. package/dist/src/mcp/index.d.ts +3 -0
  67. package/dist/src/mcp/index.js +10 -0
  68. package/dist/src/mcp/index.js.map +1 -0
  69. package/dist/src/mcp/mcp-resource-and-prompt-tools.d.ts +29 -0
  70. package/dist/src/mcp/mcp-resource-and-prompt-tools.js +185 -0
  71. package/dist/src/mcp/mcp-resource-and-prompt-tools.js.map +1 -0
  72. package/dist/src/mcp/mcp-server-config.d.ts +86 -0
  73. package/dist/src/mcp/mcp-server-config.js +38 -0
  74. package/dist/src/mcp/mcp-server-config.js.map +1 -0
  75. package/dist/src/mcp/mcp-tool-source.d.ts +470 -0
  76. package/dist/src/mcp/mcp-tool-source.js +1204 -0
  77. package/dist/src/mcp/mcp-tool-source.js.map +1 -0
  78. package/dist/src/stepwise/agent-definition.d.ts +158 -0
  79. package/dist/src/stepwise/agent-definition.js +9 -0
  80. package/dist/src/stepwise/agent-definition.js.map +1 -0
  81. package/dist/src/stepwise/function-tool.d.ts +130 -0
  82. package/dist/src/stepwise/function-tool.js +75 -0
  83. package/dist/src/stepwise/function-tool.js.map +1 -0
  84. package/dist/src/stepwise/index.d.ts +7 -0
  85. package/dist/src/stepwise/index.js +12 -0
  86. package/dist/src/stepwise/index.js.map +1 -0
  87. package/dist/src/stepwise/json-schema-validation.d.ts +9 -0
  88. package/dist/src/stepwise/json-schema-validation.js +141 -0
  89. package/dist/src/stepwise/json-schema-validation.js.map +1 -0
  90. package/dist/src/stepwise/model-client.d.ts +194 -0
  91. package/dist/src/stepwise/model-client.js +9 -0
  92. package/dist/src/stepwise/model-client.js.map +1 -0
  93. package/dist/src/stepwise/scripted-model-client.d.ts +99 -0
  94. package/dist/src/stepwise/scripted-model-client.js +156 -0
  95. package/dist/src/stepwise/scripted-model-client.js.map +1 -0
  96. package/dist/src/stepwise/stepwise-agent-runner.d.ts +189 -0
  97. package/dist/src/stepwise/stepwise-agent-runner.js +804 -0
  98. package/dist/src/stepwise/stepwise-agent-runner.js.map +1 -0
  99. package/dist/src/stepwise/stepwise-agent-session-flow.d.ts +35 -0
  100. package/dist/src/stepwise/stepwise-agent-session-flow.js +56 -0
  101. package/dist/src/stepwise/stepwise-agent-session-flow.js.map +1 -0
  102. package/dist/src/stepwise/stepwise-input-items.d.ts +12 -0
  103. package/dist/src/stepwise/stepwise-input-items.js +172 -0
  104. package/dist/src/stepwise/stepwise-input-items.js.map +1 -0
  105. package/dist/src/stepwise/tool-source.d.ts +103 -0
  106. package/dist/src/stepwise/tool-source.js +9 -0
  107. package/dist/src/stepwise/tool-source.js.map +1 -0
  108. package/dist/src/testing/agent-session-test-client.d.ts +205 -0
  109. package/dist/src/testing/agent-session-test-client.js +290 -0
  110. package/dist/src/testing/agent-session-test-client.js.map +1 -0
  111. package/dist/src/testing/agent-test-environment.d.ts +52 -0
  112. package/dist/src/testing/agent-test-environment.js +86 -0
  113. package/dist/src/testing/agent-test-environment.js.map +1 -0
  114. package/dist/src/testing/agent-worker-process.d.ts +91 -0
  115. package/dist/src/testing/agent-worker-process.js +150 -0
  116. package/dist/src/testing/agent-worker-process.js.map +1 -0
  117. package/dist/src/testing/child-process-group.d.ts +33 -0
  118. package/dist/src/testing/child-process-group.js +164 -0
  119. package/dist/src/testing/child-process-group.js.map +1 -0
  120. package/dist/src/testing/coordination.d.ts +99 -0
  121. package/dist/src/testing/coordination.js +155 -0
  122. package/dist/src/testing/coordination.js.map +1 -0
  123. package/dist/src/testing/index.d.ts +8 -0
  124. package/dist/src/testing/index.js +16 -0
  125. package/dist/src/testing/index.js.map +1 -0
  126. package/dist/src/testing/polling.d.ts +31 -0
  127. package/dist/src/testing/polling.js +51 -0
  128. package/dist/src/testing/polling.js.map +1 -0
  129. package/dist/src/testing/private-dex-engine.d.ts +101 -0
  130. package/dist/src/testing/private-dex-engine.js +223 -0
  131. package/dist/src/testing/private-dex-engine.js.map +1 -0
  132. package/dist/src/testing/scripted-model-client.d.ts +1 -0
  133. package/dist/src/testing/scripted-model-client.js +10 -0
  134. package/dist/src/testing/scripted-model-client.js.map +1 -0
  135. package/dist/src/testing/step-history.d.ts +40 -0
  136. package/dist/src/testing/step-history.js +59 -0
  137. package/dist/src/testing/step-history.js.map +1 -0
  138. package/dist/src/ui-stream/agent-event-json.d.ts +30 -0
  139. package/dist/src/ui-stream/agent-event-json.js +64 -0
  140. package/dist/src/ui-stream/agent-event-json.js.map +1 -0
  141. package/dist/src/ui-stream/agent-event-sse.d.ts +154 -0
  142. package/dist/src/ui-stream/agent-event-sse.js +159 -0
  143. package/dist/src/ui-stream/agent-event-sse.js.map +1 -0
  144. package/dist/src/ui-stream/agent-event-stream-positions.d.ts +42 -0
  145. package/dist/src/ui-stream/agent-event-stream-positions.js +153 -0
  146. package/dist/src/ui-stream/agent-event-stream-positions.js.map +1 -0
  147. package/dist/src/ui-stream/agent-session-record-reader.d.ts +72 -0
  148. package/dist/src/ui-stream/agent-session-record-reader.js +241 -0
  149. package/dist/src/ui-stream/agent-session-record-reader.js.map +1 -0
  150. package/dist/src/ui-stream/agent-ui-message-stream-responses.d.ts +219 -0
  151. package/dist/src/ui-stream/agent-ui-message-stream-responses.js +440 -0
  152. package/dist/src/ui-stream/agent-ui-message-stream-responses.js.map +1 -0
  153. package/dist/src/ui-stream/agent-ui-message-stream.d.ts +138 -0
  154. package/dist/src/ui-stream/agent-ui-message-stream.js +681 -0
  155. package/dist/src/ui-stream/agent-ui-message-stream.js.map +1 -0
  156. package/dist/src/ui-stream/agent-ui-message-types.d.ts +238 -0
  157. package/dist/src/ui-stream/agent-ui-message-types.js +57 -0
  158. package/dist/src/ui-stream/agent-ui-message-types.js.map +1 -0
  159. package/dist/src/ui-stream/agent-ui-messages.d.ts +66 -0
  160. package/dist/src/ui-stream/agent-ui-messages.js +220 -0
  161. package/dist/src/ui-stream/agent-ui-messages.js.map +1 -0
  162. package/dist/src/ui-stream/agent-ui-stream-cursor.d.ts +64 -0
  163. package/dist/src/ui-stream/agent-ui-stream-cursor.js +186 -0
  164. package/dist/src/ui-stream/agent-ui-stream-cursor.js.map +1 -0
  165. package/dist/src/ui-stream/index.d.ts +14 -0
  166. package/dist/src/ui-stream/index.js +22 -0
  167. package/dist/src/ui-stream/index.js.map +1 -0
  168. package/dist/src/ui-stream/server-sent-event-responses.d.ts +6 -0
  169. package/dist/src/ui-stream/server-sent-event-responses.js +34 -0
  170. package/dist/src/ui-stream/server-sent-event-responses.js.map +1 -0
  171. package/package.json +86 -4
@@ -0,0 +1,681 @@
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 { describeToolCallOutcome, outcomeUnknownToolResult, } from "../agent-session-values.js";
9
+ import { findAgentEventResumeTokenBeforeTurn, parseTurnNumber, probeAgentEventResumeToken, readNextAgentEvent, readStepType, requireAgentEventPollTimeoutMs, resumeTokenProbeTimeoutMs, } from "./agent-event-stream-positions.js";
10
+ import { convertAgentEventToJson } from "./agent-event-json.js";
11
+ import { AgentSessionRecordReader } from "./agent-session-record-reader.js";
12
+ import { agentAssistantUIMessageId, agentEventDataPartType, agentResumeTokenDataPartType, } from "./agent-ui-message-types.js";
13
+ import { cloneAgentUIStreamCursor, createAgentUIStreamCursor, decodeAgentUIStreamCursor, encodeAgentUIStreamCursor, } from "./agent-ui-stream-cursor.js";
14
+ const modelCallStepType = "RunModel";
15
+ const failedToolCallText = "The tool call failed.";
16
+ /**
17
+ * Translates one agent turn from the session's `AgentEvents` Stream into AI SDK UI message stream parts.
18
+ *
19
+ * The parts follow one turn. Without a resume token they replay the session's active turn from its start.
20
+ * With one they replay the token's turn, or continue it exactly with `continueAfterResumeToken`. The
21
+ * generator returns after the turn completes (`finish` with `stop`), fails (`error`, then `finish` with
22
+ * `error`), ends through steering or `cancel_turn` (the `turn_steered` or `turn_cancelled` event data part, then
23
+ * `finish` with `other`), or waits only for tool approvals (`finish` with `tool-calls`). It also returns when the turn
24
+ * ended without its end event on the Stream, or the session Flow closed (`finish` with `other`); the
25
+ * session's committed state decides this, so the stream ends even when the Stream lost every event. On
26
+ * `other`, reload the stored messages with {@link readAgentSessionUIMessages}.
27
+ *
28
+ * Every `RunModel` Step execution is one AI SDK step. When a new attempt of the same Step execution starts,
29
+ * for example after a Worker crash, the generator emits `reset-step`, so the browser drops the partial
30
+ * text of the interrupted attempt. Tool calls become dynamic tool parts. A tool call that needs approval
31
+ * gets a `tool-approval-request` whose `approvalId` is the `interrupt_id` for `resolve_interrupt`. A
32
+ * `question` interrupt is not an approval: its `interrupt_raised` and `interrupt_resolved` events arrive as
33
+ * agent event data parts, and the browser answers it with `answer_question`. A
34
+ * transient `data-agent-resume-token` part follows every part; send the latest one back to resume.
35
+ *
36
+ * The generator reads tool arguments and results from the committed `AgentToolCalls` records, waiting up
37
+ * to 3 seconds for the commit that follows each event, and from the stored messages once the turn deleted
38
+ * its records. It also waits up to 3 seconds for a turn's end to commit before it finishes. Events are
39
+ * best effort: a part whose event the Stream lost or trimmed is missing until the stored messages replace
40
+ * the streamed message.
41
+ *
42
+ * Stop in the browser only closes the HTTP response, and the turn keeps running in Dex. To stop the turn
43
+ * durably, the application's stop route calls the session's `cancel_turn` RPC.
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * import { createUIMessageStreamResponse } from "ai";
48
+ * import { streamAgentUIMessageChunks } from "@superdurable/dex-agents/ui-stream";
49
+ *
50
+ * export async function GET(request: Request, sessionId: string): Promise<Response> {
51
+ * const chunks = streamAgentUIMessageChunks({
52
+ * client, flow, flowId: sessionId,
53
+ * resumeToken: request.headers.get("x-agent-resume-token") ?? undefined, signal: request.signal,
54
+ * });
55
+ * return createUIMessageStreamResponse({ stream: ReadableStream.from(chunks) });
56
+ * }
57
+ * ```
58
+ * @param options - Session, start position, and polling.
59
+ * @returns An async generator of UI message stream parts. It yields nothing when the token's turn already
60
+ * finished, or when the stream would replay a turn that is not active or not on the Stream.
61
+ * @throws {TypeError} When `resumeToken` is not a UI stream resume token.
62
+ * @throws {RangeError} When `pollTimeoutMs` is not a positive whole number of seconds.
63
+ * @throws {DexServiceError} When a Stream read, Attribute read, or RPC fails.
64
+ */
65
+ export async function* streamAgentUIMessageChunks(options) {
66
+ const pollTimeoutMs = requireAgentEventPollTimeoutMs(options.pollTimeoutMs);
67
+ const start = await prepareAgentUIStreamStart(options);
68
+ if (start === undefined) {
69
+ return;
70
+ }
71
+ for await (const chunks of readAgentUIMessageChunkBatches(options, start, pollTimeoutMs)) {
72
+ if (chunks !== undefined) {
73
+ yield* chunks;
74
+ }
75
+ }
76
+ }
77
+ /**
78
+ * Resolves where a stream starts and checks the Dex resume token with one short read.
79
+ *
80
+ * A token that Dex rejects, such as a forged one, restarts the stream from the turn's first retained
81
+ * event, which the Server lists itself.
82
+ * @returns The start, or `undefined` when there is nothing to stream.
83
+ * @throws {TypeError} When the resume token is invalid.
84
+ */
85
+ export async function prepareAgentUIStreamStart(options) {
86
+ const cursor = await resolveAgentUIStreamStartCursor(options);
87
+ if (cursor === undefined) {
88
+ return undefined;
89
+ }
90
+ return probeAgentUIStreamStart(options.client, options.flow, options.flowId, cursor);
91
+ }
92
+ /** Checks a cursor's Dex resume token, replacing a rejected one with the turn's listed start. */
93
+ export async function probeAgentUIStreamStart(client, flow, flowId, cursor) {
94
+ const probe = await probeAgentEventResumeToken(client, flow, flowId, cursor.eventResumeToken);
95
+ if (probe.isAccepted) {
96
+ return { cursor, firstMessage: probe.firstMessage };
97
+ }
98
+ if (cursor.turnId === null) {
99
+ return undefined;
100
+ }
101
+ const replayCursor = await createTurnReplayCursor(client, flow, flowId, cursor.turnId, null);
102
+ if (replayCursor === undefined) {
103
+ return undefined;
104
+ }
105
+ const replayProbe = await probeAgentEventResumeToken(client, flow, flowId, replayCursor.eventResumeToken);
106
+ return replayProbe.isAccepted ? { cursor: replayCursor, firstMessage: replayProbe.firstMessage } : undefined;
107
+ }
108
+ /**
109
+ * Returns the cursor where a stream starts, or `undefined` when there is nothing to stream.
110
+ * @throws {TypeError} When the resume token is invalid.
111
+ */
112
+ export async function resolveAgentUIStreamStartCursor(options) {
113
+ const { client, flow, flowId } = options;
114
+ if (options.resumeToken !== undefined) {
115
+ const cursor = decodeAgentUIStreamCursor(options.resumeToken);
116
+ if (cursor.isTurnFinished) {
117
+ return undefined;
118
+ }
119
+ if (options.continueAfterResumeToken === true || cursor.turnId === null) {
120
+ return cursor;
121
+ }
122
+ // The browser may hold any part of the turn, so replay it and let the start part reset the message.
123
+ return ((await createTurnReplayCursor(client, flow, flowId, cursor.turnId, cursor.turnStartResumeToken)) ??
124
+ restartTurnAtCursor(cursor));
125
+ }
126
+ const [state] = await client.getAttributes(flowId, flow.agentState);
127
+ const activeTurnId = state?.activeTurnId ?? null;
128
+ return activeTurnId === null
129
+ ? undefined
130
+ : createTurnReplayCursor(client, flow, flowId, activeTurnId, null);
131
+ }
132
+ /**
133
+ * Returns a cursor that replays a turn from its first retained event, or `undefined` when none is retained.
134
+ * @param knownTurnStartResumeToken - The turn's start position recorded in a cursor, or `null` to search.
135
+ */
136
+ export async function createTurnReplayCursor(client, flow, flowId, turnId, knownTurnStartResumeToken) {
137
+ const eventResumeToken = knownTurnStartResumeToken ?? (await findAgentEventResumeTokenBeforeTurn(client, flow, flowId, turnId));
138
+ if (eventResumeToken === undefined) {
139
+ return undefined;
140
+ }
141
+ const cursor = createAgentUIStreamCursor(eventResumeToken, turnId, false);
142
+ cursor.turnStartResumeToken = eventResumeToken;
143
+ return cursor;
144
+ }
145
+ /** Restarts the message at the cursor's position when the turn's start is no longer on the Stream. */
146
+ function restartTurnAtCursor(cursor) {
147
+ const restarted = createAgentUIStreamCursor(cursor.eventResumeToken, cursor.turnId, false);
148
+ restarted.skippedChunkCount = 0;
149
+ return restarted;
150
+ }
151
+ /**
152
+ * Reads events from the cursor and yields the parts of each one.
153
+ * Yields `undefined` after an empty long poll, so a response can send a keep-alive comment.
154
+ */
155
+ export async function* readAgentUIMessageChunkBatches(options, start, pollTimeoutMs) {
156
+ const { client, flow, flowId } = options;
157
+ const cursor = start.cursor;
158
+ const translator = new AgentUIMessageChunkTranslator(new AgentSessionRecordReader(client, flow, flowId), cursor, options.finishWhenAwaitingApproval ?? true);
159
+ let pendingMessage = start.firstMessage;
160
+ while (!translator.isComplete && options.signal?.aborted !== true) {
161
+ const message = pendingMessage ??
162
+ (await readNextAgentEvent(client, flow, flowId, cursor.eventResumeToken, pollTimeoutMs));
163
+ pendingMessage = undefined;
164
+ if (message !== undefined) {
165
+ yield await translator.translateEvent(message);
166
+ continue;
167
+ }
168
+ const idleDecision = await translator.decideAfterIdle();
169
+ if (idleDecision !== "wait") {
170
+ // Events reach the Stream before their commit; read once more before ending on committed state.
171
+ pendingMessage = await readNextAgentEvent(client, flow, flowId, cursor.eventResumeToken, resumeTokenProbeTimeoutMs);
172
+ if (pendingMessage !== undefined) {
173
+ continue;
174
+ }
175
+ }
176
+ const chunks = translator.applyIdleDecision(idleDecision);
177
+ yield chunks.length > 0 ? chunks : undefined;
178
+ }
179
+ }
180
+ /** Marks where the final resume token goes, before the `error` and `finish` parts. */
181
+ const finalResumeTokenMarker = Symbol("final resume token");
182
+ /** Translates the events of the followed turn into parts, keeping the cursor in step with the browser. */
183
+ class AgentUIMessageChunkTranslator {
184
+ records;
185
+ cursor;
186
+ isFinishedWhenAwaitingApproval;
187
+ chunks = [];
188
+ isStreamEnded = false;
189
+ constructor(records, cursor, isFinishedWhenAwaitingApproval) {
190
+ this.records = records;
191
+ this.cursor = cursor;
192
+ this.isFinishedWhenAwaitingApproval = isFinishedWhenAwaitingApproval;
193
+ }
194
+ get isComplete() {
195
+ return this.isStreamEnded || this.cursor.isTurnFinished;
196
+ }
197
+ async translateEvent(message) {
198
+ const skippedChunkCount = this.cursor.skippedChunkCount;
199
+ this.cursor.skippedChunkCount = 0;
200
+ const cursorBeforeEvent = cloneAgentUIStreamCursor(this.cursor);
201
+ this.cursor.eventResumeToken = message.resumeToken;
202
+ const event = message.value;
203
+ if (this.isFollowedTurnEvent(event, cursorBeforeEvent.eventResumeToken)) {
204
+ this.startMessage();
205
+ await this.translateFollowedTurnEvent(event);
206
+ if (!this.isComplete &&
207
+ this.isFinishedWhenAwaitingApproval &&
208
+ this.cursor.modelStep?.isUsageRecorded === true &&
209
+ (await this.isConfirmedWaitingOnlyForApprovals())) {
210
+ this.endStream("tool-calls");
211
+ }
212
+ }
213
+ return this.takeChunks(cursorBeforeEvent, skippedChunkCount);
214
+ }
215
+ /** Decides from committed state what an empty long poll means; it may start following a turn. */
216
+ async decideAfterIdle() {
217
+ const turnId = this.cursor.turnId;
218
+ const state = await this.records.readAgentState();
219
+ if (turnId === null) {
220
+ const afterTurnNumber = this.cursor.afterTurnNumber;
221
+ const activeTurnNumber = state?.activeTurnId == null ? undefined : parseTurnNumber(state.activeTurnId);
222
+ if (state?.activeTurnId != null &&
223
+ activeTurnNumber !== undefined &&
224
+ (afterTurnNumber === null || activeTurnNumber > afterTurnNumber)) {
225
+ // The turn started, but its turn_started event never reached this stream.
226
+ this.cursor.turnId = state.activeTurnId;
227
+ return "wait";
228
+ }
229
+ if (afterTurnNumber !== null && state !== undefined && state.turnCount > afterTurnNumber) {
230
+ return "end";
231
+ }
232
+ return (await this.records.isFlowRunning()) ? "wait" : "end";
233
+ }
234
+ if (state?.activeTurnId !== turnId || !(await this.records.isFlowRunning())) {
235
+ return "end";
236
+ }
237
+ return this.isFinishedWhenAwaitingApproval && (await this.isConfirmedWaitingOnlyForApprovals())
238
+ ? "pause"
239
+ : "wait";
240
+ }
241
+ applyIdleDecision(idleDecision) {
242
+ if (idleDecision === "end") {
243
+ this.endStream("other");
244
+ }
245
+ else if (idleDecision === "pause") {
246
+ this.endStream("tool-calls");
247
+ }
248
+ return this.takeChunks(undefined, 0);
249
+ }
250
+ isFollowedTurnEvent(event, eventResumeTokenBefore) {
251
+ if (this.cursor.turnId !== null) {
252
+ return event.turnId === this.cursor.turnId;
253
+ }
254
+ // A cursor without a turn follows the next turn that starts after its position.
255
+ if (event.type !== "turn_started" || event.turnId === null) {
256
+ return false;
257
+ }
258
+ const turnNumber = parseTurnNumber(event.turnId);
259
+ const afterTurnNumber = this.cursor.afterTurnNumber;
260
+ if (afterTurnNumber !== null && (turnNumber === undefined || turnNumber <= afterTurnNumber)) {
261
+ return false;
262
+ }
263
+ this.cursor.turnId = event.turnId;
264
+ this.cursor.turnStartResumeToken = eventResumeTokenBefore;
265
+ return true;
266
+ }
267
+ startMessage() {
268
+ if (this.cursor.isMessageStarted || this.cursor.turnId === null) {
269
+ return;
270
+ }
271
+ this.chunks.push({ type: "start", messageId: agentAssistantUIMessageId(this.cursor.turnId) });
272
+ this.cursor.isMessageStarted = true;
273
+ }
274
+ async translateFollowedTurnEvent(event) {
275
+ const payload = parseEventPayload(event.payload);
276
+ if (payload === undefined) {
277
+ this.pushAgentEventData(event);
278
+ return;
279
+ }
280
+ switch (event.type) {
281
+ case "turn_started":
282
+ return;
283
+ case "text_delta":
284
+ case "reasoning_delta": {
285
+ const text = readPayloadString(payload, "text");
286
+ // Text from a tool Step belongs to no model call, so it travels as event data.
287
+ if (text === undefined || !isModelCallStepType(readStepType(event.stepExecutionId))) {
288
+ this.pushAgentEventData(event);
289
+ return;
290
+ }
291
+ if (this.enterModelStep(event)) {
292
+ this.appendDelta(event.type === "text_delta" ? "text" : "reasoning", text);
293
+ }
294
+ return;
295
+ }
296
+ case "tool_call_deferred": {
297
+ const toolCallId = readPayloadString(payload, "tool_call_id");
298
+ const toolName = readPayloadString(payload, "tool_name");
299
+ if (toolCallId === undefined || toolName === undefined) {
300
+ this.pushAgentEventData(event);
301
+ return;
302
+ }
303
+ if (this.enterModelStep(event)) {
304
+ await this.introduceToolCall(toolCallId, toolName);
305
+ }
306
+ return;
307
+ }
308
+ case "interrupt_raised": {
309
+ const interruptId = readPayloadString(payload, "interrupt_id");
310
+ const toolCallId = readPayloadString(payload, "tool_call_id");
311
+ // A question is answered with answer_question, not approved, so it stays an event data part.
312
+ if (interruptId === undefined || toolCallId === undefined || payload["kind"] === "question") {
313
+ this.pushAgentEventData(event);
314
+ return;
315
+ }
316
+ if (this.enterModelStep(event)) {
317
+ await this.requestToolApproval(toolCallId, readPayloadString(payload, "tool_name"), interruptId);
318
+ }
319
+ return;
320
+ }
321
+ case "usage_recorded":
322
+ // A CompactContext summary call is no step of the assistant message; its usage is only event data.
323
+ if (isModelCallStepType(readStepType(event.stepExecutionId)) &&
324
+ this.enterModelStep(event) &&
325
+ this.cursor.modelStep !== null) {
326
+ this.cursor.modelStep.isUsageRecorded = true;
327
+ }
328
+ this.pushAgentEventData(event);
329
+ return;
330
+ case "tool_call_started": {
331
+ const toolCallId = readPayloadString(payload, "tool_call_id");
332
+ if (toolCallId !== undefined) {
333
+ await this.introduceToolCall(toolCallId, readPayloadString(payload, "tool_name"));
334
+ }
335
+ this.pushAgentEventData(event);
336
+ return;
337
+ }
338
+ case "interrupt_resolved": {
339
+ const interruptId = readPayloadString(payload, "interrupt_id");
340
+ const isApproved = payload["approved"];
341
+ if (interruptId === undefined || typeof isApproved !== "boolean" || payload["kind"] === "question") {
342
+ this.pushAgentEventData(event);
343
+ return;
344
+ }
345
+ await this.recordToolApproval(interruptId, isApproved, readPayloadString(payload, "rejection_message"));
346
+ return;
347
+ }
348
+ case "tool_call_finished": {
349
+ const toolCallId = readPayloadString(payload, "tool_call_id");
350
+ const status = readPayloadString(payload, "status");
351
+ if (toolCallId === undefined || status === undefined) {
352
+ this.pushAgentEventData(event);
353
+ return;
354
+ }
355
+ await this.finishToolCall(toolCallId, readPayloadString(payload, "tool_name"), status, event);
356
+ return;
357
+ }
358
+ case "turn_completed":
359
+ if (this.enterModelStep(event) && (await this.records.waitForTurnToEnd(this.requireTurnId()))) {
360
+ this.endStream("stop");
361
+ }
362
+ return;
363
+ case "turn_failed":
364
+ if (await this.records.waitForTurnToEnd(this.requireTurnId())) {
365
+ this.endStream("error", readPayloadString(payload, "error") ?? "the agent turn failed");
366
+ }
367
+ return;
368
+ case "turn_steered":
369
+ case "turn_cancelled":
370
+ // The turn ended without an answer; the browser reloads its stored messages on `other`.
371
+ this.pushAgentEventData(event);
372
+ if (await this.records.waitForTurnToEnd(this.requireTurnId())) {
373
+ this.endStream("other");
374
+ }
375
+ return;
376
+ default:
377
+ this.pushAgentEventData(event);
378
+ }
379
+ }
380
+ /**
381
+ * Moves to the event's model call, opening a new step or resetting an interrupted attempt.
382
+ * @returns `false` for an event of an attempt older than the one the browser shows, which is dropped.
383
+ */
384
+ enterModelStep(event) {
385
+ const modelStep = this.cursor.modelStep;
386
+ if (modelStep === null || modelStep.stepExecutionId !== event.stepExecutionId) {
387
+ this.closeOpenPart();
388
+ if (modelStep !== null) {
389
+ this.chunks.push({ type: "finish-step" });
390
+ }
391
+ this.chunks.push({ type: "start-step" });
392
+ this.cursor.modelStep = {
393
+ stepExecutionId: event.stepExecutionId,
394
+ attempt: event.attempt,
395
+ openedPartCount: 0,
396
+ isUsageRecorded: false,
397
+ };
398
+ // Every tool call of the previous model call finished before this one started.
399
+ this.cursor.toolCalls.clear();
400
+ return true;
401
+ }
402
+ if (event.attempt < modelStep.attempt) {
403
+ return false;
404
+ }
405
+ if (event.attempt > modelStep.attempt) {
406
+ // The browser removes every part of the current step, open parts included.
407
+ this.chunks.push({ type: "reset-step" });
408
+ this.cursor.openPart = null;
409
+ this.cursor.toolCalls.clear();
410
+ modelStep.attempt = event.attempt;
411
+ modelStep.openedPartCount = 0;
412
+ modelStep.isUsageRecorded = false;
413
+ }
414
+ return true;
415
+ }
416
+ appendDelta(kind, text) {
417
+ const modelStep = this.cursor.modelStep;
418
+ if (text === "" || modelStep === null) {
419
+ return;
420
+ }
421
+ let openPart = this.cursor.openPart;
422
+ if (openPart !== null && openPart.kind !== kind) {
423
+ this.closeOpenPart();
424
+ openPart = null;
425
+ }
426
+ if (openPart === null) {
427
+ const id = `${modelStep.stepExecutionId}.${modelStep.attempt}.${modelStep.openedPartCount}`;
428
+ modelStep.openedPartCount += 1;
429
+ openPart = { kind, id };
430
+ this.cursor.openPart = openPart;
431
+ this.chunks.push(kind === "text" ? { type: "text-start", id } : { type: "reasoning-start", id });
432
+ }
433
+ this.chunks.push(kind === "text"
434
+ ? { type: "text-delta", id: openPart.id, delta: text }
435
+ : { type: "reasoning-delta", id: openPart.id, delta: text });
436
+ }
437
+ closeOpenPart() {
438
+ const openPart = this.cursor.openPart;
439
+ if (openPart === null) {
440
+ return;
441
+ }
442
+ this.chunks.push(openPart.kind === "text"
443
+ ? { type: "text-end", id: openPart.id }
444
+ : { type: "reasoning-end", id: openPart.id });
445
+ this.cursor.openPart = null;
446
+ }
447
+ /**
448
+ * Adds the dynamic tool part of a tool call the browser does not show yet.
449
+ * @returns Whether the browser shows the tool call's part afterwards.
450
+ */
451
+ async introduceToolCall(toolCallId, toolName) {
452
+ if (this.cursor.toolCalls.has(toolCallId)) {
453
+ return true;
454
+ }
455
+ const details = await this.records.readToolCallDetails(this.requireTurnId(), toolCallId, () => true);
456
+ const resolvedToolName = toolName ?? details.toolName;
457
+ if (resolvedToolName === undefined) {
458
+ return false;
459
+ }
460
+ this.closeOpenPart();
461
+ this.chunks.push({
462
+ type: "tool-input-available",
463
+ toolCallId,
464
+ toolName: resolvedToolName,
465
+ // JSON drops undefined, and the AI SDK requires the field.
466
+ input: parseToolArguments(details.toolArguments) ?? null,
467
+ dynamic: true,
468
+ });
469
+ this.cursor.toolCalls.set(toolCallId, "input");
470
+ return true;
471
+ }
472
+ async requestToolApproval(toolCallId, toolName, interruptId) {
473
+ if (!(await this.introduceToolCall(toolCallId, toolName))) {
474
+ return;
475
+ }
476
+ if (this.cursor.toolCalls.get(toolCallId) !== "input") {
477
+ return;
478
+ }
479
+ this.chunks.push({ type: "tool-approval-request", approvalId: interruptId, toolCallId });
480
+ this.cursor.toolCalls.set(toolCallId, "approval_requested");
481
+ }
482
+ async recordToolApproval(interruptId, isApproved, rejectionMessage) {
483
+ // A tool approval's interrupt ID is its tool call ID.
484
+ const toolCallId = interruptId;
485
+ await this.requestToolApproval(toolCallId, undefined, interruptId);
486
+ if (this.cursor.toolCalls.get(toolCallId) !== "approval_requested") {
487
+ return;
488
+ }
489
+ this.chunks.push({
490
+ type: "tool-approval-response",
491
+ approvalId: interruptId,
492
+ approved: isApproved,
493
+ ...(rejectionMessage === undefined ? {} : { reason: rejectionMessage }),
494
+ });
495
+ this.cursor.toolCalls.set(toolCallId, "approval_resolved");
496
+ }
497
+ async finishToolCall(toolCallId, toolName, status, event) {
498
+ if (status === "rejected") {
499
+ await this.recordToolApproval(toolCallId, false, undefined);
500
+ if (this.cursor.toolCalls.has(toolCallId)) {
501
+ this.chunks.push({ type: "tool-output-denied", toolCallId });
502
+ this.cursor.toolCalls.set(toolCallId, "finished");
503
+ }
504
+ return;
505
+ }
506
+ if (status !== "succeeded" && status !== "failed" && status !== "outcome_unknown") {
507
+ this.pushAgentEventData(event);
508
+ return;
509
+ }
510
+ if (!(await this.introduceToolCall(toolCallId, toolName))) {
511
+ return;
512
+ }
513
+ if (status === "outcome_unknown") {
514
+ this.chunks.push({ type: "tool-output-error", toolCallId, errorText: outcomeUnknownToolResult, dynamic: true });
515
+ }
516
+ else {
517
+ const details = await this.records.readToolCallDetails(this.requireTurnId(), toolCallId, (toolCall) => toolCall.status === status);
518
+ const record = details.record?.status === status ? details.record : undefined;
519
+ if (status === "succeeded") {
520
+ this.chunks.push({
521
+ type: "tool-output-available",
522
+ toolCallId,
523
+ output: record?.output ?? details.storedToolOutput ?? null,
524
+ dynamic: true,
525
+ });
526
+ }
527
+ else {
528
+ this.chunks.push({
529
+ type: "tool-output-error",
530
+ toolCallId,
531
+ errorText: record === undefined
532
+ ? (details.storedToolOutput ?? failedToolCallText)
533
+ : describeToolCallOutcome(record),
534
+ dynamic: true,
535
+ });
536
+ }
537
+ }
538
+ this.cursor.toolCalls.set(toolCallId, "finished");
539
+ }
540
+ /**
541
+ * Reports whether the turn waits only for approvals, judged by the committed records and
542
+ * `PendingInterrupts`: a replay reaches an approval request before the events that answered it.
543
+ */
544
+ async isConfirmedWaitingOnlyForApprovals() {
545
+ if (!this.isWaitingOnlyForApprovals()) {
546
+ return false;
547
+ }
548
+ const requestedApprovalToolCallIds = [];
549
+ for (const [toolCallId, state] of this.cursor.toolCalls) {
550
+ if (state !== "approval_requested") {
551
+ continue;
552
+ }
553
+ const record = await this.records.readCommittedToolCall(this.requireTurnId(), toolCallId);
554
+ if (record?.status !== "awaiting_approval") {
555
+ return false;
556
+ }
557
+ requestedApprovalToolCallIds.push(toolCallId);
558
+ }
559
+ // resolve_interrupt removes the interrupt before the approval Step commits the record.
560
+ const pendingApprovalToolCallIds = await this.records.readPendingApprovalToolCallIds();
561
+ return requestedApprovalToolCallIds.every((toolCallId) => pendingApprovalToolCallIds.has(toolCallId));
562
+ }
563
+ isWaitingOnlyForApprovals() {
564
+ let hasRequestedApproval = false;
565
+ for (const state of this.cursor.toolCalls.values()) {
566
+ if (state === "approval_requested") {
567
+ hasRequestedApproval = true;
568
+ }
569
+ else if (state !== "finished") {
570
+ return false;
571
+ }
572
+ }
573
+ return hasRequestedApproval;
574
+ }
575
+ endStream(finishReason, errorText) {
576
+ this.closeOpenPart();
577
+ if (finishReason === "tool-calls") {
578
+ this.isStreamEnded = true;
579
+ }
580
+ else {
581
+ if (this.cursor.modelStep !== null) {
582
+ this.chunks.push({ type: "finish-step" });
583
+ }
584
+ this.cursor.isTurnFinished = true;
585
+ }
586
+ this.chunks.push(finalResumeTokenMarker);
587
+ if (errorText !== undefined) {
588
+ this.chunks.push({ type: "error", errorText });
589
+ }
590
+ this.chunks.push({ type: "finish", finishReason });
591
+ }
592
+ pushAgentEventData(event) {
593
+ this.chunks.push({ type: agentEventDataPartType, data: convertAgentEventToJson(event), transient: true });
594
+ }
595
+ /**
596
+ * Returns the event's parts after the ones the browser already holds, each followed by a resume token.
597
+ * A token inside the event names the state before it plus the parts already sent, so a cut between
598
+ * two parts of one event resumes without repeating either. Parts of no event, `cursorBeforeEvent`
599
+ * `undefined`, get only the final token.
600
+ */
601
+ takeChunks(cursorBeforeEvent, skippedChunkCount) {
602
+ const chunks = this.chunks;
603
+ this.chunks = [];
604
+ let lastChunkIndex = chunks.length - 1;
605
+ while (lastChunkIndex >= 0 && chunks[lastChunkIndex] === finalResumeTokenMarker) {
606
+ lastChunkIndex -= 1;
607
+ }
608
+ const markerIndex = chunks.indexOf(finalResumeTokenMarker);
609
+ const translatedChunks = [];
610
+ let chunkCount = 0;
611
+ for (const [index, chunk] of chunks.entries()) {
612
+ if (chunk === finalResumeTokenMarker) {
613
+ translatedChunks.push(this.createResumeTokenChunk(this.cursor));
614
+ continue;
615
+ }
616
+ chunkCount += 1;
617
+ if (chunkCount <= skippedChunkCount) {
618
+ continue;
619
+ }
620
+ translatedChunks.push(chunk);
621
+ if (markerIndex >= 0 && index > markerIndex) {
622
+ continue;
623
+ }
624
+ if (index === lastChunkIndex || index + 1 === markerIndex) {
625
+ if (markerIndex < 0) {
626
+ translatedChunks.push(this.createResumeTokenChunk(this.cursor));
627
+ }
628
+ continue;
629
+ }
630
+ if (cursorBeforeEvent !== undefined) {
631
+ translatedChunks.push(this.createResumeTokenChunk({ ...cursorBeforeEvent, skippedChunkCount: chunkCount }));
632
+ }
633
+ }
634
+ return translatedChunks;
635
+ }
636
+ createResumeTokenChunk(cursor) {
637
+ return {
638
+ type: agentResumeTokenDataPartType,
639
+ data: encodeAgentUIStreamCursor(cursor),
640
+ transient: true,
641
+ };
642
+ }
643
+ requireTurnId() {
644
+ if (this.cursor.turnId === null) {
645
+ throw new Error("the agent UI stream translated an event before it followed a turn");
646
+ }
647
+ return this.cursor.turnId;
648
+ }
649
+ }
650
+ function parseEventPayload(payload) {
651
+ try {
652
+ const value = JSON.parse(payload);
653
+ return typeof value === "object" && value !== null && !Array.isArray(value)
654
+ ? value
655
+ : undefined;
656
+ }
657
+ catch {
658
+ return undefined;
659
+ }
660
+ }
661
+ function readPayloadString(payload, key) {
662
+ const value = payload[key];
663
+ return typeof value === "string" ? value : undefined;
664
+ }
665
+ /** Parses tool arguments JSON text; text that is not JSON stays a string. */
666
+ export function parseToolArguments(toolArguments) {
667
+ if (toolArguments === null || toolArguments === undefined) {
668
+ return undefined;
669
+ }
670
+ try {
671
+ return JSON.parse(toolArguments);
672
+ }
673
+ catch {
674
+ return toolArguments;
675
+ }
676
+ }
677
+ /** Reports whether events of this Step type belong to a model call: `RunModel`, or unknown for old events. */
678
+ function isModelCallStepType(stepType) {
679
+ return stepType === modelCallStepType || stepType === "";
680
+ }
681
+ //# sourceMappingURL=agent-ui-message-stream.js.map