@context-chef/ai-sdk-middleware 1.1.5 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -0
- package/README.zh-CN.md +20 -0
- package/dist/index.cjs +31 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -0
- package/dist/index.d.mts +25 -0
- package/dist/index.mjs +31 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -96,6 +96,25 @@ const model = withContextChef(openai('gpt-4o'), {
|
|
|
96
96
|
});
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
+
Per-tool overrides via `perTool` — bare strings preserve a tool entirely (storage is also bypassed), object entries override `threshold` / `headChars` / `tailChars` for that one tool:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
const model = withContextChef(openai('gpt-4o'), {
|
|
103
|
+
contextWindow: 128_000,
|
|
104
|
+
truncate: {
|
|
105
|
+
threshold: 5000,
|
|
106
|
+
tailChars: 1000,
|
|
107
|
+
perTool: [
|
|
108
|
+
'read_file', // never truncate; not stored in VFS
|
|
109
|
+
{ name: 'fetch_logs', threshold: 50_000 }, // higher threshold
|
|
110
|
+
{ name: 'big_query', tailChars: 5000 }, // bigger tail
|
|
111
|
+
],
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Tools not listed fall back to the top-level defaults. The lookup key is `tool-result.toolName`, and filtering is applied **per `tool-result` part** — so a single tool message can mix preserved and truncated parts. (The TanStack middleware applies the same option per message instead, since one TanStack tool message corresponds to a single tool call.) Wildcards are not supported, and `storage` cannot be overridden per-tool. `perTool` only affects the truncate step itself — a preserved message may still be dropped by `compact` (when `compact.toolCalls` targets it), summarized by `compress` over the token budget, or rewritten by `transformContext`.
|
|
117
|
+
|
|
99
118
|
### Token Budget Tracking
|
|
100
119
|
|
|
101
120
|
The middleware automatically extracts token usage from `generateText` and `streamText` responses and feeds it back to the compression engine. No manual `reportTokenUsage()` calls needed.
|
|
@@ -149,6 +168,7 @@ const wrappedModel = withContextChef(model, options);
|
|
|
149
168
|
| `truncate.headChars` | `number` | No | Characters to preserve from start (default: `0`) |
|
|
150
169
|
| `truncate.tailChars` | `number` | No | Characters to preserve from end (default: `1000`) |
|
|
151
170
|
| `truncate.storage` | `VFSStorageAdapter` | No | Storage adapter to persist original content before truncation |
|
|
171
|
+
| `truncate.perTool` | `Array<string \| { name; threshold?; headChars?; tailChars? }>` | No | Per-tool overrides. Bare string = preserve (and bypass storage); object = override params for that tool. Last entry wins on duplicates. |
|
|
152
172
|
| `compact` | `CompactConfig` | No | Mechanical message pruning (reasoning, tool calls). Delegates to AI SDK's `pruneMessages` |
|
|
153
173
|
| `tokenizer` | `(msgs) => number` | No | Custom tokenizer for precise counting |
|
|
154
174
|
| `onCompress` | `(summary, count) => void` | No | Hook called after compression |
|
package/README.zh-CN.md
CHANGED
|
@@ -98,6 +98,25 @@ const model = withContextChef(openai('gpt-4o'), {
|
|
|
98
98
|
});
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
+
通过 `perTool` 做按工具覆写 —— 字符串条目完全保留该工具(同时跳过 VFS 写入),对象条目则只针对该工具覆盖 `threshold` / `headChars` / `tailChars`:
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
const model = withContextChef(openai('gpt-4o'), {
|
|
105
|
+
contextWindow: 128_000,
|
|
106
|
+
truncate: {
|
|
107
|
+
threshold: 5000,
|
|
108
|
+
tailChars: 1000,
|
|
109
|
+
perTool: [
|
|
110
|
+
'read_file', // 永不截断;也不写入 VFS
|
|
111
|
+
{ name: 'fetch_logs', threshold: 50_000 }, // 提高阈值
|
|
112
|
+
{ name: 'big_query', tailChars: 5000 }, // 保留更多尾部
|
|
113
|
+
],
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
未列出的工具继续使用顶层默认值。查找键是 `tool-result.toolName`,过滤粒度是**单个 `tool-result` part** —— 因此同一条 tool 消息可以混合保留与截断的 part。(TanStack 中间件的同名选项粒度是**单条消息**,因为 TanStack 的一条 tool 消息对应一次 tool 调用。)不支持通配符,`storage` 也无法按工具覆写。`perTool` 只控制 truncate 这一步 —— 保留下来的消息仍可能被 `compact` 整条删除(若 `compact.toolCalls` 命中)、被 `compress` 在超出 token 预算时摘要,或被 `transformContext` 改写。
|
|
119
|
+
|
|
101
120
|
### Token 预算追踪
|
|
102
121
|
|
|
103
122
|
中间件自动从 `generateText` 和 `streamText` 响应中提取 token 用量,并回传给压缩引擎。无需手动调用 `reportTokenUsage()`。
|
|
@@ -151,6 +170,7 @@ const wrappedModel = withContextChef(model, options);
|
|
|
151
170
|
| `truncate.headChars` | `number` | 否 | 保留开头的字符数(默认:`0`) |
|
|
152
171
|
| `truncate.tailChars` | `number` | 否 | 保留结尾的字符数(默认:`1000`) |
|
|
153
172
|
| `truncate.storage` | `VFSStorageAdapter` | 否 | 截断前持久化原始内容的存储适配器 |
|
|
173
|
+
| `truncate.perTool` | `Array<string \| { name; threshold?; headChars?; tailChars? }>` | 否 | 按工具覆写。字符串 = 保留(同时跳过存储);对象 = 为该工具覆写参数。重复名称时后者胜出。 |
|
|
154
174
|
| `compact` | `CompactConfig` | 否 | 机械消息裁剪(reasoning、工具调用)。委托给 AI SDK 的 `pruneMessages` |
|
|
155
175
|
| `tokenizer` | `(msgs) => number` | 否 | 自定义分词器用于精确计数 |
|
|
156
176
|
| `onCompress` | `(summary, count) => void` | 否 | 压缩完成后的回调 |
|
package/dist/index.cjs
CHANGED
|
@@ -193,6 +193,7 @@ async function truncateToolResults(prompt, options) {
|
|
|
193
193
|
adapter: storage,
|
|
194
194
|
storageDir: ""
|
|
195
195
|
}) : null;
|
|
196
|
+
const policy = buildPolicyMap(options.perTool);
|
|
196
197
|
const result = [];
|
|
197
198
|
for (const msg of prompt) {
|
|
198
199
|
if (msg.role !== "tool") {
|
|
@@ -205,16 +206,24 @@ async function truncateToolResults(prompt, options) {
|
|
|
205
206
|
newContent.push(part);
|
|
206
207
|
continue;
|
|
207
208
|
}
|
|
209
|
+
const toolPolicy = policy.get(part.toolName);
|
|
210
|
+
if (toolPolicy?.preserve) {
|
|
211
|
+
newContent.push(part);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
const effThreshold = toolPolicy?.threshold ?? threshold;
|
|
215
|
+
const effHeadChars = toolPolicy?.headChars ?? headChars;
|
|
216
|
+
const effTailChars = toolPolicy?.tailChars ?? tailChars;
|
|
208
217
|
const text = extractText(part.output);
|
|
209
|
-
if (text.length <=
|
|
218
|
+
if (text.length <= effThreshold || effHeadChars + effTailChars >= text.length) {
|
|
210
219
|
newContent.push(part);
|
|
211
220
|
continue;
|
|
212
221
|
}
|
|
213
222
|
if (offloader) try {
|
|
214
223
|
const vfsResult = await offloader.offloadAsync(text, {
|
|
215
|
-
threshold,
|
|
216
|
-
headChars,
|
|
217
|
-
tailChars
|
|
224
|
+
threshold: effThreshold,
|
|
225
|
+
headChars: effHeadChars,
|
|
226
|
+
tailChars: effTailChars
|
|
218
227
|
});
|
|
219
228
|
newContent.push({
|
|
220
229
|
...part,
|
|
@@ -227,8 +236,8 @@ async function truncateToolResults(prompt, options) {
|
|
|
227
236
|
} catch (error) {
|
|
228
237
|
console.warn(`[context-chef] Storage adapter write failed for tool result (${part.toolCallId}). Falling back to simple truncation. Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
229
238
|
}
|
|
230
|
-
const head = text.slice(0,
|
|
231
|
-
const tail = text.slice(text.length -
|
|
239
|
+
const head = text.slice(0, effHeadChars);
|
|
240
|
+
const tail = text.slice(text.length - effTailChars);
|
|
232
241
|
const truncated = [
|
|
233
242
|
head,
|
|
234
243
|
`\n--- truncated (${text.split("\n").length} lines, ${text.length} chars total) ---\n`,
|
|
@@ -249,6 +258,22 @@ async function truncateToolResults(prompt, options) {
|
|
|
249
258
|
}
|
|
250
259
|
return result;
|
|
251
260
|
}
|
|
261
|
+
/**
|
|
262
|
+
* Normalises `perTool` into a name → policy lookup.
|
|
263
|
+
* Bare strings become `{ preserve: true }`; objects keep their partial overrides.
|
|
264
|
+
* Last entry wins on duplicate names.
|
|
265
|
+
*/
|
|
266
|
+
function buildPolicyMap(perTool) {
|
|
267
|
+
const map = /* @__PURE__ */ new Map();
|
|
268
|
+
if (!perTool) return map;
|
|
269
|
+
for (const entry of perTool) if (typeof entry === "string") map.set(entry, { preserve: true });
|
|
270
|
+
else map.set(entry.name, {
|
|
271
|
+
threshold: entry.threshold,
|
|
272
|
+
headChars: entry.headChars,
|
|
273
|
+
tailChars: entry.tailChars
|
|
274
|
+
});
|
|
275
|
+
return map;
|
|
276
|
+
}
|
|
252
277
|
function extractText(output) {
|
|
253
278
|
switch (output.type) {
|
|
254
279
|
case "text":
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["Offloader","Janitor","XmlGenerator"],"sources":["../src/adapter.ts","../src/truncator.ts","../src/middleware.ts","../src/index.ts"],"sourcesContent":["import type {\n LanguageModelV3Message,\n LanguageModelV3Prompt,\n LanguageModelV3ToolResultOutput,\n LanguageModelV3ToolResultPart,\n SharedV3ProviderOptions,\n} from '@ai-sdk/provider';\nimport type { Attachment, Message, ToolCall } from '@context-chef/core';\n\n/** Content types for each AI SDK message role */\ntype UserContent = Extract<LanguageModelV3Message, { role: 'user' }>['content'];\ntype AssistantContent = Extract<LanguageModelV3Message, { role: 'assistant' }>['content'];\ntype ToolContent = Extract<LanguageModelV3Message, { role: 'tool' }>['content'];\n\n/**\n * Extended IR message with typed pass-through fields for lossless AI SDK round-trip.\n * Per-role content fields avoid union types, so no `as` casts are needed in `toAISDK`.\n */\nexport interface AISDKMessage extends Message {\n _userContent?: UserContent;\n _assistantContent?: AssistantContent;\n _toolContent?: ToolContent;\n _originalText?: string;\n _providerOptions?: SharedV3ProviderOptions;\n _toolName?: string;\n}\n\n/**\n * Converts an AI SDK V3 prompt to context-chef IR messages.\n *\n * Original AI SDK content is stored in per-role fields for lossless round-trip.\n * `_originalText` caches the extracted text so `toAISDK` can detect Janitor modifications.\n * `_providerOptions` preserves message-level provider options (e.g. Anthropic cache control).\n */\nexport function fromAISDK(prompt: LanguageModelV3Prompt): AISDKMessage[] {\n const messages: AISDKMessage[] = [];\n\n for (const msg of prompt) {\n if (msg.role === 'system') {\n messages.push({\n role: 'system',\n content: msg.content,\n ...(msg.providerOptions ? { _providerOptions: msg.providerOptions } : {}),\n });\n continue;\n }\n\n if (msg.role === 'user') {\n const text = msg.content\n .filter((p) => p.type === 'text')\n .map((p) => p.text)\n .join('\\n');\n\n const attachments: Attachment[] = [];\n for (const part of msg.content) {\n if (part.type === 'file') {\n // `attachment.data` here is just a presence/metadata signal for Janitor\n // (used by `m.attachments?.length` checks and the `[image]`/`[document]`\n // placeholder helper, neither of which read `data`). The real binary\n // payload — including `Uint8Array` / `URL` shapes — round-trips losslessly\n // through `_userContent`, which `toAISDK` hands back to the AI SDK\n // provider verbatim. We only record `data` when it's already a string,\n // so we never invent a fake encoding for non-string inputs.\n attachments.push({\n mediaType: part.mediaType,\n data: typeof part.data === 'string' ? part.data : '',\n ...(part.filename ? { filename: part.filename } : {}),\n });\n }\n }\n\n const m: AISDKMessage = {\n role: 'user',\n content: text,\n _userContent: msg.content,\n _originalText: text,\n ...(msg.providerOptions ? { _providerOptions: msg.providerOptions } : {}),\n };\n if (attachments.length) m.attachments = attachments;\n messages.push(m);\n continue;\n }\n\n if (msg.role === 'assistant') {\n const text: string[] = [];\n const toolCalls: ToolCall[] = [];\n const attachments: Attachment[] = [];\n let thinking: { thinking: string } | undefined;\n\n for (const part of msg.content) {\n if (part.type === 'text') text.push(part.text);\n else if (part.type === 'tool-call') {\n toolCalls.push({\n id: part.toolCallId,\n type: 'function',\n function: {\n name: part.toolName,\n arguments: typeof part.input === 'string' ? part.input : JSON.stringify(part.input),\n },\n });\n } else if (part.type === 'reasoning') {\n thinking = { thinking: part.text };\n } else if (part.type === 'file') {\n // See user-side comment above: data is a presence signal only;\n // _assistantContent carries the actual payload through round-trip.\n attachments.push({\n mediaType: part.mediaType,\n data: typeof part.data === 'string' ? part.data : '',\n ...(part.filename ? { filename: part.filename } : {}),\n });\n }\n }\n\n const joinedText = text.join('\\n');\n const m: AISDKMessage = {\n role: 'assistant',\n content: joinedText,\n _assistantContent: msg.content,\n _originalText: joinedText,\n ...(msg.providerOptions ? { _providerOptions: msg.providerOptions } : {}),\n };\n if (toolCalls.length > 0) m.tool_calls = toolCalls;\n if (thinking) m.thinking = thinking;\n if (attachments.length) m.attachments = attachments;\n messages.push(m);\n continue;\n }\n\n if (msg.role === 'tool') {\n for (const part of msg.content) {\n if (part.type === 'tool-result') {\n const text = stringifyToolOutput(part.output);\n messages.push({\n role: 'tool',\n content: text,\n tool_call_id: part.toolCallId,\n _toolContent: [part],\n _originalText: text,\n _toolName: part.toolName,\n });\n }\n }\n }\n }\n\n return messages;\n}\n\n/**\n * Narrows a generic Message to AISDKMessage for typed access to pass-through fields.\n */\nfunction asAISDK(msg: Message): AISDKMessage {\n return msg;\n}\n\n/**\n * Converts context-chef IR messages back to AI SDK V3 prompt format.\n *\n * Uses per-role original content when unmodified (detected via `_originalText`).\n * Falls back to constructing from IR fields when content was modified by Janitor\n * (e.g. compact() cleared tool results) or for new messages (e.g. compression summaries).\n */\nexport function toAISDK(messages: Message[]): LanguageModelV3Prompt {\n const prompt: LanguageModelV3Prompt = [];\n\n let i = 0;\n while (i < messages.length) {\n const msg = asAISDK(messages[i]);\n const contentModified = msg._originalText !== undefined && msg._originalText !== msg.content;\n\n if (msg.role === 'system') {\n prompt.push({\n role: 'system',\n content: msg.content,\n ...(msg._providerOptions ? { providerOptions: msg._providerOptions } : {}),\n });\n i++;\n continue;\n }\n\n if (msg.role === 'user') {\n prompt.push({\n role: 'user',\n content:\n !contentModified && msg._userContent\n ? msg._userContent\n : [{ type: 'text', text: msg.content }],\n ...(msg._providerOptions ? { providerOptions: msg._providerOptions } : {}),\n });\n i++;\n continue;\n }\n\n if (msg.role === 'assistant') {\n prompt.push({\n role: 'assistant',\n content:\n !contentModified && msg._assistantContent\n ? msg._assistantContent\n : [{ type: 'text', text: msg.content }],\n ...(msg._providerOptions ? { providerOptions: msg._providerOptions } : {}),\n });\n i++;\n continue;\n }\n\n if (msg.role === 'tool') {\n const toolResults: LanguageModelV3ToolResultPart[] = [];\n while (i < messages.length && messages[i].role === 'tool') {\n const toolMsg = asAISDK(messages[i]);\n const toolModified =\n toolMsg._originalText !== undefined && toolMsg._originalText !== toolMsg.content;\n\n if (!toolModified && toolMsg._toolContent) {\n for (const part of toolMsg._toolContent) {\n if (part.type === 'tool-result') {\n toolResults.push(part);\n }\n }\n } else {\n toolResults.push({\n type: 'tool-result',\n toolCallId: toolMsg.tool_call_id ?? '',\n toolName: toolMsg._toolName ?? 'unknown',\n output: { type: 'text', value: toolMsg.content },\n });\n }\n i++;\n }\n prompt.push({ role: 'tool', content: toolResults });\n continue;\n }\n\n i++;\n }\n\n return prompt;\n}\n\nfunction stringifyToolOutput(output: LanguageModelV3ToolResultOutput): string {\n switch (output.type) {\n case 'text':\n case 'error-text':\n return output.value;\n case 'json':\n case 'error-json':\n return JSON.stringify(output.value);\n case 'content':\n return output.value\n .map((v) => (v.type === 'text' ? v.text : ''))\n .filter(Boolean)\n .join('\\n');\n default:\n return JSON.stringify(output);\n }\n}\n","import type {\n LanguageModelV3Prompt,\n LanguageModelV3ToolResultOutput,\n LanguageModelV3ToolResultPart,\n} from '@ai-sdk/provider';\nimport { Offloader } from '@context-chef/core';\nimport type { TruncateOptions } from './types';\n\n/**\n * Truncates tool-result content within an AI SDK prompt when it exceeds the configured threshold.\n * When a storage adapter is provided, original content is persisted and a URI is included in the output.\n */\nexport async function truncateToolResults(\n prompt: LanguageModelV3Prompt,\n options: TruncateOptions,\n): Promise<LanguageModelV3Prompt> {\n const { threshold, headChars = 0, tailChars = 1000, storage } = options;\n\n const offloader = storage ? new Offloader({ threshold, adapter: storage, storageDir: '' }) : null;\n\n const result: LanguageModelV3Prompt = [];\n\n for (const msg of prompt) {\n if (msg.role !== 'tool') {\n result.push(msg);\n continue;\n }\n\n const newContent: typeof msg.content = [];\n\n for (const part of msg.content) {\n if (part.type !== 'tool-result') {\n newContent.push(part);\n continue;\n }\n\n const text = extractText(part.output);\n if (text.length <= threshold || headChars + tailChars >= text.length) {\n newContent.push(part);\n continue;\n }\n\n // With storage: use Offloader to persist original and get a URI-annotated truncation\n if (offloader) {\n try {\n const vfsResult = await offloader.offloadAsync(text, { threshold, headChars, tailChars });\n newContent.push({\n ...part,\n output: {\n type: 'text',\n value: vfsResult.content,\n } satisfies LanguageModelV3ToolResultOutput,\n } satisfies LanguageModelV3ToolResultPart);\n continue;\n } catch (error) {\n console.warn(\n `[context-chef] Storage adapter write failed for tool result (${part.toolCallId}). ` +\n `Falling back to simple truncation. Error: ${error instanceof Error ? error.message : String(error)}`,\n );\n // Fall through to simple truncation below\n }\n }\n\n // Without storage: simple truncation, original is discarded\n const head = text.slice(0, headChars);\n const tail = text.slice(text.length - tailChars);\n const totalLines = text.split('\\n').length;\n\n const truncated = [\n head,\n `\\n--- truncated (${totalLines} lines, ${text.length} chars total) ---\\n`,\n tail,\n ]\n .filter(Boolean)\n .join('')\n .trim();\n\n newContent.push({\n ...part,\n output: { type: 'text', value: truncated } satisfies LanguageModelV3ToolResultOutput,\n } satisfies LanguageModelV3ToolResultPart);\n }\n\n result.push({ ...msg, content: newContent });\n }\n\n return result;\n}\n\nfunction extractText(output: LanguageModelV3ToolResultOutput): string {\n switch (output.type) {\n case 'text':\n case 'error-text':\n return output.value;\n case 'json':\n case 'error-json':\n return JSON.stringify(output.value);\n case 'content':\n return output.value\n .map((v: { type: string; text?: string }) => (v.type === 'text' ? (v.text ?? '') : ''))\n .filter(Boolean)\n .join('\\n');\n default:\n return '';\n }\n}\n","import type {\n LanguageModelV3,\n LanguageModelV3Message,\n LanguageModelV3Prompt,\n LanguageModelV3StreamPart,\n} from '@ai-sdk/provider';\nimport { Janitor, type Message, XmlGenerator } from '@context-chef/core';\nimport { generateText, type LanguageModelMiddleware, type ModelMessage, pruneMessages } from 'ai';\n\nimport { fromAISDK, toAISDK } from './adapter';\nimport { truncateToolResults } from './truncator';\nimport type { ContextChefOptions, DynamicStateConfig } from './types';\n\ntype CompressRole = 'system' | 'user' | 'assistant';\n\n/**\n * Creates a LanguageModelMiddleware that transparently applies\n * context-chef compression and truncation to AI SDK model calls.\n *\n * The middleware holds a stateful Janitor instance that tracks\n * token usage across calls for compression decisions.\n */\nexport function createMiddleware(options: ContextChefOptions): LanguageModelMiddleware {\n let usageWarned = false;\n\n const janitor = new Janitor({\n contextWindow: options.contextWindow,\n tokenizer: options.tokenizer ? (msgs: Message[]) => options.tokenizer?.(msgs) ?? 0 : undefined,\n preserveRatio: options.compress?.preserveRatio ?? 0.8,\n compressionModel: options.compress?.model\n ? createCompressionAdapter(options.compress.model)\n : undefined,\n onCompress: options.onCompress\n ? (summary, count) => options.onCompress?.(summary.content, count)\n : undefined,\n onBeforeCompress: options.onBeforeCompress ?? options.onBudgetExceeded,\n });\n\n return {\n specificationVersion: 'v3',\n\n transformParams: async ({ params }) => {\n let { prompt } = params;\n\n // 1. Truncate large tool results\n if (options.truncate) {\n prompt = await truncateToolResults(prompt, options.truncate);\n }\n\n // 2. Compact (mechanical, zero LLM cost) via pruneMessages\n if (options.compact) {\n prompt = compactPrompt(prompt, options.compact);\n }\n\n // 3. Convert to IR and separate system messages from conversation.\n // System messages are standing instructions — they must not be\n // compressed away. Only conversation history goes through compact/compress.\n const allIR = fromAISDK(prompt);\n const systemMessages = allIR.filter((m) => m.role === 'system');\n let conversation = allIR.filter((m) => m.role !== 'system');\n\n // 4. Compress conversation history if over token budget\n conversation = await janitor.compress(conversation);\n\n // 5. Reassemble sandwich: user system + skill instructions + conversation.\n // The skill slot mirrors @context-chef/core compile() ordering\n // (SKILL_SPEC §6.3): a dedicated system message AFTER user system\n // and BEFORE the conversation history. Empty instructions are\n // skipped to avoid emitting an empty system message.\n const skillMessages = await resolveSkillMessages(options.skill);\n const irMessages = [...systemMessages, ...skillMessages, ...conversation];\n\n // 6. Convert back to AI SDK format\n prompt = toAISDK(irMessages);\n\n // 7. Dynamic state injection\n if (options.dynamicState) {\n prompt = await injectDynamicState(prompt, options.dynamicState);\n }\n\n // 8. Custom transform hook\n if (options.transformContext) {\n prompt = await options.transformContext(prompt);\n }\n\n return { ...params, prompt };\n },\n\n wrapGenerate: async ({ doGenerate }) => {\n const result = await doGenerate();\n\n if (result.usage?.inputTokens?.total != null) {\n janitor.feedTokenUsage(result.usage.inputTokens.total);\n } else if (!usageWarned && !options.tokenizer) {\n usageWarned = true;\n console.warn(\n '[context-chef] Model response did not include usage.inputTokens.total. ' +\n 'Token-based compression may not trigger accurately. ' +\n 'Consider providing a tokenizer for precise token counting.',\n );\n }\n\n return result;\n },\n\n wrapStream: async ({ doStream }) => {\n const { stream, ...rest } = await doStream();\n\n const transform = new TransformStream<LanguageModelV3StreamPart, LanguageModelV3StreamPart>({\n transform(chunk, controller) {\n if (chunk.type === 'finish') {\n if (chunk.usage?.inputTokens?.total != null) {\n janitor.feedTokenUsage(chunk.usage.inputTokens.total);\n } else if (!usageWarned && !options.tokenizer) {\n usageWarned = true;\n console.warn(\n '[context-chef] Stream finish did not include usage.inputTokens.total. ' +\n 'Token-based compression may not trigger accurately. ' +\n 'Consider providing a tokenizer for precise token counting.',\n );\n }\n }\n controller.enqueue(chunk);\n },\n });\n\n return { ...rest, stream: stream.pipeThrough(transform) };\n },\n };\n}\n\n/**\n * Prunes a LanguageModelV3Prompt via AI SDK's pruneMessages.\n *\n * LanguageModelV3Message (from @ai-sdk/provider) and ModelMessage\n * (from @ai-sdk/provider-utils) share identical runtime structure but\n * differ at the TypeScript level (e.g. ImagePart, FilePart.data).\n * Since pruneMessages only filters — never transforms — every content\n * part in the output is an original V3 part, making the casts safe.\n */\nfunction compactPrompt(\n prompt: LanguageModelV3Prompt,\n config: Omit<Parameters<typeof pruneMessages>[0], 'messages'>,\n): LanguageModelV3Prompt {\n const messages = prompt.map(\n (msg) =>\n ({\n role: msg.role,\n content: msg.content,\n providerOptions: msg.providerOptions,\n }) as ModelMessage,\n );\n const pruned = pruneMessages({ messages, ...config });\n return pruned.map(\n (msg) =>\n ({\n role: msg.role,\n content: msg.content,\n providerOptions: msg.providerOptions,\n }) as LanguageModelV3Message,\n );\n}\n\n/**\n * Resolves the `skill` option into IR system messages to insert between\n * user system messages and conversation. Returns `[]` when no skill is\n * active, the resolver returns null/undefined, or instructions are empty.\n *\n * The function form is invoked on every transformParams call so the\n * caller can swap skills dynamically without recreating the middleware.\n */\nasync function resolveSkillMessages(skill: ContextChefOptions['skill']): Promise<Message[]> {\n if (!skill) return [];\n const resolved = typeof skill === 'function' ? await skill() : skill;\n // Treat whitespace-only instructions as empty — they would otherwise pollute\n // the prompt and create a needless cache breakpoint between system and history.\n if (!resolved || !resolved.instructions || !resolved.instructions.trim()) return [];\n return [{ role: 'system', content: resolved.instructions }];\n}\n\n/**\n * Injects dynamic state XML into the AI SDK prompt.\n *\n * - `last_user`: Appends to the last user message's content parts.\n * Leverages Recency Bias for maximum LLM attention.\n * - `system`: Adds as a standalone system message at the end.\n */\nasync function injectDynamicState(\n prompt: LanguageModelV3Prompt,\n config: DynamicStateConfig,\n): Promise<LanguageModelV3Prompt> {\n const state = await config.getState();\n const xml = XmlGenerator.objectToXml(state, 'dynamic_state');\n const placement = config.placement ?? 'last_user';\n\n if (placement === 'system') {\n return [...prompt, { role: 'system', content: `CURRENT TASK STATE:\\n${xml}` }];\n }\n\n // last_user: inject into the last user message\n const result = [...prompt];\n const stateBlock = `\\n\\n${xml}\\nAbove is the current system state. Use it to guide your next action.`;\n\n for (let i = result.length - 1; i >= 0; i--) {\n const msg = result[i];\n if (msg.role === 'user') {\n result[i] = {\n ...msg,\n content: [...msg.content, { type: 'text', text: stateBlock }],\n };\n return result;\n }\n }\n\n // No user message found — append as new user message\n result.push({\n role: 'user',\n content: [{ type: 'text', text: stateBlock.trim() }],\n });\n return result;\n}\n\n/**\n * Maps an IR role to a role accepted by generateText.\n * Tool messages are handled separately before this is called.\n */\nfunction toCompressRole(role: string): CompressRole {\n if (role === 'system' || role === 'user' || role === 'assistant') return role;\n return 'user';\n}\n\n/**\n * Adapts an AI SDK LanguageModelV3 into the compressionModel callback\n * that Janitor expects: (messages: Message[]) => Promise<string>\n *\n * Tool messages are converted to user messages describing the tool interaction,\n * since generateText only accepts system/user/assistant roles.\n */\nfunction createCompressionAdapter(\n model: LanguageModelV3,\n): (messages: Message[]) => Promise<string> {\n return async (messages: Message[]): Promise<string> => {\n const formatted = messages.map((m): { role: CompressRole; content: string } => {\n if (m.role === 'tool') {\n return {\n role: 'user' satisfies CompressRole,\n content: `[Tool result${m.tool_call_id ? ` (${m.tool_call_id})` : ''}: ${m.content}]`,\n };\n }\n if (m.role === 'assistant' && m.tool_calls?.length) {\n const toolCallsDesc = m.tool_calls\n .map((tc) => `[Called tool: ${tc.function.name}(${tc.function.arguments})]`)\n .join('\\n');\n return {\n role: 'assistant' satisfies CompressRole,\n content: m.content ? `${m.content}\\n${toolCallsDesc}` : toolCallsDesc,\n };\n }\n return {\n role: toCompressRole(m.role),\n content: m.content,\n };\n });\n\n const { text } = await generateText({\n model,\n messages: formatted,\n maxOutputTokens: 2048,\n });\n\n return text || '[Compression produced no output]';\n };\n}\n","import type { LanguageModelV3 } from '@ai-sdk/provider';\nimport { wrapLanguageModel } from 'ai';\n\nimport { createMiddleware } from './middleware';\nimport type { ContextChefOptions } from './types';\n\nexport { type AISDKMessage, fromAISDK, toAISDK } from './adapter';\nexport { createMiddleware } from './middleware';\nexport type {\n CompactConfig,\n CompressOptions,\n ContextChefOptions,\n DynamicStateConfig,\n TruncateOptions,\n} from './types';\n\n/**\n * Wraps an AI SDK language model with context-chef middleware for\n * transparent history compression, tool result truncation, and token budget management.\n *\n * @example\n * ```typescript\n * import { withContextChef } from '@context-chef/ai-sdk-middleware';\n * import { openai } from '@ai-sdk/openai';\n * import { generateText } from 'ai';\n *\n * const model = withContextChef(openai('gpt-4o'), {\n * contextWindow: 128_000,\n * compress: { model: openai('gpt-4o-mini') },\n * truncate: { threshold: 5000, headChars: 500, tailChars: 1000 },\n * });\n *\n * // Use exactly like normal — zero other code changes\n * const result = await generateText({ model, messages, tools });\n * ```\n */\nexport function withContextChef(\n model: LanguageModelV3,\n options: ContextChefOptions,\n): LanguageModelV3 {\n const middleware = createMiddleware(options);\n return wrapLanguageModel({ model, middleware });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["Offloader","Janitor","XmlGenerator"],"sources":["../src/adapter.ts","../src/truncator.ts","../src/middleware.ts","../src/index.ts"],"sourcesContent":["import type {\n LanguageModelV3Message,\n LanguageModelV3Prompt,\n LanguageModelV3ToolResultOutput,\n LanguageModelV3ToolResultPart,\n SharedV3ProviderOptions,\n} from '@ai-sdk/provider';\nimport type { Attachment, Message, ToolCall } from '@context-chef/core';\n\n/** Content types for each AI SDK message role */\ntype UserContent = Extract<LanguageModelV3Message, { role: 'user' }>['content'];\ntype AssistantContent = Extract<LanguageModelV3Message, { role: 'assistant' }>['content'];\ntype ToolContent = Extract<LanguageModelV3Message, { role: 'tool' }>['content'];\n\n/**\n * Extended IR message with typed pass-through fields for lossless AI SDK round-trip.\n * Per-role content fields avoid union types, so no `as` casts are needed in `toAISDK`.\n */\nexport interface AISDKMessage extends Message {\n _userContent?: UserContent;\n _assistantContent?: AssistantContent;\n _toolContent?: ToolContent;\n _originalText?: string;\n _providerOptions?: SharedV3ProviderOptions;\n _toolName?: string;\n}\n\n/**\n * Converts an AI SDK V3 prompt to context-chef IR messages.\n *\n * Original AI SDK content is stored in per-role fields for lossless round-trip.\n * `_originalText` caches the extracted text so `toAISDK` can detect Janitor modifications.\n * `_providerOptions` preserves message-level provider options (e.g. Anthropic cache control).\n */\nexport function fromAISDK(prompt: LanguageModelV3Prompt): AISDKMessage[] {\n const messages: AISDKMessage[] = [];\n\n for (const msg of prompt) {\n if (msg.role === 'system') {\n messages.push({\n role: 'system',\n content: msg.content,\n ...(msg.providerOptions ? { _providerOptions: msg.providerOptions } : {}),\n });\n continue;\n }\n\n if (msg.role === 'user') {\n const text = msg.content\n .filter((p) => p.type === 'text')\n .map((p) => p.text)\n .join('\\n');\n\n const attachments: Attachment[] = [];\n for (const part of msg.content) {\n if (part.type === 'file') {\n // `attachment.data` here is just a presence/metadata signal for Janitor\n // (used by `m.attachments?.length` checks and the `[image]`/`[document]`\n // placeholder helper, neither of which read `data`). The real binary\n // payload — including `Uint8Array` / `URL` shapes — round-trips losslessly\n // through `_userContent`, which `toAISDK` hands back to the AI SDK\n // provider verbatim. We only record `data` when it's already a string,\n // so we never invent a fake encoding for non-string inputs.\n attachments.push({\n mediaType: part.mediaType,\n data: typeof part.data === 'string' ? part.data : '',\n ...(part.filename ? { filename: part.filename } : {}),\n });\n }\n }\n\n const m: AISDKMessage = {\n role: 'user',\n content: text,\n _userContent: msg.content,\n _originalText: text,\n ...(msg.providerOptions ? { _providerOptions: msg.providerOptions } : {}),\n };\n if (attachments.length) m.attachments = attachments;\n messages.push(m);\n continue;\n }\n\n if (msg.role === 'assistant') {\n const text: string[] = [];\n const toolCalls: ToolCall[] = [];\n const attachments: Attachment[] = [];\n let thinking: { thinking: string } | undefined;\n\n for (const part of msg.content) {\n if (part.type === 'text') text.push(part.text);\n else if (part.type === 'tool-call') {\n toolCalls.push({\n id: part.toolCallId,\n type: 'function',\n function: {\n name: part.toolName,\n arguments: typeof part.input === 'string' ? part.input : JSON.stringify(part.input),\n },\n });\n } else if (part.type === 'reasoning') {\n thinking = { thinking: part.text };\n } else if (part.type === 'file') {\n // See user-side comment above: data is a presence signal only;\n // _assistantContent carries the actual payload through round-trip.\n attachments.push({\n mediaType: part.mediaType,\n data: typeof part.data === 'string' ? part.data : '',\n ...(part.filename ? { filename: part.filename } : {}),\n });\n }\n }\n\n const joinedText = text.join('\\n');\n const m: AISDKMessage = {\n role: 'assistant',\n content: joinedText,\n _assistantContent: msg.content,\n _originalText: joinedText,\n ...(msg.providerOptions ? { _providerOptions: msg.providerOptions } : {}),\n };\n if (toolCalls.length > 0) m.tool_calls = toolCalls;\n if (thinking) m.thinking = thinking;\n if (attachments.length) m.attachments = attachments;\n messages.push(m);\n continue;\n }\n\n if (msg.role === 'tool') {\n for (const part of msg.content) {\n if (part.type === 'tool-result') {\n const text = stringifyToolOutput(part.output);\n messages.push({\n role: 'tool',\n content: text,\n tool_call_id: part.toolCallId,\n _toolContent: [part],\n _originalText: text,\n _toolName: part.toolName,\n });\n }\n }\n }\n }\n\n return messages;\n}\n\n/**\n * Narrows a generic Message to AISDKMessage for typed access to pass-through fields.\n */\nfunction asAISDK(msg: Message): AISDKMessage {\n return msg;\n}\n\n/**\n * Converts context-chef IR messages back to AI SDK V3 prompt format.\n *\n * Uses per-role original content when unmodified (detected via `_originalText`).\n * Falls back to constructing from IR fields when content was modified by Janitor\n * (e.g. compact() cleared tool results) or for new messages (e.g. compression summaries).\n */\nexport function toAISDK(messages: Message[]): LanguageModelV3Prompt {\n const prompt: LanguageModelV3Prompt = [];\n\n let i = 0;\n while (i < messages.length) {\n const msg = asAISDK(messages[i]);\n const contentModified = msg._originalText !== undefined && msg._originalText !== msg.content;\n\n if (msg.role === 'system') {\n prompt.push({\n role: 'system',\n content: msg.content,\n ...(msg._providerOptions ? { providerOptions: msg._providerOptions } : {}),\n });\n i++;\n continue;\n }\n\n if (msg.role === 'user') {\n prompt.push({\n role: 'user',\n content:\n !contentModified && msg._userContent\n ? msg._userContent\n : [{ type: 'text', text: msg.content }],\n ...(msg._providerOptions ? { providerOptions: msg._providerOptions } : {}),\n });\n i++;\n continue;\n }\n\n if (msg.role === 'assistant') {\n prompt.push({\n role: 'assistant',\n content:\n !contentModified && msg._assistantContent\n ? msg._assistantContent\n : [{ type: 'text', text: msg.content }],\n ...(msg._providerOptions ? { providerOptions: msg._providerOptions } : {}),\n });\n i++;\n continue;\n }\n\n if (msg.role === 'tool') {\n const toolResults: LanguageModelV3ToolResultPart[] = [];\n while (i < messages.length && messages[i].role === 'tool') {\n const toolMsg = asAISDK(messages[i]);\n const toolModified =\n toolMsg._originalText !== undefined && toolMsg._originalText !== toolMsg.content;\n\n if (!toolModified && toolMsg._toolContent) {\n for (const part of toolMsg._toolContent) {\n if (part.type === 'tool-result') {\n toolResults.push(part);\n }\n }\n } else {\n toolResults.push({\n type: 'tool-result',\n toolCallId: toolMsg.tool_call_id ?? '',\n toolName: toolMsg._toolName ?? 'unknown',\n output: { type: 'text', value: toolMsg.content },\n });\n }\n i++;\n }\n prompt.push({ role: 'tool', content: toolResults });\n continue;\n }\n\n i++;\n }\n\n return prompt;\n}\n\nfunction stringifyToolOutput(output: LanguageModelV3ToolResultOutput): string {\n switch (output.type) {\n case 'text':\n case 'error-text':\n return output.value;\n case 'json':\n case 'error-json':\n return JSON.stringify(output.value);\n case 'content':\n return output.value\n .map((v) => (v.type === 'text' ? v.text : ''))\n .filter(Boolean)\n .join('\\n');\n default:\n return JSON.stringify(output);\n }\n}\n","import type {\n LanguageModelV3Prompt,\n LanguageModelV3ToolResultOutput,\n LanguageModelV3ToolResultPart,\n} from '@ai-sdk/provider';\nimport { Offloader } from '@context-chef/core';\nimport type { TruncateOptions } from './types';\n\n/**\n * Truncates tool-result content within an AI SDK prompt when it exceeds the configured threshold.\n * When a storage adapter is provided, original content is persisted and a URI is included in the output.\n */\nexport async function truncateToolResults(\n prompt: LanguageModelV3Prompt,\n options: TruncateOptions,\n): Promise<LanguageModelV3Prompt> {\n const { threshold, headChars = 0, tailChars = 1000, storage } = options;\n\n const offloader = storage ? new Offloader({ threshold, adapter: storage, storageDir: '' }) : null;\n const policy = buildPolicyMap(options.perTool);\n\n const result: LanguageModelV3Prompt = [];\n\n for (const msg of prompt) {\n if (msg.role !== 'tool') {\n result.push(msg);\n continue;\n }\n\n const newContent: typeof msg.content = [];\n\n for (const part of msg.content) {\n if (part.type !== 'tool-result') {\n newContent.push(part);\n continue;\n }\n\n const toolPolicy = policy.get(part.toolName);\n if (toolPolicy?.preserve) {\n // Preserve = full bypass: no truncation, no storage write.\n newContent.push(part);\n continue;\n }\n\n const effThreshold = toolPolicy?.threshold ?? threshold;\n const effHeadChars = toolPolicy?.headChars ?? headChars;\n const effTailChars = toolPolicy?.tailChars ?? tailChars;\n\n const text = extractText(part.output);\n if (text.length <= effThreshold || effHeadChars + effTailChars >= text.length) {\n newContent.push(part);\n continue;\n }\n\n // With storage: use Offloader to persist original and get a URI-annotated truncation\n if (offloader) {\n try {\n const vfsResult = await offloader.offloadAsync(text, {\n threshold: effThreshold,\n headChars: effHeadChars,\n tailChars: effTailChars,\n });\n newContent.push({\n ...part,\n output: {\n type: 'text',\n value: vfsResult.content,\n } satisfies LanguageModelV3ToolResultOutput,\n } satisfies LanguageModelV3ToolResultPart);\n continue;\n } catch (error) {\n console.warn(\n `[context-chef] Storage adapter write failed for tool result (${part.toolCallId}). ` +\n `Falling back to simple truncation. Error: ${error instanceof Error ? error.message : String(error)}`,\n );\n // Fall through to simple truncation below\n }\n }\n\n // Without storage: simple truncation, original is discarded\n const head = text.slice(0, effHeadChars);\n const tail = text.slice(text.length - effTailChars);\n const totalLines = text.split('\\n').length;\n\n const truncated = [\n head,\n `\\n--- truncated (${totalLines} lines, ${text.length} chars total) ---\\n`,\n tail,\n ]\n .filter(Boolean)\n .join('')\n .trim();\n\n newContent.push({\n ...part,\n output: { type: 'text', value: truncated } satisfies LanguageModelV3ToolResultOutput,\n } satisfies LanguageModelV3ToolResultPart);\n }\n\n result.push({ ...msg, content: newContent });\n }\n\n return result;\n}\n\ntype ToolPolicy =\n | { preserve: true }\n | {\n preserve?: false;\n threshold?: number;\n headChars?: number;\n tailChars?: number;\n };\n\n/**\n * Normalises `perTool` into a name → policy lookup.\n * Bare strings become `{ preserve: true }`; objects keep their partial overrides.\n * Last entry wins on duplicate names.\n */\nfunction buildPolicyMap(perTool: TruncateOptions['perTool']): Map<string, ToolPolicy> {\n const map = new Map<string, ToolPolicy>();\n if (!perTool) return map;\n for (const entry of perTool) {\n if (typeof entry === 'string') {\n map.set(entry, { preserve: true });\n } else {\n map.set(entry.name, {\n threshold: entry.threshold,\n headChars: entry.headChars,\n tailChars: entry.tailChars,\n });\n }\n }\n return map;\n}\n\nfunction extractText(output: LanguageModelV3ToolResultOutput): string {\n switch (output.type) {\n case 'text':\n case 'error-text':\n return output.value;\n case 'json':\n case 'error-json':\n return JSON.stringify(output.value);\n case 'content':\n return output.value\n .map((v: { type: string; text?: string }) => (v.type === 'text' ? (v.text ?? '') : ''))\n .filter(Boolean)\n .join('\\n');\n default:\n return '';\n }\n}\n","import type {\n LanguageModelV3,\n LanguageModelV3Message,\n LanguageModelV3Prompt,\n LanguageModelV3StreamPart,\n} from '@ai-sdk/provider';\nimport { Janitor, type Message, XmlGenerator } from '@context-chef/core';\nimport { generateText, type LanguageModelMiddleware, type ModelMessage, pruneMessages } from 'ai';\n\nimport { fromAISDK, toAISDK } from './adapter';\nimport { truncateToolResults } from './truncator';\nimport type { ContextChefOptions, DynamicStateConfig } from './types';\n\ntype CompressRole = 'system' | 'user' | 'assistant';\n\n/**\n * Creates a LanguageModelMiddleware that transparently applies\n * context-chef compression and truncation to AI SDK model calls.\n *\n * The middleware holds a stateful Janitor instance that tracks\n * token usage across calls for compression decisions.\n */\nexport function createMiddleware(options: ContextChefOptions): LanguageModelMiddleware {\n let usageWarned = false;\n\n const janitor = new Janitor({\n contextWindow: options.contextWindow,\n tokenizer: options.tokenizer ? (msgs: Message[]) => options.tokenizer?.(msgs) ?? 0 : undefined,\n preserveRatio: options.compress?.preserveRatio ?? 0.8,\n compressionModel: options.compress?.model\n ? createCompressionAdapter(options.compress.model)\n : undefined,\n onCompress: options.onCompress\n ? (summary, count) => options.onCompress?.(summary.content, count)\n : undefined,\n onBeforeCompress: options.onBeforeCompress ?? options.onBudgetExceeded,\n });\n\n return {\n specificationVersion: 'v3',\n\n transformParams: async ({ params }) => {\n let { prompt } = params;\n\n // 1. Truncate large tool results\n if (options.truncate) {\n prompt = await truncateToolResults(prompt, options.truncate);\n }\n\n // 2. Compact (mechanical, zero LLM cost) via pruneMessages\n if (options.compact) {\n prompt = compactPrompt(prompt, options.compact);\n }\n\n // 3. Convert to IR and separate system messages from conversation.\n // System messages are standing instructions — they must not be\n // compressed away. Only conversation history goes through compact/compress.\n const allIR = fromAISDK(prompt);\n const systemMessages = allIR.filter((m) => m.role === 'system');\n let conversation = allIR.filter((m) => m.role !== 'system');\n\n // 4. Compress conversation history if over token budget\n conversation = await janitor.compress(conversation);\n\n // 5. Reassemble sandwich: user system + skill instructions + conversation.\n // The skill slot mirrors @context-chef/core compile() ordering\n // (SKILL_SPEC §6.3): a dedicated system message AFTER user system\n // and BEFORE the conversation history. Empty instructions are\n // skipped to avoid emitting an empty system message.\n const skillMessages = await resolveSkillMessages(options.skill);\n const irMessages = [...systemMessages, ...skillMessages, ...conversation];\n\n // 6. Convert back to AI SDK format\n prompt = toAISDK(irMessages);\n\n // 7. Dynamic state injection\n if (options.dynamicState) {\n prompt = await injectDynamicState(prompt, options.dynamicState);\n }\n\n // 8. Custom transform hook\n if (options.transformContext) {\n prompt = await options.transformContext(prompt);\n }\n\n return { ...params, prompt };\n },\n\n wrapGenerate: async ({ doGenerate }) => {\n const result = await doGenerate();\n\n if (result.usage?.inputTokens?.total != null) {\n janitor.feedTokenUsage(result.usage.inputTokens.total);\n } else if (!usageWarned && !options.tokenizer) {\n usageWarned = true;\n console.warn(\n '[context-chef] Model response did not include usage.inputTokens.total. ' +\n 'Token-based compression may not trigger accurately. ' +\n 'Consider providing a tokenizer for precise token counting.',\n );\n }\n\n return result;\n },\n\n wrapStream: async ({ doStream }) => {\n const { stream, ...rest } = await doStream();\n\n const transform = new TransformStream<LanguageModelV3StreamPart, LanguageModelV3StreamPart>({\n transform(chunk, controller) {\n if (chunk.type === 'finish') {\n if (chunk.usage?.inputTokens?.total != null) {\n janitor.feedTokenUsage(chunk.usage.inputTokens.total);\n } else if (!usageWarned && !options.tokenizer) {\n usageWarned = true;\n console.warn(\n '[context-chef] Stream finish did not include usage.inputTokens.total. ' +\n 'Token-based compression may not trigger accurately. ' +\n 'Consider providing a tokenizer for precise token counting.',\n );\n }\n }\n controller.enqueue(chunk);\n },\n });\n\n return { ...rest, stream: stream.pipeThrough(transform) };\n },\n };\n}\n\n/**\n * Prunes a LanguageModelV3Prompt via AI SDK's pruneMessages.\n *\n * LanguageModelV3Message (from @ai-sdk/provider) and ModelMessage\n * (from @ai-sdk/provider-utils) share identical runtime structure but\n * differ at the TypeScript level (e.g. ImagePart, FilePart.data).\n * Since pruneMessages only filters — never transforms — every content\n * part in the output is an original V3 part, making the casts safe.\n */\nfunction compactPrompt(\n prompt: LanguageModelV3Prompt,\n config: Omit<Parameters<typeof pruneMessages>[0], 'messages'>,\n): LanguageModelV3Prompt {\n const messages = prompt.map(\n (msg) =>\n ({\n role: msg.role,\n content: msg.content,\n providerOptions: msg.providerOptions,\n }) as ModelMessage,\n );\n const pruned = pruneMessages({ messages, ...config });\n return pruned.map(\n (msg) =>\n ({\n role: msg.role,\n content: msg.content,\n providerOptions: msg.providerOptions,\n }) as LanguageModelV3Message,\n );\n}\n\n/**\n * Resolves the `skill` option into IR system messages to insert between\n * user system messages and conversation. Returns `[]` when no skill is\n * active, the resolver returns null/undefined, or instructions are empty.\n *\n * The function form is invoked on every transformParams call so the\n * caller can swap skills dynamically without recreating the middleware.\n */\nasync function resolveSkillMessages(skill: ContextChefOptions['skill']): Promise<Message[]> {\n if (!skill) return [];\n const resolved = typeof skill === 'function' ? await skill() : skill;\n // Treat whitespace-only instructions as empty — they would otherwise pollute\n // the prompt and create a needless cache breakpoint between system and history.\n if (!resolved || !resolved.instructions || !resolved.instructions.trim()) return [];\n return [{ role: 'system', content: resolved.instructions }];\n}\n\n/**\n * Injects dynamic state XML into the AI SDK prompt.\n *\n * - `last_user`: Appends to the last user message's content parts.\n * Leverages Recency Bias for maximum LLM attention.\n * - `system`: Adds as a standalone system message at the end.\n */\nasync function injectDynamicState(\n prompt: LanguageModelV3Prompt,\n config: DynamicStateConfig,\n): Promise<LanguageModelV3Prompt> {\n const state = await config.getState();\n const xml = XmlGenerator.objectToXml(state, 'dynamic_state');\n const placement = config.placement ?? 'last_user';\n\n if (placement === 'system') {\n return [...prompt, { role: 'system', content: `CURRENT TASK STATE:\\n${xml}` }];\n }\n\n // last_user: inject into the last user message\n const result = [...prompt];\n const stateBlock = `\\n\\n${xml}\\nAbove is the current system state. Use it to guide your next action.`;\n\n for (let i = result.length - 1; i >= 0; i--) {\n const msg = result[i];\n if (msg.role === 'user') {\n result[i] = {\n ...msg,\n content: [...msg.content, { type: 'text', text: stateBlock }],\n };\n return result;\n }\n }\n\n // No user message found — append as new user message\n result.push({\n role: 'user',\n content: [{ type: 'text', text: stateBlock.trim() }],\n });\n return result;\n}\n\n/**\n * Maps an IR role to a role accepted by generateText.\n * Tool messages are handled separately before this is called.\n */\nfunction toCompressRole(role: string): CompressRole {\n if (role === 'system' || role === 'user' || role === 'assistant') return role;\n return 'user';\n}\n\n/**\n * Adapts an AI SDK LanguageModelV3 into the compressionModel callback\n * that Janitor expects: (messages: Message[]) => Promise<string>\n *\n * Tool messages are converted to user messages describing the tool interaction,\n * since generateText only accepts system/user/assistant roles.\n */\nfunction createCompressionAdapter(\n model: LanguageModelV3,\n): (messages: Message[]) => Promise<string> {\n return async (messages: Message[]): Promise<string> => {\n const formatted = messages.map((m): { role: CompressRole; content: string } => {\n if (m.role === 'tool') {\n return {\n role: 'user' satisfies CompressRole,\n content: `[Tool result${m.tool_call_id ? ` (${m.tool_call_id})` : ''}: ${m.content}]`,\n };\n }\n if (m.role === 'assistant' && m.tool_calls?.length) {\n const toolCallsDesc = m.tool_calls\n .map((tc) => `[Called tool: ${tc.function.name}(${tc.function.arguments})]`)\n .join('\\n');\n return {\n role: 'assistant' satisfies CompressRole,\n content: m.content ? `${m.content}\\n${toolCallsDesc}` : toolCallsDesc,\n };\n }\n return {\n role: toCompressRole(m.role),\n content: m.content,\n };\n });\n\n const { text } = await generateText({\n model,\n messages: formatted,\n maxOutputTokens: 2048,\n });\n\n return text || '[Compression produced no output]';\n };\n}\n","import type { LanguageModelV3 } from '@ai-sdk/provider';\nimport { wrapLanguageModel } from 'ai';\n\nimport { createMiddleware } from './middleware';\nimport type { ContextChefOptions } from './types';\n\nexport { type AISDKMessage, fromAISDK, toAISDK } from './adapter';\nexport { createMiddleware } from './middleware';\nexport type {\n CompactConfig,\n CompressOptions,\n ContextChefOptions,\n DynamicStateConfig,\n TruncateOptions,\n} from './types';\n\n/**\n * Wraps an AI SDK language model with context-chef middleware for\n * transparent history compression, tool result truncation, and token budget management.\n *\n * @example\n * ```typescript\n * import { withContextChef } from '@context-chef/ai-sdk-middleware';\n * import { openai } from '@ai-sdk/openai';\n * import { generateText } from 'ai';\n *\n * const model = withContextChef(openai('gpt-4o'), {\n * contextWindow: 128_000,\n * compress: { model: openai('gpt-4o-mini') },\n * truncate: { threshold: 5000, headChars: 500, tailChars: 1000 },\n * });\n *\n * // Use exactly like normal — zero other code changes\n * const result = await generateText({ model, messages, tools });\n * ```\n */\nexport function withContextChef(\n model: LanguageModelV3,\n options: ContextChefOptions,\n): LanguageModelV3 {\n const middleware = createMiddleware(options);\n return wrapLanguageModel({ model, middleware });\n}\n"],"mappings":"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"}
|
package/dist/index.d.cts
CHANGED
|
@@ -17,6 +17,31 @@ interface TruncateOptions {
|
|
|
17
17
|
* When omitted, original content is discarded after truncation.
|
|
18
18
|
*/
|
|
19
19
|
storage?: VFSStorageAdapter;
|
|
20
|
+
/**
|
|
21
|
+
* Per-tool overrides applied on top of the defaults above.
|
|
22
|
+
*
|
|
23
|
+
* - String entry → preserve: never truncate this tool's result. Storage
|
|
24
|
+
* is bypassed entirely (nothing written to VFS).
|
|
25
|
+
* - Object entry → override `threshold` / `headChars` / `tailChars` for
|
|
26
|
+
* that tool only. Storage behavior unchanged.
|
|
27
|
+
*
|
|
28
|
+
* Tools not listed fall back to the top-level defaults. If the same
|
|
29
|
+
* `name` appears more than once, the last entry wins (a bare string
|
|
30
|
+
* after an object discards that object → becomes preserve).
|
|
31
|
+
*
|
|
32
|
+
* Notes:
|
|
33
|
+
* - Wildcards / globs are NOT supported.
|
|
34
|
+
* - `storage` cannot be overridden per-tool.
|
|
35
|
+
* - `perTool` only affects the truncate step; a preserved message may
|
|
36
|
+
* still be dropped by `compact`, summarized by `compress`, or
|
|
37
|
+
* rewritten by `transformContext`.
|
|
38
|
+
*/
|
|
39
|
+
perTool?: Array<string | {
|
|
40
|
+
name: string;
|
|
41
|
+
threshold?: number;
|
|
42
|
+
headChars?: number;
|
|
43
|
+
tailChars?: number;
|
|
44
|
+
}>;
|
|
20
45
|
}
|
|
21
46
|
interface CompressOptions {
|
|
22
47
|
/** A cheap model used for summarization (e.g. openai('gpt-4o-mini')). */
|
package/dist/index.d.mts
CHANGED
|
@@ -17,6 +17,31 @@ interface TruncateOptions {
|
|
|
17
17
|
* When omitted, original content is discarded after truncation.
|
|
18
18
|
*/
|
|
19
19
|
storage?: VFSStorageAdapter;
|
|
20
|
+
/**
|
|
21
|
+
* Per-tool overrides applied on top of the defaults above.
|
|
22
|
+
*
|
|
23
|
+
* - String entry → preserve: never truncate this tool's result. Storage
|
|
24
|
+
* is bypassed entirely (nothing written to VFS).
|
|
25
|
+
* - Object entry → override `threshold` / `headChars` / `tailChars` for
|
|
26
|
+
* that tool only. Storage behavior unchanged.
|
|
27
|
+
*
|
|
28
|
+
* Tools not listed fall back to the top-level defaults. If the same
|
|
29
|
+
* `name` appears more than once, the last entry wins (a bare string
|
|
30
|
+
* after an object discards that object → becomes preserve).
|
|
31
|
+
*
|
|
32
|
+
* Notes:
|
|
33
|
+
* - Wildcards / globs are NOT supported.
|
|
34
|
+
* - `storage` cannot be overridden per-tool.
|
|
35
|
+
* - `perTool` only affects the truncate step; a preserved message may
|
|
36
|
+
* still be dropped by `compact`, summarized by `compress`, or
|
|
37
|
+
* rewritten by `transformContext`.
|
|
38
|
+
*/
|
|
39
|
+
perTool?: Array<string | {
|
|
40
|
+
name: string;
|
|
41
|
+
threshold?: number;
|
|
42
|
+
headChars?: number;
|
|
43
|
+
tailChars?: number;
|
|
44
|
+
}>;
|
|
20
45
|
}
|
|
21
46
|
interface CompressOptions {
|
|
22
47
|
/** A cheap model used for summarization (e.g. openai('gpt-4o-mini')). */
|
package/dist/index.mjs
CHANGED
|
@@ -192,6 +192,7 @@ async function truncateToolResults(prompt, options) {
|
|
|
192
192
|
adapter: storage,
|
|
193
193
|
storageDir: ""
|
|
194
194
|
}) : null;
|
|
195
|
+
const policy = buildPolicyMap(options.perTool);
|
|
195
196
|
const result = [];
|
|
196
197
|
for (const msg of prompt) {
|
|
197
198
|
if (msg.role !== "tool") {
|
|
@@ -204,16 +205,24 @@ async function truncateToolResults(prompt, options) {
|
|
|
204
205
|
newContent.push(part);
|
|
205
206
|
continue;
|
|
206
207
|
}
|
|
208
|
+
const toolPolicy = policy.get(part.toolName);
|
|
209
|
+
if (toolPolicy?.preserve) {
|
|
210
|
+
newContent.push(part);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
const effThreshold = toolPolicy?.threshold ?? threshold;
|
|
214
|
+
const effHeadChars = toolPolicy?.headChars ?? headChars;
|
|
215
|
+
const effTailChars = toolPolicy?.tailChars ?? tailChars;
|
|
207
216
|
const text = extractText(part.output);
|
|
208
|
-
if (text.length <=
|
|
217
|
+
if (text.length <= effThreshold || effHeadChars + effTailChars >= text.length) {
|
|
209
218
|
newContent.push(part);
|
|
210
219
|
continue;
|
|
211
220
|
}
|
|
212
221
|
if (offloader) try {
|
|
213
222
|
const vfsResult = await offloader.offloadAsync(text, {
|
|
214
|
-
threshold,
|
|
215
|
-
headChars,
|
|
216
|
-
tailChars
|
|
223
|
+
threshold: effThreshold,
|
|
224
|
+
headChars: effHeadChars,
|
|
225
|
+
tailChars: effTailChars
|
|
217
226
|
});
|
|
218
227
|
newContent.push({
|
|
219
228
|
...part,
|
|
@@ -226,8 +235,8 @@ async function truncateToolResults(prompt, options) {
|
|
|
226
235
|
} catch (error) {
|
|
227
236
|
console.warn(`[context-chef] Storage adapter write failed for tool result (${part.toolCallId}). Falling back to simple truncation. Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
228
237
|
}
|
|
229
|
-
const head = text.slice(0,
|
|
230
|
-
const tail = text.slice(text.length -
|
|
238
|
+
const head = text.slice(0, effHeadChars);
|
|
239
|
+
const tail = text.slice(text.length - effTailChars);
|
|
231
240
|
const truncated = [
|
|
232
241
|
head,
|
|
233
242
|
`\n--- truncated (${text.split("\n").length} lines, ${text.length} chars total) ---\n`,
|
|
@@ -248,6 +257,22 @@ async function truncateToolResults(prompt, options) {
|
|
|
248
257
|
}
|
|
249
258
|
return result;
|
|
250
259
|
}
|
|
260
|
+
/**
|
|
261
|
+
* Normalises `perTool` into a name → policy lookup.
|
|
262
|
+
* Bare strings become `{ preserve: true }`; objects keep their partial overrides.
|
|
263
|
+
* Last entry wins on duplicate names.
|
|
264
|
+
*/
|
|
265
|
+
function buildPolicyMap(perTool) {
|
|
266
|
+
const map = /* @__PURE__ */ new Map();
|
|
267
|
+
if (!perTool) return map;
|
|
268
|
+
for (const entry of perTool) if (typeof entry === "string") map.set(entry, { preserve: true });
|
|
269
|
+
else map.set(entry.name, {
|
|
270
|
+
threshold: entry.threshold,
|
|
271
|
+
headChars: entry.headChars,
|
|
272
|
+
tailChars: entry.tailChars
|
|
273
|
+
});
|
|
274
|
+
return map;
|
|
275
|
+
}
|
|
251
276
|
function extractText(output) {
|
|
252
277
|
switch (output.type) {
|
|
253
278
|
case "text":
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/adapter.ts","../src/truncator.ts","../src/middleware.ts","../src/index.ts"],"sourcesContent":["import type {\n LanguageModelV3Message,\n LanguageModelV3Prompt,\n LanguageModelV3ToolResultOutput,\n LanguageModelV3ToolResultPart,\n SharedV3ProviderOptions,\n} from '@ai-sdk/provider';\nimport type { Attachment, Message, ToolCall } from '@context-chef/core';\n\n/** Content types for each AI SDK message role */\ntype UserContent = Extract<LanguageModelV3Message, { role: 'user' }>['content'];\ntype AssistantContent = Extract<LanguageModelV3Message, { role: 'assistant' }>['content'];\ntype ToolContent = Extract<LanguageModelV3Message, { role: 'tool' }>['content'];\n\n/**\n * Extended IR message with typed pass-through fields for lossless AI SDK round-trip.\n * Per-role content fields avoid union types, so no `as` casts are needed in `toAISDK`.\n */\nexport interface AISDKMessage extends Message {\n _userContent?: UserContent;\n _assistantContent?: AssistantContent;\n _toolContent?: ToolContent;\n _originalText?: string;\n _providerOptions?: SharedV3ProviderOptions;\n _toolName?: string;\n}\n\n/**\n * Converts an AI SDK V3 prompt to context-chef IR messages.\n *\n * Original AI SDK content is stored in per-role fields for lossless round-trip.\n * `_originalText` caches the extracted text so `toAISDK` can detect Janitor modifications.\n * `_providerOptions` preserves message-level provider options (e.g. Anthropic cache control).\n */\nexport function fromAISDK(prompt: LanguageModelV3Prompt): AISDKMessage[] {\n const messages: AISDKMessage[] = [];\n\n for (const msg of prompt) {\n if (msg.role === 'system') {\n messages.push({\n role: 'system',\n content: msg.content,\n ...(msg.providerOptions ? { _providerOptions: msg.providerOptions } : {}),\n });\n continue;\n }\n\n if (msg.role === 'user') {\n const text = msg.content\n .filter((p) => p.type === 'text')\n .map((p) => p.text)\n .join('\\n');\n\n const attachments: Attachment[] = [];\n for (const part of msg.content) {\n if (part.type === 'file') {\n // `attachment.data` here is just a presence/metadata signal for Janitor\n // (used by `m.attachments?.length` checks and the `[image]`/`[document]`\n // placeholder helper, neither of which read `data`). The real binary\n // payload — including `Uint8Array` / `URL` shapes — round-trips losslessly\n // through `_userContent`, which `toAISDK` hands back to the AI SDK\n // provider verbatim. We only record `data` when it's already a string,\n // so we never invent a fake encoding for non-string inputs.\n attachments.push({\n mediaType: part.mediaType,\n data: typeof part.data === 'string' ? part.data : '',\n ...(part.filename ? { filename: part.filename } : {}),\n });\n }\n }\n\n const m: AISDKMessage = {\n role: 'user',\n content: text,\n _userContent: msg.content,\n _originalText: text,\n ...(msg.providerOptions ? { _providerOptions: msg.providerOptions } : {}),\n };\n if (attachments.length) m.attachments = attachments;\n messages.push(m);\n continue;\n }\n\n if (msg.role === 'assistant') {\n const text: string[] = [];\n const toolCalls: ToolCall[] = [];\n const attachments: Attachment[] = [];\n let thinking: { thinking: string } | undefined;\n\n for (const part of msg.content) {\n if (part.type === 'text') text.push(part.text);\n else if (part.type === 'tool-call') {\n toolCalls.push({\n id: part.toolCallId,\n type: 'function',\n function: {\n name: part.toolName,\n arguments: typeof part.input === 'string' ? part.input : JSON.stringify(part.input),\n },\n });\n } else if (part.type === 'reasoning') {\n thinking = { thinking: part.text };\n } else if (part.type === 'file') {\n // See user-side comment above: data is a presence signal only;\n // _assistantContent carries the actual payload through round-trip.\n attachments.push({\n mediaType: part.mediaType,\n data: typeof part.data === 'string' ? part.data : '',\n ...(part.filename ? { filename: part.filename } : {}),\n });\n }\n }\n\n const joinedText = text.join('\\n');\n const m: AISDKMessage = {\n role: 'assistant',\n content: joinedText,\n _assistantContent: msg.content,\n _originalText: joinedText,\n ...(msg.providerOptions ? { _providerOptions: msg.providerOptions } : {}),\n };\n if (toolCalls.length > 0) m.tool_calls = toolCalls;\n if (thinking) m.thinking = thinking;\n if (attachments.length) m.attachments = attachments;\n messages.push(m);\n continue;\n }\n\n if (msg.role === 'tool') {\n for (const part of msg.content) {\n if (part.type === 'tool-result') {\n const text = stringifyToolOutput(part.output);\n messages.push({\n role: 'tool',\n content: text,\n tool_call_id: part.toolCallId,\n _toolContent: [part],\n _originalText: text,\n _toolName: part.toolName,\n });\n }\n }\n }\n }\n\n return messages;\n}\n\n/**\n * Narrows a generic Message to AISDKMessage for typed access to pass-through fields.\n */\nfunction asAISDK(msg: Message): AISDKMessage {\n return msg;\n}\n\n/**\n * Converts context-chef IR messages back to AI SDK V3 prompt format.\n *\n * Uses per-role original content when unmodified (detected via `_originalText`).\n * Falls back to constructing from IR fields when content was modified by Janitor\n * (e.g. compact() cleared tool results) or for new messages (e.g. compression summaries).\n */\nexport function toAISDK(messages: Message[]): LanguageModelV3Prompt {\n const prompt: LanguageModelV3Prompt = [];\n\n let i = 0;\n while (i < messages.length) {\n const msg = asAISDK(messages[i]);\n const contentModified = msg._originalText !== undefined && msg._originalText !== msg.content;\n\n if (msg.role === 'system') {\n prompt.push({\n role: 'system',\n content: msg.content,\n ...(msg._providerOptions ? { providerOptions: msg._providerOptions } : {}),\n });\n i++;\n continue;\n }\n\n if (msg.role === 'user') {\n prompt.push({\n role: 'user',\n content:\n !contentModified && msg._userContent\n ? msg._userContent\n : [{ type: 'text', text: msg.content }],\n ...(msg._providerOptions ? { providerOptions: msg._providerOptions } : {}),\n });\n i++;\n continue;\n }\n\n if (msg.role === 'assistant') {\n prompt.push({\n role: 'assistant',\n content:\n !contentModified && msg._assistantContent\n ? msg._assistantContent\n : [{ type: 'text', text: msg.content }],\n ...(msg._providerOptions ? { providerOptions: msg._providerOptions } : {}),\n });\n i++;\n continue;\n }\n\n if (msg.role === 'tool') {\n const toolResults: LanguageModelV3ToolResultPart[] = [];\n while (i < messages.length && messages[i].role === 'tool') {\n const toolMsg = asAISDK(messages[i]);\n const toolModified =\n toolMsg._originalText !== undefined && toolMsg._originalText !== toolMsg.content;\n\n if (!toolModified && toolMsg._toolContent) {\n for (const part of toolMsg._toolContent) {\n if (part.type === 'tool-result') {\n toolResults.push(part);\n }\n }\n } else {\n toolResults.push({\n type: 'tool-result',\n toolCallId: toolMsg.tool_call_id ?? '',\n toolName: toolMsg._toolName ?? 'unknown',\n output: { type: 'text', value: toolMsg.content },\n });\n }\n i++;\n }\n prompt.push({ role: 'tool', content: toolResults });\n continue;\n }\n\n i++;\n }\n\n return prompt;\n}\n\nfunction stringifyToolOutput(output: LanguageModelV3ToolResultOutput): string {\n switch (output.type) {\n case 'text':\n case 'error-text':\n return output.value;\n case 'json':\n case 'error-json':\n return JSON.stringify(output.value);\n case 'content':\n return output.value\n .map((v) => (v.type === 'text' ? v.text : ''))\n .filter(Boolean)\n .join('\\n');\n default:\n return JSON.stringify(output);\n }\n}\n","import type {\n LanguageModelV3Prompt,\n LanguageModelV3ToolResultOutput,\n LanguageModelV3ToolResultPart,\n} from '@ai-sdk/provider';\nimport { Offloader } from '@context-chef/core';\nimport type { TruncateOptions } from './types';\n\n/**\n * Truncates tool-result content within an AI SDK prompt when it exceeds the configured threshold.\n * When a storage adapter is provided, original content is persisted and a URI is included in the output.\n */\nexport async function truncateToolResults(\n prompt: LanguageModelV3Prompt,\n options: TruncateOptions,\n): Promise<LanguageModelV3Prompt> {\n const { threshold, headChars = 0, tailChars = 1000, storage } = options;\n\n const offloader = storage ? new Offloader({ threshold, adapter: storage, storageDir: '' }) : null;\n\n const result: LanguageModelV3Prompt = [];\n\n for (const msg of prompt) {\n if (msg.role !== 'tool') {\n result.push(msg);\n continue;\n }\n\n const newContent: typeof msg.content = [];\n\n for (const part of msg.content) {\n if (part.type !== 'tool-result') {\n newContent.push(part);\n continue;\n }\n\n const text = extractText(part.output);\n if (text.length <= threshold || headChars + tailChars >= text.length) {\n newContent.push(part);\n continue;\n }\n\n // With storage: use Offloader to persist original and get a URI-annotated truncation\n if (offloader) {\n try {\n const vfsResult = await offloader.offloadAsync(text, { threshold, headChars, tailChars });\n newContent.push({\n ...part,\n output: {\n type: 'text',\n value: vfsResult.content,\n } satisfies LanguageModelV3ToolResultOutput,\n } satisfies LanguageModelV3ToolResultPart);\n continue;\n } catch (error) {\n console.warn(\n `[context-chef] Storage adapter write failed for tool result (${part.toolCallId}). ` +\n `Falling back to simple truncation. Error: ${error instanceof Error ? error.message : String(error)}`,\n );\n // Fall through to simple truncation below\n }\n }\n\n // Without storage: simple truncation, original is discarded\n const head = text.slice(0, headChars);\n const tail = text.slice(text.length - tailChars);\n const totalLines = text.split('\\n').length;\n\n const truncated = [\n head,\n `\\n--- truncated (${totalLines} lines, ${text.length} chars total) ---\\n`,\n tail,\n ]\n .filter(Boolean)\n .join('')\n .trim();\n\n newContent.push({\n ...part,\n output: { type: 'text', value: truncated } satisfies LanguageModelV3ToolResultOutput,\n } satisfies LanguageModelV3ToolResultPart);\n }\n\n result.push({ ...msg, content: newContent });\n }\n\n return result;\n}\n\nfunction extractText(output: LanguageModelV3ToolResultOutput): string {\n switch (output.type) {\n case 'text':\n case 'error-text':\n return output.value;\n case 'json':\n case 'error-json':\n return JSON.stringify(output.value);\n case 'content':\n return output.value\n .map((v: { type: string; text?: string }) => (v.type === 'text' ? (v.text ?? '') : ''))\n .filter(Boolean)\n .join('\\n');\n default:\n return '';\n }\n}\n","import type {\n LanguageModelV3,\n LanguageModelV3Message,\n LanguageModelV3Prompt,\n LanguageModelV3StreamPart,\n} from '@ai-sdk/provider';\nimport { Janitor, type Message, XmlGenerator } from '@context-chef/core';\nimport { generateText, type LanguageModelMiddleware, type ModelMessage, pruneMessages } from 'ai';\n\nimport { fromAISDK, toAISDK } from './adapter';\nimport { truncateToolResults } from './truncator';\nimport type { ContextChefOptions, DynamicStateConfig } from './types';\n\ntype CompressRole = 'system' | 'user' | 'assistant';\n\n/**\n * Creates a LanguageModelMiddleware that transparently applies\n * context-chef compression and truncation to AI SDK model calls.\n *\n * The middleware holds a stateful Janitor instance that tracks\n * token usage across calls for compression decisions.\n */\nexport function createMiddleware(options: ContextChefOptions): LanguageModelMiddleware {\n let usageWarned = false;\n\n const janitor = new Janitor({\n contextWindow: options.contextWindow,\n tokenizer: options.tokenizer ? (msgs: Message[]) => options.tokenizer?.(msgs) ?? 0 : undefined,\n preserveRatio: options.compress?.preserveRatio ?? 0.8,\n compressionModel: options.compress?.model\n ? createCompressionAdapter(options.compress.model)\n : undefined,\n onCompress: options.onCompress\n ? (summary, count) => options.onCompress?.(summary.content, count)\n : undefined,\n onBeforeCompress: options.onBeforeCompress ?? options.onBudgetExceeded,\n });\n\n return {\n specificationVersion: 'v3',\n\n transformParams: async ({ params }) => {\n let { prompt } = params;\n\n // 1. Truncate large tool results\n if (options.truncate) {\n prompt = await truncateToolResults(prompt, options.truncate);\n }\n\n // 2. Compact (mechanical, zero LLM cost) via pruneMessages\n if (options.compact) {\n prompt = compactPrompt(prompt, options.compact);\n }\n\n // 3. Convert to IR and separate system messages from conversation.\n // System messages are standing instructions — they must not be\n // compressed away. Only conversation history goes through compact/compress.\n const allIR = fromAISDK(prompt);\n const systemMessages = allIR.filter((m) => m.role === 'system');\n let conversation = allIR.filter((m) => m.role !== 'system');\n\n // 4. Compress conversation history if over token budget\n conversation = await janitor.compress(conversation);\n\n // 5. Reassemble sandwich: user system + skill instructions + conversation.\n // The skill slot mirrors @context-chef/core compile() ordering\n // (SKILL_SPEC §6.3): a dedicated system message AFTER user system\n // and BEFORE the conversation history. Empty instructions are\n // skipped to avoid emitting an empty system message.\n const skillMessages = await resolveSkillMessages(options.skill);\n const irMessages = [...systemMessages, ...skillMessages, ...conversation];\n\n // 6. Convert back to AI SDK format\n prompt = toAISDK(irMessages);\n\n // 7. Dynamic state injection\n if (options.dynamicState) {\n prompt = await injectDynamicState(prompt, options.dynamicState);\n }\n\n // 8. Custom transform hook\n if (options.transformContext) {\n prompt = await options.transformContext(prompt);\n }\n\n return { ...params, prompt };\n },\n\n wrapGenerate: async ({ doGenerate }) => {\n const result = await doGenerate();\n\n if (result.usage?.inputTokens?.total != null) {\n janitor.feedTokenUsage(result.usage.inputTokens.total);\n } else if (!usageWarned && !options.tokenizer) {\n usageWarned = true;\n console.warn(\n '[context-chef] Model response did not include usage.inputTokens.total. ' +\n 'Token-based compression may not trigger accurately. ' +\n 'Consider providing a tokenizer for precise token counting.',\n );\n }\n\n return result;\n },\n\n wrapStream: async ({ doStream }) => {\n const { stream, ...rest } = await doStream();\n\n const transform = new TransformStream<LanguageModelV3StreamPart, LanguageModelV3StreamPart>({\n transform(chunk, controller) {\n if (chunk.type === 'finish') {\n if (chunk.usage?.inputTokens?.total != null) {\n janitor.feedTokenUsage(chunk.usage.inputTokens.total);\n } else if (!usageWarned && !options.tokenizer) {\n usageWarned = true;\n console.warn(\n '[context-chef] Stream finish did not include usage.inputTokens.total. ' +\n 'Token-based compression may not trigger accurately. ' +\n 'Consider providing a tokenizer for precise token counting.',\n );\n }\n }\n controller.enqueue(chunk);\n },\n });\n\n return { ...rest, stream: stream.pipeThrough(transform) };\n },\n };\n}\n\n/**\n * Prunes a LanguageModelV3Prompt via AI SDK's pruneMessages.\n *\n * LanguageModelV3Message (from @ai-sdk/provider) and ModelMessage\n * (from @ai-sdk/provider-utils) share identical runtime structure but\n * differ at the TypeScript level (e.g. ImagePart, FilePart.data).\n * Since pruneMessages only filters — never transforms — every content\n * part in the output is an original V3 part, making the casts safe.\n */\nfunction compactPrompt(\n prompt: LanguageModelV3Prompt,\n config: Omit<Parameters<typeof pruneMessages>[0], 'messages'>,\n): LanguageModelV3Prompt {\n const messages = prompt.map(\n (msg) =>\n ({\n role: msg.role,\n content: msg.content,\n providerOptions: msg.providerOptions,\n }) as ModelMessage,\n );\n const pruned = pruneMessages({ messages, ...config });\n return pruned.map(\n (msg) =>\n ({\n role: msg.role,\n content: msg.content,\n providerOptions: msg.providerOptions,\n }) as LanguageModelV3Message,\n );\n}\n\n/**\n * Resolves the `skill` option into IR system messages to insert between\n * user system messages and conversation. Returns `[]` when no skill is\n * active, the resolver returns null/undefined, or instructions are empty.\n *\n * The function form is invoked on every transformParams call so the\n * caller can swap skills dynamically without recreating the middleware.\n */\nasync function resolveSkillMessages(skill: ContextChefOptions['skill']): Promise<Message[]> {\n if (!skill) return [];\n const resolved = typeof skill === 'function' ? await skill() : skill;\n // Treat whitespace-only instructions as empty — they would otherwise pollute\n // the prompt and create a needless cache breakpoint between system and history.\n if (!resolved || !resolved.instructions || !resolved.instructions.trim()) return [];\n return [{ role: 'system', content: resolved.instructions }];\n}\n\n/**\n * Injects dynamic state XML into the AI SDK prompt.\n *\n * - `last_user`: Appends to the last user message's content parts.\n * Leverages Recency Bias for maximum LLM attention.\n * - `system`: Adds as a standalone system message at the end.\n */\nasync function injectDynamicState(\n prompt: LanguageModelV3Prompt,\n config: DynamicStateConfig,\n): Promise<LanguageModelV3Prompt> {\n const state = await config.getState();\n const xml = XmlGenerator.objectToXml(state, 'dynamic_state');\n const placement = config.placement ?? 'last_user';\n\n if (placement === 'system') {\n return [...prompt, { role: 'system', content: `CURRENT TASK STATE:\\n${xml}` }];\n }\n\n // last_user: inject into the last user message\n const result = [...prompt];\n const stateBlock = `\\n\\n${xml}\\nAbove is the current system state. Use it to guide your next action.`;\n\n for (let i = result.length - 1; i >= 0; i--) {\n const msg = result[i];\n if (msg.role === 'user') {\n result[i] = {\n ...msg,\n content: [...msg.content, { type: 'text', text: stateBlock }],\n };\n return result;\n }\n }\n\n // No user message found — append as new user message\n result.push({\n role: 'user',\n content: [{ type: 'text', text: stateBlock.trim() }],\n });\n return result;\n}\n\n/**\n * Maps an IR role to a role accepted by generateText.\n * Tool messages are handled separately before this is called.\n */\nfunction toCompressRole(role: string): CompressRole {\n if (role === 'system' || role === 'user' || role === 'assistant') return role;\n return 'user';\n}\n\n/**\n * Adapts an AI SDK LanguageModelV3 into the compressionModel callback\n * that Janitor expects: (messages: Message[]) => Promise<string>\n *\n * Tool messages are converted to user messages describing the tool interaction,\n * since generateText only accepts system/user/assistant roles.\n */\nfunction createCompressionAdapter(\n model: LanguageModelV3,\n): (messages: Message[]) => Promise<string> {\n return async (messages: Message[]): Promise<string> => {\n const formatted = messages.map((m): { role: CompressRole; content: string } => {\n if (m.role === 'tool') {\n return {\n role: 'user' satisfies CompressRole,\n content: `[Tool result${m.tool_call_id ? ` (${m.tool_call_id})` : ''}: ${m.content}]`,\n };\n }\n if (m.role === 'assistant' && m.tool_calls?.length) {\n const toolCallsDesc = m.tool_calls\n .map((tc) => `[Called tool: ${tc.function.name}(${tc.function.arguments})]`)\n .join('\\n');\n return {\n role: 'assistant' satisfies CompressRole,\n content: m.content ? `${m.content}\\n${toolCallsDesc}` : toolCallsDesc,\n };\n }\n return {\n role: toCompressRole(m.role),\n content: m.content,\n };\n });\n\n const { text } = await generateText({\n model,\n messages: formatted,\n maxOutputTokens: 2048,\n });\n\n return text || '[Compression produced no output]';\n };\n}\n","import type { LanguageModelV3 } from '@ai-sdk/provider';\nimport { wrapLanguageModel } from 'ai';\n\nimport { createMiddleware } from './middleware';\nimport type { ContextChefOptions } from './types';\n\nexport { type AISDKMessage, fromAISDK, toAISDK } from './adapter';\nexport { createMiddleware } from './middleware';\nexport type {\n CompactConfig,\n CompressOptions,\n ContextChefOptions,\n DynamicStateConfig,\n TruncateOptions,\n} from './types';\n\n/**\n * Wraps an AI SDK language model with context-chef middleware for\n * transparent history compression, tool result truncation, and token budget management.\n *\n * @example\n * ```typescript\n * import { withContextChef } from '@context-chef/ai-sdk-middleware';\n * import { openai } from '@ai-sdk/openai';\n * import { generateText } from 'ai';\n *\n * const model = withContextChef(openai('gpt-4o'), {\n * contextWindow: 128_000,\n * compress: { model: openai('gpt-4o-mini') },\n * truncate: { threshold: 5000, headChars: 500, tailChars: 1000 },\n * });\n *\n * // Use exactly like normal — zero other code changes\n * const result = await generateText({ model, messages, tools });\n * ```\n */\nexport function withContextChef(\n model: LanguageModelV3,\n options: ContextChefOptions,\n): LanguageModelV3 {\n const middleware = createMiddleware(options);\n return wrapLanguageModel({ model, middleware });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/adapter.ts","../src/truncator.ts","../src/middleware.ts","../src/index.ts"],"sourcesContent":["import type {\n LanguageModelV3Message,\n LanguageModelV3Prompt,\n LanguageModelV3ToolResultOutput,\n LanguageModelV3ToolResultPart,\n SharedV3ProviderOptions,\n} from '@ai-sdk/provider';\nimport type { Attachment, Message, ToolCall } from '@context-chef/core';\n\n/** Content types for each AI SDK message role */\ntype UserContent = Extract<LanguageModelV3Message, { role: 'user' }>['content'];\ntype AssistantContent = Extract<LanguageModelV3Message, { role: 'assistant' }>['content'];\ntype ToolContent = Extract<LanguageModelV3Message, { role: 'tool' }>['content'];\n\n/**\n * Extended IR message with typed pass-through fields for lossless AI SDK round-trip.\n * Per-role content fields avoid union types, so no `as` casts are needed in `toAISDK`.\n */\nexport interface AISDKMessage extends Message {\n _userContent?: UserContent;\n _assistantContent?: AssistantContent;\n _toolContent?: ToolContent;\n _originalText?: string;\n _providerOptions?: SharedV3ProviderOptions;\n _toolName?: string;\n}\n\n/**\n * Converts an AI SDK V3 prompt to context-chef IR messages.\n *\n * Original AI SDK content is stored in per-role fields for lossless round-trip.\n * `_originalText` caches the extracted text so `toAISDK` can detect Janitor modifications.\n * `_providerOptions` preserves message-level provider options (e.g. Anthropic cache control).\n */\nexport function fromAISDK(prompt: LanguageModelV3Prompt): AISDKMessage[] {\n const messages: AISDKMessage[] = [];\n\n for (const msg of prompt) {\n if (msg.role === 'system') {\n messages.push({\n role: 'system',\n content: msg.content,\n ...(msg.providerOptions ? { _providerOptions: msg.providerOptions } : {}),\n });\n continue;\n }\n\n if (msg.role === 'user') {\n const text = msg.content\n .filter((p) => p.type === 'text')\n .map((p) => p.text)\n .join('\\n');\n\n const attachments: Attachment[] = [];\n for (const part of msg.content) {\n if (part.type === 'file') {\n // `attachment.data` here is just a presence/metadata signal for Janitor\n // (used by `m.attachments?.length` checks and the `[image]`/`[document]`\n // placeholder helper, neither of which read `data`). The real binary\n // payload — including `Uint8Array` / `URL` shapes — round-trips losslessly\n // through `_userContent`, which `toAISDK` hands back to the AI SDK\n // provider verbatim. We only record `data` when it's already a string,\n // so we never invent a fake encoding for non-string inputs.\n attachments.push({\n mediaType: part.mediaType,\n data: typeof part.data === 'string' ? part.data : '',\n ...(part.filename ? { filename: part.filename } : {}),\n });\n }\n }\n\n const m: AISDKMessage = {\n role: 'user',\n content: text,\n _userContent: msg.content,\n _originalText: text,\n ...(msg.providerOptions ? { _providerOptions: msg.providerOptions } : {}),\n };\n if (attachments.length) m.attachments = attachments;\n messages.push(m);\n continue;\n }\n\n if (msg.role === 'assistant') {\n const text: string[] = [];\n const toolCalls: ToolCall[] = [];\n const attachments: Attachment[] = [];\n let thinking: { thinking: string } | undefined;\n\n for (const part of msg.content) {\n if (part.type === 'text') text.push(part.text);\n else if (part.type === 'tool-call') {\n toolCalls.push({\n id: part.toolCallId,\n type: 'function',\n function: {\n name: part.toolName,\n arguments: typeof part.input === 'string' ? part.input : JSON.stringify(part.input),\n },\n });\n } else if (part.type === 'reasoning') {\n thinking = { thinking: part.text };\n } else if (part.type === 'file') {\n // See user-side comment above: data is a presence signal only;\n // _assistantContent carries the actual payload through round-trip.\n attachments.push({\n mediaType: part.mediaType,\n data: typeof part.data === 'string' ? part.data : '',\n ...(part.filename ? { filename: part.filename } : {}),\n });\n }\n }\n\n const joinedText = text.join('\\n');\n const m: AISDKMessage = {\n role: 'assistant',\n content: joinedText,\n _assistantContent: msg.content,\n _originalText: joinedText,\n ...(msg.providerOptions ? { _providerOptions: msg.providerOptions } : {}),\n };\n if (toolCalls.length > 0) m.tool_calls = toolCalls;\n if (thinking) m.thinking = thinking;\n if (attachments.length) m.attachments = attachments;\n messages.push(m);\n continue;\n }\n\n if (msg.role === 'tool') {\n for (const part of msg.content) {\n if (part.type === 'tool-result') {\n const text = stringifyToolOutput(part.output);\n messages.push({\n role: 'tool',\n content: text,\n tool_call_id: part.toolCallId,\n _toolContent: [part],\n _originalText: text,\n _toolName: part.toolName,\n });\n }\n }\n }\n }\n\n return messages;\n}\n\n/**\n * Narrows a generic Message to AISDKMessage for typed access to pass-through fields.\n */\nfunction asAISDK(msg: Message): AISDKMessage {\n return msg;\n}\n\n/**\n * Converts context-chef IR messages back to AI SDK V3 prompt format.\n *\n * Uses per-role original content when unmodified (detected via `_originalText`).\n * Falls back to constructing from IR fields when content was modified by Janitor\n * (e.g. compact() cleared tool results) or for new messages (e.g. compression summaries).\n */\nexport function toAISDK(messages: Message[]): LanguageModelV3Prompt {\n const prompt: LanguageModelV3Prompt = [];\n\n let i = 0;\n while (i < messages.length) {\n const msg = asAISDK(messages[i]);\n const contentModified = msg._originalText !== undefined && msg._originalText !== msg.content;\n\n if (msg.role === 'system') {\n prompt.push({\n role: 'system',\n content: msg.content,\n ...(msg._providerOptions ? { providerOptions: msg._providerOptions } : {}),\n });\n i++;\n continue;\n }\n\n if (msg.role === 'user') {\n prompt.push({\n role: 'user',\n content:\n !contentModified && msg._userContent\n ? msg._userContent\n : [{ type: 'text', text: msg.content }],\n ...(msg._providerOptions ? { providerOptions: msg._providerOptions } : {}),\n });\n i++;\n continue;\n }\n\n if (msg.role === 'assistant') {\n prompt.push({\n role: 'assistant',\n content:\n !contentModified && msg._assistantContent\n ? msg._assistantContent\n : [{ type: 'text', text: msg.content }],\n ...(msg._providerOptions ? { providerOptions: msg._providerOptions } : {}),\n });\n i++;\n continue;\n }\n\n if (msg.role === 'tool') {\n const toolResults: LanguageModelV3ToolResultPart[] = [];\n while (i < messages.length && messages[i].role === 'tool') {\n const toolMsg = asAISDK(messages[i]);\n const toolModified =\n toolMsg._originalText !== undefined && toolMsg._originalText !== toolMsg.content;\n\n if (!toolModified && toolMsg._toolContent) {\n for (const part of toolMsg._toolContent) {\n if (part.type === 'tool-result') {\n toolResults.push(part);\n }\n }\n } else {\n toolResults.push({\n type: 'tool-result',\n toolCallId: toolMsg.tool_call_id ?? '',\n toolName: toolMsg._toolName ?? 'unknown',\n output: { type: 'text', value: toolMsg.content },\n });\n }\n i++;\n }\n prompt.push({ role: 'tool', content: toolResults });\n continue;\n }\n\n i++;\n }\n\n return prompt;\n}\n\nfunction stringifyToolOutput(output: LanguageModelV3ToolResultOutput): string {\n switch (output.type) {\n case 'text':\n case 'error-text':\n return output.value;\n case 'json':\n case 'error-json':\n return JSON.stringify(output.value);\n case 'content':\n return output.value\n .map((v) => (v.type === 'text' ? v.text : ''))\n .filter(Boolean)\n .join('\\n');\n default:\n return JSON.stringify(output);\n }\n}\n","import type {\n LanguageModelV3Prompt,\n LanguageModelV3ToolResultOutput,\n LanguageModelV3ToolResultPart,\n} from '@ai-sdk/provider';\nimport { Offloader } from '@context-chef/core';\nimport type { TruncateOptions } from './types';\n\n/**\n * Truncates tool-result content within an AI SDK prompt when it exceeds the configured threshold.\n * When a storage adapter is provided, original content is persisted and a URI is included in the output.\n */\nexport async function truncateToolResults(\n prompt: LanguageModelV3Prompt,\n options: TruncateOptions,\n): Promise<LanguageModelV3Prompt> {\n const { threshold, headChars = 0, tailChars = 1000, storage } = options;\n\n const offloader = storage ? new Offloader({ threshold, adapter: storage, storageDir: '' }) : null;\n const policy = buildPolicyMap(options.perTool);\n\n const result: LanguageModelV3Prompt = [];\n\n for (const msg of prompt) {\n if (msg.role !== 'tool') {\n result.push(msg);\n continue;\n }\n\n const newContent: typeof msg.content = [];\n\n for (const part of msg.content) {\n if (part.type !== 'tool-result') {\n newContent.push(part);\n continue;\n }\n\n const toolPolicy = policy.get(part.toolName);\n if (toolPolicy?.preserve) {\n // Preserve = full bypass: no truncation, no storage write.\n newContent.push(part);\n continue;\n }\n\n const effThreshold = toolPolicy?.threshold ?? threshold;\n const effHeadChars = toolPolicy?.headChars ?? headChars;\n const effTailChars = toolPolicy?.tailChars ?? tailChars;\n\n const text = extractText(part.output);\n if (text.length <= effThreshold || effHeadChars + effTailChars >= text.length) {\n newContent.push(part);\n continue;\n }\n\n // With storage: use Offloader to persist original and get a URI-annotated truncation\n if (offloader) {\n try {\n const vfsResult = await offloader.offloadAsync(text, {\n threshold: effThreshold,\n headChars: effHeadChars,\n tailChars: effTailChars,\n });\n newContent.push({\n ...part,\n output: {\n type: 'text',\n value: vfsResult.content,\n } satisfies LanguageModelV3ToolResultOutput,\n } satisfies LanguageModelV3ToolResultPart);\n continue;\n } catch (error) {\n console.warn(\n `[context-chef] Storage adapter write failed for tool result (${part.toolCallId}). ` +\n `Falling back to simple truncation. Error: ${error instanceof Error ? error.message : String(error)}`,\n );\n // Fall through to simple truncation below\n }\n }\n\n // Without storage: simple truncation, original is discarded\n const head = text.slice(0, effHeadChars);\n const tail = text.slice(text.length - effTailChars);\n const totalLines = text.split('\\n').length;\n\n const truncated = [\n head,\n `\\n--- truncated (${totalLines} lines, ${text.length} chars total) ---\\n`,\n tail,\n ]\n .filter(Boolean)\n .join('')\n .trim();\n\n newContent.push({\n ...part,\n output: { type: 'text', value: truncated } satisfies LanguageModelV3ToolResultOutput,\n } satisfies LanguageModelV3ToolResultPart);\n }\n\n result.push({ ...msg, content: newContent });\n }\n\n return result;\n}\n\ntype ToolPolicy =\n | { preserve: true }\n | {\n preserve?: false;\n threshold?: number;\n headChars?: number;\n tailChars?: number;\n };\n\n/**\n * Normalises `perTool` into a name → policy lookup.\n * Bare strings become `{ preserve: true }`; objects keep their partial overrides.\n * Last entry wins on duplicate names.\n */\nfunction buildPolicyMap(perTool: TruncateOptions['perTool']): Map<string, ToolPolicy> {\n const map = new Map<string, ToolPolicy>();\n if (!perTool) return map;\n for (const entry of perTool) {\n if (typeof entry === 'string') {\n map.set(entry, { preserve: true });\n } else {\n map.set(entry.name, {\n threshold: entry.threshold,\n headChars: entry.headChars,\n tailChars: entry.tailChars,\n });\n }\n }\n return map;\n}\n\nfunction extractText(output: LanguageModelV3ToolResultOutput): string {\n switch (output.type) {\n case 'text':\n case 'error-text':\n return output.value;\n case 'json':\n case 'error-json':\n return JSON.stringify(output.value);\n case 'content':\n return output.value\n .map((v: { type: string; text?: string }) => (v.type === 'text' ? (v.text ?? '') : ''))\n .filter(Boolean)\n .join('\\n');\n default:\n return '';\n }\n}\n","import type {\n LanguageModelV3,\n LanguageModelV3Message,\n LanguageModelV3Prompt,\n LanguageModelV3StreamPart,\n} from '@ai-sdk/provider';\nimport { Janitor, type Message, XmlGenerator } from '@context-chef/core';\nimport { generateText, type LanguageModelMiddleware, type ModelMessage, pruneMessages } from 'ai';\n\nimport { fromAISDK, toAISDK } from './adapter';\nimport { truncateToolResults } from './truncator';\nimport type { ContextChefOptions, DynamicStateConfig } from './types';\n\ntype CompressRole = 'system' | 'user' | 'assistant';\n\n/**\n * Creates a LanguageModelMiddleware that transparently applies\n * context-chef compression and truncation to AI SDK model calls.\n *\n * The middleware holds a stateful Janitor instance that tracks\n * token usage across calls for compression decisions.\n */\nexport function createMiddleware(options: ContextChefOptions): LanguageModelMiddleware {\n let usageWarned = false;\n\n const janitor = new Janitor({\n contextWindow: options.contextWindow,\n tokenizer: options.tokenizer ? (msgs: Message[]) => options.tokenizer?.(msgs) ?? 0 : undefined,\n preserveRatio: options.compress?.preserveRatio ?? 0.8,\n compressionModel: options.compress?.model\n ? createCompressionAdapter(options.compress.model)\n : undefined,\n onCompress: options.onCompress\n ? (summary, count) => options.onCompress?.(summary.content, count)\n : undefined,\n onBeforeCompress: options.onBeforeCompress ?? options.onBudgetExceeded,\n });\n\n return {\n specificationVersion: 'v3',\n\n transformParams: async ({ params }) => {\n let { prompt } = params;\n\n // 1. Truncate large tool results\n if (options.truncate) {\n prompt = await truncateToolResults(prompt, options.truncate);\n }\n\n // 2. Compact (mechanical, zero LLM cost) via pruneMessages\n if (options.compact) {\n prompt = compactPrompt(prompt, options.compact);\n }\n\n // 3. Convert to IR and separate system messages from conversation.\n // System messages are standing instructions — they must not be\n // compressed away. Only conversation history goes through compact/compress.\n const allIR = fromAISDK(prompt);\n const systemMessages = allIR.filter((m) => m.role === 'system');\n let conversation = allIR.filter((m) => m.role !== 'system');\n\n // 4. Compress conversation history if over token budget\n conversation = await janitor.compress(conversation);\n\n // 5. Reassemble sandwich: user system + skill instructions + conversation.\n // The skill slot mirrors @context-chef/core compile() ordering\n // (SKILL_SPEC §6.3): a dedicated system message AFTER user system\n // and BEFORE the conversation history. Empty instructions are\n // skipped to avoid emitting an empty system message.\n const skillMessages = await resolveSkillMessages(options.skill);\n const irMessages = [...systemMessages, ...skillMessages, ...conversation];\n\n // 6. Convert back to AI SDK format\n prompt = toAISDK(irMessages);\n\n // 7. Dynamic state injection\n if (options.dynamicState) {\n prompt = await injectDynamicState(prompt, options.dynamicState);\n }\n\n // 8. Custom transform hook\n if (options.transformContext) {\n prompt = await options.transformContext(prompt);\n }\n\n return { ...params, prompt };\n },\n\n wrapGenerate: async ({ doGenerate }) => {\n const result = await doGenerate();\n\n if (result.usage?.inputTokens?.total != null) {\n janitor.feedTokenUsage(result.usage.inputTokens.total);\n } else if (!usageWarned && !options.tokenizer) {\n usageWarned = true;\n console.warn(\n '[context-chef] Model response did not include usage.inputTokens.total. ' +\n 'Token-based compression may not trigger accurately. ' +\n 'Consider providing a tokenizer for precise token counting.',\n );\n }\n\n return result;\n },\n\n wrapStream: async ({ doStream }) => {\n const { stream, ...rest } = await doStream();\n\n const transform = new TransformStream<LanguageModelV3StreamPart, LanguageModelV3StreamPart>({\n transform(chunk, controller) {\n if (chunk.type === 'finish') {\n if (chunk.usage?.inputTokens?.total != null) {\n janitor.feedTokenUsage(chunk.usage.inputTokens.total);\n } else if (!usageWarned && !options.tokenizer) {\n usageWarned = true;\n console.warn(\n '[context-chef] Stream finish did not include usage.inputTokens.total. ' +\n 'Token-based compression may not trigger accurately. ' +\n 'Consider providing a tokenizer for precise token counting.',\n );\n }\n }\n controller.enqueue(chunk);\n },\n });\n\n return { ...rest, stream: stream.pipeThrough(transform) };\n },\n };\n}\n\n/**\n * Prunes a LanguageModelV3Prompt via AI SDK's pruneMessages.\n *\n * LanguageModelV3Message (from @ai-sdk/provider) and ModelMessage\n * (from @ai-sdk/provider-utils) share identical runtime structure but\n * differ at the TypeScript level (e.g. ImagePart, FilePart.data).\n * Since pruneMessages only filters — never transforms — every content\n * part in the output is an original V3 part, making the casts safe.\n */\nfunction compactPrompt(\n prompt: LanguageModelV3Prompt,\n config: Omit<Parameters<typeof pruneMessages>[0], 'messages'>,\n): LanguageModelV3Prompt {\n const messages = prompt.map(\n (msg) =>\n ({\n role: msg.role,\n content: msg.content,\n providerOptions: msg.providerOptions,\n }) as ModelMessage,\n );\n const pruned = pruneMessages({ messages, ...config });\n return pruned.map(\n (msg) =>\n ({\n role: msg.role,\n content: msg.content,\n providerOptions: msg.providerOptions,\n }) as LanguageModelV3Message,\n );\n}\n\n/**\n * Resolves the `skill` option into IR system messages to insert between\n * user system messages and conversation. Returns `[]` when no skill is\n * active, the resolver returns null/undefined, or instructions are empty.\n *\n * The function form is invoked on every transformParams call so the\n * caller can swap skills dynamically without recreating the middleware.\n */\nasync function resolveSkillMessages(skill: ContextChefOptions['skill']): Promise<Message[]> {\n if (!skill) return [];\n const resolved = typeof skill === 'function' ? await skill() : skill;\n // Treat whitespace-only instructions as empty — they would otherwise pollute\n // the prompt and create a needless cache breakpoint between system and history.\n if (!resolved || !resolved.instructions || !resolved.instructions.trim()) return [];\n return [{ role: 'system', content: resolved.instructions }];\n}\n\n/**\n * Injects dynamic state XML into the AI SDK prompt.\n *\n * - `last_user`: Appends to the last user message's content parts.\n * Leverages Recency Bias for maximum LLM attention.\n * - `system`: Adds as a standalone system message at the end.\n */\nasync function injectDynamicState(\n prompt: LanguageModelV3Prompt,\n config: DynamicStateConfig,\n): Promise<LanguageModelV3Prompt> {\n const state = await config.getState();\n const xml = XmlGenerator.objectToXml(state, 'dynamic_state');\n const placement = config.placement ?? 'last_user';\n\n if (placement === 'system') {\n return [...prompt, { role: 'system', content: `CURRENT TASK STATE:\\n${xml}` }];\n }\n\n // last_user: inject into the last user message\n const result = [...prompt];\n const stateBlock = `\\n\\n${xml}\\nAbove is the current system state. Use it to guide your next action.`;\n\n for (let i = result.length - 1; i >= 0; i--) {\n const msg = result[i];\n if (msg.role === 'user') {\n result[i] = {\n ...msg,\n content: [...msg.content, { type: 'text', text: stateBlock }],\n };\n return result;\n }\n }\n\n // No user message found — append as new user message\n result.push({\n role: 'user',\n content: [{ type: 'text', text: stateBlock.trim() }],\n });\n return result;\n}\n\n/**\n * Maps an IR role to a role accepted by generateText.\n * Tool messages are handled separately before this is called.\n */\nfunction toCompressRole(role: string): CompressRole {\n if (role === 'system' || role === 'user' || role === 'assistant') return role;\n return 'user';\n}\n\n/**\n * Adapts an AI SDK LanguageModelV3 into the compressionModel callback\n * that Janitor expects: (messages: Message[]) => Promise<string>\n *\n * Tool messages are converted to user messages describing the tool interaction,\n * since generateText only accepts system/user/assistant roles.\n */\nfunction createCompressionAdapter(\n model: LanguageModelV3,\n): (messages: Message[]) => Promise<string> {\n return async (messages: Message[]): Promise<string> => {\n const formatted = messages.map((m): { role: CompressRole; content: string } => {\n if (m.role === 'tool') {\n return {\n role: 'user' satisfies CompressRole,\n content: `[Tool result${m.tool_call_id ? ` (${m.tool_call_id})` : ''}: ${m.content}]`,\n };\n }\n if (m.role === 'assistant' && m.tool_calls?.length) {\n const toolCallsDesc = m.tool_calls\n .map((tc) => `[Called tool: ${tc.function.name}(${tc.function.arguments})]`)\n .join('\\n');\n return {\n role: 'assistant' satisfies CompressRole,\n content: m.content ? `${m.content}\\n${toolCallsDesc}` : toolCallsDesc,\n };\n }\n return {\n role: toCompressRole(m.role),\n content: m.content,\n };\n });\n\n const { text } = await generateText({\n model,\n messages: formatted,\n maxOutputTokens: 2048,\n });\n\n return text || '[Compression produced no output]';\n };\n}\n","import type { LanguageModelV3 } from '@ai-sdk/provider';\nimport { wrapLanguageModel } from 'ai';\n\nimport { createMiddleware } from './middleware';\nimport type { ContextChefOptions } from './types';\n\nexport { type AISDKMessage, fromAISDK, toAISDK } from './adapter';\nexport { createMiddleware } from './middleware';\nexport type {\n CompactConfig,\n CompressOptions,\n ContextChefOptions,\n DynamicStateConfig,\n TruncateOptions,\n} from './types';\n\n/**\n * Wraps an AI SDK language model with context-chef middleware for\n * transparent history compression, tool result truncation, and token budget management.\n *\n * @example\n * ```typescript\n * import { withContextChef } from '@context-chef/ai-sdk-middleware';\n * import { openai } from '@ai-sdk/openai';\n * import { generateText } from 'ai';\n *\n * const model = withContextChef(openai('gpt-4o'), {\n * contextWindow: 128_000,\n * compress: { model: openai('gpt-4o-mini') },\n * truncate: { threshold: 5000, headChars: 500, tailChars: 1000 },\n * });\n *\n * // Use exactly like normal — zero other code changes\n * const result = await generateText({ model, messages, tools });\n * ```\n */\nexport function withContextChef(\n model: LanguageModelV3,\n options: ContextChefOptions,\n): LanguageModelV3 {\n const middleware = createMiddleware(options);\n return wrapLanguageModel({ model, middleware });\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@context-chef/ai-sdk-middleware",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"url": "https://github.com/MyPrototypeWhat/context-chef/issues"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@context-chef/core": "3.
|
|
42
|
+
"@context-chef/core": "3.3.0"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"@ai-sdk/provider": ">=3",
|