@bubblebrain-ai/bubble 0.0.1
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 +70 -0
- package/dist/agent/evidence-tracker.d.ts +15 -0
- package/dist/agent/evidence-tracker.js +93 -0
- package/dist/agent/execution-governor.d.ts +30 -0
- package/dist/agent/execution-governor.js +169 -0
- package/dist/agent/subtask-policy.d.ts +14 -0
- package/dist/agent/subtask-policy.js +60 -0
- package/dist/agent/task-classifier.d.ts +3 -0
- package/dist/agent/task-classifier.js +36 -0
- package/dist/agent/tool-arbiter.d.ts +7 -0
- package/dist/agent/tool-arbiter.js +33 -0
- package/dist/agent/tool-intent.d.ts +20 -0
- package/dist/agent/tool-intent.js +176 -0
- package/dist/agent.d.ts +95 -0
- package/dist/agent.js +672 -0
- package/dist/approval/controller.d.ts +48 -0
- package/dist/approval/controller.js +78 -0
- package/dist/approval/danger.d.ts +13 -0
- package/dist/approval/danger.js +55 -0
- package/dist/approval/diff-hunks.d.ts +12 -0
- package/dist/approval/diff-hunks.js +32 -0
- package/dist/approval/session-cache.d.ts +35 -0
- package/dist/approval/session-cache.js +68 -0
- package/dist/approval/tool-helper.d.ts +14 -0
- package/dist/approval/tool-helper.js +32 -0
- package/dist/approval/types.d.ts +56 -0
- package/dist/approval/types.js +8 -0
- package/dist/bubble-home.d.ts +8 -0
- package/dist/bubble-home.js +19 -0
- package/dist/cli.d.ts +19 -0
- package/dist/cli.js +82 -0
- package/dist/config.d.ts +41 -0
- package/dist/config.js +144 -0
- package/dist/context/budget.d.ts +21 -0
- package/dist/context/budget.js +72 -0
- package/dist/context/compact-llm.d.ts +16 -0
- package/dist/context/compact-llm.js +132 -0
- package/dist/context/compact.d.ts +15 -0
- package/dist/context/compact.js +251 -0
- package/dist/context/overflow.d.ts +9 -0
- package/dist/context/overflow.js +46 -0
- package/dist/context/projector.d.ts +26 -0
- package/dist/context/projector.js +150 -0
- package/dist/context/prune.d.ts +9 -0
- package/dist/context/prune.js +111 -0
- package/dist/lsp/config.d.ts +18 -0
- package/dist/lsp/config.js +58 -0
- package/dist/lsp/diagnostics.d.ts +24 -0
- package/dist/lsp/diagnostics.js +103 -0
- package/dist/lsp/index.d.ts +3 -0
- package/dist/lsp/index.js +3 -0
- package/dist/lsp/service.d.ts +85 -0
- package/dist/lsp/service.js +695 -0
- package/dist/main.d.ts +5 -0
- package/dist/main.js +352 -0
- package/dist/mcp/client.d.ts +68 -0
- package/dist/mcp/client.js +163 -0
- package/dist/mcp/config.d.ts +26 -0
- package/dist/mcp/config.js +127 -0
- package/dist/mcp/manager.d.ts +55 -0
- package/dist/mcp/manager.js +296 -0
- package/dist/mcp/name.d.ts +26 -0
- package/dist/mcp/name.js +40 -0
- package/dist/mcp/transports.d.ts +53 -0
- package/dist/mcp/transports.js +248 -0
- package/dist/mcp/types.d.ts +111 -0
- package/dist/mcp/types.js +14 -0
- package/dist/memory/db.d.ts +62 -0
- package/dist/memory/db.js +313 -0
- package/dist/memory/index.d.ts +9 -0
- package/dist/memory/index.js +9 -0
- package/dist/memory/paths.d.ts +18 -0
- package/dist/memory/paths.js +38 -0
- package/dist/memory/phase1.d.ts +23 -0
- package/dist/memory/phase1.js +172 -0
- package/dist/memory/phase2.d.ts +19 -0
- package/dist/memory/phase2.js +100 -0
- package/dist/memory/prompts.d.ts +19 -0
- package/dist/memory/prompts.js +99 -0
- package/dist/memory/reset.d.ts +1 -0
- package/dist/memory/reset.js +13 -0
- package/dist/memory/start.d.ts +24 -0
- package/dist/memory/start.js +50 -0
- package/dist/memory/storage.d.ts +10 -0
- package/dist/memory/storage.js +82 -0
- package/dist/memory/store.d.ts +43 -0
- package/dist/memory/store.js +193 -0
- package/dist/memory/usage.d.ts +1 -0
- package/dist/memory/usage.js +38 -0
- package/dist/model-catalog.d.ts +20 -0
- package/dist/model-catalog.js +99 -0
- package/dist/model-config.d.ts +32 -0
- package/dist/model-config.js +59 -0
- package/dist/model-pricing.d.ts +23 -0
- package/dist/model-pricing.js +46 -0
- package/dist/oauth/index.d.ts +3 -0
- package/dist/oauth/index.js +2 -0
- package/dist/oauth/openai-codex.d.ts +9 -0
- package/dist/oauth/openai-codex.js +173 -0
- package/dist/oauth/storage.d.ts +18 -0
- package/dist/oauth/storage.js +60 -0
- package/dist/oauth/types.d.ts +15 -0
- package/dist/oauth/types.js +1 -0
- package/dist/orchestrator/default-hooks.d.ts +2 -0
- package/dist/orchestrator/default-hooks.js +96 -0
- package/dist/orchestrator/hooks.d.ts +78 -0
- package/dist/orchestrator/hooks.js +52 -0
- package/dist/orchestrator/workflow.d.ts +10 -0
- package/dist/orchestrator/workflow.js +22 -0
- package/dist/permission/mode.d.ts +23 -0
- package/dist/permission/mode.js +20 -0
- package/dist/permissions/rule.d.ts +39 -0
- package/dist/permissions/rule.js +234 -0
- package/dist/permissions/settings.d.ts +71 -0
- package/dist/permissions/settings.js +202 -0
- package/dist/permissions/types.d.ts +61 -0
- package/dist/permissions/types.js +14 -0
- package/dist/prompt/compose.d.ts +12 -0
- package/dist/prompt/compose.js +67 -0
- package/dist/prompt/environment.d.ts +12 -0
- package/dist/prompt/environment.js +38 -0
- package/dist/prompt/provider-prompts/anthropic.d.ts +1 -0
- package/dist/prompt/provider-prompts/anthropic.js +5 -0
- package/dist/prompt/provider-prompts/codex.d.ts +1 -0
- package/dist/prompt/provider-prompts/codex.js +5 -0
- package/dist/prompt/provider-prompts/default.d.ts +1 -0
- package/dist/prompt/provider-prompts/default.js +6 -0
- package/dist/prompt/provider-prompts/gemini.d.ts +1 -0
- package/dist/prompt/provider-prompts/gemini.js +5 -0
- package/dist/prompt/provider-prompts/gpt.d.ts +1 -0
- package/dist/prompt/provider-prompts/gpt.js +5 -0
- package/dist/prompt/reminders.d.ts +30 -0
- package/dist/prompt/reminders.js +164 -0
- package/dist/prompt/runtime.d.ts +12 -0
- package/dist/prompt/runtime.js +31 -0
- package/dist/prompt/skills.d.ts +2 -0
- package/dist/prompt/skills.js +4 -0
- package/dist/provider-openai-codex.d.ts +14 -0
- package/dist/provider-openai-codex.js +409 -0
- package/dist/provider-registry.d.ts +56 -0
- package/dist/provider-registry.js +244 -0
- package/dist/provider-transform.d.ts +10 -0
- package/dist/provider-transform.js +69 -0
- package/dist/provider.d.ts +31 -0
- package/dist/provider.js +269 -0
- package/dist/question/controller.d.ts +22 -0
- package/dist/question/controller.js +97 -0
- package/dist/question/index.d.ts +2 -0
- package/dist/question/index.js +2 -0
- package/dist/question/types.d.ts +42 -0
- package/dist/question/types.js +6 -0
- package/dist/session-log.d.ts +16 -0
- package/dist/session-log.js +267 -0
- package/dist/session-types.d.ts +55 -0
- package/dist/session-types.js +1 -0
- package/dist/session.d.ts +32 -0
- package/dist/session.js +135 -0
- package/dist/skills/discovery.d.ts +12 -0
- package/dist/skills/discovery.js +148 -0
- package/dist/skills/format.d.ts +2 -0
- package/dist/skills/format.js +47 -0
- package/dist/skills/frontmatter.d.ts +5 -0
- package/dist/skills/frontmatter.js +60 -0
- package/dist/skills/invocation.d.ts +8 -0
- package/dist/skills/invocation.js +51 -0
- package/dist/skills/registry.d.ts +17 -0
- package/dist/skills/registry.js +42 -0
- package/dist/skills/types.d.ts +32 -0
- package/dist/skills/types.js +1 -0
- package/dist/slash-commands/commands.d.ts +7 -0
- package/dist/slash-commands/commands.js +779 -0
- package/dist/slash-commands/index.d.ts +4 -0
- package/dist/slash-commands/index.js +8 -0
- package/dist/slash-commands/registry.d.ts +31 -0
- package/dist/slash-commands/registry.js +70 -0
- package/dist/slash-commands/types.d.ts +44 -0
- package/dist/slash-commands/types.js +1 -0
- package/dist/slash-commands/unified.d.ts +38 -0
- package/dist/slash-commands/unified.js +38 -0
- package/dist/system-prompt.d.ts +34 -0
- package/dist/system-prompt.js +7 -0
- package/dist/tools/bash.d.ts +6 -0
- package/dist/tools/bash.js +135 -0
- package/dist/tools/edit.d.ts +16 -0
- package/dist/tools/edit.js +95 -0
- package/dist/tools/exa-mcp.d.ts +3 -0
- package/dist/tools/exa-mcp.js +74 -0
- package/dist/tools/exit-plan-mode.d.ts +17 -0
- package/dist/tools/exit-plan-mode.js +68 -0
- package/dist/tools/glob.d.ts +5 -0
- package/dist/tools/glob.js +129 -0
- package/dist/tools/grep.d.ts +5 -0
- package/dist/tools/grep.js +111 -0
- package/dist/tools/index.d.ts +36 -0
- package/dist/tools/index.js +59 -0
- package/dist/tools/lsp.d.ts +4 -0
- package/dist/tools/lsp.js +92 -0
- package/dist/tools/memory.d.ts +3 -0
- package/dist/tools/memory.js +90 -0
- package/dist/tools/question.d.ts +3 -0
- package/dist/tools/question.js +174 -0
- package/dist/tools/read.d.ts +7 -0
- package/dist/tools/read.js +83 -0
- package/dist/tools/sensitive-paths.d.ts +3 -0
- package/dist/tools/sensitive-paths.js +24 -0
- package/dist/tools/skill.d.ts +5 -0
- package/dist/tools/skill.js +51 -0
- package/dist/tools/task.d.ts +2 -0
- package/dist/tools/task.js +57 -0
- package/dist/tools/todo.d.ts +12 -0
- package/dist/tools/todo.js +151 -0
- package/dist/tools/tool-search.d.ts +23 -0
- package/dist/tools/tool-search.js +124 -0
- package/dist/tools/web-fetch.d.ts +6 -0
- package/dist/tools/web-fetch.js +75 -0
- package/dist/tools/web-search.d.ts +5 -0
- package/dist/tools/web-search.js +49 -0
- package/dist/tools/write.d.ts +11 -0
- package/dist/tools/write.js +77 -0
- package/dist/tui/display-history.d.ts +35 -0
- package/dist/tui/display-history.js +243 -0
- package/dist/tui/file-mentions.d.ts +29 -0
- package/dist/tui/file-mentions.js +174 -0
- package/dist/tui/image-paste.d.ts +54 -0
- package/dist/tui/image-paste.js +288 -0
- package/dist/tui/markdown-theme-rules.d.ts +23 -0
- package/dist/tui/markdown-theme-rules.js +164 -0
- package/dist/tui/markdown-theme.d.ts +5 -0
- package/dist/tui/markdown-theme.js +27 -0
- package/dist/tui/opencode-spinner.d.ts +21 -0
- package/dist/tui/opencode-spinner.js +216 -0
- package/dist/tui/prompt-keybindings.d.ts +41 -0
- package/dist/tui/prompt-keybindings.js +28 -0
- package/dist/tui/recent-activity.d.ts +8 -0
- package/dist/tui/recent-activity.js +71 -0
- package/dist/tui/run.d.ts +39 -0
- package/dist/tui/run.js +5696 -0
- package/dist/tui/sidebar-mcp.d.ts +31 -0
- package/dist/tui/sidebar-mcp.js +62 -0
- package/dist/tui/sidebar-state.d.ts +12 -0
- package/dist/tui/sidebar-state.js +69 -0
- package/dist/types.d.ts +219 -0
- package/dist/types.js +4 -0
- package/dist/variant/thinking-level.d.ts +5 -0
- package/dist/variant/thinking-level.js +25 -0
- package/dist/variant/variant-resolver.d.ts +4 -0
- package/dist/variant/variant-resolver.js +12 -0
- package/package.json +47 -0
package/dist/config.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User-level configuration manager.
|
|
3
|
+
*
|
|
4
|
+
* Uses a single JSON file in Bubble home, normally ~/.bubble/config.json.
|
|
5
|
+
*/
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { dirname, join } from "node:path";
|
|
8
|
+
import { getBubbleHome } from "./bubble-home.js";
|
|
9
|
+
const HIDDEN_PROVIDER_IDS = new Set(["openrouter", "openai-codex"]);
|
|
10
|
+
function getConfigPath() {
|
|
11
|
+
return join(getBubbleHome(), "config.json");
|
|
12
|
+
}
|
|
13
|
+
function isHiddenProviderId(providerId) {
|
|
14
|
+
return !!providerId && HIDDEN_PROVIDER_IDS.has(providerId);
|
|
15
|
+
}
|
|
16
|
+
function modelProviderId(model) {
|
|
17
|
+
if (!model.includes(":"))
|
|
18
|
+
return undefined;
|
|
19
|
+
return model.split(":", 1)[0];
|
|
20
|
+
}
|
|
21
|
+
function sanitizeRecentModels(models) {
|
|
22
|
+
if (!models)
|
|
23
|
+
return undefined;
|
|
24
|
+
return models.filter((model) => !isHiddenProviderId(modelProviderId(model)));
|
|
25
|
+
}
|
|
26
|
+
function sanitizeProviders(providers) {
|
|
27
|
+
if (!providers)
|
|
28
|
+
return undefined;
|
|
29
|
+
return providers.filter((provider) => !isHiddenProviderId(provider.id));
|
|
30
|
+
}
|
|
31
|
+
function sanitizeDefaultModel(model) {
|
|
32
|
+
if (!model)
|
|
33
|
+
return undefined;
|
|
34
|
+
return isHiddenProviderId(modelProviderId(model)) ? undefined : model;
|
|
35
|
+
}
|
|
36
|
+
function sanitizeDefaultProvider(providerId) {
|
|
37
|
+
return isHiddenProviderId(providerId) ? undefined : providerId;
|
|
38
|
+
}
|
|
39
|
+
export class UserConfig {
|
|
40
|
+
data = {};
|
|
41
|
+
constructor() {
|
|
42
|
+
this.load();
|
|
43
|
+
}
|
|
44
|
+
load() {
|
|
45
|
+
const configPath = getConfigPath();
|
|
46
|
+
if (!existsSync(configPath))
|
|
47
|
+
return;
|
|
48
|
+
try {
|
|
49
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
50
|
+
const parsed = JSON.parse(raw);
|
|
51
|
+
this.data = {
|
|
52
|
+
...parsed,
|
|
53
|
+
defaultModel: sanitizeDefaultModel(parsed.defaultModel),
|
|
54
|
+
recentModels: sanitizeRecentModels(parsed.recentModels),
|
|
55
|
+
providers: sanitizeProviders(parsed.providers),
|
|
56
|
+
defaultProvider: sanitizeDefaultProvider(parsed.defaultProvider),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
this.data = {};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
save() {
|
|
64
|
+
const configPath = getConfigPath();
|
|
65
|
+
const dir = dirname(configPath);
|
|
66
|
+
if (!existsSync(dir)) {
|
|
67
|
+
mkdirSync(dir, { recursive: true });
|
|
68
|
+
}
|
|
69
|
+
writeFileSync(configPath, JSON.stringify(this.data, null, 2) + "\n");
|
|
70
|
+
}
|
|
71
|
+
getDefaultModel() {
|
|
72
|
+
return sanitizeDefaultModel(this.data.defaultModel)
|
|
73
|
+
?? sanitizeRecentModels(this.data.recentModels)?.[0];
|
|
74
|
+
}
|
|
75
|
+
setDefaultModel(model) {
|
|
76
|
+
this.data.defaultModel = sanitizeDefaultModel(model);
|
|
77
|
+
this.save();
|
|
78
|
+
}
|
|
79
|
+
getDefaultThinkingLevel() {
|
|
80
|
+
return this.data.defaultThinkingLevel;
|
|
81
|
+
}
|
|
82
|
+
setDefaultThinkingLevel(level) {
|
|
83
|
+
this.data.defaultThinkingLevel = level;
|
|
84
|
+
this.save();
|
|
85
|
+
}
|
|
86
|
+
getRecentModels() {
|
|
87
|
+
return sanitizeRecentModels(this.data.recentModels)?.slice() ?? [];
|
|
88
|
+
}
|
|
89
|
+
pushRecentModel(model) {
|
|
90
|
+
if (isHiddenProviderId(modelProviderId(model))) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const recent = this.data.recentModels ?? [];
|
|
94
|
+
const uniq = [model, ...recent.filter((m) => m !== model)];
|
|
95
|
+
const sanitized = sanitizeRecentModels(uniq.slice(0, 10));
|
|
96
|
+
this.data.recentModels = sanitized;
|
|
97
|
+
this.data.defaultModel = sanitized?.[0];
|
|
98
|
+
this.save();
|
|
99
|
+
}
|
|
100
|
+
getApiKey() {
|
|
101
|
+
return this.data.apiKey;
|
|
102
|
+
}
|
|
103
|
+
setApiKey(key) {
|
|
104
|
+
this.data.apiKey = key;
|
|
105
|
+
this.save();
|
|
106
|
+
}
|
|
107
|
+
getProviders() {
|
|
108
|
+
return sanitizeProviders(this.data.providers)?.slice() ?? [];
|
|
109
|
+
}
|
|
110
|
+
setProviders(providers) {
|
|
111
|
+
this.data.providers = sanitizeProviders(providers);
|
|
112
|
+
this.save();
|
|
113
|
+
}
|
|
114
|
+
getDefaultProvider() {
|
|
115
|
+
return sanitizeDefaultProvider(this.data.defaultProvider);
|
|
116
|
+
}
|
|
117
|
+
setDefaultProvider(id) {
|
|
118
|
+
this.data.defaultProvider = sanitizeDefaultProvider(id);
|
|
119
|
+
this.save();
|
|
120
|
+
}
|
|
121
|
+
getSkillPaths() {
|
|
122
|
+
return Array.isArray(this.data.skillPaths) ? this.data.skillPaths.slice() : [];
|
|
123
|
+
}
|
|
124
|
+
setSkillPaths(paths) {
|
|
125
|
+
this.data.skillPaths = paths.slice();
|
|
126
|
+
this.save();
|
|
127
|
+
}
|
|
128
|
+
getTheme() {
|
|
129
|
+
const theme = this.data.theme;
|
|
130
|
+
if (!theme || typeof theme !== "object" || Array.isArray(theme))
|
|
131
|
+
return {};
|
|
132
|
+
return Object.fromEntries(Object.entries(theme).filter(([, value]) => typeof value === "string"));
|
|
133
|
+
}
|
|
134
|
+
setTheme(theme) {
|
|
135
|
+
this.data.theme = { ...theme };
|
|
136
|
+
this.save();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/** Mask an API key for safe display. */
|
|
140
|
+
export function maskKey(key) {
|
|
141
|
+
if (key.length <= 12)
|
|
142
|
+
return "****";
|
|
143
|
+
return key.slice(0, 6) + "..." + key.slice(-4);
|
|
144
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Message } from "../types.js";
|
|
2
|
+
export declare const OUTPUT_RESERVE_TOKENS = 20000;
|
|
3
|
+
export declare const AUTOCOMPACT_BUFFER_TOKENS = 13000;
|
|
4
|
+
export declare const PRUNE_BUFFER_TOKENS = 50000;
|
|
5
|
+
export declare const MIN_WINDOW_FOR_RESERVE = 40000;
|
|
6
|
+
export interface ContextBudget {
|
|
7
|
+
estimatedTokens: number;
|
|
8
|
+
contextWindow?: number;
|
|
9
|
+
percent?: number;
|
|
10
|
+
shouldPrune: boolean;
|
|
11
|
+
shouldCompact: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface ContextBudgetOptions {
|
|
14
|
+
/** Authoritative input-token count from the most recent response usage. */
|
|
15
|
+
usageAnchorTokens?: number;
|
|
16
|
+
/** Messages appended after the anchor (their tokens are estimated and added). */
|
|
17
|
+
tailMessages?: Message[];
|
|
18
|
+
}
|
|
19
|
+
export declare function estimateMessageTokens(message: Message): number;
|
|
20
|
+
export declare function estimateContextTokens(messages: Message[]): number;
|
|
21
|
+
export declare function getContextBudget(providerId: string, modelId: string, messages: Message[], options?: ContextBudgetOptions): ContextBudget;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { getModelContextWindow } from "../model-catalog.js";
|
|
2
|
+
export const OUTPUT_RESERVE_TOKENS = 20_000;
|
|
3
|
+
export const AUTOCOMPACT_BUFFER_TOKENS = 13_000;
|
|
4
|
+
export const PRUNE_BUFFER_TOKENS = 50_000;
|
|
5
|
+
export const MIN_WINDOW_FOR_RESERVE = 40_000;
|
|
6
|
+
export function estimateMessageTokens(message) {
|
|
7
|
+
switch (message.role) {
|
|
8
|
+
case "system":
|
|
9
|
+
case "tool":
|
|
10
|
+
return estimateTextTokens(message.content);
|
|
11
|
+
case "assistant":
|
|
12
|
+
return estimateTextTokens(message.content)
|
|
13
|
+
+ estimateTextTokens(message.reasoning ?? "")
|
|
14
|
+
+ (message.toolCalls?.reduce((sum, toolCall) => sum + estimateTextTokens(toolCall.arguments) + 12, 0) ?? 0)
|
|
15
|
+
+ 8;
|
|
16
|
+
case "user":
|
|
17
|
+
if (typeof message.content === "string") {
|
|
18
|
+
return estimateTextTokens(message.content) + 8;
|
|
19
|
+
}
|
|
20
|
+
return message.content.reduce((sum, part) => {
|
|
21
|
+
if (part.type === "text") {
|
|
22
|
+
return sum + estimateTextTokens(part.text);
|
|
23
|
+
}
|
|
24
|
+
return sum + 256;
|
|
25
|
+
}, 8);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function estimateContextTokens(messages) {
|
|
29
|
+
return messages.reduce((sum, message) => sum + estimateMessageTokens(message), 0);
|
|
30
|
+
}
|
|
31
|
+
export function getContextBudget(providerId, modelId, messages, options = {}) {
|
|
32
|
+
const estimatedTokens = computeEstimatedTokens(messages, options);
|
|
33
|
+
const contextWindow = getModelContextWindow(providerId, modelId);
|
|
34
|
+
const percent = contextWindow ? Math.min(100, (estimatedTokens / contextWindow) * 100) : undefined;
|
|
35
|
+
return {
|
|
36
|
+
estimatedTokens,
|
|
37
|
+
contextWindow,
|
|
38
|
+
percent,
|
|
39
|
+
shouldPrune: shouldTriggerPrune(estimatedTokens, contextWindow),
|
|
40
|
+
shouldCompact: shouldTriggerCompact(estimatedTokens, contextWindow),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function computeEstimatedTokens(messages, options) {
|
|
44
|
+
if (options.usageAnchorTokens !== undefined && options.tailMessages) {
|
|
45
|
+
return options.usageAnchorTokens + estimateContextTokens(options.tailMessages);
|
|
46
|
+
}
|
|
47
|
+
return estimateContextTokens(messages);
|
|
48
|
+
}
|
|
49
|
+
function shouldTriggerPrune(estimatedTokens, contextWindow) {
|
|
50
|
+
if (!contextWindow) {
|
|
51
|
+
return estimatedTokens >= 16_000;
|
|
52
|
+
}
|
|
53
|
+
const threshold = contextWindow >= MIN_WINDOW_FOR_RESERVE
|
|
54
|
+
? contextWindow - OUTPUT_RESERVE_TOKENS - PRUNE_BUFFER_TOKENS
|
|
55
|
+
: contextWindow * 0.55;
|
|
56
|
+
return estimatedTokens >= threshold;
|
|
57
|
+
}
|
|
58
|
+
function shouldTriggerCompact(estimatedTokens, contextWindow) {
|
|
59
|
+
if (!contextWindow) {
|
|
60
|
+
return estimatedTokens >= 32_000;
|
|
61
|
+
}
|
|
62
|
+
const threshold = contextWindow >= MIN_WINDOW_FOR_RESERVE
|
|
63
|
+
? contextWindow - OUTPUT_RESERVE_TOKENS - AUTOCOMPACT_BUFFER_TOKENS
|
|
64
|
+
: contextWindow * 0.75;
|
|
65
|
+
return estimatedTokens >= threshold;
|
|
66
|
+
}
|
|
67
|
+
function estimateTextTokens(text) {
|
|
68
|
+
if (!text) {
|
|
69
|
+
return 0;
|
|
70
|
+
}
|
|
71
|
+
return Math.ceil(text.length / 4);
|
|
72
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM-backed structured conversation compaction.
|
|
3
|
+
*
|
|
4
|
+
* Generates a 9-section summary of the older turns via the provider's
|
|
5
|
+
* completion API, replacing the dropped history with a single system
|
|
6
|
+
* message. Falls back to the heuristic `compactMessages` if the LLM call
|
|
7
|
+
* fails.
|
|
8
|
+
*/
|
|
9
|
+
import type { CompactOptions, CompactResult } from "./compact.js";
|
|
10
|
+
import type { Message, Provider } from "../types.js";
|
|
11
|
+
export interface LLMCompactOptions extends CompactOptions {
|
|
12
|
+
provider: Provider;
|
|
13
|
+
model: string;
|
|
14
|
+
thinkingLevel?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
|
|
15
|
+
}
|
|
16
|
+
export declare function compactMessagesWithLLM(messages: Message[], options: LLMCompactOptions): Promise<CompactResult>;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM-backed structured conversation compaction.
|
|
3
|
+
*
|
|
4
|
+
* Generates a 9-section summary of the older turns via the provider's
|
|
5
|
+
* completion API, replacing the dropped history with a single system
|
|
6
|
+
* message. Falls back to the heuristic `compactMessages` if the LLM call
|
|
7
|
+
* fails.
|
|
8
|
+
*/
|
|
9
|
+
import { compactMessages as compactMessagesHeuristic } from "./compact.js";
|
|
10
|
+
const COMPACT_SYSTEM_PROMPT = `You are a conversation summarizer. Your job is to produce a structured
|
|
11
|
+
summary of an earlier portion of a software-engineering assistant's
|
|
12
|
+
conversation so that the assistant can continue working without the full
|
|
13
|
+
history. Preserve fidelity over brevity where the user's intent, file
|
|
14
|
+
paths, or decisions are concerned. Output ONLY the summary, no preamble.`;
|
|
15
|
+
const COMPACT_INSTRUCTIONS = `Summarize the conversation above using exactly these 9 sections, each
|
|
16
|
+
preceded by the literal heading on its own line. If a section has no
|
|
17
|
+
content, write "None".
|
|
18
|
+
|
|
19
|
+
1. Primary Request and Intent
|
|
20
|
+
- What the user ultimately wants, in their own framing.
|
|
21
|
+
|
|
22
|
+
2. Key Technical Concepts
|
|
23
|
+
- Libraries, frameworks, architectural patterns referenced.
|
|
24
|
+
|
|
25
|
+
3. Files and Code Sections
|
|
26
|
+
- Files read, written, or discussed. Include full paths and a one-line note.
|
|
27
|
+
|
|
28
|
+
4. Errors and Fixes
|
|
29
|
+
- Bugs encountered and how they were resolved.
|
|
30
|
+
|
|
31
|
+
5. Problem Solving
|
|
32
|
+
- Non-trivial debugging or design decisions.
|
|
33
|
+
|
|
34
|
+
6. All User Messages
|
|
35
|
+
- Every user message, verbatim, in order. Do not summarize here.
|
|
36
|
+
|
|
37
|
+
7. Pending Tasks
|
|
38
|
+
- Work that was planned but not yet completed.
|
|
39
|
+
|
|
40
|
+
8. Current Work
|
|
41
|
+
- What was being actively worked on when the summary was taken.
|
|
42
|
+
|
|
43
|
+
9. Optional Next Step
|
|
44
|
+
- The single most natural next action, if obvious.`;
|
|
45
|
+
export async function compactMessagesWithLLM(messages, options) {
|
|
46
|
+
const keepRecentTurns = options.keepRecentTurns ?? 2;
|
|
47
|
+
const systemMessages = messages.filter((m) => m.role === "system");
|
|
48
|
+
const nonSystemMessages = messages.filter((m) => m.role !== "system");
|
|
49
|
+
const turnStartIndexes = nonSystemMessages
|
|
50
|
+
.map((m, i) => (m.role === "user" ? i : -1))
|
|
51
|
+
.filter((i) => i >= 0);
|
|
52
|
+
if (turnStartIndexes.length <= keepRecentTurns) {
|
|
53
|
+
return { compacted: false };
|
|
54
|
+
}
|
|
55
|
+
const keepStartIndex = turnStartIndexes[Math.max(0, turnStartIndexes.length - keepRecentTurns)];
|
|
56
|
+
if (keepStartIndex <= 0) {
|
|
57
|
+
return { compacted: false };
|
|
58
|
+
}
|
|
59
|
+
const oldMessages = nonSystemMessages.slice(0, keepStartIndex);
|
|
60
|
+
const keptMessages = nonSystemMessages.slice(keepStartIndex);
|
|
61
|
+
let summary;
|
|
62
|
+
try {
|
|
63
|
+
summary = await generateSummary(oldMessages, options);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return compactMessagesHeuristic(messages, { keepRecentTurns, maxSummaryItems: options.maxSummaryItems });
|
|
67
|
+
}
|
|
68
|
+
if (!summary.trim()) {
|
|
69
|
+
return compactMessagesHeuristic(messages, { keepRecentTurns, maxSummaryItems: options.maxSummaryItems });
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
compacted: true,
|
|
73
|
+
summary,
|
|
74
|
+
messages: [
|
|
75
|
+
...systemMessages,
|
|
76
|
+
{ role: "system", content: `Previous conversation summary:\n${summary}` },
|
|
77
|
+
...keptMessages,
|
|
78
|
+
],
|
|
79
|
+
droppedEntries: oldMessages.length,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
async function generateSummary(oldMessages, options) {
|
|
83
|
+
const transcript = serializeTranscript(oldMessages);
|
|
84
|
+
const messages = [
|
|
85
|
+
{ role: "system", content: COMPACT_SYSTEM_PROMPT },
|
|
86
|
+
{
|
|
87
|
+
role: "user",
|
|
88
|
+
content: `Conversation to summarize:\n\n${transcript}\n\n---\n\n${COMPACT_INSTRUCTIONS}`,
|
|
89
|
+
},
|
|
90
|
+
];
|
|
91
|
+
return options.provider.complete(messages, {
|
|
92
|
+
model: options.model,
|
|
93
|
+
temperature: 0.2,
|
|
94
|
+
thinkingLevel: options.thinkingLevel ?? "off",
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
function serializeTranscript(messages) {
|
|
98
|
+
const lines = [];
|
|
99
|
+
for (const message of messages) {
|
|
100
|
+
switch (message.role) {
|
|
101
|
+
case "user":
|
|
102
|
+
lines.push(`[user] ${contentToText(message.content)}`);
|
|
103
|
+
break;
|
|
104
|
+
case "assistant":
|
|
105
|
+
if (message.content)
|
|
106
|
+
lines.push(`[assistant] ${message.content}`);
|
|
107
|
+
for (const toolCall of message.toolCalls ?? []) {
|
|
108
|
+
lines.push(`[assistant tool_call] ${toolCall.name}(${toolCall.arguments})`);
|
|
109
|
+
}
|
|
110
|
+
break;
|
|
111
|
+
case "tool":
|
|
112
|
+
lines.push(`[tool] ${truncate(message.content, 800)}`);
|
|
113
|
+
break;
|
|
114
|
+
case "system":
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return lines.join("\n");
|
|
119
|
+
}
|
|
120
|
+
function contentToText(content) {
|
|
121
|
+
if (typeof content === "string")
|
|
122
|
+
return content;
|
|
123
|
+
return content
|
|
124
|
+
.filter((p) => p.type === "text")
|
|
125
|
+
.map((p) => p.text)
|
|
126
|
+
.join(" ");
|
|
127
|
+
}
|
|
128
|
+
function truncate(text, max) {
|
|
129
|
+
if (text.length <= max)
|
|
130
|
+
return text;
|
|
131
|
+
return `${text.slice(0, max - 1)}…`;
|
|
132
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Message } from "../types.js";
|
|
2
|
+
import type { SessionLogEntry } from "../session-types.js";
|
|
3
|
+
export interface CompactOptions {
|
|
4
|
+
keepRecentTurns?: number;
|
|
5
|
+
maxSummaryItems?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface CompactResult {
|
|
8
|
+
compacted: boolean;
|
|
9
|
+
summary?: string;
|
|
10
|
+
entries?: SessionLogEntry[];
|
|
11
|
+
messages?: Message[];
|
|
12
|
+
droppedEntries?: number;
|
|
13
|
+
}
|
|
14
|
+
export declare function compactSessionEntries(entries: SessionLogEntry[], options?: CompactOptions): CompactResult;
|
|
15
|
+
export declare function compactMessages(messages: Message[], options?: CompactOptions): CompactResult;
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
export function compactSessionEntries(entries, options = {}) {
|
|
2
|
+
const keepRecentTurns = options.keepRecentTurns ?? 2;
|
|
3
|
+
const maxSummaryItems = options.maxSummaryItems ?? 4;
|
|
4
|
+
const metadataEntries = entries.filter((entry) => entry.type === "metadata");
|
|
5
|
+
const nonMetadataEntries = entries.filter((entry) => entry.type !== "metadata");
|
|
6
|
+
const latestSummaryIndex = findLatestSummaryIndex(nonMetadataEntries);
|
|
7
|
+
const baseIndex = latestSummaryIndex >= 0 ? latestSummaryIndex + 1 : 0;
|
|
8
|
+
const activeEntries = nonMetadataEntries.slice(baseIndex);
|
|
9
|
+
const turnStartIndexes = activeEntries
|
|
10
|
+
.map((entry, index) => (entry.type === "user_message" ? index : -1))
|
|
11
|
+
.filter((index) => index >= 0);
|
|
12
|
+
if (turnStartIndexes.length <= keepRecentTurns) {
|
|
13
|
+
return { compacted: false };
|
|
14
|
+
}
|
|
15
|
+
const keepStartIndex = turnStartIndexes[Math.max(0, turnStartIndexes.length - keepRecentTurns)];
|
|
16
|
+
if (keepStartIndex <= 0) {
|
|
17
|
+
return { compacted: false };
|
|
18
|
+
}
|
|
19
|
+
const oldEntries = activeEntries.slice(0, keepStartIndex);
|
|
20
|
+
const keptEntries = activeEntries.slice(keepStartIndex);
|
|
21
|
+
const summary = buildCompactionSummary(oldEntries, maxSummaryItems);
|
|
22
|
+
if (!summary) {
|
|
23
|
+
return { compacted: false };
|
|
24
|
+
}
|
|
25
|
+
const summaryEntry = {
|
|
26
|
+
id: nextSummaryId(entries),
|
|
27
|
+
type: "summary",
|
|
28
|
+
summary,
|
|
29
|
+
timestamp: Date.now(),
|
|
30
|
+
};
|
|
31
|
+
const nextEntries = [
|
|
32
|
+
...metadataEntries,
|
|
33
|
+
summaryEntry,
|
|
34
|
+
...keptEntries,
|
|
35
|
+
];
|
|
36
|
+
return {
|
|
37
|
+
compacted: true,
|
|
38
|
+
summary,
|
|
39
|
+
entries: nextEntries,
|
|
40
|
+
droppedEntries: oldEntries.length,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export function compactMessages(messages, options = {}) {
|
|
44
|
+
const keepRecentTurns = options.keepRecentTurns ?? 2;
|
|
45
|
+
const maxSummaryItems = options.maxSummaryItems ?? 4;
|
|
46
|
+
const systemMessages = messages.filter((message) => message.role === "system");
|
|
47
|
+
const nonSystemMessages = messages.filter((message) => message.role !== "system");
|
|
48
|
+
const turnStartIndexes = nonSystemMessages
|
|
49
|
+
.map((message, index) => (message.role === "user" ? index : -1))
|
|
50
|
+
.filter((index) => index >= 0);
|
|
51
|
+
if (turnStartIndexes.length <= keepRecentTurns) {
|
|
52
|
+
return { compacted: false };
|
|
53
|
+
}
|
|
54
|
+
const keepStartIndex = turnStartIndexes[Math.max(0, turnStartIndexes.length - keepRecentTurns)];
|
|
55
|
+
if (keepStartIndex <= 0) {
|
|
56
|
+
return { compacted: false };
|
|
57
|
+
}
|
|
58
|
+
const oldMessages = nonSystemMessages.slice(0, keepStartIndex);
|
|
59
|
+
const keptMessages = nonSystemMessages.slice(keepStartIndex);
|
|
60
|
+
const summary = buildMessageSummary(oldMessages, maxSummaryItems);
|
|
61
|
+
if (!summary) {
|
|
62
|
+
return { compacted: false };
|
|
63
|
+
}
|
|
64
|
+
const compactedMessages = [
|
|
65
|
+
...systemMessages.map((message) => cloneMessage(message)),
|
|
66
|
+
{
|
|
67
|
+
role: "system",
|
|
68
|
+
content: `Previous conversation summary:\n${summary}`,
|
|
69
|
+
},
|
|
70
|
+
...keptMessages.map((message) => cloneMessage(message)),
|
|
71
|
+
];
|
|
72
|
+
return {
|
|
73
|
+
compacted: true,
|
|
74
|
+
summary,
|
|
75
|
+
messages: compactedMessages,
|
|
76
|
+
droppedEntries: oldMessages.length,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function buildCompactionSummary(entries, maxSummaryItems) {
|
|
80
|
+
const messages = entriesToMessages(entries);
|
|
81
|
+
return buildMessageSummary(messages, maxSummaryItems);
|
|
82
|
+
}
|
|
83
|
+
function buildMessageSummary(messages, maxSummaryItems) {
|
|
84
|
+
if (messages.length === 0) {
|
|
85
|
+
return "";
|
|
86
|
+
}
|
|
87
|
+
const userMessages = messages.filter((message) => message.role === "user");
|
|
88
|
+
const assistantMessages = messages.filter((message) => message.role === "assistant");
|
|
89
|
+
const toolCalls = collectToolCalls(messages);
|
|
90
|
+
const relevantFiles = collectRelevantFiles(toolCalls);
|
|
91
|
+
const toolFindings = collectToolFindings(messages, maxSummaryItems);
|
|
92
|
+
const goal = userMessages[0] ? summarizeContent(userMessages[0].content) : "Unknown";
|
|
93
|
+
const progress = userMessages.slice(0, maxSummaryItems).map((message) => `- ${summarizeContent(message.content)}`);
|
|
94
|
+
const decisions = assistantMessages
|
|
95
|
+
.map((message) => message.content.trim())
|
|
96
|
+
.filter(Boolean)
|
|
97
|
+
.slice(0, maxSummaryItems)
|
|
98
|
+
.map((content) => `- ${summarizeText(content)}`);
|
|
99
|
+
const lines = [
|
|
100
|
+
"Goal:",
|
|
101
|
+
`- ${goal}`,
|
|
102
|
+
"",
|
|
103
|
+
"Progress:",
|
|
104
|
+
...(progress.length > 0 ? progress : ["- No user progress recorded"]),
|
|
105
|
+
"",
|
|
106
|
+
"Key Decisions:",
|
|
107
|
+
...(decisions.length > 0 ? decisions : ["- No assistant decisions recorded"]),
|
|
108
|
+
"",
|
|
109
|
+
"Next Steps:",
|
|
110
|
+
["- Continue from the most recent preserved turn"],
|
|
111
|
+
"",
|
|
112
|
+
"Relevant Files:",
|
|
113
|
+
...(relevantFiles.length > 0 ? relevantFiles.map((file) => `- ${file}`) : ["- None captured"]),
|
|
114
|
+
"",
|
|
115
|
+
"Tool Findings:",
|
|
116
|
+
...(toolFindings.length > 0 ? toolFindings.map((item) => `- ${item}`) : ["- None captured"]),
|
|
117
|
+
];
|
|
118
|
+
return lines.flat().join("\n");
|
|
119
|
+
}
|
|
120
|
+
function entriesToMessages(entries) {
|
|
121
|
+
const messages = [];
|
|
122
|
+
for (const entry of entries) {
|
|
123
|
+
switch (entry.type) {
|
|
124
|
+
case "user_message":
|
|
125
|
+
messages.push({ ...entry.message });
|
|
126
|
+
break;
|
|
127
|
+
case "assistant_message":
|
|
128
|
+
messages.push({
|
|
129
|
+
role: "assistant",
|
|
130
|
+
content: entry.message.content,
|
|
131
|
+
reasoning: entry.message.reasoning,
|
|
132
|
+
});
|
|
133
|
+
break;
|
|
134
|
+
case "tool_call": {
|
|
135
|
+
const last = messages[messages.length - 1];
|
|
136
|
+
if (last?.role === "assistant") {
|
|
137
|
+
last.toolCalls = [...(last.toolCalls ?? []), { ...entry.toolCall }];
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
messages.push({
|
|
141
|
+
role: "assistant",
|
|
142
|
+
content: "",
|
|
143
|
+
toolCalls: [{ ...entry.toolCall }],
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
case "tool_result":
|
|
149
|
+
messages.push({ ...entry.message });
|
|
150
|
+
break;
|
|
151
|
+
default:
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return messages;
|
|
156
|
+
}
|
|
157
|
+
function collectToolCalls(messages) {
|
|
158
|
+
return messages.flatMap((message) => message.role === "assistant" ? (message.toolCalls ?? []) : []);
|
|
159
|
+
}
|
|
160
|
+
function collectRelevantFiles(toolCalls) {
|
|
161
|
+
const files = new Set();
|
|
162
|
+
for (const toolCall of toolCalls) {
|
|
163
|
+
let parsed = {};
|
|
164
|
+
try {
|
|
165
|
+
parsed = JSON.parse(toolCall.arguments || "{}");
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
parsed = {};
|
|
169
|
+
}
|
|
170
|
+
for (const key of ["file", "path", "paths"]) {
|
|
171
|
+
const value = parsed[key];
|
|
172
|
+
if (typeof value === "string" && value) {
|
|
173
|
+
files.add(value);
|
|
174
|
+
}
|
|
175
|
+
if (Array.isArray(value)) {
|
|
176
|
+
for (const item of value) {
|
|
177
|
+
if (typeof item === "string" && item) {
|
|
178
|
+
files.add(item);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return [...files].slice(0, 12);
|
|
185
|
+
}
|
|
186
|
+
function collectToolFindings(messages, maxItems) {
|
|
187
|
+
const findings = [];
|
|
188
|
+
const toolNameByCallId = new Map();
|
|
189
|
+
for (const message of messages) {
|
|
190
|
+
if (message.role === "assistant" && message.toolCalls) {
|
|
191
|
+
for (const toolCall of message.toolCalls) {
|
|
192
|
+
toolNameByCallId.set(toolCall.id, toolCall.name);
|
|
193
|
+
}
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (message.role !== "tool") {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
const toolName = toolNameByCallId.get(message.toolCallId) ?? "tool";
|
|
200
|
+
findings.push(`${toolName}: ${summarizeText(message.content)}`);
|
|
201
|
+
if (findings.length >= maxItems) {
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return findings;
|
|
206
|
+
}
|
|
207
|
+
function summarizeContent(content) {
|
|
208
|
+
if (typeof content === "string") {
|
|
209
|
+
return summarizeText(content);
|
|
210
|
+
}
|
|
211
|
+
const textParts = content
|
|
212
|
+
.filter((part) => part.type === "text")
|
|
213
|
+
.map((part) => part.text);
|
|
214
|
+
return summarizeText(textParts.join(" "));
|
|
215
|
+
}
|
|
216
|
+
function summarizeText(value) {
|
|
217
|
+
const normalized = value.replace(/\s+/g, " ").trim();
|
|
218
|
+
if (normalized.length <= 140) {
|
|
219
|
+
return normalized || "(empty)";
|
|
220
|
+
}
|
|
221
|
+
return `${normalized.slice(0, 137)}...`;
|
|
222
|
+
}
|
|
223
|
+
function findLatestSummaryIndex(entries) {
|
|
224
|
+
for (let index = entries.length - 1; index >= 0; index--) {
|
|
225
|
+
if (entries[index].type === "summary") {
|
|
226
|
+
return index;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return -1;
|
|
230
|
+
}
|
|
231
|
+
function nextSummaryId(entries) {
|
|
232
|
+
return `${entries.length + 1}`;
|
|
233
|
+
}
|
|
234
|
+
function cloneMessage(message) {
|
|
235
|
+
if (message.role === "assistant") {
|
|
236
|
+
return {
|
|
237
|
+
...message,
|
|
238
|
+
toolCalls: message.toolCalls?.map((toolCall) => ({ ...toolCall })),
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
if (message.role === "user" && Array.isArray(message.content)) {
|
|
242
|
+
return {
|
|
243
|
+
...message,
|
|
244
|
+
content: message.content.map((part) => ({
|
|
245
|
+
...part,
|
|
246
|
+
...(part.type === "image_url" ? { image_url: { ...part.image_url } } : {}),
|
|
247
|
+
})),
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
return { ...message };
|
|
251
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context-overflow detection.
|
|
3
|
+
*
|
|
4
|
+
* When the upstream provider rejects a request because the prompt exceeds
|
|
5
|
+
* the effective context window, we catch the error here so the agent can
|
|
6
|
+
* compact history and retry automatically.
|
|
7
|
+
*/
|
|
8
|
+
export declare function isContextOverflowError(error: unknown): boolean;
|
|
9
|
+
export declare function isContextOverflowByUsage(inputTokens: number | undefined, contextWindow: number | undefined): boolean;
|