@p-sw/brainbox 0.1.2-alpha.4 → 0.1.2-alpha.5

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 (2) hide show
  1. package/dist/index.js +108 -57
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -430,6 +430,21 @@ function defaultReasoningEffort(effort, model, identityModel) {
430
430
  return effort;
431
431
  return model === identityModel ? "medium" : "none";
432
432
  }
433
+ function stripThinkTags(content) {
434
+ return content.replace(/<think\b[^>]*>[\s\S]*?<\/think>/gi, "").trim();
435
+ }
436
+ function parseModelJson(content) {
437
+ const cleaned = stripThinkTags(content);
438
+ try {
439
+ return JSON.parse(cleaned);
440
+ } catch (first) {
441
+ const fence = cleaned.match(/```(?:json)?\s*([\s\S]*?)```/i);
442
+ if (fence?.[1]) {
443
+ return JSON.parse(fence[1].trim());
444
+ }
445
+ throw first;
446
+ }
447
+ }
433
448
  function readAuthString(auth, key, envName) {
434
449
  const fromAuth = typeof auth?.[key] === "string" ? auth[key] : undefined;
435
450
  if (fromAuth)
@@ -546,7 +561,7 @@ function fromOrChoice(choice) {
546
561
  }));
547
562
  return {
548
563
  message: {
549
- content: typeof msg.content === "string" ? msg.content : undefined,
564
+ content: typeof msg.content === "string" ? stripThinkTags(msg.content) : undefined,
550
565
  toolCalls
551
566
  }
552
567
  };
@@ -598,13 +613,14 @@ class OpenRouterExecutor extends LLMExecutor {
598
613
  stream: false
599
614
  }
600
615
  });
601
- const content = result.choices[0]?.message?.content;
616
+ const raw = result.choices[0]?.message?.content;
617
+ const content = typeof raw === "string" ? stripThinkTags(raw) : raw;
602
618
  if (!content) {
603
619
  log3.debug(`call: empty content in choice 0`);
604
620
  throw new Error("Empty response from model");
605
621
  }
606
622
  log3.debug(`call: response ${content.length} chars`);
607
- return jsonMode ? JSON.parse(content) : content;
623
+ return jsonMode ? parseModelJson(content) : content;
608
624
  }
609
625
  async chatWithTools(model, options) {
610
626
  log3.debug(`chatWithTools: model=${model} msgs=${options.messages.length} tools=${options.tools.length}`);
@@ -663,7 +679,7 @@ function fromChoice(choice) {
663
679
  }));
664
680
  return {
665
681
  message: {
666
- content: typeof msg.content === "string" ? msg.content : undefined,
682
+ content: typeof msg.content === "string" ? stripThinkTags(msg.content) : undefined,
667
683
  toolCalls
668
684
  }
669
685
  };
@@ -778,13 +794,14 @@ class OpenAICompatibleExecutor extends LLMExecutor {
778
794
  reasoningEffort: reasoning
779
795
  });
780
796
  const data = await this.sendRequest(body, options.reasoningEffort);
781
- const content = data.choices?.[0]?.message?.content;
797
+ const raw = data.choices?.[0]?.message?.content;
798
+ const content = typeof raw === "string" ? stripThinkTags(raw) : raw;
782
799
  if (!content) {
783
800
  log4.debug(`call: empty content in choice 0`);
784
801
  throw new Error("Empty response from model");
785
802
  }
786
803
  log4.debug(`call: response ${content.length} chars`);
787
- return jsonMode ? JSON.parse(content) : content;
804
+ return jsonMode ? parseModelJson(content) : content;
788
805
  }
789
806
  async chatWithTools(model, options) {
790
807
  const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
@@ -1099,28 +1116,34 @@ class ZenMuxExecutor extends OpenAICompatibleExecutor {
1099
1116
  }
1100
1117
 
1101
1118
  // src/provider/providers/minimax.ts
1102
- class MiniMaxExecutor extends OpenAICompatibleExecutor {
1119
+ class MiniMaxBase extends OpenAICompatibleExecutor {
1120
+ buildBody(opts) {
1121
+ const body = super.buildBody(opts);
1122
+ delete body["reasoning_effort"];
1123
+ body["reasoning_split"] = true;
1124
+ body["thinking"] = opts.reasoningEffort && opts.reasoningEffort !== "none" ? { type: "adaptive" } : { type: "disabled" };
1125
+ return body;
1126
+ }
1127
+ }
1128
+ function minimaxOpts(providerName, baseURL, opts) {
1129
+ return {
1130
+ providerName,
1131
+ baseURL,
1132
+ apiKey: opts.apiKey,
1133
+ conversationModel: opts.conversationModel,
1134
+ identityModel: opts.identityModel
1135
+ };
1136
+ }
1137
+
1138
+ class MiniMaxExecutor extends MiniMaxBase {
1103
1139
  constructor(opts) {
1104
- super({
1105
- providerName: "minimax",
1106
- baseURL: "https://api.minimax.io/v1",
1107
- apiKey: opts.apiKey,
1108
- conversationModel: opts.conversationModel,
1109
- identityModel: opts.identityModel
1110
- });
1140
+ super(minimaxOpts("minimax", "https://api.minimax.io/v1", opts));
1111
1141
  }
1112
1142
  }
1113
1143
 
1114
- // src/provider/providers/minimax_cn.ts
1115
- class MiniMaxCnExecutor extends OpenAICompatibleExecutor {
1144
+ class MiniMaxCnExecutor extends MiniMaxBase {
1116
1145
  constructor(opts) {
1117
- super({
1118
- providerName: "minimax-cn",
1119
- baseURL: "https://api.minimaxi.com/v1",
1120
- apiKey: opts.apiKey,
1121
- conversationModel: opts.conversationModel,
1122
- identityModel: opts.identityModel
1123
- });
1146
+ super(minimaxOpts("minimax-cn", "https://api.minimaxi.com/v1", opts));
1124
1147
  }
1125
1148
  }
1126
1149
 
@@ -1273,7 +1296,17 @@ class CloudflareGatewayExecutor extends OpenAICompatibleExecutor {
1273
1296
  // src/provider/providers/cloudflare_workers.ts
1274
1297
  function toCloudflareMessage(m) {
1275
1298
  if (m.role === "assistant") {
1276
- return { role: "assistant", content: m.content ?? "" };
1299
+ return {
1300
+ role: "assistant",
1301
+ content: m.content ?? "",
1302
+ tool_calls: m.toolCalls?.map((c) => {
1303
+ let args = c.function.arguments;
1304
+ try {
1305
+ args = JSON.parse(c.function.arguments);
1306
+ } catch {}
1307
+ return { id: c.id, name: c.function.name, arguments: args };
1308
+ })
1309
+ };
1277
1310
  }
1278
1311
  if (m.role === "tool") {
1279
1312
  return { role: "tool", content: m.content, tool_call_id: m.toolCallId };
@@ -1335,11 +1368,11 @@ class CloudflareWorkersExecutor extends LLMExecutor {
1335
1368
  if (data.errors && data.errors.length > 0) {
1336
1369
  throw new Error(`cloudflare-workers API error: ${data.errors.map((e) => e.message).join("; ")}`);
1337
1370
  }
1338
- const content = data.result?.response ?? "";
1371
+ const content = stripThinkTags(data.result?.response ?? "");
1339
1372
  if (!content) {
1340
1373
  throw new Error("Empty response from model");
1341
1374
  }
1342
- return jsonMode ? JSON.parse(content) : content;
1375
+ return jsonMode ? parseModelJson(content) : content;
1343
1376
  }
1344
1377
  async chatWithTools(model, options) {
1345
1378
  const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
@@ -1364,7 +1397,7 @@ class CloudflareWorkersExecutor extends LLMExecutor {
1364
1397
  if (data.errors && data.errors.length > 0) {
1365
1398
  throw new Error(`cloudflare-workers API error: ${data.errors.map((e) => e.message).join("; ")}`);
1366
1399
  }
1367
- const content = data.result?.response ?? "";
1400
+ const content = stripThinkTags(data.result?.response ?? "");
1368
1401
  const toolCalls = data.result?.tool_calls?.map((c, idx) => ({
1369
1402
  id: `call_${idx}`,
1370
1403
  function: {
@@ -1402,7 +1435,8 @@ class AzureOpenAIExecutor extends OpenAICompatibleExecutor {
1402
1435
  super({
1403
1436
  providerName: "azure-openai",
1404
1437
  baseURL: `https://${resource || "__resource__"}.openai.azure.com/openai/deployments`,
1405
- apiKey: opts.apiKey,
1438
+ apiKey: "",
1439
+ defaultHeaders: { "api-key": opts.apiKey },
1406
1440
  conversationModel: opts.conversationModel,
1407
1441
  identityModel: opts.identityModel
1408
1442
  });
@@ -1422,7 +1456,8 @@ class AzureCognitiveExecutor extends OpenAICompatibleExecutor {
1422
1456
  super({
1423
1457
  providerName: "azure-cognitive",
1424
1458
  baseURL: `https://${resource || "__resource__"}.cognitiveservices.azure.com/openai/deployments`,
1425
- apiKey: opts.apiKey,
1459
+ apiKey: "",
1460
+ defaultHeaders: { "api-key": opts.apiKey },
1426
1461
  conversationModel: opts.conversationModel,
1427
1462
  identityModel: opts.identityModel
1428
1463
  });
@@ -1546,44 +1581,50 @@ class AnthropicExecutor extends LLMExecutor {
1546
1581
  const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
1547
1582
  const log5 = logger.child("llm:anthropic");
1548
1583
  log5.debug(`call: model=${model} jsonSchema=${jsonMode ? options.jsonSchemaName : "-"} msgLen=${options.message.length}`);
1584
+ const outputCap = 4096;
1549
1585
  const body = {
1550
1586
  model,
1551
- max_tokens: 4096,
1587
+ max_tokens: outputCap,
1552
1588
  system: options.instruction,
1553
1589
  messages: [{ role: "user", content: options.message }]
1554
1590
  };
1555
1591
  if (reasoning !== "none") {
1592
+ const budget = REASONING_BUDGET[reasoning];
1593
+ body["max_tokens"] = budget + outputCap;
1556
1594
  body["thinking"] = {
1557
1595
  type: "enabled",
1558
- budget_tokens: REASONING_BUDGET[reasoning]
1596
+ budget_tokens: budget
1559
1597
  };
1560
1598
  }
1561
1599
  const data = await this.send(body);
1562
1600
  if (data.error) {
1563
1601
  throw new Error(`anthropic API error: ${data.error.message ?? "unknown"}`);
1564
1602
  }
1565
- const text = (data.content ?? []).filter((b) => b.type === "text").map((b) => b.text).join("");
1603
+ const text = stripThinkTags((data.content ?? []).filter((b) => b.type === "text").map((b) => b.text).join(""));
1566
1604
  if (!text) {
1567
1605
  throw new Error("Empty response from model");
1568
1606
  }
1569
- return jsonMode ? JSON.parse(text) : text;
1607
+ return jsonMode ? parseModelJson(text) : text;
1570
1608
  }
1571
1609
  async chatWithTools(model, options) {
1572
1610
  const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
1573
1611
  const log5 = logger.child("llm:anthropic");
1574
1612
  log5.debug(`chatWithTools: model=${model} msgs=${options.messages.length} tools=${options.tools.length}`);
1575
1613
  const { system, msgs } = toAnthropicMessages(options.messages);
1614
+ const outputCap = 4096;
1576
1615
  const body = {
1577
1616
  model,
1578
- max_tokens: 4096,
1617
+ max_tokens: outputCap,
1579
1618
  system: system ?? options.instruction,
1580
1619
  messages: msgs,
1581
1620
  tools: options.tools.map(toAnthropicTool)
1582
1621
  };
1583
1622
  if (reasoning !== "none") {
1623
+ const budget = REASONING_BUDGET[reasoning];
1624
+ body["max_tokens"] = budget + outputCap;
1584
1625
  body["thinking"] = {
1585
1626
  type: "enabled",
1586
- budget_tokens: REASONING_BUDGET[reasoning]
1627
+ budget_tokens: budget
1587
1628
  };
1588
1629
  }
1589
1630
  const data = await this.send(body);
@@ -1591,7 +1632,7 @@ class AnthropicExecutor extends LLMExecutor {
1591
1632
  throw new Error(`anthropic API error: ${data.error.message ?? "unknown"}`);
1592
1633
  }
1593
1634
  const blocks = data.content ?? [];
1594
- const text = blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
1635
+ const text = stripThinkTags(blocks.filter((b) => b.type === "text").map((b) => b.text).join(""));
1595
1636
  const toolCalls = blocks.filter((b) => b.type === "tool_use").map((b) => ({
1596
1637
  id: b.id,
1597
1638
  function: {
@@ -1717,7 +1758,7 @@ function toAnthropicTool2(t) {
1717
1758
  }
1718
1759
  function extractAnthropicContent(data) {
1719
1760
  const content = data["content"] ?? [];
1720
- const text = content.filter((b) => b["type"] === "text").map((b) => b["text"]).join("");
1761
+ const text = stripThinkTags(content.filter((b) => b["type"] === "text").map((b) => b["text"]).join(""));
1721
1762
  const toolCalls = content.filter((b) => b["type"] === "tool_use").map((b) => ({
1722
1763
  id: b["id"],
1723
1764
  function: {
@@ -1792,7 +1833,7 @@ class BedrockExecutor extends LLMExecutor {
1792
1833
  if (!text) {
1793
1834
  throw new Error("Empty response from model");
1794
1835
  }
1795
- return jsonMode ? JSON.parse(text) : text;
1836
+ return jsonMode ? parseModelJson(text) : text;
1796
1837
  }
1797
1838
  async chatWithTools(model, options) {
1798
1839
  const log5 = logger.child("llm:bedrock");
@@ -1822,6 +1863,7 @@ var VertexAuthSchema = z5.object({
1822
1863
  }).loose();
1823
1864
  function toGeminiContents(messages) {
1824
1865
  const out = [];
1866
+ const nameByCallId = new Map;
1825
1867
  for (const m of messages) {
1826
1868
  if (m.role === "system")
1827
1869
  continue;
@@ -1835,6 +1877,7 @@ function toGeminiContents(messages) {
1835
1877
  parts.push({ text: m.content });
1836
1878
  if (m.toolCalls) {
1837
1879
  for (const c of m.toolCalls) {
1880
+ nameByCallId.set(c.id, c.function.name);
1838
1881
  let args = {};
1839
1882
  try {
1840
1883
  args = c.function.arguments ? JSON.parse(c.function.arguments) : {};
@@ -1856,21 +1899,26 @@ function toGeminiContents(messages) {
1856
1899
  }
1857
1900
  out.push({
1858
1901
  role: "user",
1859
- parts: [{ functionResponse: { name: "", response } }]
1902
+ parts: [
1903
+ {
1904
+ functionResponse: {
1905
+ name: nameByCallId.get(m.toolCallId) ?? "",
1906
+ response
1907
+ }
1908
+ }
1909
+ ]
1860
1910
  });
1861
1911
  }
1862
1912
  }
1863
1913
  return out;
1864
1914
  }
1865
- function toGeminiTool(t) {
1915
+ function toGeminiTools(tools) {
1866
1916
  return {
1867
- functionDeclarations: [
1868
- {
1869
- name: t.name,
1870
- description: t.description,
1871
- parameters: t.parameters ?? { type: "object", properties: {} }
1872
- }
1873
- ]
1917
+ functionDeclarations: tools.map((t) => ({
1918
+ name: t.name,
1919
+ description: t.description,
1920
+ parameters: t.parameters ?? { type: "object", properties: {} }
1921
+ }))
1874
1922
  };
1875
1923
  }
1876
1924
  function extractFromGemini(data) {
@@ -1891,7 +1939,10 @@ function extractFromGemini(data) {
1891
1939
  });
1892
1940
  }
1893
1941
  }
1894
- return { text, toolCalls: toolCalls.length > 0 ? toolCalls : undefined };
1942
+ return {
1943
+ text: stripThinkTags(text),
1944
+ toolCalls: toolCalls.length > 0 ? toolCalls : undefined
1945
+ };
1895
1946
  }
1896
1947
 
1897
1948
  class VertexExecutor extends LLMExecutor {
@@ -1957,7 +2008,7 @@ class VertexExecutor extends LLMExecutor {
1957
2008
  if (!text) {
1958
2009
  throw new Error("Empty response from model");
1959
2010
  }
1960
- return jsonMode ? JSON.parse(text) : text;
2011
+ return jsonMode ? parseModelJson(text) : text;
1961
2012
  }
1962
2013
  async chatWithTools(model, options) {
1963
2014
  const log5 = logger.child("llm:vertex");
@@ -1966,7 +2017,7 @@ class VertexExecutor extends LLMExecutor {
1966
2017
  const body = {
1967
2018
  contents,
1968
2019
  systemInstruction: { parts: [{ text: options.instruction }] },
1969
- tools: options.tools.length > 0 ? [toGeminiTool(options.tools[0])] : undefined
2020
+ tools: options.tools.length > 0 ? [toGeminiTools(options.tools)] : undefined
1970
2021
  };
1971
2022
  const data = await this.generate(model, body);
1972
2023
  if (data.error) {
@@ -2074,11 +2125,11 @@ class GitLabDuoExecutor extends LLMExecutor {
2074
2125
  if (data.error) {
2075
2126
  throw new Error(`gitlab-duo API error: ${data.error.message ?? "unknown"}`);
2076
2127
  }
2077
- const content = data.choices?.[0]?.message?.content ?? "";
2128
+ const content = stripThinkTags(data.choices?.[0]?.message?.content ?? "");
2078
2129
  if (!content) {
2079
2130
  throw new Error("Empty response from model");
2080
2131
  }
2081
- return jsonMode ? JSON.parse(content) : content;
2132
+ return jsonMode ? parseModelJson(content) : content;
2082
2133
  }
2083
2134
  async chatWithTools(model, options) {
2084
2135
  const log5 = logger.child("llm:gitlab-duo");
@@ -2102,7 +2153,7 @@ class GitLabDuoExecutor extends LLMExecutor {
2102
2153
  }));
2103
2154
  return {
2104
2155
  message: {
2105
- content: choice?.message?.content ?? undefined,
2156
+ content: choice?.message?.content ? stripThinkTags(choice.message.content) : undefined,
2106
2157
  toolCalls
2107
2158
  }
2108
2159
  };
@@ -2193,11 +2244,11 @@ class SnowflakeCortexExecutor extends LLMExecutor {
2193
2244
  if (data.error) {
2194
2245
  throw new Error(`snowflake-cortex API error: ${data.error.message ?? "unknown"}`);
2195
2246
  }
2196
- const content = data.choices?.[0]?.message?.content ?? data.message ?? "";
2247
+ const content = stripThinkTags(data.choices?.[0]?.message?.content ?? data.message ?? "");
2197
2248
  if (!content) {
2198
2249
  throw new Error("Empty response from model");
2199
2250
  }
2200
- return jsonMode ? JSON.parse(content) : content;
2251
+ return jsonMode ? parseModelJson(content) : content;
2201
2252
  }
2202
2253
  async chatWithTools(model, options) {
2203
2254
  const log5 = logger.child("llm:snowflake-cortex");
@@ -2215,7 +2266,7 @@ class SnowflakeCortexExecutor extends LLMExecutor {
2215
2266
  throw new Error(`snowflake-cortex API error: ${data.error.message ?? "unknown"}`);
2216
2267
  }
2217
2268
  const choice = data.choices?.[0];
2218
- const content = choice?.message?.content ?? data.message ?? "";
2269
+ const content = stripThinkTags(choice?.message?.content ?? data.message ?? "");
2219
2270
  const toolCalls = choice?.message?.tool_calls?.map((c) => ({
2220
2271
  id: c.id,
2221
2272
  function: {
@@ -5282,7 +5333,7 @@ function BrainApp({
5282
5333
  }, undefined, false, undefined, this),
5283
5334
  busy ? /* @__PURE__ */ jsxDEV5(Text5, {
5284
5335
  dimColor: true,
5285
- children: "Creating brain…"
5336
+ children: "Creating brain… (This can take few minutes depending on the model's response speed)"
5286
5337
  }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV5(TextInput, {
5287
5338
  prompt: "seed> ",
5288
5339
  onSubmit: (raw) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@p-sw/brainbox",
3
- "version": "0.1.2-alpha.4",
3
+ "version": "0.1.2-alpha.5",
4
4
  "module": "dist/index.js",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",