@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,244 @@
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 { BUILTIN_PROVIDERS as CATALOG_PROVIDERS, getBuiltinProvider, listBuiltinModels, } from "./model-catalog.js";
8
+ import { ModelConfig } from "./model-config.js";
9
+ import { AuthStorage } from "./oauth/index.js";
10
+ import { fetchOpenAICodexModels } from "./provider-openai-codex.js";
11
+ import { refreshOpenAICodex } from "./oauth/openai-codex.js";
12
+ export const BUILTIN_PROVIDERS = CATALOG_PROVIDERS;
13
+ export const USER_VISIBLE_PROVIDER_IDS = BUILTIN_PROVIDERS
14
+ .filter((provider) => provider.id !== "openrouter" && provider.id !== "openai-codex")
15
+ .map((provider) => provider.id);
16
+ export function isUserVisibleProvider(providerId) {
17
+ return USER_VISIBLE_PROVIDER_IDS.includes(providerId);
18
+ }
19
+ export class ProviderRegistry {
20
+ config;
21
+ modelConfig;
22
+ authStorage;
23
+ constructor(config) {
24
+ this.config = config;
25
+ this.modelConfig = new ModelConfig();
26
+ this.authStorage = new AuthStorage();
27
+ }
28
+ getModelConfig() {
29
+ return this.modelConfig;
30
+ }
31
+ getAuthStorage() {
32
+ return this.authStorage;
33
+ }
34
+ supportsOAuth(providerId) {
35
+ return !!getBuiltinProvider(providerId)?.supportsOAuth;
36
+ }
37
+ resolveOAuthAuthKey(providerId) {
38
+ if (providerId === "openai" && !this.authStorage.has("openai") && this.authStorage.has("openai-codex")) {
39
+ return "openai-codex";
40
+ }
41
+ return providerId;
42
+ }
43
+ getDefaultModel(providerId, authType = "api") {
44
+ const customModels = this.modelConfig.getCustomModels(providerId);
45
+ if (customModels.length > 0) {
46
+ return customModels[0].id;
47
+ }
48
+ if (providerId === "openai" && authType === "oauth") {
49
+ return listBuiltinModels("openai-codex")[0]?.id;
50
+ }
51
+ return listBuiltinModels(providerId)[0]?.id;
52
+ }
53
+ async prepareProvider(providerId) {
54
+ const authKey = this.resolveOAuthAuthKey(providerId);
55
+ if (providerId === "openai" && this.authStorage.isExpired(authKey)) {
56
+ const creds = this.authStorage.get(authKey);
57
+ if (creds?.refreshToken) {
58
+ const refreshed = await refreshOpenAICodex(creds.refreshToken);
59
+ this.authStorage.set("openai", {
60
+ type: "oauth",
61
+ accessToken: refreshed.accessToken,
62
+ refreshToken: refreshed.refreshToken,
63
+ expiresAt: refreshed.expiresAt,
64
+ idToken: refreshed.idToken,
65
+ accountId: refreshed.accountId,
66
+ });
67
+ }
68
+ }
69
+ }
70
+ getConfigured() {
71
+ // 1. Try models.json first
72
+ const modelsJsonProviders = this.modelConfig.getAllProviders();
73
+ const keys = Object.keys(modelsJsonProviders);
74
+ let providers = [];
75
+ if (keys.length > 0) {
76
+ providers = keys.map((id) => {
77
+ const builtin = getBuiltinProvider(id);
78
+ const cfg = modelsJsonProviders[id];
79
+ return {
80
+ id,
81
+ name: builtin?.name || id,
82
+ baseURL: cfg.baseURL || builtin?.baseURL || "",
83
+ apiKey: cfg.apiKey || "",
84
+ enabled: true,
85
+ authType: "api",
86
+ };
87
+ });
88
+ }
89
+ else {
90
+ // 2. Fall back to config.json providers (interactive TUI style)
91
+ providers = this.config.getProviders();
92
+ }
93
+ // 3. Inject OAuth access tokens
94
+ for (const p of providers) {
95
+ const authKey = this.resolveOAuthAuthKey(p.id);
96
+ if (this.authStorage.has(authKey)) {
97
+ const token = this.authStorage.getAccessToken(authKey);
98
+ if (token) {
99
+ p.apiKey = token;
100
+ p.authType = "oauth";
101
+ if (p.id === "openai") {
102
+ p.baseURL = "https://chatgpt.com/backend-api";
103
+ }
104
+ }
105
+ }
106
+ }
107
+ // 4. Auto-include built-in OAuth providers that have credentials
108
+ const configuredIds = new Set(providers.map((p) => p.id));
109
+ for (const builtin of BUILTIN_PROVIDERS) {
110
+ if (builtin.id === "openai-codex")
111
+ continue;
112
+ if (configuredIds.has(builtin.id))
113
+ continue;
114
+ const authKey = this.resolveOAuthAuthKey(builtin.id);
115
+ if (this.authStorage.has(authKey)) {
116
+ const token = this.authStorage.getAccessToken(authKey);
117
+ providers.push({
118
+ ...builtin,
119
+ apiKey: token || "",
120
+ enabled: !!token,
121
+ authType: "oauth",
122
+ ...(builtin.id === "openai" ? { baseURL: "https://chatgpt.com/backend-api" } : {}),
123
+ });
124
+ }
125
+ }
126
+ return providers;
127
+ }
128
+ getEnabled() {
129
+ return this.getConfigured().filter((p) => p.enabled && p.apiKey);
130
+ }
131
+ getDefault() {
132
+ const enabled = this.getEnabled();
133
+ if (enabled.length === 0)
134
+ return undefined;
135
+ const defaultId = this.config.getDefaultProvider();
136
+ const preferred = enabled.filter((provider) => isUserVisibleProvider(provider.id));
137
+ return preferred.find((p) => p.id === defaultId)
138
+ || preferred[0]
139
+ || enabled.find((p) => p.id === defaultId)
140
+ || enabled[0];
141
+ }
142
+ setDefault(id) {
143
+ this.config.setDefaultProvider(id);
144
+ }
145
+ addProvider(id, apiKey) {
146
+ const builtin = getBuiltinProvider(id);
147
+ if (!builtin)
148
+ return false;
149
+ const providers = this.config.getProviders();
150
+ const idx = providers.findIndex((p) => p.id === id);
151
+ const profile = { ...builtin, apiKey, enabled: true };
152
+ if (idx >= 0) {
153
+ providers[idx] = profile;
154
+ }
155
+ else {
156
+ providers.push(profile);
157
+ }
158
+ this.config.setProviders(providers);
159
+ return true;
160
+ }
161
+ removeProvider(id) {
162
+ const providers = this.config.getProviders().filter((p) => p.id !== id);
163
+ this.config.setProviders(providers);
164
+ }
165
+ updateProviderKey(id, apiKey) {
166
+ const providers = this.config.getProviders();
167
+ const p = providers.find((x) => x.id === id);
168
+ if (p) {
169
+ p.apiKey = apiKey;
170
+ this.config.setProviders(providers);
171
+ }
172
+ }
173
+ async listModels(provider) {
174
+ // 1. Custom models from models.json always take precedence
175
+ const customModels = this.modelConfig.getCustomModels(provider.id);
176
+ if (customModels.length > 0) {
177
+ return customModels;
178
+ }
179
+ // 2. Built-in provider dynamic/static lists
180
+ if (provider.id === "openrouter") {
181
+ try {
182
+ const res = await fetch("https://openrouter.ai/api/v1/models");
183
+ const data = (await res.json());
184
+ const models = data.data || [];
185
+ return models.map((m) => ({
186
+ id: m.id,
187
+ name: m.name || m.id,
188
+ providerId: provider.id,
189
+ }));
190
+ }
191
+ catch {
192
+ // fall through to static
193
+ }
194
+ }
195
+ if (provider.id === "openai" && provider.authType === "oauth" && provider.apiKey) {
196
+ try {
197
+ const models = await fetchOpenAICodexModels({
198
+ baseURL: provider.baseURL,
199
+ accessToken: provider.apiKey,
200
+ });
201
+ if (models.length > 0) {
202
+ return models.map((id) => ({ id, name: id, providerId: provider.id }));
203
+ }
204
+ }
205
+ catch {
206
+ // fall through to static
207
+ }
208
+ return listBuiltinModels("openai-codex").map((model) => ({
209
+ id: model.id,
210
+ name: model.name,
211
+ providerId: provider.id,
212
+ }));
213
+ }
214
+ return listBuiltinModels(provider.id).map((model) => ({
215
+ id: model.id,
216
+ name: model.name,
217
+ providerId: provider.id,
218
+ }));
219
+ }
220
+ }
221
+ /** Encode a model selection as "providerId:modelId". */
222
+ export function encodeModel(providerId, modelId) {
223
+ return `${providerId}:${modelId}`;
224
+ }
225
+ /** Decode "providerId:modelId" or legacy plain modelId. */
226
+ export function decodeModel(value) {
227
+ if (value.includes(":")) {
228
+ const [providerId, ...rest] = value.split(":");
229
+ return { providerId, modelId: rest.join(":") };
230
+ }
231
+ return { modelId: value };
232
+ }
233
+ /** Strip provider prefix for concise display. */
234
+ export function displayModel(model) {
235
+ const { modelId } = decodeModel(model);
236
+ return modelId;
237
+ }
238
+ /** Normalize user input to provider:model format when possible. */
239
+ export function normalizeModel(model, defaultProvider = "openai") {
240
+ const { providerId, modelId } = decodeModel(model);
241
+ if (providerId)
242
+ return model;
243
+ return encodeModel(defaultProvider, modelId);
244
+ }
@@ -0,0 +1,10 @@
1
+ import type { ThinkingLevel } from "./types.js";
2
+ export { getAvailableThinkingLevels, getDefaultThinkingLevel, normalizeThinkingLevel } from "./variant/variant-resolver.js";
3
+ export interface ProviderRequestConfig {
4
+ effectiveThinkingLevel: ThinkingLevel;
5
+ reasoningEffort?: ThinkingLevel;
6
+ reasoningContentEcho?: "tool_calls" | "all";
7
+ extraBody?: Record<string, unknown>;
8
+ omitTemperature?: boolean;
9
+ }
10
+ export declare function resolveProviderRequestConfig(providerId: string, modelId: string, requestedLevel: ThinkingLevel): ProviderRequestConfig;
@@ -0,0 +1,69 @@
1
+ import { getAvailableThinkingLevels, normalizeThinkingLevel } from "./variant/variant-resolver.js";
2
+ export { getAvailableThinkingLevels, getDefaultThinkingLevel, normalizeThinkingLevel } from "./variant/variant-resolver.js";
3
+ const MOONSHOT_PROVIDER_IDS = new Set(["moonshot-cn", "moonshot-intl", "kimi-for-coding"]);
4
+ const KIMI_K25_FAMILY = new Set(["kimi-k2.5", "k2.6-code-preview", "kimi-k2.6"]);
5
+ const KIMI_THINKING_FAMILY = new Set(["kimi-k2-thinking", "kimi-k2-thinking-turbo"]);
6
+ export function resolveProviderRequestConfig(providerId, modelId, requestedLevel) {
7
+ const supportedLevels = getAvailableThinkingLevels(providerId, modelId);
8
+ const effectiveThinkingLevel = normalizeThinkingLevel(requestedLevel, supportedLevels);
9
+ // ChatGPT OAuth via openai-codex currently rejects explicit reasoning params for this account path.
10
+ // Keep the session/UI state, but don't send reasoning flags on this provider until the protocol is clearer.
11
+ if (providerId === "openai-codex") {
12
+ return { effectiveThinkingLevel };
13
+ }
14
+ if (providerId === "deepseek" && (modelId === "deepseek-v4-flash" || modelId === "deepseek-v4-pro")) {
15
+ return {
16
+ effectiveThinkingLevel,
17
+ reasoningContentEcho: "all",
18
+ extraBody: {
19
+ thinking: { type: "enabled" },
20
+ reasoning_effort: effectiveThinkingLevel,
21
+ },
22
+ };
23
+ }
24
+ // Zhipu/Z.AI OpenAI-compatible endpoints expose reasoning via a provider-specific
25
+ // `thinking` block rather than OpenAI's `reasoning_effort` shape.
26
+ if (["zhipuai", "zhipuai-coding-plan", "zai", "zai-coding-plan"].includes(providerId)) {
27
+ return {
28
+ effectiveThinkingLevel,
29
+ extraBody: effectiveThinkingLevel === "off"
30
+ ? undefined
31
+ : {
32
+ thinking: {
33
+ type: "enabled",
34
+ clear_thinking: false,
35
+ },
36
+ },
37
+ };
38
+ }
39
+ // Moonshot / Kimi: kimi-k2.5 family (incl. k2.6-code-preview, kimi-k2.6) locks
40
+ // temperature/top_p/n/penalties and exposes thinking via extra_body.thinking;
41
+ // kimi-k2-thinking family locks temperature=1.
42
+ if (MOONSHOT_PROVIDER_IDS.has(providerId)) {
43
+ if (KIMI_K25_FAMILY.has(modelId)) {
44
+ return {
45
+ effectiveThinkingLevel,
46
+ omitTemperature: true,
47
+ reasoningContentEcho: "tool_calls",
48
+ extraBody: {
49
+ thinking: { type: effectiveThinkingLevel === "off" ? "disabled" : "enabled" },
50
+ },
51
+ };
52
+ }
53
+ if (KIMI_THINKING_FAMILY.has(modelId)) {
54
+ return { effectiveThinkingLevel, omitTemperature: true };
55
+ }
56
+ return { effectiveThinkingLevel };
57
+ }
58
+ if (providerId === "openai"
59
+ || providerId === "openrouter"
60
+ || providerId === "google"
61
+ || providerId === "azure"
62
+ || providerId === "openai-compatible") {
63
+ return {
64
+ effectiveThinkingLevel,
65
+ reasoningEffort: effectiveThinkingLevel === "off" ? undefined : effectiveThinkingLevel,
66
+ };
67
+ }
68
+ return { effectiveThinkingLevel };
69
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * OpenAI-compatible Provider implementation.
3
+ *
4
+ * Works with OpenRouter, OpenAI, DeepSeek, Google, Groq, Together, and local OpenAI-compatible endpoints.
5
+ */
6
+ import type { Message, Provider, StreamChunk, ThinkingLevel } from "./types.js";
7
+ type ReasoningContentEcho = "tool_calls" | "all";
8
+ export declare function toChatCompletionsMessage(message: Message, options?: {
9
+ reasoningContentEcho?: ReasoningContentEcho;
10
+ }): Record<string, unknown>;
11
+ export interface ProviderInstanceOptions {
12
+ providerId?: string;
13
+ apiKey: string;
14
+ baseURL: string;
15
+ /** Requested thinking level */
16
+ thinkingLevel?: ThinkingLevel;
17
+ }
18
+ export declare function createUnavailableProvider(message: string): Provider;
19
+ export declare function createProviderInstance(options: ProviderInstanceOptions): Provider;
20
+ export declare function normalizeToolArgs(raw: string): string;
21
+ /**
22
+ * Convert an OpenAI-compatible chat-completions stream into our internal StreamChunk events.
23
+ *
24
+ * Multi-tool-call streams are buffered by `index` and emitted in index order at
25
+ * `finish_reason === "tool_calls"`, so the agent layer always sees a clean
26
+ * (isStart -> args -> isEnd) sequence per call. This matters for providers like
27
+ * Kimi K2.5 that emit several parallel tool calls per assistant turn -- the
28
+ * previous single-slot implementation silently dropped every call but the last.
29
+ */
30
+ export declare function translateOpenAIStream(stream: AsyncIterable<any>): AsyncIterable<StreamChunk>;
31
+ export {};
@@ -0,0 +1,269 @@
1
+ /**
2
+ * OpenAI-compatible Provider implementation.
3
+ *
4
+ * Works with OpenRouter, OpenAI, DeepSeek, Google, Groq, Together, and local OpenAI-compatible endpoints.
5
+ */
6
+ import OpenAI from "openai";
7
+ import { createOpenAICodexProvider, isOpenAICodexBaseUrl } from "./provider-openai-codex.js";
8
+ import { resolveProviderRequestConfig } from "./provider-transform.js";
9
+ export function toChatCompletionsMessage(message, options = {}) {
10
+ const reasoningContentEcho = options.reasoningContentEcho ?? "tool_calls";
11
+ if (message.role === "assistant") {
12
+ const out = {
13
+ role: "assistant",
14
+ content: message.content || null,
15
+ };
16
+ if (reasoningContentEcho === "all") {
17
+ // DeepSeek thinking mode requires every assistant message to echo the
18
+ // provider field, even when the original value is an empty string.
19
+ out.reasoning_content = message.reasoning ?? "";
20
+ }
21
+ if (message.toolCalls && message.toolCalls.length > 0) {
22
+ out.tool_calls = message.toolCalls.map((tc) => ({
23
+ id: tc.id,
24
+ type: "function",
25
+ function: { name: tc.name, arguments: tc.arguments || "{}" },
26
+ }));
27
+ // Kimi-k2.5 with thinking enabled requires reasoning_content to be echoed
28
+ // back on assistant messages that carry tool_calls.
29
+ if (message.reasoning && reasoningContentEcho === "tool_calls") {
30
+ out.reasoning_content = message.reasoning;
31
+ }
32
+ }
33
+ return out;
34
+ }
35
+ if (message.role === "tool") {
36
+ return { role: "tool", tool_call_id: message.toolCallId, content: message.content };
37
+ }
38
+ return { role: message.role, content: message.content };
39
+ }
40
+ export function createUnavailableProvider(message) {
41
+ async function* streamChat() {
42
+ throw new Error(message);
43
+ }
44
+ async function complete() {
45
+ throw new Error(message);
46
+ }
47
+ return { streamChat, complete };
48
+ }
49
+ export function createProviderInstance(options) {
50
+ if (isOpenAICodexBaseUrl(options.baseURL)) {
51
+ return createOpenAICodexProvider({ ...options, providerId: options.providerId || "openai-codex" });
52
+ }
53
+ const client = new OpenAI({
54
+ apiKey: options.apiKey,
55
+ baseURL: options.baseURL,
56
+ });
57
+ const fallbackModel = "gpt-4o";
58
+ async function* streamChat(messages, chatOptions) {
59
+ const requestConfig = resolveProviderRequestConfig(options.providerId || "", chatOptions.model, chatOptions.thinkingLevel ?? options.thinkingLevel ?? "off");
60
+ const tools = chatOptions.tools?.map((t) => ({
61
+ type: "function",
62
+ function: {
63
+ name: t.name,
64
+ description: t.description,
65
+ parameters: t.parameters,
66
+ },
67
+ }));
68
+ const body = {
69
+ model: chatOptions.model,
70
+ messages: messages.map((message) => toChatCompletionsMessage(message, {
71
+ reasoningContentEcho: requestConfig.reasoningContentEcho ?? "tool_calls",
72
+ })),
73
+ tools: tools && tools.length > 0 ? tools : undefined,
74
+ tool_choice: tools && tools.length > 0 ? "auto" : undefined,
75
+ stream: true,
76
+ };
77
+ // DeepSeek only emits final usage in streaming mode when this flag is set.
78
+ if (options.providerId === "deepseek") {
79
+ body.stream_options = { include_usage: true };
80
+ }
81
+ if (!requestConfig.omitTemperature) {
82
+ body.temperature = chatOptions.temperature ?? 0.2;
83
+ }
84
+ if (requestConfig.extraBody) {
85
+ Object.assign(body, requestConfig.extraBody);
86
+ }
87
+ if (requestConfig.reasoningEffort && requestConfig.reasoningEffort !== "off") {
88
+ body.reasoning = { enabled: true };
89
+ }
90
+ const stream = (await client.chat.completions.create(body));
91
+ yield* translateOpenAIStream(stream);
92
+ yield { type: "done" };
93
+ }
94
+ async function complete(messages, chatOptions) {
95
+ const requestConfig = resolveProviderRequestConfig(options.providerId || "", chatOptions?.model ?? fallbackModel, chatOptions?.thinkingLevel ?? options.thinkingLevel ?? "off");
96
+ const body = {
97
+ model: chatOptions?.model ?? fallbackModel,
98
+ messages: messages.map((message) => toChatCompletionsMessage(message, {
99
+ reasoningContentEcho: requestConfig.reasoningContentEcho ?? "tool_calls",
100
+ })),
101
+ };
102
+ if (!requestConfig.omitTemperature) {
103
+ body.temperature = chatOptions?.temperature ?? 0.2;
104
+ }
105
+ if (requestConfig.extraBody) {
106
+ Object.assign(body, requestConfig.extraBody);
107
+ }
108
+ if (requestConfig.reasoningEffort && requestConfig.reasoningEffort !== "off") {
109
+ body.reasoning = { enabled: true };
110
+ }
111
+ const response = await client.chat.completions.create(body);
112
+ return response.choices[0]?.message?.content ?? "";
113
+ }
114
+ return { streamChat, complete };
115
+ }
116
+ // Some providers (notably Fireworks-hosted Kimi) stream tool-call arguments
117
+ // as repeated full snapshots in each delta instead of incremental chunks, so
118
+ // a naive `+=` produces `{"x":1}{"x":1}` — not valid JSON. Parse the raw
119
+ // stream; if it doesn't parse but contains a balanced `{…}` prefix or suffix
120
+ // that does, use that. Empty or unsalvageable input becomes `"{}"` so the
121
+ // downstream echo to the model is always valid JSON.
122
+ export function normalizeToolArgs(raw) {
123
+ const s = (raw ?? "").trim();
124
+ if (!s)
125
+ return "{}";
126
+ try {
127
+ JSON.parse(s);
128
+ return s;
129
+ }
130
+ catch { }
131
+ const firstBrace = extractBalancedJson(s, 0);
132
+ if (firstBrace) {
133
+ try {
134
+ JSON.parse(firstBrace);
135
+ }
136
+ catch {
137
+ return "{}";
138
+ }
139
+ // If the content after the first balanced object is another valid object
140
+ // with the same parse, we've got a snapshot duplication — keep one copy.
141
+ const rest = s.slice(firstBrace.length).trim();
142
+ if (!rest)
143
+ return firstBrace;
144
+ try {
145
+ JSON.parse(rest);
146
+ return firstBrace;
147
+ }
148
+ catch { }
149
+ return firstBrace;
150
+ }
151
+ return "{}";
152
+ }
153
+ function extractBalancedJson(s, start) {
154
+ if (s[start] !== "{")
155
+ return null;
156
+ let depth = 0;
157
+ let inStr = false;
158
+ let escape = false;
159
+ for (let i = start; i < s.length; i++) {
160
+ const c = s[i];
161
+ if (escape) {
162
+ escape = false;
163
+ continue;
164
+ }
165
+ if (c === "\\" && inStr) {
166
+ escape = true;
167
+ continue;
168
+ }
169
+ if (c === '"') {
170
+ inStr = !inStr;
171
+ continue;
172
+ }
173
+ if (inStr)
174
+ continue;
175
+ if (c === "{")
176
+ depth++;
177
+ else if (c === "}") {
178
+ depth--;
179
+ if (depth === 0)
180
+ return s.slice(start, i + 1);
181
+ }
182
+ }
183
+ return null;
184
+ }
185
+ /**
186
+ * Convert an OpenAI-compatible chat-completions stream into our internal StreamChunk events.
187
+ *
188
+ * Multi-tool-call streams are buffered by `index` and emitted in index order at
189
+ * `finish_reason === "tool_calls"`, so the agent layer always sees a clean
190
+ * (isStart -> args -> isEnd) sequence per call. This matters for providers like
191
+ * Kimi K2.5 that emit several parallel tool calls per assistant turn -- the
192
+ * previous single-slot implementation silently dropped every call but the last.
193
+ */
194
+ export async function* translateOpenAIStream(stream) {
195
+ const toolCalls = new Map();
196
+ function* flushToolCalls() {
197
+ if (toolCalls.size === 0)
198
+ return;
199
+ const sorted = [...toolCalls.entries()].sort(([a], [b]) => a - b);
200
+ for (const [, entry] of sorted) {
201
+ if (!entry.id || !entry.name)
202
+ continue;
203
+ const args = normalizeToolArgs(entry.args);
204
+ yield { type: "tool_call", id: entry.id, name: entry.name, arguments: "", isStart: true, isEnd: false };
205
+ yield { type: "tool_call", id: entry.id, name: entry.name, arguments: args, isStart: false, isEnd: false };
206
+ yield { type: "tool_call", id: entry.id, name: entry.name, arguments: "", isStart: false, isEnd: true };
207
+ }
208
+ toolCalls.clear();
209
+ }
210
+ for await (const chunk of stream) {
211
+ const delta = chunk.choices?.[0]?.delta;
212
+ const usage = chunk.usage;
213
+ if (usage) {
214
+ yield {
215
+ type: "usage",
216
+ usage: {
217
+ promptTokens: typeof usage.prompt_tokens === "number" ? usage.prompt_tokens : 0,
218
+ completionTokens: typeof usage.completion_tokens === "number" ? usage.completion_tokens : 0,
219
+ promptCacheHitTokens: typeof usage.prompt_cache_hit_tokens === "number" ? usage.prompt_cache_hit_tokens : undefined,
220
+ promptCacheMissTokens: typeof usage.prompt_cache_miss_tokens === "number" ? usage.prompt_cache_miss_tokens : undefined,
221
+ reasoningTokens: typeof usage.completion_tokens_details?.reasoning_tokens === "number"
222
+ ? usage.completion_tokens_details.reasoning_tokens
223
+ : undefined,
224
+ totalTokens: typeof usage.total_tokens === "number" ? usage.total_tokens : undefined,
225
+ },
226
+ };
227
+ }
228
+ const reasoning = delta?.reasoning ?? delta?.thinking ?? delta?.reasoning_content;
229
+ if (reasoning) {
230
+ yield { type: "reasoning_delta", content: reasoning };
231
+ }
232
+ if (delta?.content) {
233
+ const thinkMatch = delta.content.match(/<think>([\s\S]*?)<\/think>/);
234
+ if (thinkMatch) {
235
+ if (thinkMatch[1]) {
236
+ yield { type: "reasoning_delta", content: thinkMatch[1] };
237
+ }
238
+ const remaining = delta.content.replace(/<think>[\s\S]*?<\/think>/, "");
239
+ if (remaining) {
240
+ yield { type: "text", content: remaining };
241
+ }
242
+ }
243
+ else {
244
+ yield { type: "text", content: delta.content };
245
+ }
246
+ }
247
+ if (delta?.tool_calls) {
248
+ for (const tc of delta.tool_calls) {
249
+ const idx = typeof tc.index === "number" ? tc.index : 0;
250
+ let entry = toolCalls.get(idx);
251
+ if (!entry) {
252
+ entry = { id: "", name: "", args: "" };
253
+ toolCalls.set(idx, entry);
254
+ }
255
+ if (tc.id)
256
+ entry.id = tc.id;
257
+ if (tc.function?.name)
258
+ entry.name = tc.function.name;
259
+ if (typeof tc.function?.arguments === "string")
260
+ entry.args += tc.function.arguments;
261
+ }
262
+ }
263
+ const finishReason = chunk.choices?.[0]?.finish_reason;
264
+ if (finishReason === "tool_calls") {
265
+ yield* flushToolCalls();
266
+ }
267
+ }
268
+ yield* flushToolCalls();
269
+ }
@@ -0,0 +1,22 @@
1
+ import type { QuestionAnswer, QuestionEvent, QuestionPrompt, QuestionRequest, QuestionToolRef } from "./types.js";
2
+ import { QuestionRejectedError } from "./types.js";
3
+ export interface QuestionAskInput {
4
+ sessionID?: string;
5
+ questions: QuestionPrompt[];
6
+ tool?: QuestionToolRef;
7
+ }
8
+ export declare class QuestionController {
9
+ private pending;
10
+ private listeners;
11
+ private counter;
12
+ ask(input: QuestionAskInput): Promise<QuestionAnswer[]>;
13
+ reply(requestID: string, answers: QuestionAnswer[]): boolean;
14
+ reject(requestID: string): boolean;
15
+ list(sessionID?: string): QuestionRequest[];
16
+ subscribe(listener: (event: QuestionEvent) => void): () => void;
17
+ rejectAll(): void;
18
+ private emit;
19
+ private nextID;
20
+ }
21
+ export type { QuestionAnswer, QuestionEvent, QuestionPrompt, QuestionRequest };
22
+ export { QuestionRejectedError };