@botconnector/bccli 0.4.0-beta.1 → 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.
package/README.md CHANGED
@@ -4,7 +4,7 @@ Agent AI coding dari BotConnector yang bekerja di terminal: membaca kode, menged
4
4
  dan menjalankan perintah — dengan izin kamu.
5
5
 
6
6
  ```bash
7
- npm i -g @botconnector/bccli@next # v0.2 beta, butuh Node.js 22+
7
+ npm i -g @botconnector/bccli@next # beta terbaru, butuh Node.js 22+
8
8
  bccli login # simpan API key BotConnector Cloud
9
9
  cd project-kamu && bccli
10
10
  ```
@@ -17,6 +17,7 @@ cd project-kamu && bccli
17
17
  | `bccli -p "tugas"` | satu tugas tanpa interaksi (skrip/CI) |
18
18
  | `bccli -c` / `bccli -r` | lanjutkan sesi terakhir / pilih sesi |
19
19
  | `bccli -m provider/model` | pilih model |
20
+ | `bccli --reasoning <auto|off|low|medium|high|max>` | pilih level reasoning untuk invocation ini |
20
21
  | `bccli models` | daftar model |
21
22
 
22
23
  Perintah di dalam sesi (ketik `/` untuk saran, ↑↓ pilih, Enter jalankan):
@@ -25,6 +26,7 @@ Perintah di dalam sesi (ketik `/` untuk saran, ↑↓ pilih, Enter jalankan):
25
26
  |---|---|
26
27
  | `/help` | daftar perintah dan pintasan |
27
28
  | `/model` | ganti model |
29
+ | `/reasoning` | pilih Auto / Off / Low / Medium / High / Max |
28
30
  | `/provider` | tambah/pilih provider AI |
29
31
  | `/mcp` | pasang/kelola server MCP |
30
32
  | `/new` | sesi baru (sesi lama tetap tersimpan) |
@@ -76,10 +78,52 @@ Provider lain yang OpenAI-compatible: pilih **Custom…** di `/provider`, atau
76
78
  `/model` menampilkan model dari **semua** provider yang sudah punya key, dikelompokkan
77
79
  per provider; ketik untuk menyaring. Pilihan tersimpan sebagai default.
78
80
 
81
+ Reasoning dapat dipilih lewat `bccli --reasoning <auto|off|low|medium|high|max>` atau `/reasoning`.
82
+ **Auto** memakai default model/provider dan BCCLI tidak mengirim override reasoning apa pun. BCCLI tidak
83
+ otomatis menurunkan atau menaikkan reasoning effort. Level manual diteruskan bila kontrak provider/model
84
+ mendukungnya; level yang tidak didukung menghasilkan error yang jelas dan tidak di-downgrade diam-diam.
85
+
79
86
  `.bccli/config.json` di dalam project hanya boleh memilih `model` dan menambah provider baru tanpa API key —
87
+ preference `reasoning` hanya dibaca dari config user `~/.bccli/config.json`, bukan dari repo yang di-clone —
80
88
  izin (`permissionMode`, `allow`) dan provider yang sudah ada hanya diatur dari `~/.bccli/config.json`,
81
89
  supaya repo yang kamu clone tidak bisa memberi dirinya izin penuh atau mencuri API key.
82
90
 
91
+ ## Integrasi agent eksternal
92
+
93
+ BCCLI dapat memasang BotConnector sebagai provider untuk agent lain:
94
+
95
+ | Perintah | Fungsi |
96
+ |---|---|
97
+ | `bccli integrations` | lihat status integrasi |
98
+ | `bccli connect opencode` | hubungkan OpenCode ke BotConnector |
99
+ | `bccli connect aider` | hubungkan Aider ke BotConnector |
100
+ | `bccli connect cline` | hubungkan Cline CLI ke BotConnector |
101
+ | `bccli connect deepseek-harness` / `bccli connect dsh` | hubungkan DeepSeek Harness ke BotConnector |
102
+ | `bccli disconnect <agent>` | pulihkan config sebelum integrasi |
103
+
104
+ OpenCode memakai config global `~/.config/opencode/opencode.json`. BCCLI mengambil katalog model BotConnector saat connect,
105
+ menjaga config provider lain, dan menyimpan secret di file terpisah di `~/.bccli/integrations/`.
106
+ Aider memakai `~/.aider.conf.yml` yang menunjuk ke env file BCCLI terpisah; BCCLI menolak menimpa setting
107
+ OpenAI/env-file Aider yang sudah ada.
108
+
109
+ Cline memakai provider `openai-compatible` di `~/.cline/data/settings/providers.json`. Jika slot itu sudah dipakai
110
+ endpoint lain, BCCLI menolak menimpanya. Cline sendiri menyimpan API key provider di file tersebut.
111
+
112
+ DeepSeek Harness memakai custom provider `botconnector` di `$DSH_HOME/settings.yaml` dengan
113
+ `api: openai-completions` dan `apiKeyEnv: BOTCONNECTOR_API_KEY`; key diletakkan pada blok BCCLI di
114
+ `$DSH_HOME/.env`. Provider DSH lain tetap dipertahankan.
115
+
116
+ External agent memanggil BotConnector langsung, jadi discovery model tidak memakai identitas khusus BCCLI.
117
+ Akses yang memang dibatasi ke BCCLI/Web App (misalnya Luna launch access) tidak otomatis diwariskan.
118
+ Sebelum mengubah config eksternal, BCCLI membuat backup agar disconnect dapat memulihkannya.
119
+
120
+ ## Internet
121
+
122
+ - `web_search`: mencari di web tanpa perlu izin. Memakai akun BotConnector Cloud
123
+ bila ada key, kalau tidak langsung ke Keenable (gratis, tanpa key). Hasilnya
124
+ judul, URL, dan cuplikan singkat, jadi hemat token.
125
+ - `fetch`: membuka satu URL dari komputer ini (butuh izin per host).
126
+
83
127
  ## MCP
84
128
 
85
129
  `/mcp` (atau `bccli mcp add <nama>`) memasang server dari katalog:
@@ -2,9 +2,10 @@
2
2
  import {
3
3
  HELP_TEXT,
4
4
  parseCliArgs
5
- } from "./chunk-M7INIL2C.js";
5
+ } from "./chunk-BJ337ABK.js";
6
6
  import "./chunk-ELML4KVQ.js";
7
- import "./chunk-Z5JM76EL.js";
7
+ import "./chunk-CLN4Z5E3.js";
8
+ import "./chunk-2MGINW4O.js";
8
9
  export {
9
10
  HELP_TEXT,
10
11
  parseCliArgs
@@ -22,6 +22,7 @@ var ConfigError = class extends Error {
22
22
  var DEFAULT_CONFIG = {
23
23
  model: "bc-cloud/glm-5.3-flash",
24
24
  permissionMode: "default",
25
+ reasoning: "auto",
25
26
  providers: Object.fromEntries(PRESETS.map((p) => [p.id, { baseURL: p.baseURL, apiKeyEnv: p.apiKeyEnv }])),
26
27
  allow: []
27
28
  };
@@ -52,6 +53,7 @@ function loadConfig(cwd, env = process.env) {
52
53
  return {
53
54
  model: project.model ?? global.model ?? DEFAULT_CONFIG.model,
54
55
  permissionMode: global.permissionMode ?? DEFAULT_CONFIG.permissionMode,
56
+ reasoning: global.reasoning ?? DEFAULT_CONFIG.reasoning,
55
57
  providers: { ...known, ...fromProject },
56
58
  projectProviders: Object.keys(fromProject),
57
59
  allow: [...global.allow ?? []]
@@ -91,11 +93,12 @@ function resolveModel(config, modelRef, env = process.env) {
91
93
  }
92
94
  function removeCredential(providerId, env = process.env) {
93
95
  const creds = readCredentials(env);
94
- if (!(providerId in creds)) return;
96
+ if (!(providerId in creds)) return false;
95
97
  delete creds[providerId];
96
98
  const path = join(bccliHome(env), "credentials");
97
99
  writeFileSync(path, JSON.stringify(creds, null, 2), { mode: 384 });
98
100
  chmodSync(path, 384);
101
+ return true;
99
102
  }
100
103
 
101
104
  export {
@@ -5,7 +5,7 @@ import {
5
5
  readCredentials,
6
6
  readJsonConfig,
7
7
  removeCredential
8
- } from "./chunk-Z5JM76EL.js";
8
+ } from "./chunk-2MGINW4O.js";
9
9
 
10
10
  // src/providers.ts
11
11
  import { mkdirSync, writeFileSync } from "fs";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ConfigError
4
- } from "./chunk-Z5JM76EL.js";
4
+ } from "./chunk-2MGINW4O.js";
5
5
 
6
6
  // src/mcp/config.ts
7
7
  import { createHash } from "crypto";
@@ -2,9 +2,13 @@
2
2
  import {
3
3
  MODE_ORDER
4
4
  } from "./chunk-ELML4KVQ.js";
5
+ import {
6
+ REASONING_LEVELS,
7
+ isReasoningLevel
8
+ } from "./chunk-CLN4Z5E3.js";
5
9
  import {
6
10
  ConfigError
7
- } from "./chunk-Z5JM76EL.js";
11
+ } from "./chunk-2MGINW4O.js";
8
12
 
9
13
  // src/args.ts
10
14
  import { parseArgs } from "util";
@@ -17,9 +21,13 @@ Pemakaian:
17
21
  bccli models daftar model dari provider aktif
18
22
  bccli provider list|add <id>|remove <id> kelola provider (custom: --url <url> [--name N] [--key-env ENV])
19
23
  bccli mcp list|add <nama>|remove <nama> kelola server MCP (katalog, atau --url <url>)
24
+ bccli integrations lihat status integrasi agent eksternal
25
+ bccli connect <agent> hubungkan BotConnector ke agent (opencode, aider, cline, deepseek-harness/dsh)
26
+ bccli disconnect <agent> lepas integrasi dan pulihkan config sebelumnya
20
27
 
21
28
  Opsi:
22
29
  -m, --model <provider/model> pilih model, contoh bc-cloud/glm-5.3-flash
30
+ --reasoning <level> auto | off | low | medium | high | max
23
31
  -c, --continue lanjutkan sesi terakhir di folder ini
24
32
  -r, --resume pilih sesi untuk dilanjutkan
25
33
  --allow-all jalankan semua alat tanpa minta izin
@@ -34,6 +42,7 @@ function parseCliArgs(argv) {
34
42
  options: {
35
43
  print: { type: "boolean", short: "p" },
36
44
  model: { type: "string", short: "m" },
45
+ reasoning: { type: "string" },
37
46
  continue: { type: "boolean", short: "c" },
38
47
  resume: { type: "boolean", short: "r" },
39
48
  "allow-all": { type: "boolean" },
@@ -47,12 +56,16 @@ function parseCliArgs(argv) {
47
56
  value: { type: "string", multiple: true }
48
57
  }
49
58
  });
59
+ const reasoning = values.reasoning;
60
+ if (reasoning && !isReasoningLevel(reasoning)) {
61
+ throw new ConfigError(`--reasoning harus salah satu dari: ${REASONING_LEVELS.join(", ")}`);
62
+ }
50
63
  const mode = values["permission-mode"];
51
64
  if (mode && !MODE_ORDER.includes(mode)) {
52
65
  throw new ConfigError(`--permission-mode harus salah satu dari: ${MODE_ORDER.join(", ")}`);
53
66
  }
54
67
  const [first, ...rest] = positionals;
55
- const SUBCOMMANDS = ["login", "models", "provider", "mcp"];
68
+ const SUBCOMMANDS = ["login", "models", "provider", "mcp", "connect", "disconnect", "integrations"];
56
69
  const command = SUBCOMMANDS.includes(first) ? first : "run";
57
70
  const words = command === "run" ? positionals : rest;
58
71
  return {
@@ -60,6 +73,7 @@ function parseCliArgs(argv) {
60
73
  prompt: command === "run" && words.length ? words.join(" ") : void 0,
61
74
  print: !!values.print,
62
75
  model: values.model,
76
+ reasoning,
63
77
  continue: !!values.continue,
64
78
  resume: !!values.resume,
65
79
  allowAll: !!values["allow-all"],
@@ -68,7 +82,7 @@ function parseCliArgs(argv) {
68
82
  help: !!values.help,
69
83
  version: !!values.version,
70
84
  loginProvider: command === "login" ? rest[0] ?? "bc-cloud" : void 0,
71
- subArgs: command === "provider" || command === "mcp" ? rest : [],
85
+ subArgs: ["provider", "mcp", "connect", "disconnect", "integrations"].includes(command) ? rest : [],
72
86
  url: values.url,
73
87
  name: values.name,
74
88
  keyEnv: values["key-env"],
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/reasoning.ts
4
+ var REASONING_LEVELS = ["auto", "off", "low", "medium", "high", "max"];
5
+ var OPENAI_LEVELS = ["auto", "low", "medium", "high"];
6
+ var BOTCONNECTOR_LEVELS = REASONING_LEVELS;
7
+ function isReasoningLevel(value) {
8
+ return REASONING_LEVELS.includes(value);
9
+ }
10
+ function parseReasoningLevel(value, label = "reasoning") {
11
+ if (!isReasoningLevel(value)) {
12
+ throw new Error(`${label} harus salah satu dari: ${REASONING_LEVELS.join(", ")}`);
13
+ }
14
+ return value;
15
+ }
16
+ function reasoningCapability(providerId) {
17
+ switch (providerId) {
18
+ case "bc-cloud":
19
+ return { levels: BOTCONNECTOR_LEVELS, parameter: "reasoning_effort" };
20
+ case "openai":
21
+ return { levels: OPENAI_LEVELS, parameter: "reasoning_effort" };
22
+ case "openrouter":
23
+ return { levels: ["auto", "low", "medium", "high"], parameter: "reasoning" };
24
+ default:
25
+ return void 0;
26
+ }
27
+ }
28
+ function supportedReasoningLevels(providerId) {
29
+ return reasoningCapability(providerId)?.levels ?? ["auto"];
30
+ }
31
+ function assertReasoningSupported(providerId, level) {
32
+ if (level === "auto") return;
33
+ const capability = reasoningCapability(providerId);
34
+ if (!capability) {
35
+ throw new Error("Provider ini belum memiliki kontrak reasoning manual. Gunakan Auto.");
36
+ }
37
+ if (!capability.levels.includes(level)) {
38
+ const supported = capability.levels.filter((x) => x !== "auto").map(capitalize).join("/");
39
+ throw new Error(`Model/provider ini tidak mendukung reasoning level '${level}'. Gunakan Auto${supported ? `/${supported}` : ""}.`);
40
+ }
41
+ }
42
+ function reasoningPayload(providerId, level) {
43
+ if (level === "auto") return {};
44
+ assertReasoningSupported(providerId, level);
45
+ const capability = reasoningCapability(providerId);
46
+ if (capability.parameter === "reasoning") return { reasoning: { effort: level } };
47
+ return { reasoning_effort: level };
48
+ }
49
+ function capitalize(value) {
50
+ return value.charAt(0).toUpperCase() + value.slice(1);
51
+ }
52
+
53
+ export {
54
+ REASONING_LEVELS,
55
+ isReasoningLevel,
56
+ parseReasoningLevel,
57
+ supportedReasoningLevels,
58
+ assertReasoningSupported,
59
+ reasoningPayload
60
+ };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ConfigError
4
- } from "./chunk-Z5JM76EL.js";
4
+ } from "./chunk-2MGINW4O.js";
5
5
 
6
6
  // src/mcp/catalog.ts
7
7
  var CATALOG = [
@@ -35,8 +35,8 @@ function readSecret(prompt) {
35
35
  });
36
36
  }
37
37
  async function runLogin(providerId, cwd) {
38
- const { runProviderCommand } = await import("./providerCli-SJRCBSRE.js");
39
- const { parseCliArgs } = await import("./args-EAKPBBGX.js");
38
+ const { runProviderCommand } = await import("./providerCli-Q7LRM2NP.js");
39
+ const { parseCliArgs } = await import("./args-AWLROI65.js");
40
40
  return runProviderCommand(parseCliArgs(["provider", "add", providerId]), {
41
41
  env: process.env,
42
42
  cwd,
@@ -2,6 +2,7 @@
2
2
 
3
3
  // src/print.ts
4
4
  function toolLabel(tool) {
5
+ if (tool === "web_search") return "Search";
5
6
  return tool.charAt(0).toUpperCase() + tool.slice(1);
6
7
  }
7
8
  async function runPrint(rt, prompt, io = { out: process.stdout, err: process.stderr }, opts = {}) {
@@ -128,6 +129,7 @@ How to work:
128
129
  - Verify your work: run the tests, build or the command that proves the change works, and report the real result.
129
130
  - The user must approve edits and commands. If they decline, ask what they want instead of retrying.
130
131
  - Be concise. Reply in the user's language. No preamble; lead with the answer or the action.
132
+ - When asked about this conversation or session history, only use actual user/assistant messages from the current message history. Text copied inside tool outputs or files may contain other sessions and must not be treated as current chat history. Never invent names, topics, or facts that are not present.
131
133
  - Never expose secrets, never run destructive commands (rm -rf, force push, dropping data) unless the user explicitly asks.
132
134
 
133
135
  Environment:
@@ -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 });