@botconnector/bccli 0.4.0-beta.9 → 0.4.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 (183) hide show
  1. package/README.en.md +253 -0
  2. package/README.md +93 -6
  3. package/dist/abort.d.ts +5 -0
  4. package/dist/acp-3PHBAI64.js +182 -0
  5. package/dist/acp.d.ts +27 -0
  6. package/dist/agent.d.ts +143 -0
  7. package/dist/agents.d.ts +4 -0
  8. package/dist/args-W6BNKY7K.js +13 -0
  9. package/dist/args.d.ts +51 -0
  10. package/dist/budget.d.ts +23 -0
  11. package/dist/checkpoints.d.ts +34 -0
  12. package/dist/{chunk-BFZXRKW5.js → chunk-24JN75FZ.js} +29 -7
  13. package/dist/chunk-45SDLMTV.js +341 -0
  14. package/dist/chunk-75BECX4F.js +56 -0
  15. package/dist/{chunk-SJH3YLW4.js → chunk-BC7UXFE4.js} +69 -6
  16. package/dist/chunk-BGXNR74O.js +733 -0
  17. package/dist/chunk-BKGFA2PT.js +8 -0
  18. package/dist/{chunk-CLN4Z5E3.js → chunk-BLOYFIAD.js} +6 -3
  19. package/dist/chunk-BXQQCUAC.js +145 -0
  20. package/dist/chunk-FLQPE4S5.js +373 -0
  21. package/dist/chunk-GN4W3T6E.js +76 -0
  22. package/dist/{chunk-7S6JXQCH.js → chunk-JHPGOAXU.js} +2 -1
  23. package/dist/chunk-MFHCQQEN.js +201 -0
  24. package/dist/chunk-MHIYLXJC.js +143 -0
  25. package/dist/chunk-PSO7R5AD.js +10 -0
  26. package/dist/chunk-PZRKU4UM.js +2143 -0
  27. package/dist/chunk-QZKLZELZ.js +64 -0
  28. package/dist/chunk-R5RE46CW.js +231 -0
  29. package/dist/chunk-UZWJQF67.js +500 -0
  30. package/dist/{chunk-DX7CZTBB.js → chunk-WI22M2ID.js} +14 -9
  31. package/dist/chunk-WNACAUDK.js +946 -0
  32. package/dist/chunk-XV7ZVL4J.js +235 -0
  33. package/dist/cli.d.ts +1 -0
  34. package/dist/cli.js +150 -1382
  35. package/dist/clipboardImage.d.ts +17 -0
  36. package/dist/commands.d.ts +16 -0
  37. package/dist/completion-HPPFDAPB.js +125 -0
  38. package/dist/completion.d.ts +14 -0
  39. package/dist/{config-YUNKPCNB.js → config-DIDLDONU.js} +5 -2
  40. package/dist/config-IU76CPK2.js +34 -0
  41. package/dist/config.d.ts +97 -0
  42. package/dist/context.d.ts +13 -0
  43. package/dist/contextWindow.d.ts +3 -0
  44. package/dist/diff.d.ts +7 -0
  45. package/dist/exit.d.ts +9 -0
  46. package/dist/extensions.d.ts +34 -0
  47. package/dist/ghBotCli-E3VPHNZY.js +677 -0
  48. package/dist/ghBotCli.d.ts +9 -0
  49. package/dist/ghBotDispatch.d.ts +68 -0
  50. package/dist/ghBotRunner.d.ts +82 -0
  51. package/dist/ghBotServe.d.ts +21 -0
  52. package/dist/ghBotWebhook.d.ts +45 -0
  53. package/dist/hooks.d.ts +27 -0
  54. package/dist/i18n/id.d.ts +1 -0
  55. package/dist/i18n.d.ts +16 -0
  56. package/dist/integrationCli-Q2YPDTPG.js +685 -0
  57. package/dist/integrationCli.d.ts +9 -0
  58. package/dist/keybinds.d.ts +22 -0
  59. package/dist/login-FP3BB44A.js +16 -0
  60. package/dist/login.d.ts +19 -0
  61. package/dist/lsp.d.ts +47 -0
  62. package/dist/mcp/catalog.d.ts +14 -0
  63. package/dist/mcp/config.d.ts +36 -0
  64. package/dist/mcp/manager.d.ts +34 -0
  65. package/dist/mcp/oauth.d.ts +82 -0
  66. package/dist/{mcpCli-DZWQPTOS.js → mcpCli-SNBDUWJN.js} +42 -9
  67. package/dist/mcpCli.d.ts +3 -0
  68. package/dist/mcpServer/serve.d.ts +8 -0
  69. package/dist/mcpServer/server.d.ts +19 -0
  70. package/dist/models.d.ts +13 -0
  71. package/dist/modelsCli-CZ2HNGKR.js +36 -0
  72. package/dist/modelsCli.d.ts +15 -0
  73. package/dist/npmRunner-LU43GM3W.js +46 -0
  74. package/dist/npmRunner.d.ts +21 -0
  75. package/dist/oauthLogin-6FWQABJ7.js +210 -0
  76. package/dist/oauthLogin.d.ts +47 -0
  77. package/dist/permissions.d.ts +30 -0
  78. package/dist/pluginSpec.d.ts +25 -0
  79. package/dist/plugins.d.ts +45 -0
  80. package/dist/pluginsCli-LPIUUDWB.js +284 -0
  81. package/dist/pluginsCli.d.ts +16 -0
  82. package/dist/prCli-5IMV3VNQ.js +78 -0
  83. package/dist/prCli.d.ts +33 -0
  84. package/dist/presets.d.ts +8 -0
  85. package/dist/print.d.ts +14 -0
  86. package/dist/provider.d.ts +140 -0
  87. package/dist/providerCli-NMUFXAAS.js +15 -0
  88. package/dist/providerCli.d.ts +17 -0
  89. package/dist/providers.d.ts +6 -0
  90. package/dist/reasoning.d.ts +12 -0
  91. package/dist/repomap.d.ts +10 -0
  92. package/dist/sdk.d.ts +57 -0
  93. package/dist/sdk.js +5571 -0
  94. package/dist/serve-KWOECK5W.js +178 -0
  95. package/dist/serve-X2ZV4S5U.js +521 -0
  96. package/dist/serve.d.ts +37 -0
  97. package/dist/serveHeartbeat.d.ts +14 -0
  98. package/dist/serveProtocol.d.ts +47 -0
  99. package/dist/session-GKEXDJKZ.js +12 -0
  100. package/dist/session.d.ts +30 -0
  101. package/dist/setup-OV3NWQN5.js +24 -0
  102. package/dist/setup.d.ts +74 -0
  103. package/dist/slash/copy.d.ts +1 -0
  104. package/dist/slash/diff.d.ts +1 -0
  105. package/dist/slash/doctor.d.ts +18 -0
  106. package/dist/slash/export.d.ts +2 -0
  107. package/dist/slash/info.d.ts +45 -0
  108. package/dist/slash/memory.d.ts +10 -0
  109. package/dist/statsCli-CF3R7ATL.js +353 -0
  110. package/dist/statsCli.d.ts +62 -0
  111. package/dist/textToolCalls.d.ts +6 -0
  112. package/dist/thinking.d.ts +17 -0
  113. package/dist/tokenBudget.d.ts +22 -0
  114. package/dist/tools/bash.d.ts +52 -0
  115. package/dist/tools/diagnostics.d.ts +5 -0
  116. package/dist/tools/edit.d.ts +7 -0
  117. package/dist/tools/fetch.d.ts +41 -0
  118. package/dist/tools/glob.d.ts +5 -0
  119. package/dist/tools/globSafety.d.ts +7 -0
  120. package/dist/tools/grep.d.ts +13 -0
  121. package/dist/tools/ignore.d.ts +2 -0
  122. package/dist/tools/index.d.ts +12 -0
  123. package/dist/tools/memory.d.ts +13 -0
  124. package/dist/tools/paths.d.ts +2 -0
  125. package/dist/tools/plan.d.ts +10 -0
  126. package/dist/tools/read.d.ts +16 -0
  127. package/dist/tools/repomap.d.ts +2 -0
  128. package/dist/tools/sandbox.d.ts +12 -0
  129. package/dist/tools/shell.d.ts +18 -0
  130. package/dist/tools/skill.d.ts +3 -0
  131. package/dist/tools/task.d.ts +28 -0
  132. package/dist/tools/todo.d.ts +13 -0
  133. package/dist/tools/types.d.ts +52 -0
  134. package/dist/tools/websearch.d.ts +9 -0
  135. package/dist/tools/write.d.ts +5 -0
  136. package/dist/transferCli-YUFPI6AH.js +71 -0
  137. package/dist/transferCli.d.ts +23 -0
  138. package/dist/ui/App.d.ts +7 -0
  139. package/dist/ui/DiffView.d.ts +3 -0
  140. package/dist/ui/EditableText.d.ts +7 -0
  141. package/dist/ui/LinePrompt.d.ts +6 -0
  142. package/dist/ui/ListPicker.d.ts +11 -0
  143. package/dist/ui/Markdown.d.ts +8 -0
  144. package/dist/ui/McpMenu.d.ts +11 -0
  145. package/dist/ui/ModelPicker.d.ts +6 -0
  146. package/dist/ui/PermissionPrompt.d.ts +5 -0
  147. package/dist/ui/PlanApproval.d.ts +5 -0
  148. package/dist/ui/PromptInput.d.ts +18 -0
  149. package/dist/ui/ProviderMenu.d.ts +10 -0
  150. package/dist/ui/Spinner.d.ts +4 -0
  151. package/dist/ui/StatusBar.d.ts +9 -0
  152. package/dist/ui/TodoList.d.ts +4 -0
  153. package/dist/ui/ToolBlock.d.ts +14 -0
  154. package/dist/ui/complete.d.ts +1 -0
  155. package/dist/ui/focusReport.d.ts +1 -0
  156. package/dist/ui/index.d.ts +7 -0
  157. package/dist/ui/lineEdit.d.ts +42 -0
  158. package/dist/ui/replay.d.ts +4 -0
  159. package/dist/ui/table.d.ts +20 -0
  160. package/dist/ui/theme.d.ts +5 -0
  161. package/dist/ui/thinkingMouse.d.ts +33 -0
  162. package/dist/ui/transcript.d.ts +55 -0
  163. package/dist/ui/vim.d.ts +26 -0
  164. package/dist/ui-IVIFNUCC.js +2367 -0
  165. package/dist/update-TZ2G6KQ3.js +145 -0
  166. package/dist/update.d.ts +30 -0
  167. package/dist/verify.d.ts +16 -0
  168. package/dist/version.d.ts +1 -0
  169. package/dist/webBudget.d.ts +25 -0
  170. package/dist/worktree.d.ts +12 -0
  171. package/package.json +42 -8
  172. package/dist/args-JPVSGCGY.js +0 -12
  173. package/dist/chunk-2MGINW4O.js +0 -114
  174. package/dist/chunk-ELML4KVQ.js +0 -76
  175. package/dist/chunk-ISGBGFSU.js +0 -52
  176. package/dist/chunk-KV27KTOX.js +0 -254
  177. package/dist/chunk-U7GK73CK.js +0 -341
  178. package/dist/chunk-ZYCYTEKD.js +0 -96
  179. package/dist/integrationCli-BGT5UZU4.js +0 -223
  180. package/dist/login-6K32PIXV.js +0 -9
  181. package/dist/providerCli-Q7LRM2NP.js +0 -12
  182. package/dist/session-7UB77CEG.js +0 -9
  183. package/dist/ui-6GTQUJOE.js +0 -1415
@@ -0,0 +1,946 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ reasoningPayload
4
+ } from "./chunk-BLOYFIAD.js";
5
+ import {
6
+ t
7
+ } from "./chunk-UZWJQF67.js";
8
+
9
+ // src/provider.ts
10
+ import OpenAI from "openai";
11
+ import { ResponsesWS } from "openai/resources/responses/ws";
12
+ import { toResponseInputItems } from "openai/lib/responses/ResponseInputItems";
13
+
14
+ // src/thinking.ts
15
+ var OPEN = "<think>";
16
+ var CLOSE = "</think>";
17
+ function partialTag(text, tag) {
18
+ for (let n = Math.min(tag.length - 1, text.length); n > 0; n--) if (tag.startsWith(text.slice(-n))) return n;
19
+ return 0;
20
+ }
21
+ var ThinkSplitter = class {
22
+ inside = false;
23
+ held = "";
24
+ push(delta) {
25
+ let rest = this.held + delta;
26
+ this.held = "";
27
+ let text = "";
28
+ let thinking = "";
29
+ while (rest) {
30
+ const tag = this.inside ? CLOSE : OPEN;
31
+ const at = rest.indexOf(tag);
32
+ if (at >= 0) {
33
+ if (this.inside) thinking += rest.slice(0, at);
34
+ else text += rest.slice(0, at);
35
+ rest = rest.slice(at + tag.length);
36
+ this.inside = !this.inside;
37
+ continue;
38
+ }
39
+ const keep = partialTag(rest, tag);
40
+ const out = rest.slice(0, rest.length - keep);
41
+ if (this.inside) thinking += out;
42
+ else text += out;
43
+ this.held = rest.slice(rest.length - keep);
44
+ break;
45
+ }
46
+ return { text, thinking };
47
+ }
48
+ /** Whatever was held back as a possible tag start is plain text after all. */
49
+ flush() {
50
+ const held = this.held;
51
+ this.held = "";
52
+ return this.inside ? { text: "", thinking: held } : { text: held, thinking: "" };
53
+ }
54
+ };
55
+ function splitThinking(text) {
56
+ const s = new ThinkSplitter();
57
+ const a = s.push(text);
58
+ const b = s.flush();
59
+ return { text: a.text + b.text, thinking: a.thinking + b.thinking };
60
+ }
61
+
62
+ // src/provider.ts
63
+ var LUNA_AUTO_COMPACT_THRESHOLD = 24e4;
64
+ var ProviderError = class extends Error {
65
+ constructor(message, status, code) {
66
+ super(message);
67
+ this.status = status;
68
+ this.code = code;
69
+ }
70
+ status;
71
+ code;
72
+ };
73
+ function outputReasoningSummary(items) {
74
+ return (items ?? []).flatMap((item) => {
75
+ if (!item || typeof item !== "object" || !("type" in item) || item.type !== "reasoning") return [];
76
+ if (!("summary" in item) || !Array.isArray(item.summary)) return [];
77
+ return item.summary;
78
+ }).map((part) => part && typeof part === "object" && "text" in part && typeof part.text === "string" ? part.text : "").filter(Boolean).join("\n");
79
+ }
80
+ var retryable = (status) => status === 429 || status >= 500;
81
+ function detectRepetition(text) {
82
+ const tail = text.slice(-600);
83
+ for (let len = 1; len <= 12; len++) {
84
+ const unit = tail.slice(-len);
85
+ if (unit.length < len || !unit.trim()) continue;
86
+ let i = tail.length;
87
+ let count = 0;
88
+ while (i >= len && tail.slice(i - len, i) === unit) {
89
+ count++;
90
+ i -= len;
91
+ }
92
+ if (count >= 20 && count * len >= 300) return text.length - count * len;
93
+ }
94
+ return -1;
95
+ }
96
+ function sleep(ms, signal) {
97
+ return new Promise((resolve, reject) => {
98
+ const onAbort = () => {
99
+ clearTimeout(timer);
100
+ reject(signal?.reason);
101
+ };
102
+ const timer = setTimeout(() => {
103
+ signal?.removeEventListener("abort", onAbort);
104
+ resolve();
105
+ }, ms);
106
+ signal?.addEventListener("abort", onAbort, { once: true });
107
+ });
108
+ }
109
+ function errorMessage(body) {
110
+ try {
111
+ const parsed = JSON.parse(body);
112
+ return parsed?.error?.message ?? parsed?.message ?? body.slice(0, 300);
113
+ } catch {
114
+ return body.slice(0, 300);
115
+ }
116
+ }
117
+ var toUsage = (u) => u ? {
118
+ inputTokens: u.prompt_tokens ?? 0,
119
+ outputTokens: u.completion_tokens ?? 0,
120
+ ...u.prompt_tokens_details?.cached_tokens != null ? { cachedInputTokens: u.prompt_tokens_details.cached_tokens } : {},
121
+ ...u.prompt_tokens_details?.cache_write_tokens != null ? { cacheWriteTokens: u.prompt_tokens_details.cache_write_tokens } : {}
122
+ } : void 0;
123
+ async function readStream(res, onText, onThinking) {
124
+ let text = "";
125
+ let thinking = "";
126
+ const splitter = new ThinkSplitter();
127
+ const emit = (part) => {
128
+ if (part.thinking) {
129
+ thinking += part.thinking;
130
+ onThinking?.(part.thinking);
131
+ }
132
+ if (part.text) {
133
+ text += part.text;
134
+ onText?.(part.text);
135
+ if (loopAt < 0) loopAt = detectRepetition(text);
136
+ }
137
+ };
138
+ let usage;
139
+ let finishReason;
140
+ const calls = [];
141
+ const byIndex = /* @__PURE__ */ new Map();
142
+ let current;
143
+ const decoder = new TextDecoder();
144
+ let buffer = "";
145
+ let loopAt = -1;
146
+ const handle = (line) => {
147
+ if (!line.startsWith("data:")) return;
148
+ const data = line.slice(5).trim();
149
+ if (!data || data === "[DONE]") return;
150
+ let event;
151
+ try {
152
+ event = JSON.parse(data);
153
+ } catch {
154
+ return;
155
+ }
156
+ if (event.error) throw new ProviderError(t("Error from the provider: {message}", { message: event.error.message ?? JSON.stringify(event.error) }));
157
+ if (event.usage) usage = toUsage(event.usage);
158
+ const choice = event.choices?.[0];
159
+ if (!choice) return;
160
+ if (choice.finish_reason) finishReason = choice.finish_reason;
161
+ const delta = choice.delta ?? {};
162
+ const reasoning = delta.reasoning_content || delta.reasoning;
163
+ if (reasoning) emit({ text: "", thinking: reasoning });
164
+ if (delta.content) emit(splitter.push(delta.content));
165
+ for (const tc of delta.tool_calls ?? []) {
166
+ let target = tc.index !== void 0 ? byIndex.get(tc.index) : current;
167
+ if (!target || tc.index === void 0 && tc.id && target.id && target.id !== tc.id) {
168
+ target = { id: "", name: "", arguments: "" };
169
+ calls.push(target);
170
+ if (tc.index !== void 0) byIndex.set(tc.index, target);
171
+ }
172
+ current = target;
173
+ if (tc.id) target.id = tc.id;
174
+ if (tc.function?.name && !target.name) target.name = tc.function.name;
175
+ if (tc.function?.arguments) target.arguments += tc.function.arguments;
176
+ }
177
+ };
178
+ for await (const chunk of res.body) {
179
+ buffer += decoder.decode(chunk, { stream: true });
180
+ let newline = buffer.indexOf("\n");
181
+ while (newline >= 0 && loopAt < 0) {
182
+ handle(buffer.slice(0, newline).trim());
183
+ buffer = buffer.slice(newline + 1);
184
+ newline = buffer.indexOf("\n");
185
+ }
186
+ if (loopAt >= 0) return { text: text.slice(0, loopAt), toolCalls: [], thinking: thinking || void 0, usage, finishReason: "repetition" };
187
+ }
188
+ handle(buffer.trim());
189
+ emit(splitter.flush());
190
+ const toolCalls = calls.map((call, index) => ({ ...call, id: call.id || `call_${index}` }));
191
+ return { text, toolCalls, thinking: thinking || void 0, usage, finishReason };
192
+ }
193
+ async function readJson(res) {
194
+ const body = await res.json();
195
+ const choice = body.choices?.[0];
196
+ const toolCalls = (choice?.message?.tool_calls ?? []).map((tc, i) => ({
197
+ id: tc.id || `call_${i}`,
198
+ name: tc.function?.name ?? "",
199
+ arguments: tc.function?.arguments ?? ""
200
+ }));
201
+ const split = splitThinking(choice?.message?.content ?? "");
202
+ const thinking = (choice?.message?.reasoning_content || choice?.message?.reasoning || "") + split.thinking;
203
+ return { text: split.text, toolCalls, thinking: thinking || void 0, usage: toUsage(body.usage), finishReason: choice?.finish_reason };
204
+ }
205
+ function contentText(content) {
206
+ if (typeof content === "string") return content;
207
+ const text = content.filter((part) => part.type === "text").map((part) => part.text).join(" ").trim();
208
+ const hasImage = content.some((part) => part.type === "image_url");
209
+ if (!hasImage) return text;
210
+ return text ? `${text} [image]` : "[image]";
211
+ }
212
+ function imageUserMessage(images) {
213
+ const paths = images.map((image) => image.path);
214
+ const caption = paths.length === 1 ? `Image read from ${paths[0]}.` : `Images read from ${paths.join(", ")}.`;
215
+ return {
216
+ role: "user",
217
+ content: [
218
+ { type: "text", text: caption },
219
+ ...images.map((image) => ({ type: "image_url", image_url: { url: `data:${image.mediaType};base64,${image.data}` } }))
220
+ ]
221
+ };
222
+ }
223
+ function responseInputFromMessages(messages) {
224
+ const input = [];
225
+ for (const message of messages) {
226
+ if (message.role === "system" || message.role === "user") {
227
+ if (Array.isArray(message.content)) {
228
+ input.push({
229
+ role: message.role,
230
+ content: message.content.map(
231
+ (part) => part.type === "image_url" ? { type: "input_image", image_url: part.image_url.url } : { type: "input_text", text: part.text }
232
+ )
233
+ });
234
+ } else {
235
+ input.push({ role: message.role, content: message.content });
236
+ }
237
+ continue;
238
+ }
239
+ if (message.role === "assistant") {
240
+ if (message.responses_output_items?.length) {
241
+ input.push(...toResponseInputItems(message.responses_output_items));
242
+ continue;
243
+ }
244
+ if (message.content) input.push({ role: "assistant", content: message.content });
245
+ for (const call of message.tool_calls ?? []) {
246
+ input.push({
247
+ type: "function_call",
248
+ call_id: call.id,
249
+ name: call.function.name,
250
+ arguments: call.function.arguments || "{}",
251
+ ...call.caller ? { caller: call.caller } : {}
252
+ });
253
+ }
254
+ continue;
255
+ }
256
+ input.push({
257
+ type: "function_call_output",
258
+ call_id: message.tool_call_id,
259
+ output: message.content,
260
+ ...message.responses_caller ? { caller: message.responses_caller } : {}
261
+ });
262
+ }
263
+ return input;
264
+ }
265
+ var LUNA_TOOL_SEARCH_MIN_MCP_TOOLS = 20;
266
+ var LUNA_TOOL_SEARCH_MIN_MCP_SCHEMA_CHARS = 32e3;
267
+ var LUNA_PTC_SAFE_FUNCTIONS = /* @__PURE__ */ new Set(["read", "grep", "glob"]);
268
+ var LUNA_PTC_TOOL_OUTPUT_TRIGGER_CHARS = 8e3;
269
+ var LUNA_PTC_MULTI_WORK_CUE = /\b(both|all|multiple|several|compare|comparison|aggregate|aggregation|total|count|sum|average|filter|join|merge|dedupe|deduplicate|rank|scan|across|every|semua|keduanya|beberapa|bandingkan|perbandingan|agregasi|hitung|jumlah|rata[- ]?rata|gabung|deduplikasi|urutkan|pindai|seluruh)\b|\b(many|banyak)\s+(files?|file|records?|rows?|items?|dokumen|berkas)\b/i;
270
+ var LUNA_PTC_FORCE_CUE = /\b(count|sum|total|average|aggregate|aggregation|filter|join|merge|dedupe|deduplicate|hitung|jumlah|rata[- ]?rata|agregasi|gabung|deduplikasi)\b/i;
271
+ function shouldForceLunaPtc(messages, enabled) {
272
+ if (!enabled) return false;
273
+ for (let i = messages.length - 1; i >= 0; i--) {
274
+ const message = messages[i];
275
+ if (message.role === "assistant" && message.responses_output_items?.some((item) => item.type === "program")) return true;
276
+ if (message.role === "user") return LUNA_PTC_FORCE_CUE.test(contentText(message.content));
277
+ }
278
+ return false;
279
+ }
280
+ function shouldEnableLunaPtc(messages, enabled, programmaticToolNames = []) {
281
+ if (!enabled) return false;
282
+ let lastUser = -1;
283
+ for (let i = messages.length - 1; i >= 0; i--) {
284
+ if (messages[i].role === "user") {
285
+ lastUser = i;
286
+ break;
287
+ }
288
+ }
289
+ const turn = messages.slice(Math.max(0, lastUser));
290
+ const latestUser = lastUser >= 0 ? messages[lastUser] : void 0;
291
+ const prompt = latestUser?.role === "user" ? contentText(latestUser.content) : "";
292
+ if (LUNA_PTC_MULTI_WORK_CUE.test(prompt)) return true;
293
+ const safeToolNames = /* @__PURE__ */ new Set([...LUNA_PTC_SAFE_FUNCTIONS, ...programmaticToolNames]);
294
+ const safeCalls = /* @__PURE__ */ new Set();
295
+ let safeOutputChars = 0;
296
+ for (const message of turn) {
297
+ if (message.role === "assistant") {
298
+ if (message.responses_output_items?.some((item) => item.type === "program")) return true;
299
+ for (const call of message.tool_calls ?? []) {
300
+ if (safeToolNames.has(call.function.name)) safeCalls.add(call.id);
301
+ }
302
+ continue;
303
+ }
304
+ if (message.role === "tool" && safeCalls.has(message.tool_call_id)) {
305
+ safeOutputChars += message.content.length;
306
+ if (safeOutputChars >= LUNA_PTC_TOOL_OUTPUT_TRIGGER_CHARS) return true;
307
+ }
308
+ }
309
+ return false;
310
+ }
311
+ function lunaToolPlan(tools, enableProgrammaticToolCalling = false, programmaticToolNames = [], forceProgrammaticOnly = false) {
312
+ const native = (tools ?? []).map((tool) => ({
313
+ type: "function",
314
+ name: tool.function.name,
315
+ description: tool.function.description,
316
+ parameters: tool.function.parameters,
317
+ ...tool.function.output_schema ? { output_schema: tool.function.output_schema } : {},
318
+ strict: false
319
+ }));
320
+ const mcpIndexes = native.map((tool, index) => String(tool.name).startsWith("mcp__") ? index : -1).filter((index) => index >= 0);
321
+ const deferredSchemaChars = mcpIndexes.reduce((sum, index) => sum + JSON.stringify(native[index]).length, 0);
322
+ const useToolSearch = mcpIndexes.length >= LUNA_TOOL_SEARCH_MIN_MCP_TOOLS || deferredSchemaChars >= LUNA_TOOL_SEARCH_MIN_MCP_SCHEMA_CHARS;
323
+ let programmaticToolCount = 0;
324
+ const safeToolNames = /* @__PURE__ */ new Set([...LUNA_PTC_SAFE_FUNCTIONS, ...programmaticToolNames]);
325
+ if (enableProgrammaticToolCalling) {
326
+ for (let index = 0; index < native.length; index++) {
327
+ if (safeToolNames.has(String(native[index].name))) {
328
+ native[index] = { ...native[index], allowed_callers: forceProgrammaticOnly ? ["programmatic"] : ["direct", "programmatic"] };
329
+ programmaticToolCount++;
330
+ }
331
+ }
332
+ if (programmaticToolCount > 0) native.push({ type: "programmatic_tool_calling" });
333
+ }
334
+ const useProgrammaticToolCalling = programmaticToolCount > 0;
335
+ if (!useToolSearch) {
336
+ return {
337
+ tools: native,
338
+ useToolSearch: false,
339
+ useProgrammaticToolCalling,
340
+ deferredToolCount: 0,
341
+ deferredSchemaChars
342
+ };
343
+ }
344
+ for (const index of mcpIndexes) native[index] = { ...native[index], defer_loading: true };
345
+ native.push({
346
+ type: "tool_search",
347
+ execution: "server"
348
+ });
349
+ return {
350
+ tools: native,
351
+ useToolSearch: true,
352
+ useProgrammaticToolCalling,
353
+ deferredToolCount: mcpIndexes.length,
354
+ deferredSchemaChars
355
+ };
356
+ }
357
+ function responseTools(tools, enableProgrammaticToolCalling = false, programmaticToolNames = [], forceProgrammaticOnly = false) {
358
+ return lunaToolPlan(tools, enableProgrammaticToolCalling, programmaticToolNames, forceProgrammaticOnly).tools;
359
+ }
360
+ function responseReasoning(level) {
361
+ if (level === "off") return { reasoning: { effort: "none" } };
362
+ return { reasoning: { ...level === "auto" ? {} : { effort: level }, summary: "auto" } };
363
+ }
364
+ function toResponsesUsage(usage) {
365
+ return usage ? { inputTokens: usage.input_tokens ?? 0, outputTokens: usage.output_tokens ?? 0 } : void 0;
366
+ }
367
+ function programCaller(item) {
368
+ const caller = item?.caller;
369
+ return caller?.type === "program" && typeof caller.caller_id === "string" ? { type: "program", caller_id: caller.caller_id } : void 0;
370
+ }
371
+ function responsesFinishReason(items, toolCallCount, incompleteReason) {
372
+ if (incompleteReason) return incompleteReason;
373
+ if (toolCallCount) return "tool_calls";
374
+ const hasMessage = items?.some((item) => item.type === "message") ?? false;
375
+ const hasProgramState = items?.some((item) => item.type === "program" || item.type === "program_output") ?? false;
376
+ return !hasMessage && hasProgramState ? "continue" : "stop";
377
+ }
378
+ function hasPendingProgrammaticReplay(messages) {
379
+ let pending = false;
380
+ for (const message of messages) {
381
+ if (message.role !== "assistant") continue;
382
+ for (const item of message.responses_output_items ?? []) {
383
+ if (item.type === "program") pending = true;
384
+ else if (item.type === "message" && pending) pending = false;
385
+ }
386
+ }
387
+ return pending;
388
+ }
389
+ async function readResponsesStream(res, onText, onThinking) {
390
+ let text = "";
391
+ let thinking = "";
392
+ let usage;
393
+ let finishReason;
394
+ let loopAt = -1;
395
+ const calls = /* @__PURE__ */ new Map();
396
+ const outputItems = /* @__PURE__ */ new Map();
397
+ let responsesOutputItems;
398
+ const decoder = new TextDecoder();
399
+ let buffer = "";
400
+ const emitText = (delta) => {
401
+ if (!delta) return;
402
+ text += delta;
403
+ onText?.(delta);
404
+ if (loopAt < 0) loopAt = detectRepetition(text);
405
+ };
406
+ const handle = (line) => {
407
+ if (!line.startsWith("data:")) return;
408
+ const data = line.slice(5).trim();
409
+ if (!data || data === "[DONE]") return;
410
+ let event;
411
+ try {
412
+ event = JSON.parse(data);
413
+ } catch {
414
+ return;
415
+ }
416
+ if (event.type === "error") {
417
+ throw new ProviderError(t("Error from the provider: {message}", { message: event.message ?? event.error?.message ?? JSON.stringify(event) }));
418
+ }
419
+ if (event.type === "response.failed") {
420
+ throw new ProviderError(t("Error from the provider: {message}", { message: event.response?.error?.message ?? "Response failed" }));
421
+ }
422
+ if (event.type === "response.reasoning_summary_text.delta" || event.type === "response.reasoning_summary.delta") {
423
+ const summary = String(event.delta ?? "");
424
+ thinking += summary;
425
+ if (summary) onThinking?.(summary);
426
+ return;
427
+ }
428
+ if (event.type === "response.output_text.delta" || event.type === "response.refusal.delta") {
429
+ emitText(String(event.delta ?? ""));
430
+ return;
431
+ }
432
+ if (event.type === "response.output_item.added" && event.item?.type === "function_call") {
433
+ calls.set(Number(event.output_index ?? calls.size), {
434
+ id: String(event.item.call_id || event.item.id || `call_${calls.size}`),
435
+ name: String(event.item.name || ""),
436
+ arguments: String(event.item.arguments || ""),
437
+ ...programCaller(event.item) ? { caller: programCaller(event.item) } : {}
438
+ });
439
+ return;
440
+ }
441
+ if (event.type === "response.function_call_arguments.delta") {
442
+ const index = Number(event.output_index ?? 0);
443
+ const call = calls.get(index);
444
+ if (call) call.arguments += String(event.delta || "");
445
+ return;
446
+ }
447
+ if (event.type === "response.output_item.done" && event.item) {
448
+ const index = Number(event.output_index ?? outputItems.size);
449
+ outputItems.set(index, event.item);
450
+ if (event.item.type === "function_call") {
451
+ const current = calls.get(index);
452
+ calls.set(index, {
453
+ id: String(event.item.call_id || current?.id || event.item.id || `call_${index}`),
454
+ name: String(event.item.name || current?.name || ""),
455
+ arguments: String(event.item.arguments ?? current?.arguments ?? ""),
456
+ ...programCaller(event.item) ?? current?.caller ? { caller: programCaller(event.item) ?? current?.caller } : {}
457
+ });
458
+ }
459
+ return;
460
+ }
461
+ if (event.type === "response.completed") {
462
+ usage = toResponsesUsage(event.response?.usage);
463
+ responsesOutputItems = event.response?.output ?? [...outputItems.entries()].sort(([a], [b]) => a - b).map(([, item]) => item);
464
+ if (!thinking) {
465
+ const summary = outputReasoningSummary(responsesOutputItems);
466
+ if (summary) {
467
+ thinking = summary;
468
+ onThinking?.(summary);
469
+ }
470
+ }
471
+ finishReason = responsesFinishReason(responsesOutputItems, calls.size);
472
+ return;
473
+ }
474
+ if (event.type === "response.incomplete") {
475
+ usage = toResponsesUsage(event.response?.usage);
476
+ responsesOutputItems = event.response?.output ?? [...outputItems.entries()].sort(([a], [b]) => a - b).map(([, item]) => item);
477
+ if (!thinking) {
478
+ const summary = outputReasoningSummary(responsesOutputItems);
479
+ if (summary) {
480
+ thinking = summary;
481
+ onThinking?.(summary);
482
+ }
483
+ }
484
+ finishReason = responsesFinishReason(
485
+ responsesOutputItems,
486
+ calls.size,
487
+ String(event.response?.incomplete_details?.reason || "incomplete")
488
+ );
489
+ }
490
+ };
491
+ for await (const chunk of res.body) {
492
+ buffer += decoder.decode(chunk, { stream: true });
493
+ let newline = buffer.indexOf("\n");
494
+ while (newline >= 0 && loopAt < 0) {
495
+ handle(buffer.slice(0, newline).trim());
496
+ buffer = buffer.slice(newline + 1);
497
+ newline = buffer.indexOf("\n");
498
+ }
499
+ if (loopAt >= 0) {
500
+ return { text: text.slice(0, loopAt), toolCalls: [], usage, finishReason: "repetition" };
501
+ }
502
+ }
503
+ handle(buffer.trim());
504
+ return {
505
+ text,
506
+ toolCalls: [...calls.entries()].sort(([a], [b]) => a - b).map(([, call]) => call),
507
+ thinking: thinking || void 0,
508
+ usage,
509
+ finishReason,
510
+ responsesOutputItems
511
+ };
512
+ }
513
+ async function readResponsesJson(res, onThinking) {
514
+ const body = await res.json();
515
+ let text = "";
516
+ let thinking = "";
517
+ const toolCalls = [];
518
+ for (const item of body.output ?? []) {
519
+ if (item?.type === "message") {
520
+ for (const part of item.content ?? []) {
521
+ if (part?.type === "output_text" && typeof part.text === "string") text += part.text;
522
+ else if (part?.type === "refusal" && typeof part.refusal === "string") text += part.refusal;
523
+ }
524
+ } else if (item?.type === "reasoning" && Array.isArray(item.summary)) {
525
+ const summary = item.summary.filter((part) => typeof part === "object" && part !== null && typeof part.text === "string").map((part) => part.text).join("");
526
+ if (summary) {
527
+ thinking += summary;
528
+ onThinking?.(summary);
529
+ }
530
+ } else if (item?.type === "function_call") {
531
+ toolCalls.push({
532
+ id: String(item.call_id || item.id || `call_${toolCalls.length}`),
533
+ name: String(item.name || ""),
534
+ arguments: String(item.arguments || "{}"),
535
+ ...programCaller(item) ? { caller: programCaller(item) } : {}
536
+ });
537
+ }
538
+ }
539
+ return {
540
+ text,
541
+ toolCalls,
542
+ thinking: thinking || void 0,
543
+ usage: toResponsesUsage(body.usage),
544
+ responsesOutputItems: body.output,
545
+ finishReason: responsesFinishReason(
546
+ body.output,
547
+ toolCalls.length,
548
+ body.status === "incomplete" ? String(body.incomplete_details?.reason || "incomplete") : void 0
549
+ )
550
+ };
551
+ }
552
+ function cloneMessages(messages) {
553
+ return structuredClone(messages);
554
+ }
555
+ function sameMessage(a, b) {
556
+ return JSON.stringify(a) === JSON.stringify(b);
557
+ }
558
+ function completionAssistantMessage(completion) {
559
+ return {
560
+ role: "assistant",
561
+ content: completion.text || null,
562
+ ...completion.toolCalls.length ? {
563
+ tool_calls: completion.toolCalls.map((call) => ({
564
+ id: call.id,
565
+ type: "function",
566
+ function: { name: call.name, arguments: call.arguments },
567
+ ...call.caller ? { caller: call.caller } : {}
568
+ }))
569
+ } : {},
570
+ ...completion.responsesOutputItems?.length ? { responses_output_items: completion.responsesOutputItems } : {}
571
+ };
572
+ }
573
+ function responseContinuationInput(messages, continuation) {
574
+ if (!continuation || messages.length <= continuation.requestMessages.length) {
575
+ return { input: responseInputFromMessages(messages), incremental: false };
576
+ }
577
+ for (let i = 0; i < continuation.requestMessages.length; i++) {
578
+ if (!sameMessage(messages[i], continuation.requestMessages[i])) {
579
+ return { input: responseInputFromMessages(messages), incremental: false };
580
+ }
581
+ }
582
+ const expectedAssistant = completionAssistantMessage(continuation.completion);
583
+ const assistant = messages[continuation.requestMessages.length];
584
+ if (!assistant || !sameMessage(assistant, expectedAssistant)) {
585
+ return { input: responseInputFromMessages(messages), incremental: false };
586
+ }
587
+ const delta = messages.slice(continuation.requestMessages.length + 1);
588
+ if (!delta.length) {
589
+ if (continuation.completion.finishReason === "continue") {
590
+ return { input: [], previousResponseId: continuation.responseId, incremental: true };
591
+ }
592
+ return { input: responseInputFromMessages(messages), incremental: false };
593
+ }
594
+ return {
595
+ input: responseInputFromMessages(delta),
596
+ previousResponseId: continuation.responseId,
597
+ incremental: true
598
+ };
599
+ }
600
+ async function readResponsesWebSocketTurn(events, ws, onText, onThinking, signal) {
601
+ let responseId = "";
602
+ let text = "";
603
+ let thinking = "";
604
+ let usage;
605
+ let finishReason;
606
+ let loopAt = -1;
607
+ const calls = /* @__PURE__ */ new Map();
608
+ const outputItems = /* @__PURE__ */ new Map();
609
+ const emitText = (delta) => {
610
+ if (!delta) return;
611
+ text += delta;
612
+ onText?.(delta);
613
+ if (loopAt < 0) loopAt = detectRepetition(text);
614
+ };
615
+ const abort = () => {
616
+ try {
617
+ ws.close({ code: 1e3, reason: "BCCLI request cancelled" });
618
+ } catch {
619
+ }
620
+ };
621
+ signal?.addEventListener("abort", abort, { once: true });
622
+ try {
623
+ while (true) {
624
+ if (signal?.aborted) throw signal.reason ?? new Error("Request aborted");
625
+ const next = await events.next();
626
+ if (next.done) throw new ProviderError(t("Connection closed before the response finished."));
627
+ const envelope = next.value;
628
+ if (envelope.type === "error") {
629
+ throw new ProviderError(envelope.error.message || t("WebSocket connection failed."));
630
+ }
631
+ if (envelope.type === "close") {
632
+ throw new ProviderError(
633
+ t("WebSocket connection closed: {reason}", { reason: envelope.reason || String(envelope.code) })
634
+ );
635
+ }
636
+ if (envelope.type !== "message") continue;
637
+ const event = envelope.message;
638
+ if (event.type === "error") {
639
+ const code = "code" in (event.error ?? {}) ? String(event.error.code || "") : void 0;
640
+ throw new ProviderError(
641
+ t("Error from the provider: {message}", { message: event.error?.message ?? event.message ?? JSON.stringify(event) }),
642
+ event.status,
643
+ code
644
+ );
645
+ }
646
+ if (event.type === "response.created") {
647
+ responseId = String(event.response?.id || responseId);
648
+ continue;
649
+ }
650
+ if (event.type === "response.reasoning_summary_text.delta") {
651
+ const summary = String(event.delta ?? "");
652
+ thinking += summary;
653
+ if (summary) onThinking?.(summary);
654
+ continue;
655
+ }
656
+ if (event.type === "response.output_text.delta" || event.type === "response.refusal.delta") {
657
+ emitText(String(event.delta ?? ""));
658
+ if (loopAt >= 0) {
659
+ abort();
660
+ return {
661
+ responseId,
662
+ completion: { text: text.slice(0, loopAt), toolCalls: [], usage, finishReason: "repetition" }
663
+ };
664
+ }
665
+ continue;
666
+ }
667
+ if (event.type === "response.output_item.added" && event.item?.type === "function_call") {
668
+ const index = Number(event.output_index ?? calls.size);
669
+ calls.set(index, {
670
+ id: String(event.item.call_id || event.item.id || `call_${index}`),
671
+ name: String(event.item.name || ""),
672
+ arguments: String(event.item.arguments || ""),
673
+ ...programCaller(event.item) ? { caller: programCaller(event.item) } : {}
674
+ });
675
+ continue;
676
+ }
677
+ if (event.type === "response.function_call_arguments.delta") {
678
+ const index = Number(event.output_index ?? 0);
679
+ const call = calls.get(index);
680
+ if (call) call.arguments += String(event.delta || "");
681
+ continue;
682
+ }
683
+ if (event.type === "response.output_item.done" && event.item) {
684
+ const index = Number(event.output_index ?? outputItems.size);
685
+ outputItems.set(index, event.item);
686
+ if (event.item.type === "function_call") {
687
+ const current = calls.get(index);
688
+ calls.set(index, {
689
+ id: String(event.item.call_id || current?.id || event.item.id || `call_${index}`),
690
+ name: String(event.item.name || current?.name || ""),
691
+ arguments: String(event.item.arguments ?? current?.arguments ?? ""),
692
+ ...programCaller(event.item) ?? current?.caller ? { caller: programCaller(event.item) ?? current?.caller } : {}
693
+ });
694
+ }
695
+ continue;
696
+ }
697
+ if (event.type === "response.failed") {
698
+ throw new ProviderError(
699
+ t("Error from the provider: {message}", { message: event.response?.error?.message ?? "Response failed" })
700
+ );
701
+ }
702
+ if (event.type === "response.completed" || event.type === "response.incomplete") {
703
+ responseId = String(event.response?.id || responseId);
704
+ usage = toResponsesUsage(event.response?.usage);
705
+ const responsesOutputItems = event.response?.output ?? [...outputItems.entries()].sort(([a], [b]) => a - b).map(([, item]) => item);
706
+ if (!thinking) {
707
+ const summary = outputReasoningSummary(responsesOutputItems);
708
+ if (summary) {
709
+ thinking = summary;
710
+ onThinking?.(summary);
711
+ }
712
+ }
713
+ finishReason = responsesFinishReason(
714
+ responsesOutputItems,
715
+ calls.size,
716
+ event.type === "response.incomplete" ? String(event.response?.incomplete_details?.reason || "incomplete") : void 0
717
+ );
718
+ return {
719
+ responseId,
720
+ completion: {
721
+ text,
722
+ toolCalls: [...calls.entries()].sort(([a], [b]) => a - b).map(([, call]) => call),
723
+ thinking: thinking || void 0,
724
+ usage,
725
+ finishReason,
726
+ responsesOutputItems
727
+ }
728
+ };
729
+ }
730
+ }
731
+ } finally {
732
+ signal?.removeEventListener("abort", abort);
733
+ }
734
+ }
735
+ function createProvider(options) {
736
+ const {
737
+ baseURL,
738
+ apiKey,
739
+ model,
740
+ providerId,
741
+ fetch: fetchOverride,
742
+ retryDelayMs = 1e3,
743
+ responsesWebSocketFactory,
744
+ enableProgrammaticToolCalling = false
745
+ } = options;
746
+ const doFetch = fetchOverride ?? fetch;
747
+ const headers = { "content-type": "application/json" };
748
+ if (apiKey) headers.authorization = `Bearer ${apiKey}`;
749
+ const isBotConnectorCloud = /^https:\/\/api\.botconnector\.id\/v1\/?$/.test(baseURL) && apiKey?.startsWith("bc_live_");
750
+ if (isBotConnectorCloud) {
751
+ headers["x-botconnector-client"] = "bccli";
752
+ headers["x-botconnector-client-version"] = "0.4.1";
753
+ }
754
+ const useNativeResponses = isBotConnectorCloud && providerId === "bc-cloud" && model === "gpt-6-luna";
755
+ const useResponsesWebSocket = useNativeResponses && Boolean(apiKey) && (fetchOverride === void 0 || responsesWebSocketFactory !== void 0);
756
+ let lunaWs;
757
+ let lunaWsEvents;
758
+ let lunaContinuation;
759
+ let lunaWsIdleTimer;
760
+ const resetLunaWebSocket = () => {
761
+ if (lunaWsIdleTimer) clearTimeout(lunaWsIdleTimer);
762
+ lunaWsIdleTimer = void 0;
763
+ const current = lunaWs;
764
+ lunaWs = void 0;
765
+ lunaWsEvents = void 0;
766
+ lunaContinuation = void 0;
767
+ if (current) {
768
+ try {
769
+ current.close({ code: 1e3, reason: "BCCLI reset" });
770
+ } catch {
771
+ }
772
+ }
773
+ };
774
+ const ensureLunaWebSocket = () => {
775
+ if (lunaWsIdleTimer) clearTimeout(lunaWsIdleTimer);
776
+ lunaWsIdleTimer = void 0;
777
+ if (!lunaWs || !lunaWsEvents) {
778
+ const client = new OpenAI({
779
+ apiKey,
780
+ baseURL: baseURL.replace(/\/+$/, ""),
781
+ defaultHeaders: {
782
+ "x-botconnector-client": "bccli",
783
+ "x-botconnector-client-version": "0.4.1"
784
+ }
785
+ });
786
+ lunaWs = responsesWebSocketFactory ? responsesWebSocketFactory(client) : new ResponsesWS(client, { reconnect: null });
787
+ lunaWsEvents = lunaWs.stream({ maxBufferedEvents: 4096 });
788
+ }
789
+ return { ws: lunaWs, events: lunaWsEvents };
790
+ };
791
+ const scheduleLunaWebSocketIdleReset = () => {
792
+ if (lunaWsIdleTimer) clearTimeout(lunaWsIdleTimer);
793
+ lunaWsIdleTimer = setTimeout(() => resetLunaWebSocket(), 10 * 6e4);
794
+ lunaWsIdleTimer.unref?.();
795
+ };
796
+ async function chatLunaWebSocket(messages, tools, programmaticToolNames, reasoning, signal, onText, onThinking) {
797
+ const ptcEnabled = shouldEnableLunaPtc(messages, enableProgrammaticToolCalling, programmaticToolNames);
798
+ const forcePtc = shouldForceLunaPtc(messages, ptcEnabled);
799
+ const nativeTools = responseTools(tools, ptcEnabled, programmaticToolNames, forcePtc);
800
+ const initialPlan = responseContinuationInput(messages, lunaContinuation);
801
+ for (let attempt = 0; attempt < 2; attempt++) {
802
+ if (signal?.aborted) throw signal.reason ?? new Error("Request aborted");
803
+ const plan = attempt === 0 ? initialPlan : { input: responseInputFromMessages(messages), incremental: false, previousResponseId: void 0 };
804
+ const { ws, events } = ensureLunaWebSocket();
805
+ const createEvent = {
806
+ type: "response.create",
807
+ stream_id: "main",
808
+ model,
809
+ store: false,
810
+ input: plan.input,
811
+ ...plan.previousResponseId ? { previous_response_id: plan.previousResponseId } : {},
812
+ ...nativeTools.length ? { tools: nativeTools } : {},
813
+ context_management: [{ type: "compaction", compact_threshold: LUNA_AUTO_COMPACT_THRESHOLD }],
814
+ ...responseReasoning(reasoning)
815
+ };
816
+ let emittedText = false;
817
+ const trackText = (delta) => {
818
+ if (delta) emittedText = true;
819
+ onText?.(delta);
820
+ };
821
+ const trackSummary = (delta) => {
822
+ if (delta) emittedText = true;
823
+ onThinking?.(delta);
824
+ };
825
+ try {
826
+ ws.send(createEvent);
827
+ const result = await readResponsesWebSocketTurn(events, ws, trackText, trackSummary, signal);
828
+ if (result.completion.finishReason === "repetition") {
829
+ resetLunaWebSocket();
830
+ return result.completion;
831
+ }
832
+ if (!result.responseId) {
833
+ resetLunaWebSocket();
834
+ return result.completion;
835
+ }
836
+ lunaContinuation = {
837
+ responseId: result.responseId,
838
+ requestMessages: cloneMessages(messages),
839
+ completion: structuredClone(result.completion)
840
+ };
841
+ scheduleLunaWebSocketIdleReset();
842
+ return result.completion;
843
+ } catch (error) {
844
+ const err = error instanceof ProviderError ? error : new ProviderError(error.message);
845
+ const lostPrevious = err.code === "previous_response_not_found";
846
+ const handshakeStatus = /unexpected server response:\s*(\d{3})\b/i.exec(err.message);
847
+ const handshakeRejected = Boolean(handshakeStatus && Number(handshakeStatus[1]) >= 400);
848
+ const connectionLost = /websocket|connection closed|socket|network/i.test(err.message) || err.code === "websocket_connection_limit_reached";
849
+ resetLunaWebSocket();
850
+ if (!emittedText && !signal?.aborted && handshakeRejected) {
851
+ throw new ProviderError(err.message, Number(handshakeStatus?.[1]), "bc_ws_http_fallback");
852
+ }
853
+ if (attempt === 0 && !emittedText && !signal?.aborted && (plan.incremental || lostPrevious || connectionLost)) {
854
+ continue;
855
+ }
856
+ if (!emittedText && !signal?.aborted && connectionLost) {
857
+ throw new ProviderError(err.message, err.status, "bc_ws_http_fallback");
858
+ }
859
+ throw err;
860
+ }
861
+ }
862
+ throw new ProviderError(t("Request failed"));
863
+ }
864
+ async function post(path, body, signal) {
865
+ let lastError;
866
+ for (let attempt = 0; attempt < 4; attempt++) {
867
+ if (attempt > 0) await sleep(retryDelayMs * 2 ** (attempt - 1), signal);
868
+ let res;
869
+ try {
870
+ res = await doFetch(`${baseURL}${path}`, { method: "POST", headers, body: JSON.stringify(body), signal });
871
+ } catch (error) {
872
+ if (signal?.aborted) throw error;
873
+ lastError = new ProviderError(t("Cannot connect to {url}: {error}", { url: baseURL, error: error.message }));
874
+ continue;
875
+ }
876
+ if (res.ok) return res;
877
+ const text = await res.text().catch(() => "");
878
+ lastError = new ProviderError(t("{status} from {url}: {message}", { status: res.status, url: baseURL, message: errorMessage(text) }), res.status);
879
+ const deterministicQuota429 = res.status === 429 && /"code"\s*:\s*"(?:luna_monthly_quota_exhausted|plan_allowance_exhausted)"/i.test(text);
880
+ if (!retryable(res.status) || deterministicQuota429) throw lastError;
881
+ }
882
+ throw lastError ?? new ProviderError(t("Request failed"));
883
+ }
884
+ return {
885
+ async chat({ messages, tools, programmaticToolNames = [], signal, onText, onThinking, reasoning = "auto" }) {
886
+ let reasoningOverride;
887
+ try {
888
+ reasoningOverride = reasoningPayload(providerId, reasoning);
889
+ } catch (error) {
890
+ throw new ProviderError(error.message);
891
+ }
892
+ if (useResponsesWebSocket) {
893
+ try {
894
+ return await chatLunaWebSocket(messages, tools, programmaticToolNames, reasoning, signal, onText, onThinking);
895
+ } catch (error) {
896
+ const err = error instanceof ProviderError ? error : new ProviderError(error.message);
897
+ if (err.code !== "bc_ws_http_fallback") throw err;
898
+ }
899
+ }
900
+ if (useNativeResponses) {
901
+ const ptcEnabled = shouldEnableLunaPtc(messages, enableProgrammaticToolCalling, programmaticToolNames);
902
+ const forcePtc = shouldForceLunaPtc(messages, ptcEnabled);
903
+ const nativeTools = responseTools(tools, ptcEnabled, programmaticToolNames, forcePtc);
904
+ const res2 = await post(
905
+ "/responses",
906
+ {
907
+ model,
908
+ input: responseInputFromMessages(messages),
909
+ stream: true,
910
+ store: false,
911
+ ...nativeTools.length ? { tools: nativeTools } : {},
912
+ context_management: [{ type: "compaction", compact_threshold: LUNA_AUTO_COMPACT_THRESHOLD }],
913
+ ...responseReasoning(reasoning)
914
+ },
915
+ signal
916
+ );
917
+ if ((res2.headers.get("content-type") ?? "").includes("application/json")) return readResponsesJson(res2, onThinking);
918
+ if (!res2.body) throw new ProviderError(t("Empty response from the provider"));
919
+ return readResponsesStream(res2, onText, onThinking);
920
+ }
921
+ const res = await post(
922
+ "/chat/completions",
923
+ { model, messages, stream: true, stream_options: { include_usage: true }, ...tools?.length ? { tools } : {}, ...reasoningOverride },
924
+ signal
925
+ );
926
+ if ((res.headers.get("content-type") ?? "").includes("application/json")) return readJson(res);
927
+ if (!res.body) throw new ProviderError(t("Empty response from the provider"));
928
+ return readStream(res, onText, onThinking);
929
+ },
930
+ async listModels() {
931
+ const res = await doFetch(`${baseURL}/models`, { headers });
932
+ if (!res.ok) throw new ProviderError(t("{status} while fetching the model list", { status: res.status }), res.status);
933
+ const body = await res.json();
934
+ const ids = (body.data ?? []).map((m) => m?.id).filter((id) => typeof id === "string" && id.length > 0);
935
+ return [...new Set(ids)].sort();
936
+ }
937
+ };
938
+ }
939
+
940
+ export {
941
+ contentText,
942
+ imageUserMessage,
943
+ hasPendingProgrammaticReplay,
944
+ completionAssistantMessage,
945
+ createProvider
946
+ };