@bubblebrain-ai/bubble 0.0.16 → 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/dist/agent/internal-reminder-sanitizer.d.ts +2 -0
- package/dist/agent/internal-reminder-sanitizer.js +27 -0
- package/dist/agent.js +97 -5
- package/dist/context/budget.js +15 -0
- 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 +38 -0
- package/dist/model-config.d.ts +3 -0
- package/dist/model-config.js +3 -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-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 +119 -40
- package/dist/session-log.js +14 -2
- package/dist/slash-commands/commands.js +4 -2
- package/dist/types.d.ts +13 -0
- package/package.json +1 -1
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/session-log.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { sanitizeInternalReminderBlocks } from "./agent/internal-reminder-sanitizer.js";
|
|
1
|
+
import { sanitizeAssistantProviderMetadata, sanitizeInternalReminderBlocks } from "./agent/internal-reminder-sanitizer.js";
|
|
2
2
|
export class SessionLog {
|
|
3
3
|
entries = [];
|
|
4
4
|
load(lines) {
|
|
@@ -128,6 +128,11 @@ export class SessionLog {
|
|
|
128
128
|
messages.push({
|
|
129
129
|
...entry.message,
|
|
130
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)),
|
|
131
136
|
});
|
|
132
137
|
break;
|
|
133
138
|
case "tool_call": {
|
|
@@ -190,7 +195,7 @@ function normalizeMessageToEntries(message, id, timestamp) {
|
|
|
190
195
|
type: "assistant_message",
|
|
191
196
|
message: {
|
|
192
197
|
role: "assistant",
|
|
193
|
-
content: message.content,
|
|
198
|
+
content: sanitizeInternalReminderBlocks(message.content),
|
|
194
199
|
reasoning: message.reasoning !== undefined
|
|
195
200
|
? sanitizeInternalReminderBlocks(message.reasoning)
|
|
196
201
|
: undefined,
|
|
@@ -199,6 +204,7 @@ function normalizeMessageToEntries(message, id, timestamp) {
|
|
|
199
204
|
modelId: message.modelId,
|
|
200
205
|
usage: message.usage,
|
|
201
206
|
error: message.error,
|
|
207
|
+
providerMetadata: sanitizeAssistantProviderMetadata(cloneProviderMetadata(message.providerMetadata)),
|
|
202
208
|
},
|
|
203
209
|
timestamp,
|
|
204
210
|
};
|
|
@@ -250,6 +256,7 @@ function cloneMessage(message) {
|
|
|
250
256
|
return {
|
|
251
257
|
...message,
|
|
252
258
|
toolCalls: message.toolCalls?.map((toolCall) => ({ ...toolCall })),
|
|
259
|
+
providerMetadata: cloneProviderMetadata(message.providerMetadata),
|
|
253
260
|
};
|
|
254
261
|
}
|
|
255
262
|
if (message.role === "user" && Array.isArray(message.content)) {
|
|
@@ -263,6 +270,11 @@ function cloneMessage(message) {
|
|
|
263
270
|
}
|
|
264
271
|
return { ...message };
|
|
265
272
|
}
|
|
273
|
+
function cloneProviderMetadata(metadata) {
|
|
274
|
+
if (metadata === undefined)
|
|
275
|
+
return undefined;
|
|
276
|
+
return JSON.parse(JSON.stringify(metadata));
|
|
277
|
+
}
|
|
266
278
|
function pruneIncompleteTail(messages) {
|
|
267
279
|
let currentTurnStart = -1;
|
|
268
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/types.d.ts
CHANGED
|
@@ -14,6 +14,14 @@ export interface ImageContent {
|
|
|
14
14
|
export type ContentPart = TextContent | ImageContent;
|
|
15
15
|
export type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
|
|
16
16
|
export type ReasoningEffort = ThinkingLevel;
|
|
17
|
+
export type ProviderRawContentBlock = Record<string, unknown> & {
|
|
18
|
+
type: string;
|
|
19
|
+
};
|
|
20
|
+
export interface AssistantProviderMetadata {
|
|
21
|
+
anthropic?: {
|
|
22
|
+
contentBlocks?: ProviderRawContentBlock[];
|
|
23
|
+
};
|
|
24
|
+
}
|
|
17
25
|
export interface UserMessage {
|
|
18
26
|
role: "user";
|
|
19
27
|
content: string | ContentPart[];
|
|
@@ -23,6 +31,7 @@ export interface AssistantMessage {
|
|
|
23
31
|
content: string;
|
|
24
32
|
reasoning?: string;
|
|
25
33
|
toolCalls?: ToolCall[];
|
|
34
|
+
providerMetadata?: AssistantProviderMetadata;
|
|
26
35
|
/** Model metadata captured for local usage statistics. */
|
|
27
36
|
model?: string;
|
|
28
37
|
providerId?: string;
|
|
@@ -239,6 +248,10 @@ export type StreamChunk = {
|
|
|
239
248
|
} | {
|
|
240
249
|
type: "reasoning_delta";
|
|
241
250
|
content: string;
|
|
251
|
+
} | {
|
|
252
|
+
type: "provider_content_block";
|
|
253
|
+
provider: "anthropic";
|
|
254
|
+
block: ProviderRawContentBlock;
|
|
242
255
|
} | {
|
|
243
256
|
type: "tool_call";
|
|
244
257
|
id: string;
|