@supyagent/sdk 0.1.36 → 0.1.38
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/dist/context.cjs +322 -0
- package/dist/context.cjs.map +1 -0
- package/dist/context.d.cts +204 -0
- package/dist/context.d.ts +204 -0
- package/dist/context.js +290 -0
- package/dist/context.js.map +1 -0
- package/dist/react.cjs +147 -44
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +36 -1
- package/dist/react.d.ts +36 -1
- package/dist/react.js +144 -44
- package/dist/react.js.map +1 -1
- package/package.json +7 -1
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { UIMessage } from 'ai';
|
|
2
|
+
|
|
3
|
+
interface ContextManagerOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Maximum context window size in tokens for the model.
|
|
6
|
+
* Used to calculate soft/hard thresholds.
|
|
7
|
+
* @default 128_000
|
|
8
|
+
*/
|
|
9
|
+
maxTokens?: number;
|
|
10
|
+
/**
|
|
11
|
+
* Soft threshold as a fraction of maxTokens (0–1).
|
|
12
|
+
* When exceeded after a response completes, triggers background summarization.
|
|
13
|
+
* @default 0.75
|
|
14
|
+
*/
|
|
15
|
+
softThreshold?: number;
|
|
16
|
+
/**
|
|
17
|
+
* Hard threshold as a fraction of maxTokens (0–1).
|
|
18
|
+
* When exceeded, blocks before the next LLM call and compactifies synchronously.
|
|
19
|
+
* @default 0.90
|
|
20
|
+
*/
|
|
21
|
+
hardThreshold?: number;
|
|
22
|
+
/**
|
|
23
|
+
* Tokens to reserve for the model's response output.
|
|
24
|
+
* Subtracted from the context budget when checking thresholds.
|
|
25
|
+
* @default 4096
|
|
26
|
+
*/
|
|
27
|
+
responseReserve?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Minimum number of recent messages to always keep (never summarized away).
|
|
30
|
+
* @default 4
|
|
31
|
+
*/
|
|
32
|
+
minRecentMessages?: number;
|
|
33
|
+
/**
|
|
34
|
+
* Custom prompt for the default summarizer.
|
|
35
|
+
* Overrides the built-in summarization system prompt.
|
|
36
|
+
*/
|
|
37
|
+
summaryPrompt?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Full override for compactification.
|
|
40
|
+
* When provided, replaces the default summarizer entirely.
|
|
41
|
+
* Receives all messages and must return the compacted message list.
|
|
42
|
+
*/
|
|
43
|
+
compactify?: (messages: UIMessage[]) => Promise<UIMessage[]>;
|
|
44
|
+
/**
|
|
45
|
+
* The model to use for summarization (same type as streamText's `model` param).
|
|
46
|
+
* Required unless a custom `compactify` function is provided.
|
|
47
|
+
*/
|
|
48
|
+
summaryModel?: any;
|
|
49
|
+
/**
|
|
50
|
+
* Custom token estimator function.
|
|
51
|
+
* Given a UIMessage[], returns an estimated token count.
|
|
52
|
+
* @default Character-based heuristic (~4 chars per token)
|
|
53
|
+
*/
|
|
54
|
+
estimateTokens?: (messages: UIMessage[]) => number;
|
|
55
|
+
}
|
|
56
|
+
interface ContextState {
|
|
57
|
+
/** Total input tokens consumed across all LLM calls in this chat */
|
|
58
|
+
totalInputTokens: number;
|
|
59
|
+
/** Total output tokens consumed across all LLM calls in this chat */
|
|
60
|
+
totalOutputTokens: number;
|
|
61
|
+
/** Estimated current context size (tokens for the next LLM call) */
|
|
62
|
+
estimatedContextSize: number;
|
|
63
|
+
/** The configured maximum context window */
|
|
64
|
+
maxTokens: number;
|
|
65
|
+
/** Usage ratio (estimatedContextSize / effectiveBudget), clamped 0–1 */
|
|
66
|
+
usageRatio: number;
|
|
67
|
+
/** Whether the soft threshold has been exceeded */
|
|
68
|
+
softThresholdExceeded: boolean;
|
|
69
|
+
/** Whether the hard threshold has been exceeded */
|
|
70
|
+
hardThresholdExceeded: boolean;
|
|
71
|
+
/** Number of context-summary messages found in the chat */
|
|
72
|
+
summaryCount: number;
|
|
73
|
+
}
|
|
74
|
+
interface ContextSummaryMetadata {
|
|
75
|
+
type: "context-summary";
|
|
76
|
+
/** Number of messages that were summarized */
|
|
77
|
+
messagesSummarized: number;
|
|
78
|
+
/** Estimated tokens of the original messages that were summarized */
|
|
79
|
+
originalTokens: number;
|
|
80
|
+
/** Estimated tokens of the summary */
|
|
81
|
+
summaryTokens: number;
|
|
82
|
+
/** ISO timestamp of when the summary was created */
|
|
83
|
+
createdAt: string;
|
|
84
|
+
}
|
|
85
|
+
interface ContextMessageMetadata {
|
|
86
|
+
context?: {
|
|
87
|
+
/** Input tokens used by this specific LLM call */
|
|
88
|
+
inputTokens?: number;
|
|
89
|
+
/** Output tokens used by this specific LLM call */
|
|
90
|
+
outputTokens?: number;
|
|
91
|
+
/** Cumulative total tokens consumed in this chat so far */
|
|
92
|
+
totalTokens?: number;
|
|
93
|
+
/** Current usage ratio at completion (0–1) */
|
|
94
|
+
usageRatio?: number;
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
interface ContextManager {
|
|
98
|
+
/** Get the current context state. */
|
|
99
|
+
getState(): ContextState;
|
|
100
|
+
/**
|
|
101
|
+
* Record token usage from a completed LLM step.
|
|
102
|
+
* Called from onStepFinish or onFinish callbacks.
|
|
103
|
+
*/
|
|
104
|
+
recordUsage(usage: {
|
|
105
|
+
inputTokens?: number;
|
|
106
|
+
outputTokens?: number;
|
|
107
|
+
}): void;
|
|
108
|
+
/**
|
|
109
|
+
* Update the estimated context size based on current messages.
|
|
110
|
+
* Should be called whenever the message list changes.
|
|
111
|
+
*/
|
|
112
|
+
updateEstimate(messages: UIMessage[]): void;
|
|
113
|
+
/**
|
|
114
|
+
* Prepare messages for the LLM call.
|
|
115
|
+
* - Finds the last context-summary message
|
|
116
|
+
* - Drops all messages before it
|
|
117
|
+
* - Injects the summary text into the system prompt
|
|
118
|
+
* - Returns the trimmed messages and updated system prompt
|
|
119
|
+
*
|
|
120
|
+
* Does NOT mutate the input array.
|
|
121
|
+
*/
|
|
122
|
+
prepareMessages(messages: UIMessage[], systemPrompt: string): Promise<{
|
|
123
|
+
messages: UIMessage[];
|
|
124
|
+
systemPrompt: string;
|
|
125
|
+
}>;
|
|
126
|
+
/**
|
|
127
|
+
* Returns true if estimated context exceeds the hard threshold.
|
|
128
|
+
* Indicates compactification should block before the next LLM call.
|
|
129
|
+
*/
|
|
130
|
+
shouldCompactify(messages: UIMessage[]): boolean;
|
|
131
|
+
/**
|
|
132
|
+
* Returns true if estimated context exceeds the soft threshold
|
|
133
|
+
* but not the hard threshold.
|
|
134
|
+
* Indicates background summarization should be triggered.
|
|
135
|
+
*/
|
|
136
|
+
shouldSummarize(messages: UIMessage[]): boolean;
|
|
137
|
+
/**
|
|
138
|
+
* Perform compactification (synchronous in the request flow).
|
|
139
|
+
* Returns a new message array with a summary message inserted
|
|
140
|
+
* and older messages removed. Does NOT mutate the input.
|
|
141
|
+
*/
|
|
142
|
+
compactify(messages: UIMessage[]): Promise<UIMessage[]>;
|
|
143
|
+
/**
|
|
144
|
+
* Get context metadata to attach to the streamed response.
|
|
145
|
+
* Used with toUIMessageStreamResponse's messageMetadata callback.
|
|
146
|
+
*/
|
|
147
|
+
getMessageMetadata(): ContextMessageMetadata;
|
|
148
|
+
/** Reset internal state (e.g. when starting a new chat). */
|
|
149
|
+
reset(): void;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Prepare messages for an LLM call by trimming everything before the last
|
|
154
|
+
* context-summary message and injecting the summary into the system prompt.
|
|
155
|
+
*
|
|
156
|
+
* - If no summary exists, all messages pass through unchanged.
|
|
157
|
+
* - If a summary exists at index `i`, messages `[0..i]` are dropped and the
|
|
158
|
+
* summary text is appended to the system prompt.
|
|
159
|
+
*
|
|
160
|
+
* Neither the input array nor any individual message is mutated.
|
|
161
|
+
*/
|
|
162
|
+
declare function prepareMessages(messages: UIMessage[], systemPrompt: string): {
|
|
163
|
+
messages: UIMessage[];
|
|
164
|
+
systemPrompt: string;
|
|
165
|
+
};
|
|
166
|
+
/**
|
|
167
|
+
* Find the index of the last message that is a context summary.
|
|
168
|
+
* Returns -1 if none found.
|
|
169
|
+
*/
|
|
170
|
+
declare function findLastSummaryIndex(messages: UIMessage[]): number;
|
|
171
|
+
/** Count how many context-summary messages exist in the array. */
|
|
172
|
+
declare function countSummaries(messages: UIMessage[]): number;
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Create a context manager that tracks token usage, detects threshold
|
|
176
|
+
* breaches, and can compactify message history via summarisation.
|
|
177
|
+
*
|
|
178
|
+
* The manager is stateless across HTTP requests — it derives cumulative
|
|
179
|
+
* totals from message metadata written by previous requests and
|
|
180
|
+
* accumulates usage recorded during the current request.
|
|
181
|
+
*/
|
|
182
|
+
declare function createContextManager(options?: ContextManagerOptions): ContextManager;
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Estimate the token count of a UIMessage array using a character-based heuristic.
|
|
186
|
+
*
|
|
187
|
+
* This is intentionally model-agnostic (~4 chars per token) and errs slightly
|
|
188
|
+
* on the conservative side. Pass a custom `estimateTokens` to
|
|
189
|
+
* `createContextManager` if you need model-specific accuracy.
|
|
190
|
+
*/
|
|
191
|
+
declare function estimateTokens(messages: UIMessage[]): number;
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Summarise a list of UIMessages into a single prose block
|
|
195
|
+
* using an LLM call via the AI SDK's `generateText`.
|
|
196
|
+
*/
|
|
197
|
+
declare function summarize(messages: UIMessage[], options: {
|
|
198
|
+
model: any;
|
|
199
|
+
prompt?: string;
|
|
200
|
+
/** Set to true when compacting mid-tool-chain */
|
|
201
|
+
midChain?: boolean;
|
|
202
|
+
}): Promise<string>;
|
|
203
|
+
|
|
204
|
+
export { type ContextManager, type ContextManagerOptions, type ContextMessageMetadata, type ContextState, type ContextSummaryMetadata, countSummaries, createContextManager, estimateTokens, findLastSummaryIndex, prepareMessages, summarize };
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
// src/context/token-estimator.ts
|
|
2
|
+
var CHARS_PER_TOKEN = 4;
|
|
3
|
+
var MESSAGE_OVERHEAD = 4;
|
|
4
|
+
var CONVERSATION_OVERHEAD = 3;
|
|
5
|
+
function estimateTokens(messages) {
|
|
6
|
+
let totalChars = 0;
|
|
7
|
+
for (const msg of messages) {
|
|
8
|
+
totalChars += MESSAGE_OVERHEAD * CHARS_PER_TOKEN;
|
|
9
|
+
for (const part of msg.parts) {
|
|
10
|
+
switch (part.type) {
|
|
11
|
+
case "text":
|
|
12
|
+
totalChars += (part.text ?? "").length;
|
|
13
|
+
break;
|
|
14
|
+
case "tool-invocation": {
|
|
15
|
+
const inv = part;
|
|
16
|
+
if (inv.toolName) totalChars += inv.toolName.length;
|
|
17
|
+
if (inv.input) totalChars += JSON.stringify(inv.input).length;
|
|
18
|
+
if (inv.output) totalChars += JSON.stringify(inv.output).length;
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
case "file": {
|
|
22
|
+
const file = part;
|
|
23
|
+
totalChars += (file.url ?? "").length;
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
default:
|
|
27
|
+
try {
|
|
28
|
+
totalChars += JSON.stringify(part).length;
|
|
29
|
+
} catch {
|
|
30
|
+
totalChars += 50;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return Math.ceil(totalChars / CHARS_PER_TOKEN) + CONVERSATION_OVERHEAD;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// src/context/message-preparation.ts
|
|
39
|
+
var SUMMARY_PREAMBLE = "\n\n[Previous conversation summary]\nThe following is a summary of the conversation history that has been compacted to save context space:\n\n";
|
|
40
|
+
function prepareMessages(messages, systemPrompt) {
|
|
41
|
+
const summaryIdx = findLastSummaryIndex(messages);
|
|
42
|
+
if (summaryIdx === -1) {
|
|
43
|
+
return { messages, systemPrompt };
|
|
44
|
+
}
|
|
45
|
+
const summaryMessage = messages[summaryIdx];
|
|
46
|
+
const summaryText = extractSummaryText(summaryMessage);
|
|
47
|
+
const trimmedMessages = messages.slice(summaryIdx + 1);
|
|
48
|
+
return {
|
|
49
|
+
messages: trimmedMessages,
|
|
50
|
+
systemPrompt: systemPrompt + SUMMARY_PREAMBLE + summaryText
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function findLastSummaryIndex(messages) {
|
|
54
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
55
|
+
const meta = messages[i].metadata;
|
|
56
|
+
if (meta?.type === "context-summary") {
|
|
57
|
+
return i;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return -1;
|
|
61
|
+
}
|
|
62
|
+
function countSummaries(messages) {
|
|
63
|
+
let count = 0;
|
|
64
|
+
for (const msg of messages) {
|
|
65
|
+
const meta = msg.metadata;
|
|
66
|
+
if (meta?.type === "context-summary") count++;
|
|
67
|
+
}
|
|
68
|
+
return count;
|
|
69
|
+
}
|
|
70
|
+
function extractSummaryText(message) {
|
|
71
|
+
for (const part of message.parts) {
|
|
72
|
+
if (part.type === "text") {
|
|
73
|
+
return part.text ?? "";
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return "";
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/context/summarizer.ts
|
|
80
|
+
import { generateText } from "ai";
|
|
81
|
+
var DEFAULT_SUMMARY_PROMPT = `You are a conversation summariser. Produce a concise summary of the conversation below.
|
|
82
|
+
|
|
83
|
+
Focus on:
|
|
84
|
+
1. Key topics discussed and decisions made
|
|
85
|
+
2. Tasks completed and their outcomes (including important IDs, names, or values)
|
|
86
|
+
3. Pending tasks or open questions
|
|
87
|
+
4. Any in-progress multi-step work the assistant was performing
|
|
88
|
+
|
|
89
|
+
Be concise but preserve all actionable information. Output only the summary text, no preamble.`;
|
|
90
|
+
var MID_CHAIN_ADDENDUM = `
|
|
91
|
+
|
|
92
|
+
IMPORTANT: The assistant was in the middle of executing a multi-step task when this summary was requested. Make sure to clearly note what step it was on and what remains to be done, so the task can be resumed seamlessly.`;
|
|
93
|
+
async function summarize(messages, options) {
|
|
94
|
+
const conversationText = formatConversation(messages);
|
|
95
|
+
let systemPrompt = options.prompt ?? DEFAULT_SUMMARY_PROMPT;
|
|
96
|
+
if (options.midChain) {
|
|
97
|
+
systemPrompt += MID_CHAIN_ADDENDUM;
|
|
98
|
+
}
|
|
99
|
+
const { text } = await generateText({
|
|
100
|
+
model: options.model,
|
|
101
|
+
system: systemPrompt,
|
|
102
|
+
prompt: conversationText
|
|
103
|
+
});
|
|
104
|
+
return text;
|
|
105
|
+
}
|
|
106
|
+
function formatConversation(messages) {
|
|
107
|
+
const lines = [];
|
|
108
|
+
for (const msg of messages) {
|
|
109
|
+
const role = msg.role.toUpperCase();
|
|
110
|
+
const textParts = [];
|
|
111
|
+
const toolParts = [];
|
|
112
|
+
for (const part of msg.parts) {
|
|
113
|
+
if (part.type === "text") {
|
|
114
|
+
const text = part.text;
|
|
115
|
+
if (text?.trim()) textParts.push(text.trim());
|
|
116
|
+
} else if (part.type === "tool-invocation") {
|
|
117
|
+
const inv = part;
|
|
118
|
+
const name = inv.toolName ?? "unknown";
|
|
119
|
+
const state = inv.state ?? "";
|
|
120
|
+
if (state === "output-available" && inv.output != null) {
|
|
121
|
+
const outputStr = typeof inv.output === "string" ? inv.output : JSON.stringify(inv.output, null, 0);
|
|
122
|
+
const truncated = outputStr.length > 2e3 ? outputStr.slice(0, 2e3) + "... (truncated)" : outputStr;
|
|
123
|
+
toolParts.push(`[Tool: ${name} \u2192 ${truncated}]`);
|
|
124
|
+
} else {
|
|
125
|
+
toolParts.push(`[Tool: ${name} (${state || "pending"})]`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const content = [...textParts, ...toolParts].join("\n");
|
|
130
|
+
if (content.trim()) {
|
|
131
|
+
lines.push(`${role}: ${content}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return lines.join("\n\n");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// src/context/context-manager.ts
|
|
138
|
+
var DEFAULT_MAX_TOKENS = 128e3;
|
|
139
|
+
var DEFAULT_SOFT_THRESHOLD = 0.75;
|
|
140
|
+
var DEFAULT_HARD_THRESHOLD = 0.9;
|
|
141
|
+
var DEFAULT_RESPONSE_RESERVE = 4096;
|
|
142
|
+
var DEFAULT_MIN_RECENT = 4;
|
|
143
|
+
function generateId() {
|
|
144
|
+
return `ctx-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
145
|
+
}
|
|
146
|
+
function createContextManager(options) {
|
|
147
|
+
const maxTokens = options?.maxTokens ?? DEFAULT_MAX_TOKENS;
|
|
148
|
+
const softThreshold = options?.softThreshold ?? DEFAULT_SOFT_THRESHOLD;
|
|
149
|
+
const hardThreshold = options?.hardThreshold ?? DEFAULT_HARD_THRESHOLD;
|
|
150
|
+
const responseReserve = options?.responseReserve ?? DEFAULT_RESPONSE_RESERVE;
|
|
151
|
+
const minRecentMessages = options?.minRecentMessages ?? DEFAULT_MIN_RECENT;
|
|
152
|
+
const estimateTokens2 = options?.estimateTokens ?? estimateTokens;
|
|
153
|
+
const effectiveBudget = maxTokens - responseReserve;
|
|
154
|
+
let requestInputTokens = 0;
|
|
155
|
+
let requestOutputTokens = 0;
|
|
156
|
+
let estimatedContextSize = 0;
|
|
157
|
+
function getCumulativeTotals(messages) {
|
|
158
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
159
|
+
const meta = messages[i].metadata;
|
|
160
|
+
if (meta?.context) {
|
|
161
|
+
return {
|
|
162
|
+
totalTokens: meta.context.totalTokens ?? 0,
|
|
163
|
+
inputTokens: meta.context.inputTokens ?? 0,
|
|
164
|
+
outputTokens: meta.context.outputTokens ?? 0
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return { totalTokens: 0, inputTokens: 0, outputTokens: 0 };
|
|
169
|
+
}
|
|
170
|
+
function computeUsageRatio() {
|
|
171
|
+
if (effectiveBudget <= 0) return 0;
|
|
172
|
+
return Math.min(Math.max(estimatedContextSize / effectiveBudget, 0), 1);
|
|
173
|
+
}
|
|
174
|
+
const manager = {
|
|
175
|
+
getState() {
|
|
176
|
+
const ratio = computeUsageRatio();
|
|
177
|
+
return {
|
|
178
|
+
totalInputTokens: requestInputTokens,
|
|
179
|
+
totalOutputTokens: requestOutputTokens,
|
|
180
|
+
estimatedContextSize,
|
|
181
|
+
maxTokens,
|
|
182
|
+
usageRatio: ratio,
|
|
183
|
+
softThresholdExceeded: ratio > softThreshold,
|
|
184
|
+
hardThresholdExceeded: ratio > hardThreshold,
|
|
185
|
+
summaryCount: 0
|
|
186
|
+
// updated by updateEstimate
|
|
187
|
+
};
|
|
188
|
+
},
|
|
189
|
+
recordUsage(usage) {
|
|
190
|
+
requestInputTokens += usage.inputTokens ?? 0;
|
|
191
|
+
requestOutputTokens += usage.outputTokens ?? 0;
|
|
192
|
+
if (usage.inputTokens != null && usage.inputTokens > 0) {
|
|
193
|
+
estimatedContextSize = usage.inputTokens;
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
updateEstimate(messages) {
|
|
197
|
+
const { messages: trimmed } = prepareMessages(messages, "");
|
|
198
|
+
estimatedContextSize = estimateTokens2(trimmed);
|
|
199
|
+
},
|
|
200
|
+
async prepareMessages(messages, systemPrompt) {
|
|
201
|
+
return prepareMessages(messages, systemPrompt);
|
|
202
|
+
},
|
|
203
|
+
shouldCompactify(messages) {
|
|
204
|
+
const { messages: trimmed } = prepareMessages(messages, "");
|
|
205
|
+
const estimate = estimateTokens2(trimmed);
|
|
206
|
+
return estimate / effectiveBudget > hardThreshold;
|
|
207
|
+
},
|
|
208
|
+
shouldSummarize(messages) {
|
|
209
|
+
const { messages: trimmed } = prepareMessages(messages, "");
|
|
210
|
+
const estimate = estimateTokens2(trimmed);
|
|
211
|
+
const ratio = estimate / effectiveBudget;
|
|
212
|
+
return ratio > softThreshold && ratio <= hardThreshold;
|
|
213
|
+
},
|
|
214
|
+
async compactify(messages) {
|
|
215
|
+
if (options?.compactify) {
|
|
216
|
+
return options.compactify(messages);
|
|
217
|
+
}
|
|
218
|
+
if (!options?.summaryModel) {
|
|
219
|
+
throw new Error(
|
|
220
|
+
"createContextManager: either `summaryModel` or a custom `compactify` function is required to perform compactification."
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
if (messages.length <= minRecentMessages) {
|
|
224
|
+
return messages;
|
|
225
|
+
}
|
|
226
|
+
const splitIdx = messages.length - minRecentMessages;
|
|
227
|
+
const messagesToSummarize = messages.slice(0, splitIdx);
|
|
228
|
+
const recentMessages = messages.slice(splitIdx);
|
|
229
|
+
const lastMsg = messages[messages.length - 1];
|
|
230
|
+
const midChain = lastMsg?.role === "assistant" && lastMsg.parts.some(
|
|
231
|
+
(p) => p.type === "tool-invocation" && p.state !== "output-available"
|
|
232
|
+
);
|
|
233
|
+
const summaryText = await summarize(messagesToSummarize, {
|
|
234
|
+
model: options.summaryModel,
|
|
235
|
+
prompt: options.summaryPrompt,
|
|
236
|
+
midChain
|
|
237
|
+
});
|
|
238
|
+
const originalTokens = estimateTokens2(messagesToSummarize);
|
|
239
|
+
const summaryMessage = {
|
|
240
|
+
id: generateId(),
|
|
241
|
+
role: "assistant",
|
|
242
|
+
parts: [{ type: "text", text: summaryText }],
|
|
243
|
+
metadata: {
|
|
244
|
+
type: "context-summary",
|
|
245
|
+
messagesSummarized: messagesToSummarize.length,
|
|
246
|
+
originalTokens,
|
|
247
|
+
summaryTokens: estimateTokens2([
|
|
248
|
+
{
|
|
249
|
+
id: "tmp",
|
|
250
|
+
role: "assistant",
|
|
251
|
+
parts: [{ type: "text", text: summaryText }]
|
|
252
|
+
}
|
|
253
|
+
]),
|
|
254
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
return [summaryMessage, ...recentMessages];
|
|
258
|
+
},
|
|
259
|
+
getMessageMetadata() {
|
|
260
|
+
return {
|
|
261
|
+
context: {
|
|
262
|
+
inputTokens: requestInputTokens,
|
|
263
|
+
outputTokens: requestOutputTokens,
|
|
264
|
+
totalTokens: requestInputTokens + requestOutputTokens,
|
|
265
|
+
usageRatio: computeUsageRatio()
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
},
|
|
269
|
+
reset() {
|
|
270
|
+
requestInputTokens = 0;
|
|
271
|
+
requestOutputTokens = 0;
|
|
272
|
+
estimatedContextSize = 0;
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
const originalGetState = manager.getState.bind(manager);
|
|
276
|
+
manager.getState = function getStateWithSummaryCount() {
|
|
277
|
+
const state = originalGetState();
|
|
278
|
+
return state;
|
|
279
|
+
};
|
|
280
|
+
return manager;
|
|
281
|
+
}
|
|
282
|
+
export {
|
|
283
|
+
countSummaries,
|
|
284
|
+
createContextManager,
|
|
285
|
+
estimateTokens,
|
|
286
|
+
findLastSummaryIndex,
|
|
287
|
+
prepareMessages,
|
|
288
|
+
summarize
|
|
289
|
+
};
|
|
290
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/context/token-estimator.ts","../src/context/message-preparation.ts","../src/context/summarizer.ts","../src/context/context-manager.ts"],"sourcesContent":["import type { UIMessage } from \"ai\";\n\n/** Approximate characters per token for mixed English text and code. */\nconst CHARS_PER_TOKEN = 4;\n\n/** Overhead tokens added per message (role, framing). */\nconst MESSAGE_OVERHEAD = 4;\n\n/** Base overhead for the conversation itself. */\nconst CONVERSATION_OVERHEAD = 3;\n\n/**\n * Estimate the token count of a UIMessage array using a character-based heuristic.\n *\n * This is intentionally model-agnostic (~4 chars per token) and errs slightly\n * on the conservative side. Pass a custom `estimateTokens` to\n * `createContextManager` if you need model-specific accuracy.\n */\nexport function estimateTokens(messages: UIMessage[]): number {\n let totalChars = 0;\n\n for (const msg of messages) {\n totalChars += MESSAGE_OVERHEAD * CHARS_PER_TOKEN; // per-message overhead\n\n for (const part of msg.parts) {\n switch (part.type) {\n case \"text\":\n totalChars += ((part as { text: string }).text ?? \"\").length;\n break;\n\n case \"tool-invocation\": {\n // Estimate from the serialised input + output\n const inv = part as {\n toolName?: string;\n input?: unknown;\n output?: unknown;\n };\n if (inv.toolName) totalChars += inv.toolName.length;\n if (inv.input) totalChars += JSON.stringify(inv.input).length;\n if (inv.output) totalChars += JSON.stringify(inv.output).length;\n break;\n }\n\n case \"file\": {\n // Only count the URL length (actual file content is handled by the provider)\n const file = part as { url?: string };\n totalChars += (file.url ?? \"\").length;\n break;\n }\n\n default:\n // Catch-all for unknown part types\n try {\n totalChars += JSON.stringify(part).length;\n } catch {\n totalChars += 50; // small fallback\n }\n }\n }\n }\n\n return Math.ceil(totalChars / CHARS_PER_TOKEN) + CONVERSATION_OVERHEAD;\n}\n","import type { UIMessage } from \"ai\";\nimport type { ContextSummaryMetadata } from \"./types.js\";\n\nconst SUMMARY_PREAMBLE =\n \"\\n\\n[Previous conversation summary]\\nThe following is a summary of the conversation history that has been compacted to save context space:\\n\\n\";\n\n/**\n * Prepare messages for an LLM call by trimming everything before the last\n * context-summary message and injecting the summary into the system prompt.\n *\n * - If no summary exists, all messages pass through unchanged.\n * - If a summary exists at index `i`, messages `[0..i]` are dropped and the\n * summary text is appended to the system prompt.\n *\n * Neither the input array nor any individual message is mutated.\n */\nexport function prepareMessages(\n messages: UIMessage[],\n systemPrompt: string\n): { messages: UIMessage[]; systemPrompt: string } {\n const summaryIdx = findLastSummaryIndex(messages);\n\n if (summaryIdx === -1) {\n return { messages, systemPrompt };\n }\n\n const summaryMessage = messages[summaryIdx];\n const summaryText = extractSummaryText(summaryMessage);\n const trimmedMessages = messages.slice(summaryIdx + 1);\n\n return {\n messages: trimmedMessages,\n systemPrompt: systemPrompt + SUMMARY_PREAMBLE + summaryText,\n };\n}\n\n/**\n * Find the index of the last message that is a context summary.\n * Returns -1 if none found.\n */\nexport function findLastSummaryIndex(messages: UIMessage[]): number {\n for (let i = messages.length - 1; i >= 0; i--) {\n const meta = messages[i].metadata as ContextSummaryMetadata | undefined;\n if (meta?.type === \"context-summary\") {\n return i;\n }\n }\n return -1;\n}\n\n/** Count how many context-summary messages exist in the array. */\nexport function countSummaries(messages: UIMessage[]): number {\n let count = 0;\n for (const msg of messages) {\n const meta = msg.metadata as ContextSummaryMetadata | undefined;\n if (meta?.type === \"context-summary\") count++;\n }\n return count;\n}\n\n/** Extract the text content from a summary message. */\nfunction extractSummaryText(message: UIMessage): string {\n for (const part of message.parts) {\n if (part.type === \"text\") {\n return (part as { text: string }).text ?? \"\";\n }\n }\n return \"\";\n}\n","import { generateText, type UIMessage } from \"ai\";\n\nconst DEFAULT_SUMMARY_PROMPT = `You are a conversation summariser. Produce a concise summary of the conversation below.\n\nFocus on:\n1. Key topics discussed and decisions made\n2. Tasks completed and their outcomes (including important IDs, names, or values)\n3. Pending tasks or open questions\n4. Any in-progress multi-step work the assistant was performing\n\nBe concise but preserve all actionable information. Output only the summary text, no preamble.`;\n\nconst MID_CHAIN_ADDENDUM = `\\n\\nIMPORTANT: The assistant was in the middle of executing a multi-step task when this summary was requested. Make sure to clearly note what step it was on and what remains to be done, so the task can be resumed seamlessly.`;\n\n/**\n * Summarise a list of UIMessages into a single prose block\n * using an LLM call via the AI SDK's `generateText`.\n */\nexport async function summarize(\n messages: UIMessage[],\n options: {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n model: any;\n prompt?: string;\n /** Set to true when compacting mid-tool-chain */\n midChain?: boolean;\n }\n): Promise<string> {\n const conversationText = formatConversation(messages);\n\n let systemPrompt = options.prompt ?? DEFAULT_SUMMARY_PROMPT;\n if (options.midChain) {\n systemPrompt += MID_CHAIN_ADDENDUM;\n }\n\n const { text } = await generateText({\n model: options.model,\n system: systemPrompt,\n prompt: conversationText,\n });\n\n return text;\n}\n\n/** Convert UIMessage[] into a plain-text conversation transcript for the summariser. */\nfunction formatConversation(messages: UIMessage[]): string {\n const lines: string[] = [];\n\n for (const msg of messages) {\n const role = msg.role.toUpperCase();\n const textParts: string[] = [];\n const toolParts: string[] = [];\n\n for (const part of msg.parts) {\n if (part.type === \"text\") {\n const text = (part as { text: string }).text;\n if (text?.trim()) textParts.push(text.trim());\n } else if (part.type === \"tool-invocation\") {\n const inv = part as { toolName?: string; output?: unknown; state?: string };\n const name = inv.toolName ?? \"unknown\";\n const state = inv.state ?? \"\";\n if (state === \"output-available\" && inv.output != null) {\n const outputStr =\n typeof inv.output === \"string\"\n ? inv.output\n : JSON.stringify(inv.output, null, 0);\n // Truncate very large outputs to keep the summariser prompt reasonable\n const truncated =\n outputStr.length > 2000\n ? outputStr.slice(0, 2000) + \"... (truncated)\"\n : outputStr;\n toolParts.push(`[Tool: ${name} → ${truncated}]`);\n } else {\n toolParts.push(`[Tool: ${name} (${state || \"pending\"})]`);\n }\n }\n }\n\n const content = [...textParts, ...toolParts].join(\"\\n\");\n if (content.trim()) {\n lines.push(`${role}: ${content}`);\n }\n }\n\n return lines.join(\"\\n\\n\");\n}\n","import type { UIMessage } from \"ai\";\nimport type {\n ContextManager,\n ContextManagerOptions,\n ContextMessageMetadata,\n ContextState,\n ContextSummaryMetadata,\n} from \"./types.js\";\nimport { estimateTokens as defaultEstimateTokens } from \"./token-estimator.js\";\nimport {\n prepareMessages as doPrepareMessages,\n findLastSummaryIndex,\n countSummaries,\n} from \"./message-preparation.js\";\nimport { summarize } from \"./summarizer.js\";\n\nconst DEFAULT_MAX_TOKENS = 128_000;\nconst DEFAULT_SOFT_THRESHOLD = 0.75;\nconst DEFAULT_HARD_THRESHOLD = 0.9;\nconst DEFAULT_RESPONSE_RESERVE = 4096;\nconst DEFAULT_MIN_RECENT = 4;\n\n/** Generate a simple unique ID for summary messages. */\nfunction generateId(): string {\n return `ctx-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;\n}\n\n/**\n * Create a context manager that tracks token usage, detects threshold\n * breaches, and can compactify message history via summarisation.\n *\n * The manager is stateless across HTTP requests — it derives cumulative\n * totals from message metadata written by previous requests and\n * accumulates usage recorded during the current request.\n */\nexport function createContextManager(\n options?: ContextManagerOptions\n): ContextManager {\n const maxTokens = options?.maxTokens ?? DEFAULT_MAX_TOKENS;\n const softThreshold = options?.softThreshold ?? DEFAULT_SOFT_THRESHOLD;\n const hardThreshold = options?.hardThreshold ?? DEFAULT_HARD_THRESHOLD;\n const responseReserve = options?.responseReserve ?? DEFAULT_RESPONSE_RESERVE;\n const minRecentMessages = options?.minRecentMessages ?? DEFAULT_MIN_RECENT;\n const estimateTokens = options?.estimateTokens ?? defaultEstimateTokens;\n\n // Effective budget = maxTokens minus what we reserve for the response\n const effectiveBudget = maxTokens - responseReserve;\n\n // ── Mutable per-request state ──────────────────────────────────────\n\n /** Input tokens recorded during this request (from onStepFinish). */\n let requestInputTokens = 0;\n /** Output tokens recorded during this request. */\n let requestOutputTokens = 0;\n /** Latest estimated context size in tokens. */\n let estimatedContextSize = 0;\n\n // ── Helpers ────────────────────────────────────────────────────────\n\n /** Read cumulative totals from the last assistant message that has context metadata. */\n function getCumulativeTotals(messages: UIMessage[]): {\n totalTokens: number;\n inputTokens: number;\n outputTokens: number;\n } {\n for (let i = messages.length - 1; i >= 0; i--) {\n const meta = messages[i].metadata as ContextMessageMetadata | undefined;\n if (meta?.context) {\n return {\n totalTokens: meta.context.totalTokens ?? 0,\n inputTokens: meta.context.inputTokens ?? 0,\n outputTokens: meta.context.outputTokens ?? 0,\n };\n }\n }\n return { totalTokens: 0, inputTokens: 0, outputTokens: 0 };\n }\n\n function computeUsageRatio(): number {\n if (effectiveBudget <= 0) return 0;\n return Math.min(Math.max(estimatedContextSize / effectiveBudget, 0), 1);\n }\n\n // ── ContextManager implementation ──────────────────────────────────\n\n const manager: ContextManager = {\n getState(): ContextState {\n const ratio = computeUsageRatio();\n return {\n totalInputTokens: requestInputTokens,\n totalOutputTokens: requestOutputTokens,\n estimatedContextSize,\n maxTokens,\n usageRatio: ratio,\n softThresholdExceeded: ratio > softThreshold,\n hardThresholdExceeded: ratio > hardThreshold,\n summaryCount: 0, // updated by updateEstimate\n };\n },\n\n recordUsage(usage) {\n requestInputTokens += usage.inputTokens ?? 0;\n requestOutputTokens += usage.outputTokens ?? 0;\n // Use actual input tokens as the new context size estimate\n // (they reflect the real token count the provider saw)\n if (usage.inputTokens != null && usage.inputTokens > 0) {\n estimatedContextSize = usage.inputTokens;\n }\n },\n\n updateEstimate(messages) {\n // After prepareMessages, estimate what the LLM would see\n const { messages: trimmed } = doPrepareMessages(messages, \"\");\n estimatedContextSize = estimateTokens(trimmed);\n },\n\n async prepareMessages(messages, systemPrompt) {\n return doPrepareMessages(messages, systemPrompt);\n },\n\n shouldCompactify(messages) {\n // Re-estimate in case messages changed since last updateEstimate\n const { messages: trimmed } = doPrepareMessages(messages, \"\");\n const estimate = estimateTokens(trimmed);\n return estimate / effectiveBudget > hardThreshold;\n },\n\n shouldSummarize(messages) {\n const { messages: trimmed } = doPrepareMessages(messages, \"\");\n const estimate = estimateTokens(trimmed);\n const ratio = estimate / effectiveBudget;\n return ratio > softThreshold && ratio <= hardThreshold;\n },\n\n async compactify(messages) {\n // If a custom compactify is provided, delegate entirely\n if (options?.compactify) {\n return options.compactify(messages);\n }\n\n if (!options?.summaryModel) {\n throw new Error(\n \"createContextManager: either `summaryModel` or a custom `compactify` function is required to perform compactification.\"\n );\n }\n\n // Don't compact if there aren't enough messages\n if (messages.length <= minRecentMessages) {\n return messages;\n }\n\n // Split: everything before the cutpoint gets summarised\n const splitIdx = messages.length - minRecentMessages;\n const messagesToSummarize = messages.slice(0, splitIdx);\n const recentMessages = messages.slice(splitIdx);\n\n // Detect if we're mid-chain (last message is assistant with tool invocations\n // that might indicate an in-progress workflow)\n const lastMsg = messages[messages.length - 1];\n const midChain =\n lastMsg?.role === \"assistant\" &&\n lastMsg.parts.some(\n (p) =>\n p.type === \"tool-invocation\" &&\n (p as { state?: string }).state !== \"output-available\"\n );\n\n const summaryText = await summarize(messagesToSummarize, {\n model: options.summaryModel,\n prompt: options.summaryPrompt,\n midChain,\n });\n\n const originalTokens = estimateTokens(messagesToSummarize);\n const summaryMessage: UIMessage = {\n id: generateId(),\n role: \"assistant\",\n parts: [{ type: \"text\", text: summaryText }],\n metadata: {\n type: \"context-summary\",\n messagesSummarized: messagesToSummarize.length,\n originalTokens,\n summaryTokens: estimateTokens([\n {\n id: \"tmp\",\n role: \"assistant\",\n parts: [{ type: \"text\", text: summaryText }],\n },\n ]),\n createdAt: new Date().toISOString(),\n } satisfies ContextSummaryMetadata,\n };\n\n return [summaryMessage, ...recentMessages];\n },\n\n getMessageMetadata(): ContextMessageMetadata {\n return {\n context: {\n inputTokens: requestInputTokens,\n outputTokens: requestOutputTokens,\n totalTokens: requestInputTokens + requestOutputTokens,\n usageRatio: computeUsageRatio(),\n },\n };\n },\n\n reset() {\n requestInputTokens = 0;\n requestOutputTokens = 0;\n estimatedContextSize = 0;\n },\n };\n\n // Patch getState to include summaryCount dynamically\n const originalGetState = manager.getState.bind(manager);\n manager.getState = function getStateWithSummaryCount() {\n const state = originalGetState();\n // summaryCount is derived; caller should use countSummaries if needed\n return state;\n };\n\n return manager;\n}\n\nexport { countSummaries };\n"],"mappings":"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","names":["estimateTokens"]}
|