@bubblebrain-ai/bubble 0.0.55 → 0.0.57
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 +35 -0
- package/dist/agent/child-runner.d.ts +7 -0
- package/dist/agent/child-runner.js +67 -3
- package/dist/agent/subagent/runtime.d.ts +3 -0
- package/dist/agent/subagent/runtime.js +38 -8
- package/dist/agent/transcript-helpers.d.ts +1 -1
- package/dist/agent/transcript-helpers.js +4 -3
- package/dist/agent.d.ts +6 -0
- package/dist/agent.js +36 -0
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +22 -3
- package/dist/context/budget.js +7 -2
- package/dist/context/compact.js +8 -2
- package/dist/feishu/agent-host/run-driver.js +3 -0
- package/dist/main.js +16 -1
- package/dist/mcp/client.js +15 -1
- package/dist/mcp/transports.d.ts +2 -2
- package/dist/mcp/types.d.ts +1 -1
- package/dist/memory/phase1.js +4 -0
- package/dist/model-catalog.d.ts +1 -1
- package/dist/model-catalog.js +7 -0
- package/dist/model-pricing.js +0 -0
- package/dist/provider-ark-responses.d.ts +12 -2
- package/dist/provider-ark-responses.js +68 -13
- package/dist/provider-error-record.d.ts +34 -0
- package/dist/provider-error-record.js +185 -0
- package/dist/provider-transform.js +1 -0
- package/dist/provider.js +7 -1
- package/dist/sdk/index.js +5 -0
- package/dist/session-log.d.ts +3 -1
- package/dist/session-log.js +11 -0
- package/dist/session-types.d.ts +6 -1
- package/dist/session.d.ts +2 -0
- package/dist/session.js +4 -0
- package/dist/stream-json.d.ts +13 -0
- package/dist/stream-json.js +74 -0
- package/dist/tui/controller/agent-event-reducer.js +2 -1
- package/dist/tui/controller/controller.js +6 -5
- package/dist/tui/model/image-paste.js +9 -10
- package/dist/types.d.ts +3 -1
- package/package.json +1 -1
package/dist/mcp/client.js
CHANGED
|
@@ -154,7 +154,21 @@ export class MCPClient {
|
|
|
154
154
|
}
|
|
155
155
|
return;
|
|
156
156
|
}
|
|
157
|
-
//
|
|
157
|
+
// Ping is a core MCP request and requires no advertised capability. Long-lived
|
|
158
|
+
// servers (including Playwright) close healthy sessions when it is ignored.
|
|
159
|
+
if ("id" in msg && "method" in msg) {
|
|
160
|
+
const response = msg.method === "ping"
|
|
161
|
+
? { jsonrpc: "2.0", id: msg.id, result: {} }
|
|
162
|
+
: { jsonrpc: "2.0", id: msg.id, error: { code: -32601, message: `Unsupported client method: ${msg.method}` } };
|
|
163
|
+
void this.transport.send(response).catch((error) => {
|
|
164
|
+
for (const pending of this.pending.values()) {
|
|
165
|
+
clearTimeout(pending.timer);
|
|
166
|
+
pending.reject(error instanceof Error ? error : new Error(String(error)));
|
|
167
|
+
}
|
|
168
|
+
this.pending.clear();
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
// Notifications need no response; sampling and roots are not advertised.
|
|
158
172
|
}
|
|
159
173
|
async close() {
|
|
160
174
|
this.closed = true;
|
package/dist/mcp/transports.d.ts
CHANGED
|
@@ -27,7 +27,7 @@ export declare class StdioTransport implements McpTransport {
|
|
|
27
27
|
constructor(config: StdioServerConfig);
|
|
28
28
|
start(): Promise<void>;
|
|
29
29
|
private onStdout;
|
|
30
|
-
send(message: JsonRpcRequest | JsonRpcNotification): Promise<void>;
|
|
30
|
+
send(message: JsonRpcRequest | JsonRpcNotification | JsonRpcResponse): Promise<void>;
|
|
31
31
|
onMessage(handler: IncomingHandler): void;
|
|
32
32
|
onError(handler: (err: Error) => void): void;
|
|
33
33
|
onClose(handler: () => void): void;
|
|
@@ -44,7 +44,7 @@ export declare class HttpTransport implements McpTransport {
|
|
|
44
44
|
private readonly baseHeaders;
|
|
45
45
|
constructor(config: HttpServerConfig | SseServerConfig);
|
|
46
46
|
start(): Promise<void>;
|
|
47
|
-
send(message: JsonRpcRequest | JsonRpcNotification): Promise<void>;
|
|
47
|
+
send(message: JsonRpcRequest | JsonRpcNotification | JsonRpcResponse): Promise<void>;
|
|
48
48
|
private parseSseBody;
|
|
49
49
|
onMessage(handler: IncomingHandler): void;
|
|
50
50
|
onError(handler: (err: Error) => void): void;
|
package/dist/mcp/types.d.ts
CHANGED
|
@@ -100,7 +100,7 @@ export type JsonRpcMessage = JsonRpcRequest | JsonRpcNotification | JsonRpcRespo
|
|
|
100
100
|
/** Low-level transport interface. */
|
|
101
101
|
export interface McpTransport {
|
|
102
102
|
start(): Promise<void>;
|
|
103
|
-
send(message: JsonRpcRequest | JsonRpcNotification): Promise<void>;
|
|
103
|
+
send(message: JsonRpcRequest | JsonRpcNotification | JsonRpcResponse): Promise<void>;
|
|
104
104
|
/** Called once per incoming JSON-RPC message (response or server→client request/notification). */
|
|
105
105
|
onMessage(handler: (msg: JsonRpcResponse | JsonRpcNotification | JsonRpcRequest) => void): void;
|
|
106
106
|
onError(handler: (err: Error) => void): void;
|
package/dist/memory/phase1.js
CHANGED
|
@@ -160,6 +160,10 @@ function serializeSessionEntry(entry) {
|
|
|
160
160
|
return `[tool_call:${entry.toolCall.name}] ${truncate(entry.toolCall.arguments, 1_500)}`;
|
|
161
161
|
case "tool_result":
|
|
162
162
|
return `[tool_result${entry.message.isError ? " error=true" : ""}] ${truncate(entry.message.content, 2_000)}`;
|
|
163
|
+
case "provider_error":
|
|
164
|
+
// Operational diagnostics stay out of the memory-extraction prompt. The
|
|
165
|
+
// structured session record remains available for local debugging.
|
|
166
|
+
return "";
|
|
163
167
|
}
|
|
164
168
|
}
|
|
165
169
|
function countMeaningfulEntries(entries) {
|
package/dist/model-catalog.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ReasoningEffort } from "./types.js";
|
|
2
|
-
export type ProviderProtocol = "openai-chat" | "anthropic-messages" | "ark-responses" | "ai-sdk";
|
|
2
|
+
export type ProviderProtocol = "openai-chat" | "openai-responses" | "anthropic-messages" | "ark-responses" | "ai-sdk";
|
|
3
3
|
export interface BuiltinProviderDefinition {
|
|
4
4
|
id: string;
|
|
5
5
|
name: string;
|
package/dist/model-catalog.js
CHANGED
|
@@ -3,6 +3,7 @@ export const BUILTIN_PROVIDERS = [
|
|
|
3
3
|
{ id: "openrouter", name: "OpenRouter", baseURL: "https://openrouter.ai/api/v1" },
|
|
4
4
|
{ id: "openai", name: "OpenAI", baseURL: "https://api.openai.com/v1", supportsOAuth: true },
|
|
5
5
|
{ id: "openai-codex", name: "OpenAI Codex (ChatGPT)", baseURL: "https://chatgpt.com/backend-api" },
|
|
6
|
+
{ id: "opencode-zen", name: "OpenCode Zen", baseURL: "https://opencode.ai/zen/v1", protocol: "openai-responses" },
|
|
6
7
|
// Grok subscription models via the CLI chat proxy. OAuth-only: the proxy
|
|
7
8
|
// accepts xAI session bearers (from /login grok), not api.x.ai API keys.
|
|
8
9
|
{ id: "grok", name: "Grok Subscription", baseURL: "https://cli-chat-proxy.grok.com/v1", supportsOAuth: true },
|
|
@@ -39,6 +40,7 @@ const GPT51_LEVELS = ["off", "low", "medium", "high"];
|
|
|
39
40
|
const GPT51_CODEX_MAX_LEVELS = ["off", "low", "medium", "high", "xhigh"];
|
|
40
41
|
const GPT51_CODEX_MINI_LEVELS = ["off", "medium", "high"];
|
|
41
42
|
const OPENAI_CHAT_LEVELS = ["off"];
|
|
43
|
+
const MUSE_SPARK_LEVELS = ["minimal", "low", "medium", "high", "xhigh"];
|
|
42
44
|
// Ox Alpha declares mandatory reasoning with exactly low/high/max through
|
|
43
45
|
// OpenRouter's live model metadata. The endpoint rejects disabled reasoning.
|
|
44
46
|
const OPENROUTER_OX_REASONING_LEVELS = ["low", "high", "max"];
|
|
@@ -117,11 +119,16 @@ export const BUILTIN_MODELS = [
|
|
|
117
119
|
{ id: "o1-preview", name: "o1-preview", providerId: "openai", tier: "strong", reasoningLevels: ["off", "low", "medium", "high"], contextWindow: 128000 },
|
|
118
120
|
{ id: "o1-mini", name: "o1-mini", providerId: "openai", tier: "fast", reasoningLevels: ["off", "low", "medium", "high"], contextWindow: 128000 },
|
|
119
121
|
{ id: "gpt-4-turbo", name: "gpt-4-turbo", providerId: "openai", reasoningLevels: OPENAI_CHAT_LEVELS, contextWindow: 128000 },
|
|
122
|
+
{ id: "muse-spark-1.3-contributor-free", name: "Muse Spark 1.3 Free", providerId: "opencode-zen", reasoningLevels: MUSE_SPARK_LEVELS, defaultReasoningLevel: "high", contextWindow: 1048576 },
|
|
123
|
+
{ id: "muse-spark-1.2", name: "Muse Spark 1.2", providerId: "opencode-zen", reasoningLevels: MUSE_SPARK_LEVELS, defaultReasoningLevel: "high", contextWindow: 1048576 },
|
|
124
|
+
{ id: "muse-spark-1.2-contributor-free", name: "Muse Spark 1.2 Free", providerId: "opencode-zen", reasoningLevels: MUSE_SPARK_LEVELS, defaultReasoningLevel: "high", contextWindow: 1048576 },
|
|
120
125
|
{ id: "claude-fable-5", name: "Claude Fable 5", providerId: "anthropic", tier: "strong", reasoningLevels: ANTHROPIC_FABLE_EFFORT_LEVELS, defaultReasoningLevel: "high", contextWindow: 1000000 },
|
|
121
126
|
{ id: "claude-opus-4-8", name: "Claude Opus 4.8", providerId: "anthropic", tier: "strong", reasoningLevels: ANTHROPIC_OPUS_EFFORT_LEVELS, defaultReasoningLevel: "high", contextWindow: 1000000 },
|
|
122
127
|
{ id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6", providerId: "anthropic", tier: "balanced", reasoningLevels: ANTHROPIC_SONNET_EFFORT_LEVELS, defaultReasoningLevel: "high", contextWindow: 1000000 },
|
|
123
128
|
{ id: "claude-haiku-4-5-20251001", name: "Claude Haiku 4.5", providerId: "anthropic", tier: "fast", reasoningLevels: ANTHROPIC_CHAT_LEVELS, contextWindow: 200000 },
|
|
124
129
|
{ id: "deepseek-v4-flash", name: "deepseek-v4-flash", providerId: "deepseek", tier: "fast", reasoningLevels: DEEPSEEK_V4_LEVELS, defaultReasoningLevel: "high", contextWindow: 1048576 },
|
|
130
|
+
// DeepSeek Flash: retain the high effort used by the previous preview.
|
|
131
|
+
{ id: "deepseek-flash", name: "deepseek-flash", providerId: "deepseek", reasoningLevels: ["high"], defaultReasoningLevel: "high" },
|
|
125
132
|
// Experimental vision model stays explicit-only: it is text-capable, but
|
|
126
133
|
// automatic fast-tier routing must not silently switch ordinary subagents
|
|
127
134
|
// from the stable Flash model to an experimental multimodal endpoint.
|
package/dist/model-pricing.js
CHANGED
|
Binary file
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Provider, ProviderMessage, StreamChunk, ThinkingLevel, TokenUsage, ToolChoiceMode, ToolDefinition } from "./types.js";
|
|
2
|
-
export interface
|
|
2
|
+
export interface ResponsesProviderOptions {
|
|
3
3
|
providerId?: string;
|
|
4
4
|
apiKey: string;
|
|
5
5
|
baseURL: string;
|
|
@@ -7,7 +7,9 @@ export interface ArkResponsesProviderOptions {
|
|
|
7
7
|
/** User-configured extra headers, merged last (client-identity gates). */
|
|
8
8
|
headers?: Record<string, string>;
|
|
9
9
|
}
|
|
10
|
+
export type ArkResponsesProviderOptions = ResponsesProviderOptions;
|
|
10
11
|
export declare function createArkResponsesProvider(options: ArkResponsesProviderOptions): Provider;
|
|
12
|
+
export declare function createOpenAIResponsesProvider(options: ResponsesProviderOptions): Provider;
|
|
11
13
|
export declare function buildArkResponsesBody(messages: ProviderMessage[], options: {
|
|
12
14
|
model: string;
|
|
13
15
|
tools?: ToolDefinition[];
|
|
@@ -15,5 +17,13 @@ export declare function buildArkResponsesBody(messages: ProviderMessage[], optio
|
|
|
15
17
|
thinkingLevel?: ThinkingLevel;
|
|
16
18
|
stream: boolean;
|
|
17
19
|
}): Record<string, unknown>;
|
|
18
|
-
export declare function
|
|
20
|
+
export declare function buildOpenAIResponsesBody(messages: ProviderMessage[], options: {
|
|
21
|
+
model: string;
|
|
22
|
+
tools?: ToolDefinition[];
|
|
23
|
+
toolChoice?: ToolChoiceMode;
|
|
24
|
+
temperature?: number;
|
|
25
|
+
thinkingLevel?: ThinkingLevel;
|
|
26
|
+
stream: boolean;
|
|
27
|
+
}): Record<string, unknown>;
|
|
28
|
+
export declare function translateArkResponsesStream(response: Response, captureOpenAIReasoning?: boolean): AsyncIterable<StreamChunk>;
|
|
19
29
|
export declare function normalizeArkResponsesUsage(usage: any): TokenUsage;
|
|
@@ -1,33 +1,43 @@
|
|
|
1
1
|
import { createProviderProtocolArtifactFilter } from "./provider-artifacts.js";
|
|
2
2
|
import { RateLimitError } from "./network/errors.js";
|
|
3
3
|
import { computeRetryDelayMs, getProviderMaxRetries, isRetryableHttpStatus, retryAfterMsFromResponse, sleepBeforeRetry, } from "./network/retry.js";
|
|
4
|
+
import { providerFetch } from "./network/provider-transport.js";
|
|
4
5
|
const DEFAULT_ARK_MODEL = "doubao-seed-2-1-pro-260628";
|
|
6
|
+
const DEFAULT_MUSE_MODEL = "muse-spark-1.3-contributor-free";
|
|
5
7
|
export function createArkResponsesProvider(options) {
|
|
8
|
+
return createResponsesProvider(options, "ark", DEFAULT_ARK_MODEL);
|
|
9
|
+
}
|
|
10
|
+
export function createOpenAIResponsesProvider(options) {
|
|
11
|
+
return createResponsesProvider(options, "openai", DEFAULT_MUSE_MODEL);
|
|
12
|
+
}
|
|
13
|
+
function createResponsesProvider(options, dialect, defaultModel) {
|
|
6
14
|
async function* streamChat(messages, chatOptions) {
|
|
7
|
-
const body =
|
|
15
|
+
const body = buildResponsesBody(messages, {
|
|
8
16
|
model: chatOptions.model,
|
|
9
17
|
tools: chatOptions.tools,
|
|
10
18
|
toolChoice: chatOptions.toolChoice,
|
|
19
|
+
temperature: chatOptions.temperature,
|
|
11
20
|
thinkingLevel: chatOptions.thinkingLevel ?? options.thinkingLevel ?? "high",
|
|
12
21
|
stream: true,
|
|
13
|
-
});
|
|
22
|
+
}, dialect);
|
|
14
23
|
for (let attempt = 0;; attempt += 1) {
|
|
15
24
|
const response = await sendArkResponsesRequest(options, body, {
|
|
16
25
|
signal: chatOptions.abortSignal,
|
|
17
26
|
rateLimitPolicy: chatOptions.rateLimitPolicy,
|
|
18
27
|
attempt,
|
|
19
28
|
});
|
|
20
|
-
yield* translateArkResponsesStream(response);
|
|
29
|
+
yield* translateArkResponsesStream(response, dialect === "openai");
|
|
21
30
|
yield { type: "done" };
|
|
22
31
|
return;
|
|
23
32
|
}
|
|
24
33
|
}
|
|
25
34
|
async function complete(messages, chatOptions) {
|
|
26
|
-
const body =
|
|
27
|
-
model: chatOptions?.model ??
|
|
35
|
+
const body = buildResponsesBody(messages, {
|
|
36
|
+
model: chatOptions?.model ?? defaultModel,
|
|
37
|
+
temperature: chatOptions?.temperature,
|
|
28
38
|
thinkingLevel: chatOptions?.thinkingLevel ?? options.thinkingLevel ?? "high",
|
|
29
39
|
stream: false,
|
|
30
|
-
});
|
|
40
|
+
}, dialect);
|
|
31
41
|
const response = await sendArkResponsesRequest(options, body, {
|
|
32
42
|
signal: chatOptions?.abortSignal,
|
|
33
43
|
attempt: 0,
|
|
@@ -38,15 +48,34 @@ export function createArkResponsesProvider(options) {
|
|
|
38
48
|
return { streamChat, complete };
|
|
39
49
|
}
|
|
40
50
|
export function buildArkResponsesBody(messages, options) {
|
|
51
|
+
return buildResponsesBody(messages, options, "ark");
|
|
52
|
+
}
|
|
53
|
+
export function buildOpenAIResponsesBody(messages, options) {
|
|
54
|
+
return buildResponsesBody(messages, options, "openai");
|
|
55
|
+
}
|
|
56
|
+
function buildResponsesBody(messages, options, dialect) {
|
|
41
57
|
const body = {
|
|
42
58
|
model: options.model,
|
|
43
|
-
input: messages.flatMap((message) =>
|
|
59
|
+
input: messages.flatMap((message) => convertMessageToResponsesInput(message, dialect)),
|
|
44
60
|
store: false,
|
|
45
61
|
stream: options.stream,
|
|
46
|
-
thinking: {
|
|
47
|
-
type: shouldDisableArkThinking(options.thinkingLevel) ? "disabled" : "enabled",
|
|
48
|
-
},
|
|
49
62
|
};
|
|
63
|
+
if (dialect === "ark") {
|
|
64
|
+
body.thinking = {
|
|
65
|
+
type: shouldDisableArkThinking(options.thinkingLevel) ? "disabled" : "enabled",
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
body.include = ["reasoning.encrypted_content"];
|
|
70
|
+
if (options.thinkingLevel && options.thinkingLevel !== "off") {
|
|
71
|
+
body.reasoning = {
|
|
72
|
+
effort: options.thinkingLevel === "ultra" ? "xhigh" : options.thinkingLevel,
|
|
73
|
+
summary: "auto",
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
if (options.temperature !== undefined)
|
|
77
|
+
body.temperature = options.temperature;
|
|
78
|
+
}
|
|
50
79
|
if (options.tools && options.tools.length > 0) {
|
|
51
80
|
body.tools = options.tools.map((tool) => ({
|
|
52
81
|
type: "function",
|
|
@@ -63,7 +92,7 @@ export function buildArkResponsesBody(messages, options) {
|
|
|
63
92
|
function shouldDisableArkThinking(level) {
|
|
64
93
|
return level === "off" || level === "minimal";
|
|
65
94
|
}
|
|
66
|
-
function
|
|
95
|
+
function convertMessageToResponsesInput(message, dialect) {
|
|
67
96
|
if (message.role === "user") {
|
|
68
97
|
if (typeof message.content === "string") {
|
|
69
98
|
return [{
|
|
@@ -92,6 +121,17 @@ function convertMessageToArkResponsesInput(message) {
|
|
|
92
121
|
}
|
|
93
122
|
if (message.role === "assistant") {
|
|
94
123
|
const items = [];
|
|
124
|
+
if (dialect === "openai") {
|
|
125
|
+
for (const block of message.providerMetadata?.openai?.contentBlocks ?? []) {
|
|
126
|
+
if (block.type !== "reasoning" || typeof block.encrypted_content !== "string")
|
|
127
|
+
continue;
|
|
128
|
+
items.push({
|
|
129
|
+
type: "reasoning",
|
|
130
|
+
summary: Array.isArray(block.summary) ? block.summary : [],
|
|
131
|
+
encrypted_content: block.encrypted_content,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
95
135
|
if (message.content) {
|
|
96
136
|
items.push({
|
|
97
137
|
type: "message",
|
|
@@ -118,7 +158,7 @@ function convertMessageToArkResponsesInput(message) {
|
|
|
118
158
|
}];
|
|
119
159
|
}
|
|
120
160
|
async function sendArkResponsesRequest(options, body, requestOptions) {
|
|
121
|
-
const response = await
|
|
161
|
+
const response = await providerFetch(resolveArkResponsesUrl(options.baseURL), {
|
|
122
162
|
method: "POST",
|
|
123
163
|
headers: {
|
|
124
164
|
"Authorization": `Bearer ${options.apiKey}`,
|
|
@@ -128,6 +168,8 @@ async function sendArkResponsesRequest(options, body, requestOptions) {
|
|
|
128
168
|
},
|
|
129
169
|
body: JSON.stringify(body),
|
|
130
170
|
signal: requestOptions.signal,
|
|
171
|
+
}, {
|
|
172
|
+
providerName: options.providerId ?? "Responses provider",
|
|
131
173
|
});
|
|
132
174
|
if (response.ok)
|
|
133
175
|
return response;
|
|
@@ -159,7 +201,7 @@ async function sendArkResponsesRequest(options, body, requestOptions) {
|
|
|
159
201
|
function resolveArkResponsesUrl(baseURL) {
|
|
160
202
|
return `${baseURL.trim().replace(/\/+$/, "")}/responses`;
|
|
161
203
|
}
|
|
162
|
-
export async function* translateArkResponsesStream(response) {
|
|
204
|
+
export async function* translateArkResponsesStream(response, captureOpenAIReasoning = false) {
|
|
163
205
|
const toolCalls = new Map();
|
|
164
206
|
const textFilter = createProviderProtocolArtifactFilter();
|
|
165
207
|
for await (const event of parseArkResponsesSse(response)) {
|
|
@@ -253,6 +295,19 @@ export async function* translateArkResponsesStream(response) {
|
|
|
253
295
|
}
|
|
254
296
|
if (type === "response.output_item.done") {
|
|
255
297
|
const item = event.item;
|
|
298
|
+
if (captureOpenAIReasoning
|
|
299
|
+
&& item?.type === "reasoning"
|
|
300
|
+
&& typeof item.encrypted_content === "string") {
|
|
301
|
+
yield {
|
|
302
|
+
type: "provider_content_block",
|
|
303
|
+
provider: "openai",
|
|
304
|
+
block: {
|
|
305
|
+
type: "reasoning",
|
|
306
|
+
summary: Array.isArray(item.summary) ? item.summary : [],
|
|
307
|
+
encrypted_content: item.encrypted_content,
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
}
|
|
256
311
|
if (item?.type === "function_call") {
|
|
257
312
|
const key = toolCallEventKey(event, item);
|
|
258
313
|
const entry = toolCalls.get(key);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ThinkingLevel } from "./types.js";
|
|
2
|
+
export interface SanitizedProviderError {
|
|
3
|
+
providerId: string;
|
|
4
|
+
modelId: string;
|
|
5
|
+
model?: string;
|
|
6
|
+
thinkingLevel: ThinkingLevel;
|
|
7
|
+
name: string;
|
|
8
|
+
message: string;
|
|
9
|
+
httpStatus?: number;
|
|
10
|
+
code?: string;
|
|
11
|
+
parameter?: string;
|
|
12
|
+
messageCount: number;
|
|
13
|
+
toolCount: number;
|
|
14
|
+
bubbleVersion: string;
|
|
15
|
+
pid: number;
|
|
16
|
+
runtimeStartedAt: number;
|
|
17
|
+
}
|
|
18
|
+
export interface ProviderErrorContext {
|
|
19
|
+
providerId: string;
|
|
20
|
+
modelId: string;
|
|
21
|
+
model?: string;
|
|
22
|
+
thinkingLevel: ThinkingLevel;
|
|
23
|
+
messageCount: number;
|
|
24
|
+
toolCount: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Build the allowlisted diagnostic record written to a session log.
|
|
28
|
+
*
|
|
29
|
+
* Deliberately do not serialize the original error object: SDK errors can
|
|
30
|
+
* contain request headers, request bodies, response bodies and stacks. Only
|
|
31
|
+
* scalar identifiers plus a locally-authored error category are retained.
|
|
32
|
+
*/
|
|
33
|
+
export declare function createSanitizedProviderError(error: unknown, context: ProviderErrorContext): SanitizedProviderError;
|
|
34
|
+
export declare function sanitizeProviderErrorText(value: string): string;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { getCurrentVersion } from "./update/index.js";
|
|
2
|
+
const MAX_FIELD_CHARS = 160;
|
|
3
|
+
const RUNTIME_STARTED_AT = Date.now();
|
|
4
|
+
const SAFE_ERROR_CODES = new Map([
|
|
5
|
+
["authentication_error", "authentication_error"],
|
|
6
|
+
["bad_request", "bad_request"],
|
|
7
|
+
["context_length_exceeded", "context_length_exceeded"],
|
|
8
|
+
["failed_precondition", "failed_precondition"],
|
|
9
|
+
["internal_error", "internal_error"],
|
|
10
|
+
["invalid_argument", "invalid_argument"],
|
|
11
|
+
["invalidparameter", "invalid_parameter"],
|
|
12
|
+
["invalid_parameter", "invalid_parameter"],
|
|
13
|
+
["location_not_supported", "location_not_supported"],
|
|
14
|
+
["permission_denied", "permission_denied"],
|
|
15
|
+
["rate_limit_exceeded", "rate_limit_exceeded"],
|
|
16
|
+
["rate_limit_exhausted", "rate_limit_exhausted"],
|
|
17
|
+
["resource_exhausted", "resource_exhausted"],
|
|
18
|
+
["service_unavailable", "service_unavailable"],
|
|
19
|
+
["timeout", "timeout"],
|
|
20
|
+
["unauthenticated", "unauthenticated"],
|
|
21
|
+
["unsupported_parameter", "unsupported_parameter"],
|
|
22
|
+
["unsupported_value", "unsupported_value"],
|
|
23
|
+
]);
|
|
24
|
+
const SAFE_PARAMETER_NAMES = new Set([
|
|
25
|
+
"contents",
|
|
26
|
+
"frequency_penalty",
|
|
27
|
+
"generationConfig",
|
|
28
|
+
"input",
|
|
29
|
+
"max_output_tokens",
|
|
30
|
+
"max_tokens",
|
|
31
|
+
"messages",
|
|
32
|
+
"model",
|
|
33
|
+
"n",
|
|
34
|
+
"presence_penalty",
|
|
35
|
+
"prompt",
|
|
36
|
+
"reasoning_effort",
|
|
37
|
+
"response_format",
|
|
38
|
+
"seed",
|
|
39
|
+
"stop",
|
|
40
|
+
"stream",
|
|
41
|
+
"temperature",
|
|
42
|
+
"thinkingConfig",
|
|
43
|
+
"thinkingLevel",
|
|
44
|
+
"thinking_level",
|
|
45
|
+
"tool_choice",
|
|
46
|
+
"tools",
|
|
47
|
+
"topP",
|
|
48
|
+
"top_p",
|
|
49
|
+
]);
|
|
50
|
+
/**
|
|
51
|
+
* Build the allowlisted diagnostic record written to a session log.
|
|
52
|
+
*
|
|
53
|
+
* Deliberately do not serialize the original error object: SDK errors can
|
|
54
|
+
* contain request headers, request bodies, response bodies and stacks. Only
|
|
55
|
+
* scalar identifiers plus a locally-authored error category are retained.
|
|
56
|
+
*/
|
|
57
|
+
export function createSanitizedProviderError(error, context) {
|
|
58
|
+
const sources = errorSources(error);
|
|
59
|
+
const rawMessage = firstString(sources, ["message"])
|
|
60
|
+
|| (typeof error === "string" ? error : "Provider request failed.");
|
|
61
|
+
const httpStatus = firstHttpStatus(sources);
|
|
62
|
+
const code = sanitizeErrorCode(firstScalarString(sources, ["code", "errorCode", "type"]));
|
|
63
|
+
const parameter = sanitizeParameterName(firstScalarString(sources, ["param", "parameter"]));
|
|
64
|
+
return {
|
|
65
|
+
providerId: sanitizeControlledIdentifier(context.providerId),
|
|
66
|
+
modelId: sanitizeControlledIdentifier(context.modelId),
|
|
67
|
+
...(context.model ? { model: sanitizeControlledIdentifier(context.model) } : {}),
|
|
68
|
+
thinkingLevel: context.thinkingLevel,
|
|
69
|
+
name: "ProviderError",
|
|
70
|
+
message: sanitizeProviderErrorText(rawMessage),
|
|
71
|
+
...(httpStatus !== undefined ? { httpStatus } : {}),
|
|
72
|
+
...(code ? { code } : {}),
|
|
73
|
+
...(parameter ? { parameter } : {}),
|
|
74
|
+
messageCount: Math.max(0, Math.trunc(context.messageCount)),
|
|
75
|
+
toolCount: Math.max(0, Math.trunc(context.toolCount)),
|
|
76
|
+
bubbleVersion: getCurrentVersion(),
|
|
77
|
+
pid: process.pid,
|
|
78
|
+
runtimeStartedAt: RUNTIME_STARTED_AT,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
export function sanitizeProviderErrorText(value) {
|
|
82
|
+
const text = value.replace(/[\r\n\t]+/g, " ").replace(/\s{2,}/g, " ").trim();
|
|
83
|
+
// Fail closed: provider/SDK messages can embed request headers, bodies,
|
|
84
|
+
// prompts, URLs or credentials in formats a denylist cannot anticipate.
|
|
85
|
+
// Persist only fixed, locally-authored summaries selected by broad error
|
|
86
|
+
// categories; never return any substring copied from the provider message.
|
|
87
|
+
if (/user location (?:is )?not supported|location[^.]{0,40}(?:unsupported|not supported)/i.test(text)) {
|
|
88
|
+
return "User location is not supported for API use.";
|
|
89
|
+
}
|
|
90
|
+
if (/context[_ -]?(?:length|window)|prompt (?:is )?too long|maximum context|token limit/i.test(text)) {
|
|
91
|
+
return "Provider context limit was exceeded.";
|
|
92
|
+
}
|
|
93
|
+
if (/rate limit|too many requests|quota|resource exhausted|\b429\b/i.test(text)) {
|
|
94
|
+
return "Provider rate limit was exceeded.";
|
|
95
|
+
}
|
|
96
|
+
if (/unauthori[sz]ed|authentication|invalid (?:api )?key|missing (?:api )?key|\b401\b/i.test(text)) {
|
|
97
|
+
return "Provider authentication failed.";
|
|
98
|
+
}
|
|
99
|
+
if (/forbidden|permission denied|access denied|\b403\b/i.test(text)) {
|
|
100
|
+
return "Provider denied the request.";
|
|
101
|
+
}
|
|
102
|
+
if (/invalid[^.]{0,60}(?:parameter|argument)|unsupported[^.]{0,60}(?:parameter|argument)|bad request|\b400\b/i.test(text)) {
|
|
103
|
+
return "Provider rejected a request parameter.";
|
|
104
|
+
}
|
|
105
|
+
if (/timed? ?out|timeout/i.test(text)) {
|
|
106
|
+
return "Provider request timed out.";
|
|
107
|
+
}
|
|
108
|
+
if (/network|connection|socket|fetch failed|stream (?:closed|interrupted)/i.test(text)) {
|
|
109
|
+
return "Provider connection failed.";
|
|
110
|
+
}
|
|
111
|
+
if (/abort|cancel/i.test(text)) {
|
|
112
|
+
return "Provider request was cancelled.";
|
|
113
|
+
}
|
|
114
|
+
return "Provider request failed.";
|
|
115
|
+
}
|
|
116
|
+
function errorSources(error) {
|
|
117
|
+
const sources = [];
|
|
118
|
+
const root = asRecord(error);
|
|
119
|
+
if (root)
|
|
120
|
+
sources.push(root);
|
|
121
|
+
const nested = asRecord(root?.error);
|
|
122
|
+
if (nested)
|
|
123
|
+
sources.push(nested);
|
|
124
|
+
const cause = asRecord(root?.cause);
|
|
125
|
+
if (cause)
|
|
126
|
+
sources.push(cause);
|
|
127
|
+
const causeError = asRecord(cause?.error);
|
|
128
|
+
if (causeError)
|
|
129
|
+
sources.push(causeError);
|
|
130
|
+
return sources;
|
|
131
|
+
}
|
|
132
|
+
function asRecord(value) {
|
|
133
|
+
return value !== null && (typeof value === "object" || typeof value === "function")
|
|
134
|
+
? value
|
|
135
|
+
: undefined;
|
|
136
|
+
}
|
|
137
|
+
function firstString(sources, keys) {
|
|
138
|
+
for (const source of sources) {
|
|
139
|
+
for (const key of keys) {
|
|
140
|
+
const value = source[key];
|
|
141
|
+
if (typeof value === "string" && value.trim())
|
|
142
|
+
return value.trim();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
function firstScalarString(sources, keys) {
|
|
148
|
+
for (const source of sources) {
|
|
149
|
+
for (const key of keys) {
|
|
150
|
+
const value = source[key];
|
|
151
|
+
if ((typeof value === "string" || typeof value === "number") && String(value).trim()) {
|
|
152
|
+
return String(value).trim();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
function firstHttpStatus(sources) {
|
|
159
|
+
for (const source of sources) {
|
|
160
|
+
for (const key of ["status", "statusCode", "httpStatus"]) {
|
|
161
|
+
const value = source[key];
|
|
162
|
+
const numeric = typeof value === "number" ? value : Number(value);
|
|
163
|
+
if (Number.isInteger(numeric) && numeric >= 100 && numeric <= 599)
|
|
164
|
+
return numeric;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
169
|
+
function sanitizeControlledIdentifier(value) {
|
|
170
|
+
const normalized = value.trim();
|
|
171
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._:/@+-]*$/.test(normalized))
|
|
172
|
+
return "unknown";
|
|
173
|
+
return normalized.slice(0, MAX_FIELD_CHARS);
|
|
174
|
+
}
|
|
175
|
+
function sanitizeErrorCode(value) {
|
|
176
|
+
if (!value)
|
|
177
|
+
return undefined;
|
|
178
|
+
return SAFE_ERROR_CODES.get(value.trim().toLowerCase());
|
|
179
|
+
}
|
|
180
|
+
function sanitizeParameterName(value) {
|
|
181
|
+
if (!value)
|
|
182
|
+
return undefined;
|
|
183
|
+
const normalized = value.trim();
|
|
184
|
+
return SAFE_PARAMETER_NAMES.has(normalized) ? normalized : undefined;
|
|
185
|
+
}
|
|
@@ -17,6 +17,7 @@ const KIMI_TOGGLE_THINKING_FAMILY = new Set(["kimi-k2.5", "kimi-k2.6"]);
|
|
|
17
17
|
const KIMI_K26_DEFAULT_MAX_TOKENS = 32768;
|
|
18
18
|
const MINIMAX_M3_FAMILY = new Set(["MiniMax-M3"]);
|
|
19
19
|
const DEEPSEEK_V4_FAMILY = new Set([
|
|
20
|
+
"deepseek-flash",
|
|
20
21
|
"deepseek-v4-flash",
|
|
21
22
|
"deepseek-v4-flash-vision-exp",
|
|
22
23
|
"deepseek-v4-pro",
|
package/dist/provider.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import OpenAI from "openai";
|
|
7
7
|
import { appendFileSync } from "node:fs";
|
|
8
8
|
import { createAnthropicMessagesProvider } from "./provider-anthropic.js";
|
|
9
|
-
import { createArkResponsesProvider } from "./provider-ark-responses.js";
|
|
9
|
+
import { createArkResponsesProvider, createOpenAIResponsesProvider } from "./provider-ark-responses.js";
|
|
10
10
|
import { createAiSdkProvider } from "./provider-ai-sdk.js";
|
|
11
11
|
import { createOpenAICodexProvider, isOpenAICodexBaseUrl } from "./provider-openai-codex.js";
|
|
12
12
|
import { buildGrokSubscriptionHeaders, createGrokSubscriptionFetch, isGrokSubscriptionBaseUrl, } from "./provider-grok.js";
|
|
@@ -89,6 +89,9 @@ export function createProviderInstance(options) {
|
|
|
89
89
|
if (protocol === "ark-responses") {
|
|
90
90
|
return createArkResponsesProvider(options);
|
|
91
91
|
}
|
|
92
|
+
if (protocol === "openai-responses") {
|
|
93
|
+
return createOpenAIResponsesProvider(options);
|
|
94
|
+
}
|
|
92
95
|
if (protocol === "ai-sdk") {
|
|
93
96
|
return createAiSdkProvider(options);
|
|
94
97
|
}
|
|
@@ -273,6 +276,9 @@ function resolveProviderProtocol(options) {
|
|
|
273
276
|
return options.protocol;
|
|
274
277
|
const providerId = (options.providerId || "").toLowerCase();
|
|
275
278
|
const baseURL = (options.baseURL || "").toLowerCase();
|
|
279
|
+
if (providerId === "opencode-zen") {
|
|
280
|
+
return "openai-responses";
|
|
281
|
+
}
|
|
276
282
|
if (providerId === "doubao"
|
|
277
283
|
&& baseURL.replace(/\/+$/, "") === "https://ark.cn-beijing.volces.com/api/v3") {
|
|
278
284
|
return "ark-responses";
|
package/dist/sdk/index.js
CHANGED
|
@@ -417,6 +417,11 @@ export class BubbleSdk {
|
|
|
417
417
|
if (message.role === "assistant")
|
|
418
418
|
recordMemoryCitations(cwd, message.content);
|
|
419
419
|
},
|
|
420
|
+
onProviderError: (error) => {
|
|
421
|
+
if (this.turnCoordinator.isDeleted(sessionId))
|
|
422
|
+
return;
|
|
423
|
+
session.appendProviderError(error);
|
|
424
|
+
},
|
|
420
425
|
onCompactionApplied: (summary) => {
|
|
421
426
|
if (this.turnCoordinator.isDeleted(sessionId))
|
|
422
427
|
return;
|
package/dist/session-log.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Message } from "./types.js";
|
|
2
|
-
import type { SessionLogEntry, SessionMarkerKind, SessionMetadata, SessionSummaryEntry } from "./session-types.js";
|
|
2
|
+
import type { SessionLogEntry, SessionMarkerKind, SessionMetadata, SessionProviderErrorEntry, SessionSummaryEntry } from "./session-types.js";
|
|
3
|
+
import type { SanitizedProviderError } from "./provider-error-record.js";
|
|
3
4
|
export declare class SessionLog {
|
|
4
5
|
private entries;
|
|
5
6
|
load(lines: string[]): void;
|
|
@@ -10,5 +11,6 @@ export declare class SessionLog {
|
|
|
10
11
|
appendMessage(message: Message): SessionLogEntry[];
|
|
11
12
|
appendSummary(summary: string): SessionSummaryEntry;
|
|
12
13
|
appendMarker(kind: SessionMarkerKind, value: string): SessionLogEntry;
|
|
14
|
+
appendProviderError(error: SanitizedProviderError): SessionProviderErrorEntry;
|
|
13
15
|
toMessages(): Message[];
|
|
14
16
|
}
|
package/dist/session-log.js
CHANGED
|
@@ -71,6 +71,16 @@ export class SessionLog {
|
|
|
71
71
|
this.entries.push(entry);
|
|
72
72
|
return entry;
|
|
73
73
|
}
|
|
74
|
+
appendProviderError(error) {
|
|
75
|
+
const entry = {
|
|
76
|
+
id: nextEntryId(this.entries),
|
|
77
|
+
type: "provider_error",
|
|
78
|
+
error,
|
|
79
|
+
timestamp: Date.now(),
|
|
80
|
+
};
|
|
81
|
+
this.entries.push(entry);
|
|
82
|
+
return entry;
|
|
83
|
+
}
|
|
74
84
|
toMessages() {
|
|
75
85
|
const messages = [];
|
|
76
86
|
let latestSummaryIndex = -1;
|
|
@@ -217,6 +227,7 @@ function isSessionLogEntry(entry) {
|
|
|
217
227
|
"assistant_message",
|
|
218
228
|
"tool_call",
|
|
219
229
|
"tool_result",
|
|
230
|
+
"provider_error",
|
|
220
231
|
].includes(entry.type);
|
|
221
232
|
}
|
|
222
233
|
function nextEntryId(entries) {
|
package/dist/session-types.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AssistantMessage, Message, ThinkingLevel, ToolCall, ToolMessage, UserMessage } from "./types.js";
|
|
2
2
|
import type { GoalState } from "./goal/store.js";
|
|
3
|
+
import type { SanitizedProviderError } from "./provider-error-record.js";
|
|
3
4
|
export interface ExternalRuntimeSessionMetadata {
|
|
4
5
|
/** External agent runtime that owns the conversational state. */
|
|
5
6
|
id: "grok";
|
|
@@ -65,7 +66,11 @@ export interface SessionToolResultEntry extends BaseSessionLogEntry {
|
|
|
65
66
|
type: "tool_result";
|
|
66
67
|
message: ToolMessage;
|
|
67
68
|
}
|
|
68
|
-
export
|
|
69
|
+
export interface SessionProviderErrorEntry extends BaseSessionLogEntry {
|
|
70
|
+
type: "provider_error";
|
|
71
|
+
error: SanitizedProviderError;
|
|
72
|
+
}
|
|
73
|
+
export type SessionLogEntry = SessionMetadataEntry | SessionSummaryEntry | SessionMarkerEntry | SessionUserMessageEntry | SessionAssistantMessageEntry | SessionToolCallEntry | SessionToolResultEntry | SessionProviderErrorEntry;
|
|
69
74
|
export interface LegacySessionEntry {
|
|
70
75
|
id: string;
|
|
71
76
|
type: "metadata" | "message" | "compaction";
|
package/dist/session.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { CheckpointStore } from "./checkpoints.js";
|
|
|
5
5
|
import { type CompactOptions, type CompactResult } from "./context/compact.js";
|
|
6
6
|
import type { Message } from "./types.js";
|
|
7
7
|
import type { SessionLogEntry, SessionMarkerKind, SessionMetadata } from "./session-types.js";
|
|
8
|
+
import type { SanitizedProviderError } from "./provider-error-record.js";
|
|
8
9
|
export interface SessionSummary {
|
|
9
10
|
file: string;
|
|
10
11
|
name: string;
|
|
@@ -62,6 +63,7 @@ export declare class SessionManager {
|
|
|
62
63
|
appendMessage(message: Message): void;
|
|
63
64
|
appendCompaction(summary: string): void;
|
|
64
65
|
appendMarker(kind: SessionMarkerKind, value: string): void;
|
|
66
|
+
appendProviderError(error: SanitizedProviderError): void;
|
|
65
67
|
compact(options?: CompactOptions): CompactResult;
|
|
66
68
|
/**
|
|
67
69
|
* Inspect whether the session is large enough to compact and, if so, return
|