@bubblebrain-ai/bubble 0.0.16 → 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/internal-reminder-sanitizer.d.ts +2 -0
- package/dist/agent/internal-reminder-sanitizer.js +27 -0
- package/dist/agent/tool-intent.js +0 -1
- package/dist/agent.d.ts +1 -0
- package/dist/agent.js +148 -23
- package/dist/context/budget.js +15 -0
- package/dist/context/prune.d.ts +1 -0
- package/dist/context/prune.js +32 -0
- package/dist/debug-trace.js +14 -0
- package/dist/feishu/agent-host/run-driver.js +2 -2
- package/dist/feishu/card/run-state.js +1 -0
- package/dist/feishu/serve.js +1 -0
- package/dist/main.js +13 -9
- package/dist/model-catalog.d.ts +3 -0
- package/dist/model-catalog.js +38 -0
- package/dist/model-config.d.ts +3 -0
- package/dist/model-config.js +3 -0
- 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/compose.js +1 -1
- package/dist/prompt/environment.js +1 -3
- package/dist/prompt/reminders.js +3 -3
- package/dist/prompt/runtime.js +2 -1
- package/dist/provider-anthropic.d.ts +89 -0
- package/dist/provider-anthropic.js +597 -0
- package/dist/provider-openai-codex.js +3 -1
- package/dist/provider-registry.d.ts +2 -0
- package/dist/provider-registry.js +29 -3
- package/dist/provider-transform.d.ts +1 -1
- package/dist/provider-transform.js +14 -0
- package/dist/provider.d.ts +4 -1
- package/dist/provider.js +120 -41
- package/dist/session-log.js +14 -2
- package/dist/session-title.js +3 -6
- package/dist/slash-commands/commands.js +8 -2
- 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 +22 -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
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import type { AssistantProviderMetadata } from "../types.js";
|
|
1
2
|
export declare function formatInternalReminderBlock(kind: string, content: string): string;
|
|
2
3
|
export declare function formatInternalContextBlock(kind: string, content: string): string;
|
|
3
4
|
export declare function sanitizeInternalReminderBlocks(text: string): string;
|
|
5
|
+
export declare function sanitizeAssistantProviderMetadata(metadata: AssistantProviderMetadata | undefined): AssistantProviderMetadata | undefined;
|
|
4
6
|
export declare function createStreamingInternalReminderSanitizer(): {
|
|
5
7
|
push(delta: string): string;
|
|
6
8
|
flush(): string;
|
|
@@ -37,6 +37,33 @@ export function sanitizeInternalReminderBlocks(text) {
|
|
|
37
37
|
const sanitizer = createStreamingInternalReminderSanitizer();
|
|
38
38
|
return sanitizer.push(text) + sanitizer.flush();
|
|
39
39
|
}
|
|
40
|
+
export function sanitizeAssistantProviderMetadata(metadata) {
|
|
41
|
+
const anthropic = metadata?.anthropic;
|
|
42
|
+
const blocks = anthropic?.contentBlocks;
|
|
43
|
+
if (!metadata || !anthropic || !blocks?.length)
|
|
44
|
+
return metadata;
|
|
45
|
+
let changed = false;
|
|
46
|
+
const sanitizedBlocks = blocks.map((block) => {
|
|
47
|
+
if (block.type !== "text" || typeof block.text !== "string") {
|
|
48
|
+
return block;
|
|
49
|
+
}
|
|
50
|
+
const sanitizedText = sanitizeInternalReminderBlocks(block.text);
|
|
51
|
+
if (sanitizedText === block.text) {
|
|
52
|
+
return block;
|
|
53
|
+
}
|
|
54
|
+
changed = true;
|
|
55
|
+
return { ...block, text: sanitizedText };
|
|
56
|
+
});
|
|
57
|
+
if (!changed)
|
|
58
|
+
return metadata;
|
|
59
|
+
return {
|
|
60
|
+
...metadata,
|
|
61
|
+
anthropic: {
|
|
62
|
+
...anthropic,
|
|
63
|
+
contentBlocks: sanitizedBlocks,
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
40
67
|
export function createStreamingInternalReminderSanitizer() {
|
|
41
68
|
let pending = "";
|
|
42
69
|
const drain = (final) => {
|
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
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
import { compactMessages } from "./context/compact.js";
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
7
|
import { compactMessagesWithLLM } from "./context/compact-llm.js";
|
|
8
|
-
import { getContextBudget } from "./context/budget.js";
|
|
8
|
+
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";
|
|
@@ -20,10 +20,11 @@ import { composeAbortSignals } from "./agent/budget-ledger.js";
|
|
|
20
20
|
import { assignAgentNickname, builtinAgentProfiles, mergeUsage, selectToolsForAgentProfile, validateAgentProfileTools } from "./agent/profiles.js";
|
|
21
21
|
import { snapshotSubagentThread, subagentResultFromThread } from "./agent/subagent-control.js";
|
|
22
22
|
import { isHiddenToolResult } from "./agent/discovery-barrier.js";
|
|
23
|
-
import { createStreamingInternalReminderSanitizer, sanitizeInternalReminderBlocks } from "./agent/internal-reminder-sanitizer.js";
|
|
23
|
+
import { createStreamingInternalReminderSanitizer, sanitizeAssistantProviderMetadata, sanitizeInternalReminderBlocks } from "./agent/internal-reminder-sanitizer.js";
|
|
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;
|
|
@@ -376,6 +382,7 @@ export class Agent {
|
|
|
376
382
|
modelId: this.apiModel,
|
|
377
383
|
};
|
|
378
384
|
const streamingToolCalls = new Map();
|
|
385
|
+
const textSanitizer = createStreamingInternalReminderSanitizer();
|
|
379
386
|
const reasoningSanitizer = createStreamingInternalReminderSanitizer();
|
|
380
387
|
let turnUsage;
|
|
381
388
|
let assistantAppended = false;
|
|
@@ -397,11 +404,9 @@ export class Agent {
|
|
|
397
404
|
};
|
|
398
405
|
await hookBus.runBeforeModelCall(beforeModelCallCtx);
|
|
399
406
|
toolEntries = beforeModelCallCtx.toolEntries;
|
|
400
|
-
if (this._mode !== "plan") {
|
|
401
|
-
toolEntries = toolEntries.filter((t) => t.name !== "exit_plan_mode");
|
|
402
|
-
}
|
|
403
407
|
flushGovernorReminders();
|
|
404
|
-
const
|
|
408
|
+
const textOnly = !!hookState.forceTextOnlyReason;
|
|
409
|
+
const toolDefinitions = toolEntries
|
|
405
410
|
.map((t) => ({
|
|
406
411
|
name: t.name,
|
|
407
412
|
description: t.description,
|
|
@@ -416,6 +421,7 @@ export class Agent {
|
|
|
416
421
|
const bufferedStreamingToolCallIds = new Set();
|
|
417
422
|
const discoveryBarrier = hookState.discoveryBarrier;
|
|
418
423
|
try {
|
|
424
|
+
markStableCurrentToolResultsForCache(this.messages);
|
|
419
425
|
const projectedMessages = projectMessages(this.messages, {
|
|
420
426
|
mode: "budgeted",
|
|
421
427
|
providerId: this.providerId,
|
|
@@ -433,10 +439,12 @@ export class Agent {
|
|
|
433
439
|
toolCount: toolDefinitions.length,
|
|
434
440
|
thinkingLevel: this.thinkingLevel,
|
|
435
441
|
mode: this._mode,
|
|
442
|
+
requestFingerprint: buildProviderRequestFingerprint(projectedMessages, toolDefinitions, this.providerId, toolDefinitions.length > 0 ? (textOnly ? "none" : "auto") : undefined),
|
|
436
443
|
}, traceContext);
|
|
437
444
|
const stream = this.provider.streamChat(projectedMessages, {
|
|
438
445
|
model: this.apiModel,
|
|
439
446
|
tools: toolDefinitions,
|
|
447
|
+
toolChoice: toolDefinitions.length > 0 ? (textOnly ? "none" : "auto") : undefined,
|
|
440
448
|
temperature: this.temperature,
|
|
441
449
|
thinkingLevel: this.thinkingLevel,
|
|
442
450
|
abortSignal,
|
|
@@ -445,9 +453,14 @@ export class Agent {
|
|
|
445
453
|
throwIfAborted(abortSignal);
|
|
446
454
|
switch (chunk.type) {
|
|
447
455
|
case "text":
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
456
|
+
{
|
|
457
|
+
const sanitizedDelta = textSanitizer.push(chunk.content);
|
|
458
|
+
if (sanitizedDelta) {
|
|
459
|
+
assistantMsg.content += sanitizedDelta;
|
|
460
|
+
streamTextChars += sanitizedDelta.length;
|
|
461
|
+
yield emit({ type: "text_delta", content: sanitizedDelta });
|
|
462
|
+
}
|
|
463
|
+
}
|
|
451
464
|
break;
|
|
452
465
|
case "reasoning_delta":
|
|
453
466
|
{
|
|
@@ -468,6 +481,9 @@ export class Agent {
|
|
|
468
481
|
}
|
|
469
482
|
}
|
|
470
483
|
break;
|
|
484
|
+
case "provider_content_block":
|
|
485
|
+
appendProviderContentBlock(assistantMsg, chunk.provider, chunk.block);
|
|
486
|
+
break;
|
|
471
487
|
case "tool_call":
|
|
472
488
|
if (discoveryBarrier?.isEnabled()
|
|
473
489
|
&& (bufferedStreamingToolCallIds.has(chunk.id) || discoveryBarrier.shouldBufferStreamingToolCall(chunk.name))) {
|
|
@@ -540,6 +556,12 @@ export class Agent {
|
|
|
540
556
|
for (const update of this.drainSubagentToolUpdates())
|
|
541
557
|
yield emit(update);
|
|
542
558
|
}
|
|
559
|
+
const flushedText = textSanitizer.flush();
|
|
560
|
+
if (flushedText) {
|
|
561
|
+
assistantMsg.content += flushedText;
|
|
562
|
+
streamTextChars += flushedText.length;
|
|
563
|
+
yield emit({ type: "text_delta", content: flushedText });
|
|
564
|
+
}
|
|
543
565
|
const flushedReasoning = reasoningSanitizer.flush();
|
|
544
566
|
if (flushedReasoning) {
|
|
545
567
|
debugReasoningStream({
|
|
@@ -1350,7 +1372,7 @@ export class Agent {
|
|
|
1350
1372
|
thinkingLevel: route.thinkingLevel,
|
|
1351
1373
|
mode: "plan",
|
|
1352
1374
|
workingDir: cwd,
|
|
1353
|
-
tools
|
|
1375
|
+
...buildToolPromptOptions(tools),
|
|
1354
1376
|
memoryPrompt: childToolNames.some((name) => name === "memory_search" || name === "memory_read_summary")
|
|
1355
1377
|
? this.memoryPrompt
|
|
1356
1378
|
: undefined,
|
|
@@ -1502,8 +1524,7 @@ export class Agent {
|
|
|
1502
1524
|
|| heapUsed >= RESIDENT_HISTORY_HEAP_HARD_LIMIT;
|
|
1503
1525
|
const shouldCompact = !!budget?.shouldCompact
|
|
1504
1526
|
|| candidate.length >= RESIDENT_HISTORY_MESSAGE_LIMIT
|
|
1505
|
-
|| residentChars >= RESIDENT_HISTORY_CHAR_SOFT_LIMIT
|
|
1506
|
-
|| heapUsed >= RESIDENT_HISTORY_HEAP_SOFT_LIMIT;
|
|
1527
|
+
|| residentChars >= RESIDENT_HISTORY_CHAR_SOFT_LIMIT;
|
|
1507
1528
|
if (shouldAggressivelyPrune) {
|
|
1508
1529
|
candidate = aggressivePruneMessages(candidate);
|
|
1509
1530
|
}
|
|
@@ -1525,9 +1546,15 @@ export class Agent {
|
|
|
1525
1546
|
}
|
|
1526
1547
|
}
|
|
1527
1548
|
appendMessage(message) {
|
|
1549
|
+
if (message.role === "assistant" && message.content) {
|
|
1550
|
+
message.content = sanitizeInternalReminderBlocks(message.content);
|
|
1551
|
+
}
|
|
1528
1552
|
if (message.role === "assistant" && message.reasoning) {
|
|
1529
1553
|
message.reasoning = sanitizeInternalReminderBlocks(message.reasoning);
|
|
1530
1554
|
}
|
|
1555
|
+
if (message.role === "assistant" && message.providerMetadata) {
|
|
1556
|
+
message.providerMetadata = sanitizeAssistantProviderMetadata(message.providerMetadata);
|
|
1557
|
+
}
|
|
1531
1558
|
this.messages.push(message);
|
|
1532
1559
|
traceEvent("agent_message_append", {
|
|
1533
1560
|
message: summarizeTraceMessage(message),
|
|
@@ -1605,7 +1632,22 @@ export class Agent {
|
|
|
1605
1632
|
metadata: { kind: "security", reason: "args_corrupt" },
|
|
1606
1633
|
};
|
|
1607
1634
|
}
|
|
1608
|
-
|
|
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);
|
|
1609
1651
|
if (missingRequired.length > 0) {
|
|
1610
1652
|
return {
|
|
1611
1653
|
content: `Error: Tool "${toolCall.name}" was called without required argument${missingRequired.length === 1 ? "" : "s"}: ${missingRequired.map((name) => `"${name}"`).join(", ")}. ` +
|
|
@@ -1616,7 +1658,7 @@ export class Agent {
|
|
|
1616
1658
|
};
|
|
1617
1659
|
}
|
|
1618
1660
|
try {
|
|
1619
|
-
return await tool.execute(
|
|
1661
|
+
return await tool.execute(preparedArgs, {
|
|
1620
1662
|
cwd,
|
|
1621
1663
|
sessionID: this.sessionID,
|
|
1622
1664
|
abortSignal,
|
|
@@ -1682,6 +1724,89 @@ function estimateResidentChars(messages) {
|
|
|
1682
1724
|
}
|
|
1683
1725
|
return total;
|
|
1684
1726
|
}
|
|
1727
|
+
function appendProviderContentBlock(message, provider, block) {
|
|
1728
|
+
if (provider !== "anthropic")
|
|
1729
|
+
return;
|
|
1730
|
+
const current = message.providerMetadata?.anthropic?.contentBlocks ?? [];
|
|
1731
|
+
message.providerMetadata = {
|
|
1732
|
+
...message.providerMetadata,
|
|
1733
|
+
anthropic: {
|
|
1734
|
+
...message.providerMetadata?.anthropic,
|
|
1735
|
+
contentBlocks: [...current, cloneProviderRawContentBlock(block)],
|
|
1736
|
+
},
|
|
1737
|
+
};
|
|
1738
|
+
}
|
|
1739
|
+
function buildProviderRequestFingerprint(messages, tools, providerId, toolChoice) {
|
|
1740
|
+
const roleCounts = {};
|
|
1741
|
+
let contentChars = 0;
|
|
1742
|
+
let reasoningChars = 0;
|
|
1743
|
+
let toolResultChars = 0;
|
|
1744
|
+
let maxToolResultChars = 0;
|
|
1745
|
+
let assistantToolCalls = 0;
|
|
1746
|
+
let rawAnthropicBlocks = 0;
|
|
1747
|
+
let rawAnthropicThinkingBlocks = 0;
|
|
1748
|
+
let rawAnthropicSignatureChars = 0;
|
|
1749
|
+
for (const message of messages) {
|
|
1750
|
+
roleCounts[message.role] = (roleCounts[message.role] ?? 0) + 1;
|
|
1751
|
+
if (message.role === "assistant") {
|
|
1752
|
+
contentChars += message.content.length;
|
|
1753
|
+
reasoningChars += message.reasoning?.length ?? 0;
|
|
1754
|
+
assistantToolCalls += message.toolCalls?.length ?? 0;
|
|
1755
|
+
const blocks = message.providerMetadata?.anthropic?.contentBlocks ?? [];
|
|
1756
|
+
rawAnthropicBlocks += blocks.length;
|
|
1757
|
+
for (const block of blocks) {
|
|
1758
|
+
if (block.type === "thinking" || block.type === "redacted_thinking") {
|
|
1759
|
+
rawAnthropicThinkingBlocks += 1;
|
|
1760
|
+
}
|
|
1761
|
+
if (typeof block.signature === "string") {
|
|
1762
|
+
rawAnthropicSignatureChars += block.signature.length;
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
else if (message.role === "tool") {
|
|
1767
|
+
toolResultChars += message.content.length;
|
|
1768
|
+
maxToolResultChars = Math.max(maxToolResultChars, message.content.length);
|
|
1769
|
+
}
|
|
1770
|
+
else if (message.role === "user") {
|
|
1771
|
+
contentChars += typeof message.content === "string"
|
|
1772
|
+
? message.content.length
|
|
1773
|
+
: message.content.reduce((sum, part) => sum + (part.type === "text" ? part.text.length : part.image_url.url.length), 0);
|
|
1774
|
+
}
|
|
1775
|
+
else {
|
|
1776
|
+
contentChars += message.content.length;
|
|
1777
|
+
}
|
|
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");
|
|
1784
|
+
return {
|
|
1785
|
+
roleCounts,
|
|
1786
|
+
estimatedTokens: estimateContextTokens(messages, providerId),
|
|
1787
|
+
projectedJsonBytes: Buffer.byteLength(JSON.stringify(messages), "utf8"),
|
|
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,
|
|
1797
|
+
contentChars,
|
|
1798
|
+
reasoningChars,
|
|
1799
|
+
toolResultChars,
|
|
1800
|
+
maxToolResultChars,
|
|
1801
|
+
assistantToolCalls,
|
|
1802
|
+
rawAnthropicBlocks,
|
|
1803
|
+
rawAnthropicThinkingBlocks,
|
|
1804
|
+
rawAnthropicSignatureChars,
|
|
1805
|
+
};
|
|
1806
|
+
}
|
|
1807
|
+
function cloneProviderRawContentBlock(block) {
|
|
1808
|
+
return JSON.parse(JSON.stringify(block));
|
|
1809
|
+
}
|
|
1685
1810
|
function throwIfAborted(signal) {
|
|
1686
1811
|
if (!signal?.aborted)
|
|
1687
1812
|
return;
|
package/dist/context/budget.js
CHANGED
|
@@ -20,6 +20,7 @@ export function estimateMessageTokens(message, providerId) {
|
|
|
20
20
|
case "assistant":
|
|
21
21
|
return estimate(message.content)
|
|
22
22
|
+ estimate(message.reasoning ?? "")
|
|
23
|
+
+ estimateProviderMetadataOverhead(message.providerMetadata, providerId)
|
|
23
24
|
+ (message.toolCalls?.reduce((sum, toolCall) => sum + estimate(toolCall.arguments) + 12, 0) ?? 0)
|
|
24
25
|
+ 8;
|
|
25
26
|
case "user":
|
|
@@ -34,6 +35,20 @@ export function estimateMessageTokens(message, providerId) {
|
|
|
34
35
|
}, 8);
|
|
35
36
|
}
|
|
36
37
|
}
|
|
38
|
+
function estimateProviderMetadataOverhead(metadata, providerId) {
|
|
39
|
+
const blocks = metadata?.anthropic?.contentBlocks;
|
|
40
|
+
if (!blocks || blocks.length === 0)
|
|
41
|
+
return 0;
|
|
42
|
+
const estimate = (text) => estimateTextTokens(text, providerId);
|
|
43
|
+
return blocks.reduce((sum, block) => {
|
|
44
|
+
let overhead = 0;
|
|
45
|
+
if (typeof block.signature === "string")
|
|
46
|
+
overhead += estimate(block.signature);
|
|
47
|
+
if (block.type === "redacted_thinking" && typeof block.data === "string")
|
|
48
|
+
overhead += estimate(block.data);
|
|
49
|
+
return sum + overhead;
|
|
50
|
+
}, 0);
|
|
51
|
+
}
|
|
37
52
|
export function estimateContextTokens(messages, providerId) {
|
|
38
53
|
return messages.reduce((sum, message) => sum + estimateMessageTokens(message, providerId), 0);
|
|
39
54
|
}
|
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
|
package/dist/debug-trace.js
CHANGED
|
@@ -112,6 +112,7 @@ export function summarizeTraceMessage(message) {
|
|
|
112
112
|
content: summarizeTraceText(message.content),
|
|
113
113
|
reasoning: summarizeTraceText(message.reasoning ?? ""),
|
|
114
114
|
error: message.error,
|
|
115
|
+
providerMetadata: summarizeAssistantProviderMetadata(message),
|
|
115
116
|
toolCalls: message.toolCalls?.map((call) => ({
|
|
116
117
|
id: call.id,
|
|
117
118
|
name: call.name,
|
|
@@ -141,6 +142,19 @@ export function summarizeTraceMessage(message) {
|
|
|
141
142
|
content: summarizeTraceText(message.content),
|
|
142
143
|
};
|
|
143
144
|
}
|
|
145
|
+
function summarizeAssistantProviderMetadata(message) {
|
|
146
|
+
const blocks = message.providerMetadata?.anthropic?.contentBlocks;
|
|
147
|
+
if (!blocks || blocks.length === 0)
|
|
148
|
+
return undefined;
|
|
149
|
+
return {
|
|
150
|
+
anthropic: {
|
|
151
|
+
contentBlocks: blocks.length,
|
|
152
|
+
thinkingBlocks: blocks.filter((block) => block.type === "thinking" || block.type === "redacted_thinking").length,
|
|
153
|
+
signatureChars: blocks.reduce((sum, block) => sum + (typeof block.signature === "string" ? block.signature.length : 0), 0),
|
|
154
|
+
types: blocks.map((block) => block.type).slice(0, 32),
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
144
158
|
export function summarizeTraceToolResult(result) {
|
|
145
159
|
return {
|
|
146
160
|
content: summarizeTraceText(result.content),
|
|
@@ -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/feishu/serve.js
CHANGED
|
@@ -96,6 +96,7 @@ export async function serveFeishu(opts = {}) {
|
|
|
96
96
|
apiKey,
|
|
97
97
|
baseURL,
|
|
98
98
|
promptCacheKey,
|
|
99
|
+
protocol: providerRegistry.getConfigured().find((provider) => provider.id === providerId)?.protocol,
|
|
99
100
|
openAICodexAuth: providerRegistry.createOpenAICodexAuthAdapter(providerId),
|
|
100
101
|
});
|
|
101
102
|
const createProviderForRoute = async (route, promptCacheKey) => {
|
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";
|
|
@@ -83,6 +84,7 @@ async function main() {
|
|
|
83
84
|
baseURL: defaultProvider.baseURL,
|
|
84
85
|
thinkingLevel: args.thinkingLevel,
|
|
85
86
|
promptCacheKey: sessionPromptCacheKey,
|
|
87
|
+
protocol: defaultProvider.protocol,
|
|
86
88
|
openAICodexAuth: registry.createOpenAICodexAuthAdapter(defaultProvider.id),
|
|
87
89
|
})
|
|
88
90
|
: createUnavailableProvider(unavailableProviderMessage);
|
|
@@ -92,6 +94,7 @@ async function main() {
|
|
|
92
94
|
baseURL,
|
|
93
95
|
thinkingLevel: args.thinkingLevel,
|
|
94
96
|
promptCacheKey: sessionPromptCacheKey,
|
|
97
|
+
protocol: registry.getConfigured().find((provider) => provider.id === providerId)?.protocol,
|
|
95
98
|
openAICodexAuth: registry.createOpenAICodexAuthAdapter(providerId),
|
|
96
99
|
});
|
|
97
100
|
const createProviderForRoute = async (route) => {
|
|
@@ -242,18 +245,19 @@ async function main() {
|
|
|
242
245
|
}
|
|
243
246
|
sessionPromptCacheKey = sessionManager.getOrCreatePromptCacheKey();
|
|
244
247
|
// Model resolution:
|
|
245
|
-
// 1. Session metadata
|
|
248
|
+
// 1. CLI flag 2. Session metadata 3. User-configured default model
|
|
246
249
|
// No implicit built-in model fallback.
|
|
247
250
|
const fallbackProviderId = defaultProvider?.id || "";
|
|
248
251
|
const sessionModel = sessionManager?.getMetadata().model;
|
|
249
|
-
const
|
|
252
|
+
const defaultModel = userConfig.getDefaultModel();
|
|
250
253
|
const sessionThinkingLevel = sessionManager?.getMetadata().thinkingLevel;
|
|
251
254
|
const configuredThinkingLevel = userConfig.getDefaultThinkingLevel();
|
|
252
|
-
const normalizedConfiguredModel =
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
255
|
+
const normalizedConfiguredModel = resolveConfiguredModel({
|
|
256
|
+
cliModel: args.model,
|
|
257
|
+
sessionModel,
|
|
258
|
+
defaultModel,
|
|
259
|
+
fallbackProviderId,
|
|
260
|
+
});
|
|
257
261
|
const { providerId: effectiveProviderId, modelId: effectiveModelId } = normalizedConfiguredModel
|
|
258
262
|
? decodeModel(normalizedConfiguredModel)
|
|
259
263
|
: { providerId: undefined, modelId: "" };
|
|
@@ -286,7 +290,7 @@ async function main() {
|
|
|
286
290
|
thinkingLevel: initialThinkingLevel,
|
|
287
291
|
mode: initialMode,
|
|
288
292
|
workingDir: args.cwd,
|
|
289
|
-
tools
|
|
293
|
+
...buildToolPromptOptions(tools.filter((tool) => !tool.deferred)),
|
|
290
294
|
memoryPrompt,
|
|
291
295
|
});
|
|
292
296
|
const traceInfo = configureDebugTrace({
|
package/dist/model-catalog.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import type { ReasoningEffort } from "./types.js";
|
|
2
|
+
export type ProviderProtocol = "openai-chat" | "anthropic-messages";
|
|
2
3
|
export interface BuiltinProviderDefinition {
|
|
3
4
|
id: string;
|
|
4
5
|
name: string;
|
|
5
6
|
baseURL: string;
|
|
7
|
+
protocol?: ProviderProtocol;
|
|
8
|
+
hidden?: boolean;
|
|
6
9
|
supportsOAuth?: boolean;
|
|
7
10
|
}
|
|
8
11
|
export interface BuiltinModelDefinition {
|