@bubblebrain-ai/bubble 0.0.1

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.
Files changed (248) hide show
  1. package/README.md +70 -0
  2. package/dist/agent/evidence-tracker.d.ts +15 -0
  3. package/dist/agent/evidence-tracker.js +93 -0
  4. package/dist/agent/execution-governor.d.ts +30 -0
  5. package/dist/agent/execution-governor.js +169 -0
  6. package/dist/agent/subtask-policy.d.ts +14 -0
  7. package/dist/agent/subtask-policy.js +60 -0
  8. package/dist/agent/task-classifier.d.ts +3 -0
  9. package/dist/agent/task-classifier.js +36 -0
  10. package/dist/agent/tool-arbiter.d.ts +7 -0
  11. package/dist/agent/tool-arbiter.js +33 -0
  12. package/dist/agent/tool-intent.d.ts +20 -0
  13. package/dist/agent/tool-intent.js +176 -0
  14. package/dist/agent.d.ts +95 -0
  15. package/dist/agent.js +672 -0
  16. package/dist/approval/controller.d.ts +48 -0
  17. package/dist/approval/controller.js +78 -0
  18. package/dist/approval/danger.d.ts +13 -0
  19. package/dist/approval/danger.js +55 -0
  20. package/dist/approval/diff-hunks.d.ts +12 -0
  21. package/dist/approval/diff-hunks.js +32 -0
  22. package/dist/approval/session-cache.d.ts +35 -0
  23. package/dist/approval/session-cache.js +68 -0
  24. package/dist/approval/tool-helper.d.ts +14 -0
  25. package/dist/approval/tool-helper.js +32 -0
  26. package/dist/approval/types.d.ts +56 -0
  27. package/dist/approval/types.js +8 -0
  28. package/dist/bubble-home.d.ts +8 -0
  29. package/dist/bubble-home.js +19 -0
  30. package/dist/cli.d.ts +19 -0
  31. package/dist/cli.js +82 -0
  32. package/dist/config.d.ts +41 -0
  33. package/dist/config.js +144 -0
  34. package/dist/context/budget.d.ts +21 -0
  35. package/dist/context/budget.js +72 -0
  36. package/dist/context/compact-llm.d.ts +16 -0
  37. package/dist/context/compact-llm.js +132 -0
  38. package/dist/context/compact.d.ts +15 -0
  39. package/dist/context/compact.js +251 -0
  40. package/dist/context/overflow.d.ts +9 -0
  41. package/dist/context/overflow.js +46 -0
  42. package/dist/context/projector.d.ts +26 -0
  43. package/dist/context/projector.js +150 -0
  44. package/dist/context/prune.d.ts +9 -0
  45. package/dist/context/prune.js +111 -0
  46. package/dist/lsp/config.d.ts +18 -0
  47. package/dist/lsp/config.js +58 -0
  48. package/dist/lsp/diagnostics.d.ts +24 -0
  49. package/dist/lsp/diagnostics.js +103 -0
  50. package/dist/lsp/index.d.ts +3 -0
  51. package/dist/lsp/index.js +3 -0
  52. package/dist/lsp/service.d.ts +85 -0
  53. package/dist/lsp/service.js +695 -0
  54. package/dist/main.d.ts +5 -0
  55. package/dist/main.js +352 -0
  56. package/dist/mcp/client.d.ts +68 -0
  57. package/dist/mcp/client.js +163 -0
  58. package/dist/mcp/config.d.ts +26 -0
  59. package/dist/mcp/config.js +127 -0
  60. package/dist/mcp/manager.d.ts +55 -0
  61. package/dist/mcp/manager.js +296 -0
  62. package/dist/mcp/name.d.ts +26 -0
  63. package/dist/mcp/name.js +40 -0
  64. package/dist/mcp/transports.d.ts +53 -0
  65. package/dist/mcp/transports.js +248 -0
  66. package/dist/mcp/types.d.ts +111 -0
  67. package/dist/mcp/types.js +14 -0
  68. package/dist/memory/db.d.ts +62 -0
  69. package/dist/memory/db.js +313 -0
  70. package/dist/memory/index.d.ts +9 -0
  71. package/dist/memory/index.js +9 -0
  72. package/dist/memory/paths.d.ts +18 -0
  73. package/dist/memory/paths.js +38 -0
  74. package/dist/memory/phase1.d.ts +23 -0
  75. package/dist/memory/phase1.js +172 -0
  76. package/dist/memory/phase2.d.ts +19 -0
  77. package/dist/memory/phase2.js +100 -0
  78. package/dist/memory/prompts.d.ts +19 -0
  79. package/dist/memory/prompts.js +99 -0
  80. package/dist/memory/reset.d.ts +1 -0
  81. package/dist/memory/reset.js +13 -0
  82. package/dist/memory/start.d.ts +24 -0
  83. package/dist/memory/start.js +50 -0
  84. package/dist/memory/storage.d.ts +10 -0
  85. package/dist/memory/storage.js +82 -0
  86. package/dist/memory/store.d.ts +43 -0
  87. package/dist/memory/store.js +193 -0
  88. package/dist/memory/usage.d.ts +1 -0
  89. package/dist/memory/usage.js +38 -0
  90. package/dist/model-catalog.d.ts +20 -0
  91. package/dist/model-catalog.js +99 -0
  92. package/dist/model-config.d.ts +32 -0
  93. package/dist/model-config.js +59 -0
  94. package/dist/model-pricing.d.ts +23 -0
  95. package/dist/model-pricing.js +46 -0
  96. package/dist/oauth/index.d.ts +3 -0
  97. package/dist/oauth/index.js +2 -0
  98. package/dist/oauth/openai-codex.d.ts +9 -0
  99. package/dist/oauth/openai-codex.js +173 -0
  100. package/dist/oauth/storage.d.ts +18 -0
  101. package/dist/oauth/storage.js +60 -0
  102. package/dist/oauth/types.d.ts +15 -0
  103. package/dist/oauth/types.js +1 -0
  104. package/dist/orchestrator/default-hooks.d.ts +2 -0
  105. package/dist/orchestrator/default-hooks.js +96 -0
  106. package/dist/orchestrator/hooks.d.ts +78 -0
  107. package/dist/orchestrator/hooks.js +52 -0
  108. package/dist/orchestrator/workflow.d.ts +10 -0
  109. package/dist/orchestrator/workflow.js +22 -0
  110. package/dist/permission/mode.d.ts +23 -0
  111. package/dist/permission/mode.js +20 -0
  112. package/dist/permissions/rule.d.ts +39 -0
  113. package/dist/permissions/rule.js +234 -0
  114. package/dist/permissions/settings.d.ts +71 -0
  115. package/dist/permissions/settings.js +202 -0
  116. package/dist/permissions/types.d.ts +61 -0
  117. package/dist/permissions/types.js +14 -0
  118. package/dist/prompt/compose.d.ts +12 -0
  119. package/dist/prompt/compose.js +67 -0
  120. package/dist/prompt/environment.d.ts +12 -0
  121. package/dist/prompt/environment.js +38 -0
  122. package/dist/prompt/provider-prompts/anthropic.d.ts +1 -0
  123. package/dist/prompt/provider-prompts/anthropic.js +5 -0
  124. package/dist/prompt/provider-prompts/codex.d.ts +1 -0
  125. package/dist/prompt/provider-prompts/codex.js +5 -0
  126. package/dist/prompt/provider-prompts/default.d.ts +1 -0
  127. package/dist/prompt/provider-prompts/default.js +6 -0
  128. package/dist/prompt/provider-prompts/gemini.d.ts +1 -0
  129. package/dist/prompt/provider-prompts/gemini.js +5 -0
  130. package/dist/prompt/provider-prompts/gpt.d.ts +1 -0
  131. package/dist/prompt/provider-prompts/gpt.js +5 -0
  132. package/dist/prompt/reminders.d.ts +30 -0
  133. package/dist/prompt/reminders.js +164 -0
  134. package/dist/prompt/runtime.d.ts +12 -0
  135. package/dist/prompt/runtime.js +31 -0
  136. package/dist/prompt/skills.d.ts +2 -0
  137. package/dist/prompt/skills.js +4 -0
  138. package/dist/provider-openai-codex.d.ts +14 -0
  139. package/dist/provider-openai-codex.js +409 -0
  140. package/dist/provider-registry.d.ts +56 -0
  141. package/dist/provider-registry.js +244 -0
  142. package/dist/provider-transform.d.ts +10 -0
  143. package/dist/provider-transform.js +69 -0
  144. package/dist/provider.d.ts +31 -0
  145. package/dist/provider.js +269 -0
  146. package/dist/question/controller.d.ts +22 -0
  147. package/dist/question/controller.js +97 -0
  148. package/dist/question/index.d.ts +2 -0
  149. package/dist/question/index.js +2 -0
  150. package/dist/question/types.d.ts +42 -0
  151. package/dist/question/types.js +6 -0
  152. package/dist/session-log.d.ts +16 -0
  153. package/dist/session-log.js +267 -0
  154. package/dist/session-types.d.ts +55 -0
  155. package/dist/session-types.js +1 -0
  156. package/dist/session.d.ts +32 -0
  157. package/dist/session.js +135 -0
  158. package/dist/skills/discovery.d.ts +12 -0
  159. package/dist/skills/discovery.js +148 -0
  160. package/dist/skills/format.d.ts +2 -0
  161. package/dist/skills/format.js +47 -0
  162. package/dist/skills/frontmatter.d.ts +5 -0
  163. package/dist/skills/frontmatter.js +60 -0
  164. package/dist/skills/invocation.d.ts +8 -0
  165. package/dist/skills/invocation.js +51 -0
  166. package/dist/skills/registry.d.ts +17 -0
  167. package/dist/skills/registry.js +42 -0
  168. package/dist/skills/types.d.ts +32 -0
  169. package/dist/skills/types.js +1 -0
  170. package/dist/slash-commands/commands.d.ts +7 -0
  171. package/dist/slash-commands/commands.js +779 -0
  172. package/dist/slash-commands/index.d.ts +4 -0
  173. package/dist/slash-commands/index.js +8 -0
  174. package/dist/slash-commands/registry.d.ts +31 -0
  175. package/dist/slash-commands/registry.js +70 -0
  176. package/dist/slash-commands/types.d.ts +44 -0
  177. package/dist/slash-commands/types.js +1 -0
  178. package/dist/slash-commands/unified.d.ts +38 -0
  179. package/dist/slash-commands/unified.js +38 -0
  180. package/dist/system-prompt.d.ts +34 -0
  181. package/dist/system-prompt.js +7 -0
  182. package/dist/tools/bash.d.ts +6 -0
  183. package/dist/tools/bash.js +135 -0
  184. package/dist/tools/edit.d.ts +16 -0
  185. package/dist/tools/edit.js +95 -0
  186. package/dist/tools/exa-mcp.d.ts +3 -0
  187. package/dist/tools/exa-mcp.js +74 -0
  188. package/dist/tools/exit-plan-mode.d.ts +17 -0
  189. package/dist/tools/exit-plan-mode.js +68 -0
  190. package/dist/tools/glob.d.ts +5 -0
  191. package/dist/tools/glob.js +129 -0
  192. package/dist/tools/grep.d.ts +5 -0
  193. package/dist/tools/grep.js +111 -0
  194. package/dist/tools/index.d.ts +36 -0
  195. package/dist/tools/index.js +59 -0
  196. package/dist/tools/lsp.d.ts +4 -0
  197. package/dist/tools/lsp.js +92 -0
  198. package/dist/tools/memory.d.ts +3 -0
  199. package/dist/tools/memory.js +90 -0
  200. package/dist/tools/question.d.ts +3 -0
  201. package/dist/tools/question.js +174 -0
  202. package/dist/tools/read.d.ts +7 -0
  203. package/dist/tools/read.js +83 -0
  204. package/dist/tools/sensitive-paths.d.ts +3 -0
  205. package/dist/tools/sensitive-paths.js +24 -0
  206. package/dist/tools/skill.d.ts +5 -0
  207. package/dist/tools/skill.js +51 -0
  208. package/dist/tools/task.d.ts +2 -0
  209. package/dist/tools/task.js +57 -0
  210. package/dist/tools/todo.d.ts +12 -0
  211. package/dist/tools/todo.js +151 -0
  212. package/dist/tools/tool-search.d.ts +23 -0
  213. package/dist/tools/tool-search.js +124 -0
  214. package/dist/tools/web-fetch.d.ts +6 -0
  215. package/dist/tools/web-fetch.js +75 -0
  216. package/dist/tools/web-search.d.ts +5 -0
  217. package/dist/tools/web-search.js +49 -0
  218. package/dist/tools/write.d.ts +11 -0
  219. package/dist/tools/write.js +77 -0
  220. package/dist/tui/display-history.d.ts +35 -0
  221. package/dist/tui/display-history.js +243 -0
  222. package/dist/tui/file-mentions.d.ts +29 -0
  223. package/dist/tui/file-mentions.js +174 -0
  224. package/dist/tui/image-paste.d.ts +54 -0
  225. package/dist/tui/image-paste.js +288 -0
  226. package/dist/tui/markdown-theme-rules.d.ts +23 -0
  227. package/dist/tui/markdown-theme-rules.js +164 -0
  228. package/dist/tui/markdown-theme.d.ts +5 -0
  229. package/dist/tui/markdown-theme.js +27 -0
  230. package/dist/tui/opencode-spinner.d.ts +21 -0
  231. package/dist/tui/opencode-spinner.js +216 -0
  232. package/dist/tui/prompt-keybindings.d.ts +41 -0
  233. package/dist/tui/prompt-keybindings.js +28 -0
  234. package/dist/tui/recent-activity.d.ts +8 -0
  235. package/dist/tui/recent-activity.js +71 -0
  236. package/dist/tui/run.d.ts +39 -0
  237. package/dist/tui/run.js +5696 -0
  238. package/dist/tui/sidebar-mcp.d.ts +31 -0
  239. package/dist/tui/sidebar-mcp.js +62 -0
  240. package/dist/tui/sidebar-state.d.ts +12 -0
  241. package/dist/tui/sidebar-state.js +69 -0
  242. package/dist/types.d.ts +219 -0
  243. package/dist/types.js +4 -0
  244. package/dist/variant/thinking-level.d.ts +5 -0
  245. package/dist/variant/thinking-level.js +25 -0
  246. package/dist/variant/variant-resolver.d.ts +4 -0
  247. package/dist/variant/variant-resolver.js +12 -0
  248. package/package.json +47 -0
@@ -0,0 +1,12 @@
1
+ import type { PermissionMode, ThinkingLevel } from "../types.js";
2
+ export interface RuntimePromptOptions {
3
+ thinkingLevel?: ThinkingLevel;
4
+ /**
5
+ * Kept for API compatibility. Agent mode is no longer baked into the static
6
+ * system prompt — mode changes are signalled via <system-reminder> injections
7
+ * (see src/prompt/reminders.ts) so the base prompt stays stable for caching.
8
+ */
9
+ mode?: PermissionMode;
10
+ guidelines?: string[];
11
+ }
12
+ export declare function buildRuntimePrompt(options?: RuntimePromptOptions): string;
@@ -0,0 +1,31 @@
1
+ const defaultGuidelines = [
2
+ "Before editing or writing files, read them first if they exist",
3
+ "Use edit for targeted changes to existing files; use write for creating new files",
4
+ "Be concise in your responses",
5
+ "Show file paths clearly when working with files",
6
+ "Prefer structured search tools over bash for repository searches whenever possible",
7
+ "Do not repeat near-identical searches when they are not producing new evidence",
8
+ "When investigating configuration or security questions, stop once the relevant load path, storage path, and exposure path are identified",
9
+ "Use the task tool for bounded investigative subproblems instead of letting the main loop churn on repeated exploratory searches",
10
+ ];
11
+ export function buildRuntimePrompt(options = {}) {
12
+ const thinkingLevel = options.thinkingLevel ?? "off";
13
+ const guidelines = dedupe(defaultGuidelines, options.guidelines ?? []);
14
+ return `Current thinking level: ${thinkingLevel}
15
+
16
+ Guidelines:
17
+ ${guidelines.map((item) => `- ${item}`).join("\n")}`;
18
+ }
19
+ function dedupe(...groups) {
20
+ const seen = new Set();
21
+ const result = [];
22
+ for (const group of groups) {
23
+ for (const item of group) {
24
+ if (!seen.has(item)) {
25
+ seen.add(item);
26
+ result.push(item);
27
+ }
28
+ }
29
+ }
30
+ return result;
31
+ }
@@ -0,0 +1,2 @@
1
+ import type { SkillSummary } from "../skills/types.js";
2
+ export declare function buildSkillsPrompt(skills?: SkillSummary[]): string;
@@ -0,0 +1,4 @@
1
+ import { formatSkillsPrompt } from "../skills/format.js";
2
+ export function buildSkillsPrompt(skills = []) {
3
+ return formatSkillsPrompt(skills);
4
+ }
@@ -0,0 +1,14 @@
1
+ import type { Provider, ThinkingLevel } from "./types.js";
2
+ export declare function isOpenAICodexBaseUrl(baseURL: string): boolean;
3
+ export declare function getOpenAICodexFallbackModels(): string[];
4
+ export declare function extractChatGptAccountId(accessToken: string): string | undefined;
5
+ export declare function createOpenAICodexProvider(options: {
6
+ providerId?: string;
7
+ apiKey: string;
8
+ baseURL: string;
9
+ thinkingLevel?: ThinkingLevel;
10
+ }): Provider;
11
+ export declare function fetchOpenAICodexModels(options: {
12
+ baseURL: string;
13
+ accessToken: string;
14
+ }): Promise<string[]>;
@@ -0,0 +1,409 @@
1
+ import { listBuiltinModels } from "./model-catalog.js";
2
+ import { resolveProviderRequestConfig } from "./provider-transform.js";
3
+ const DEFAULT_CODEX_BASE_URL = "https://chatgpt.com/backend-api";
4
+ const OPENAI_BETA_RESPONSES = "responses=experimental";
5
+ const CODEX_CLIENT_VERSION = "0.121.0";
6
+ const MODEL_DISCOVERY_PATHS = [
7
+ `/codex/models?client_version=${CODEX_CLIENT_VERSION}`,
8
+ "/models",
9
+ ];
10
+ export function isOpenAICodexBaseUrl(baseURL) {
11
+ const normalized = baseURL.trim().replace(/\/+$/, "");
12
+ return normalized === DEFAULT_CODEX_BASE_URL || normalized.startsWith(`${DEFAULT_CODEX_BASE_URL}/`);
13
+ }
14
+ export function getOpenAICodexFallbackModels() {
15
+ return listBuiltinModels("openai-codex").map((model) => model.id);
16
+ }
17
+ export function extractChatGptAccountId(accessToken) {
18
+ try {
19
+ const parts = accessToken.split(".");
20
+ if (parts.length !== 3)
21
+ return undefined;
22
+ const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf-8"));
23
+ const auth = payload["https://api.openai.com/auth"];
24
+ if (typeof auth?.chatgpt_account_id === "string" && auth.chatgpt_account_id) {
25
+ return auth.chatgpt_account_id;
26
+ }
27
+ if (typeof auth?.account_id === "string" && auth.account_id) {
28
+ return auth.account_id;
29
+ }
30
+ return undefined;
31
+ }
32
+ catch {
33
+ return undefined;
34
+ }
35
+ }
36
+ export function createOpenAICodexProvider(options) {
37
+ const sessionId = globalThis.crypto?.randomUUID?.() ?? `bubble_${Date.now()}`;
38
+ async function* streamChat(messages, chatOptions) {
39
+ const requestConfig = resolveProviderRequestConfig("openai-codex", chatOptions.model, chatOptions.thinkingLevel ?? options.thinkingLevel ?? "off");
40
+ const accountId = extractChatGptAccountId(options.apiKey);
41
+ if (!accountId) {
42
+ throw new Error("Failed to extract chatgpt_account_id from ChatGPT OAuth token.");
43
+ }
44
+ const response = await fetch(resolveCodexUrl(options.baseURL), {
45
+ method: "POST",
46
+ headers: buildSseHeaders(options.apiKey, accountId, sessionId),
47
+ body: JSON.stringify(buildRequestBody(messages, {
48
+ model: chatOptions.model,
49
+ tools: chatOptions.tools,
50
+ reasoningEffort: requestConfig.reasoningEffort,
51
+ sessionId,
52
+ })),
53
+ });
54
+ if (!response.ok) {
55
+ const errorText = await response.text().catch(() => "");
56
+ throw new Error(`${response.status} status code${errorText ? `: ${errorText}` : " (no body)"}`);
57
+ }
58
+ let currentToolCall;
59
+ for await (const event of parseSse(response)) {
60
+ const type = typeof event.type === "string" ? event.type : undefined;
61
+ if (!type)
62
+ continue;
63
+ if (type === "error") {
64
+ const message = typeof event.message === "string" ? event.message : JSON.stringify(event);
65
+ throw new Error(message);
66
+ }
67
+ if (type === "response.failed") {
68
+ const message = typeof event.response?.error?.message === "string"
69
+ ? event.response.error.message
70
+ : "Codex response failed";
71
+ throw new Error(message);
72
+ }
73
+ if (type === "response.output_item.added") {
74
+ const item = event.item;
75
+ if (item?.type === "function_call" && typeof item.call_id === "string" && typeof item.name === "string") {
76
+ currentToolCall = {
77
+ id: item.call_id,
78
+ name: item.name,
79
+ args: typeof item.arguments === "string" ? item.arguments : "",
80
+ started: true,
81
+ };
82
+ yield {
83
+ type: "tool_call",
84
+ id: currentToolCall.id,
85
+ name: currentToolCall.name,
86
+ arguments: "",
87
+ isStart: true,
88
+ isEnd: false,
89
+ };
90
+ }
91
+ continue;
92
+ }
93
+ if (type === "response.output_text.delta" || type === "response.refusal.delta") {
94
+ const delta = typeof event.delta === "string" ? event.delta : "";
95
+ if (delta) {
96
+ yield { type: "text", content: delta };
97
+ }
98
+ continue;
99
+ }
100
+ if (type === "response.reasoning_summary_text.delta") {
101
+ const delta = typeof event.delta === "string" ? event.delta : "";
102
+ if (delta) {
103
+ yield { type: "reasoning_delta", content: delta };
104
+ }
105
+ continue;
106
+ }
107
+ if (type === "response.function_call_arguments.delta" && currentToolCall) {
108
+ const delta = typeof event.delta === "string" ? event.delta : "";
109
+ if (delta) {
110
+ currentToolCall.args += delta;
111
+ yield {
112
+ type: "tool_call",
113
+ id: currentToolCall.id,
114
+ name: currentToolCall.name,
115
+ arguments: delta,
116
+ isStart: false,
117
+ isEnd: false,
118
+ };
119
+ }
120
+ continue;
121
+ }
122
+ if (type === "response.function_call_arguments.done" && currentToolCall) {
123
+ const finalArgs = typeof event.arguments === "string" ? event.arguments : currentToolCall.args;
124
+ if (finalArgs.startsWith(currentToolCall.args)) {
125
+ const tail = finalArgs.slice(currentToolCall.args.length);
126
+ if (tail) {
127
+ currentToolCall.args = finalArgs;
128
+ yield {
129
+ type: "tool_call",
130
+ id: currentToolCall.id,
131
+ name: currentToolCall.name,
132
+ arguments: tail,
133
+ isStart: false,
134
+ isEnd: false,
135
+ };
136
+ }
137
+ }
138
+ else {
139
+ currentToolCall.args = finalArgs;
140
+ }
141
+ continue;
142
+ }
143
+ if (type === "response.output_item.done" && currentToolCall) {
144
+ const item = event.item;
145
+ if (item?.type === "function_call" && item.call_id === currentToolCall.id) {
146
+ yield {
147
+ type: "tool_call",
148
+ id: currentToolCall.id,
149
+ name: currentToolCall.name,
150
+ arguments: "",
151
+ isStart: false,
152
+ isEnd: true,
153
+ };
154
+ currentToolCall = undefined;
155
+ }
156
+ continue;
157
+ }
158
+ if (type === "response.completed" || type === "response.done" || type === "response.incomplete") {
159
+ const usage = event.response?.usage;
160
+ if (usage) {
161
+ yield {
162
+ type: "usage",
163
+ usage: {
164
+ promptTokens: typeof usage.input_tokens === "number" ? usage.input_tokens : 0,
165
+ completionTokens: typeof usage.output_tokens === "number" ? usage.output_tokens : 0,
166
+ reasoningTokens: typeof usage.output_tokens_details?.reasoning_tokens === "number"
167
+ ? usage.output_tokens_details.reasoning_tokens
168
+ : undefined,
169
+ totalTokens: typeof usage.total_tokens === "number" ? usage.total_tokens : undefined,
170
+ },
171
+ };
172
+ }
173
+ continue;
174
+ }
175
+ }
176
+ yield { type: "done" };
177
+ }
178
+ async function complete(messages, chatOptions) {
179
+ let content = "";
180
+ for await (const chunk of streamChat(messages, {
181
+ model: chatOptions?.model ?? "gpt-5.4",
182
+ temperature: chatOptions?.temperature,
183
+ thinkingLevel: chatOptions?.thinkingLevel,
184
+ })) {
185
+ if (chunk.type === "text") {
186
+ content += chunk.content;
187
+ }
188
+ }
189
+ return content;
190
+ }
191
+ return { streamChat, complete };
192
+ }
193
+ export async function fetchOpenAICodexModels(options) {
194
+ const accountId = extractChatGptAccountId(options.accessToken);
195
+ if (!accountId) {
196
+ return [];
197
+ }
198
+ for (const path of MODEL_DISCOVERY_PATHS) {
199
+ const response = await fetch(resolveRelativeUrl(options.baseURL, path), {
200
+ method: "GET",
201
+ headers: buildBaseHeaders(options.accessToken, accountId, globalThis.crypto?.randomUUID?.() ?? `bubble_${Date.now()}`, { accept: "application/json" }),
202
+ }).catch(() => undefined);
203
+ if (!response?.ok)
204
+ continue;
205
+ const payload = await response.json().catch(() => undefined);
206
+ const ids = extractModelIds(payload);
207
+ if (ids.length > 0) {
208
+ return sortCodexModelIds(ids);
209
+ }
210
+ }
211
+ return [];
212
+ }
213
+ function buildRequestBody(messages, options) {
214
+ const instructions = messages
215
+ .filter((message) => message.role === "system")
216
+ .map((message) => message.content)
217
+ .join("\n\n");
218
+ const input = messages.flatMap((message) => convertMessage(message));
219
+ const body = {
220
+ model: options.model,
221
+ store: false,
222
+ stream: true,
223
+ instructions: instructions || undefined,
224
+ input,
225
+ include: ["reasoning.encrypted_content"],
226
+ prompt_cache_key: options.sessionId,
227
+ tool_choice: "auto",
228
+ parallel_tool_calls: true,
229
+ text: { verbosity: "medium" },
230
+ };
231
+ if (options.tools && options.tools.length > 0) {
232
+ body.tools = options.tools.map((tool) => ({
233
+ type: "function",
234
+ name: tool.name,
235
+ description: tool.description,
236
+ parameters: tool.parameters,
237
+ }));
238
+ }
239
+ return body;
240
+ }
241
+ function convertMessage(message) {
242
+ if (message.role === "system") {
243
+ return [];
244
+ }
245
+ if (message.role === "user") {
246
+ if (typeof message.content === "string") {
247
+ return [{
248
+ role: "user",
249
+ content: [{ type: "input_text", text: message.content }],
250
+ }];
251
+ }
252
+ return [{
253
+ role: "user",
254
+ content: message.content.map((part) => {
255
+ if (part.type === "text") {
256
+ return { type: "input_text", text: part.text };
257
+ }
258
+ return { type: "input_image", detail: "auto", image_url: part.image_url.url };
259
+ }),
260
+ }];
261
+ }
262
+ if (message.role === "assistant") {
263
+ const items = [];
264
+ if (message.content) {
265
+ items.push({
266
+ type: "message",
267
+ role: "assistant",
268
+ status: "completed",
269
+ content: [{ type: "output_text", text: message.content, annotations: [] }],
270
+ });
271
+ }
272
+ if (message.toolCalls) {
273
+ for (const toolCall of message.toolCalls) {
274
+ items.push({
275
+ type: "function_call",
276
+ call_id: toolCall.id,
277
+ name: toolCall.name,
278
+ arguments: toolCall.arguments || "{}",
279
+ });
280
+ }
281
+ }
282
+ return items;
283
+ }
284
+ return [{
285
+ type: "function_call_output",
286
+ call_id: message.toolCallId,
287
+ output: message.content,
288
+ }];
289
+ }
290
+ async function* parseSse(response) {
291
+ if (!response.body)
292
+ return;
293
+ const reader = response.body.getReader();
294
+ const decoder = new TextDecoder();
295
+ let buffer = "";
296
+ try {
297
+ while (true) {
298
+ const { done, value } = await reader.read();
299
+ if (done)
300
+ break;
301
+ buffer += decoder.decode(value, { stream: true });
302
+ let boundary = buffer.indexOf("\n\n");
303
+ while (boundary >= 0) {
304
+ const chunk = buffer.slice(0, boundary);
305
+ buffer = buffer.slice(boundary + 2);
306
+ const data = chunk
307
+ .split("\n")
308
+ .filter((line) => line.startsWith("data:"))
309
+ .map((line) => line.slice(5).trim())
310
+ .join("\n")
311
+ .trim();
312
+ if (data && data !== "[DONE]") {
313
+ try {
314
+ yield JSON.parse(data);
315
+ }
316
+ catch {
317
+ // Ignore malformed events.
318
+ }
319
+ }
320
+ boundary = buffer.indexOf("\n\n");
321
+ }
322
+ }
323
+ }
324
+ finally {
325
+ try {
326
+ await reader.cancel();
327
+ }
328
+ catch {
329
+ // Ignore cleanup errors.
330
+ }
331
+ }
332
+ }
333
+ function buildBaseHeaders(accessToken, accountId, sessionId, extraHeaders) {
334
+ const headers = new Headers(extraHeaders);
335
+ headers.set("Authorization", `Bearer ${accessToken}`);
336
+ headers.set("ChatGPT-Account-Id", accountId);
337
+ headers.set("originator", "bubble");
338
+ headers.set("User-Agent", "bubble");
339
+ headers.set("session_id", sessionId);
340
+ return headers;
341
+ }
342
+ function buildSseHeaders(accessToken, accountId, sessionId) {
343
+ const headers = buildBaseHeaders(accessToken, accountId, sessionId, {
344
+ accept: "text/event-stream",
345
+ "content-type": "application/json",
346
+ });
347
+ headers.set("OpenAI-Beta", OPENAI_BETA_RESPONSES);
348
+ return headers;
349
+ }
350
+ function resolveCodexUrl(baseURL) {
351
+ const normalized = (baseURL.trim() || DEFAULT_CODEX_BASE_URL).replace(/\/+$/, "");
352
+ if (normalized.endsWith("/codex/responses"))
353
+ return normalized;
354
+ if (normalized.endsWith("/codex"))
355
+ return `${normalized}/responses`;
356
+ return `${normalized}/codex/responses`;
357
+ }
358
+ function resolveRelativeUrl(baseURL, path) {
359
+ const normalized = (baseURL.trim() || DEFAULT_CODEX_BASE_URL).replace(/\/+$/, "");
360
+ return `${normalized}${path}`;
361
+ }
362
+ function extractModelIds(payload) {
363
+ const ids = [];
364
+ const seen = new Set();
365
+ const maybeAdd = (value) => {
366
+ if (typeof value !== "string")
367
+ return;
368
+ if (!/^gpt-|^codex-/i.test(value))
369
+ return;
370
+ if (seen.has(value))
371
+ return;
372
+ seen.add(value);
373
+ ids.push(value);
374
+ };
375
+ const visit = (value) => {
376
+ if (Array.isArray(value)) {
377
+ for (const item of value)
378
+ visit(item);
379
+ return;
380
+ }
381
+ if (!value || typeof value !== "object") {
382
+ maybeAdd(value);
383
+ return;
384
+ }
385
+ const record = value;
386
+ maybeAdd(record.id);
387
+ maybeAdd(record.slug);
388
+ maybeAdd(record.model);
389
+ maybeAdd(record.model_slug);
390
+ for (const child of Object.values(record)) {
391
+ if (child !== record.id && child !== record.slug && child !== record.model && child !== record.model_slug) {
392
+ visit(child);
393
+ }
394
+ }
395
+ };
396
+ visit(payload);
397
+ return ids;
398
+ }
399
+ function sortCodexModelIds(ids) {
400
+ const preferredModels = getOpenAICodexFallbackModels();
401
+ const preferred = new Map(preferredModels.map((id, index) => [id, index]));
402
+ return [...ids].sort((left, right) => {
403
+ const leftRank = preferred.get(left) ?? Number.MAX_SAFE_INTEGER;
404
+ const rightRank = preferred.get(right) ?? Number.MAX_SAFE_INTEGER;
405
+ if (leftRank !== rightRank)
406
+ return leftRank - rightRank;
407
+ return left.localeCompare(right);
408
+ });
409
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Multi-provider registry.
3
+ *
4
+ * Supports OpenAI-compatible providers with dynamic or static model lists.
5
+ * Reads provider configuration from models.json first, then falls back to config.json.
6
+ */
7
+ import type { UserConfig } from "./config.js";
8
+ import { ModelConfig } from "./model-config.js";
9
+ import { AuthStorage } from "./oauth/index.js";
10
+ export interface ProviderProfile {
11
+ id: string;
12
+ name: string;
13
+ baseURL: string;
14
+ apiKey: string;
15
+ enabled: boolean;
16
+ authType?: "api" | "oauth";
17
+ }
18
+ export interface ModelInfo {
19
+ id: string;
20
+ name: string;
21
+ providerId: string;
22
+ }
23
+ export declare const BUILTIN_PROVIDERS: import("./model-catalog.js").BuiltinProviderDefinition[];
24
+ export declare const USER_VISIBLE_PROVIDER_IDS: string[];
25
+ export declare function isUserVisibleProvider(providerId: string): boolean;
26
+ export declare class ProviderRegistry {
27
+ private config;
28
+ private modelConfig;
29
+ private authStorage;
30
+ constructor(config: UserConfig);
31
+ getModelConfig(): ModelConfig;
32
+ getAuthStorage(): AuthStorage;
33
+ supportsOAuth(providerId: string): boolean;
34
+ private resolveOAuthAuthKey;
35
+ getDefaultModel(providerId: string, authType?: ProviderProfile["authType"]): string | undefined;
36
+ prepareProvider(providerId: string): Promise<void>;
37
+ getConfigured(): ProviderProfile[];
38
+ getEnabled(): ProviderProfile[];
39
+ getDefault(): ProviderProfile | undefined;
40
+ setDefault(id: string): void;
41
+ addProvider(id: string, apiKey: string): boolean;
42
+ removeProvider(id: string): void;
43
+ updateProviderKey(id: string, apiKey: string): void;
44
+ listModels(provider: ProviderProfile): Promise<ModelInfo[]>;
45
+ }
46
+ /** Encode a model selection as "providerId:modelId". */
47
+ export declare function encodeModel(providerId: string, modelId: string): string;
48
+ /** Decode "providerId:modelId" or legacy plain modelId. */
49
+ export declare function decodeModel(value: string): {
50
+ providerId?: string;
51
+ modelId: string;
52
+ };
53
+ /** Strip provider prefix for concise display. */
54
+ export declare function displayModel(model: string): string;
55
+ /** Normalize user input to provider:model format when possible. */
56
+ export declare function normalizeModel(model: string, defaultProvider?: string): string;