@bubblebrain-ai/bubble 0.0.17 → 0.0.18
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/tool-intent.js +0 -1
- package/dist/agent.d.ts +1 -0
- package/dist/agent.js +54 -21
- package/dist/context/prune.d.ts +1 -0
- package/dist/context/prune.js +32 -0
- package/dist/feishu/agent-host/run-driver.js +2 -2
- package/dist/feishu/card/run-state.js +1 -0
- package/dist/main.js +11 -9
- package/dist/model-pricing.js +2 -1
- package/dist/model-selection.d.ts +7 -0
- package/dist/model-selection.js +9 -0
- package/dist/network/chatgpt-transport.js +1 -0
- package/dist/orchestrator/default-hooks.js +1 -1
- package/dist/prompt/environment.js +1 -3
- package/dist/prompt/runtime.js +1 -1
- package/dist/provider-anthropic.d.ts +15 -3
- package/dist/provider-anthropic.js +55 -2
- package/dist/provider-openai-codex.js +3 -1
- package/dist/provider.js +1 -1
- package/dist/session-title.js +3 -6
- package/dist/slash-commands/commands.js +4 -0
- package/dist/stats/usage.d.ts +1 -0
- package/dist/stats/usage.js +28 -3
- package/dist/tools/edit.js +75 -1
- package/dist/tools/glob.js +77 -12
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.js +1 -3
- package/dist/tools/prompt-metadata.d.ts +3 -0
- package/dist/tools/prompt-metadata.js +17 -0
- package/dist/tools/write.js +14 -0
- package/dist/tui/paste-placeholder.d.ts +10 -0
- package/dist/tui/paste-placeholder.js +45 -0
- package/dist/tui/run.js +23 -0
- package/dist/tui-ink/app.js +2 -0
- package/dist/tui-ink/input-box.d.ts +1 -8
- package/dist/tui-ink/input-box.js +8 -38
- package/dist/tui-opentui/app.js +2 -0
- package/dist/tui-opentui/input-box.d.ts +1 -3
- package/dist/tui-opentui/input-box.js +17 -26
- package/dist/types.d.ts +9 -0
- package/package.json +7 -3
- package/dist/tools/apply-patch.d.ts +0 -9
- package/dist/tools/apply-patch.js +0 -330
- package/dist/tools/patch-apply.d.ts +0 -41
- package/dist/tools/patch-apply.js +0 -312
package/dist/agent.d.ts
CHANGED
|
@@ -87,6 +87,7 @@ export declare class Agent {
|
|
|
87
87
|
unlockDeferredTools(names: string[]): void;
|
|
88
88
|
/** All deferred tools in this session (for tool_search to inspect). */
|
|
89
89
|
listDeferredTools(): ToolRegistryEntry[];
|
|
90
|
+
getSystemPromptToolOptions(): Pick<import("./system-prompt.js").SystemPromptOptions, "tools" | "toolSnippets" | "guidelines">;
|
|
90
91
|
getContextUsageSnapshot(): ContextUsageSnapshot;
|
|
91
92
|
resetContextUsageAnchor(): void;
|
|
92
93
|
/** Whether a given tool is deferred and not yet unlocked. */
|
package/dist/agent.js
CHANGED
|
@@ -9,9 +9,9 @@ import { estimateContextTokens, getContextBudget } from "./context/budget.js";
|
|
|
9
9
|
import { buildContextUsageSnapshot } from "./context/usage.js";
|
|
10
10
|
import { isContextOverflowError } from "./context/overflow.js";
|
|
11
11
|
import { projectMessages } from "./context/projector.js";
|
|
12
|
-
import { aggressivePruneMessages } from "./context/prune.js";
|
|
12
|
+
import { aggressivePruneMessages, markStableCurrentToolResultsForCache } from "./context/prune.js";
|
|
13
13
|
import { truncateToolOutputForModel } from "./context/tool-output-truncate.js";
|
|
14
|
-
import { buildDeferredToolsReminder, buildToolFreezeReminder,
|
|
14
|
+
import { buildDeferredToolsReminder, buildToolFreezeReminder, reminderForMode } from "./prompt/reminders.js";
|
|
15
15
|
import { HookBus } from "./orchestrator/hooks.js";
|
|
16
16
|
import { createDefaultHooks } from "./orchestrator/default-hooks.js";
|
|
17
17
|
import { resolveModelRoute, resolveSubagentRoute } from "./agent/categories.js";
|
|
@@ -24,6 +24,7 @@ import { createStreamingInternalReminderSanitizer, sanitizeAssistantProviderMeta
|
|
|
24
24
|
import { buildSystemPrompt } from "./system-prompt.js";
|
|
25
25
|
import { isOnlyProviderProtocolArtifacts, stripProviderProtocolArtifacts } from "./provider-artifacts.js";
|
|
26
26
|
import { debugReasoningStream, summarizeDebugText } from "./reasoning-debug.js";
|
|
27
|
+
import { buildToolPromptOptions } from "./tools/prompt-metadata.js";
|
|
27
28
|
import { stopAutoServersForSession } from "./tools/server-manager.js";
|
|
28
29
|
import { summarizeAgentEventForTrace, summarizeTraceError, summarizeTraceMessage, summarizeTraceToolResult, summarizeTraceValue, traceEvent, } from "./debug-trace.js";
|
|
29
30
|
const MAX_CONSECUTIVE_OVERFLOW_RECOVERIES = 3;
|
|
@@ -31,7 +32,6 @@ const RESIDENT_HISTORY_KEEP_RECENT_TURNS = 3;
|
|
|
31
32
|
const RESIDENT_HISTORY_MESSAGE_LIMIT = 160;
|
|
32
33
|
const RESIDENT_HISTORY_CHAR_SOFT_LIMIT = 256 * 1024;
|
|
33
34
|
const RESIDENT_HISTORY_CHAR_HARD_LIMIT = 512 * 1024;
|
|
34
|
-
const RESIDENT_HISTORY_HEAP_SOFT_LIMIT = 512 * 1024 * 1024;
|
|
35
35
|
const RESIDENT_HISTORY_HEAP_HARD_LIMIT = 768 * 1024 * 1024;
|
|
36
36
|
const MAX_EMPTY_ASSISTANT_RECOVERIES = 1;
|
|
37
37
|
const EMPTY_ASSISTANT_RECOVERY_REMINDER = "The previous model response contained no user-visible assistant content and no tool calls. " +
|
|
@@ -131,6 +131,9 @@ export class Agent {
|
|
|
131
131
|
listDeferredTools() {
|
|
132
132
|
return [...this.tools.values()].filter((t) => t.deferred);
|
|
133
133
|
}
|
|
134
|
+
getSystemPromptToolOptions() {
|
|
135
|
+
return buildToolPromptOptions(this.getActiveToolEntries());
|
|
136
|
+
}
|
|
134
137
|
getContextUsageSnapshot() {
|
|
135
138
|
return buildContextUsageSnapshot({
|
|
136
139
|
providerId: this.providerId,
|
|
@@ -153,17 +156,20 @@ export class Agent {
|
|
|
153
156
|
}
|
|
154
157
|
getActiveToolEntries() {
|
|
155
158
|
return [...this.tools.values()]
|
|
156
|
-
.filter((tool) => !tool.deferred || this.unlockedDeferred.has(tool.name))
|
|
157
|
-
.filter((tool) => this._mode === "plan" || tool.name !== "exit_plan_mode");
|
|
159
|
+
.filter((tool) => !tool.deferred || this.unlockedDeferred.has(tool.name));
|
|
158
160
|
}
|
|
159
161
|
injectSystemReminder(content) {
|
|
160
162
|
this.appendMessage({ role: "meta", kind: "system-reminder", content });
|
|
161
163
|
}
|
|
162
164
|
injectModeReminder() {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
165
|
+
const reminder = reminderForMode(this._mode);
|
|
166
|
+
const last = this.messages.at(-1);
|
|
167
|
+
if (last?.role === "meta"
|
|
168
|
+
&& last.kind === "system-reminder"
|
|
169
|
+
&& last.content === reminder) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
this.injectSystemReminder(reminder);
|
|
167
173
|
}
|
|
168
174
|
get model() {
|
|
169
175
|
return this._model;
|
|
@@ -398,11 +404,9 @@ export class Agent {
|
|
|
398
404
|
};
|
|
399
405
|
await hookBus.runBeforeModelCall(beforeModelCallCtx);
|
|
400
406
|
toolEntries = beforeModelCallCtx.toolEntries;
|
|
401
|
-
if (this._mode !== "plan") {
|
|
402
|
-
toolEntries = toolEntries.filter((t) => t.name !== "exit_plan_mode");
|
|
403
|
-
}
|
|
404
407
|
flushGovernorReminders();
|
|
405
|
-
const
|
|
408
|
+
const textOnly = !!hookState.forceTextOnlyReason;
|
|
409
|
+
const toolDefinitions = toolEntries
|
|
406
410
|
.map((t) => ({
|
|
407
411
|
name: t.name,
|
|
408
412
|
description: t.description,
|
|
@@ -417,6 +421,7 @@ export class Agent {
|
|
|
417
421
|
const bufferedStreamingToolCallIds = new Set();
|
|
418
422
|
const discoveryBarrier = hookState.discoveryBarrier;
|
|
419
423
|
try {
|
|
424
|
+
markStableCurrentToolResultsForCache(this.messages);
|
|
420
425
|
const projectedMessages = projectMessages(this.messages, {
|
|
421
426
|
mode: "budgeted",
|
|
422
427
|
providerId: this.providerId,
|
|
@@ -434,11 +439,12 @@ export class Agent {
|
|
|
434
439
|
toolCount: toolDefinitions.length,
|
|
435
440
|
thinkingLevel: this.thinkingLevel,
|
|
436
441
|
mode: this._mode,
|
|
437
|
-
requestFingerprint: buildProviderRequestFingerprint(projectedMessages, toolDefinitions, this.providerId),
|
|
442
|
+
requestFingerprint: buildProviderRequestFingerprint(projectedMessages, toolDefinitions, this.providerId, toolDefinitions.length > 0 ? (textOnly ? "none" : "auto") : undefined),
|
|
438
443
|
}, traceContext);
|
|
439
444
|
const stream = this.provider.streamChat(projectedMessages, {
|
|
440
445
|
model: this.apiModel,
|
|
441
446
|
tools: toolDefinitions,
|
|
447
|
+
toolChoice: toolDefinitions.length > 0 ? (textOnly ? "none" : "auto") : undefined,
|
|
442
448
|
temperature: this.temperature,
|
|
443
449
|
thinkingLevel: this.thinkingLevel,
|
|
444
450
|
abortSignal,
|
|
@@ -1366,7 +1372,7 @@ export class Agent {
|
|
|
1366
1372
|
thinkingLevel: route.thinkingLevel,
|
|
1367
1373
|
mode: "plan",
|
|
1368
1374
|
workingDir: cwd,
|
|
1369
|
-
tools
|
|
1375
|
+
...buildToolPromptOptions(tools),
|
|
1370
1376
|
memoryPrompt: childToolNames.some((name) => name === "memory_search" || name === "memory_read_summary")
|
|
1371
1377
|
? this.memoryPrompt
|
|
1372
1378
|
: undefined,
|
|
@@ -1518,8 +1524,7 @@ export class Agent {
|
|
|
1518
1524
|
|| heapUsed >= RESIDENT_HISTORY_HEAP_HARD_LIMIT;
|
|
1519
1525
|
const shouldCompact = !!budget?.shouldCompact
|
|
1520
1526
|
|| candidate.length >= RESIDENT_HISTORY_MESSAGE_LIMIT
|
|
1521
|
-
|| residentChars >= RESIDENT_HISTORY_CHAR_SOFT_LIMIT
|
|
1522
|
-
|| heapUsed >= RESIDENT_HISTORY_HEAP_SOFT_LIMIT;
|
|
1527
|
+
|| residentChars >= RESIDENT_HISTORY_CHAR_SOFT_LIMIT;
|
|
1523
1528
|
if (shouldAggressivelyPrune) {
|
|
1524
1529
|
candidate = aggressivePruneMessages(candidate);
|
|
1525
1530
|
}
|
|
@@ -1627,7 +1632,22 @@ export class Agent {
|
|
|
1627
1632
|
metadata: { kind: "security", reason: "args_corrupt" },
|
|
1628
1633
|
};
|
|
1629
1634
|
}
|
|
1630
|
-
|
|
1635
|
+
let preparedArgs = toolCall.parsedArgs;
|
|
1636
|
+
if (tool.prepareArguments) {
|
|
1637
|
+
try {
|
|
1638
|
+
preparedArgs = tool.prepareArguments(preparedArgs);
|
|
1639
|
+
}
|
|
1640
|
+
catch (err) {
|
|
1641
|
+
return {
|
|
1642
|
+
content: `Error: Tool "${toolCall.name}" arguments could not be normalized before execution: ` +
|
|
1643
|
+
`${err instanceof Error ? err.message : String(err)}. Re-issue the call with valid arguments.`,
|
|
1644
|
+
isError: true,
|
|
1645
|
+
status: "blocked",
|
|
1646
|
+
metadata: { kind: "security", reason: "args_prepare_failed" },
|
|
1647
|
+
};
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
const missingRequired = findMissingRequiredArgs(tool.parameters, preparedArgs);
|
|
1631
1651
|
if (missingRequired.length > 0) {
|
|
1632
1652
|
return {
|
|
1633
1653
|
content: `Error: Tool "${toolCall.name}" was called without required argument${missingRequired.length === 1 ? "" : "s"}: ${missingRequired.map((name) => `"${name}"`).join(", ")}. ` +
|
|
@@ -1638,7 +1658,7 @@ export class Agent {
|
|
|
1638
1658
|
};
|
|
1639
1659
|
}
|
|
1640
1660
|
try {
|
|
1641
|
-
return await tool.execute(
|
|
1661
|
+
return await tool.execute(preparedArgs, {
|
|
1642
1662
|
cwd,
|
|
1643
1663
|
sessionID: this.sessionID,
|
|
1644
1664
|
abortSignal,
|
|
@@ -1716,7 +1736,7 @@ function appendProviderContentBlock(message, provider, block) {
|
|
|
1716
1736
|
},
|
|
1717
1737
|
};
|
|
1718
1738
|
}
|
|
1719
|
-
function buildProviderRequestFingerprint(messages, tools, providerId) {
|
|
1739
|
+
function buildProviderRequestFingerprint(messages, tools, providerId, toolChoice) {
|
|
1720
1740
|
const roleCounts = {};
|
|
1721
1741
|
let contentChars = 0;
|
|
1722
1742
|
let reasoningChars = 0;
|
|
@@ -1756,11 +1776,24 @@ function buildProviderRequestFingerprint(messages, tools, providerId) {
|
|
|
1756
1776
|
contentChars += message.content.length;
|
|
1757
1777
|
}
|
|
1758
1778
|
}
|
|
1779
|
+
const systemMessages = messages.filter((message) => message.role === "system");
|
|
1780
|
+
const bodyMessages = messages.filter((message) => message.role !== "system");
|
|
1781
|
+
const systemJsonBytes = Buffer.byteLength(JSON.stringify(systemMessages), "utf8");
|
|
1782
|
+
const bodyJsonBytes = Buffer.byteLength(JSON.stringify(bodyMessages), "utf8");
|
|
1783
|
+
const toolSchemaJsonBytes = Buffer.byteLength(JSON.stringify(tools), "utf8");
|
|
1759
1784
|
return {
|
|
1760
1785
|
roleCounts,
|
|
1761
1786
|
estimatedTokens: estimateContextTokens(messages, providerId),
|
|
1762
1787
|
projectedJsonBytes: Buffer.byteLength(JSON.stringify(messages), "utf8"),
|
|
1763
|
-
|
|
1788
|
+
systemJsonBytes,
|
|
1789
|
+
bodyJsonBytes,
|
|
1790
|
+
toolSchemaJsonBytes,
|
|
1791
|
+
staticPrefixJsonBytes: Buffer.byteLength(JSON.stringify({
|
|
1792
|
+
system: systemMessages,
|
|
1793
|
+
tools,
|
|
1794
|
+
tool_choice: toolChoice,
|
|
1795
|
+
}), "utf8"),
|
|
1796
|
+
toolChoice,
|
|
1764
1797
|
contentChars,
|
|
1765
1798
|
reasoningChars,
|
|
1766
1799
|
toolResultChars,
|
package/dist/context/prune.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Message } from "../types.js";
|
|
2
2
|
export declare function pruneMessages<T extends Message>(messages: T[]): T[];
|
|
3
|
+
export declare function markStableCurrentToolResultsForCache(messages: Message[]): void;
|
|
3
4
|
/**
|
|
4
5
|
* Aggressive variant of pruneMessages: drops the content of every prunable
|
|
5
6
|
* tool output except the latest unresolved tool turn that the model still
|
package/dist/context/prune.js
CHANGED
|
@@ -3,6 +3,8 @@ const PRUNEABLE_TOOLS = new Set([
|
|
|
3
3
|
]);
|
|
4
4
|
const TOOL_RESULT_KEEP_COUNT = 2;
|
|
5
5
|
const MIN_PRUNE_LENGTH = 240;
|
|
6
|
+
const CACHE_STABLE_PROJECTION_KEY = "cacheStableProjection";
|
|
7
|
+
const CACHE_STABLE_FULL_PROJECTION = "full";
|
|
6
8
|
export function pruneMessages(messages) {
|
|
7
9
|
const toolNameByCallId = new Map();
|
|
8
10
|
const pruneCandidates = [];
|
|
@@ -19,6 +21,9 @@ export function pruneMessages(messages) {
|
|
|
19
21
|
if (message.role !== "tool") {
|
|
20
22
|
continue;
|
|
21
23
|
}
|
|
24
|
+
if (isCacheStableFullToolResult(message)) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
22
27
|
if (protectedToolCallIds.has(message.toolCallId)) {
|
|
23
28
|
const toolName = toolNameByCallId.get(message.toolCallId);
|
|
24
29
|
if (toolName && shouldPruneToolResult(toolName, message.content)) {
|
|
@@ -49,6 +54,30 @@ export function pruneMessages(messages) {
|
|
|
49
54
|
};
|
|
50
55
|
});
|
|
51
56
|
}
|
|
57
|
+
export function markStableCurrentToolResultsForCache(messages) {
|
|
58
|
+
const protectedToolCallIds = collectProtectedToolCallIds(messages);
|
|
59
|
+
if (protectedToolCallIds.size === 0)
|
|
60
|
+
return;
|
|
61
|
+
const toolNameByCallId = new Map();
|
|
62
|
+
for (const message of messages) {
|
|
63
|
+
if (message.role !== "assistant" || !message.toolCalls)
|
|
64
|
+
continue;
|
|
65
|
+
for (const toolCall of message.toolCalls) {
|
|
66
|
+
toolNameByCallId.set(toolCall.id, toolCall.name);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
for (const message of messages) {
|
|
70
|
+
if (message.role !== "tool" || !protectedToolCallIds.has(message.toolCallId))
|
|
71
|
+
continue;
|
|
72
|
+
const toolName = toolNameByCallId.get(message.toolCallId);
|
|
73
|
+
if (!toolName || !shouldPruneToolResult(toolName, message.content))
|
|
74
|
+
continue;
|
|
75
|
+
message.metadata = {
|
|
76
|
+
...message.metadata,
|
|
77
|
+
[CACHE_STABLE_PROJECTION_KEY]: CACHE_STABLE_FULL_PROJECTION,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
52
81
|
function shouldPruneToolResult(toolName, content) {
|
|
53
82
|
if (!PRUNEABLE_TOOLS.has(toolName)) {
|
|
54
83
|
return false;
|
|
@@ -64,6 +93,9 @@ function shouldPruneToolResult(toolName, content) {
|
|
|
64
93
|
function summarizePrunedToolResult(toolName, content) {
|
|
65
94
|
return `[${toolName} output omitted to control context size; original length ${content.length} chars]`;
|
|
66
95
|
}
|
|
96
|
+
function isCacheStableFullToolResult(message) {
|
|
97
|
+
return message.metadata?.[CACHE_STABLE_PROJECTION_KEY] === CACHE_STABLE_FULL_PROJECTION;
|
|
98
|
+
}
|
|
67
99
|
/**
|
|
68
100
|
* Aggressive variant of pruneMessages: drops the content of every prunable
|
|
69
101
|
* tool output except the latest unresolved tool turn that the model still
|
|
@@ -19,7 +19,7 @@ import { BashAllowlist } from "../../approval/session-cache.js";
|
|
|
19
19
|
import { getLspService } from "../../lsp/index.js";
|
|
20
20
|
import { buildSystemPrompt } from "../../system-prompt.js";
|
|
21
21
|
import { FileStateTracker } from "../../tools/file-state.js";
|
|
22
|
-
import { createAllTools } from "../../tools/index.js";
|
|
22
|
+
import { buildToolPromptOptions, createAllTools } from "../../tools/index.js";
|
|
23
23
|
import { displayModel, encodeModel, decodeModel } from "../../provider-registry.js";
|
|
24
24
|
import { buildMemoryPrompt, recordMemoryCitations } from "../../memory/index.js";
|
|
25
25
|
import { getDefaultThinkingLevel } from "../../provider-transform.js";
|
|
@@ -94,7 +94,7 @@ export class RunDriver {
|
|
|
94
94
|
thinkingLevel,
|
|
95
95
|
mode: initialMode,
|
|
96
96
|
workingDir: session.cwd,
|
|
97
|
-
tools
|
|
97
|
+
...buildToolPromptOptions(tools.filter((tool) => !tool.deferred)),
|
|
98
98
|
memoryPrompt,
|
|
99
99
|
});
|
|
100
100
|
const budgetLedger = new BudgetLedger();
|
|
@@ -211,6 +211,7 @@ function mergeUsage(prev, next) {
|
|
|
211
211
|
completionTokens: prev.completionTokens + (next.completionTokens ?? 0),
|
|
212
212
|
promptCacheHitTokens: (prev.promptCacheHitTokens ?? 0) + (next.promptCacheHitTokens ?? 0),
|
|
213
213
|
promptCacheMissTokens: (prev.promptCacheMissTokens ?? 0) + (next.promptCacheMissTokens ?? 0),
|
|
214
|
+
cacheCreationTokens: (prev.cacheCreationTokens ?? 0) + (next.cacheCreationTokens ?? 0),
|
|
214
215
|
reasoningTokens: (prev.reasoningTokens ?? 0) + (next.reasoningTokens ?? 0),
|
|
215
216
|
totalTokens: (prev.totalTokens ?? 0) + (next.totalTokens ?? 0),
|
|
216
217
|
};
|
package/dist/main.js
CHANGED
|
@@ -8,13 +8,14 @@ import { BudgetLedger } from "./agent/budget-ledger.js";
|
|
|
8
8
|
import { parseArgs, printHelp } from "./cli.js";
|
|
9
9
|
import { UserConfig } from "./config.js";
|
|
10
10
|
import { createProviderInstance, createUnavailableProvider } from "./provider.js";
|
|
11
|
+
import { resolveConfiguredModel } from "./model-selection.js";
|
|
11
12
|
import { getDefaultThinkingLevel } from "./provider-transform.js";
|
|
12
13
|
import { ProviderRegistry, displayModel, encodeModel, decodeModel } from "./provider-registry.js";
|
|
13
14
|
import { SessionManager } from "./session.js";
|
|
14
15
|
import { createSessionTitleUpdater } from "./session-title.js";
|
|
15
16
|
import { buildSystemPrompt } from "./system-prompt.js";
|
|
16
17
|
import { SkillRegistry } from "./skills/registry.js";
|
|
17
|
-
import { createAllTools } from "./tools/index.js";
|
|
18
|
+
import { buildToolPromptOptions, createAllTools } from "./tools/index.js";
|
|
18
19
|
import { FileStateTracker } from "./tools/file-state.js";
|
|
19
20
|
import { PermissionAwareApprovalController } from "./approval/controller.js";
|
|
20
21
|
import { BashAllowlist } from "./approval/session-cache.js";
|
|
@@ -244,18 +245,19 @@ async function main() {
|
|
|
244
245
|
}
|
|
245
246
|
sessionPromptCacheKey = sessionManager.getOrCreatePromptCacheKey();
|
|
246
247
|
// Model resolution:
|
|
247
|
-
// 1. Session metadata
|
|
248
|
+
// 1. CLI flag 2. Session metadata 3. User-configured default model
|
|
248
249
|
// No implicit built-in model fallback.
|
|
249
250
|
const fallbackProviderId = defaultProvider?.id || "";
|
|
250
251
|
const sessionModel = sessionManager?.getMetadata().model;
|
|
251
|
-
const
|
|
252
|
+
const defaultModel = userConfig.getDefaultModel();
|
|
252
253
|
const sessionThinkingLevel = sessionManager?.getMetadata().thinkingLevel;
|
|
253
254
|
const configuredThinkingLevel = userConfig.getDefaultThinkingLevel();
|
|
254
|
-
const normalizedConfiguredModel =
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
255
|
+
const normalizedConfiguredModel = resolveConfiguredModel({
|
|
256
|
+
cliModel: args.model,
|
|
257
|
+
sessionModel,
|
|
258
|
+
defaultModel,
|
|
259
|
+
fallbackProviderId,
|
|
260
|
+
});
|
|
259
261
|
const { providerId: effectiveProviderId, modelId: effectiveModelId } = normalizedConfiguredModel
|
|
260
262
|
? decodeModel(normalizedConfiguredModel)
|
|
261
263
|
: { providerId: undefined, modelId: "" };
|
|
@@ -288,7 +290,7 @@ async function main() {
|
|
|
288
290
|
thinkingLevel: initialThinkingLevel,
|
|
289
291
|
mode: initialMode,
|
|
290
292
|
workingDir: args.cwd,
|
|
291
|
-
tools
|
|
293
|
+
...buildToolPromptOptions(tools.filter((tool) => !tool.deferred)),
|
|
292
294
|
memoryPrompt,
|
|
293
295
|
});
|
|
294
296
|
const traceInfo = configureDebugTrace({
|
package/dist/model-pricing.js
CHANGED
|
@@ -38,7 +38,8 @@ export function calculateUsageCost(providerId, modelId, usage) {
|
|
|
38
38
|
if (!pricing)
|
|
39
39
|
return undefined;
|
|
40
40
|
const hasCacheBreakdown = typeof usage.promptCacheHitTokens === "number"
|
|
41
|
-
|| typeof usage.promptCacheMissTokens === "number"
|
|
41
|
+
|| typeof usage.promptCacheMissTokens === "number"
|
|
42
|
+
|| typeof usage.cacheCreationTokens === "number";
|
|
42
43
|
const hit = usage.promptCacheHitTokens ?? 0;
|
|
43
44
|
const miss = hasCacheBreakdown
|
|
44
45
|
? usage.promptCacheMissTokens ?? Math.max(0, usage.promptTokens - hit)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { encodeModel } from "./provider-registry.js";
|
|
2
|
+
export function resolveConfiguredModel(input) {
|
|
3
|
+
const selected = input.cliModel ?? input.sessionModel ?? input.defaultModel;
|
|
4
|
+
if (!selected)
|
|
5
|
+
return "";
|
|
6
|
+
if (selected.includes(":"))
|
|
7
|
+
return selected;
|
|
8
|
+
return input.fallbackProviderId ? encodeModel(input.fallbackProviderId, selected) : "";
|
|
9
|
+
}
|
|
@@ -176,7 +176,7 @@ function isCodeWriteResult(_toolCall, result) {
|
|
|
176
176
|
return result.metadata?.kind === "write" || result.metadata?.kind === "edit" || result.metadata?.kind === "patch";
|
|
177
177
|
}
|
|
178
178
|
function isMutationTool(name) {
|
|
179
|
-
return name === "edit" || name === "write"
|
|
179
|
+
return name === "edit" || name === "write";
|
|
180
180
|
}
|
|
181
181
|
function hasSubagentLifecycleActivity(toolCalls, toolResults) {
|
|
182
182
|
return toolCalls.some((toolCall) => isSubagentLifecycleTool(toolCall.name))
|
|
@@ -3,7 +3,6 @@ export const defaultToolSnippets = {
|
|
|
3
3
|
read: "Read the contents of a file",
|
|
4
4
|
bash: "Execute a bash command",
|
|
5
5
|
edit: "Apply targeted string replacements to a file",
|
|
6
|
-
apply_patch: "Apply a structured patch for multi-file or larger code changes",
|
|
7
6
|
write: "Write a new file or overwrite an existing one",
|
|
8
7
|
glob: "Find files by glob pattern without using bash",
|
|
9
8
|
grep: "Search file contents using regex",
|
|
@@ -24,7 +23,6 @@ export const defaultToolNames = [
|
|
|
24
23
|
"glob",
|
|
25
24
|
"bash",
|
|
26
25
|
"edit",
|
|
27
|
-
"apply_patch",
|
|
28
26
|
"write",
|
|
29
27
|
"grep",
|
|
30
28
|
"lsp",
|
|
@@ -46,7 +44,7 @@ export function buildEnvironmentPrompt(options = {}) {
|
|
|
46
44
|
const workingDir = options.workingDir ?? cwd().replace(/\\/g, "/");
|
|
47
45
|
const currentDate = options.currentDate ?? new Date().toISOString().slice(0, 10);
|
|
48
46
|
const tools = options.tools ?? defaultToolNames;
|
|
49
|
-
const snippets = options.toolSnippets ??
|
|
47
|
+
const snippets = { ...defaultToolSnippets, ...(options.toolSnippets ?? {}) };
|
|
50
48
|
const visibleTools = tools.filter((name) => snippets[name]);
|
|
51
49
|
const toolList = visibleTools.length > 0
|
|
52
50
|
? visibleTools.map((name) => `- ${name}: ${snippets[name]}`).join("\n")
|
package/dist/prompt/runtime.js
CHANGED
|
@@ -7,7 +7,7 @@ const defaultGuidelines = [
|
|
|
7
7
|
"Ground decisions in the codebase: inspect relevant files, command output, or runtime state before making claims about behavior. Separate confirmed facts from inference when evidence is incomplete.",
|
|
8
8
|
"Choose the smallest coherent change. Edit only the files required for the requested change; do not refactor or improve adjacent code unprompted.",
|
|
9
9
|
"Runtime meta instructions are private control state. Use them only to adjust behavior; do not quote, mention, or paraphrase them in user-facing text.",
|
|
10
|
-
"For modifications to existing code, read the file first. For brand-new files whose target path is known and does not exist, write directly without exploratory reading. Use edit for
|
|
10
|
+
"For modifications to existing code, read the file first. For brand-new files whose target path is known and does not exist, write directly without exploratory reading. Use edit for targeted changes and write for intentional full-file replacement of an existing file. Never delete and recreate a file just to overwrite it.",
|
|
11
11
|
"Prefer structured tools (glob, grep, lsp, read) over bash for search and inspection. Do not repeat a near-identical search or re-read the same file unless new evidence changes the question.",
|
|
12
12
|
"If a tool fails, diagnose the error before switching tactics. Do not retry the identical call with identical arguments. After two equivalent failures, switch approach — re-read the file, use a different tool, rewrite the whole file with write, or ask the user.",
|
|
13
13
|
"Before reporting a task complete, verify it works when verification is meaningful and cheap — run the existing test, execute the script, check the output. If no test exists, the change is purely declarative (static HTML/markdown/config), or running the code is not practical, state that explicitly rather than inventing a verification step. Do not write throwaway validation scripts to prove correctness; if there is no real check to run, report the change and stop.",
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import type { Provider, ProviderMessage, StreamChunk, ThinkingLevel, ToolDefinition } from "./types.js";
|
|
1
|
+
import type { Provider, ProviderMessage, StreamChunk, ThinkingLevel, ToolChoiceMode, ToolDefinition } from "./types.js";
|
|
2
|
+
declare const ANTHROPIC_PROMPT_CACHE_CONTROL: {
|
|
3
|
+
readonly type: "ephemeral";
|
|
4
|
+
};
|
|
2
5
|
export interface AnthropicProviderOptions {
|
|
3
6
|
providerId?: string;
|
|
4
7
|
apiKey: string;
|
|
@@ -9,10 +12,10 @@ interface AnthropicRequest {
|
|
|
9
12
|
model: string;
|
|
10
13
|
max_tokens: number;
|
|
11
14
|
messages: AnthropicMessage[];
|
|
12
|
-
system?: string;
|
|
15
|
+
system?: string | AnthropicSystemBlock[];
|
|
13
16
|
tools?: AnthropicTool[];
|
|
14
17
|
tool_choice?: {
|
|
15
|
-
type: "auto" | "any";
|
|
18
|
+
type: "auto" | "any" | "none";
|
|
16
19
|
};
|
|
17
20
|
stream?: boolean;
|
|
18
21
|
temperature?: number;
|
|
@@ -20,6 +23,12 @@ interface AnthropicRequest {
|
|
|
20
23
|
type: "adaptive";
|
|
21
24
|
};
|
|
22
25
|
}
|
|
26
|
+
type AnthropicCacheControl = typeof ANTHROPIC_PROMPT_CACHE_CONTROL;
|
|
27
|
+
interface AnthropicSystemBlock {
|
|
28
|
+
type: "text";
|
|
29
|
+
text: string;
|
|
30
|
+
cache_control?: AnthropicCacheControl;
|
|
31
|
+
}
|
|
23
32
|
type AnthropicContentBlock = {
|
|
24
33
|
type: "text";
|
|
25
34
|
text: string;
|
|
@@ -59,15 +68,18 @@ interface AnthropicTool {
|
|
|
59
68
|
name: string;
|
|
60
69
|
description: string;
|
|
61
70
|
input_schema: ToolDefinition["parameters"];
|
|
71
|
+
cache_control?: AnthropicCacheControl;
|
|
62
72
|
}
|
|
63
73
|
export declare function createAnthropicMessagesProvider(options: AnthropicProviderOptions): Provider;
|
|
64
74
|
export declare function buildAnthropicRequest(options: AnthropicProviderOptions, messages: ProviderMessage[], chatOptions: {
|
|
65
75
|
model: string;
|
|
66
76
|
tools?: ToolDefinition[];
|
|
77
|
+
toolChoice?: ToolChoiceMode;
|
|
67
78
|
temperature?: number;
|
|
68
79
|
thinkingLevel?: ThinkingLevel;
|
|
69
80
|
stream?: boolean;
|
|
70
81
|
}): AnthropicRequest;
|
|
82
|
+
export declare function supportsAnthropicPromptCache(options: AnthropicProviderOptions, model: string): boolean;
|
|
71
83
|
export declare function toAnthropicMessages(messages: ProviderMessage[], echoThinking?: boolean): {
|
|
72
84
|
system: string;
|
|
73
85
|
messages: AnthropicMessage[];
|
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
import { getAvailableThinkingLevels, normalizeThinkingLevel } from "./provider-transform.js";
|
|
2
2
|
const ANTHROPIC_VERSION = "2023-06-01";
|
|
3
3
|
const DEFAULT_MAX_TOKENS = 8192;
|
|
4
|
+
const ANTHROPIC_PROMPT_CACHE_CONTROL = { type: "ephemeral" };
|
|
5
|
+
const MINIMAX_PROMPT_CACHE_MODELS = new Set([
|
|
6
|
+
"minimax-m2.7",
|
|
7
|
+
"minimax-m2.7-highspeed",
|
|
8
|
+
"minimax-m2.5",
|
|
9
|
+
"minimax-m2.5-highspeed",
|
|
10
|
+
"minimax-m2.1",
|
|
11
|
+
"minimax-m2.1-highspeed",
|
|
12
|
+
"minimax-m2",
|
|
13
|
+
"m2-her",
|
|
14
|
+
]);
|
|
4
15
|
export function createAnthropicMessagesProvider(options) {
|
|
5
16
|
async function* streamChat(messages, chatOptions) {
|
|
6
17
|
const body = buildAnthropicRequest(options, messages, {
|
|
7
18
|
model: chatOptions.model,
|
|
8
19
|
tools: chatOptions.tools,
|
|
20
|
+
toolChoice: chatOptions.toolChoice,
|
|
9
21
|
temperature: chatOptions.temperature,
|
|
10
22
|
thinkingLevel: chatOptions.thinkingLevel,
|
|
11
23
|
stream: true,
|
|
@@ -41,18 +53,25 @@ export function createAnthropicMessagesProvider(options) {
|
|
|
41
53
|
}
|
|
42
54
|
export function buildAnthropicRequest(options, messages, chatOptions) {
|
|
43
55
|
const { system, messages: anthropicMessages } = toAnthropicMessages(messages, shouldEchoThinking(options.providerId));
|
|
56
|
+
const enablePromptCache = supportsAnthropicPromptCache(options, chatOptions.model);
|
|
44
57
|
const tools = chatOptions.tools?.map((tool) => ({
|
|
45
58
|
name: tool.name,
|
|
46
59
|
description: tool.description,
|
|
47
60
|
input_schema: tool.parameters,
|
|
48
61
|
}));
|
|
62
|
+
if (enablePromptCache && tools && tools.length > 0) {
|
|
63
|
+
tools[tools.length - 1] = {
|
|
64
|
+
...tools[tools.length - 1],
|
|
65
|
+
cache_control: ANTHROPIC_PROMPT_CACHE_CONTROL,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
49
68
|
const body = {
|
|
50
69
|
model: chatOptions.model,
|
|
51
70
|
max_tokens: DEFAULT_MAX_TOKENS,
|
|
52
|
-
system: system
|
|
71
|
+
system: buildAnthropicSystem(system, enablePromptCache),
|
|
53
72
|
messages: anthropicMessages,
|
|
54
73
|
tools: tools && tools.length > 0 ? tools : undefined,
|
|
55
|
-
tool_choice: tools && tools.length > 0 ? { type: "auto" } : undefined,
|
|
74
|
+
tool_choice: tools && tools.length > 0 ? { type: chatOptions.toolChoice ?? "auto" } : undefined,
|
|
56
75
|
stream: chatOptions.stream || undefined,
|
|
57
76
|
};
|
|
58
77
|
if (typeof chatOptions.temperature === "number") {
|
|
@@ -64,6 +83,23 @@ export function buildAnthropicRequest(options, messages, chatOptions) {
|
|
|
64
83
|
}
|
|
65
84
|
return body;
|
|
66
85
|
}
|
|
86
|
+
function buildAnthropicSystem(system, enablePromptCache) {
|
|
87
|
+
if (!system)
|
|
88
|
+
return undefined;
|
|
89
|
+
if (!enablePromptCache)
|
|
90
|
+
return system;
|
|
91
|
+
return [{ type: "text", text: system, cache_control: ANTHROPIC_PROMPT_CACHE_CONTROL }];
|
|
92
|
+
}
|
|
93
|
+
export function supportsAnthropicPromptCache(options, model) {
|
|
94
|
+
const providerId = (options.providerId ?? "").toLowerCase();
|
|
95
|
+
if (providerId === "anthropic" || isOfficialAnthropicBaseUrl(options.baseURL)) {
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
if (!isMiniMaxAnthropicEndpoint(options)) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
return MINIMAX_PROMPT_CACHE_MODELS.has(model.toLowerCase());
|
|
102
|
+
}
|
|
67
103
|
export function toAnthropicMessages(messages, echoThinking = false) {
|
|
68
104
|
const system = [];
|
|
69
105
|
const out = [];
|
|
@@ -512,6 +548,7 @@ function mergeAnthropicUsage(current, raw) {
|
|
|
512
548
|
let promptTokens = current?.promptTokens ?? 0;
|
|
513
549
|
let promptCacheHitTokens = current?.promptCacheHitTokens;
|
|
514
550
|
let promptCacheMissTokens = current?.promptCacheMissTokens;
|
|
551
|
+
let cacheCreationTokens = current?.cacheCreationTokens;
|
|
515
552
|
if (hasPromptUsage) {
|
|
516
553
|
const inputTokens = rawInput ?? promptCacheMissTokens ?? promptTokens;
|
|
517
554
|
const cacheRead = rawCacheRead ?? promptCacheHitTokens ?? 0;
|
|
@@ -519,12 +556,14 @@ function mergeAnthropicUsage(current, raw) {
|
|
|
519
556
|
promptTokens = inputTokens + cacheRead + cacheCreation;
|
|
520
557
|
promptCacheHitTokens = cacheRead;
|
|
521
558
|
promptCacheMissTokens = inputTokens + cacheCreation;
|
|
559
|
+
cacheCreationTokens = cacheCreation;
|
|
522
560
|
}
|
|
523
561
|
return {
|
|
524
562
|
promptTokens,
|
|
525
563
|
completionTokens: outputTokens,
|
|
526
564
|
promptCacheHitTokens,
|
|
527
565
|
promptCacheMissTokens,
|
|
566
|
+
cacheCreationTokens,
|
|
528
567
|
totalTokens: promptTokens + outputTokens,
|
|
529
568
|
};
|
|
530
569
|
}
|
|
@@ -534,6 +573,20 @@ function shouldEchoThinking(providerId) {
|
|
|
534
573
|
function shouldSendBearerAuth(options) {
|
|
535
574
|
return !isOfficialAnthropicBaseUrl(options.baseURL) || options.providerId?.startsWith("minimax") === true;
|
|
536
575
|
}
|
|
576
|
+
function isMiniMaxAnthropicEndpoint(options) {
|
|
577
|
+
const providerId = (options.providerId ?? "").toLowerCase();
|
|
578
|
+
if (providerId !== "minimax" && providerId !== "minimax-anthropic")
|
|
579
|
+
return false;
|
|
580
|
+
try {
|
|
581
|
+
const url = new URL(options.baseURL);
|
|
582
|
+
const host = url.hostname.toLowerCase();
|
|
583
|
+
const path = url.pathname.toLowerCase();
|
|
584
|
+
return (host === "api.minimax.io" || host === "api.minimaxi.com") && path.includes("/anthropic");
|
|
585
|
+
}
|
|
586
|
+
catch {
|
|
587
|
+
return false;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
537
590
|
function isOfficialAnthropicBaseUrl(baseURL) {
|
|
538
591
|
try {
|
|
539
592
|
return new URL(baseURL).hostname === "api.anthropic.com";
|
|
@@ -72,6 +72,7 @@ export function createOpenAICodexProvider(options) {
|
|
|
72
72
|
const body = JSON.stringify(buildRequestBody(messages, {
|
|
73
73
|
model: chatOptions.model,
|
|
74
74
|
tools: chatOptions.tools,
|
|
75
|
+
toolChoice: chatOptions.toolChoice,
|
|
75
76
|
reasoningEffort: requestConfig.reasoningEffort,
|
|
76
77
|
sessionId,
|
|
77
78
|
providerId: options.providerId,
|
|
@@ -314,7 +315,7 @@ function buildRequestBody(messages, options) {
|
|
|
314
315
|
providerId: options.providerId,
|
|
315
316
|
model: options.model,
|
|
316
317
|
}),
|
|
317
|
-
tool_choice: "auto",
|
|
318
|
+
tool_choice: options.tools && options.tools.length > 0 ? options.toolChoice ?? "auto" : undefined,
|
|
318
319
|
parallel_tool_calls: true,
|
|
319
320
|
text: { verbosity: "medium" },
|
|
320
321
|
};
|
|
@@ -454,6 +455,7 @@ function isTransientCodexTransportError(error) {
|
|
|
454
455
|
/\bEPIPE\b/i,
|
|
455
456
|
/socket hang up/i,
|
|
456
457
|
/fetch failed/i,
|
|
458
|
+
/Unable to connect\. Is the computer able to access the url\?/i,
|
|
457
459
|
/unknown certificate verification error/i,
|
|
458
460
|
/certificate (?:verify|verification) (?:failed|error)/i,
|
|
459
461
|
/unable to verify (?:the )?(?:first )?certificate/i,
|
package/dist/provider.js
CHANGED
|
@@ -106,7 +106,7 @@ export function createProviderInstance(options) {
|
|
|
106
106
|
reasoningContentEcho: requestConfig.reasoningContentEcho ?? "tool_calls",
|
|
107
107
|
})),
|
|
108
108
|
tools: tools && tools.length > 0 ? tools : undefined,
|
|
109
|
-
tool_choice: tools && tools.length > 0 ? "auto" : undefined,
|
|
109
|
+
tool_choice: tools && tools.length > 0 ? chatOptions.toolChoice ?? "auto" : undefined,
|
|
110
110
|
stream: true,
|
|
111
111
|
};
|
|
112
112
|
// DeepSeek and MiniMax only emit final usage in streaming mode when this flag is set.
|