@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.
- package/README.md +44 -6
- package/dist/src/agent-context-compaction.d.ts +150 -4
- package/dist/src/agent-context-compaction.js +253 -5
- package/dist/src/agent-context-compaction.js.map +1 -1
- package/dist/src/agent-session-codecs.js +1 -0
- 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 +23 -2
- package/dist/src/agent-session-flow.js +5 -0
- package/dist/src/agent-session-flow.js.map +1 -1
- 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 +52 -1
- package/dist/src/agent-session-step-options.js +34 -2
- package/dist/src/agent-session-step-options.js.map +1 -1
- package/dist/src/agent-session-steps.d.ts +2 -0
- package/dist/src/agent-session-steps.js +80 -19
- package/dist/src/agent-session-steps.js.map +1 -1
- package/dist/src/agent-session-transitions.d.ts +12 -1
- package/dist/src/agent-session-transitions.js +53 -1
- package/dist/src/agent-session-transitions.js.map +1 -1
- package/dist/src/agent-session-values.d.ts +7 -2
- package/dist/src/agent-session-values.js.map +1 -1
- package/dist/src/index.d.ts +6 -1
- package/dist/src/index.js +5 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/stepwise/agent-definition.d.ts +8 -0
- package/dist/src/stepwise/index.d.ts +2 -1
- package/dist/src/stepwise/index.js +2 -1
- package/dist/src/stepwise/index.js.map +1 -1
- package/dist/src/stepwise/stepwise-agent-runner.d.ts +3 -21
- package/dist/src/stepwise/stepwise-agent-runner.js +2 -29
- package/dist/src/stepwise/stepwise-agent-runner.js.map +1 -1
- package/dist/src/stepwise/stepwise-agent-session-flow.js +1 -0
- package/dist/src/stepwise/stepwise-agent-session-flow.js.map +1 -1
- package/dist/src/testing/agent-session-test-client.js +2 -5
- 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 +1 -0
- package/dist/src/testing/index.js +1 -0
- package/dist/src/testing/index.js.map +1 -1
- package/dist/src/ui-stream/agent-event-sse.d.ts +3 -2
- package/dist/src/ui-stream/agent-event-sse.js +11 -3
- package/dist/src/ui-stream/agent-event-sse.js.map +1 -1
- package/dist/src/ui-stream/agent-session-record-reader.d.ts +2 -0
- package/dist/src/ui-stream/agent-session-record-reader.js +5 -0
- package/dist/src/ui-stream/agent-session-record-reader.js.map +1 -1
- package/dist/src/ui-stream/agent-ui-message-stream-responses.js +3 -2
- package/dist/src/ui-stream/agent-ui-message-stream-responses.js.map +1 -1
- package/dist/src/ui-stream/agent-ui-message-stream.js +17 -3
- package/dist/src/ui-stream/agent-ui-message-stream.js.map +1 -1
- package/dist/src/ui-stream/agent-ui-messages.d.ts +2 -0
- package/dist/src/ui-stream/agent-ui-messages.js +5 -3
- package/dist/src/ui-stream/agent-ui-messages.js.map +1 -1
- 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.
|
|
30
|
+
npm i @superdurable/dex-agents @superdurable/dex@1.38.0
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
`@superdurable/dex` 1.
|
|
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
|
|
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
|
|
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.
|
|
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,
|
|
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
|
-
/**
|
|
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,
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
-
|
|
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;
|
|
1
|
+
{"version":3,"file":"agent-context-compaction.js","sourceRoot":"","sources":["../../src/agent-context-compaction.ts"],"names":[],"mappings":"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"}
|