@bubblebrain-ai/bubble 0.0.17 → 0.0.19
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.d.ts +1 -0
- package/dist/network/chatgpt-transport.js +123 -16
- 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
|
+
}
|
|
@@ -13,4 +13,5 @@ export declare function createChatGptFetch(options?: ChatGptFetchOptions): ChatG
|
|
|
13
13
|
export declare function createChatGptDispatcher(env?: NodeJS.ProcessEnv, input?: RequestInfo | URL): Dispatcher | undefined;
|
|
14
14
|
export declare function withChatGptNetworkOptions(input: RequestInfo | URL, init: RequestInit | undefined, env?: NodeJS.ProcessEnv, dispatcher?: Dispatcher | undefined): RequestInitWithDispatcher;
|
|
15
15
|
export declare function normalizeChatGptNetworkError(error: unknown, env?: NodeJS.ProcessEnv): Error;
|
|
16
|
+
export declare function parseMacSystemProxyForUrl(output: string, url: URL): string | undefined;
|
|
16
17
|
export {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
1
2
|
import { readFileSync } from "node:fs";
|
|
2
3
|
import { delimiter } from "node:path";
|
|
3
4
|
import { rootCertificates } from "node:tls";
|
|
@@ -19,9 +20,8 @@ export function getChatGptFetch(env = process.env) {
|
|
|
19
20
|
export function createChatGptFetch(options = {}) {
|
|
20
21
|
const env = options.env ?? process.env;
|
|
21
22
|
const fetchImpl = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
|
|
22
|
-
const dispatcher = createChatGptDispatcher(env);
|
|
23
23
|
return async (input, init) => {
|
|
24
|
-
const requestInit = withChatGptNetworkOptions(input, init, env
|
|
24
|
+
const requestInit = withChatGptNetworkOptions(input, init, env);
|
|
25
25
|
try {
|
|
26
26
|
return await fetchImpl(input, requestInit);
|
|
27
27
|
}
|
|
@@ -34,9 +34,9 @@ export function createChatGptDispatcher(env = process.env, input) {
|
|
|
34
34
|
if (isBunRuntime())
|
|
35
35
|
return undefined;
|
|
36
36
|
const ca = loadExtraCaCertificates(env);
|
|
37
|
-
if (!hasProxyEnv(env) && ca.length === 0)
|
|
38
|
-
return undefined;
|
|
39
37
|
const proxy = input ? nodeProxyForUrl(input, env) : defaultNodeProxy(env);
|
|
38
|
+
if (!proxy && ca.length === 0)
|
|
39
|
+
return undefined;
|
|
40
40
|
const caOptions = ca.length > 0 ? { ca: [...rootCertificates, ...ca] } : undefined;
|
|
41
41
|
if (proxy) {
|
|
42
42
|
return new ProxyAgent({
|
|
@@ -73,41 +73,144 @@ export function normalizeChatGptNetworkError(error, env = process.env) {
|
|
|
73
73
|
: "This looks like a proxy or network transport failure.",
|
|
74
74
|
hasProxyEnv(env)
|
|
75
75
|
? "Bubble is using proxy environment variables for ChatGPT requests. Make sure NO_PROXY includes localhost,127.0.0.1."
|
|
76
|
-
:
|
|
76
|
+
: hasMacSystemProxy(env)
|
|
77
|
+
? "Bubble detected the macOS system proxy for ChatGPT requests. If it is stale, restart your proxy client or set BUBBLE_DISABLE_SYSTEM_PROXY=1 and configure HTTPS_PROXY explicitly."
|
|
78
|
+
: "If your network requires a proxy, set HTTPS_PROXY or HTTP_PROXY, and set NO_PROXY=localhost,127.0.0.1.",
|
|
77
79
|
"Do not disable TLS verification with NODE_TLS_REJECT_UNAUTHORIZED=0.",
|
|
78
80
|
`Original error: ${firstMeaningfulErrorMessage(error) || "unknown network error"}`,
|
|
79
81
|
].join(" ");
|
|
80
82
|
return new Error(message, { cause: error });
|
|
81
83
|
}
|
|
82
84
|
function hasProxyEnv(env) {
|
|
83
|
-
return Boolean(env.
|
|
85
|
+
return Boolean(env.BUBBLE_CHATGPT_PROXY ||
|
|
86
|
+
env.bubble_chatgpt_proxy ||
|
|
87
|
+
env.HTTPS_PROXY ||
|
|
88
|
+
env.https_proxy ||
|
|
89
|
+
env.HTTP_PROXY ||
|
|
90
|
+
env.http_proxy ||
|
|
91
|
+
env.ALL_PROXY ||
|
|
92
|
+
env.all_proxy);
|
|
84
93
|
}
|
|
85
94
|
function isBunRuntime() {
|
|
86
95
|
return typeof globalThis.Bun !== "undefined";
|
|
87
96
|
}
|
|
88
97
|
function bunProxyForUrl(input, env) {
|
|
98
|
+
return proxyForUrl(input, env);
|
|
99
|
+
}
|
|
100
|
+
function nodeProxyForUrl(input, env) {
|
|
101
|
+
return proxyForUrl(input, env);
|
|
102
|
+
}
|
|
103
|
+
function proxyForUrl(input, env) {
|
|
89
104
|
const url = urlFromInput(input);
|
|
90
105
|
if (!url || shouldBypassProxy(url, env))
|
|
91
106
|
return undefined;
|
|
107
|
+
const explicit = explicitProxyForUrl(url, env);
|
|
108
|
+
return explicit ?? macSystemProxyForUrl(url, env);
|
|
109
|
+
}
|
|
110
|
+
function defaultNodeProxy(env) {
|
|
111
|
+
return (env.BUBBLE_CHATGPT_PROXY ??
|
|
112
|
+
env.bubble_chatgpt_proxy ??
|
|
113
|
+
env.HTTPS_PROXY ??
|
|
114
|
+
env.https_proxy ??
|
|
115
|
+
env.HTTP_PROXY ??
|
|
116
|
+
env.http_proxy ??
|
|
117
|
+
env.ALL_PROXY ??
|
|
118
|
+
env.all_proxy ??
|
|
119
|
+
macSystemProxyForUrl(new URL("https://chatgpt.com/"), env));
|
|
120
|
+
}
|
|
121
|
+
function explicitProxyForUrl(url, env) {
|
|
122
|
+
const chatGptProxy = env.BUBBLE_CHATGPT_PROXY ?? env.bubble_chatgpt_proxy;
|
|
123
|
+
if (chatGptProxy)
|
|
124
|
+
return chatGptProxy;
|
|
92
125
|
const allProxy = env.ALL_PROXY ?? env.all_proxy;
|
|
93
126
|
if (url.protocol === "https:")
|
|
94
127
|
return env.HTTPS_PROXY ?? env.https_proxy ?? allProxy;
|
|
95
128
|
if (url.protocol === "http:")
|
|
96
129
|
return env.HTTP_PROXY ?? env.http_proxy ?? allProxy;
|
|
130
|
+
return allProxy;
|
|
131
|
+
}
|
|
132
|
+
function hasMacSystemProxy(env) {
|
|
133
|
+
return Boolean(macSystemProxyForUrl(new URL("https://chatgpt.com/"), env));
|
|
134
|
+
}
|
|
135
|
+
function macSystemProxyForUrl(url, env) {
|
|
136
|
+
if (process.platform !== "darwin" || truthyEnv(env.BUBBLE_DISABLE_SYSTEM_PROXY))
|
|
137
|
+
return undefined;
|
|
138
|
+
const output = readMacSystemProxy();
|
|
139
|
+
return output ? parseMacSystemProxyForUrl(output, url) : undefined;
|
|
140
|
+
}
|
|
141
|
+
export function parseMacSystemProxyForUrl(output, url) {
|
|
142
|
+
if (macProxyExceptionMatches(output, url.hostname, url.port))
|
|
143
|
+
return undefined;
|
|
144
|
+
const values = parseScutilProxyValues(output);
|
|
145
|
+
if (url.protocol === "https:") {
|
|
146
|
+
return formatMacProxy(values.HTTPSEnable, values.HTTPSProxy, values.HTTPSPort);
|
|
147
|
+
}
|
|
148
|
+
if (url.protocol === "http:") {
|
|
149
|
+
return formatMacProxy(values.HTTPEnable, values.HTTPProxy, values.HTTPPort);
|
|
150
|
+
}
|
|
97
151
|
return undefined;
|
|
98
152
|
}
|
|
99
|
-
function
|
|
100
|
-
const
|
|
101
|
-
|
|
153
|
+
function parseScutilProxyValues(output) {
|
|
154
|
+
const values = {};
|
|
155
|
+
for (const line of output.split(/\r?\n/)) {
|
|
156
|
+
const match = line.match(/^\s*([A-Za-z]+(?:Enable|Proxy|Port))\s*:\s*(.+?)\s*$/);
|
|
157
|
+
if (match)
|
|
158
|
+
values[match[1]] = match[2];
|
|
159
|
+
}
|
|
160
|
+
return values;
|
|
161
|
+
}
|
|
162
|
+
function formatMacProxy(enabled, host, port) {
|
|
163
|
+
if (enabled !== "1" || !host || !port)
|
|
102
164
|
return undefined;
|
|
103
|
-
if (
|
|
104
|
-
return
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
return defaultNodeProxy(env);
|
|
165
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(host))
|
|
166
|
+
return host;
|
|
167
|
+
const normalizedHost = host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
|
|
168
|
+
return `http://${normalizedHost}:${port}`;
|
|
108
169
|
}
|
|
109
|
-
function
|
|
110
|
-
|
|
170
|
+
function macProxyExceptionMatches(output, hostname, port) {
|
|
171
|
+
const exceptions = parseMacProxyExceptions(output);
|
|
172
|
+
return exceptions.some((entry) => noProxyEntryMatches(entry.toLowerCase(), hostname.toLowerCase(), port));
|
|
173
|
+
}
|
|
174
|
+
function parseMacProxyExceptions(output) {
|
|
175
|
+
const exceptions = [];
|
|
176
|
+
let inExceptions = false;
|
|
177
|
+
for (const line of output.split(/\r?\n/)) {
|
|
178
|
+
if (line.includes("ExceptionsList")) {
|
|
179
|
+
inExceptions = true;
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (!inExceptions)
|
|
183
|
+
continue;
|
|
184
|
+
if (/^\s*}\s*$/.test(line))
|
|
185
|
+
break;
|
|
186
|
+
const match = line.match(/^\s*\d+\s*:\s*(.+?)\s*$/);
|
|
187
|
+
if (match)
|
|
188
|
+
exceptions.push(match[1].trim());
|
|
189
|
+
}
|
|
190
|
+
return exceptions;
|
|
191
|
+
}
|
|
192
|
+
let cachedMacSystemProxy;
|
|
193
|
+
function readMacSystemProxy() {
|
|
194
|
+
const now = Date.now();
|
|
195
|
+
if (cachedMacSystemProxy && now - cachedMacSystemProxy.checkedAtMs < 5_000) {
|
|
196
|
+
return cachedMacSystemProxy.output;
|
|
197
|
+
}
|
|
198
|
+
let output;
|
|
199
|
+
try {
|
|
200
|
+
output = execFileSync("scutil", ["--proxy"], {
|
|
201
|
+
encoding: "utf-8",
|
|
202
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
203
|
+
timeout: 1_000,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
output = undefined;
|
|
208
|
+
}
|
|
209
|
+
cachedMacSystemProxy = { checkedAtMs: now, output };
|
|
210
|
+
return output;
|
|
211
|
+
}
|
|
212
|
+
function truthyEnv(value) {
|
|
213
|
+
return /^(1|true|yes|on)$/i.test(value?.trim() ?? "");
|
|
111
214
|
}
|
|
112
215
|
function bunExtraCaFiles(env) {
|
|
113
216
|
const bun = globalThis.Bun;
|
|
@@ -177,6 +280,9 @@ function networkEnvSignature(env) {
|
|
|
177
280
|
env.https_proxy,
|
|
178
281
|
env.ALL_PROXY,
|
|
179
282
|
env.all_proxy,
|
|
283
|
+
env.BUBBLE_CHATGPT_PROXY,
|
|
284
|
+
env.bubble_chatgpt_proxy,
|
|
285
|
+
env.BUBBLE_DISABLE_SYSTEM_PROXY,
|
|
180
286
|
env.NO_PROXY,
|
|
181
287
|
env.no_proxy,
|
|
182
288
|
env.NODE_EXTRA_CA_CERTS,
|
|
@@ -195,6 +301,7 @@ function isChatGptNetworkErrorText(text) {
|
|
|
195
301
|
/\bEPIPE\b/i,
|
|
196
302
|
/\bUND_ERR_/i,
|
|
197
303
|
/socket hang up/i,
|
|
304
|
+
/Unable to connect\. Is the computer able to access the url\?/i,
|
|
198
305
|
/certificate/i,
|
|
199
306
|
/unable to verify/i,
|
|
200
307
|
/self[- ]signed/i,
|
|
@@ -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.",
|