@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.
@@ -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.js CHANGED
@@ -5,7 +5,7 @@
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";
@@ -20,7 +20,7 @@ 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";
@@ -376,6 +376,7 @@ export class Agent {
376
376
  modelId: this.apiModel,
377
377
  };
378
378
  const streamingToolCalls = new Map();
379
+ const textSanitizer = createStreamingInternalReminderSanitizer();
379
380
  const reasoningSanitizer = createStreamingInternalReminderSanitizer();
380
381
  let turnUsage;
381
382
  let assistantAppended = false;
@@ -433,6 +434,7 @@ export class Agent {
433
434
  toolCount: toolDefinitions.length,
434
435
  thinkingLevel: this.thinkingLevel,
435
436
  mode: this._mode,
437
+ requestFingerprint: buildProviderRequestFingerprint(projectedMessages, toolDefinitions, this.providerId),
436
438
  }, traceContext);
437
439
  const stream = this.provider.streamChat(projectedMessages, {
438
440
  model: this.apiModel,
@@ -445,9 +447,14 @@ export class Agent {
445
447
  throwIfAborted(abortSignal);
446
448
  switch (chunk.type) {
447
449
  case "text":
448
- assistantMsg.content += chunk.content;
449
- streamTextChars += chunk.content.length;
450
- yield emit({ type: "text_delta", content: chunk.content });
450
+ {
451
+ const sanitizedDelta = textSanitizer.push(chunk.content);
452
+ if (sanitizedDelta) {
453
+ assistantMsg.content += sanitizedDelta;
454
+ streamTextChars += sanitizedDelta.length;
455
+ yield emit({ type: "text_delta", content: sanitizedDelta });
456
+ }
457
+ }
451
458
  break;
452
459
  case "reasoning_delta":
453
460
  {
@@ -468,6 +475,9 @@ export class Agent {
468
475
  }
469
476
  }
470
477
  break;
478
+ case "provider_content_block":
479
+ appendProviderContentBlock(assistantMsg, chunk.provider, chunk.block);
480
+ break;
471
481
  case "tool_call":
472
482
  if (discoveryBarrier?.isEnabled()
473
483
  && (bufferedStreamingToolCallIds.has(chunk.id) || discoveryBarrier.shouldBufferStreamingToolCall(chunk.name))) {
@@ -540,6 +550,12 @@ export class Agent {
540
550
  for (const update of this.drainSubagentToolUpdates())
541
551
  yield emit(update);
542
552
  }
553
+ const flushedText = textSanitizer.flush();
554
+ if (flushedText) {
555
+ assistantMsg.content += flushedText;
556
+ streamTextChars += flushedText.length;
557
+ yield emit({ type: "text_delta", content: flushedText });
558
+ }
543
559
  const flushedReasoning = reasoningSanitizer.flush();
544
560
  if (flushedReasoning) {
545
561
  debugReasoningStream({
@@ -1525,9 +1541,15 @@ export class Agent {
1525
1541
  }
1526
1542
  }
1527
1543
  appendMessage(message) {
1544
+ if (message.role === "assistant" && message.content) {
1545
+ message.content = sanitizeInternalReminderBlocks(message.content);
1546
+ }
1528
1547
  if (message.role === "assistant" && message.reasoning) {
1529
1548
  message.reasoning = sanitizeInternalReminderBlocks(message.reasoning);
1530
1549
  }
1550
+ if (message.role === "assistant" && message.providerMetadata) {
1551
+ message.providerMetadata = sanitizeAssistantProviderMetadata(message.providerMetadata);
1552
+ }
1531
1553
  this.messages.push(message);
1532
1554
  traceEvent("agent_message_append", {
1533
1555
  message: summarizeTraceMessage(message),
@@ -1682,6 +1704,76 @@ function estimateResidentChars(messages) {
1682
1704
  }
1683
1705
  return total;
1684
1706
  }
1707
+ function appendProviderContentBlock(message, provider, block) {
1708
+ if (provider !== "anthropic")
1709
+ return;
1710
+ const current = message.providerMetadata?.anthropic?.contentBlocks ?? [];
1711
+ message.providerMetadata = {
1712
+ ...message.providerMetadata,
1713
+ anthropic: {
1714
+ ...message.providerMetadata?.anthropic,
1715
+ contentBlocks: [...current, cloneProviderRawContentBlock(block)],
1716
+ },
1717
+ };
1718
+ }
1719
+ function buildProviderRequestFingerprint(messages, tools, providerId) {
1720
+ const roleCounts = {};
1721
+ let contentChars = 0;
1722
+ let reasoningChars = 0;
1723
+ let toolResultChars = 0;
1724
+ let maxToolResultChars = 0;
1725
+ let assistantToolCalls = 0;
1726
+ let rawAnthropicBlocks = 0;
1727
+ let rawAnthropicThinkingBlocks = 0;
1728
+ let rawAnthropicSignatureChars = 0;
1729
+ for (const message of messages) {
1730
+ roleCounts[message.role] = (roleCounts[message.role] ?? 0) + 1;
1731
+ if (message.role === "assistant") {
1732
+ contentChars += message.content.length;
1733
+ reasoningChars += message.reasoning?.length ?? 0;
1734
+ assistantToolCalls += message.toolCalls?.length ?? 0;
1735
+ const blocks = message.providerMetadata?.anthropic?.contentBlocks ?? [];
1736
+ rawAnthropicBlocks += blocks.length;
1737
+ for (const block of blocks) {
1738
+ if (block.type === "thinking" || block.type === "redacted_thinking") {
1739
+ rawAnthropicThinkingBlocks += 1;
1740
+ }
1741
+ if (typeof block.signature === "string") {
1742
+ rawAnthropicSignatureChars += block.signature.length;
1743
+ }
1744
+ }
1745
+ }
1746
+ else if (message.role === "tool") {
1747
+ toolResultChars += message.content.length;
1748
+ maxToolResultChars = Math.max(maxToolResultChars, message.content.length);
1749
+ }
1750
+ else if (message.role === "user") {
1751
+ contentChars += typeof message.content === "string"
1752
+ ? message.content.length
1753
+ : message.content.reduce((sum, part) => sum + (part.type === "text" ? part.text.length : part.image_url.url.length), 0);
1754
+ }
1755
+ else {
1756
+ contentChars += message.content.length;
1757
+ }
1758
+ }
1759
+ return {
1760
+ roleCounts,
1761
+ estimatedTokens: estimateContextTokens(messages, providerId),
1762
+ projectedJsonBytes: Buffer.byteLength(JSON.stringify(messages), "utf8"),
1763
+ toolSchemaJsonBytes: Buffer.byteLength(JSON.stringify(tools), "utf8"),
1764
+ contentChars,
1765
+ reasoningChars,
1766
+ toolResultChars,
1767
+ maxToolResultChars,
1768
+ assistantToolCalls,
1769
+ rawAnthropicBlocks,
1770
+ rawAnthropicThinkingBlocks,
1771
+ rawAnthropicSignatureChars,
1772
+ };
1773
+ }
1774
+ function cloneProviderRawContentBlock(block) {
1775
+ return JSON.parse(JSON.stringify(block));
1776
+ }
1685
1777
  function throwIfAborted(signal) {
1686
1778
  if (!signal?.aborted)
1687
1779
  return;
@@ -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
  }
@@ -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),
@@ -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
@@ -83,6 +83,7 @@ async function main() {
83
83
  baseURL: defaultProvider.baseURL,
84
84
  thinkingLevel: args.thinkingLevel,
85
85
  promptCacheKey: sessionPromptCacheKey,
86
+ protocol: defaultProvider.protocol,
86
87
  openAICodexAuth: registry.createOpenAICodexAuthAdapter(defaultProvider.id),
87
88
  })
88
89
  : createUnavailableProvider(unavailableProviderMessage);
@@ -92,6 +93,7 @@ async function main() {
92
93
  baseURL,
93
94
  thinkingLevel: args.thinkingLevel,
94
95
  promptCacheKey: sessionPromptCacheKey,
96
+ protocol: registry.getConfigured().find((provider) => provider.id === providerId)?.protocol,
95
97
  openAICodexAuth: registry.createOpenAICodexAuthAdapter(providerId),
96
98
  });
97
99
  const createProviderForRoute = async (route) => {
@@ -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 {
@@ -2,6 +2,7 @@ export const BUILTIN_PROVIDERS = [
2
2
  { id: "openrouter", name: "OpenRouter", baseURL: "https://openrouter.ai/api/v1" },
3
3
  { id: "openai", name: "OpenAI", baseURL: "https://api.openai.com/v1", supportsOAuth: true },
4
4
  { id: "openai-codex", name: "OpenAI Codex (ChatGPT)", baseURL: "https://chatgpt.com/backend-api" },
5
+ { id: "anthropic", name: "Anthropic", baseURL: "https://api.anthropic.com", protocol: "anthropic-messages" },
5
6
  { id: "deepseek", name: "DeepSeek", baseURL: "https://api.deepseek.com" },
6
7
  { id: "google", name: "Google", baseURL: "https://generativelanguage.googleapis.com/v1beta/openai" },
7
8
  { id: "zhipuai", name: "Zhipu AI", baseURL: "https://open.bigmodel.cn/api/paas/v4" },
@@ -9,6 +10,9 @@ export const BUILTIN_PROVIDERS = [
9
10
  { id: "zai", name: "Z.AI", baseURL: "https://api.z.ai/api/paas/v4" },
10
11
  { id: "zai-coding-plan", name: "Z.AI Coding Plan", baseURL: "https://api.z.ai/api/coding/paas/v4" },
11
12
  { id: "alibaba", name: "Alibaba DashScope", baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1" },
13
+ { id: "minimax", name: "MiniMax Token Plan", baseURL: "https://api.minimaxi.com/anthropic", protocol: "anthropic-messages" },
14
+ { id: "minimax-openai", name: "MiniMax (OpenAI Compatible)", baseURL: "https://api.minimaxi.com/v1" },
15
+ { id: "minimax-anthropic", name: "MiniMax (Anthropic Compatible)", baseURL: "https://api.minimaxi.com/anthropic", protocol: "anthropic-messages", hidden: true },
12
16
  { id: "stepfun", name: "StepFun Step Plan", baseURL: "https://api.stepfun.com/step_plan/v1" },
13
17
  { id: "moonshot-cn", name: "Moonshot (国内 platform.moonshot.cn)", baseURL: "https://api.moonshot.cn/v1" },
14
18
  { id: "moonshot-intl", name: "Moonshot (海外 platform.moonshot.ai)", baseURL: "https://api.moonshot.ai/v1" },
@@ -26,6 +30,10 @@ const OPENAI_CHAT_LEVELS = ["off"];
26
30
  const TOGGLE_THINKING_LEVELS = ["off", "medium"];
27
31
  const DEEPSEEK_V4_LEVELS = ["high", "max"];
28
32
  const STEPFUN_REASONING_LEVELS = ["off", "low", "medium", "high"];
33
+ const MINIMAX_M3_REASONING_LEVELS = ["off", "medium"];
34
+ const MINIMAX_REASONING_LEVELS = ["medium"];
35
+ const ANTHROPIC_ADAPTIVE_LEVELS = ["off", "medium"];
36
+ const ANTHROPIC_CHAT_LEVELS = ["off"];
29
37
  export const BUILTIN_MODELS = [
30
38
  { id: "gpt-5.5", name: "gpt-5.5", providerId: "openai-codex", reasoningLevels: ALL_OPENAI_LEVELS, contextWindow: 272000, toolOutputTokenLimit: 10000 },
31
39
  { id: "gpt-5.4", name: "gpt-5.4", providerId: "openai-codex", reasoningLevels: ALL_OPENAI_LEVELS, contextWindow: 272000 },
@@ -42,6 +50,9 @@ export const BUILTIN_MODELS = [
42
50
  { id: "o1-preview", name: "o1-preview", providerId: "openai", reasoningLevels: ["off", "low", "medium", "high"], contextWindow: 128000 },
43
51
  { id: "o1-mini", name: "o1-mini", providerId: "openai", reasoningLevels: ["off", "low", "medium", "high"], contextWindow: 128000 },
44
52
  { id: "gpt-4-turbo", name: "gpt-4-turbo", providerId: "openai", reasoningLevels: OPENAI_CHAT_LEVELS, contextWindow: 128000 },
53
+ { id: "claude-opus-4-8", name: "Claude Opus 4.8", providerId: "anthropic", reasoningLevels: ANTHROPIC_ADAPTIVE_LEVELS, contextWindow: 1000000 },
54
+ { id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6", providerId: "anthropic", reasoningLevels: ANTHROPIC_ADAPTIVE_LEVELS, contextWindow: 1000000 },
55
+ { id: "claude-haiku-4-5-20251001", name: "Claude Haiku 4.5", providerId: "anthropic", reasoningLevels: ANTHROPIC_CHAT_LEVELS, contextWindow: 200000 },
45
56
  { id: "deepseek-v4-flash", name: "deepseek-v4-flash", providerId: "deepseek", reasoningLevels: DEEPSEEK_V4_LEVELS, contextWindow: 1048576 },
46
57
  { id: "deepseek-v4-pro", name: "deepseek-v4-pro", providerId: "deepseek", reasoningLevels: DEEPSEEK_V4_LEVELS, contextWindow: 1048576 },
47
58
  { id: "gemini-2.5-pro-preview-03-25", name: "gemini-2.5-pro-preview-03-25", providerId: "google", reasoningLevels: ["off", "low", "high"], contextWindow: 128000 },
@@ -61,6 +72,33 @@ export const BUILTIN_MODELS = [
61
72
  { id: "glm-4.6", name: "GLM-4.6", providerId: "zai-coding-plan", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 200000 },
62
73
  { id: "qwen3.6-plus", name: "Qwen3.6 Plus", providerId: "alibaba", reasoningLevels: ["off"], contextWindow: 1048576 },
63
74
  { id: "qwen3.7-max", name: "Qwen3.7 Max", providerId: "alibaba", reasoningLevels: ["off"], contextWindow: 1048576 },
75
+ { id: "MiniMax-M3", name: "MiniMax M3", providerId: "minimax", reasoningLevels: MINIMAX_M3_REASONING_LEVELS, contextWindow: 1000000 },
76
+ { id: "MiniMax-M2.7", name: "MiniMax M2.7", providerId: "minimax", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
77
+ { id: "MiniMax-M2.7-highspeed", name: "MiniMax M2.7 Highspeed", providerId: "minimax", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
78
+ { id: "MiniMax-M2.5", name: "MiniMax M2.5", providerId: "minimax", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
79
+ { id: "MiniMax-M2.5-highspeed", name: "MiniMax M2.5 Highspeed", providerId: "minimax", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
80
+ { id: "MiniMax-M2.1", name: "MiniMax M2.1", providerId: "minimax", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
81
+ { id: "MiniMax-M2.1-highspeed", name: "MiniMax M2.1 Highspeed", providerId: "minimax", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
82
+ { id: "MiniMax-M2", name: "MiniMax M2", providerId: "minimax", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
83
+ { id: "M2-her", name: "M2-her", providerId: "minimax", reasoningLevels: ["off"], contextWindow: 64000 },
84
+ { id: "MiniMax-M3", name: "MiniMax M3", providerId: "minimax-openai", reasoningLevels: MINIMAX_M3_REASONING_LEVELS, contextWindow: 1000000 },
85
+ { id: "MiniMax-M2.7", name: "MiniMax M2.7", providerId: "minimax-openai", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
86
+ { id: "MiniMax-M2.7-highspeed", name: "MiniMax M2.7 Highspeed", providerId: "minimax-openai", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
87
+ { id: "MiniMax-M2.5", name: "MiniMax M2.5", providerId: "minimax-openai", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
88
+ { id: "MiniMax-M2.5-highspeed", name: "MiniMax M2.5 Highspeed", providerId: "minimax-openai", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
89
+ { id: "MiniMax-M2.1", name: "MiniMax M2.1", providerId: "minimax-openai", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
90
+ { id: "MiniMax-M2.1-highspeed", name: "MiniMax M2.1 Highspeed", providerId: "minimax-openai", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
91
+ { id: "MiniMax-M2", name: "MiniMax M2", providerId: "minimax-openai", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
92
+ { id: "M2-her", name: "M2-her", providerId: "minimax-openai", reasoningLevels: ["off"], contextWindow: 64000 },
93
+ { id: "MiniMax-M3", name: "MiniMax M3", providerId: "minimax-anthropic", reasoningLevels: MINIMAX_M3_REASONING_LEVELS, contextWindow: 1000000 },
94
+ { id: "MiniMax-M2.7", name: "MiniMax M2.7", providerId: "minimax-anthropic", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
95
+ { id: "MiniMax-M2.7-highspeed", name: "MiniMax M2.7 Highspeed", providerId: "minimax-anthropic", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
96
+ { id: "MiniMax-M2.5", name: "MiniMax M2.5", providerId: "minimax-anthropic", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
97
+ { id: "MiniMax-M2.5-highspeed", name: "MiniMax M2.5 Highspeed", providerId: "minimax-anthropic", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
98
+ { id: "MiniMax-M2.1", name: "MiniMax M2.1", providerId: "minimax-anthropic", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
99
+ { id: "MiniMax-M2.1-highspeed", name: "MiniMax M2.1 Highspeed", providerId: "minimax-anthropic", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
100
+ { id: "MiniMax-M2", name: "MiniMax M2", providerId: "minimax-anthropic", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
101
+ { id: "M2-her", name: "M2-her", providerId: "minimax-anthropic", reasoningLevels: ["off"], contextWindow: 64000 },
64
102
  { id: "step-3.7-flash", name: "Step 3.7 Flash", providerId: "stepfun", reasoningLevels: STEPFUN_REASONING_LEVELS, contextWindow: 256000 },
65
103
  { id: "step-3.5-flash-2603", name: "Step 3.5 Flash 2603", providerId: "stepfun", reasoningLevels: STEPFUN_REASONING_LEVELS },
66
104
  { id: "step-3.5-flash", name: "Step 3.5 Flash", providerId: "stepfun", reasoningLevels: STEPFUN_REASONING_LEVELS },
@@ -4,10 +4,12 @@
4
4
  * Users can define providers, API keys, base URLs, and custom models
5
5
  * in ~/.bubble/models.json.
6
6
  */
7
+ import type { ProviderProtocol } from "./model-catalog.js";
7
8
  import type { ModelInfo } from "./provider-registry.js";
8
9
  export interface ProviderModelConfig {
9
10
  baseURL?: string;
10
11
  apiKey?: string;
12
+ protocol?: ProviderProtocol;
11
13
  models?: Array<{
12
14
  id: string;
13
15
  name?: string;
@@ -29,4 +31,5 @@ export declare class ModelConfig {
29
31
  getCustomModels(providerId: string): ModelInfo[];
30
32
  getApiKey(providerId: string): string | undefined;
31
33
  getBaseURL(providerId: string): string | undefined;
34
+ getProtocol(providerId: string): ProviderProtocol | undefined;
32
35
  }
@@ -56,4 +56,7 @@ export class ModelConfig {
56
56
  getBaseURL(providerId) {
57
57
  return this.data?.providers?.[providerId]?.baseURL;
58
58
  }
59
+ getProtocol(providerId) {
60
+ return this.data?.providers?.[providerId]?.protocol;
61
+ }
59
62
  }
@@ -38,7 +38,7 @@ function buildProviderPrompt(agentName, providerId, modelId, modelName) {
38
38
  const rawModel = modelId ?? modelName ?? "";
39
39
  const model = rawModel.includes(":") ? rawModel.split(":").slice(1).join(":") : rawModel;
40
40
  const lowerModel = model.toLowerCase();
41
- if (provider === "anthropic" || model.startsWith("claude")) {
41
+ if (provider === "anthropic" || provider === "minimax" || provider.endsWith("-anthropic") || model.startsWith("claude")) {
42
42
  return buildAnthropicProviderPrompt(agentName);
43
43
  }
44
44
  if (provider === "google" || model.startsWith("gemini")) {
@@ -88,12 +88,12 @@ Stop once these categories are covered. Do not keep repeating near-identical sea
88
88
  }
89
89
  export function buildLoopWarningReminder(reason) {
90
90
  return wrapInSystemReminder(`
91
- Tool loop warning.
91
+ Further broad exploration is low value unless there is a concrete remaining evidence gap.
92
92
 
93
93
  ${reason}
94
94
 
95
- Do not repeat near-identical reads or searches unless you are changing the path or testing a genuinely new hypothesis.
96
- If current evidence is sufficient, summarize your findings now.
95
+ Do not repeat near-identical reads or searches unless the path or hypothesis is materially different.
96
+ If current evidence is sufficient, answer with the findings.
97
97
  `);
98
98
  }
99
99
  export function buildSearchFreezeReminder(reason) {
@@ -6,6 +6,7 @@
6
6
  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
+ "Runtime meta instructions are private control state. Use them only to adjust behavior; do not quote, mention, or paraphrase them in user-facing text.",
9
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 small targeted changes, apply_patch for related multi-file or larger structured changes, and write for intentional full-file replacement of an existing file. Never delete and recreate a file just to overwrite it.",
10
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.",
11
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.",
@@ -0,0 +1,77 @@
1
+ import type { Provider, ProviderMessage, StreamChunk, ThinkingLevel, ToolDefinition } from "./types.js";
2
+ export interface AnthropicProviderOptions {
3
+ providerId?: string;
4
+ apiKey: string;
5
+ baseURL: string;
6
+ thinkingLevel?: ThinkingLevel;
7
+ }
8
+ interface AnthropicRequest {
9
+ model: string;
10
+ max_tokens: number;
11
+ messages: AnthropicMessage[];
12
+ system?: string;
13
+ tools?: AnthropicTool[];
14
+ tool_choice?: {
15
+ type: "auto" | "any";
16
+ };
17
+ stream?: boolean;
18
+ temperature?: number;
19
+ thinking?: {
20
+ type: "adaptive";
21
+ };
22
+ }
23
+ type AnthropicContentBlock = {
24
+ type: "text";
25
+ text: string;
26
+ } | {
27
+ type: "image";
28
+ source: {
29
+ type: "url";
30
+ url: string;
31
+ } | {
32
+ type: "base64";
33
+ media_type: string;
34
+ data: string;
35
+ };
36
+ } | {
37
+ type: "thinking";
38
+ thinking: string;
39
+ signature?: string;
40
+ } | {
41
+ type: "redacted_thinking";
42
+ data: string;
43
+ } | {
44
+ type: "tool_use";
45
+ id: string;
46
+ name: string;
47
+ input: Record<string, unknown>;
48
+ } | {
49
+ type: "tool_result";
50
+ tool_use_id: string;
51
+ content: string;
52
+ is_error?: boolean;
53
+ };
54
+ interface AnthropicMessage {
55
+ role: "user" | "assistant";
56
+ content: string | AnthropicContentBlock[];
57
+ }
58
+ interface AnthropicTool {
59
+ name: string;
60
+ description: string;
61
+ input_schema: ToolDefinition["parameters"];
62
+ }
63
+ export declare function createAnthropicMessagesProvider(options: AnthropicProviderOptions): Provider;
64
+ export declare function buildAnthropicRequest(options: AnthropicProviderOptions, messages: ProviderMessage[], chatOptions: {
65
+ model: string;
66
+ tools?: ToolDefinition[];
67
+ temperature?: number;
68
+ thinkingLevel?: ThinkingLevel;
69
+ stream?: boolean;
70
+ }): AnthropicRequest;
71
+ export declare function toAnthropicMessages(messages: ProviderMessage[], echoThinking?: boolean): {
72
+ system: string;
73
+ messages: AnthropicMessage[];
74
+ };
75
+ export declare function translateAnthropicStream(events: AsyncIterable<Record<string, unknown>>): AsyncIterable<StreamChunk>;
76
+ export declare function readSseEvents(response: Response): AsyncIterable<Record<string, unknown>>;
77
+ export {};