@superdurable/dex-agents 0.2.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.
Files changed (70) hide show
  1. package/README.md +44 -6
  2. package/dist/src/agent-context-compaction.d.ts +150 -4
  3. package/dist/src/agent-context-compaction.js +253 -5
  4. package/dist/src/agent-context-compaction.js.map +1 -1
  5. package/dist/src/agent-session-codecs.js +1 -0
  6. package/dist/src/agent-session-codecs.js.map +1 -1
  7. package/dist/src/agent-session-failure.d.ts +58 -0
  8. package/dist/src/agent-session-failure.js +126 -0
  9. package/dist/src/agent-session-failure.js.map +1 -0
  10. package/dist/src/agent-session-flow-graph-cli.d.ts +2 -0
  11. package/dist/src/agent-session-flow-graph-cli.js +47 -0
  12. package/dist/src/agent-session-flow-graph-cli.js.map +1 -0
  13. package/dist/src/agent-session-flow-graph.d.ts +38 -0
  14. package/dist/src/agent-session-flow-graph.js +54 -0
  15. package/dist/src/agent-session-flow-graph.js.map +1 -0
  16. package/dist/src/agent-session-flow-graph.json +2187 -0
  17. package/dist/src/agent-session-flow.d.ts +23 -2
  18. package/dist/src/agent-session-flow.js +5 -0
  19. package/dist/src/agent-session-flow.js.map +1 -1
  20. package/dist/src/agent-session-spend-limit.d.ts +156 -0
  21. package/dist/src/agent-session-spend-limit.js +42 -0
  22. package/dist/src/agent-session-spend-limit.js.map +1 -0
  23. package/dist/src/agent-session-start.d.ts +49 -0
  24. package/dist/src/agent-session-start.js +67 -0
  25. package/dist/src/agent-session-start.js.map +1 -0
  26. package/dist/src/agent-session-step-options.d.ts +52 -1
  27. package/dist/src/agent-session-step-options.js +34 -2
  28. package/dist/src/agent-session-step-options.js.map +1 -1
  29. package/dist/src/agent-session-steps.d.ts +2 -0
  30. package/dist/src/agent-session-steps.js +80 -19
  31. package/dist/src/agent-session-steps.js.map +1 -1
  32. package/dist/src/agent-session-transitions.d.ts +12 -1
  33. package/dist/src/agent-session-transitions.js +53 -1
  34. package/dist/src/agent-session-transitions.js.map +1 -1
  35. package/dist/src/agent-session-values.d.ts +7 -2
  36. package/dist/src/agent-session-values.js.map +1 -1
  37. package/dist/src/index.d.ts +6 -1
  38. package/dist/src/index.js +5 -0
  39. package/dist/src/index.js.map +1 -1
  40. package/dist/src/stepwise/agent-definition.d.ts +8 -0
  41. package/dist/src/stepwise/index.d.ts +2 -1
  42. package/dist/src/stepwise/index.js +2 -1
  43. package/dist/src/stepwise/index.js.map +1 -1
  44. package/dist/src/stepwise/stepwise-agent-runner.d.ts +3 -21
  45. package/dist/src/stepwise/stepwise-agent-runner.js +2 -29
  46. package/dist/src/stepwise/stepwise-agent-runner.js.map +1 -1
  47. package/dist/src/stepwise/stepwise-agent-session-flow.js +1 -0
  48. package/dist/src/stepwise/stepwise-agent-session-flow.js.map +1 -1
  49. package/dist/src/testing/agent-session-test-client.js +2 -5
  50. package/dist/src/testing/agent-session-test-client.js.map +1 -1
  51. package/dist/src/testing/context-window.d.ts +194 -0
  52. package/dist/src/testing/context-window.js +360 -0
  53. package/dist/src/testing/context-window.js.map +1 -0
  54. package/dist/src/testing/index.d.ts +1 -0
  55. package/dist/src/testing/index.js +1 -0
  56. package/dist/src/testing/index.js.map +1 -1
  57. package/dist/src/ui-stream/agent-event-sse.d.ts +3 -2
  58. package/dist/src/ui-stream/agent-event-sse.js +11 -3
  59. package/dist/src/ui-stream/agent-event-sse.js.map +1 -1
  60. package/dist/src/ui-stream/agent-session-record-reader.d.ts +2 -0
  61. package/dist/src/ui-stream/agent-session-record-reader.js +5 -0
  62. package/dist/src/ui-stream/agent-session-record-reader.js.map +1 -1
  63. package/dist/src/ui-stream/agent-ui-message-stream-responses.js +3 -2
  64. package/dist/src/ui-stream/agent-ui-message-stream-responses.js.map +1 -1
  65. package/dist/src/ui-stream/agent-ui-message-stream.js +17 -3
  66. package/dist/src/ui-stream/agent-ui-message-stream.js.map +1 -1
  67. package/dist/src/ui-stream/agent-ui-messages.d.ts +2 -0
  68. package/dist/src/ui-stream/agent-ui-messages.js +5 -3
  69. package/dist/src/ui-stream/agent-ui-messages.js.map +1 -1
  70. package/package.json +6 -3
package/README.md CHANGED
@@ -27,10 +27,10 @@ Applications usually install an adapter next to this package. To build a
27
27
  runner of your own, install the core with the Dex SDK it supports:
28
28
 
29
29
  ```bash
30
- npm i @superdurable/dex-agents @superdurable/dex@1.35.0
30
+ npm i @superdurable/dex-agents @superdurable/dex@1.38.0
31
31
  ```
32
32
 
33
- `@superdurable/dex` 1.35.0 is a peer dependency, so the application, the core,
33
+ `@superdurable/dex` 1.38.0 is a peer dependency, so the application, the core,
34
34
  and every adapter share one copy. The Dex Registry matches Flow classes and
35
35
  RPC registrations by identity, so a second copy makes it reject the Flow
36
36
  (`steps must be a StepList`). Adapters list this package as a peer dependency
@@ -77,7 +77,7 @@ agents packages share one version line; see the
77
77
 
78
78
  ```ts
79
79
  import { Client, Registry, Worker, openBlobCache } from "@superdurable/dex";
80
- import { createAgentSessionFlow } from "@superdurable/dex-agents";
80
+ import { createAgentSessionFlow, startAgentSession } from "@superdurable/dex-agents";
81
81
 
82
82
  const flow = createAgentSessionFlow(new MyFrameworkRunner(agent), "SupportAgent", {
83
83
  toolPolicies: { search_orders: { effect: "read" } },
@@ -90,7 +90,7 @@ await worker.start();
90
90
  await worker.waitPullRegistered({ timeoutMs: 10_000 });
91
91
  const client = new Client(registry, blobCache, { serverAddress: "127.0.0.1:8801" });
92
92
 
93
- await client.startFlow(flow, "session-42", { initialMessage: "Where is order 42?" });
93
+ await startAgentSession(client, flow, "session-42", { initialMessage: "Where is order 42?" });
94
94
  const snapshot = await client.invokeRPC(flow.getAgentSnapshot, "session-42");
95
95
  for (const interrupt of snapshot.pendingInterrupts) {
96
96
  await client.invokeRPC(flow.resolveInterrupt, "session-42", {
@@ -533,6 +533,21 @@ it. The AI SDK UI message stream sends
533
533
  the compaction events and the summary call's `usage_recorded` as event data,
534
534
  outside the assistant message's steps.
535
535
 
536
+ A framework adapter cannot measure or rebuild the framework's own request, so
537
+ the core exports one shared rule. `estimateStoredInputTokens(turn,
538
+ fixedInputTokens)` counts each stored `frameworkItem` with `estimateTextTokens`,
539
+ three UTF-8 bytes per token, which errs high for English text.
540
+ `selectTurnBoundaryCutoff(turn, retainedTokenBudget)` cuts only between turns,
541
+ never inside the running turn, and returns `null` while `turn.frameworkRunState`
542
+ is set, because a resumed call continues from that state.
543
+ `buildContextSummaryPrompt(request)` writes the range as one plain text prompt,
544
+ starting with `contextSummaryTranscriptHeader`, that fits `inputTokenBudget`,
545
+ byte for byte as the Python core writes it. `CompactionPolicy.reservedInputTokens`
546
+ adds what an adapter's estimate cannot see, such as a framework's system prompt.
547
+ The adapters of this repository use them; see each adapter's README.
548
+ `ContextWindowSessionScenario` from `/testing` runs the same session past a
549
+ small context window for any adapter.
550
+
536
551
  ## Session settings
537
552
 
538
553
  Session settings let one Flow type serve sessions that differ, such as by
@@ -556,7 +571,7 @@ const flow = createStepwiseAgentSessionFlow(
556
571
  );
557
572
 
558
573
  validateSessionSettings({ model: "gpt-5" });
559
- await client.startFlow(flow, sessionId, { initialMessage: "Hello", sessionSettings: { model: "gpt-5" } });
574
+ await startAgentSession(client, flow, sessionId, { initialMessage: "Hello", sessionSettings: { model: "gpt-5" } });
560
575
  await client.invokeRPC(flow.updateSessionSettings, sessionId, { sessionSettings: { model: "gpt-5-mini" } });
561
576
  ```
562
577
 
@@ -978,6 +993,29 @@ adapter-specific events a prefix, such as `"vercel_ai.step_finished"`. The
978
993
  writer coalesces consecutive deltas of one type for up to 100 ms or 1,024
979
994
  characters, and drops writes after the handler returns.
980
995
 
996
+ ## Finding sessions that need a person
997
+
998
+ `startAgentSession` makes a session searchable by the Steps it waits in, so one query finds every session waiting
999
+ for a person, across agents:
1000
+
1001
+ ```ts
1002
+ await startAgentSession(client, flow, "session-42", { initialMessage: "Refund order 7" });
1003
+ const waiting = await client.searchFlows('ActiveStepTypes = "AwaitToolApproval"', 50);
1004
+ ```
1005
+
1006
+ `AwaitQuestionAnswer` finds questions the same way. A session leaves the search when it moves on. `Client.startFlow`
1007
+ does not turn this on; pass `configOverride: { activeStepSearchMode: agentSessionActiveStepSearchMode }` there, or use
1008
+ `Client.updateFlowConfig` for a running session. See
1009
+ [Find sessions that need a person](https://docs.superdurable.io/agent-frameworks/deploy-agents#find-sessions).
1010
+
1011
+ ## Spend limits
1012
+
1013
+ `ModelSpendLimit` is an application's spend limit, usually per account or API key
1014
+ across many sessions. The session asks it before each model call and reports what
1015
+ the call used. Counters and limits belong to the application. Without one, the
1016
+ session makes no checks. See the interface's documentation and
1017
+ [Spend limits](https://docs.superdurable.io/agent-frameworks/deploy-agents#spend-limits).
1018
+
981
1019
  ## Flow shape
982
1020
 
983
1021
  ```text
@@ -1157,7 +1195,7 @@ It needs `@modelcontextprotocol/sdk` 1.x, an optional peer dependency; the main
1157
1195
  entry point never imports it:
1158
1196
 
1159
1197
  ```bash
1160
- npm i @superdurable/dex-agents @superdurable/dex@1.35.0 @modelcontextprotocol/sdk zod
1198
+ npm i @superdurable/dex-agents @superdurable/dex@1.38.0 @modelcontextprotocol/sdk zod
1161
1199
  ```
1162
1200
 
1163
1201
  Configure the servers on the Worker, never from Flow input, a user message, or
@@ -1,4 +1,4 @@
1
- import type { AgentEventWriter, AgentFrameworkRunner, DeferredToolOutcome, ModelCallMetadata } from "./agent-framework-runner.js";
1
+ import type { AgentEventWriter, AgentFrameworkRunner, AgentTurnContext, DeferredToolOutcome, ModelCallMetadata } from "./agent-framework-runner.js";
2
2
  import type { AgentMessage, AgentUsage, ContextSummary, SessionSettings } from "./agent-session-values.js";
3
3
  /**
4
4
  * When a session summarizes its earlier messages so the model input stays under the context window, passed
@@ -27,6 +27,13 @@ export interface CompactionPolicy {
27
27
  readonly triggerRatio?: number;
28
28
  /** Share of the window that the newest messages may keep unsummarized. Defaults to 0.3. */
29
29
  readonly retainedRatio?: number;
30
+ /**
31
+ * Tokens of every model input that the runner's estimate cannot see, such as a system prompt that a
32
+ * framework builds, a dynamic instruction, or tools listed at run time. The session adds them to the
33
+ * estimate. Defaults to 0.
34
+ * @since 0.3.0
35
+ */
36
+ readonly reservedInputTokens?: number;
30
37
  }
31
38
  /** A {@link CompactionPolicy} with every default filled in. */
32
39
  export interface ResolvedCompactionPolicy {
@@ -36,7 +43,143 @@ export interface ResolvedCompactionPolicy {
36
43
  readonly triggerRatio: number;
37
44
  /** Share of the window that the newest messages may keep. */
38
45
  readonly retainedRatio: number;
46
+ /**
47
+ * Tokens added to every estimate. `undefined` reads as 0.
48
+ * @since 0.3.0
49
+ */
50
+ readonly reservedInputTokens?: number;
39
51
  }
52
+ /** First line of the user text that carries the {@link ContextSummary} into the model input. */
53
+ export declare const contextSummaryNoteHeader = "[Dex] Summary of the earlier conversation:";
54
+ /** Instructions of the model call that writes a {@link ContextSummary}. */
55
+ export declare const contextSummaryInstructions: string;
56
+ /** Last user text of the model call that writes a {@link ContextSummary}. */
57
+ export declare const contextSummaryRequest = "[Dex] Summarize the conversation above. Reply with the summary only.";
58
+ /**
59
+ * First line of the transcript in the prompt of {@link buildContextSummaryPrompt}.
60
+ * @since 0.3.0
61
+ */
62
+ export declare const contextSummaryTranscriptHeader = "[Dex] The conversation to summarize:";
63
+ /**
64
+ * The most tokens {@link buildContextSummaryPrompt} leaves free for the summary it asks for.
65
+ * @since 0.3.0
66
+ */
67
+ export declare const contextSummaryOutputTokens = 4096;
68
+ /**
69
+ * Returns the user text that says how many of the oldest messages a summary request left out.
70
+ * @param omittedCount - Messages left out, at least 1.
71
+ * @returns `[Dex] <omittedCount> earlier messages did not fit in this request and are left out.`, or
72
+ * `[Dex] 1 earlier message did not fit in this request and is left out.` for one message.
73
+ */
74
+ export declare function buildContextSummaryOmissionNote(omittedCount: number): string;
75
+ /**
76
+ * Returns the user text that puts a summary before the newer messages in a model input.
77
+ * @param summary - The session's summary.
78
+ * @returns {@link contextSummaryNoteHeader}, a newline, and the summary text.
79
+ */
80
+ export declare function buildContextSummaryNote(summary: ContextSummary): string;
81
+ /**
82
+ * Estimates the tokens of a text without a tokenizer, rounding up.
83
+ *
84
+ * Use it where a framework assembles the prompt and offers no token counter. It counts one token per three UTF-8
85
+ * bytes. That is more than a provider counts for English text, and about one token per CJK character, so the
86
+ * estimate errs toward compacting early.
87
+ *
88
+ * @example
89
+ * ```ts
90
+ * estimateTextTokens("twelve bytes"); // 4
91
+ * ```
92
+ * @param text - The text to measure.
93
+ * @returns The estimated tokens; 0 for an empty text.
94
+ * @since 0.3.0
95
+ */
96
+ export declare function estimateTextTokens(text: string): number;
97
+ /**
98
+ * Estimates a model input that a framework builds from the stored items.
99
+ *
100
+ * Call it from `estimateInputTokens` of a framework adapter. It measures each `frameworkItem` as stored, so JSON
101
+ * syntax and opaque provider items count too, plus the summary and any `frameworkRunState`.
102
+ *
103
+ * @example
104
+ * ```ts
105
+ * public estimateInputTokens(turn: AgentTurnContext): number {
106
+ * return estimateStoredInputTokens(turn, estimateTextTokens(this.instructions));
107
+ * }
108
+ * ```
109
+ * @param turn - The context of the next model call.
110
+ * @param fixedInputTokens - Tokens of what every request repeats, such as instructions and tool definitions.
111
+ * Measure what the framework shows; `CompactionPolicy.reservedInputTokens` covers the rest. Defaults to 0.
112
+ * @returns The estimated input tokens.
113
+ * @since 0.3.0
114
+ */
115
+ export declare function estimateStoredInputTokens(turn: AgentTurnContext, fixedInputTokens?: number): number;
116
+ /**
117
+ * Chooses the last message to summarize, cutting only between turns.
118
+ *
119
+ * Call it from `selectCompactionCutoff` of a framework adapter. A turn's messages are stored together, so a cut
120
+ * between turns never separates a tool call from its result and never splits a model response, whatever
121
+ * framework items the turn holds. The running turn is always kept, because the adapter reads its opening user
122
+ * message from `turn.messages`.
123
+ *
124
+ * It returns `null` while `turn.frameworkRunState` is set. A resumed model call continues from that value, which
125
+ * may already hold the older messages, so the summary could not replace them. Compaction waits for the next turn.
126
+ *
127
+ * @example
128
+ * ```ts
129
+ * public selectCompactionCutoff(turn: AgentTurnContext, retainedTokenBudget: number): number | null {
130
+ * return selectTurnBoundaryCutoff(turn, retainedTokenBudget);
131
+ * }
132
+ * ```
133
+ * @param turn - The context of the model call that crossed the threshold.
134
+ * @param retainedTokenBudget - Tokens that the kept messages may use.
135
+ * @returns The sequence of the last message to summarize: the earliest turn boundary whose kept messages fit the
136
+ * budget, else the boundary before the running turn. `null` when the turn is resuming, every message already
137
+ * fits, or no earlier turn is left to summarize.
138
+ * @since 0.3.0
139
+ */
140
+ export declare function selectTurnBoundaryCutoff(turn: AgentTurnContext, retainedTokenBudget: number): number | null;
141
+ /**
142
+ * Returns a stored message as the plain text a summary prompt shows.
143
+ *
144
+ * Wrap it in the `describeMessage` of {@link buildContextSummaryPrompt} to leave out items that only a provider can
145
+ * read.
146
+ *
147
+ * @param message - The stored message.
148
+ * @returns Its role and text, tool arguments, and tool output. A message with none of them returns its role and
149
+ * `frameworkItem`, such as a model response with several tool calls.
150
+ * @since 0.3.0
151
+ */
152
+ export declare function describeStoredMessage(message: AgentMessage): string;
153
+ /**
154
+ * Builds the one user prompt of a summary call for a framework adapter.
155
+ *
156
+ * Call it from `summarizeContext` and send the result with {@link contextSummaryInstructions} and no tools. The
157
+ * prompt holds the previous summary, the messages as plain text, and {@link contextSummaryRequest}. Plain text
158
+ * works with any framework: it needs no tool definitions, and no provider sees an item out of its place.
159
+ *
160
+ * The prompt fits `request.inputTokenBudget`, less room for the summary, as {@link estimateTextTokens} counts.
161
+ * When the range is too large, it leaves out the oldest messages and says so with
162
+ * {@link buildContextSummaryOmissionNote}. It shortens a single message only when that message alone is too
163
+ * large.
164
+ *
165
+ * @example
166
+ * ```ts
167
+ * public async summarizeContext(request: ContextSummaryRequest): Promise<ContextSummaryResult> {
168
+ * const result = await generateText({
169
+ * model,
170
+ * system: contextSummaryInstructions,
171
+ * prompt: buildContextSummaryPrompt(request),
172
+ * });
173
+ * return { text: result.text, usage: toAgentUsage(result.usage) };
174
+ * }
175
+ * ```
176
+ * @param request - The previous summary and the messages to summarize.
177
+ * @param describeMessage - Returns a message's text for the prompt, or `null` to leave it out, such as an
178
+ * encrypted reasoning item. Defaults to {@link describeStoredMessage}.
179
+ * @returns The prompt text.
180
+ * @since 0.3.0
181
+ */
182
+ export declare function buildContextSummaryPrompt(request: ContextSummaryRequest, describeMessage?: (message: AgentMessage) => string | null): string;
40
183
  /** What {@link AgentFrameworkRunner.summarizeContext} receives: the messages to fold into a new summary. */
41
184
  export interface ContextSummaryRequest {
42
185
  /** Flow ID, which is the session ID. */
@@ -98,11 +241,14 @@ export type ContextCompactionRunner = AgentFrameworkRunner<DeferredToolOutcome>
98
241
  export declare function isContextCompactionRunner(runner: AgentFrameworkRunner<DeferredToolOutcome>): runner is ContextCompactionRunner;
99
242
  /**
100
243
  * Fills in a compaction policy's defaults and checks it.
101
- * @throws {RangeError} When `contextWindowTokens` is not a positive integer, or unless
102
- * `0 < retainedRatio < triggerRatio <= 1`.
244
+ * @throws {RangeError} When `contextWindowTokens` is not a positive integer, `reservedInputTokens` is not an
245
+ * integer from 0 to below the window, or unless `0 < retainedRatio < triggerRatio <= 1`.
103
246
  */
104
247
  export declare function resolveCompactionPolicy(policy: CompactionPolicy): ResolvedCompactionPolicy;
105
- /** Reports whether a model input of this size makes the session compact before the model call. */
248
+ /**
249
+ * Reports whether a model input of this size makes the session compact before the model call: the estimate
250
+ * plus `reservedInputTokens` reaches `triggerRatio` of the window.
251
+ */
106
252
  export declare function isCompactionTriggered(policy: ResolvedCompactionPolicy, estimatedInputTokens: number): boolean;
107
253
  /** Tokens that the newest messages may keep after compaction: the retained share of the window, rounded down. */
108
254
  export declare function readRetainedTokenBudget(policy: ResolvedCompactionPolicy): number;
@@ -5,6 +5,245 @@
5
5
  // See the LICENSE file in the repository root.
6
6
  //
7
7
  // SPDX-License-Identifier: LicenseRef-Sustainable-Use-1.0
8
+ /** First line of the user text that carries the {@link ContextSummary} into the model input. */
9
+ export const contextSummaryNoteHeader = "[Dex] Summary of the earlier conversation:";
10
+ /** Instructions of the model call that writes a {@link ContextSummary}. */
11
+ export const contextSummaryInstructions = "You summarize the earlier part of an agent conversation. The agent continues from your " +
12
+ "summary and the messages after it, so keep every fact, decision, user request and " +
13
+ "preference, open task, and tool result that it still needs, including what an earlier " +
14
+ "summary says. Write plain text.";
15
+ /** Last user text of the model call that writes a {@link ContextSummary}. */
16
+ export const contextSummaryRequest = "[Dex] Summarize the conversation above. Reply with the summary only.";
17
+ /**
18
+ * First line of the transcript in the prompt of {@link buildContextSummaryPrompt}.
19
+ * @since 0.3.0
20
+ */
21
+ export const contextSummaryTranscriptHeader = "[Dex] The conversation to summarize:";
22
+ /**
23
+ * The most tokens {@link buildContextSummaryPrompt} leaves free for the summary it asks for.
24
+ * @since 0.3.0
25
+ */
26
+ export const contextSummaryOutputTokens = 4096;
27
+ // Bytes per estimated token. Below the 4 of English text, and 1 token per CJK character.
28
+ const estimatedBytesPerToken = 3;
29
+ const textEncoder = new TextEncoder();
30
+ /**
31
+ * Returns the user text that says how many of the oldest messages a summary request left out.
32
+ * @param omittedCount - Messages left out, at least 1.
33
+ * @returns `[Dex] <omittedCount> earlier messages did not fit in this request and are left out.`, or
34
+ * `[Dex] 1 earlier message did not fit in this request and is left out.` for one message.
35
+ */
36
+ export function buildContextSummaryOmissionNote(omittedCount) {
37
+ if (omittedCount === 1) {
38
+ return "[Dex] 1 earlier message did not fit in this request and is left out.";
39
+ }
40
+ return `[Dex] ${omittedCount} earlier messages did not fit in this request and are left out.`;
41
+ }
42
+ /**
43
+ * Returns the user text that puts a summary before the newer messages in a model input.
44
+ * @param summary - The session's summary.
45
+ * @returns {@link contextSummaryNoteHeader}, a newline, and the summary text.
46
+ */
47
+ export function buildContextSummaryNote(summary) {
48
+ return `${contextSummaryNoteHeader}\n${summary.text}`;
49
+ }
50
+ /**
51
+ * Estimates the tokens of a text without a tokenizer, rounding up.
52
+ *
53
+ * Use it where a framework assembles the prompt and offers no token counter. It counts one token per three UTF-8
54
+ * bytes. That is more than a provider counts for English text, and about one token per CJK character, so the
55
+ * estimate errs toward compacting early.
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * estimateTextTokens("twelve bytes"); // 4
60
+ * ```
61
+ * @param text - The text to measure.
62
+ * @returns The estimated tokens; 0 for an empty text.
63
+ * @since 0.3.0
64
+ */
65
+ export function estimateTextTokens(text) {
66
+ return Math.ceil(textEncoder.encode(text).length / estimatedBytesPerToken);
67
+ }
68
+ /**
69
+ * Estimates a model input that a framework builds from the stored items.
70
+ *
71
+ * Call it from `estimateInputTokens` of a framework adapter. It measures each `frameworkItem` as stored, so JSON
72
+ * syntax and opaque provider items count too, plus the summary and any `frameworkRunState`.
73
+ *
74
+ * @example
75
+ * ```ts
76
+ * public estimateInputTokens(turn: AgentTurnContext): number {
77
+ * return estimateStoredInputTokens(turn, estimateTextTokens(this.instructions));
78
+ * }
79
+ * ```
80
+ * @param turn - The context of the next model call.
81
+ * @param fixedInputTokens - Tokens of what every request repeats, such as instructions and tool definitions.
82
+ * Measure what the framework shows; `CompactionPolicy.reservedInputTokens` covers the rest. Defaults to 0.
83
+ * @returns The estimated input tokens.
84
+ * @since 0.3.0
85
+ */
86
+ export function estimateStoredInputTokens(turn, fixedInputTokens = 0) {
87
+ let tokens = fixedInputTokens;
88
+ for (const message of turn.messages) {
89
+ tokens += estimateTextTokens(message.frameworkItem);
90
+ }
91
+ if (turn.contextSummary) {
92
+ tokens += estimateTextTokens(buildContextSummaryNote(turn.contextSummary));
93
+ }
94
+ if (turn.frameworkRunState !== null) {
95
+ tokens += estimateTextTokens(turn.frameworkRunState);
96
+ }
97
+ return tokens;
98
+ }
99
+ /**
100
+ * Chooses the last message to summarize, cutting only between turns.
101
+ *
102
+ * Call it from `selectCompactionCutoff` of a framework adapter. A turn's messages are stored together, so a cut
103
+ * between turns never separates a tool call from its result and never splits a model response, whatever
104
+ * framework items the turn holds. The running turn is always kept, because the adapter reads its opening user
105
+ * message from `turn.messages`.
106
+ *
107
+ * It returns `null` while `turn.frameworkRunState` is set. A resumed model call continues from that value, which
108
+ * may already hold the older messages, so the summary could not replace them. Compaction waits for the next turn.
109
+ *
110
+ * @example
111
+ * ```ts
112
+ * public selectCompactionCutoff(turn: AgentTurnContext, retainedTokenBudget: number): number | null {
113
+ * return selectTurnBoundaryCutoff(turn, retainedTokenBudget);
114
+ * }
115
+ * ```
116
+ * @param turn - The context of the model call that crossed the threshold.
117
+ * @param retainedTokenBudget - Tokens that the kept messages may use.
118
+ * @returns The sequence of the last message to summarize: the earliest turn boundary whose kept messages fit the
119
+ * budget, else the boundary before the running turn. `null` when the turn is resuming, every message already
120
+ * fits, or no earlier turn is left to summarize.
121
+ * @since 0.3.0
122
+ */
123
+ export function selectTurnBoundaryCutoff(turn, retainedTokenBudget) {
124
+ if (turn.frameworkRunState !== null) {
125
+ return null;
126
+ }
127
+ const messages = turn.messages;
128
+ const foundRunningTurnIndex = messages.findIndex((message) => message.turnId === turn.turnId);
129
+ const runningTurnIndex = foundRunningTurnIndex === -1 ? messages.length : foundRunningTurnIndex;
130
+ const cuts = [];
131
+ for (let index = 1; index < Math.min(runningTurnIndex + 1, messages.length); index += 1) {
132
+ if (messages[index]?.turnId !== messages[index - 1]?.turnId) {
133
+ cuts.push(index);
134
+ }
135
+ }
136
+ if (cuts.length === 0) {
137
+ return null;
138
+ }
139
+ const keptTokens = new Array(messages.length + 1).fill(0);
140
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
141
+ keptTokens[index] = (keptTokens[index + 1] ?? 0) + estimateTextTokens(messages[index]?.frameworkItem ?? "");
142
+ }
143
+ if ((keptTokens[0] ?? 0) <= retainedTokenBudget) {
144
+ return null;
145
+ }
146
+ const cut = cuts.find((candidate) => (keptTokens[candidate] ?? 0) <= retainedTokenBudget) ?? cuts.at(-1);
147
+ return cut === undefined ? null : (messages[cut - 1]?.sequence ?? null);
148
+ }
149
+ /**
150
+ * Returns a stored message as the plain text a summary prompt shows.
151
+ *
152
+ * Wrap it in the `describeMessage` of {@link buildContextSummaryPrompt} to leave out items that only a provider can
153
+ * read.
154
+ *
155
+ * @param message - The stored message.
156
+ * @returns Its role and text, tool arguments, and tool output. A message with none of them returns its role and
157
+ * `frameworkItem`, such as a model response with several tool calls.
158
+ * @since 0.3.0
159
+ */
160
+ export function describeStoredMessage(message) {
161
+ const lines = [];
162
+ if (message.text) {
163
+ lines.push(`${message.role}: ${message.text}`);
164
+ }
165
+ if (message.toolArguments !== null) {
166
+ lines.push(`tool call ${message.toolName ?? ""}: ${message.toolArguments}`);
167
+ }
168
+ if (message.toolOutput !== null) {
169
+ lines.push(`tool result ${message.toolName ?? ""}: ${message.toolOutput}`);
170
+ }
171
+ return lines.length > 0 ? lines.join("\n") : `${message.role}: ${message.frameworkItem}`;
172
+ }
173
+ /**
174
+ * Builds the one user prompt of a summary call for a framework adapter.
175
+ *
176
+ * Call it from `summarizeContext` and send the result with {@link contextSummaryInstructions} and no tools. The
177
+ * prompt holds the previous summary, the messages as plain text, and {@link contextSummaryRequest}. Plain text
178
+ * works with any framework: it needs no tool definitions, and no provider sees an item out of its place.
179
+ *
180
+ * The prompt fits `request.inputTokenBudget`, less room for the summary, as {@link estimateTextTokens} counts.
181
+ * When the range is too large, it leaves out the oldest messages and says so with
182
+ * {@link buildContextSummaryOmissionNote}. It shortens a single message only when that message alone is too
183
+ * large.
184
+ *
185
+ * @example
186
+ * ```ts
187
+ * public async summarizeContext(request: ContextSummaryRequest): Promise<ContextSummaryResult> {
188
+ * const result = await generateText({
189
+ * model,
190
+ * system: contextSummaryInstructions,
191
+ * prompt: buildContextSummaryPrompt(request),
192
+ * });
193
+ * return { text: result.text, usage: toAgentUsage(result.usage) };
194
+ * }
195
+ * ```
196
+ * @param request - The previous summary and the messages to summarize.
197
+ * @param describeMessage - Returns a message's text for the prompt, or `null` to leave it out, such as an
198
+ * encrypted reasoning item. Defaults to {@link describeStoredMessage}.
199
+ * @returns The prompt text.
200
+ * @since 0.3.0
201
+ */
202
+ export function buildContextSummaryPrompt(request, describeMessage = describeStoredMessage) {
203
+ const opening = request.previousSummary === null ? [] : [buildContextSummaryNote(request.previousSummary)];
204
+ const closing = [contextSummaryTranscriptHeader, contextSummaryRequest];
205
+ const descriptions = request.messages.flatMap((message) => {
206
+ const description = describeMessage(message);
207
+ return description === null ? [] : [description];
208
+ });
209
+ let remainingTokens;
210
+ if (request.inputTokenBudget !== undefined) {
211
+ const fixedTokens = estimateTextTokens([contextSummaryInstructions, ...opening, ...closing].join("\n\n"));
212
+ const roomForTranscript = request.inputTokenBudget - fixedTokens;
213
+ remainingTokens =
214
+ roomForTranscript - Math.min(contextSummaryOutputTokens, Math.floor(Math.max(roomForTranscript, 0) / 4));
215
+ }
216
+ const kept = [];
217
+ for (const description of [...descriptions].reverse()) {
218
+ if (remainingTokens === undefined) {
219
+ kept.push(description);
220
+ continue;
221
+ }
222
+ // Two newlines separate messages; they count toward the estimate.
223
+ const tokens = estimateTextTokens(`${description}\n\n`);
224
+ if (tokens <= remainingTokens) {
225
+ kept.push(description);
226
+ remainingTokens -= tokens;
227
+ continue;
228
+ }
229
+ if (kept.length === 0 && remainingTokens > 0) {
230
+ kept.push(shortenToTokens(description, remainingTokens));
231
+ }
232
+ break;
233
+ }
234
+ kept.reverse();
235
+ const omittedCount = descriptions.length - kept.length;
236
+ if (omittedCount > 0) {
237
+ opening.push(buildContextSummaryOmissionNote(omittedCount));
238
+ }
239
+ return [...opening, contextSummaryTranscriptHeader, ...kept, contextSummaryRequest].join("\n\n");
240
+ }
241
+ function shortenToTokens(text, tokens) {
242
+ const marker = "[Dex] The rest of this message is left out.";
243
+ const keptBytes = Math.max(tokens - estimateTextTokens(marker) - 1, 0) * estimatedBytesPerToken;
244
+ const kept = new TextDecoder().decode(textEncoder.encode(text).slice(0, keptBytes)).replace(/\uFFFD$/u, "");
245
+ return `${kept}\n${marker}`;
246
+ }
8
247
  /** Reports whether a runner implements every compaction method. */
9
248
  export function isContextCompactionRunner(runner) {
10
249
  return (typeof runner.estimateInputTokens === "function" &&
@@ -13,8 +252,8 @@ export function isContextCompactionRunner(runner) {
13
252
  }
14
253
  /**
15
254
  * Fills in a compaction policy's defaults and checks it.
16
- * @throws {RangeError} When `contextWindowTokens` is not a positive integer, or unless
17
- * `0 < retainedRatio < triggerRatio <= 1`.
255
+ * @throws {RangeError} When `contextWindowTokens` is not a positive integer, `reservedInputTokens` is not an
256
+ * integer from 0 to below the window, or unless `0 < retainedRatio < triggerRatio <= 1`.
18
257
  */
19
258
  export function resolveCompactionPolicy(policy) {
20
259
  const contextWindowTokens = policy.contextWindowTokens;
@@ -26,11 +265,20 @@ export function resolveCompactionPolicy(policy) {
26
265
  if (!(retainedRatio > 0 && retainedRatio < triggerRatio && triggerRatio <= 1)) {
27
266
  throw new RangeError("compaction needs 0 < retainedRatio < triggerRatio <= 1");
28
267
  }
29
- return Object.freeze({ contextWindowTokens, triggerRatio, retainedRatio });
268
+ const reservedInputTokens = policy.reservedInputTokens ?? 0;
269
+ const isReservationValid = Number.isSafeInteger(reservedInputTokens) && reservedInputTokens >= 0 && reservedInputTokens < contextWindowTokens;
270
+ if (!isReservationValid) {
271
+ throw new RangeError("compaction.reservedInputTokens must be an integer from 0 to below contextWindowTokens");
272
+ }
273
+ return Object.freeze({ contextWindowTokens, triggerRatio, retainedRatio, reservedInputTokens });
30
274
  }
31
- /** Reports whether a model input of this size makes the session compact before the model call. */
275
+ /**
276
+ * Reports whether a model input of this size makes the session compact before the model call: the estimate
277
+ * plus `reservedInputTokens` reaches `triggerRatio` of the window.
278
+ */
32
279
  export function isCompactionTriggered(policy, estimatedInputTokens) {
33
- return estimatedInputTokens >= policy.triggerRatio * policy.contextWindowTokens;
280
+ const estimatedTokens = estimatedInputTokens + (policy.reservedInputTokens ?? 0);
281
+ return estimatedTokens >= policy.triggerRatio * policy.contextWindowTokens;
34
282
  }
35
283
  /** Tokens that the newest messages may keep after compaction: the retained share of the window, rounded down. */
36
284
  export function readRetainedTokenBudget(policy) {
@@ -1 +1 @@
1
- {"version":3,"file":"agent-context-compaction.js","sourceRoot":"","sources":["../../src/agent-context-compaction.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,EAAE;AACF,kDAAkD;AAClD,mEAAmE;AACnE,+CAA+C;AAC/C,EAAE;AACF,0DAA0D;AAgH1D,mEAAmE;AACnE,MAAM,UAAU,yBAAyB,CACvC,MAAiD;IAEjD,OAAO,CACL,OAAO,MAAM,CAAC,mBAAmB,KAAK,UAAU;QAChD,OAAO,MAAM,CAAC,sBAAsB,KAAK,UAAU;QACnD,OAAO,MAAM,CAAC,gBAAgB,KAAK,UAAU,CAC9C,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CAAC,MAAwB;IAC9D,MAAM,mBAAmB,GAAG,MAAM,CAAC,mBAAmB,CAAC;IACvD,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,mBAAmB,CAAC,IAAI,mBAAmB,IAAI,CAAC,EAAE,CAAC;QAC3E,MAAM,IAAI,UAAU,CAAC,2DAA2D,CAAC,CAAC;IACpF,CAAC;IACD,MAAM,YAAY,GAAG,MAAM,CAAC,YAAY,IAAI,IAAI,CAAC;IACjD,MAAM,aAAa,GAAG,MAAM,CAAC,aAAa,IAAI,GAAG,CAAC;IAClD,IAAI,CAAC,CAAC,aAAa,GAAG,CAAC,IAAI,aAAa,GAAG,YAAY,IAAI,YAAY,IAAI,CAAC,CAAC,EAAE,CAAC;QAC9E,MAAM,IAAI,UAAU,CAAC,wDAAwD,CAAC,CAAC;IACjF,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,mBAAmB,EAAE,YAAY,EAAE,aAAa,EAAE,CAAC,CAAC;AAC7E,CAAC;AAED,kGAAkG;AAClG,MAAM,UAAU,qBAAqB,CAAC,MAAgC,EAAE,oBAA4B;IAClG,OAAO,oBAAoB,IAAI,MAAM,CAAC,YAAY,GAAG,MAAM,CAAC,mBAAmB,CAAC;AAClF,CAAC;AAED,iHAAiH;AACjH,MAAM,UAAU,uBAAuB,CAAC,MAAgC;IACtE,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,aAAa,GAAG,MAAM,CAAC,mBAAmB,CAAC,CAAC;AACvE,CAAC"}
1
+ {"version":3,"file":"agent-context-compaction.js","sourceRoot":"","sources":["../../src/agent-context-compaction.ts"],"names":[],"mappings":"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"}
@@ -12,6 +12,7 @@ const agentSessionStatuses = [
12
12
  "running_model",
13
13
  "running_tools",
14
14
  "compacting_context",
15
+ "failed",
15
16
  ];
16
17
  const agentMessageRoles = ["user", "assistant", "tool"];
17
18
  const toolEffects = [