@botconnector/bccli 0.4.0-beta.3 → 0.4.0-beta.4

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.
@@ -12,6 +12,54 @@ import {
12
12
  saveCredential
13
13
  } from "./chunk-RZUJYAQV.js";
14
14
 
15
+ // src/thinking.ts
16
+ var OPEN = "<think>";
17
+ var CLOSE = "</think>";
18
+ function partialTag(text, tag) {
19
+ for (let n = Math.min(tag.length - 1, text.length); n > 0; n--) if (tag.startsWith(text.slice(-n))) return n;
20
+ return 0;
21
+ }
22
+ var ThinkSplitter = class {
23
+ inside = false;
24
+ held = "";
25
+ push(delta) {
26
+ let rest = this.held + delta;
27
+ this.held = "";
28
+ let text = "";
29
+ let thinking = "";
30
+ while (rest) {
31
+ const tag = this.inside ? CLOSE : OPEN;
32
+ const at = rest.indexOf(tag);
33
+ if (at >= 0) {
34
+ if (this.inside) thinking += rest.slice(0, at);
35
+ else text += rest.slice(0, at);
36
+ rest = rest.slice(at + tag.length);
37
+ this.inside = !this.inside;
38
+ continue;
39
+ }
40
+ const keep = partialTag(rest, tag);
41
+ const out = rest.slice(0, rest.length - keep);
42
+ if (this.inside) thinking += out;
43
+ else text += out;
44
+ this.held = rest.slice(rest.length - keep);
45
+ break;
46
+ }
47
+ return { text, thinking };
48
+ }
49
+ /** Whatever was held back as a possible tag start is plain text after all. */
50
+ flush() {
51
+ const held = this.held;
52
+ this.held = "";
53
+ return this.inside ? { text: "", thinking: held } : { text: held, thinking: "" };
54
+ }
55
+ };
56
+ function splitThinking(text) {
57
+ const s = new ThinkSplitter();
58
+ const a = s.push(text);
59
+ const b = s.flush();
60
+ return { text: a.text + b.text, thinking: a.thinking + b.thinking };
61
+ }
62
+
15
63
  // src/provider.ts
16
64
  var ProviderError = class extends Error {
17
65
  constructor(message, status) {
@@ -58,8 +106,21 @@ function errorMessage(body) {
58
106
  }
59
107
  }
60
108
  var toUsage = (u) => u ? { inputTokens: u.prompt_tokens ?? 0, outputTokens: u.completion_tokens ?? 0 } : void 0;
61
- async function readStream(res, onText) {
109
+ async function readStream(res, onText, onThinking) {
62
110
  let text = "";
111
+ let thinking = "";
112
+ const splitter = new ThinkSplitter();
113
+ const emit = (part) => {
114
+ if (part.thinking) {
115
+ thinking += part.thinking;
116
+ onThinking?.(part.thinking);
117
+ }
118
+ if (part.text) {
119
+ text += part.text;
120
+ onText?.(part.text);
121
+ if (loopAt < 0) loopAt = detectRepetition(text);
122
+ }
123
+ };
63
124
  let usage;
64
125
  let finishReason;
65
126
  const calls = [];
@@ -84,11 +145,9 @@ async function readStream(res, onText) {
84
145
  if (!choice) return;
85
146
  if (choice.finish_reason) finishReason = choice.finish_reason;
86
147
  const delta = choice.delta ?? {};
87
- if (delta.content) {
88
- text += delta.content;
89
- onText?.(delta.content);
90
- if (loopAt < 0) loopAt = detectRepetition(text);
91
- }
148
+ const reasoning = delta.reasoning_content || delta.reasoning;
149
+ if (reasoning) emit({ text: "", thinking: reasoning });
150
+ if (delta.content) emit(splitter.push(delta.content));
92
151
  for (const tc of delta.tool_calls ?? []) {
93
152
  let target = tc.index !== void 0 ? byIndex.get(tc.index) : current;
94
153
  if (!target || tc.index === void 0 && tc.id && target.id && target.id !== tc.id) {
@@ -110,11 +169,12 @@ async function readStream(res, onText) {
110
169
  buffer = buffer.slice(newline + 1);
111
170
  newline = buffer.indexOf("\n");
112
171
  }
113
- if (loopAt >= 0) return { text: text.slice(0, loopAt), toolCalls: [], usage, finishReason: "repetition" };
172
+ if (loopAt >= 0) return { text: text.slice(0, loopAt), toolCalls: [], thinking: thinking || void 0, usage, finishReason: "repetition" };
114
173
  }
115
174
  handle(buffer.trim());
175
+ emit(splitter.flush());
116
176
  const toolCalls = calls.map((call, index) => ({ ...call, id: call.id || `call_${index}` }));
117
- return { text, toolCalls, usage, finishReason };
177
+ return { text, toolCalls, thinking: thinking || void 0, usage, finishReason };
118
178
  }
119
179
  async function readJson(res) {
120
180
  const body = await res.json();
@@ -124,7 +184,9 @@ async function readJson(res) {
124
184
  name: tc.function?.name ?? "",
125
185
  arguments: tc.function?.arguments ?? ""
126
186
  }));
127
- return { text: choice?.message?.content ?? "", toolCalls, usage: toUsage(body.usage), finishReason: choice?.finish_reason };
187
+ const split = splitThinking(choice?.message?.content ?? "");
188
+ const thinking = (choice?.message?.reasoning_content || choice?.message?.reasoning || "") + split.thinking;
189
+ return { text: split.text, toolCalls, thinking: thinking || void 0, usage: toUsage(body.usage), finishReason: choice?.finish_reason };
128
190
  }
129
191
  function createProvider({ baseURL, apiKey, model, fetch: doFetch = fetch, retryDelayMs = 1e3 }) {
130
192
  const headers = { "content-type": "application/json" };
@@ -149,7 +211,7 @@ function createProvider({ baseURL, apiKey, model, fetch: doFetch = fetch, retryD
149
211
  throw lastError ?? new ProviderError("Permintaan gagal");
150
212
  }
151
213
  return {
152
- async chat({ messages, tools, signal, onText }) {
214
+ async chat({ messages, tools, signal, onText, onThinking }) {
153
215
  const res = await post(
154
216
  "/chat/completions",
155
217
  { model, messages, stream: true, stream_options: { include_usage: true }, ...tools?.length ? { tools } : {} },
@@ -157,7 +219,7 @@ function createProvider({ baseURL, apiKey, model, fetch: doFetch = fetch, retryD
157
219
  );
158
220
  if ((res.headers.get("content-type") ?? "").includes("application/json")) return readJson(res);
159
221
  if (!res.body) throw new ProviderError("Respons provider kosong");
160
- return readStream(res, onText);
222
+ return readStream(res, onText, onThinking);
161
223
  },
162
224
  async listModels() {
163
225
  const res = await doFetch(`${baseURL}/models`, { headers });
@@ -35,7 +35,7 @@ function readSecret(prompt) {
35
35
  });
36
36
  }
37
37
  async function runLogin(providerId, cwd) {
38
- const { runProviderCommand } = await import("./providerCli-G3X7KENO.js");
38
+ const { runProviderCommand } = await import("./providerCli-MXV6PD6F.js");
39
39
  const { parseCliArgs } = await import("./args-FCLPQ7TO.js");
40
40
  return runProviderCommand(parseCliArgs(["provider", "add", providerId]), {
41
41
  env: process.env,
package/dist/cli.js CHANGED
@@ -7,10 +7,10 @@ import {
7
7
  addProviderKey,
8
8
  createProvider,
9
9
  listAllModels
10
- } from "./chunk-2JBRLGKD.js";
10
+ } from "./chunk-C5AFWX7H.js";
11
11
  import {
12
12
  runLogin
13
- } from "./chunk-L7L4RGKG.js";
13
+ } from "./chunk-GW36UMHZ.js";
14
14
  import {
15
15
  BUILTIN_AGENTS,
16
16
  buildSystemPrompt,
@@ -684,7 +684,8 @@ ${completion.text}` });
684
684
  messages: [{ role: "system", content: this.systemPrompt }, ...this.messages],
685
685
  tools: this.turnDefinitions,
686
686
  signal,
687
- onText: (delta) => this.onEvent({ type: "text", delta })
687
+ onText: (delta) => this.onEvent({ type: "text", delta }),
688
+ onThinking: (delta) => this.onEvent({ type: "thinking", delta })
688
689
  });
689
690
  if (!completion.toolCalls.length && completion.finishReason !== "repetition") {
690
691
  const recovered = recoverTextToolCalls(completion.text, this.turnDefinitions);
@@ -1272,7 +1273,7 @@ function createRuntime(opts) {
1272
1273
  }
1273
1274
 
1274
1275
  // src/version.ts
1275
- var VERSION = true ? "0.4.0-beta.3" : "0.0.0-dev";
1276
+ var VERSION = true ? "0.4.0-beta.4" : "0.0.0-dev";
1276
1277
 
1277
1278
  // src/cli.ts
1278
1279
  async function main() {
@@ -1289,12 +1290,12 @@ async function main() {
1289
1290
  if (args.command === "login") return runLogin(args.loginProvider ?? "bc-cloud", cwd);
1290
1291
  if (args.command === "mcp") {
1291
1292
  const { runMcpCommand } = await import("./mcpCli-Q2H63ISP.js");
1292
- const { readSecret } = await import("./login-KHU7ONSR.js");
1293
+ const { readSecret } = await import("./login-F4G5QXOU.js");
1293
1294
  return runMcpCommand(args, { env: process.env, cwd, out: (s) => console.log(s), err: (s) => console.error(s), readSecret });
1294
1295
  }
1295
1296
  if (args.command === "provider") {
1296
- const { runProviderCommand } = await import("./providerCli-G3X7KENO.js");
1297
- const { readSecret } = await import("./login-KHU7ONSR.js");
1297
+ const { runProviderCommand } = await import("./providerCli-MXV6PD6F.js");
1298
+ const { readSecret } = await import("./login-F4G5QXOU.js");
1298
1299
  return runProviderCommand(args, { env: process.env, cwd, out: (s) => console.log(s), err: (s) => console.error(s), readSecret });
1299
1300
  }
1300
1301
  if (args.command === "models") {
@@ -1324,7 +1325,7 @@ async function main() {
1324
1325
  }
1325
1326
  }
1326
1327
  if (!process.stdin.isTTY) throw new ConfigError('Mode interaktif butuh terminal. Untuk skrip/CI pakai: bccli -p "tugas"');
1327
- const { startInteractive } = await import("./ui-A32PTF4F.js");
1328
+ const { startInteractive } = await import("./ui-7FXPXXGN.js");
1328
1329
  return startInteractive(rt, { initialPrompt: args.prompt, resume: args.resume, version: VERSION });
1329
1330
  }
1330
1331
  main().then(
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  readSecret,
4
4
  runLogin
5
- } from "./chunk-L7L4RGKG.js";
5
+ } from "./chunk-GW36UMHZ.js";
6
6
  export {
7
7
  readSecret,
8
8
  runLogin
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  addProviderKey,
4
4
  runProviderCommand
5
- } from "./chunk-2JBRLGKD.js";
5
+ } from "./chunk-C5AFWX7H.js";
6
6
  import "./chunk-U7DLMVPZ.js";
7
7
  import "./chunk-RZUJYAQV.js";
8
8
  export {
@@ -638,6 +638,11 @@ function applyEvent(t, event) {
638
638
  if (last?.kind === "assistant") return { ...t, live: [...t.live.slice(0, -1), { ...last, text: last.text + event.delta }] };
639
639
  return { ...t, live: [...t.live, entry({ kind: "assistant", text: event.delta })] };
640
640
  }
641
+ case "thinking": {
642
+ const last = t.live.at(-1);
643
+ if (last?.kind === "thinking") return { ...t, live: [...t.live.slice(0, -1), { ...last, text: last.text + event.delta }] };
644
+ return { ...t, live: [...t.live, entry({ kind: "thinking", text: event.delta })] };
645
+ }
641
646
  case "textReplace": {
642
647
  const last = t.live.at(-1);
643
648
  if (last?.kind !== "assistant") return t;
@@ -687,6 +692,7 @@ function helpText(runtime) {
687
692
  "",
688
693
  "shift+tab ganti mode izin esc batalkan giliran",
689
694
  "ctrl+o output alat terakhir lengkap \\ + enter baris baru",
695
+ "ctrl+t buka/tutup thinking model",
690
696
  "@file + tab lengkapi nama file \u2191\u2193 riwayat input"
691
697
  ].join("\n");
692
698
  }
@@ -713,6 +719,8 @@ function App({ runtime, initialPrompt, version }) {
713
719
  const [mcpMenu, setMcpMenu] = useState8(null);
714
720
  const [listPicker, setListPicker] = useState8(null);
715
721
  const [planAsk, setPlanAsk] = useState8(null);
722
+ const [queued, setQueued] = useState8([]);
723
+ const [showThinking, setShowThinking] = useState8(false);
716
724
  useEffect2(() => runtime.todos.subscribe(setTodos), [runtime]);
717
725
  const extraCommands = [
718
726
  ...runtime.commands.filter((c) => !SLASH_COMMANDS.some((b) => b.name === c.name.toLowerCase())).map((c) => ({ name: c.name.toLowerCase(), description: c.description ?? "perintah custom" })),
@@ -1086,6 +1094,19 @@ ARGUMENTS: ${slash.args}`);
1086
1094
  },
1087
1095
  [runSlash, runTurn, runtime, notice]
1088
1096
  );
1097
+ const onPrompt = useCallback(
1098
+ (text) => {
1099
+ if (busy) setQueued((q) => [...q, text]);
1100
+ else submit(text);
1101
+ },
1102
+ [busy, submit]
1103
+ );
1104
+ useEffect2(() => {
1105
+ if (busy || pending || planAsk || !queued.length) return;
1106
+ const [next, ...rest] = queued;
1107
+ setQueued(rest);
1108
+ submit(next);
1109
+ }, [busy, pending, planAsk, queued, submit]);
1089
1110
  const initialSent = useRef(false);
1090
1111
  useEffect2(() => {
1091
1112
  if (initialPrompt && !initialSent.current) {
@@ -1100,6 +1121,25 @@ ARGUMENTS: ${slash.args}`);
1100
1121
  setMode(next);
1101
1122
  } else if (key.escape && busy && !pending) {
1102
1123
  controller.current?.abort();
1124
+ if (queued.length) {
1125
+ setQueued([]);
1126
+ notice(`${queued.length} pesan antrian dibatalkan.`, "warn");
1127
+ }
1128
+ } else if (key.ctrl && input === "t") {
1129
+ const next = !showThinking;
1130
+ setShowThinking(next);
1131
+ setTranscript((t) => {
1132
+ const last = [...t.done, ...t.live].findLast((e) => e.kind === "thinking");
1133
+ const reprint = next && last?.kind === "thinking" && t.done.includes(last);
1134
+ return {
1135
+ ...t,
1136
+ done: [
1137
+ ...t.done,
1138
+ ...reprint ? [entry({ kind: "thinking", text: last.text, open: true })] : [],
1139
+ entry({ kind: "notice", text: next ? "Thinking ditampilkan (ctrl+t untuk menutup)." : "Thinking disembunyikan (ctrl+t untuk membuka).", tone: "info" })
1140
+ ]
1141
+ };
1142
+ });
1103
1143
  } else if (key.ctrl && input === "o" && lastTool.current) {
1104
1144
  const t = lastTool.current;
1105
1145
  notice(`\u23BF ${t.tool} ${t.target}
@@ -1180,6 +1220,18 @@ ${t.output}`);
1180
1220
  /* @__PURE__ */ jsx15(Text15, { color: color(ACCENT), children: "\u25CF " }),
1181
1221
  /* @__PURE__ */ jsx15(Markdown, { text: e.text.replace(/^\s+/, "") })
1182
1222
  ] }, e.id);
1223
+ case "thinking": {
1224
+ const text = e.text.trim();
1225
+ if (!text) return null;
1226
+ if (!(e.open ?? showThinking)) {
1227
+ const lines = text.split("\n").length;
1228
+ return /* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: `\u273B Berpikir \xB7 ${lines} baris \xB7 ctrl+t buka` }) }, e.id);
1229
+ }
1230
+ return /* @__PURE__ */ jsxs10(Box13, { marginTop: 1, flexDirection: "column", children: [
1231
+ /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: "\u273B Berpikir (ctrl+t tutup)" }),
1232
+ /* @__PURE__ */ jsx15(Box13, { paddingLeft: 2, children: /* @__PURE__ */ jsx15(Text15, { dimColor: true, italic: true, children: text }) })
1233
+ ] }, e.id);
1234
+ }
1183
1235
  case "tool":
1184
1236
  return /* @__PURE__ */ jsx15(ToolBlock, { tool: e.tool, target: e.target, output: e.output, display: e.display, isError: e.isError, done: e.done, sub: e.sub }, e.id);
1185
1237
  case "notice":
@@ -1232,7 +1284,8 @@ ${t.output}`);
1232
1284
  }
1233
1285
  ) : null,
1234
1286
  /* @__PURE__ */ jsx15(TodoList, { items: todos }),
1235
- /* @__PURE__ */ jsx15(PromptInput, { disabled: busy || !!pending || !!picker || !!providerMenu || !!prompt || !!planAsk || !!mcpMenu || !!listPicker, history, cwd: runtime.cwd, onSubmit: submit, extraCommands }),
1287
+ queued.map((q, i) => /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: ` \u23F3 antri: ${q}` }, `${i}-${q}`)),
1288
+ /* @__PURE__ */ jsx15(PromptInput, { disabled: !!pending || !!picker || !!providerMenu || !!prompt || !!planAsk || !!mcpMenu || !!listPicker, history, cwd: runtime.cwd, onSubmit: onPrompt, extraCommands }),
1236
1289
  /* @__PURE__ */ jsx15(StatusBar, { mode, tokens, busy, model: modelLabel })
1237
1290
  ] });
1238
1291
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botconnector/bccli",
3
- "version": "0.4.0-beta.3",
3
+ "version": "0.4.0-beta.4",
4
4
  "description": "BCCLI — BotConnector AI coding agent for the terminal",
5
5
  "type": "module",
6
6
  "bin": {