@p-sw/brainbox 0.1.2-alpha.3 → 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 +113 -48
  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,15 +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.chat/v1",
1107
- apiKey: opts.apiKey,
1108
- conversationModel: opts.conversationModel,
1109
- identityModel: opts.identityModel
1110
- });
1140
+ super(minimaxOpts("minimax", "https://api.minimax.io/v1", opts));
1141
+ }
1142
+ }
1143
+
1144
+ class MiniMaxCnExecutor extends MiniMaxBase {
1145
+ constructor(opts) {
1146
+ super(minimaxOpts("minimax-cn", "https://api.minimaxi.com/v1", opts));
1111
1147
  }
1112
1148
  }
1113
1149
 
@@ -1260,7 +1296,17 @@ class CloudflareGatewayExecutor extends OpenAICompatibleExecutor {
1260
1296
  // src/provider/providers/cloudflare_workers.ts
1261
1297
  function toCloudflareMessage(m) {
1262
1298
  if (m.role === "assistant") {
1263
- 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
+ };
1264
1310
  }
1265
1311
  if (m.role === "tool") {
1266
1312
  return { role: "tool", content: m.content, tool_call_id: m.toolCallId };
@@ -1322,11 +1368,11 @@ class CloudflareWorkersExecutor extends LLMExecutor {
1322
1368
  if (data.errors && data.errors.length > 0) {
1323
1369
  throw new Error(`cloudflare-workers API error: ${data.errors.map((e) => e.message).join("; ")}`);
1324
1370
  }
1325
- const content = data.result?.response ?? "";
1371
+ const content = stripThinkTags(data.result?.response ?? "");
1326
1372
  if (!content) {
1327
1373
  throw new Error("Empty response from model");
1328
1374
  }
1329
- return jsonMode ? JSON.parse(content) : content;
1375
+ return jsonMode ? parseModelJson(content) : content;
1330
1376
  }
1331
1377
  async chatWithTools(model, options) {
1332
1378
  const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
@@ -1351,7 +1397,7 @@ class CloudflareWorkersExecutor extends LLMExecutor {
1351
1397
  if (data.errors && data.errors.length > 0) {
1352
1398
  throw new Error(`cloudflare-workers API error: ${data.errors.map((e) => e.message).join("; ")}`);
1353
1399
  }
1354
- const content = data.result?.response ?? "";
1400
+ const content = stripThinkTags(data.result?.response ?? "");
1355
1401
  const toolCalls = data.result?.tool_calls?.map((c, idx) => ({
1356
1402
  id: `call_${idx}`,
1357
1403
  function: {
@@ -1389,7 +1435,8 @@ class AzureOpenAIExecutor extends OpenAICompatibleExecutor {
1389
1435
  super({
1390
1436
  providerName: "azure-openai",
1391
1437
  baseURL: `https://${resource || "__resource__"}.openai.azure.com/openai/deployments`,
1392
- apiKey: opts.apiKey,
1438
+ apiKey: "",
1439
+ defaultHeaders: { "api-key": opts.apiKey },
1393
1440
  conversationModel: opts.conversationModel,
1394
1441
  identityModel: opts.identityModel
1395
1442
  });
@@ -1409,7 +1456,8 @@ class AzureCognitiveExecutor extends OpenAICompatibleExecutor {
1409
1456
  super({
1410
1457
  providerName: "azure-cognitive",
1411
1458
  baseURL: `https://${resource || "__resource__"}.cognitiveservices.azure.com/openai/deployments`,
1412
- apiKey: opts.apiKey,
1459
+ apiKey: "",
1460
+ defaultHeaders: { "api-key": opts.apiKey },
1413
1461
  conversationModel: opts.conversationModel,
1414
1462
  identityModel: opts.identityModel
1415
1463
  });
@@ -1533,44 +1581,50 @@ class AnthropicExecutor extends LLMExecutor {
1533
1581
  const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
1534
1582
  const log5 = logger.child("llm:anthropic");
1535
1583
  log5.debug(`call: model=${model} jsonSchema=${jsonMode ? options.jsonSchemaName : "-"} msgLen=${options.message.length}`);
1584
+ const outputCap = 4096;
1536
1585
  const body = {
1537
1586
  model,
1538
- max_tokens: 4096,
1587
+ max_tokens: outputCap,
1539
1588
  system: options.instruction,
1540
1589
  messages: [{ role: "user", content: options.message }]
1541
1590
  };
1542
1591
  if (reasoning !== "none") {
1592
+ const budget = REASONING_BUDGET[reasoning];
1593
+ body["max_tokens"] = budget + outputCap;
1543
1594
  body["thinking"] = {
1544
1595
  type: "enabled",
1545
- budget_tokens: REASONING_BUDGET[reasoning]
1596
+ budget_tokens: budget
1546
1597
  };
1547
1598
  }
1548
1599
  const data = await this.send(body);
1549
1600
  if (data.error) {
1550
1601
  throw new Error(`anthropic API error: ${data.error.message ?? "unknown"}`);
1551
1602
  }
1552
- 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(""));
1553
1604
  if (!text) {
1554
1605
  throw new Error("Empty response from model");
1555
1606
  }
1556
- return jsonMode ? JSON.parse(text) : text;
1607
+ return jsonMode ? parseModelJson(text) : text;
1557
1608
  }
1558
1609
  async chatWithTools(model, options) {
1559
1610
  const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
1560
1611
  const log5 = logger.child("llm:anthropic");
1561
1612
  log5.debug(`chatWithTools: model=${model} msgs=${options.messages.length} tools=${options.tools.length}`);
1562
1613
  const { system, msgs } = toAnthropicMessages(options.messages);
1614
+ const outputCap = 4096;
1563
1615
  const body = {
1564
1616
  model,
1565
- max_tokens: 4096,
1617
+ max_tokens: outputCap,
1566
1618
  system: system ?? options.instruction,
1567
1619
  messages: msgs,
1568
1620
  tools: options.tools.map(toAnthropicTool)
1569
1621
  };
1570
1622
  if (reasoning !== "none") {
1623
+ const budget = REASONING_BUDGET[reasoning];
1624
+ body["max_tokens"] = budget + outputCap;
1571
1625
  body["thinking"] = {
1572
1626
  type: "enabled",
1573
- budget_tokens: REASONING_BUDGET[reasoning]
1627
+ budget_tokens: budget
1574
1628
  };
1575
1629
  }
1576
1630
  const data = await this.send(body);
@@ -1578,7 +1632,7 @@ class AnthropicExecutor extends LLMExecutor {
1578
1632
  throw new Error(`anthropic API error: ${data.error.message ?? "unknown"}`);
1579
1633
  }
1580
1634
  const blocks = data.content ?? [];
1581
- 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(""));
1582
1636
  const toolCalls = blocks.filter((b) => b.type === "tool_use").map((b) => ({
1583
1637
  id: b.id,
1584
1638
  function: {
@@ -1704,7 +1758,7 @@ function toAnthropicTool2(t) {
1704
1758
  }
1705
1759
  function extractAnthropicContent(data) {
1706
1760
  const content = data["content"] ?? [];
1707
- 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(""));
1708
1762
  const toolCalls = content.filter((b) => b["type"] === "tool_use").map((b) => ({
1709
1763
  id: b["id"],
1710
1764
  function: {
@@ -1779,7 +1833,7 @@ class BedrockExecutor extends LLMExecutor {
1779
1833
  if (!text) {
1780
1834
  throw new Error("Empty response from model");
1781
1835
  }
1782
- return jsonMode ? JSON.parse(text) : text;
1836
+ return jsonMode ? parseModelJson(text) : text;
1783
1837
  }
1784
1838
  async chatWithTools(model, options) {
1785
1839
  const log5 = logger.child("llm:bedrock");
@@ -1809,6 +1863,7 @@ var VertexAuthSchema = z5.object({
1809
1863
  }).loose();
1810
1864
  function toGeminiContents(messages) {
1811
1865
  const out = [];
1866
+ const nameByCallId = new Map;
1812
1867
  for (const m of messages) {
1813
1868
  if (m.role === "system")
1814
1869
  continue;
@@ -1822,6 +1877,7 @@ function toGeminiContents(messages) {
1822
1877
  parts.push({ text: m.content });
1823
1878
  if (m.toolCalls) {
1824
1879
  for (const c of m.toolCalls) {
1880
+ nameByCallId.set(c.id, c.function.name);
1825
1881
  let args = {};
1826
1882
  try {
1827
1883
  args = c.function.arguments ? JSON.parse(c.function.arguments) : {};
@@ -1843,21 +1899,26 @@ function toGeminiContents(messages) {
1843
1899
  }
1844
1900
  out.push({
1845
1901
  role: "user",
1846
- parts: [{ functionResponse: { name: "", response } }]
1902
+ parts: [
1903
+ {
1904
+ functionResponse: {
1905
+ name: nameByCallId.get(m.toolCallId) ?? "",
1906
+ response
1907
+ }
1908
+ }
1909
+ ]
1847
1910
  });
1848
1911
  }
1849
1912
  }
1850
1913
  return out;
1851
1914
  }
1852
- function toGeminiTool(t) {
1915
+ function toGeminiTools(tools) {
1853
1916
  return {
1854
- functionDeclarations: [
1855
- {
1856
- name: t.name,
1857
- description: t.description,
1858
- parameters: t.parameters ?? { type: "object", properties: {} }
1859
- }
1860
- ]
1917
+ functionDeclarations: tools.map((t) => ({
1918
+ name: t.name,
1919
+ description: t.description,
1920
+ parameters: t.parameters ?? { type: "object", properties: {} }
1921
+ }))
1861
1922
  };
1862
1923
  }
1863
1924
  function extractFromGemini(data) {
@@ -1878,7 +1939,10 @@ function extractFromGemini(data) {
1878
1939
  });
1879
1940
  }
1880
1941
  }
1881
- return { text, toolCalls: toolCalls.length > 0 ? toolCalls : undefined };
1942
+ return {
1943
+ text: stripThinkTags(text),
1944
+ toolCalls: toolCalls.length > 0 ? toolCalls : undefined
1945
+ };
1882
1946
  }
1883
1947
 
1884
1948
  class VertexExecutor extends LLMExecutor {
@@ -1944,7 +2008,7 @@ class VertexExecutor extends LLMExecutor {
1944
2008
  if (!text) {
1945
2009
  throw new Error("Empty response from model");
1946
2010
  }
1947
- return jsonMode ? JSON.parse(text) : text;
2011
+ return jsonMode ? parseModelJson(text) : text;
1948
2012
  }
1949
2013
  async chatWithTools(model, options) {
1950
2014
  const log5 = logger.child("llm:vertex");
@@ -1953,7 +2017,7 @@ class VertexExecutor extends LLMExecutor {
1953
2017
  const body = {
1954
2018
  contents,
1955
2019
  systemInstruction: { parts: [{ text: options.instruction }] },
1956
- tools: options.tools.length > 0 ? [toGeminiTool(options.tools[0])] : undefined
2020
+ tools: options.tools.length > 0 ? [toGeminiTools(options.tools)] : undefined
1957
2021
  };
1958
2022
  const data = await this.generate(model, body);
1959
2023
  if (data.error) {
@@ -2061,11 +2125,11 @@ class GitLabDuoExecutor extends LLMExecutor {
2061
2125
  if (data.error) {
2062
2126
  throw new Error(`gitlab-duo API error: ${data.error.message ?? "unknown"}`);
2063
2127
  }
2064
- const content = data.choices?.[0]?.message?.content ?? "";
2128
+ const content = stripThinkTags(data.choices?.[0]?.message?.content ?? "");
2065
2129
  if (!content) {
2066
2130
  throw new Error("Empty response from model");
2067
2131
  }
2068
- return jsonMode ? JSON.parse(content) : content;
2132
+ return jsonMode ? parseModelJson(content) : content;
2069
2133
  }
2070
2134
  async chatWithTools(model, options) {
2071
2135
  const log5 = logger.child("llm:gitlab-duo");
@@ -2089,7 +2153,7 @@ class GitLabDuoExecutor extends LLMExecutor {
2089
2153
  }));
2090
2154
  return {
2091
2155
  message: {
2092
- content: choice?.message?.content ?? undefined,
2156
+ content: choice?.message?.content ? stripThinkTags(choice.message.content) : undefined,
2093
2157
  toolCalls
2094
2158
  }
2095
2159
  };
@@ -2180,11 +2244,11 @@ class SnowflakeCortexExecutor extends LLMExecutor {
2180
2244
  if (data.error) {
2181
2245
  throw new Error(`snowflake-cortex API error: ${data.error.message ?? "unknown"}`);
2182
2246
  }
2183
- const content = data.choices?.[0]?.message?.content ?? data.message ?? "";
2247
+ const content = stripThinkTags(data.choices?.[0]?.message?.content ?? data.message ?? "");
2184
2248
  if (!content) {
2185
2249
  throw new Error("Empty response from model");
2186
2250
  }
2187
- return jsonMode ? JSON.parse(content) : content;
2251
+ return jsonMode ? parseModelJson(content) : content;
2188
2252
  }
2189
2253
  async chatWithTools(model, options) {
2190
2254
  const log5 = logger.child("llm:snowflake-cortex");
@@ -2202,7 +2266,7 @@ class SnowflakeCortexExecutor extends LLMExecutor {
2202
2266
  throw new Error(`snowflake-cortex API error: ${data.error.message ?? "unknown"}`);
2203
2267
  }
2204
2268
  const choice = data.choices?.[0];
2205
- const content = choice?.message?.content ?? data.message ?? "";
2269
+ const content = stripThinkTags(choice?.message?.content ?? data.message ?? "");
2206
2270
  const toolCalls = choice?.message?.tool_calls?.map((c) => ({
2207
2271
  id: c.id,
2208
2272
  function: {
@@ -2242,6 +2306,7 @@ register("gmi", GmiExecutor);
2242
2306
  register("zai", ZaiExecutor);
2243
2307
  register("zenmux", ZenMuxExecutor);
2244
2308
  register("minimax", MiniMaxExecutor);
2309
+ register("minimax-cn", MiniMaxCnExecutor);
2245
2310
  register("ionet", IoNetExecutor);
2246
2311
  register("baseten", BasetenExecutor);
2247
2312
  register("cortecs", CortecsExecutor);
@@ -5268,7 +5333,7 @@ function BrainApp({
5268
5333
  }, undefined, false, undefined, this),
5269
5334
  busy ? /* @__PURE__ */ jsxDEV5(Text5, {
5270
5335
  dimColor: true,
5271
- children: "Creating brain…"
5336
+ children: "Creating brain… (This can take few minutes depending on the model's response speed)"
5272
5337
  }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV5(TextInput, {
5273
5338
  prompt: "seed> ",
5274
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.3",
3
+ "version": "0.1.2-alpha.5",
4
4
  "module": "dist/index.js",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",