@superdurable/dex-agents 0.1.0 → 0.3.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.
- package/README.md +1608 -64
- package/dist/src/agent-built-in-tools.d.ts +81 -0
- package/dist/src/agent-built-in-tools.js +246 -0
- package/dist/src/agent-built-in-tools.js.map +1 -0
- package/dist/src/agent-context-compaction.d.ts +254 -0
- package/dist/src/agent-context-compaction.js +287 -0
- package/dist/src/agent-context-compaction.js.map +1 -0
- package/dist/src/agent-framework-runner.d.ts +456 -16
- package/dist/src/agent-framework-runner.js +4 -3
- package/dist/src/agent-framework-runner.js.map +1 -1
- package/dist/src/agent-plans.d.ts +48 -0
- package/dist/src/agent-plans.js +86 -0
- package/dist/src/agent-plans.js.map +1 -0
- package/dist/src/agent-session-cancellation-steps.d.ts +28 -0
- package/dist/src/agent-session-cancellation-steps.js +154 -0
- package/dist/src/agent-session-cancellation-steps.js.map +1 -0
- package/dist/src/agent-session-codecs.d.ts +65 -4
- package/dist/src/agent-session-codecs.js +343 -8
- package/dist/src/agent-session-codecs.js.map +1 -1
- package/dist/src/agent-session-failure.d.ts +58 -0
- package/dist/src/agent-session-failure.js +126 -0
- package/dist/src/agent-session-failure.js.map +1 -0
- package/dist/src/agent-session-flow-graph-cli.d.ts +2 -0
- package/dist/src/agent-session-flow-graph-cli.js +47 -0
- package/dist/src/agent-session-flow-graph-cli.js.map +1 -0
- package/dist/src/agent-session-flow-graph.d.ts +38 -0
- package/dist/src/agent-session-flow-graph.js +54 -0
- package/dist/src/agent-session-flow-graph.js.map +1 -0
- package/dist/src/agent-session-flow-graph.json +2187 -0
- package/dist/src/agent-session-flow.d.ts +317 -28
- package/dist/src/agent-session-flow.js +373 -21
- package/dist/src/agent-session-flow.js.map +1 -1
- package/dist/src/agent-session-persistence.d.ts +81 -4
- package/dist/src/agent-session-persistence.js +33 -2
- package/dist/src/agent-session-persistence.js.map +1 -1
- package/dist/src/agent-session-settings.d.ts +58 -0
- package/dist/src/agent-session-settings.js +81 -0
- package/dist/src/agent-session-settings.js.map +1 -0
- package/dist/src/agent-session-spend-limit.d.ts +156 -0
- package/dist/src/agent-session-spend-limit.js +42 -0
- package/dist/src/agent-session-spend-limit.js.map +1 -0
- package/dist/src/agent-session-start.d.ts +49 -0
- package/dist/src/agent-session-start.js +67 -0
- package/dist/src/agent-session-start.js.map +1 -0
- package/dist/src/agent-session-step-options.d.ts +78 -0
- package/dist/src/agent-session-step-options.js +71 -0
- package/dist/src/agent-session-step-options.js.map +1 -0
- package/dist/src/agent-session-step-types.d.ts +20 -0
- package/dist/src/agent-session-step-types.js +28 -0
- package/dist/src/agent-session-step-types.js.map +1 -0
- package/dist/src/agent-session-steps.d.ts +103 -8
- package/dist/src/agent-session-steps.js +997 -197
- package/dist/src/agent-session-steps.js.map +1 -1
- package/dist/src/agent-session-tracer.d.ts +61 -0
- package/dist/src/agent-session-tracer.js +608 -0
- package/dist/src/agent-session-tracer.js.map +1 -0
- package/dist/src/agent-session-transitions.d.ts +200 -26
- package/dist/src/agent-session-transitions.js +732 -32
- package/dist/src/agent-session-transitions.js.map +1 -1
- package/dist/src/agent-session-values.d.ts +696 -30
- package/dist/src/agent-session-values.js +39 -4
- package/dist/src/agent-session-values.js.map +1 -1
- package/dist/src/agent-session-wait-steps.d.ts +53 -0
- package/dist/src/agent-session-wait-steps.js +389 -0
- package/dist/src/agent-session-wait-steps.js.map +1 -0
- package/dist/src/agent-tracing-options.d.ts +107 -0
- package/dist/src/agent-tracing-options.js +20 -0
- package/dist/src/agent-tracing-options.js.map +1 -0
- package/dist/src/ai-sdk/ai-sdk-model-client.d.ts +40 -0
- package/dist/src/ai-sdk/ai-sdk-model-client.js +235 -0
- package/dist/src/ai-sdk/ai-sdk-model-client.js.map +1 -0
- package/dist/src/ai-sdk/index.d.ts +1 -0
- package/dist/src/ai-sdk/index.js +9 -0
- package/dist/src/ai-sdk/index.js.map +1 -0
- package/dist/src/index.d.ts +14 -3
- package/dist/src/index.js +12 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcp/index.d.ts +3 -0
- package/dist/src/mcp/index.js +10 -0
- package/dist/src/mcp/index.js.map +1 -0
- package/dist/src/mcp/mcp-resource-and-prompt-tools.d.ts +29 -0
- package/dist/src/mcp/mcp-resource-and-prompt-tools.js +185 -0
- package/dist/src/mcp/mcp-resource-and-prompt-tools.js.map +1 -0
- package/dist/src/mcp/mcp-server-config.d.ts +86 -0
- package/dist/src/mcp/mcp-server-config.js +38 -0
- package/dist/src/mcp/mcp-server-config.js.map +1 -0
- package/dist/src/mcp/mcp-tool-source.d.ts +470 -0
- package/dist/src/mcp/mcp-tool-source.js +1204 -0
- package/dist/src/mcp/mcp-tool-source.js.map +1 -0
- package/dist/src/stepwise/agent-definition.d.ts +166 -0
- package/dist/src/stepwise/agent-definition.js +9 -0
- package/dist/src/stepwise/agent-definition.js.map +1 -0
- package/dist/src/stepwise/function-tool.d.ts +130 -0
- package/dist/src/stepwise/function-tool.js +75 -0
- package/dist/src/stepwise/function-tool.js.map +1 -0
- package/dist/src/stepwise/index.d.ts +8 -0
- package/dist/src/stepwise/index.js +13 -0
- package/dist/src/stepwise/index.js.map +1 -0
- package/dist/src/stepwise/json-schema-validation.d.ts +9 -0
- package/dist/src/stepwise/json-schema-validation.js +141 -0
- package/dist/src/stepwise/json-schema-validation.js.map +1 -0
- package/dist/src/stepwise/model-client.d.ts +194 -0
- package/dist/src/stepwise/model-client.js +9 -0
- package/dist/src/stepwise/model-client.js.map +1 -0
- package/dist/src/stepwise/scripted-model-client.d.ts +99 -0
- package/dist/src/stepwise/scripted-model-client.js +156 -0
- package/dist/src/stepwise/scripted-model-client.js.map +1 -0
- package/dist/src/stepwise/stepwise-agent-runner.d.ts +171 -0
- package/dist/src/stepwise/stepwise-agent-runner.js +777 -0
- package/dist/src/stepwise/stepwise-agent-runner.js.map +1 -0
- package/dist/src/stepwise/stepwise-agent-session-flow.d.ts +35 -0
- package/dist/src/stepwise/stepwise-agent-session-flow.js +57 -0
- package/dist/src/stepwise/stepwise-agent-session-flow.js.map +1 -0
- package/dist/src/stepwise/stepwise-input-items.d.ts +12 -0
- package/dist/src/stepwise/stepwise-input-items.js +172 -0
- package/dist/src/stepwise/stepwise-input-items.js.map +1 -0
- package/dist/src/stepwise/tool-source.d.ts +103 -0
- package/dist/src/stepwise/tool-source.js +9 -0
- package/dist/src/stepwise/tool-source.js.map +1 -0
- package/dist/src/testing/agent-session-test-client.d.ts +65 -8
- package/dist/src/testing/agent-session-test-client.js +92 -22
- package/dist/src/testing/agent-session-test-client.js.map +1 -1
- package/dist/src/testing/context-window.d.ts +194 -0
- package/dist/src/testing/context-window.js +360 -0
- package/dist/src/testing/context-window.js.map +1 -0
- package/dist/src/testing/index.d.ts +2 -0
- package/dist/src/testing/index.js +2 -0
- package/dist/src/testing/index.js.map +1 -1
- package/dist/src/testing/private-dex-engine.d.ts +9 -0
- package/dist/src/testing/private-dex-engine.js +9 -1
- package/dist/src/testing/private-dex-engine.js.map +1 -1
- package/dist/src/testing/scripted-model-client.d.ts +1 -0
- package/dist/src/testing/scripted-model-client.js +10 -0
- package/dist/src/testing/scripted-model-client.js.map +1 -0
- package/dist/src/ui-stream/agent-event-json.d.ts +30 -0
- package/dist/src/ui-stream/agent-event-json.js +64 -0
- package/dist/src/ui-stream/agent-event-json.js.map +1 -0
- package/dist/src/ui-stream/agent-event-sse.d.ts +155 -0
- package/dist/src/ui-stream/agent-event-sse.js +167 -0
- package/dist/src/ui-stream/agent-event-sse.js.map +1 -0
- package/dist/src/ui-stream/agent-event-stream-positions.d.ts +42 -0
- package/dist/src/ui-stream/agent-event-stream-positions.js +153 -0
- package/dist/src/ui-stream/agent-event-stream-positions.js.map +1 -0
- package/dist/src/ui-stream/agent-session-record-reader.d.ts +74 -0
- package/dist/src/ui-stream/agent-session-record-reader.js +246 -0
- package/dist/src/ui-stream/agent-session-record-reader.js.map +1 -0
- package/dist/src/ui-stream/agent-ui-message-stream-responses.d.ts +219 -0
- package/dist/src/ui-stream/agent-ui-message-stream-responses.js +441 -0
- package/dist/src/ui-stream/agent-ui-message-stream-responses.js.map +1 -0
- package/dist/src/ui-stream/agent-ui-message-stream.d.ts +138 -0
- package/dist/src/ui-stream/agent-ui-message-stream.js +695 -0
- package/dist/src/ui-stream/agent-ui-message-stream.js.map +1 -0
- package/dist/src/ui-stream/agent-ui-message-types.d.ts +238 -0
- package/dist/src/ui-stream/agent-ui-message-types.js +57 -0
- package/dist/src/ui-stream/agent-ui-message-types.js.map +1 -0
- package/dist/src/ui-stream/agent-ui-messages.d.ts +68 -0
- package/dist/src/ui-stream/agent-ui-messages.js +222 -0
- package/dist/src/ui-stream/agent-ui-messages.js.map +1 -0
- package/dist/src/ui-stream/agent-ui-stream-cursor.d.ts +64 -0
- package/dist/src/ui-stream/agent-ui-stream-cursor.js +186 -0
- package/dist/src/ui-stream/agent-ui-stream-cursor.js.map +1 -0
- package/dist/src/ui-stream/index.d.ts +14 -0
- package/dist/src/ui-stream/index.js +22 -0
- package/dist/src/ui-stream/index.js.map +1 -0
- package/dist/src/ui-stream/server-sent-event-responses.d.ts +6 -0
- package/dist/src/ui-stream/server-sent-event-responses.js +34 -0
- package/dist/src/ui-stream/server-sent-event-responses.js.map +1 -0
- package/package.json +44 -5
|
@@ -0,0 +1,155 @@
|
|
|
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, AgentSnapshot } from "../agent-session-values.js";
|
|
5
|
+
/** HTTP headers of the plain `AgentEvents` Server-Sent Events response. */
|
|
6
|
+
export declare const agentEventStreamHeaders: Readonly<Record<string, string>>;
|
|
7
|
+
/**
|
|
8
|
+
* Selects the agent session and Stream position of a plain `AgentEvents` Server-Sent Events stream.
|
|
9
|
+
*
|
|
10
|
+
* Pass it to {@link streamAgentEventSseFrames} or {@link createAgentEventSseResponse}.
|
|
11
|
+
*/
|
|
12
|
+
export interface AgentEventSseOptions {
|
|
13
|
+
/** Client used for every Stream read. The caller keeps ownership. */
|
|
14
|
+
readonly client: Client;
|
|
15
|
+
/** The agent session Flow, the same object the Worker registered. */
|
|
16
|
+
readonly flow: AgentSessionFlow<AgentFrameworkRunner<DeferredToolOutcome>>;
|
|
17
|
+
/** Session ID: the Flow ID of the agent session. */
|
|
18
|
+
readonly flowId: string;
|
|
19
|
+
/**
|
|
20
|
+
* Dex resume token of the last event the browser received: the `Last-Event-ID` request header, or the
|
|
21
|
+
* `resumeToken` of {@link readAgentEventStreamSnapshot} on the first connection. Omit it, or pass `""`,
|
|
22
|
+
* to start at the oldest retained event.
|
|
23
|
+
*/
|
|
24
|
+
readonly resumeToken?: string | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Long-poll duration of one Stream read, in milliseconds. It must be a whole number of seconds. The
|
|
27
|
+
* stream sends a keep-alive comment after each empty poll. Defaults to 10,000.
|
|
28
|
+
*/
|
|
29
|
+
readonly pollTimeoutMs?: number;
|
|
30
|
+
/** Ends the stream before its next Stream read once aborted, such as the request's `signal`. */
|
|
31
|
+
readonly signal?: AbortSignal | undefined;
|
|
32
|
+
}
|
|
33
|
+
/** A session snapshot and the Dex resume token that a plain event stream opened with it starts from. */
|
|
34
|
+
export interface AgentEventStreamSnapshot {
|
|
35
|
+
/** Output of the `get_agent_snapshot` RPC. */
|
|
36
|
+
readonly snapshot: AgentSnapshot;
|
|
37
|
+
/**
|
|
38
|
+
* Dex resume token to open the event stream with. While a turn is active it replays that turn's events
|
|
39
|
+
* from its first retained event; otherwise it starts after the newest event read before the snapshot.
|
|
40
|
+
*/
|
|
41
|
+
readonly resumeToken: string;
|
|
42
|
+
}
|
|
43
|
+
/** Snake_case wire JSON of an {@link AgentEventStreamSnapshot}, the same JSON as the Python core. */
|
|
44
|
+
export interface AgentEventStreamSnapshotJson {
|
|
45
|
+
/** The `AgentSnapshot` wire JSON. */
|
|
46
|
+
readonly snapshot: Readonly<Record<string, unknown>>;
|
|
47
|
+
/** Dex resume token to open the event stream with. */
|
|
48
|
+
readonly resume_token: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Formats one `AgentEvents` message as a Server-Sent Event whose `id` is its Dex resume token.
|
|
52
|
+
*
|
|
53
|
+
* An `EventSource` sends the `id` back as the `Last-Event-ID` header when it reconnects. The event has no
|
|
54
|
+
* `event:` field, so `EventSource.onmessage` receives every event; switch on `type` in the data.
|
|
55
|
+
*
|
|
56
|
+
* @param message - Message read from the `AgentEvents` Stream.
|
|
57
|
+
* @returns `id: <resume token>`, then `data: <AgentEvent wire JSON>`, then a blank line.
|
|
58
|
+
*/
|
|
59
|
+
export declare function formatAgentEventSseFrame(message: StreamMessage<AgentEvent>): string;
|
|
60
|
+
/**
|
|
61
|
+
* Yields an agent session's `AgentEvents` as Server-Sent Event frames, resuming after a Dex resume token.
|
|
62
|
+
*
|
|
63
|
+
* Each event becomes one frame whose `id` is its Dex resume token and whose data is the `AgentEvent` wire
|
|
64
|
+
* JSON: `type`, `turn_id`, `attempt`, `step_execution_id`, and `payload` text. Reconnecting with the last
|
|
65
|
+
* `id` continues with the next event, with no repeated or missing event while the Stream retains them.
|
|
66
|
+
* After each empty long poll it yields a keep-alive comment. The stream ends on its own only once the
|
|
67
|
+
* session's Flow failed and every event was sent; otherwise stop iterating, or abort `signal`, when the
|
|
68
|
+
* browser goes away.
|
|
69
|
+
*
|
|
70
|
+
* The browser applies the event rules of the session contract. Within one `step_execution_id` it keeps
|
|
71
|
+
* only the highest `attempt` and drops the partial text of lower attempts. After `turn_completed`,
|
|
72
|
+
* `turn_failed`, `turn_steered`, or `turn_cancelled` it replaces the turn's live text with the stored
|
|
73
|
+
* messages. On every connection, load
|
|
74
|
+
* {@link readAgentEventStreamSnapshot} first and open the stream from its `resumeToken`.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```ts
|
|
78
|
+
* for await (const frame of streamAgentEventSseFrames({
|
|
79
|
+
* client, flow, flowId: sessionId, resumeToken: request.headers["last-event-id"] })) {
|
|
80
|
+
* response.write(frame);
|
|
81
|
+
* }
|
|
82
|
+
* ```
|
|
83
|
+
* @param options - Session, start position, and polling.
|
|
84
|
+
* @returns An async generator of SSE frames and keep-alive comments.
|
|
85
|
+
* @throws {RangeError} When `pollTimeoutMs` is not a positive whole number of seconds.
|
|
86
|
+
* @throws {DexServiceError} When a Stream read fails.
|
|
87
|
+
*/
|
|
88
|
+
export declare function streamAgentEventSseFrames(options: AgentEventSseOptions): AsyncGenerator<string, void, undefined>;
|
|
89
|
+
/**
|
|
90
|
+
* Returns a streaming `text/event-stream` Response of an agent session's `AgentEvents`.
|
|
91
|
+
*
|
|
92
|
+
* It writes {@link streamAgentEventSseFrames} and stops reading when the browser closes the response.
|
|
93
|
+
* Mount it in any server that returns a Fetch API `Response`. The session ID usually comes from the
|
|
94
|
+
* browser, so the route must check that the caller may read that session.
|
|
95
|
+
*
|
|
96
|
+
* Before it answers 200, it reads once, with a long poll of up to 1 second, to check the resume token.
|
|
97
|
+
* When Dex rejects the token, such as one of another session, it answers 400, which stops an
|
|
98
|
+
* `EventSource` from reconnecting with it; the browser then loads the snapshot again. A token from
|
|
99
|
+
* before a Stream Store restart is accepted and continues at the oldest retained event.
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```ts
|
|
103
|
+
* // GET /sessions/:id/events
|
|
104
|
+
* export async function GET(request: Request, sessionId: string): Promise<Response> {
|
|
105
|
+
* const url = new URL(request.url);
|
|
106
|
+
* return createAgentEventSseResponse({
|
|
107
|
+
* client, flow, flowId: sessionId,
|
|
108
|
+
* resumeToken: request.headers.get("last-event-id") ?? url.searchParams.get("resumeToken") ?? "",
|
|
109
|
+
* signal: request.signal,
|
|
110
|
+
* });
|
|
111
|
+
* }
|
|
112
|
+
* // In the browser:
|
|
113
|
+
* const { resumeToken } = await (await fetch(`/sessions/${sessionId}/snapshot`)).json();
|
|
114
|
+
* const events = new EventSource(`/sessions/${sessionId}/events?resumeToken=${encodeURIComponent(resumeToken)}`);
|
|
115
|
+
* events.onmessage = (message) => render(JSON.parse(message.data));
|
|
116
|
+
* ```
|
|
117
|
+
* @param options - Session, start position, polling, and extra response headers.
|
|
118
|
+
* @returns A 200 Response with {@link agentEventStreamHeaders} and the application's `headers`; 400 with
|
|
119
|
+
* a text body when Dex rejects the resume token.
|
|
120
|
+
* @throws {RangeError} When `pollTimeoutMs` is not a positive whole number of seconds.
|
|
121
|
+
* @throws {DexServiceError} When the first Stream read fails for another reason.
|
|
122
|
+
*/
|
|
123
|
+
export declare function createAgentEventSseResponse(options: AgentEventSseOptions & {
|
|
124
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
125
|
+
}): Promise<Response>;
|
|
126
|
+
/**
|
|
127
|
+
* Reads the session snapshot together with the Dex resume token to open the plain event stream from.
|
|
128
|
+
*
|
|
129
|
+
* Call it whenever the browser connects or reconnects after a gap. It reads the newest event first and
|
|
130
|
+
* the snapshot second, so events after the token may repeat what the snapshot shows but none is missing.
|
|
131
|
+
* While a turn is active the token replays that turn's events, so the browser can rebuild its live text.
|
|
132
|
+
* The `get_agent_snapshot` RPC needs a running Worker.
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```ts
|
|
136
|
+
* // GET /sessions/:id/snapshot
|
|
137
|
+
* const value = await readAgentEventStreamSnapshot({ client, flow, flowId: sessionId });
|
|
138
|
+
* return Response.json(convertAgentEventStreamSnapshotToJson(value));
|
|
139
|
+
* ```
|
|
140
|
+
* @param options - Client, Flow, and session ID.
|
|
141
|
+
* @returns The snapshot and the resume token.
|
|
142
|
+
* @throws {DexServiceError} When the Stream read or the RPC fails.
|
|
143
|
+
*/
|
|
144
|
+
export declare function readAgentEventStreamSnapshot(options: {
|
|
145
|
+
readonly client: Client;
|
|
146
|
+
readonly flow: AgentSessionFlow<AgentFrameworkRunner<DeferredToolOutcome>>;
|
|
147
|
+
readonly flowId: string;
|
|
148
|
+
}): Promise<AgentEventStreamSnapshot>;
|
|
149
|
+
/**
|
|
150
|
+
* Converts an {@link AgentEventStreamSnapshot} to snake_case wire JSON for an HTTP response.
|
|
151
|
+
*
|
|
152
|
+
* @param value - Snapshot and resume token.
|
|
153
|
+
* @returns `{ snapshot, resume_token }`, with the snapshot in its `AgentSnapshot` wire format.
|
|
154
|
+
*/
|
|
155
|
+
export declare function convertAgentEventStreamSnapshotToJson(value: AgentEventStreamSnapshot): AgentEventStreamSnapshotJson;
|
|
@@ -0,0 +1,167 @@
|
|
|
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 { readAgentSnapshot, readSessionFailure } from "../agent-session-failure.js";
|
|
9
|
+
import { convertAgentEventToJson, convertAgentSnapshotToJson } from "./agent-event-json.js";
|
|
10
|
+
import { findAgentEventResumeTokenBeforeTurn, probeAgentEventResumeToken, readAgentEventsTailResumeToken, readNextAgentEvent, requireAgentEventPollTimeoutMs, } from "./agent-event-stream-positions.js";
|
|
11
|
+
import { createServerSentEventResponse, mergeHeaders, serverSentEventKeepAliveFrame, } from "./server-sent-event-responses.js";
|
|
12
|
+
/** HTTP headers of the plain `AgentEvents` Server-Sent Events response. */
|
|
13
|
+
export const agentEventStreamHeaders = Object.freeze({
|
|
14
|
+
"content-type": "text/event-stream",
|
|
15
|
+
"cache-control": "no-cache",
|
|
16
|
+
connection: "keep-alive",
|
|
17
|
+
"x-accel-buffering": "no",
|
|
18
|
+
});
|
|
19
|
+
/**
|
|
20
|
+
* Formats one `AgentEvents` message as a Server-Sent Event whose `id` is its Dex resume token.
|
|
21
|
+
*
|
|
22
|
+
* An `EventSource` sends the `id` back as the `Last-Event-ID` header when it reconnects. The event has no
|
|
23
|
+
* `event:` field, so `EventSource.onmessage` receives every event; switch on `type` in the data.
|
|
24
|
+
*
|
|
25
|
+
* @param message - Message read from the `AgentEvents` Stream.
|
|
26
|
+
* @returns `id: <resume token>`, then `data: <AgentEvent wire JSON>`, then a blank line.
|
|
27
|
+
*/
|
|
28
|
+
export function formatAgentEventSseFrame(message) {
|
|
29
|
+
return `id: ${message.resumeToken}\ndata: ${JSON.stringify(convertAgentEventToJson(message.value))}\n\n`;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Yields an agent session's `AgentEvents` as Server-Sent Event frames, resuming after a Dex resume token.
|
|
33
|
+
*
|
|
34
|
+
* Each event becomes one frame whose `id` is its Dex resume token and whose data is the `AgentEvent` wire
|
|
35
|
+
* JSON: `type`, `turn_id`, `attempt`, `step_execution_id`, and `payload` text. Reconnecting with the last
|
|
36
|
+
* `id` continues with the next event, with no repeated or missing event while the Stream retains them.
|
|
37
|
+
* After each empty long poll it yields a keep-alive comment. The stream ends on its own only once the
|
|
38
|
+
* session's Flow failed and every event was sent; otherwise stop iterating, or abort `signal`, when the
|
|
39
|
+
* browser goes away.
|
|
40
|
+
*
|
|
41
|
+
* The browser applies the event rules of the session contract. Within one `step_execution_id` it keeps
|
|
42
|
+
* only the highest `attempt` and drops the partial text of lower attempts. After `turn_completed`,
|
|
43
|
+
* `turn_failed`, `turn_steered`, or `turn_cancelled` it replaces the turn's live text with the stored
|
|
44
|
+
* messages. On every connection, load
|
|
45
|
+
* {@link readAgentEventStreamSnapshot} first and open the stream from its `resumeToken`.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* for await (const frame of streamAgentEventSseFrames({
|
|
50
|
+
* client, flow, flowId: sessionId, resumeToken: request.headers["last-event-id"] })) {
|
|
51
|
+
* response.write(frame);
|
|
52
|
+
* }
|
|
53
|
+
* ```
|
|
54
|
+
* @param options - Session, start position, and polling.
|
|
55
|
+
* @returns An async generator of SSE frames and keep-alive comments.
|
|
56
|
+
* @throws {RangeError} When `pollTimeoutMs` is not a positive whole number of seconds.
|
|
57
|
+
* @throws {DexServiceError} When a Stream read fails.
|
|
58
|
+
*/
|
|
59
|
+
export async function* streamAgentEventSseFrames(options) {
|
|
60
|
+
yield* formatAgentEventSseFramesFrom(options, undefined);
|
|
61
|
+
}
|
|
62
|
+
async function* formatAgentEventSseFramesFrom(options, firstMessage) {
|
|
63
|
+
const pollTimeoutMs = requireAgentEventPollTimeoutMs(options.pollTimeoutMs);
|
|
64
|
+
let eventResumeToken = options.resumeToken ?? "";
|
|
65
|
+
let pendingMessage = firstMessage;
|
|
66
|
+
let hasFlowFailed = false;
|
|
67
|
+
while (options.signal?.aborted !== true) {
|
|
68
|
+
const message = pendingMessage ??
|
|
69
|
+
(await readNextAgentEvent(options.client, options.flow, options.flowId, eventResumeToken, pollTimeoutMs));
|
|
70
|
+
pendingMessage = undefined;
|
|
71
|
+
if (message === undefined) {
|
|
72
|
+
// A poll that started after the Flow failed saw every event there will be.
|
|
73
|
+
if (hasFlowFailed) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
hasFlowFailed = (await readSessionFailure(options.client, options.flowId)) !== undefined;
|
|
77
|
+
yield serverSentEventKeepAliveFrame;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
eventResumeToken = message.resumeToken;
|
|
81
|
+
yield formatAgentEventSseFrame(message);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Returns a streaming `text/event-stream` Response of an agent session's `AgentEvents`.
|
|
86
|
+
*
|
|
87
|
+
* It writes {@link streamAgentEventSseFrames} and stops reading when the browser closes the response.
|
|
88
|
+
* Mount it in any server that returns a Fetch API `Response`. The session ID usually comes from the
|
|
89
|
+
* browser, so the route must check that the caller may read that session.
|
|
90
|
+
*
|
|
91
|
+
* Before it answers 200, it reads once, with a long poll of up to 1 second, to check the resume token.
|
|
92
|
+
* When Dex rejects the token, such as one of another session, it answers 400, which stops an
|
|
93
|
+
* `EventSource` from reconnecting with it; the browser then loads the snapshot again. A token from
|
|
94
|
+
* before a Stream Store restart is accepted and continues at the oldest retained event.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```ts
|
|
98
|
+
* // GET /sessions/:id/events
|
|
99
|
+
* export async function GET(request: Request, sessionId: string): Promise<Response> {
|
|
100
|
+
* const url = new URL(request.url);
|
|
101
|
+
* return createAgentEventSseResponse({
|
|
102
|
+
* client, flow, flowId: sessionId,
|
|
103
|
+
* resumeToken: request.headers.get("last-event-id") ?? url.searchParams.get("resumeToken") ?? "",
|
|
104
|
+
* signal: request.signal,
|
|
105
|
+
* });
|
|
106
|
+
* }
|
|
107
|
+
* // In the browser:
|
|
108
|
+
* const { resumeToken } = await (await fetch(`/sessions/${sessionId}/snapshot`)).json();
|
|
109
|
+
* const events = new EventSource(`/sessions/${sessionId}/events?resumeToken=${encodeURIComponent(resumeToken)}`);
|
|
110
|
+
* events.onmessage = (message) => render(JSON.parse(message.data));
|
|
111
|
+
* ```
|
|
112
|
+
* @param options - Session, start position, polling, and extra response headers.
|
|
113
|
+
* @returns A 200 Response with {@link agentEventStreamHeaders} and the application's `headers`; 400 with
|
|
114
|
+
* a text body when Dex rejects the resume token.
|
|
115
|
+
* @throws {RangeError} When `pollTimeoutMs` is not a positive whole number of seconds.
|
|
116
|
+
* @throws {DexServiceError} When the first Stream read fails for another reason.
|
|
117
|
+
*/
|
|
118
|
+
export async function createAgentEventSseResponse(options) {
|
|
119
|
+
requireAgentEventPollTimeoutMs(options.pollTimeoutMs);
|
|
120
|
+
const probe = await probeAgentEventResumeToken(options.client, options.flow, options.flowId, options.resumeToken ?? "");
|
|
121
|
+
if (!probe.isAccepted) {
|
|
122
|
+
return new Response("the resume token does not belong to this session; load the snapshot again", {
|
|
123
|
+
status: 400,
|
|
124
|
+
headers: mergeHeaders({ "content-type": "text/plain; charset=utf-8" }, options.headers),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return createServerSentEventResponse(formatAgentEventSseFramesFrom(options, probe.firstMessage), agentEventStreamHeaders, options.headers);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Reads the session snapshot together with the Dex resume token to open the plain event stream from.
|
|
131
|
+
*
|
|
132
|
+
* Call it whenever the browser connects or reconnects after a gap. It reads the newest event first and
|
|
133
|
+
* the snapshot second, so events after the token may repeat what the snapshot shows but none is missing.
|
|
134
|
+
* While a turn is active the token replays that turn's events, so the browser can rebuild its live text.
|
|
135
|
+
* The `get_agent_snapshot` RPC needs a running Worker.
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* ```ts
|
|
139
|
+
* // GET /sessions/:id/snapshot
|
|
140
|
+
* const value = await readAgentEventStreamSnapshot({ client, flow, flowId: sessionId });
|
|
141
|
+
* return Response.json(convertAgentEventStreamSnapshotToJson(value));
|
|
142
|
+
* ```
|
|
143
|
+
* @param options - Client, Flow, and session ID.
|
|
144
|
+
* @returns The snapshot and the resume token.
|
|
145
|
+
* @throws {DexServiceError} When the Stream read or the RPC fails.
|
|
146
|
+
*/
|
|
147
|
+
export async function readAgentEventStreamSnapshot(options) {
|
|
148
|
+
const { client, flow, flowId } = options;
|
|
149
|
+
const tailResumeToken = await readAgentEventsTailResumeToken(client, flow, flowId);
|
|
150
|
+
const snapshot = await readAgentSnapshot(client, flow, flowId);
|
|
151
|
+
const activeTurnId = snapshot.state?.activeTurnId ?? null;
|
|
152
|
+
if (activeTurnId === null) {
|
|
153
|
+
return { snapshot, resumeToken: tailResumeToken };
|
|
154
|
+
}
|
|
155
|
+
const turnResumeToken = await findAgentEventResumeTokenBeforeTurn(client, flow, flowId, activeTurnId);
|
|
156
|
+
return { snapshot, resumeToken: turnResumeToken ?? tailResumeToken };
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Converts an {@link AgentEventStreamSnapshot} to snake_case wire JSON for an HTTP response.
|
|
160
|
+
*
|
|
161
|
+
* @param value - Snapshot and resume token.
|
|
162
|
+
* @returns `{ snapshot, resume_token }`, with the snapshot in its `AgentSnapshot` wire format.
|
|
163
|
+
*/
|
|
164
|
+
export function convertAgentEventStreamSnapshotToJson(value) {
|
|
165
|
+
return { snapshot: convertAgentSnapshotToJson(value.snapshot), resume_token: value.resumeToken };
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=agent-event-sse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-event-sse.js","sourceRoot":"","sources":["../../../src/ui-stream/agent-event-sse.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type Client, type 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
|
+
/** Default long-poll duration of one `AgentEvents` read: 10 seconds. */
|
|
6
|
+
export declare const defaultAgentEventPollTimeoutMs = 10000;
|
|
7
|
+
/** Long poll of the read that checks a resume token before a response commits to status 200. */
|
|
8
|
+
export declare const resumeTokenProbeTimeoutMs = 1000;
|
|
9
|
+
/** Validates a long-poll duration; Dex Stream reads take whole seconds. */
|
|
10
|
+
export declare function requireAgentEventPollTimeoutMs(pollTimeoutMs: number | undefined): number;
|
|
11
|
+
/**
|
|
12
|
+
* Reads the next `AgentEvents` message, or `undefined` when the long poll ends without one.
|
|
13
|
+
* A disabled Stream Store holds no events, so the read waits as an empty poll would.
|
|
14
|
+
*/
|
|
15
|
+
export declare function readNextAgentEvent(client: Client, flow: AgentSessionFlow<AgentFrameworkRunner<DeferredToolOutcome>>, flowId: string, eventResumeToken: string, pollTimeoutMs: number): Promise<StreamMessage<AgentEvent> | undefined>;
|
|
16
|
+
/** Reports whether Dex rejected a Stream resume token, such as one of another session. */
|
|
17
|
+
export declare function isStreamResumeTokenRejectedError(error: unknown): boolean;
|
|
18
|
+
/** Reports whether a Stream call failed because the Server's Stream Store is disabled. */
|
|
19
|
+
export declare function isStreamStoreDisabledError(error: unknown): boolean;
|
|
20
|
+
/** The first read after a resume token, done before a response commits to its status. */
|
|
21
|
+
export type AgentEventResumeTokenProbe = {
|
|
22
|
+
readonly isAccepted: true;
|
|
23
|
+
readonly firstMessage: StreamMessage<AgentEvent> | undefined;
|
|
24
|
+
} | {
|
|
25
|
+
readonly isAccepted: false;
|
|
26
|
+
};
|
|
27
|
+
/** Reads once with a short long poll to learn whether Dex accepts a resume token. */
|
|
28
|
+
export declare function probeAgentEventResumeToken(client: Client, flow: AgentSessionFlow<AgentFrameworkRunner<DeferredToolOutcome>>, flowId: string, eventResumeToken: string): Promise<AgentEventResumeTokenProbe>;
|
|
29
|
+
/** Returns the resume token of the newest retained event, or `""` when the Stream holds none. */
|
|
30
|
+
export declare function readAgentEventsTailResumeToken(client: Client, flow: AgentSessionFlow<AgentFrameworkRunner<DeferredToolOutcome>>, flowId: string): Promise<string>;
|
|
31
|
+
/**
|
|
32
|
+
* Finds the position just before a turn's first retained event.
|
|
33
|
+
*
|
|
34
|
+
* @returns The resume token whose next event is the turn's `turn_started`, or the turn's oldest retained
|
|
35
|
+
* event; `""` when that event is the oldest retained one; `undefined` when the Stream holds no event of
|
|
36
|
+
* the turn or the search reached its page limit.
|
|
37
|
+
*/
|
|
38
|
+
export declare function findAgentEventResumeTokenBeforeTurn(client: Client, flow: AgentSessionFlow<AgentFrameworkRunner<DeferredToolOutcome>>, flowId: string, turnId: string): Promise<string | undefined>;
|
|
39
|
+
/** Returns the number `n` of a `turn-<n>` turn ID, or `undefined` for another format. */
|
|
40
|
+
export declare function parseTurnNumber(turnId: string): number | undefined;
|
|
41
|
+
/** Returns the Step type of a `StepType-Number` Step execution ID, or `""` when it has no number. */
|
|
42
|
+
export declare function readStepType(stepExecutionId: string): string;
|
|
@@ -0,0 +1,153 @@
|
|
|
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 { DexServiceError, LongPollTimeoutError } from "@superdurable/dex";
|
|
10
|
+
/** Default long-poll duration of one `AgentEvents` read: 10 seconds. */
|
|
11
|
+
export const defaultAgentEventPollTimeoutMs = 10_000;
|
|
12
|
+
/** Long poll of the read that checks a resume token before a response commits to status 200. */
|
|
13
|
+
export const resumeTokenProbeTimeoutMs = 1_000;
|
|
14
|
+
const grpcInvalidArgument = 3;
|
|
15
|
+
const grpcFailedPrecondition = 9;
|
|
16
|
+
const turnStartSearchPageSize = 500;
|
|
17
|
+
const turnStartSearchPageLimit = 20;
|
|
18
|
+
const turnIdPattern = /^turn-(\d+)$/;
|
|
19
|
+
/** Validates a long-poll duration; Dex Stream reads take whole seconds. */
|
|
20
|
+
export function requireAgentEventPollTimeoutMs(pollTimeoutMs) {
|
|
21
|
+
const timeoutMs = pollTimeoutMs ?? defaultAgentEventPollTimeoutMs;
|
|
22
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1_000 || timeoutMs % 1_000 !== 0) {
|
|
23
|
+
throw new RangeError("pollTimeoutMs must be a positive whole number of seconds, in milliseconds");
|
|
24
|
+
}
|
|
25
|
+
return timeoutMs;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Reads the next `AgentEvents` message, or `undefined` when the long poll ends without one.
|
|
29
|
+
* A disabled Stream Store holds no events, so the read waits as an empty poll would.
|
|
30
|
+
*/
|
|
31
|
+
export async function readNextAgentEvent(client, flow, flowId, eventResumeToken, pollTimeoutMs) {
|
|
32
|
+
try {
|
|
33
|
+
return await client.readStream(flowId, flow.agentEvents, eventResumeToken, pollTimeoutMs);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
if (error instanceof LongPollTimeoutError) {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
if (isStreamStoreDisabledError(error)) {
|
|
40
|
+
await delay(Math.min(pollTimeoutMs, resumeTokenProbeTimeoutMs));
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** Reports whether Dex rejected a Stream resume token, such as one of another session. */
|
|
47
|
+
export function isStreamResumeTokenRejectedError(error) {
|
|
48
|
+
return error instanceof DexServiceError && Number(error.code) === grpcInvalidArgument;
|
|
49
|
+
}
|
|
50
|
+
/** Reports whether a Stream call failed because the Server's Stream Store is disabled. */
|
|
51
|
+
export function isStreamStoreDisabledError(error) {
|
|
52
|
+
return error instanceof DexServiceError && Number(error.code) === grpcFailedPrecondition;
|
|
53
|
+
}
|
|
54
|
+
/** Reads once with a short long poll to learn whether Dex accepts a resume token. */
|
|
55
|
+
export async function probeAgentEventResumeToken(client, flow, flowId, eventResumeToken) {
|
|
56
|
+
try {
|
|
57
|
+
const firstMessage = await readNextAgentEvent(client, flow, flowId, eventResumeToken, resumeTokenProbeTimeoutMs);
|
|
58
|
+
return { isAccepted: true, firstMessage };
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
if (isStreamResumeTokenRejectedError(error)) {
|
|
62
|
+
return { isAccepted: false };
|
|
63
|
+
}
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/** Returns the resume token of the newest retained event, or `""` when the Stream holds none. */
|
|
68
|
+
export async function readAgentEventsTailResumeToken(client, flow, flowId) {
|
|
69
|
+
try {
|
|
70
|
+
const page = await client.listStreamMessages(flowId, flow.agentEvents, 1);
|
|
71
|
+
return page.messages[0]?.resumeToken ?? "";
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
if (isStreamStoreDisabledError(error)) {
|
|
75
|
+
return "";
|
|
76
|
+
}
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Finds the position just before a turn's first retained event.
|
|
82
|
+
*
|
|
83
|
+
* @returns The resume token whose next event is the turn's `turn_started`, or the turn's oldest retained
|
|
84
|
+
* event; `""` when that event is the oldest retained one; `undefined` when the Stream holds no event of
|
|
85
|
+
* the turn or the search reached its page limit.
|
|
86
|
+
*/
|
|
87
|
+
export async function findAgentEventResumeTokenBeforeTurn(client, flow, flowId, turnId) {
|
|
88
|
+
try {
|
|
89
|
+
return await searchAgentEventResumeTokenBeforeTurn(client, flow, flowId, turnId);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
if (isStreamStoreDisabledError(error)) {
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async function searchAgentEventResumeTokenBeforeTurn(client, flow, flowId, turnId) {
|
|
99
|
+
const turnNumber = parseTurnNumber(turnId);
|
|
100
|
+
let hasTurnEvent = false;
|
|
101
|
+
let pageToken = "";
|
|
102
|
+
for (let pageIndex = 0; pageIndex < turnStartSearchPageLimit; pageIndex += 1) {
|
|
103
|
+
const page = await client.listStreamMessages(flowId, flow.agentEvents, turnStartSearchPageSize, pageToken);
|
|
104
|
+
for (const [index, message] of page.messages.entries()) {
|
|
105
|
+
const event = message.value;
|
|
106
|
+
if (event.turnId === turnId) {
|
|
107
|
+
hasTurnEvent = true;
|
|
108
|
+
if (event.type !== "turn_started") {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
else if (!isEarlierTurn(event.turnId, turnNumber)) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
// The events of the turn are all newer than this message, or this message is turn_started.
|
|
116
|
+
const olderMessage = event.turnId === turnId ? page.messages[index + 1] : message;
|
|
117
|
+
if (olderMessage !== undefined) {
|
|
118
|
+
return hasTurnEvent ? olderMessage.resumeToken : undefined;
|
|
119
|
+
}
|
|
120
|
+
return readResumeTokenOlderThanPage(client, flow, flowId, page.nextPageToken);
|
|
121
|
+
}
|
|
122
|
+
if (page.nextPageToken === "") {
|
|
123
|
+
return hasTurnEvent ? "" : undefined;
|
|
124
|
+
}
|
|
125
|
+
pageToken = page.nextPageToken;
|
|
126
|
+
}
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
/** Returns the number `n` of a `turn-<n>` turn ID, or `undefined` for another format. */
|
|
130
|
+
export function parseTurnNumber(turnId) {
|
|
131
|
+
const match = turnIdPattern.exec(turnId);
|
|
132
|
+
return match?.[1] === undefined ? undefined : Number(match[1]);
|
|
133
|
+
}
|
|
134
|
+
/** Returns the Step type of a `StepType-Number` Step execution ID, or `""` when it has no number. */
|
|
135
|
+
export function readStepType(stepExecutionId) {
|
|
136
|
+
const separatorIndex = stepExecutionId.lastIndexOf("-");
|
|
137
|
+
return separatorIndex <= 0 ? "" : stepExecutionId.slice(0, separatorIndex);
|
|
138
|
+
}
|
|
139
|
+
async function readResumeTokenOlderThanPage(client, flow, flowId, nextPageToken) {
|
|
140
|
+
if (nextPageToken === "") {
|
|
141
|
+
return "";
|
|
142
|
+
}
|
|
143
|
+
const olderPage = await client.listStreamMessages(flowId, flow.agentEvents, 1, nextPageToken);
|
|
144
|
+
return olderPage.messages[0]?.resumeToken ?? "";
|
|
145
|
+
}
|
|
146
|
+
function isEarlierTurn(turnId, turnNumber) {
|
|
147
|
+
if (turnId === null || turnNumber === undefined) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
const otherTurnNumber = parseTurnNumber(turnId);
|
|
151
|
+
return otherTurnNumber !== undefined && otherTurnNumber < turnNumber;
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=agent-event-stream-positions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-event-stream-positions.js","sourceRoot":"","sources":["../../../src/ui-stream/agent-event-stream-positions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { type Client } 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 { AgentState, AgentToolCallRecord } from "../agent-session-values.js";
|
|
5
|
+
/** Reads committed session state for the UI streams; Attribute reads need no running Worker. */
|
|
6
|
+
export declare class AgentSessionRecordReader {
|
|
7
|
+
private readonly client;
|
|
8
|
+
private readonly flow;
|
|
9
|
+
private readonly flowId;
|
|
10
|
+
constructor(client: Client, flow: AgentSessionFlow<AgentFrameworkRunner<DeferredToolOutcome>>, flowId: string);
|
|
11
|
+
readAgentState(): Promise<AgentState | undefined>;
|
|
12
|
+
/**
|
|
13
|
+
* Waits until a turn's end committed, because its end event reaches the Stream before the commit.
|
|
14
|
+
* @returns `true` once the turn is no longer active; `false` after 3 seconds without that commit.
|
|
15
|
+
*/
|
|
16
|
+
waitForTurnToEnd(turnId: string): Promise<boolean>;
|
|
17
|
+
/**
|
|
18
|
+
* Reads the IDs of the tool calls whose approval `resolve_interrupt` still accepts.
|
|
19
|
+
* @returns The tool call IDs of the pending `tool_approval` interrupts.
|
|
20
|
+
*/
|
|
21
|
+
readPendingApprovalToolCallIds(): Promise<ReadonlySet<string>>;
|
|
22
|
+
isFlowRunning(): Promise<boolean>;
|
|
23
|
+
/** Returns why the session's Flow failed, or `undefined` when it did not. */
|
|
24
|
+
readFlowFailure(): Promise<string | undefined>;
|
|
25
|
+
/**
|
|
26
|
+
* Reads what the session holds about one tool call of a turn.
|
|
27
|
+
*
|
|
28
|
+
* Events reach the Stream before their Step commits, so it polls the record until `isReady` holds, for
|
|
29
|
+
* up to 3 seconds. A turn that ended deleted its records; then it reads the turn's stored messages.
|
|
30
|
+
* @returns The record when it exists, plus the tool name, arguments, and stored result text from the
|
|
31
|
+
* record or the stored messages; each is `undefined` when neither holds it.
|
|
32
|
+
*/
|
|
33
|
+
readToolCallDetails(turnId: string, toolCallId: string, isReady: (toolCall: AgentToolCallRecord) => boolean): Promise<ToolCallDetails>;
|
|
34
|
+
private waitForToolCall;
|
|
35
|
+
/**
|
|
36
|
+
* Waits up to 3 seconds for a tool call record to be committed, because events precede their commit.
|
|
37
|
+
* @returns The record, or `undefined` when it never appeared or the turn is no longer active.
|
|
38
|
+
*/
|
|
39
|
+
readCommittedToolCall(turnId: string, toolCallId: string): Promise<AgentToolCallRecord | undefined>;
|
|
40
|
+
/**
|
|
41
|
+
* Reads one tool call record once, without waiting for a commit.
|
|
42
|
+
* @returns The record when it exists and belongs to `turnId`, otherwise `undefined`.
|
|
43
|
+
*/
|
|
44
|
+
readToolCall(turnId: string, toolCallId: string): Promise<AgentToolCallRecord | undefined>;
|
|
45
|
+
/**
|
|
46
|
+
* Reads the stored tool results among the last 200 stored messages of any turn.
|
|
47
|
+
* @returns The results in sequence order, each with its turn, tool call ID, and the text the model saw.
|
|
48
|
+
*/
|
|
49
|
+
readRecentStoredToolResults(): Promise<readonly StoredToolResult[]>;
|
|
50
|
+
/** Finds a tool call's stored request and result messages, newest pages first, within its turn. */
|
|
51
|
+
private readStoredToolMessages;
|
|
52
|
+
/**
|
|
53
|
+
* Reads stored messages with sequences in `[startSequence, endSequence)`, in sequence order, from the
|
|
54
|
+
* `AgentMessages` window and the `AgentMessageChunks` instances that `AgentMessageIndex` names. A message
|
|
55
|
+
* that retention deleted reads as `undefined`. A commit between the index read and the instance reads moves
|
|
56
|
+
* messages, so the read repeats with the new index.
|
|
57
|
+
* @returns The messages and the sequence of the oldest stored message.
|
|
58
|
+
* @throws Error when the index keeps changing for every attempt, or a stored message is missing.
|
|
59
|
+
*/
|
|
60
|
+
private readStoredMessages;
|
|
61
|
+
}
|
|
62
|
+
/** What the session holds about one tool call; see {@link AgentSessionRecordReader.readToolCallDetails}. */
|
|
63
|
+
export interface ToolCallDetails {
|
|
64
|
+
readonly record: AgentToolCallRecord | undefined;
|
|
65
|
+
readonly toolName: string | undefined;
|
|
66
|
+
readonly toolArguments: string | undefined;
|
|
67
|
+
readonly storedToolOutput: string | undefined;
|
|
68
|
+
}
|
|
69
|
+
/** One stored tool result; see {@link AgentSessionRecordReader.readRecentStoredToolResults}. */
|
|
70
|
+
export interface StoredToolResult {
|
|
71
|
+
readonly turnId: string;
|
|
72
|
+
readonly toolCallId: string;
|
|
73
|
+
readonly toolOutput: string;
|
|
74
|
+
}
|