@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 +45 -1
- package/dist/{args-EAKPBBGX.js → args-AWLROI65.js} +3 -2
- package/dist/{chunk-Z5JM76EL.js → chunk-2MGINW4O.js} +4 -1
- package/dist/{chunk-5C6NQFSI.js → chunk-7S6JXQCH.js} +1 -1
- package/dist/{chunk-XQLXE666.js → chunk-BFZXRKW5.js} +1 -1
- package/dist/{chunk-M7INIL2C.js → chunk-BJ337ABK.js} +17 -3
- package/dist/chunk-CLN4Z5E3.js +60 -0
- package/dist/{chunk-TV6LEHOH.js → chunk-DX7CZTBB.js} +1 -1
- package/dist/{chunk-QH7XXNHU.js → chunk-IOWDWOXS.js} +2 -2
- package/dist/{chunk-OSWQPSA4.js → chunk-KV27KTOX.js} +2 -0
- package/dist/{chunk-5ISYVAXI.js → chunk-U7GK73CK.js} +90 -15
- package/dist/cli.js +226 -40
- package/dist/{config-XPWPGNZJ.js → config-YUNKPCNB.js} +2 -2
- package/dist/integrationCli-GEIMUL3M.js +347 -0
- package/dist/{login-FTMR64IA.js → login-4C262MEI.js} +1 -1
- package/dist/{mcpCli-BV3IGR47.js → mcpCli-DZWQPTOS.js} +3 -3
- package/dist/providerCli-Q7LRM2NP.js +12 -0
- package/dist/{ui-LWZ75D7H.js → ui-6GTQUJOE.js} +211 -39
- package/package.json +2 -1
- package/dist/providerCli-SJRCBSRE.js +0 -11
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 #
|
|
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-
|
|
5
|
+
} from "./chunk-BJ337ABK.js";
|
|
6
6
|
import "./chunk-ELML4KVQ.js";
|
|
7
|
-
import "./chunk-
|
|
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 {
|
|
@@ -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-
|
|
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:
|
|
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
|
+
};
|
|
@@ -35,8 +35,8 @@ function readSecret(prompt) {
|
|
|
35
35
|
});
|
|
36
36
|
}
|
|
37
37
|
async function runLogin(providerId, cwd) {
|
|
38
|
-
const { runProviderCommand } = await import("./providerCli-
|
|
39
|
-
const { parseCliArgs } = await import("./args-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
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 });
|