@botconnector/bccli 0.3.0-beta.3 → 0.4.0-beta.10

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.
@@ -4,13 +4,64 @@ import {
4
4
  providerName,
5
5
  removeGlobalProvider,
6
6
  writeGlobalConfig
7
- } from "./chunk-5C6NQFSI.js";
7
+ } from "./chunk-7S6JXQCH.js";
8
+ import {
9
+ reasoningPayload
10
+ } from "./chunk-CLN4Z5E3.js";
8
11
  import {
9
12
  PRESETS,
10
13
  loadConfig,
11
14
  readCredentials,
12
15
  saveCredential
13
- } from "./chunk-Z5JM76EL.js";
16
+ } from "./chunk-2MGINW4O.js";
17
+
18
+ // src/thinking.ts
19
+ var OPEN = "<think>";
20
+ var CLOSE = "</think>";
21
+ function partialTag(text, tag) {
22
+ for (let n = Math.min(tag.length - 1, text.length); n > 0; n--) if (tag.startsWith(text.slice(-n))) return n;
23
+ return 0;
24
+ }
25
+ var ThinkSplitter = class {
26
+ inside = false;
27
+ held = "";
28
+ push(delta) {
29
+ let rest = this.held + delta;
30
+ this.held = "";
31
+ let text = "";
32
+ let thinking = "";
33
+ while (rest) {
34
+ const tag = this.inside ? CLOSE : OPEN;
35
+ const at = rest.indexOf(tag);
36
+ if (at >= 0) {
37
+ if (this.inside) thinking += rest.slice(0, at);
38
+ else text += rest.slice(0, at);
39
+ rest = rest.slice(at + tag.length);
40
+ this.inside = !this.inside;
41
+ continue;
42
+ }
43
+ const keep = partialTag(rest, tag);
44
+ const out = rest.slice(0, rest.length - keep);
45
+ if (this.inside) thinking += out;
46
+ else text += out;
47
+ this.held = rest.slice(rest.length - keep);
48
+ break;
49
+ }
50
+ return { text, thinking };
51
+ }
52
+ /** Whatever was held back as a possible tag start is plain text after all. */
53
+ flush() {
54
+ const held = this.held;
55
+ this.held = "";
56
+ return this.inside ? { text: "", thinking: held } : { text: held, thinking: "" };
57
+ }
58
+ };
59
+ function splitThinking(text) {
60
+ const s = new ThinkSplitter();
61
+ const a = s.push(text);
62
+ const b = s.flush();
63
+ return { text: a.text + b.text, thinking: a.thinking + b.thinking };
64
+ }
14
65
 
15
66
  // src/provider.ts
16
67
  var ProviderError = class extends Error {
@@ -58,8 +109,21 @@ function errorMessage(body) {
58
109
  }
59
110
  }
60
111
  var toUsage = (u) => u ? { inputTokens: u.prompt_tokens ?? 0, outputTokens: u.completion_tokens ?? 0 } : void 0;
61
- async function readStream(res, onText) {
112
+ async function readStream(res, onText, onThinking) {
62
113
  let text = "";
114
+ let thinking = "";
115
+ const splitter = new ThinkSplitter();
116
+ const emit = (part) => {
117
+ if (part.thinking) {
118
+ thinking += part.thinking;
119
+ onThinking?.(part.thinking);
120
+ }
121
+ if (part.text) {
122
+ text += part.text;
123
+ onText?.(part.text);
124
+ if (loopAt < 0) loopAt = detectRepetition(text);
125
+ }
126
+ };
63
127
  let usage;
64
128
  let finishReason;
65
129
  const calls = [];
@@ -84,11 +148,9 @@ async function readStream(res, onText) {
84
148
  if (!choice) return;
85
149
  if (choice.finish_reason) finishReason = choice.finish_reason;
86
150
  const delta = choice.delta ?? {};
87
- if (delta.content) {
88
- text += delta.content;
89
- onText?.(delta.content);
90
- if (loopAt < 0) loopAt = detectRepetition(text);
91
- }
151
+ const reasoning = delta.reasoning_content || delta.reasoning;
152
+ if (reasoning) emit({ text: "", thinking: reasoning });
153
+ if (delta.content) emit(splitter.push(delta.content));
92
154
  for (const tc of delta.tool_calls ?? []) {
93
155
  let target = tc.index !== void 0 ? byIndex.get(tc.index) : current;
94
156
  if (!target || tc.index === void 0 && tc.id && target.id && target.id !== tc.id) {
@@ -110,11 +172,12 @@ async function readStream(res, onText) {
110
172
  buffer = buffer.slice(newline + 1);
111
173
  newline = buffer.indexOf("\n");
112
174
  }
113
- if (loopAt >= 0) return { text: text.slice(0, loopAt), toolCalls: [], usage, finishReason: "repetition" };
175
+ if (loopAt >= 0) return { text: text.slice(0, loopAt), toolCalls: [], thinking: thinking || void 0, usage, finishReason: "repetition" };
114
176
  }
115
177
  handle(buffer.trim());
178
+ emit(splitter.flush());
116
179
  const toolCalls = calls.map((call, index) => ({ ...call, id: call.id || `call_${index}` }));
117
- return { text, toolCalls, usage, finishReason };
180
+ return { text, toolCalls, thinking: thinking || void 0, usage, finishReason };
118
181
  }
119
182
  async function readJson(res) {
120
183
  const body = await res.json();
@@ -124,11 +187,17 @@ async function readJson(res) {
124
187
  name: tc.function?.name ?? "",
125
188
  arguments: tc.function?.arguments ?? ""
126
189
  }));
127
- return { text: choice?.message?.content ?? "", toolCalls, usage: toUsage(body.usage), finishReason: choice?.finish_reason };
190
+ const split = splitThinking(choice?.message?.content ?? "");
191
+ const thinking = (choice?.message?.reasoning_content || choice?.message?.reasoning || "") + split.thinking;
192
+ return { text: split.text, toolCalls, thinking: thinking || void 0, usage: toUsage(body.usage), finishReason: choice?.finish_reason };
128
193
  }
129
- function createProvider({ baseURL, apiKey, model, fetch: doFetch = fetch, retryDelayMs = 1e3 }) {
194
+ function createProvider({ baseURL, apiKey, model, providerId, fetch: doFetch = fetch, retryDelayMs = 1e3 }) {
130
195
  const headers = { "content-type": "application/json" };
131
196
  if (apiKey) headers.authorization = `Bearer ${apiKey}`;
197
+ if (/^https:\/\/api\.botconnector\.id\/v1\/?$/.test(baseURL) && apiKey?.startsWith("bc_live_")) {
198
+ headers["x-botconnector-client"] = "bccli";
199
+ headers["x-botconnector-client-version"] = "0.4.0";
200
+ }
132
201
  async function post(path, body, signal) {
133
202
  let lastError;
134
203
  for (let attempt = 0; attempt < 4; attempt++) {
@@ -149,15 +218,21 @@ function createProvider({ baseURL, apiKey, model, fetch: doFetch = fetch, retryD
149
218
  throw lastError ?? new ProviderError("Permintaan gagal");
150
219
  }
151
220
  return {
152
- async chat({ messages, tools, signal, onText }) {
221
+ async chat({ messages, tools, signal, onText, onThinking, reasoning = "auto" }) {
222
+ let reasoningOverride;
223
+ try {
224
+ reasoningOverride = reasoningPayload(providerId, reasoning);
225
+ } catch (error) {
226
+ throw new ProviderError(error.message);
227
+ }
153
228
  const res = await post(
154
229
  "/chat/completions",
155
- { model, messages, stream: true, stream_options: { include_usage: true }, ...tools?.length ? { tools } : {} },
230
+ { model, messages, stream: true, stream_options: { include_usage: true }, ...tools?.length ? { tools } : {}, ...reasoningOverride },
156
231
  signal
157
232
  );
158
233
  if ((res.headers.get("content-type") ?? "").includes("application/json")) return readJson(res);
159
234
  if (!res.body) throw new ProviderError("Respons provider kosong");
160
- return readStream(res, onText);
235
+ return readStream(res, onText, onThinking);
161
236
  },
162
237
  async listModels() {
163
238
  const res = await doFetch(`${baseURL}/models`, { headers });