@bubblebrain-ai/bubble 0.0.55 → 0.0.56

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.
@@ -36,8 +36,11 @@ export function estimateMessageTokens(message, providerId) {
36
36
  }
37
37
  }
38
38
  function estimateProviderMetadataOverhead(metadata, providerId) {
39
- const blocks = metadata?.anthropic?.contentBlocks;
40
- if (!blocks || blocks.length === 0)
39
+ const blocks = [
40
+ ...(metadata?.anthropic?.contentBlocks ?? []),
41
+ ...(metadata?.openai?.contentBlocks ?? []),
42
+ ];
43
+ if (blocks.length === 0)
41
44
  return 0;
42
45
  const estimate = (text) => estimateTextTokens(text, providerId);
43
46
  return blocks.reduce((sum, block) => {
@@ -46,6 +49,8 @@ function estimateProviderMetadataOverhead(metadata, providerId) {
46
49
  overhead += estimate(block.signature);
47
50
  if (block.type === "redacted_thinking" && typeof block.data === "string")
48
51
  overhead += estimate(block.data);
52
+ if (block.type === "reasoning" && typeof block.encrypted_content === "string")
53
+ overhead += estimate(block.encrypted_content);
49
54
  return sum + overhead;
50
55
  }, 0);
51
56
  }
@@ -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;
@@ -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,6 +119,9 @@ 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 },
Binary file
@@ -1,5 +1,5 @@
1
1
  import type { Provider, ProviderMessage, StreamChunk, ThinkingLevel, TokenUsage, ToolChoiceMode, ToolDefinition } from "./types.js";
2
- export interface ArkResponsesProviderOptions {
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 translateArkResponsesStream(response: Response): AsyncIterable<StreamChunk>;
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 = buildArkResponsesBody(messages, {
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 = buildArkResponsesBody(messages, {
27
- model: chatOptions?.model ?? DEFAULT_ARK_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) => convertMessageToArkResponsesInput(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 convertMessageToArkResponsesInput(message) {
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 fetch(resolveArkResponsesUrl(options.baseURL), {
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);
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";
@@ -299,20 +299,19 @@ export async function getImageFromClipboard() {
299
299
  }
300
300
  }
301
301
  async function getClipboardImageDarwin() {
302
- // Probe first — `as «class PNGf»` throws if clipboard has no image.
303
- try {
304
- await execFileAsync("osascript", ["-e", "the clipboard as «class PNGf»"], {
305
- timeout: 5000,
306
- });
307
- }
308
- catch {
309
- return null;
310
- }
311
302
  const tmp = path.join(os.tmpdir(), `bubble_clip_${Date.now()}_${process.pid}.png`);
312
303
  const script = `set png_data to (the clipboard as «class PNGf»)\n` +
313
304
  `set fp to open for access POSIX file "${tmp}" with write permission\n` +
305
+ `try\n` +
306
+ `set eof fp to 0\n` +
314
307
  `write png_data to fp\n` +
315
- `close access fp`;
308
+ `close access fp\n` +
309
+ `on error err_msg number err_num\n` +
310
+ `try\n` +
311
+ `close access fp\n` +
312
+ `end try\n` +
313
+ `error err_msg number err_num\n` +
314
+ `end try`;
316
315
  try {
317
316
  await execFileAsync("osascript", ["-e", script], { timeout: 5000 });
318
317
  const buf = await fs.readFile(tmp);
package/dist/types.d.ts CHANGED
@@ -18,12 +18,14 @@ export type ReasoningEffort = ThinkingLevel;
18
18
  export type ProviderRawContentBlock = Record<string, unknown> & {
19
19
  type: string;
20
20
  };
21
- export type ProviderMetadataProvider = "anthropic" | "google";
21
+ export type ProviderMetadataProvider = "anthropic" | "google" | "openai";
22
22
  export interface ProviderContentBlockStore {
23
23
  contentBlocks?: ProviderRawContentBlock[];
24
24
  }
25
25
  export interface AssistantProviderMetadata {
26
26
  anthropic?: ProviderContentBlockStore;
27
+ /** OpenAI Responses reasoning items captured for stateless tool-loop replay. */
28
+ openai?: ProviderContentBlockStore;
27
29
  /**
28
30
  * Gemini raw parts captured for replay: reasoning/tool-call parts carrying
29
31
  * thoughtSignature so multi-turn thinking round-trips through our own
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bubblebrain-ai/bubble",
3
- "version": "0.0.55",
3
+ "version": "0.0.56",
4
4
  "description": "A terminal coding agent",
5
5
  "type": "module",
6
6
  "engines": {