@bubblebrain-ai/bubble 0.0.15 → 0.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -0
- package/dist/agent/discovery-barrier.d.ts +21 -0
- package/dist/agent/discovery-barrier.js +173 -0
- package/dist/agent/internal-reminder-sanitizer.d.ts +9 -0
- package/dist/agent/internal-reminder-sanitizer.js +198 -0
- package/dist/agent/task-classifier.js +23 -5
- package/dist/agent.js +215 -30
- package/dist/context/budget.js +15 -0
- package/dist/context/projector.js +4 -3
- package/dist/debug-trace.js +14 -0
- package/dist/feishu/serve.js +1 -0
- package/dist/main.js +2 -0
- package/dist/model-catalog.d.ts +3 -0
- package/dist/model-catalog.js +44 -0
- package/dist/model-config.d.ts +3 -0
- package/dist/model-config.js +3 -0
- package/dist/model-pricing.d.ts +3 -2
- package/dist/model-pricing.js +8 -0
- package/dist/network/chatgpt-transport.d.ts +16 -0
- package/dist/network/chatgpt-transport.js +240 -0
- package/dist/oauth/openai-codex.d.ts +7 -2
- package/dist/oauth/openai-codex.js +7 -4
- package/dist/orchestrator/default-hooks.js +13 -2
- package/dist/orchestrator/hooks.d.ts +2 -0
- package/dist/prompt/compose.js +1 -1
- package/dist/prompt/reminders.js +3 -3
- package/dist/prompt/runtime.js +1 -0
- package/dist/provider-anthropic.d.ts +77 -0
- package/dist/provider-anthropic.js +544 -0
- package/dist/provider-openai-codex.d.ts +3 -0
- package/dist/provider-openai-codex.js +11 -2
- 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 +23 -0
- package/dist/provider.d.ts +4 -1
- package/dist/provider.js +119 -40
- package/dist/reasoning-debug.js +4 -1
- package/dist/session-log.js +17 -2
- package/dist/slash-commands/commands.js +4 -2
- package/dist/stats/usage.d.ts +4 -0
- package/dist/stats/usage.js +48 -11
- package/dist/tools/glob.js +3 -0
- package/dist/tools/grep.js +7 -0
- package/dist/tui/run.js +22 -12
- package/dist/tui-ink/app.js +3 -0
- package/dist/tui-ink/message-list.js +6 -3
- package/dist/tui-opentui/app.js +3 -0
- package/dist/tui-opentui/message-list.js +6 -3
- package/dist/types.d.ts +14 -1
- package/package.json +2 -1
|
@@ -3,7 +3,7 @@ export { getAvailableThinkingLevels, getDefaultThinkingLevel, normalizeThinkingL
|
|
|
3
3
|
export interface ProviderRequestConfig {
|
|
4
4
|
effectiveThinkingLevel: ThinkingLevel;
|
|
5
5
|
reasoningEffort?: ThinkingLevel;
|
|
6
|
-
reasoningContentEcho?: "tool_calls" | "all" | "none";
|
|
6
|
+
reasoningContentEcho?: "tool_calls" | "all" | "none" | "minimax";
|
|
7
7
|
parallelToolCalls?: boolean;
|
|
8
8
|
maxTokens?: number;
|
|
9
9
|
extraBody?: Record<string, unknown>;
|
|
@@ -4,6 +4,7 @@ const MOONSHOT_PROVIDER_IDS = new Set(["moonshot-cn", "moonshot-intl", "kimi-for
|
|
|
4
4
|
const KIMI_K25_FAMILY = new Set(["kimi-k2.5", "k2.6-code-preview", "kimi-k2.6"]);
|
|
5
5
|
const KIMI_THINKING_FAMILY = new Set(["kimi-k2-thinking", "kimi-k2-thinking-turbo"]);
|
|
6
6
|
const KIMI_K26_DEFAULT_MAX_TOKENS = 32768;
|
|
7
|
+
const MINIMAX_M3_FAMILY = new Set(["MiniMax-M3"]);
|
|
7
8
|
function isFireworksKimi(providerId, modelId) {
|
|
8
9
|
const model = modelId.toLowerCase();
|
|
9
10
|
return providerId === "fireworks" && (model.includes("kimi")
|
|
@@ -36,6 +37,28 @@ export function resolveProviderRequestConfig(providerId, modelId, requestedLevel
|
|
|
36
37
|
},
|
|
37
38
|
};
|
|
38
39
|
}
|
|
40
|
+
if (providerId === "stepfun") {
|
|
41
|
+
return {
|
|
42
|
+
effectiveThinkingLevel,
|
|
43
|
+
reasoningContentEcho: "none",
|
|
44
|
+
extraBody: effectiveThinkingLevel === "off"
|
|
45
|
+
? undefined
|
|
46
|
+
: { reasoning_effort: effectiveThinkingLevel },
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
if (providerId === "minimax" || providerId === "minimax-openai") {
|
|
50
|
+
const extraBody = { reasoning_split: true };
|
|
51
|
+
if (MINIMAX_M3_FAMILY.has(modelId)) {
|
|
52
|
+
extraBody.thinking = {
|
|
53
|
+
type: effectiveThinkingLevel === "off" ? "disabled" : "adaptive",
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
effectiveThinkingLevel,
|
|
58
|
+
reasoningContentEcho: "minimax",
|
|
59
|
+
extraBody,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
39
62
|
// Zhipu/Z.AI OpenAI-compatible endpoints expose reasoning via a provider-specific
|
|
40
63
|
// `thinking` block rather than OpenAI's `reasoning_effort` shape.
|
|
41
64
|
if (["zhipuai", "zhipuai-coding-plan", "zai", "zai-coding-plan"].includes(providerId)) {
|
package/dist/provider.d.ts
CHANGED
|
@@ -4,12 +4,14 @@
|
|
|
4
4
|
* Works with OpenRouter, OpenAI, DeepSeek, Google, Groq, Together, and local OpenAI-compatible endpoints.
|
|
5
5
|
*/
|
|
6
6
|
import { type OpenAICodexAuthAdapter } from "./provider-openai-codex.js";
|
|
7
|
+
import type { ProviderProtocol } from "./model-catalog.js";
|
|
7
8
|
import type { Provider, ProviderMessage, StreamChunk, ThinkingLevel } from "./types.js";
|
|
8
|
-
type ReasoningContentEcho = "tool_calls" | "all" | "none";
|
|
9
|
+
type ReasoningContentEcho = "tool_calls" | "all" | "none" | "minimax";
|
|
9
10
|
export type ToolArgsMergeMode = "delta" | "snapshot";
|
|
10
11
|
export interface TranslateOpenAIStreamOptions {
|
|
11
12
|
toolArgsMergeMode?: ToolArgsMergeMode;
|
|
12
13
|
reasoningMergeMode?: ToolArgsMergeMode;
|
|
14
|
+
textMergeMode?: ToolArgsMergeMode;
|
|
13
15
|
debugProviderId?: string;
|
|
14
16
|
debugModelId?: string;
|
|
15
17
|
}
|
|
@@ -24,6 +26,7 @@ export interface ProviderInstanceOptions {
|
|
|
24
26
|
thinkingLevel?: ThinkingLevel;
|
|
25
27
|
/** Stable per-session seed for provider prompt caches. */
|
|
26
28
|
promptCacheKey?: string;
|
|
29
|
+
protocol?: ProviderProtocol;
|
|
27
30
|
/** Dynamic OAuth access-token loader/refresh hook for ChatGPT Codex requests. */
|
|
28
31
|
openAICodexAuth?: OpenAICodexAuthAdapter;
|
|
29
32
|
}
|
package/dist/provider.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import OpenAI from "openai";
|
|
7
7
|
import { appendFileSync } from "node:fs";
|
|
8
|
+
import { createAnthropicMessagesProvider } from "./provider-anthropic.js";
|
|
8
9
|
import { createOpenAICodexProvider, isOpenAICodexBaseUrl } from "./provider-openai-codex.js";
|
|
9
10
|
import { createProviderProtocolArtifactFilter } from "./provider-artifacts.js";
|
|
10
11
|
import { resolveProviderRequestConfig } from "./provider-transform.js";
|
|
@@ -36,6 +37,15 @@ export function toChatCompletionsMessage(message, options = {}) {
|
|
|
36
37
|
// provider field, even when the original value is an empty string.
|
|
37
38
|
out.reasoning_content = message.reasoning ?? "";
|
|
38
39
|
}
|
|
40
|
+
if (reasoningContentEcho === "minimax" && message.reasoning) {
|
|
41
|
+
out.reasoning_details = [{
|
|
42
|
+
type: "reasoning.text",
|
|
43
|
+
id: "reasoning-text-1",
|
|
44
|
+
format: "MiniMax-response-v1",
|
|
45
|
+
index: 0,
|
|
46
|
+
text: message.reasoning,
|
|
47
|
+
}];
|
|
48
|
+
}
|
|
39
49
|
if (message.toolCalls && message.toolCalls.length > 0) {
|
|
40
50
|
out.tool_calls = message.toolCalls.map((tc) => ({
|
|
41
51
|
id: tc.id,
|
|
@@ -65,6 +75,9 @@ export function createUnavailableProvider(message) {
|
|
|
65
75
|
return { streamChat, complete };
|
|
66
76
|
}
|
|
67
77
|
export function createProviderInstance(options) {
|
|
78
|
+
if (resolveProviderProtocol(options) === "anthropic-messages") {
|
|
79
|
+
return createAnthropicMessagesProvider(options);
|
|
80
|
+
}
|
|
68
81
|
if (isOpenAICodexBaseUrl(options.baseURL)) {
|
|
69
82
|
return createOpenAICodexProvider({
|
|
70
83
|
...options,
|
|
@@ -96,8 +109,8 @@ export function createProviderInstance(options) {
|
|
|
96
109
|
tool_choice: tools && tools.length > 0 ? "auto" : undefined,
|
|
97
110
|
stream: true,
|
|
98
111
|
};
|
|
99
|
-
// DeepSeek only
|
|
100
|
-
if (options.providerId === "deepseek") {
|
|
112
|
+
// DeepSeek and MiniMax only emit final usage in streaming mode when this flag is set.
|
|
113
|
+
if (options.providerId === "deepseek" || isMiniMaxOpenAICompatible(options)) {
|
|
101
114
|
body.stream_options = { include_usage: true };
|
|
102
115
|
}
|
|
103
116
|
if (!requestConfig.omitTemperature) {
|
|
@@ -121,6 +134,7 @@ export function createProviderInstance(options) {
|
|
|
121
134
|
yield* translateOpenAIStream(stream, {
|
|
122
135
|
toolArgsMergeMode: resolveToolArgsMergeMode(options.providerId || "", options.baseURL),
|
|
123
136
|
reasoningMergeMode: resolveReasoningMergeMode(options.providerId || "", options.baseURL),
|
|
137
|
+
textMergeMode: resolveTextMergeMode(options.providerId || "", options.baseURL),
|
|
124
138
|
debugProviderId: options.providerId || "",
|
|
125
139
|
debugModelId: chatOptions.model,
|
|
126
140
|
});
|
|
@@ -153,6 +167,26 @@ export function createProviderInstance(options) {
|
|
|
153
167
|
}
|
|
154
168
|
return { streamChat, complete };
|
|
155
169
|
}
|
|
170
|
+
function resolveProviderProtocol(options) {
|
|
171
|
+
if (options.protocol)
|
|
172
|
+
return options.protocol;
|
|
173
|
+
const providerId = (options.providerId || "").toLowerCase();
|
|
174
|
+
const baseURL = options.baseURL.toLowerCase();
|
|
175
|
+
if (providerId === "anthropic"
|
|
176
|
+
|| providerId.endsWith("-anthropic")
|
|
177
|
+
|| baseURL.includes("/anthropic")) {
|
|
178
|
+
return "anthropic-messages";
|
|
179
|
+
}
|
|
180
|
+
return "openai-chat";
|
|
181
|
+
}
|
|
182
|
+
function isMiniMaxOpenAICompatible(options) {
|
|
183
|
+
const providerId = (options.providerId || "").toLowerCase();
|
|
184
|
+
const baseURL = options.baseURL.toLowerCase();
|
|
185
|
+
return providerId === "minimax-openai"
|
|
186
|
+
|| (providerId === "minimax" && !baseURL.includes("/anthropic"))
|
|
187
|
+
|| baseURL.includes("api.minimaxi.com/v1")
|
|
188
|
+
|| baseURL.includes("api.minimax.io/v1");
|
|
189
|
+
}
|
|
156
190
|
export function normalizeToolArgsDetailed(raw) {
|
|
157
191
|
const s = (raw ?? "").trim();
|
|
158
192
|
if (!s) {
|
|
@@ -210,6 +244,15 @@ function resolveReasoningMergeMode(providerId, baseURL) {
|
|
|
210
244
|
const url = baseURL.toLowerCase();
|
|
211
245
|
if (id === "fireworks" || url.includes("fireworks.ai"))
|
|
212
246
|
return "snapshot";
|
|
247
|
+
if (id === "minimax" || url.includes("api.minimaxi.com") || url.includes("api.minimax.io"))
|
|
248
|
+
return "snapshot";
|
|
249
|
+
return "delta";
|
|
250
|
+
}
|
|
251
|
+
function resolveTextMergeMode(providerId, baseURL) {
|
|
252
|
+
const id = providerId.toLowerCase();
|
|
253
|
+
const url = baseURL.toLowerCase();
|
|
254
|
+
if (id === "minimax" || url.includes("api.minimaxi.com") || url.includes("api.minimax.io"))
|
|
255
|
+
return "snapshot";
|
|
213
256
|
return "delta";
|
|
214
257
|
}
|
|
215
258
|
function extractBalancedJson(s, start) {
|
|
@@ -257,7 +300,9 @@ export async function* translateOpenAIStream(stream, options = {}) {
|
|
|
257
300
|
const textFilter = createProviderProtocolArtifactFilter();
|
|
258
301
|
const toolArgsMergeMode = options.toolArgsMergeMode ?? "delta";
|
|
259
302
|
const reasoningMergeMode = options.reasoningMergeMode ?? "delta";
|
|
303
|
+
const textMergeMode = options.textMergeMode ?? "delta";
|
|
260
304
|
let reasoningBuffer = "";
|
|
305
|
+
let textBuffer = "";
|
|
261
306
|
let rawChunkSeq = 0;
|
|
262
307
|
// DeepSeek (and some inference re-hosts) sometimes deliver reasoning twice:
|
|
263
308
|
// once via a dedicated `reasoning_content` / `thinking` field, and again
|
|
@@ -318,6 +363,7 @@ export async function* translateOpenAIStream(stream, options = {}) {
|
|
|
318
363
|
reasoning: summarizeDebugText(delta?.reasoning),
|
|
319
364
|
thinking: summarizeDebugText(delta?.thinking),
|
|
320
365
|
reasoningContent: summarizeDebugText(delta?.reasoning_content),
|
|
366
|
+
reasoningDetails: summarizeDebugText(extractReasoningDetailsText(delta?.reasoning_details)),
|
|
321
367
|
});
|
|
322
368
|
if (usage) {
|
|
323
369
|
yield {
|
|
@@ -325,8 +371,16 @@ export async function* translateOpenAIStream(stream, options = {}) {
|
|
|
325
371
|
usage: {
|
|
326
372
|
promptTokens: typeof usage.prompt_tokens === "number" ? usage.prompt_tokens : 0,
|
|
327
373
|
completionTokens: typeof usage.completion_tokens === "number" ? usage.completion_tokens : 0,
|
|
328
|
-
promptCacheHitTokens: typeof usage.prompt_cache_hit_tokens === "number"
|
|
329
|
-
|
|
374
|
+
promptCacheHitTokens: typeof usage.prompt_cache_hit_tokens === "number"
|
|
375
|
+
? usage.prompt_cache_hit_tokens
|
|
376
|
+
: typeof usage.prompt_tokens_details?.cached_tokens === "number"
|
|
377
|
+
? usage.prompt_tokens_details.cached_tokens
|
|
378
|
+
: undefined,
|
|
379
|
+
promptCacheMissTokens: typeof usage.prompt_cache_miss_tokens === "number"
|
|
380
|
+
? usage.prompt_cache_miss_tokens
|
|
381
|
+
: typeof usage.prompt_tokens_details?.cached_tokens === "number" && typeof usage.prompt_tokens === "number"
|
|
382
|
+
? Math.max(0, usage.prompt_tokens - usage.prompt_tokens_details.cached_tokens)
|
|
383
|
+
: undefined,
|
|
330
384
|
reasoningTokens: typeof usage.completion_tokens_details?.reasoning_tokens === "number"
|
|
331
385
|
? usage.completion_tokens_details.reasoning_tokens
|
|
332
386
|
: undefined,
|
|
@@ -334,14 +388,21 @@ export async function* translateOpenAIStream(stream, options = {}) {
|
|
|
334
388
|
},
|
|
335
389
|
};
|
|
336
390
|
}
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
391
|
+
const reasoningDetails = extractReasoningDetailsText(delta?.reasoning_details);
|
|
392
|
+
const reasoningField = reasoningDetails !== undefined
|
|
393
|
+
? "reasoning_details"
|
|
394
|
+
: delta?.reasoning !== undefined
|
|
395
|
+
? "reasoning"
|
|
396
|
+
: delta?.thinking !== undefined
|
|
397
|
+
? "thinking"
|
|
398
|
+
: delta?.reasoning_content !== undefined
|
|
399
|
+
? "reasoning_content"
|
|
400
|
+
: undefined;
|
|
401
|
+
const reasoning = reasoningDetails !== undefined
|
|
402
|
+
? reasoningDetails
|
|
403
|
+
: reasoningField
|
|
404
|
+
? delta[reasoningField]
|
|
405
|
+
: undefined;
|
|
345
406
|
if (reasoning) {
|
|
346
407
|
hasDedicatedReasoningChannel = true;
|
|
347
408
|
const merged = mergeStreamingText(reasoningBuffer, reasoning, reasoningMergeMode);
|
|
@@ -362,36 +423,41 @@ export async function* translateOpenAIStream(stream, options = {}) {
|
|
|
362
423
|
}
|
|
363
424
|
}
|
|
364
425
|
if (delta?.content) {
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
426
|
+
const mergedContent = mergeStreamingText(textBuffer, delta.content, textMergeMode);
|
|
427
|
+
textBuffer = mergedContent.args;
|
|
428
|
+
const content = mergedContent.delta;
|
|
429
|
+
if (content) {
|
|
430
|
+
const thinkMatch = content.match(/<think>([\s\S]*?)<\/think>/);
|
|
431
|
+
if (thinkMatch) {
|
|
432
|
+
if (thinkMatch[1] && !hasDedicatedReasoningChannel) {
|
|
433
|
+
const merged = mergeStreamingText(reasoningBuffer, thinkMatch[1], reasoningMergeMode);
|
|
434
|
+
reasoningBuffer = merged.args;
|
|
435
|
+
debugReasoningStream({
|
|
436
|
+
stage: "provider_emit",
|
|
437
|
+
providerId: options.debugProviderId,
|
|
438
|
+
modelId: options.debugModelId,
|
|
439
|
+
chunkSeq: rawChunkSeq,
|
|
440
|
+
source: "content_think",
|
|
441
|
+
mergeMode: reasoningMergeMode,
|
|
442
|
+
suppressed: !merged.delta,
|
|
443
|
+
emitted: summarizeDebugText(merged.delta),
|
|
444
|
+
buffer: summarizeDebugText(reasoningBuffer),
|
|
445
|
+
});
|
|
446
|
+
if (merged.delta) {
|
|
447
|
+
yield { type: "reasoning_delta", content: merged.delta };
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
const remaining = content.replace(/<think>[\s\S]*?<\/think>/, "");
|
|
451
|
+
const cleaned = textFilter.push(remaining);
|
|
452
|
+
if (cleaned) {
|
|
453
|
+
yield { type: "text", content: cleaned };
|
|
383
454
|
}
|
|
384
455
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
}
|
|
391
|
-
else {
|
|
392
|
-
const cleaned = textFilter.push(delta.content);
|
|
393
|
-
if (cleaned) {
|
|
394
|
-
yield { type: "text", content: cleaned };
|
|
456
|
+
else {
|
|
457
|
+
const cleaned = textFilter.push(content);
|
|
458
|
+
if (cleaned) {
|
|
459
|
+
yield { type: "text", content: cleaned };
|
|
460
|
+
}
|
|
395
461
|
}
|
|
396
462
|
}
|
|
397
463
|
}
|
|
@@ -435,6 +501,19 @@ export async function* translateOpenAIStream(stream, options = {}) {
|
|
|
435
501
|
}
|
|
436
502
|
yield* flushToolCalls();
|
|
437
503
|
}
|
|
504
|
+
function extractReasoningDetailsText(value) {
|
|
505
|
+
if (!value)
|
|
506
|
+
return undefined;
|
|
507
|
+
const details = Array.isArray(value) ? value : [value];
|
|
508
|
+
const parts = details.flatMap((item) => {
|
|
509
|
+
if (!item || typeof item !== "object")
|
|
510
|
+
return [];
|
|
511
|
+
const record = item;
|
|
512
|
+
const text = record.text ?? record.thinking ?? record.content;
|
|
513
|
+
return typeof text === "string" ? [text] : [];
|
|
514
|
+
});
|
|
515
|
+
return parts.length > 0 ? parts.join("") : undefined;
|
|
516
|
+
}
|
|
438
517
|
function mergeToolArgumentDelta(current, incoming, mode) {
|
|
439
518
|
if (!current) {
|
|
440
519
|
debugToolArgs({ stage: "merge", branch: "empty-current", current, incoming, args: incoming, delta: incoming });
|
package/dist/reasoning-debug.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { appendFileSync, mkdirSync } from "node:fs";
|
|
3
3
|
import { dirname } from "node:path";
|
|
4
|
+
import { sanitizeInternalReminderBlocks } from "./agent/internal-reminder-sanitizer.js";
|
|
4
5
|
const DEBUG_PATH = process.env.BUBBLE_DEBUG_REASONING_STREAM?.trim();
|
|
5
6
|
const INCLUDE_PREVIEW = process.env.BUBBLE_DEBUG_REASONING_PREVIEW !== "0";
|
|
7
|
+
const INCLUDE_RAW_PREVIEW = ["1", "true", "yes", "on"].includes(process.env.BUBBLE_DEBUG_REASONING_RAW?.trim().toLowerCase() ?? "");
|
|
6
8
|
const PREVIEW_CHARS = 180;
|
|
7
9
|
let sequence = 0;
|
|
8
10
|
export function summarizeDebugText(value) {
|
|
@@ -13,7 +15,8 @@ export function summarizeDebugText(value) {
|
|
|
13
15
|
const hash = createHash("sha256").update(value).digest("hex").slice(0, 16);
|
|
14
16
|
const summary = { length: value.length, hash };
|
|
15
17
|
if (INCLUDE_PREVIEW) {
|
|
16
|
-
|
|
18
|
+
const previewValue = INCLUDE_RAW_PREVIEW ? value : sanitizeInternalReminderBlocks(value);
|
|
19
|
+
summary.preview = previewValue.replace(/\s+/g, " ").slice(0, PREVIEW_CHARS);
|
|
17
20
|
}
|
|
18
21
|
return summary;
|
|
19
22
|
}
|
package/dist/session-log.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sanitizeAssistantProviderMetadata, sanitizeInternalReminderBlocks } from "./agent/internal-reminder-sanitizer.js";
|
|
1
2
|
export class SessionLog {
|
|
2
3
|
entries = [];
|
|
3
4
|
load(lines) {
|
|
@@ -127,6 +128,11 @@ export class SessionLog {
|
|
|
127
128
|
messages.push({
|
|
128
129
|
...entry.message,
|
|
129
130
|
role: "assistant",
|
|
131
|
+
content: sanitizeInternalReminderBlocks(entry.message.content),
|
|
132
|
+
reasoning: entry.message.reasoning !== undefined
|
|
133
|
+
? sanitizeInternalReminderBlocks(entry.message.reasoning)
|
|
134
|
+
: undefined,
|
|
135
|
+
providerMetadata: sanitizeAssistantProviderMetadata(cloneProviderMetadata(entry.message.providerMetadata)),
|
|
130
136
|
});
|
|
131
137
|
break;
|
|
132
138
|
case "tool_call": {
|
|
@@ -189,13 +195,16 @@ function normalizeMessageToEntries(message, id, timestamp) {
|
|
|
189
195
|
type: "assistant_message",
|
|
190
196
|
message: {
|
|
191
197
|
role: "assistant",
|
|
192
|
-
content: message.content,
|
|
193
|
-
reasoning: message.reasoning
|
|
198
|
+
content: sanitizeInternalReminderBlocks(message.content),
|
|
199
|
+
reasoning: message.reasoning !== undefined
|
|
200
|
+
? sanitizeInternalReminderBlocks(message.reasoning)
|
|
201
|
+
: undefined,
|
|
194
202
|
model: message.model,
|
|
195
203
|
providerId: message.providerId,
|
|
196
204
|
modelId: message.modelId,
|
|
197
205
|
usage: message.usage,
|
|
198
206
|
error: message.error,
|
|
207
|
+
providerMetadata: sanitizeAssistantProviderMetadata(cloneProviderMetadata(message.providerMetadata)),
|
|
199
208
|
},
|
|
200
209
|
timestamp,
|
|
201
210
|
};
|
|
@@ -247,6 +256,7 @@ function cloneMessage(message) {
|
|
|
247
256
|
return {
|
|
248
257
|
...message,
|
|
249
258
|
toolCalls: message.toolCalls?.map((toolCall) => ({ ...toolCall })),
|
|
259
|
+
providerMetadata: cloneProviderMetadata(message.providerMetadata),
|
|
250
260
|
};
|
|
251
261
|
}
|
|
252
262
|
if (message.role === "user" && Array.isArray(message.content)) {
|
|
@@ -260,6 +270,11 @@ function cloneMessage(message) {
|
|
|
260
270
|
}
|
|
261
271
|
return { ...message };
|
|
262
272
|
}
|
|
273
|
+
function cloneProviderMetadata(metadata) {
|
|
274
|
+
if (metadata === undefined)
|
|
275
|
+
return undefined;
|
|
276
|
+
return JSON.parse(JSON.stringify(metadata));
|
|
277
|
+
}
|
|
263
278
|
function pruneIncompleteTail(messages) {
|
|
264
279
|
let currentTurnStart = -1;
|
|
265
280
|
let hasCompletedAssistant = false;
|
|
@@ -4,7 +4,7 @@ import { formatDiagnostics } from "../lsp/index.js";
|
|
|
4
4
|
import { normalizeNameForMCP } from "../mcp/name.js";
|
|
5
5
|
import { parseRule } from "../permissions/rule.js";
|
|
6
6
|
import { encodeModel, decodeModel, displayModel, BUILTIN_PROVIDERS, isUserVisibleProvider } from "../provider-registry.js";
|
|
7
|
-
import { getAvailableThinkingLevels, normalizeThinkingLevel } from "../provider-transform.js";
|
|
7
|
+
import { getAvailableThinkingLevels, getDefaultThinkingLevel, normalizeThinkingLevel } from "../provider-transform.js";
|
|
8
8
|
import { buildSystemPrompt } from "../system-prompt.js";
|
|
9
9
|
import { isThinkingLevel } from "../variant/thinking-level.js";
|
|
10
10
|
import { collectUsageStatsBundle, formatStatsText } from "../stats/usage.js";
|
|
@@ -131,7 +131,9 @@ function parseModelArgs(args) {
|
|
|
131
131
|
}
|
|
132
132
|
function displaySelectedModel(model, thinkingLevel) {
|
|
133
133
|
const label = displayModel(model);
|
|
134
|
-
|
|
134
|
+
const { providerId, modelId } = decodeModel(model);
|
|
135
|
+
const defaultLevel = providerId ? getDefaultThinkingLevel(providerId, modelId) : "off";
|
|
136
|
+
return thinkingLevel === "off" || thinkingLevel === defaultLevel ? label : `${label} (${thinkingLevel})`;
|
|
135
137
|
}
|
|
136
138
|
function parseMemoryScopeArgs(args) {
|
|
137
139
|
const tokens = args.trim().split(/\s+/).filter(Boolean);
|
package/dist/stats/usage.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { PricingCurrency } from "../model-pricing.js";
|
|
1
2
|
export type StatsRange = "7d" | "30d";
|
|
2
3
|
export interface DailyUsage {
|
|
3
4
|
date: string;
|
|
@@ -22,6 +23,7 @@ export interface ModelUsageStats {
|
|
|
22
23
|
reasoningTokens: number;
|
|
23
24
|
totalTokens: number;
|
|
24
25
|
cost?: number;
|
|
26
|
+
costCurrency?: PricingCurrency;
|
|
25
27
|
}
|
|
26
28
|
export interface UsageStats {
|
|
27
29
|
range: StatsRange;
|
|
@@ -32,7 +34,9 @@ export interface UsageStats {
|
|
|
32
34
|
heatmap: HeatmapColumn[];
|
|
33
35
|
models: ModelUsageStats[];
|
|
34
36
|
totalTokens: number;
|
|
37
|
+
trackedCosts?: Partial<Record<PricingCurrency, number>>;
|
|
35
38
|
trackedCost?: number;
|
|
39
|
+
trackedCostCurrency?: PricingCurrency;
|
|
36
40
|
activeDays: number;
|
|
37
41
|
sessionsScanned: number;
|
|
38
42
|
sessionsWithoutTokenData: number;
|
package/dist/stats/usage.js
CHANGED
|
@@ -61,11 +61,15 @@ export function formatCompactNumber(value) {
|
|
|
61
61
|
return String(Math.round(value));
|
|
62
62
|
}
|
|
63
63
|
export function formatCurrency(value) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
64
|
+
return formatCurrencyFor(value, "USD");
|
|
65
|
+
}
|
|
66
|
+
function formatCurrencyFor(value, currency) {
|
|
67
|
+
const amount = value >= 1
|
|
68
|
+
? value.toFixed(2)
|
|
69
|
+
: value >= 0.01
|
|
70
|
+
? value.toFixed(3)
|
|
71
|
+
: value.toFixed(4);
|
|
72
|
+
return currency === "USD" ? `$${amount}` : `CNY ${amount}`;
|
|
69
73
|
}
|
|
70
74
|
function createAccumulator(range, days, now) {
|
|
71
75
|
const end = startOfLocalDay(now);
|
|
@@ -146,7 +150,11 @@ function finalizeAccumulator(accumulator) {
|
|
|
146
150
|
.filter((model) => model.totalTokens > 0)
|
|
147
151
|
.sort((a, b) => b.totalTokens - a.totalTokens);
|
|
148
152
|
const totalTokens = models.reduce((sum, model) => sum + model.totalTokens, 0);
|
|
149
|
-
const
|
|
153
|
+
const trackedCosts = aggregateCosts(models);
|
|
154
|
+
const trackedCostEntries = trackedCosts
|
|
155
|
+
? Object.entries(trackedCosts)
|
|
156
|
+
: [];
|
|
157
|
+
const trackedCostEntry = trackedCostEntries.length === 1 ? trackedCostEntries[0] : undefined;
|
|
150
158
|
return {
|
|
151
159
|
range: accumulator.range,
|
|
152
160
|
days: accumulator.days,
|
|
@@ -156,7 +164,9 @@ function finalizeAccumulator(accumulator) {
|
|
|
156
164
|
heatmap: buildHeatmap(daily),
|
|
157
165
|
models,
|
|
158
166
|
totalTokens,
|
|
159
|
-
|
|
167
|
+
trackedCosts,
|
|
168
|
+
trackedCost: trackedCostEntry ? trackedCostEntry[1] : undefined,
|
|
169
|
+
trackedCostCurrency: trackedCostEntry ? trackedCostEntry[0] : undefined,
|
|
160
170
|
activeDays: daily.filter((day) => day.active).length,
|
|
161
171
|
sessionsScanned: accumulator.sessionsScanned,
|
|
162
172
|
sessionsWithoutTokenData: accumulator.sessionsWithoutTokenData,
|
|
@@ -189,8 +199,10 @@ function addModelUsage(accumulator, model, message, usage) {
|
|
|
189
199
|
existing.totalTokens += tokenTotal(usage);
|
|
190
200
|
if (providerId && modelId) {
|
|
191
201
|
const cost = calculateUsageCost(providerId, modelId, usage);
|
|
192
|
-
if (cost)
|
|
202
|
+
if (cost) {
|
|
193
203
|
existing.cost = (existing.cost ?? 0) + cost.cost;
|
|
204
|
+
existing.costCurrency = cost.currency;
|
|
205
|
+
}
|
|
194
206
|
}
|
|
195
207
|
accumulator.modelUsage.set(key, existing);
|
|
196
208
|
}
|
|
@@ -254,7 +266,9 @@ function formatModelUsageLines(stats, width) {
|
|
|
254
266
|
const percentText = `${Math.round(percent * 100)}%`.padStart(4, " ");
|
|
255
267
|
const tokenText = formatCompactNumber(model.totalTokens).padStart(6, " ");
|
|
256
268
|
const turnsText = `${model.turns}t`.padStart(4, " ");
|
|
257
|
-
const costText = showCost
|
|
269
|
+
const costText = showCost
|
|
270
|
+
? ` ${(model.cost !== undefined ? formatCurrencyFor(model.cost, model.costCurrency ?? "USD") : "").padStart(7, " ")}`
|
|
271
|
+
: "";
|
|
258
272
|
return ` ${truncate(model.displayName, labelWidth).padEnd(labelWidth, " ")} ${bar} ${percentText} ${tokenText} ${turnsText}${costText}`.trimEnd();
|
|
259
273
|
});
|
|
260
274
|
if (stats.models.length > MAX_MODEL_ROWS) {
|
|
@@ -270,14 +284,37 @@ function formatSummaryLines(stats, width) {
|
|
|
270
284
|
if (favorite) {
|
|
271
285
|
lines.push(` Favorite model ${truncate(favorite, Math.max(12, width - 17))}`);
|
|
272
286
|
}
|
|
273
|
-
|
|
274
|
-
|
|
287
|
+
const trackedCostText = formatTrackedCosts(stats);
|
|
288
|
+
if (trackedCostText)
|
|
289
|
+
lines.push(` Tracked cost ${trackedCostText}`);
|
|
275
290
|
lines.push(` Sessions scanned ${stats.sessionsScanned}`);
|
|
276
291
|
if (stats.sessionsWithoutTokenData > 0) {
|
|
277
292
|
lines.push(` Sessions without token data ${stats.sessionsWithoutTokenData}`);
|
|
278
293
|
}
|
|
279
294
|
return lines;
|
|
280
295
|
}
|
|
296
|
+
function aggregateCosts(models) {
|
|
297
|
+
const totals = {};
|
|
298
|
+
for (const model of models) {
|
|
299
|
+
if (model.cost === undefined)
|
|
300
|
+
continue;
|
|
301
|
+
const currency = model.costCurrency ?? "USD";
|
|
302
|
+
totals[currency] = (totals[currency] ?? 0) + model.cost;
|
|
303
|
+
}
|
|
304
|
+
return Object.keys(totals).length > 0 ? totals : undefined;
|
|
305
|
+
}
|
|
306
|
+
function formatTrackedCosts(stats) {
|
|
307
|
+
if (stats.trackedCosts) {
|
|
308
|
+
const parts = Object.entries(stats.trackedCosts)
|
|
309
|
+
.filter(([, value]) => value > 0)
|
|
310
|
+
.map(([currency, value]) => formatCurrencyFor(value, currency));
|
|
311
|
+
return parts.length > 0 ? parts.join(" + ") : undefined;
|
|
312
|
+
}
|
|
313
|
+
if (stats.trackedCost !== undefined) {
|
|
314
|
+
return formatCurrencyFor(stats.trackedCost, stats.trackedCostCurrency ?? "USD");
|
|
315
|
+
}
|
|
316
|
+
return undefined;
|
|
317
|
+
}
|
|
281
318
|
function heatmapCell(day, maxTokens) {
|
|
282
319
|
if (!day)
|
|
283
320
|
return " ";
|
package/dist/tools/glob.js
CHANGED
|
@@ -65,6 +65,7 @@ export function createGlobTool(cwd) {
|
|
|
65
65
|
}
|
|
66
66
|
files.sort((a, b) => b.mtimeMs - a.mtimeMs || a.path.localeCompare(b.path));
|
|
67
67
|
const matches = files.slice(0, MAX_RESULTS).map((item) => item.path);
|
|
68
|
+
const absoluteMatches = matches.map((item) => resolve(root, item));
|
|
68
69
|
const wasTruncated = truncated.value || files.length > MAX_RESULTS;
|
|
69
70
|
if (matches.length === 0) {
|
|
70
71
|
return {
|
|
@@ -78,6 +79,7 @@ export function createGlobTool(cwd) {
|
|
|
78
79
|
truncated: false,
|
|
79
80
|
searchSignature: `glob:${root}:${pattern}`,
|
|
80
81
|
searchFamily: `glob:${pattern}`,
|
|
82
|
+
paths: [],
|
|
81
83
|
},
|
|
82
84
|
};
|
|
83
85
|
}
|
|
@@ -92,6 +94,7 @@ export function createGlobTool(cwd) {
|
|
|
92
94
|
truncated: wasTruncated,
|
|
93
95
|
searchSignature: `glob:${root}:${pattern}`,
|
|
94
96
|
searchFamily: `glob:${pattern}`,
|
|
97
|
+
paths: absoluteMatches,
|
|
95
98
|
},
|
|
96
99
|
};
|
|
97
100
|
},
|
package/dist/tools/grep.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Grep tool - search file contents using ripgrep.
|
|
3
3
|
*/
|
|
4
4
|
import { execFile } from "node:child_process";
|
|
5
|
+
import { resolve as resolvePath } from "node:path";
|
|
5
6
|
import { isSensitivePath } from "./sensitive-paths.js";
|
|
6
7
|
import { analyzeToolIntent } from "../agent/tool-intent.js";
|
|
7
8
|
import { resolveToolPath } from "./path-utils.js";
|
|
@@ -57,6 +58,7 @@ export function createGrepTool(cwd) {
|
|
|
57
58
|
// rg returns exit code 1 when no matches found, which is not an error for us
|
|
58
59
|
const lines = stdout.split("\n").filter((l) => l.trim() !== "");
|
|
59
60
|
const matches = [];
|
|
61
|
+
const matchedPaths = new Set();
|
|
60
62
|
for (const line of lines) {
|
|
61
63
|
try {
|
|
62
64
|
const obj = JSON.parse(line);
|
|
@@ -64,6 +66,9 @@ export function createGrepTool(cwd) {
|
|
|
64
66
|
const path = obj.data.path.text;
|
|
65
67
|
const lineNum = obj.data.line_number;
|
|
66
68
|
const text = obj.data.lines.text?.trim() ?? "";
|
|
69
|
+
if (typeof path === "string" && path.trim()) {
|
|
70
|
+
matchedPaths.add(resolvePath(cwd, path));
|
|
71
|
+
}
|
|
67
72
|
matches.push(`${path}:${lineNum}: ${text}`);
|
|
68
73
|
}
|
|
69
74
|
}
|
|
@@ -83,6 +88,7 @@ export function createGrepTool(cwd) {
|
|
|
83
88
|
truncated: false,
|
|
84
89
|
searchSignature: intent.search?.signature,
|
|
85
90
|
searchFamily: intent.search?.familyKey,
|
|
91
|
+
paths: [],
|
|
86
92
|
},
|
|
87
93
|
});
|
|
88
94
|
return;
|
|
@@ -103,6 +109,7 @@ export function createGrepTool(cwd) {
|
|
|
103
109
|
truncated,
|
|
104
110
|
searchSignature: intent.search?.signature,
|
|
105
111
|
searchFamily: intent.search?.familyKey,
|
|
112
|
+
paths: [...matchedPaths],
|
|
106
113
|
},
|
|
107
114
|
});
|
|
108
115
|
});
|