@x-code-cli/core 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/file-ingest.d.ts +62 -0
- package/dist/agent/file-ingest.d.ts.map +1 -0
- package/dist/agent/file-ingest.js +390 -0
- package/dist/agent/file-ingest.js.map +1 -0
- package/dist/agent/light-compact.d.ts +13 -0
- package/dist/agent/light-compact.d.ts.map +1 -0
- package/dist/agent/light-compact.js +106 -0
- package/dist/agent/light-compact.js.map +1 -0
- package/dist/agent/loop-guard.d.ts +50 -0
- package/dist/agent/loop-guard.d.ts.map +1 -0
- package/dist/agent/loop-guard.js +107 -0
- package/dist/agent/loop-guard.js.map +1 -0
- package/dist/agent/loop-state.d.ts +11 -0
- package/dist/agent/loop-state.d.ts.map +1 -1
- package/dist/agent/loop-state.js +2 -0
- package/dist/agent/loop-state.js.map +1 -1
- package/dist/agent/loop.d.ts +2 -2
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +65 -8
- package/dist/agent/loop.js.map +1 -1
- package/dist/agent/messages.d.ts +5 -2
- package/dist/agent/messages.d.ts.map +1 -1
- package/dist/agent/messages.js.map +1 -1
- package/dist/agent/provider-compat.d.ts +7 -0
- package/dist/agent/provider-compat.d.ts.map +1 -1
- package/dist/agent/provider-compat.js +122 -0
- package/dist/agent/provider-compat.js.map +1 -1
- package/dist/agent/system-prompt.js +3 -3
- package/dist/agent/system-prompt.js.map +1 -1
- package/dist/agent/tool-execution.d.ts.map +1 -1
- package/dist/agent/tool-execution.js +68 -26
- package/dist/agent/tool-execution.js.map +1 -1
- package/dist/agent/tool-result-sanitize.d.ts +8 -0
- package/dist/agent/tool-result-sanitize.d.ts.map +1 -0
- package/dist/agent/tool-result-sanitize.js +77 -0
- package/dist/agent/tool-result-sanitize.js.map +1 -0
- package/dist/agent/vision-fallback.d.ts +22 -0
- package/dist/agent/vision-fallback.d.ts.map +1 -0
- package/dist/agent/vision-fallback.js +127 -0
- package/dist/agent/vision-fallback.js.map +1 -0
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/providers/cache-control.d.ts +29 -0
- package/dist/providers/cache-control.d.ts.map +1 -0
- package/dist/providers/cache-control.js +93 -0
- package/dist/providers/cache-control.js.map +1 -0
- package/dist/providers/capabilities.d.ts +15 -0
- package/dist/providers/capabilities.d.ts.map +1 -0
- package/dist/providers/capabilities.js +38 -0
- package/dist/providers/capabilities.js.map +1 -0
- package/dist/tools/index.d.ts +31 -5
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +1 -10
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/read-file.d.ts +29 -1
- package/dist/tools/read-file.d.ts.map +1 -1
- package/dist/tools/read-file.js +103 -10
- package/dist/tools/read-file.js.map +1 -1
- package/dist/tools/shell-provider.d.ts +13 -0
- package/dist/tools/shell-provider.d.ts.map +1 -0
- package/dist/tools/shell-provider.js +74 -0
- package/dist/tools/shell-provider.js.map +1 -0
- package/dist/tools/shell-utils.d.ts +1 -7
- package/dist/tools/shell-utils.d.ts.map +1 -1
- package/dist/tools/shell-utils.js +0 -17
- package/dist/tools/shell-utils.js.map +1 -1
- package/dist/tools/truncate.d.ts +36 -0
- package/dist/tools/truncate.d.ts.map +1 -0
- package/dist/tools/truncate.js +118 -0
- package/dist/tools/truncate.js.map +1 -0
- package/dist/tools/web-search.js +2 -2
- package/dist/tools/web-search.js.map +1 -1
- package/dist/utils/shell-error.d.ts +12 -0
- package/dist/utils/shell-error.d.ts.map +1 -0
- package/dist/utils/shell-error.js +73 -0
- package/dist/utils/shell-error.js.map +1 -0
- package/package.json +21 -12
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
// @x-code-cli/core — Tool execution & dispatch
|
|
2
|
-
import { execa } from 'execa';
|
|
3
2
|
import fs from 'node:fs/promises';
|
|
4
3
|
import path from 'node:path';
|
|
5
4
|
import { checkPermission } from '../permissions/index.js';
|
|
6
5
|
import { truncateToolResult } from '../tools/index.js';
|
|
7
6
|
import { clearProgressReporter, reportProgress } from '../tools/progress.js';
|
|
8
|
-
import {
|
|
7
|
+
import { getShellProvider } from '../tools/shell-provider.js';
|
|
8
|
+
import { foldShellErrorNoise } from '../utils/shell-error.js';
|
|
9
|
+
import { checkForLoop, recordToolCall } from './loop-guard.js';
|
|
9
10
|
import { toolResultMessage } from './messages.js';
|
|
10
11
|
/** Count occurrences of a substring without creating intermediate arrays. */
|
|
11
12
|
function countOccurrences(content, search) {
|
|
@@ -55,29 +56,7 @@ async function executeWriteTool(toolName, input, toolCallId) {
|
|
|
55
56
|
}
|
|
56
57
|
/** Execute a shell command with streaming. */
|
|
57
58
|
async function executeShell(command, timeout, callbacks, toolCallId) {
|
|
58
|
-
const
|
|
59
|
-
// On Windows, force the console codepage to UTF-8 (65001) at the OS level
|
|
60
|
-
// BEFORE PowerShell starts parsing the command. This ensures even parse errors
|
|
61
|
-
// (e.g. `&&` on PS 5.1) produce UTF-8 output instead of GBK garbled text.
|
|
62
|
-
// We wrap via `cmd.exe /c "chcp 65001 >nul && powershell ..."` because
|
|
63
|
-
// [Console]::OutputEncoding only takes effect after parsing completes.
|
|
64
|
-
let proc;
|
|
65
|
-
if (type === 'powershell') {
|
|
66
|
-
const escapedCommand = command.replace(/"/g, '\\"');
|
|
67
|
-
const psCmd = `chcp 65001 >nul && ${executable} ${args.join(' ')} "${escapedCommand}"`;
|
|
68
|
-
proc = execa('cmd.exe', ['/c', psCmd], {
|
|
69
|
-
timeout,
|
|
70
|
-
reject: false,
|
|
71
|
-
env: { ...process.env, PYTHONIOENCODING: 'utf-8' },
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
proc = execa(executable, [...args, command], {
|
|
76
|
-
timeout,
|
|
77
|
-
reject: false,
|
|
78
|
-
env: { ...process.env, PYTHONIOENCODING: 'utf-8' },
|
|
79
|
-
});
|
|
80
|
-
}
|
|
59
|
+
const proc = getShellProvider().spawn(command, { timeout });
|
|
81
60
|
reportProgress(toolCallId, 'Running command...');
|
|
82
61
|
const onChunk = (chunk) => {
|
|
83
62
|
const s = chunk.toString();
|
|
@@ -95,7 +74,16 @@ async function executeShell(command, timeout, callbacks, toolCallId) {
|
|
|
95
74
|
proc.stdout?.on('data', onChunk);
|
|
96
75
|
proc.stderr?.on('data', onChunk);
|
|
97
76
|
const result = await proc;
|
|
98
|
-
|
|
77
|
+
// Fold PowerShell/cmd multi-line error blocks to a single line before they
|
|
78
|
+
// reach the model. A misquoted command on Windows emits 5–10 lines per
|
|
79
|
+
// attempt; across a loop of failed retries those stacks accumulate faster
|
|
80
|
+
// than the actual diagnostic signal. execa's stdout/stderr are typed as
|
|
81
|
+
// `string | unknown[] | Uint8Array` — we spawn with default string mode, so
|
|
82
|
+
// a cast is safe, but keep a defensive fallback for non-string just in case.
|
|
83
|
+
const toStr = (v) => (typeof v === 'string' ? v : '');
|
|
84
|
+
const stdout = foldShellErrorNoise(toStr(result.stdout));
|
|
85
|
+
const stderr = foldShellErrorNoise(toStr(result.stderr));
|
|
86
|
+
const output = [stdout, stderr].filter(Boolean).join('\n').trim();
|
|
99
87
|
if (result.exitCode !== 0) {
|
|
100
88
|
const text = output ? `${output}\nExit code ${result.exitCode}` : `Exit code ${result.exitCode}`;
|
|
101
89
|
return { output: text, isError: true };
|
|
@@ -112,10 +100,18 @@ function pushToolResult(state, callbacks, toolCallId, toolName, output, isError
|
|
|
112
100
|
clearProgressReporter(toolCallId);
|
|
113
101
|
callbacks.onToolResult(toolCallId, output, isError);
|
|
114
102
|
}
|
|
103
|
+
/** Tools whose execution is driven by the AI SDK (they have an `execute` on
|
|
104
|
+
* the tool definition). By the time we see them in `processToolCalls`, the
|
|
105
|
+
* tool has already run and its result is already in `state.messages`. We
|
|
106
|
+
* can't pre-block these — only record for loop detection and annotate. */
|
|
107
|
+
const AUTO_EXECUTED_TOOLS = new Set(['readFile', 'glob', 'grep', 'listDir', 'webFetch', 'webSearch', 'saveKnowledge']);
|
|
115
108
|
/** Handle a single tool call. Returns when the call has been fully dispatched. */
|
|
116
109
|
async function handleToolCall(tc, state, options, callbacks) {
|
|
117
110
|
const { toolName, input, toolCallId } = tc;
|
|
118
111
|
// ── askUser tool ──
|
|
112
|
+
// Skip the loop guard for askUser — the model asking the user the same
|
|
113
|
+
// clarifying question twice is almost always intentional (e.g. the user
|
|
114
|
+
// answered ambiguously) and blocking it would silently break the UX.
|
|
119
115
|
if (toolName === 'askUser') {
|
|
120
116
|
const question = input.question;
|
|
121
117
|
const optionsList = input.options;
|
|
@@ -123,6 +119,52 @@ async function handleToolCall(tc, state, options, callbacks) {
|
|
|
123
119
|
pushToolResult(state, callbacks, toolCallId, toolName, `User answered: ${answer}`);
|
|
124
120
|
return;
|
|
125
121
|
}
|
|
122
|
+
// ── Doom-loop detection ──
|
|
123
|
+
// For manual tools we pre-block. For auto-executed tools the call has
|
|
124
|
+
// already run (result landed in state.messages via collectTurnResponse);
|
|
125
|
+
// we still record the hash and, on soft-block, push a supplemental notice
|
|
126
|
+
// so the next turn sees a clear stop signal. On hard-block, we additionally
|
|
127
|
+
// prompt the user before returning.
|
|
128
|
+
const isAutoExecuted = AUTO_EXECUTED_TOOLS.has(toolName);
|
|
129
|
+
const loopCheck = checkForLoop(state, toolName, input, toolCallId);
|
|
130
|
+
if (loopCheck.kind !== 'ok') {
|
|
131
|
+
recordToolCall(state, toolName, input);
|
|
132
|
+
if (isAutoExecuted) {
|
|
133
|
+
// The tool result already exists in state.messages. Append a follow-up
|
|
134
|
+
// user-role notice so the model's next step has explicit context that
|
|
135
|
+
// this path is spinning — without this nudge, some models keep trying.
|
|
136
|
+
state.messages.push({
|
|
137
|
+
role: 'user',
|
|
138
|
+
content: `[loop-guard] ${loopCheck.message}`,
|
|
139
|
+
});
|
|
140
|
+
callbacks.onToolResult(toolCallId, `[loop-guard] ${loopCheck.message}`, true);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
// Manual tool — short-circuit by synthesising the result. The tool body
|
|
144
|
+
// never runs; no side effects, no permission prompt.
|
|
145
|
+
pushToolResult(state, callbacks, toolCallId, toolName, `[loop-guard] ${loopCheck.message}`, true);
|
|
146
|
+
}
|
|
147
|
+
if (loopCheck.kind === 'hard-block') {
|
|
148
|
+
const answer = await callbacks
|
|
149
|
+
.onAskUser(`The model keeps calling ${toolName} with identical arguments. How do you want to proceed?`, [
|
|
150
|
+
{ label: 'Pause', description: 'Pause the turn — you can type a new instruction.' },
|
|
151
|
+
{ label: 'Continue', description: 'Let the model keep trying; the loop guard stays armed.' },
|
|
152
|
+
])
|
|
153
|
+
.catch(() => 'Pause');
|
|
154
|
+
if (answer.toLowerCase().startsWith('pause')) {
|
|
155
|
+
// Clear the recent-calls window so the guard doesn't immediately
|
|
156
|
+
// re-trigger on the next turn if the model legitimately retries
|
|
157
|
+
// once with the same args under the user's guidance.
|
|
158
|
+
state.recentToolCalls = [];
|
|
159
|
+
state.messages.push({
|
|
160
|
+
role: 'user',
|
|
161
|
+
content: '[loop-guard] User paused the loop. Wait for further instructions rather than calling more tools.',
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
recordToolCall(state, toolName, input);
|
|
126
168
|
// ── Permission check for write tools and shell ──
|
|
127
169
|
if (toolName === 'writeFile' || toolName === 'edit' || toolName === 'shell') {
|
|
128
170
|
const approved = await checkPermission({ toolCallId, toolName, input }, options.trustMode, callbacks.onAskPermission);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-execution.js","sourceRoot":"","sources":["../../src/agent/tool-execution.ts"],"names":[],"mappings":"AAAA,+CAA+C;AAC/C,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"tool-execution.js","sourceRoot":"","sources":["../../src/agent/tool-execution.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ModelMessage } from 'ai';
|
|
2
|
+
/**
|
|
3
|
+
* Walk `messages` in place and truncate any oversized tool-result parts. Only
|
|
4
|
+
* mutates the `output.value` field; the rest of the message structure is
|
|
5
|
+
* preserved exactly as the provider returned it.
|
|
6
|
+
*/
|
|
7
|
+
export declare function truncateToolResultsInMessages(messages: ModelMessage[]): void;
|
|
8
|
+
//# sourceMappingURL=tool-result-sanitize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-result-sanitize.d.ts","sourceRoot":"","sources":["../../src/agent/tool-result-sanitize.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,IAAI,CAAA;AAgCtC;;;;GAIG;AACH,wBAAgB,6BAA6B,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,IAAI,CAmC5E"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// @x-code-cli/core — Truncate tool-result parts inside ModelMessage arrays
|
|
2
|
+
//
|
|
3
|
+
// AI SDK auto-executed tools (readFile / grep / glob / listDir / webFetch /
|
|
4
|
+
// webSearch) return their results inside `response.messages` as tool-result
|
|
5
|
+
// parts. The manual tool path in `tool-execution.ts` runs every output
|
|
6
|
+
// through `truncateToolResult`, but auto-executed results bypass that path
|
|
7
|
+
// and land in `state.messages` at full size. This module walks the messages
|
|
8
|
+
// produced by a completed stream and applies the same per-tool truncation
|
|
9
|
+
// policy in-place before they persist into the conversation state.
|
|
10
|
+
//
|
|
11
|
+
// Policy is per-tool:
|
|
12
|
+
// - shell / edit / writeFile: manual path already truncated
|
|
13
|
+
// - readFile: head-tail (preserve file start + file end)
|
|
14
|
+
// - grep / glob / listDir: head-only (lexical order is meaningful; the tail
|
|
15
|
+
// carries no additional signal once the head is representative)
|
|
16
|
+
// - webFetch: head-tail (pages often have navigation cruft at top + bottom,
|
|
17
|
+
// but the meaningful content is usually the middle. head-tail still beats
|
|
18
|
+
// head-only because it preserves the final anchors)
|
|
19
|
+
// - default: head-tail
|
|
20
|
+
import { truncateToolResult } from '../tools/truncate.js';
|
|
21
|
+
const PER_TOOL_POLICY = {
|
|
22
|
+
readFile: { direction: 'head-tail' },
|
|
23
|
+
grep: { direction: 'head', maxLines: 500 },
|
|
24
|
+
glob: { direction: 'head', maxLines: 500 },
|
|
25
|
+
listDir: { direction: 'head', maxLines: 500 },
|
|
26
|
+
webFetch: { direction: 'head-tail' },
|
|
27
|
+
webSearch: { direction: 'head-tail' },
|
|
28
|
+
shell: { direction: 'head' },
|
|
29
|
+
};
|
|
30
|
+
function policyFor(toolName) {
|
|
31
|
+
if (!toolName)
|
|
32
|
+
return { direction: 'head-tail' };
|
|
33
|
+
return PER_TOOL_POLICY[toolName] ?? { direction: 'head-tail' };
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Walk `messages` in place and truncate any oversized tool-result parts. Only
|
|
37
|
+
* mutates the `output.value` field; the rest of the message structure is
|
|
38
|
+
* preserved exactly as the provider returned it.
|
|
39
|
+
*/
|
|
40
|
+
export function truncateToolResultsInMessages(messages) {
|
|
41
|
+
for (const msg of messages) {
|
|
42
|
+
if (msg.role !== 'tool')
|
|
43
|
+
continue;
|
|
44
|
+
if (!Array.isArray(msg.content))
|
|
45
|
+
continue;
|
|
46
|
+
for (const part of msg.content) {
|
|
47
|
+
if (part?.type !== 'tool-result')
|
|
48
|
+
continue;
|
|
49
|
+
const output = part.output;
|
|
50
|
+
if (!output)
|
|
51
|
+
continue;
|
|
52
|
+
// Text output: `{ type: 'text', value: string }`
|
|
53
|
+
if (output.type === 'text' && typeof output.value === 'string') {
|
|
54
|
+
const truncated = truncateToolResult(output.value, policyFor(part.toolName));
|
|
55
|
+
if (truncated.length !== output.value.length) {
|
|
56
|
+
output.value = truncated;
|
|
57
|
+
}
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
// Content output: `{ type: 'content', value: Array<{ type: string, text?: string, ... }> }`
|
|
61
|
+
// Only the text entries are mutable — image-data / file-data / file-url
|
|
62
|
+
// are binary payloads that the provider-compat layer handles elsewhere.
|
|
63
|
+
if (output.type === 'content' && Array.isArray(output.value)) {
|
|
64
|
+
const entries = output.value;
|
|
65
|
+
for (const entry of entries) {
|
|
66
|
+
if (entry?.type === 'text' && typeof entry.text === 'string') {
|
|
67
|
+
const truncated = truncateToolResult(entry.text, policyFor(part.toolName));
|
|
68
|
+
if (truncated.length !== entry.text.length) {
|
|
69
|
+
entry.text = truncated;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=tool-result-sanitize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-result-sanitize.js","sourceRoot":"","sources":["../../src/agent/tool-result-sanitize.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface VisionProvider {
|
|
2
|
+
/** Provider id, e.g. "google" / "zhipu". */
|
|
3
|
+
provider: string;
|
|
4
|
+
/** Full <provider>:<model> id passed to the AI SDK registry. */
|
|
5
|
+
modelId: string;
|
|
6
|
+
/** Short label for UI notices ("Gemini 2.5 Flash"). */
|
|
7
|
+
label: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Pick the best available vision sub-agent given the keys the user has
|
|
11
|
+
* configured. Returns null if no vision-capable provider has a key —
|
|
12
|
+
* caller should fall back to local OCR.
|
|
13
|
+
*/
|
|
14
|
+
export declare function pickVisionProvider(): VisionProvider | null;
|
|
15
|
+
/**
|
|
16
|
+
* Generate a textual description of an image via the chosen sub-agent.
|
|
17
|
+
* The prompt asks for both verbatim text AND visual elements (layout,
|
|
18
|
+
* colors, components) — OCR alone misses the latter, so we want the
|
|
19
|
+
* caption to subsume what OCR would have produced.
|
|
20
|
+
*/
|
|
21
|
+
export declare function captionImage(filePath: string, sub: VisionProvider): Promise<string>;
|
|
22
|
+
//# sourceMappingURL=vision-fallback.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vision-fallback.d.ts","sourceRoot":"","sources":["../../src/agent/vision-fallback.ts"],"names":[],"mappings":"AAwBA,MAAM,WAAW,cAAc;IAC7B,4CAA4C;IAC5C,QAAQ,EAAE,MAAM,CAAA;IAChB,gEAAgE;IAChE,OAAO,EAAE,MAAM,CAAA;IACf,uDAAuD;IACvD,KAAK,EAAE,MAAM,CAAA;CACd;AA0BD;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,cAAc,GAAG,IAAI,CAS1D;AAuBD;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAyCzF"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// @x-code-cli/core — Vision sub-agent for text-only providers
|
|
2
|
+
//
|
|
3
|
+
// When the user attaches an image but the active model can't natively see
|
|
4
|
+
// images (DeepSeek today, custom by default), automatically borrow any
|
|
5
|
+
// other configured provider that DOES have a vision model and use it as
|
|
6
|
+
// a caption sub-agent. The caption is injected as a TextPart into the
|
|
7
|
+
// user message so the main model sees a description without ever
|
|
8
|
+
// receiving the binary.
|
|
9
|
+
//
|
|
10
|
+
// Why this exists: DeepSeek users were stuck with local tesseract OCR,
|
|
11
|
+
// which is fine for code screenshots but useless for UI mockups, diagrams,
|
|
12
|
+
// or photos. Most users who set up DeepSeek also have a key for at least
|
|
13
|
+
// one free-tier provider (Gemini or GLM-4V-Flash); detecting that and
|
|
14
|
+
// reusing it removes the need for a manual /model switch every time the
|
|
15
|
+
// user pastes a screenshot.
|
|
16
|
+
import fs from 'node:fs/promises';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import { generateText } from 'ai';
|
|
19
|
+
import { getAvailableProviders } from '../config/index.js';
|
|
20
|
+
import { createModelRegistry } from '../providers/registry.js';
|
|
21
|
+
import { debugLog } from '../utils.js';
|
|
22
|
+
/** Vision-capable model id + display label per provider. Models picked to
|
|
23
|
+
* favor cheap / free-tier offerings — the goal is a quick caption, not
|
|
24
|
+
* deep analysis. */
|
|
25
|
+
const VISION_MODELS = {
|
|
26
|
+
google: { modelId: 'google:gemini-2.5-flash', label: 'Gemini 2.5 Flash' },
|
|
27
|
+
zhipu: { modelId: 'zhipu:glm-4v-flash', label: 'GLM-4V Flash' },
|
|
28
|
+
alibaba: { modelId: 'alibaba:qwen-vl-plus', label: 'Qwen-VL Plus' },
|
|
29
|
+
openai: { modelId: 'openai:gpt-4o-mini', label: 'GPT-4o Mini' },
|
|
30
|
+
anthropic: { modelId: 'anthropic:claude-haiku-4-5', label: 'Claude Haiku 4.5' },
|
|
31
|
+
moonshotai: {
|
|
32
|
+
modelId: 'moonshotai:moonshot-v1-32k-vision-preview',
|
|
33
|
+
label: 'Moonshot Vision Preview',
|
|
34
|
+
},
|
|
35
|
+
xai: { modelId: 'xai:grok-2-vision-1212', label: 'Grok 2 Vision' },
|
|
36
|
+
};
|
|
37
|
+
/** Order in which we try providers when picking a vision sub-agent.
|
|
38
|
+
* Free tiers and cheap-per-image models go first; heavier flagships
|
|
39
|
+
* last. Gemini 2.5 Flash leads because its free tier is the most
|
|
40
|
+
* generous (1500/day) and the model is also the strongest at the
|
|
41
|
+
* free price point. GLM-4V-Flash is second because it's truly free
|
|
42
|
+
* and reachable from China without a proxy. */
|
|
43
|
+
const VISION_PRIORITY = ['google', 'zhipu', 'alibaba', 'openai', 'anthropic', 'moonshotai', 'xai'];
|
|
44
|
+
/**
|
|
45
|
+
* Pick the best available vision sub-agent given the keys the user has
|
|
46
|
+
* configured. Returns null if no vision-capable provider has a key —
|
|
47
|
+
* caller should fall back to local OCR.
|
|
48
|
+
*/
|
|
49
|
+
export function pickVisionProvider() {
|
|
50
|
+
const available = new Set(getAvailableProviders());
|
|
51
|
+
for (const provider of VISION_PRIORITY) {
|
|
52
|
+
if (!available.has(provider))
|
|
53
|
+
continue;
|
|
54
|
+
const model = VISION_MODELS[provider];
|
|
55
|
+
if (!model)
|
|
56
|
+
continue;
|
|
57
|
+
return { provider, modelId: model.modelId, label: model.label };
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
/** Same media-type mapping as file-ingest.ts uses for ImagePart hints. */
|
|
62
|
+
function mediaTypeFor(filePath) {
|
|
63
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
64
|
+
if (ext === '.jpg' || ext === '.jpeg')
|
|
65
|
+
return 'image/jpeg';
|
|
66
|
+
if (ext === '.webp')
|
|
67
|
+
return 'image/webp';
|
|
68
|
+
if (ext === '.gif')
|
|
69
|
+
return 'image/gif';
|
|
70
|
+
if (ext === '.bmp')
|
|
71
|
+
return 'image/bmp';
|
|
72
|
+
return 'image/png';
|
|
73
|
+
}
|
|
74
|
+
/** In-memory cache so re-attaching the same image (or the same image across
|
|
75
|
+
* multiple submits in one session) doesn't re-burn tokens on the sub-agent.
|
|
76
|
+
* Keyed by `${providerId}:${file size}:${first-64-bytes-base64}` — same
|
|
77
|
+
* cheap collision-resistant key strategy provider-compat.ts uses for OCR. */
|
|
78
|
+
const captionCache = new Map();
|
|
79
|
+
async function cacheKey(filePath, providerModelId) {
|
|
80
|
+
const buffer = await fs.readFile(filePath);
|
|
81
|
+
return `${providerModelId}:${buffer.length}:${buffer.subarray(0, 64).toString('base64')}`;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Generate a textual description of an image via the chosen sub-agent.
|
|
85
|
+
* The prompt asks for both verbatim text AND visual elements (layout,
|
|
86
|
+
* colors, components) — OCR alone misses the latter, so we want the
|
|
87
|
+
* caption to subsume what OCR would have produced.
|
|
88
|
+
*/
|
|
89
|
+
export async function captionImage(filePath, sub) {
|
|
90
|
+
const key = await cacheKey(filePath, sub.modelId);
|
|
91
|
+
const cached = captionCache.get(key);
|
|
92
|
+
if (cached != null) {
|
|
93
|
+
debugLog('vision-fallback.cache-hit', `${sub.modelId} ${path.basename(filePath)}`);
|
|
94
|
+
return cached;
|
|
95
|
+
}
|
|
96
|
+
const buffer = await fs.readFile(filePath);
|
|
97
|
+
const registry = createModelRegistry();
|
|
98
|
+
// The registry's languageModel() type is `${string}:${string}` but our
|
|
99
|
+
// VISION_MODELS entries are typed as plain string. Cast at the boundary —
|
|
100
|
+
// we control both ends and every entry is of the form "provider:model".
|
|
101
|
+
const model = registry.languageModel(sub.modelId);
|
|
102
|
+
debugLog('vision-fallback.caption', `${sub.modelId} ${path.basename(filePath)} ${buffer.length}B`);
|
|
103
|
+
const { text } = await generateText({
|
|
104
|
+
model,
|
|
105
|
+
messages: [
|
|
106
|
+
{
|
|
107
|
+
role: 'user',
|
|
108
|
+
content: [
|
|
109
|
+
{
|
|
110
|
+
type: 'text',
|
|
111
|
+
text: 'Describe this image in detail so a text-only AI can act on it. ' +
|
|
112
|
+
'Include: (1) any visible text transcribed verbatim, ' +
|
|
113
|
+
'(2) UI elements, layout, and visual hierarchy, ' +
|
|
114
|
+
'(3) colors, icons, shapes, and other visual details, ' +
|
|
115
|
+
'(4) inferred purpose or context. ' +
|
|
116
|
+
'Be thorough and specific. Output plain text only — no markdown formatting.',
|
|
117
|
+
},
|
|
118
|
+
{ type: 'image', image: buffer, mediaType: mediaTypeFor(filePath) },
|
|
119
|
+
],
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
});
|
|
123
|
+
const caption = text.trim();
|
|
124
|
+
captionCache.set(key, caption);
|
|
125
|
+
return caption;
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=vision-fallback.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vision-fallback.js","sourceRoot":"","sources":["../../src/agent/vision-fallback.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -8,8 +8,15 @@ export { agentLoop, saveSession, compressMessages } from './agent/loop.js';
|
|
|
8
8
|
export type { LoopState } from './agent/loop.js';
|
|
9
9
|
export { buildSystemPrompt } from './agent/system-prompt.js';
|
|
10
10
|
export { classifyApiError } from './agent/api-errors.js';
|
|
11
|
+
export { buildUserContent, extractFileReferences, ingestFile, classifyFile } from './agent/file-ingest.js';
|
|
12
|
+
export type { FileKind, FileReference, IngestedPart } from './agent/file-ingest.js';
|
|
13
|
+
export { captionImage, pickVisionProvider } from './agent/vision-fallback.js';
|
|
14
|
+
export type { VisionProvider } from './agent/vision-fallback.js';
|
|
15
|
+
export { capabilitiesOf, providerOf } from './providers/capabilities.js';
|
|
16
|
+
export type { ProviderCapabilities } from './providers/capabilities.js';
|
|
11
17
|
export { toolRegistry, truncateToolResult } from './tools/index.js';
|
|
12
|
-
export {
|
|
18
|
+
export { getShellProvider } from './tools/shell-provider.js';
|
|
19
|
+
export type { ShellProvider, ShellType } from './tools/shell-provider.js';
|
|
13
20
|
export { checkPermission, getPermissionLevel } from './permissions/index.js';
|
|
14
21
|
export { GLOBAL_XCODE_DIR, XCODE_DIR, debugLog } from './utils.js';
|
|
15
22
|
export { buildKnowledgeContext } from './knowledge/loader.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,YAAY,EACV,eAAe,EACf,UAAU,EACV,cAAc,EACd,eAAe,EACf,cAAc,EACd,YAAY,EACZ,iBAAiB,EACjB,aAAa,EACb,cAAc,EACd,YAAY,EACZ,aAAa,GACd,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EAAE,aAAa,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAC9G,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAGrD,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,aAAa,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AACxH,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAGnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAA;AAG7D,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAC1E,YAAY,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,YAAY,EACV,eAAe,EACf,UAAU,EACV,cAAc,EACd,eAAe,EACf,cAAc,EACd,YAAY,EACZ,iBAAiB,EACjB,aAAa,EACb,cAAc,EACd,YAAY,EACZ,aAAa,GACd,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EAAE,aAAa,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAC9G,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAGrD,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,aAAa,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AACxH,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAGnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAA;AAG7D,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAC1E,YAAY,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AAC1G,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AACnF,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAA;AAC7E,YAAY,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAA;AAGhE,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAA;AACxE,YAAY,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAA;AAGvE,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAA;AAC5D,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAA;AAGzE,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAG5E,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAGlE,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAC7D,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AACxE,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAA;AACtG,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -8,9 +8,13 @@ export { createModelRegistry } from './providers/registry.js';
|
|
|
8
8
|
export { agentLoop, saveSession, compressMessages } from './agent/loop.js';
|
|
9
9
|
export { buildSystemPrompt } from './agent/system-prompt.js';
|
|
10
10
|
export { classifyApiError } from './agent/api-errors.js';
|
|
11
|
+
export { buildUserContent, extractFileReferences, ingestFile, classifyFile } from './agent/file-ingest.js';
|
|
12
|
+
export { captionImage, pickVisionProvider } from './agent/vision-fallback.js';
|
|
13
|
+
// Provider capabilities
|
|
14
|
+
export { capabilitiesOf, providerOf } from './providers/capabilities.js';
|
|
11
15
|
// Tools
|
|
12
16
|
export { toolRegistry, truncateToolResult } from './tools/index.js';
|
|
13
|
-
export {
|
|
17
|
+
export { getShellProvider } from './tools/shell-provider.js';
|
|
14
18
|
// Permissions
|
|
15
19
|
export { checkPermission, getPermissionLevel } from './permissions/index.js';
|
|
16
20
|
// Utils
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,wCAAwC;AAiBxC,OAAO,EAAE,aAAa,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAG9G,SAAS;AACT,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,aAAa,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAGxH,oBAAoB;AACpB,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAA;AAE7D,QAAQ;AACR,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAE1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,wCAAwC;AAiBxC,OAAO,EAAE,aAAa,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAG9G,SAAS;AACT,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,aAAa,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAGxH,oBAAoB;AACpB,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAA;AAE7D,QAAQ;AACR,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAE1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AAE1G,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAA;AAG7E,wBAAwB;AACxB,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAA;AAGxE,QAAQ;AACR,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAA;AAG5D,cAAc;AACd,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAE5E,QAAQ;AACR,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAElE,YAAY;AACZ,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAC7D,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AACxE,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAA;AACtG,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ModelMessage } from 'ai';
|
|
2
|
+
export interface CacheControlArgs {
|
|
3
|
+
/** System prompt string. May be wrapped into a system-role message if the
|
|
4
|
+
* provider needs cache_control attached to it. */
|
|
5
|
+
system: string;
|
|
6
|
+
/** Conversation messages to send. */
|
|
7
|
+
messages: ModelMessage[];
|
|
8
|
+
/** provider:model id used to select the caching strategy. */
|
|
9
|
+
modelId: string;
|
|
10
|
+
/** Stable per-session key. Used by OpenAI's `promptCacheKey` to pin
|
|
11
|
+
* identical prefixes to the same cache shard. */
|
|
12
|
+
sessionId: string;
|
|
13
|
+
}
|
|
14
|
+
export interface CacheControlResult {
|
|
15
|
+
/** Possibly-undefined: for Anthropic we fold the system prompt into the
|
|
16
|
+
* messages array to attach cache_control; in that case streamText must be
|
|
17
|
+
* called without a separate `system` param. */
|
|
18
|
+
system?: string;
|
|
19
|
+
messages: ModelMessage[];
|
|
20
|
+
/** Top-level providerOptions to pass through to streamText. */
|
|
21
|
+
providerOptions?: Record<string, unknown>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Return the request shape enriched with provider-specific caching hints.
|
|
25
|
+
* The input `messages` array is not mutated — new message objects are
|
|
26
|
+
* returned for any message that needs extra providerOptions.
|
|
27
|
+
*/
|
|
28
|
+
export declare function applyCacheControl(args: CacheControlArgs): CacheControlResult;
|
|
29
|
+
//# sourceMappingURL=cache-control.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache-control.d.ts","sourceRoot":"","sources":["../../src/providers/cache-control.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,IAAI,CAAA;AAWtC,MAAM,WAAW,gBAAgB;IAC/B;uDACmD;IACnD,MAAM,EAAE,MAAM,CAAA;IACd,qCAAqC;IACrC,QAAQ,EAAE,YAAY,EAAE,CAAA;IACxB,6DAA6D;IAC7D,OAAO,EAAE,MAAM,CAAA;IACf;sDACkD;IAClD,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC;;oDAEgD;IAChD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,YAAY,EAAE,CAAA;IACxB,+DAA+D;IAC/D,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC1C;AA6BD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,gBAAgB,GAAG,kBAAkB,CAkC5E"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// @x-code-cli/core — Per-provider prompt caching
|
|
2
|
+
//
|
|
3
|
+
// Prompt caching is the single biggest lever on per-session cost. All current
|
|
4
|
+
// providers offer it, but the activation protocol differs:
|
|
5
|
+
//
|
|
6
|
+
// Anthropic — set `cacheControl: { type: 'ephemeral' }` on the SYSTEM
|
|
7
|
+
// message and the LAST two non-system messages (three
|
|
8
|
+
// breakpoints total, under the API's limit of four). The
|
|
9
|
+
// content at each breakpoint is cached server-side with a
|
|
10
|
+
// 5-minute TTL; subsequent requests that share the exact
|
|
11
|
+
// prefix hit the cache and only pay for the uncached tail.
|
|
12
|
+
//
|
|
13
|
+
// OpenAI — automatic prefix caching, but setting `promptCacheKey`
|
|
14
|
+
// (routes identical keys to the same cache shard) and `store`
|
|
15
|
+
// (retains the call for later fetching) improves hit rates.
|
|
16
|
+
// We send the sessionId as the key so every turn in a
|
|
17
|
+
// conversation maps to the same shard.
|
|
18
|
+
//
|
|
19
|
+
// OpenAI- — the DeepSeek / Moonshot / Alibaba / Zhipu / xAI / custom
|
|
20
|
+
// compatible providers all offer automatic prefix caching with NO
|
|
21
|
+
// explicit flags required. The only prerequisite is a
|
|
22
|
+
// byte-stable prefix across turns: if the system prompt
|
|
23
|
+
// rebuilds with a fresh timestamp every turn, every request
|
|
24
|
+
// misses the cache. We therefore cache the system prompt
|
|
25
|
+
// once per session in LoopState (see loop-state.ts) and use
|
|
26
|
+
// the same string on every subsequent turn.
|
|
27
|
+
//
|
|
28
|
+
// Google — Gemini uses implicit caching; no per-request flags we can
|
|
29
|
+
// usefully set from the SDK. Left as a no-op.
|
|
30
|
+
import { providerOf } from './capabilities.js';
|
|
31
|
+
/** Max messages we attach an Anthropic cache breakpoint to. Anthropic allows
|
|
32
|
+
* up to 4 `cache_control` blocks per request; we spend one on the system
|
|
33
|
+
* prompt, leaving three for the message tail. Two is the sweet spot from
|
|
34
|
+
* opencode's testing — a third breakpoint costs a cache-write against a
|
|
35
|
+
* region (the just-before-last message) that's about to be evicted anyway. */
|
|
36
|
+
const MESSAGE_CACHE_BREAKPOINTS = 2;
|
|
37
|
+
/** Attach the given providerOptions entry to a message non-destructively. */
|
|
38
|
+
function tagMessage(msg, provider, entry) {
|
|
39
|
+
const existing = msg.providerOptions ?? {};
|
|
40
|
+
return {
|
|
41
|
+
...msg,
|
|
42
|
+
providerOptions: {
|
|
43
|
+
...existing,
|
|
44
|
+
[provider]: { ...(existing[provider] ?? {}), ...entry },
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/** Build a system-role message with Anthropic cache_control attached. */
|
|
49
|
+
function anthropicSystemMessage(system) {
|
|
50
|
+
return {
|
|
51
|
+
role: 'system',
|
|
52
|
+
content: system,
|
|
53
|
+
providerOptions: {
|
|
54
|
+
anthropic: { cacheControl: { type: 'ephemeral' } },
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Return the request shape enriched with provider-specific caching hints.
|
|
60
|
+
* The input `messages` array is not mutated — new message objects are
|
|
61
|
+
* returned for any message that needs extra providerOptions.
|
|
62
|
+
*/
|
|
63
|
+
export function applyCacheControl(args) {
|
|
64
|
+
const provider = providerOf(args.modelId);
|
|
65
|
+
if (provider === 'anthropic') {
|
|
66
|
+
// Fold system into messages so we can attach cache_control to it, then
|
|
67
|
+
// mark the last N non-system messages as additional breakpoints.
|
|
68
|
+
const nonSystemTail = args.messages.slice(-MESSAGE_CACHE_BREAKPOINTS);
|
|
69
|
+
const tailSet = new Set(nonSystemTail);
|
|
70
|
+
const tagged = args.messages.map((m) => tailSet.has(m) ? tagMessage(m, 'anthropic', { cacheControl: { type: 'ephemeral' } }) : m);
|
|
71
|
+
return {
|
|
72
|
+
system: undefined,
|
|
73
|
+
messages: [anthropicSystemMessage(args.system), ...tagged],
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
if (provider === 'openai') {
|
|
77
|
+
// store:false — we don't need the stored-call bookkeeping (retrieval via
|
|
78
|
+
// API), but the promptCacheKey still routes identical prefixes to the
|
|
79
|
+
// same cache shard which is the actual cost win.
|
|
80
|
+
return {
|
|
81
|
+
system: args.system,
|
|
82
|
+
messages: args.messages,
|
|
83
|
+
providerOptions: {
|
|
84
|
+
openai: { promptCacheKey: args.sessionId, store: false },
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// OpenAI-compatible & Gemini: no explicit flags, just rely on stable prefix.
|
|
89
|
+
// Callers must ensure buildSystemPrompt is cached in LoopState so the same
|
|
90
|
+
// system string is re-sent every turn.
|
|
91
|
+
return { system: args.system, messages: args.messages };
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=cache-control.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache-control.js","sourceRoot":"","sources":["../../src/providers/cache-control.ts"],"names":[],"mappings":"AAAA,iDAAiD;AACjD,EAAE;AACF,8EAA8E;AAC9E,2DAA2D;AAC3D,EAAE;AACF,0EAA0E;AAC1E,sEAAsE;AACtE,yEAAyE;AACzE,0EAA0E;AAC1E,yEAAyE;AACzE,2EAA2E;AAC3E,EAAE;AACF,yEAAyE;AACzE,8EAA8E;AAC9E,4EAA4E;AAC5E,sEAAsE;AACtE,uDAAuD;AACvD,EAAE;AACF,2EAA2E;AAC3E,uEAAuE;AACvE,sEAAsE;AACtE,wEAAwE;AACxE,4EAA4E;AAC5E,yEAAyE;AACzE,4EAA4E;AAC5E,4DAA4D;AAC5D,EAAE;AACF,4EAA4E;AAC5E,8DAA8D;AAI9D,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAE9C;;;;+EAI+E;AAC/E,MAAM,yBAAyB,GAAG,CAAC,CAAA;AAyBnC,6EAA6E;AAC7E,SAAS,UAAU,CACjB,GAAiB,EACjB,QAAgB,EAChB,KAA8B;IAE9B,MAAM,QAAQ,GAAI,GAAqE,CAAC,eAAe,IAAI,EAAE,CAAA;IAC7G,OAAO;QACL,GAAG,GAAG;QACN,eAAe,EAAE;YACf,GAAG,QAAQ;YACX,CAAC,QAAQ,CAAC,EAAE,EAAE,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,KAAK,EAAE;SACxD;KACc,CAAA;AACnB,CAAC;AAED,yEAAyE;AACzE,SAAS,sBAAsB,CAAC,MAAc;IAC5C,OAAO;QACL,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,MAAM;QACf,eAAe,EAAE;YACf,SAAS,EAAE,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE;SACnD;KACyB,CAAA;AAC9B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAsB;IACtD,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IAEzC,IAAI,QAAQ,KAAK,WAAW,EAAE,CAAC;QAC7B,uEAAuE;QACvE,iEAAiE;QACjE,MAAM,aAAa,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,yBAAyB,CAAC,CAAA;QACrE,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,aAAa,CAAC,CAAA;QACtC,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CACrC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,EAAE,WAAW,EAAE,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CACzF,CAAA;QACD,OAAO;YACL,MAAM,EAAE,SAAS;YACjB,QAAQ,EAAE,CAAC,sBAAsB,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,GAAG,MAAM,CAAC;SAC3D,CAAA;IACH,CAAC;IAED,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC1B,yEAAyE;QACzE,sEAAsE;QACtE,iDAAiD;QACjD,OAAO;YACL,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,eAAe,EAAE;gBACf,MAAM,EAAE,EAAE,cAAc,EAAE,IAAI,CAAC,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE;aACzD;SACF,CAAA;IACH,CAAC;IAED,6EAA6E;IAC7E,2EAA2E;IAC3E,uCAAuC;IACvC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAA;AACzD,CAAC"}
|