hoomanjs 1.36.0 → 1.37.0

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 (63) hide show
  1. package/README.md +17 -13
  2. package/dist/chat/app.js +117 -1
  3. package/dist/chat/app.js.map +1 -1
  4. package/dist/chat/components/BottomChrome.d.ts +2 -1
  5. package/dist/chat/components/BottomChrome.js +2 -2
  6. package/dist/chat/components/BottomChrome.js.map +1 -1
  7. package/dist/chat/components/ReasoningStrip.js +3 -1
  8. package/dist/chat/components/ReasoningStrip.js.map +1 -1
  9. package/dist/chat/components/StatusBar.d.ts +2 -1
  10. package/dist/chat/components/StatusBar.js +18 -3
  11. package/dist/chat/components/StatusBar.js.map +1 -1
  12. package/dist/chat/components/ThoughtEvent.d.ts +3 -1
  13. package/dist/chat/components/ThoughtEvent.js +6 -2
  14. package/dist/chat/components/ThoughtEvent.js.map +1 -1
  15. package/dist/chat/components/Transcript.d.ts +5 -2
  16. package/dist/chat/components/Transcript.js +4 -4
  17. package/dist/chat/components/Transcript.js.map +1 -1
  18. package/dist/cli.js +23 -0
  19. package/dist/cli.js.map +1 -1
  20. package/dist/configure/app.js +287 -68
  21. package/dist/configure/app.js.map +1 -1
  22. package/dist/configure/components/MenuScreen.js +29 -5
  23. package/dist/configure/components/MenuScreen.js.map +1 -1
  24. package/dist/configure/types.d.ts +8 -2
  25. package/dist/core/config.d.ts +352 -22
  26. package/dist/core/config.js +40 -0
  27. package/dist/core/config.js.map +1 -1
  28. package/dist/core/models/anthropic.d.ts +7 -1
  29. package/dist/core/models/anthropic.js +36 -7
  30. package/dist/core/models/anthropic.js.map +1 -1
  31. package/dist/core/models/azure.js +13 -0
  32. package/dist/core/models/azure.js.map +1 -1
  33. package/dist/core/models/bedrock.js +33 -0
  34. package/dist/core/models/bedrock.js.map +1 -1
  35. package/dist/core/models/google.js +5 -0
  36. package/dist/core/models/google.js.map +1 -1
  37. package/dist/core/models/groq.js +14 -0
  38. package/dist/core/models/groq.js.map +1 -1
  39. package/dist/core/models/minimax.js +2 -2
  40. package/dist/core/models/minimax.js.map +1 -1
  41. package/dist/core/models/moonshot.js +5 -0
  42. package/dist/core/models/moonshot.js.map +1 -1
  43. package/dist/core/models/ollama/index.js +5 -3
  44. package/dist/core/models/ollama/index.js.map +1 -1
  45. package/dist/core/models/openai.js +21 -1
  46. package/dist/core/models/openai.js.map +1 -1
  47. package/dist/core/models/openrouter.d.ts +2 -2
  48. package/dist/core/models/openrouter.js +28 -10
  49. package/dist/core/models/openrouter.js.map +1 -1
  50. package/dist/core/models/types.d.ts +759 -51
  51. package/dist/core/models/types.js +43 -7
  52. package/dist/core/models/types.js.map +1 -1
  53. package/dist/core/models/xai.js +12 -0
  54. package/dist/core/models/xai.js.map +1 -1
  55. package/dist/core/session-config.d.ts +10 -1
  56. package/dist/core/session-config.js +16 -0
  57. package/dist/core/session-config.js.map +1 -1
  58. package/dist/core/skills/built-in/hooman-config/SKILL.md +20 -12
  59. package/dist/core/state/todos.d.ts +4 -4
  60. package/dist/core/tools/todo.d.ts +1 -1
  61. package/dist/core/tools/web-search.js +56 -1
  62. package/dist/core/tools/web-search.js.map +1 -1
  63. package/package.json +2 -1
@@ -21,6 +21,7 @@ const SEARCH_PROVIDER_LABELS = {
21
21
  brave: "Brave",
22
22
  exa: "Exa",
23
23
  firecrawl: "Firecrawl",
24
+ litellm: "LiteLLM",
24
25
  serper: "Serper",
25
26
  tavily: "Tavily",
26
27
  };
@@ -207,11 +208,18 @@ const PROVIDER_FIELD_DEFINITIONS = {
207
208
  placeholder: '{"x-my-header":"value"}',
208
209
  },
209
210
  {
210
- key: "thinking",
211
- label: "Thinking",
212
- kind: "anthropicThinking",
213
- placeholder: "adaptive",
214
- note: 'Allowed: "disabled", "adaptive", or blank to clear.',
211
+ key: "reasoningEffort",
212
+ label: "Reasoning effort",
213
+ kind: "reasoningEffort",
214
+ placeholder: "medium",
215
+ note: 'Enables extended thinking (thinking: { type: "enabled", budget_tokens }). Allowed: "minimal", "low", "medium", "high", or blank to disable. Effort maps to a budget (1024/2048/4096/8192); defaults to "medium".',
216
+ },
217
+ {
218
+ key: "reasoningDisplay",
219
+ label: "Reasoning display",
220
+ kind: "reasoningDisplay",
221
+ placeholder: "summarized",
222
+ note: 'For Bedrock Claude via an Anthropic-compatible proxy (Opus 4.7+ hide reasoning by default): "summarized" reveals it (switches to adaptive thinking + output_config.effort); "omitted" to hide; blank keeps the enabled+budget scheme. Do NOT set for the native Anthropic API — it rejects adaptive/display.',
215
223
  },
216
224
  ],
217
225
  [LlmProvider.Azure]: [
@@ -256,6 +264,20 @@ const PROVIDER_FIELD_DEFINITIONS = {
256
264
  placeholder: "false",
257
265
  note: "Allowed: yes/no/true/false. Leave blank to use the AI SDK default.",
258
266
  },
267
+ {
268
+ key: "reasoningEffort",
269
+ label: "Reasoning effort",
270
+ kind: "reasoningEffort",
271
+ placeholder: "medium",
272
+ note: 'Responses API. Allowed: "minimal", "low", "medium", "high", or blank. Only reasoning-capable deployments honor it.',
273
+ },
274
+ {
275
+ key: "reasoningSummary",
276
+ label: "Reasoning summary",
277
+ kind: "reasoningSummary",
278
+ placeholder: "auto",
279
+ note: 'Responses API. Allowed: "auto" (default), "concise", "detailed", or "none" to disable.',
280
+ },
259
281
  ],
260
282
  [LlmProvider.Bedrock]: [
261
283
  {
@@ -284,6 +306,20 @@ const PROVIDER_FIELD_DEFINITIONS = {
284
306
  placeholder: "...",
285
307
  sensitive: true,
286
308
  },
309
+ {
310
+ key: "reasoningEffort",
311
+ label: "Reasoning effort",
312
+ kind: "reasoningEffort",
313
+ placeholder: "medium",
314
+ note: 'Enables extended thinking on supported models (e.g. Claude). Allowed: "minimal", "low", "medium", "high", or blank. Effort maps to a thinking budget (1024/2048/4096/8192 tokens); defaults to "medium".',
315
+ },
316
+ {
317
+ key: "reasoningDisplay",
318
+ label: "Reasoning display",
319
+ kind: "reasoningDisplay",
320
+ placeholder: "summarized",
321
+ note: 'Newer Bedrock Claude (Opus 4.7+) hide reasoning by default. Set "summarized" to reveal it (switches to adaptive thinking + output_config.effort); "omitted" to hide; blank keeps the enabled+budget scheme.',
322
+ },
287
323
  ],
288
324
  [LlmProvider.Google]: [
289
325
  {
@@ -293,6 +329,13 @@ const PROVIDER_FIELD_DEFINITIONS = {
293
329
  placeholder: "...",
294
330
  sensitive: true,
295
331
  },
332
+ {
333
+ key: "reasoningEffort",
334
+ label: "Reasoning effort",
335
+ kind: "reasoningEffort",
336
+ placeholder: "medium",
337
+ note: 'Enables Gemini thinking with a dynamic budget. Allowed: "minimal", "low", "medium", "high", or blank to leave at the model default.',
338
+ },
296
339
  ],
297
340
  [LlmProvider.Groq]: [
298
341
  {
@@ -314,6 +357,13 @@ const PROVIDER_FIELD_DEFINITIONS = {
314
357
  kind: "stringRecord",
315
358
  placeholder: '{"x-my-header":"value"}',
316
359
  },
360
+ {
361
+ key: "reasoningEffort",
362
+ label: "Reasoning effort",
363
+ kind: "reasoningEffort",
364
+ placeholder: "medium",
365
+ note: 'Maps to Groq reasoning_effort ("minimal" -> "low"). Allowed: "minimal", "low", "medium", "high", or blank. Only reasoning models honor it.',
366
+ },
317
367
  ],
318
368
  [LlmProvider.Minimax]: [
319
369
  {
@@ -330,11 +380,18 @@ const PROVIDER_FIELD_DEFINITIONS = {
330
380
  placeholder: '{"x-my-header":"value"}',
331
381
  },
332
382
  {
333
- key: "thinking",
334
- label: "Thinking",
335
- kind: "anthropicThinking",
336
- placeholder: "adaptive",
337
- note: 'Allowed: "disabled", "adaptive", or blank to clear.',
383
+ key: "reasoningEffort",
384
+ label: "Reasoning effort",
385
+ kind: "reasoningEffort",
386
+ placeholder: "medium",
387
+ note: 'Enables thinking (normalized to MiniMax adaptive, with budget_tokens). Allowed: "minimal", "low", "medium", "high", or blank to disable. Effort maps to a budget (1024/2048/4096/8192); defaults to "medium".',
388
+ },
389
+ {
390
+ key: "reasoningDisplay",
391
+ label: "Reasoning display",
392
+ kind: "reasoningDisplay",
393
+ placeholder: "summarized",
394
+ note: 'Set "summarized" to switch to adaptive thinking + output_config.effort; "omitted" to hide; blank keeps the adaptive+budget scheme.',
338
395
  },
339
396
  ],
340
397
  [LlmProvider.Moonshot]: [
@@ -358,6 +415,13 @@ const PROVIDER_FIELD_DEFINITIONS = {
358
415
  kind: "stringRecord",
359
416
  placeholder: '{"x-my-header":"value"}',
360
417
  },
418
+ {
419
+ key: "reasoningEffort",
420
+ label: "Reasoning effort",
421
+ kind: "reasoningEffort",
422
+ placeholder: "medium",
423
+ note: 'Enables Kimi thinking (thinking: { type: "enabled" }). Allowed: "minimal", "low", "medium", "high", or blank to disable.',
424
+ },
361
425
  ],
362
426
  [LlmProvider.Ollama]: [
363
427
  {
@@ -367,11 +431,11 @@ const PROVIDER_FIELD_DEFINITIONS = {
367
431
  placeholder: "http://127.0.0.1:11434",
368
432
  },
369
433
  {
370
- key: "thinking",
371
- label: "Thinking",
372
- kind: "ollamaThinking",
434
+ key: "reasoningEffort",
435
+ label: "Reasoning effort",
436
+ kind: "reasoningEffort",
373
437
  placeholder: "medium",
374
- note: 'Allowed: yes/no/true/false or "high", "medium", "low". Leave blank to clear.',
438
+ note: 'Enables Ollama thinking, mapped to the think level ("minimal"/"low" -> "low"). Allowed: "minimal", "low", "medium", "high", or blank to disable.',
375
439
  },
376
440
  ],
377
441
  [LlmProvider.OpenAI]: [
@@ -394,6 +458,27 @@ const PROVIDER_FIELD_DEFINITIONS = {
394
458
  kind: "stringRecord",
395
459
  placeholder: '{"x-my-header":"value"}',
396
460
  },
461
+ {
462
+ key: "api",
463
+ label: "API",
464
+ kind: "openaiApi",
465
+ placeholder: "responses",
466
+ note: 'Allowed: "responses" (default, streams reasoning) or "chat" (for OpenAI-compatible endpoints without the Responses API).',
467
+ },
468
+ {
469
+ key: "reasoningEffort",
470
+ label: "Reasoning effort",
471
+ kind: "reasoningEffort",
472
+ placeholder: "medium",
473
+ note: 'Responses API only. Allowed: "minimal", "low", "medium", "high", or blank. Some models (e.g. GPT-5) only emit a reasoning summary at "medium"+.',
474
+ },
475
+ {
476
+ key: "reasoningSummary",
477
+ label: "Reasoning summary",
478
+ kind: "reasoningSummary",
479
+ placeholder: "auto",
480
+ note: 'Responses API only. Allowed: "auto" (default), "concise", "detailed", or "none" to disable.',
481
+ },
397
482
  ],
398
483
  [LlmProvider.OpenRouter]: [
399
484
  {
@@ -417,6 +502,13 @@ const PROVIDER_FIELD_DEFINITIONS = {
417
502
  placeholder: '{"HTTP-Referer":"https://example.com","X-Title":"Hooman"}',
418
503
  note: "Optional extra headers such as attribution metadata for OpenRouter.",
419
504
  },
505
+ {
506
+ key: "reasoningEffort",
507
+ label: "Reasoning effort",
508
+ kind: "reasoningEffort",
509
+ placeholder: "medium",
510
+ note: 'Maps to OpenRouter\'s unified reasoning: { effort }. Allowed: "minimal", "low", "medium", "high", or blank. Only reasoning models honor it.',
511
+ },
420
512
  ],
421
513
  [LlmProvider.Xai]: [
422
514
  {
@@ -438,6 +530,13 @@ const PROVIDER_FIELD_DEFINITIONS = {
438
530
  kind: "stringRecord",
439
531
  placeholder: '{"x-my-header":"value"}',
440
532
  },
533
+ {
534
+ key: "reasoningEffort",
535
+ label: "Reasoning effort",
536
+ kind: "reasoningEffort",
537
+ placeholder: "high",
538
+ note: 'Maps to xAI reasoning_effort (low/high; "minimal"/"low" -> "low", "medium"/"high" -> "high"). Allowed: "minimal", "low", "medium", "high", or blank. Only reasoning models (e.g. grok-3-mini) honor it.',
539
+ },
441
540
  ],
442
541
  };
443
542
  const LLM_FIELD_DEFINITIONS = [
@@ -492,25 +591,51 @@ function parseTypedFieldValue(input, definition) {
492
591
  });
493
592
  case "bedrockCredentials":
494
593
  return undefined;
495
- case "anthropicThinking": {
594
+ case "openaiApi": {
496
595
  const value = normalizeOptional(input);
497
596
  if (value === undefined) {
498
597
  return undefined;
499
598
  }
500
- if (value === "disabled" || value === "adaptive") {
599
+ if (value === "chat" || value === "responses") {
501
600
  return value;
502
601
  }
503
- throw new Error(`${definition.label} must be "disabled" or "adaptive".`);
602
+ throw new Error(`${definition.label} must be "chat" or "responses".`);
504
603
  }
505
- case "ollamaThinking": {
604
+ case "reasoningEffort": {
506
605
  const value = normalizeOptional(input);
507
606
  if (value === undefined) {
508
607
  return undefined;
509
608
  }
510
- if (value === "high" || value === "medium" || value === "low") {
609
+ if (value === "minimal" ||
610
+ value === "low" ||
611
+ value === "medium" ||
612
+ value === "high") {
511
613
  return value;
512
614
  }
513
- return parseOptionalBoolean(value, definition.label);
615
+ throw new Error(`${definition.label} must be "minimal", "low", "medium", or "high".`);
616
+ }
617
+ case "reasoningSummary": {
618
+ const value = normalizeOptional(input);
619
+ if (value === undefined) {
620
+ return undefined;
621
+ }
622
+ if (value === "auto" ||
623
+ value === "concise" ||
624
+ value === "detailed" ||
625
+ value === "none") {
626
+ return value;
627
+ }
628
+ throw new Error(`${definition.label} must be "auto", "concise", "detailed", or "none".`);
629
+ }
630
+ case "reasoningDisplay": {
631
+ const value = normalizeOptional(input);
632
+ if (value === undefined) {
633
+ return undefined;
634
+ }
635
+ if (value === "summarized" || value === "omitted") {
636
+ return value;
637
+ }
638
+ throw new Error(`${definition.label} must be "summarized" or "omitted".`);
514
639
  }
515
640
  }
516
641
  }
@@ -583,6 +708,7 @@ export function ConfigureApp({ config, mcpConfig, mcpManager, skills, onExit, })
583
708
  prompts: config.prompts,
584
709
  tools: config.tools,
585
710
  compaction: config.compaction,
711
+ reasoning: config.reasoning,
586
712
  }), [config, revision]);
587
713
  const mcpServers = useMemo(() => mcpConfig.list(), [mcpConfig, revision]);
588
714
  useInput((input, key) => {
@@ -621,8 +747,10 @@ export function ConfigureApp({ config, mcpConfig, mcpManager, skills, onExit, })
621
747
  setScreen({ kind: "config-provider-edit", name: screen.name });
622
748
  return;
623
749
  }
624
- if (screen.kind === "config-provider-anthropic-thinking" ||
625
- screen.kind === "config-provider-ollama-thinking") {
750
+ if (screen.kind === "config-provider-openai-api" ||
751
+ screen.kind === "config-provider-reasoning-effort" ||
752
+ screen.kind === "config-provider-reasoning-summary" ||
753
+ screen.kind === "config-provider-reasoning-display") {
626
754
  setScreen({ kind: "config-provider-edit", name: screen.name });
627
755
  return;
628
756
  }
@@ -1047,6 +1175,14 @@ export function ConfigureApp({ config, mcpConfig, mcpManager, skills, onExit, })
1047
1175
  },
1048
1176
  }),
1049
1177
  },
1178
+ {
1179
+ key: "reasoning-display",
1180
+ label: `Reasoning display • ${configData.reasoning}`,
1181
+ value: () => {
1182
+ const next = configData.reasoning === "collapsed" ? "full" : "collapsed";
1183
+ updateConfig({ reasoning: next }, `Set reasoning display to "${next}".`);
1184
+ },
1185
+ },
1050
1186
  {
1051
1187
  label: "Back",
1052
1188
  value: () => setScreen({ kind: "home" }),
@@ -1257,18 +1393,38 @@ export function ConfigureApp({ config, mcpConfig, mcpManager, skills, onExit, })
1257
1393
  key: `provider-field:${entry.name}:${definition.key}`,
1258
1394
  label: `${definition.label} • ${formatTypedFieldValue(definition, definition.kind === "bedrockCredentials"
1259
1395
  ? providerOptions.accessKeyId && providerOptions.secretAccessKey
1260
- : providerOptions[definition.key])}`,
1396
+ : definition.kind === "reasoningEffort"
1397
+ ? providerOptions.reasoning?.effort
1398
+ : definition.kind === "reasoningSummary"
1399
+ ? providerOptions.reasoning?.summary
1400
+ : definition.kind === "reasoningDisplay"
1401
+ ? providerOptions.reasoning?.display
1402
+ : providerOptions[definition.key])}`,
1261
1403
  value: () => {
1262
- if (definition.kind === "anthropicThinking") {
1404
+ if (definition.kind === "openaiApi") {
1405
+ setScreen({
1406
+ kind: "config-provider-openai-api",
1407
+ name: entry.name,
1408
+ });
1409
+ return;
1410
+ }
1411
+ if (definition.kind === "reasoningEffort") {
1412
+ setScreen({
1413
+ kind: "config-provider-reasoning-effort",
1414
+ name: entry.name,
1415
+ });
1416
+ return;
1417
+ }
1418
+ if (definition.kind === "reasoningSummary") {
1263
1419
  setScreen({
1264
- kind: "config-provider-anthropic-thinking",
1420
+ kind: "config-provider-reasoning-summary",
1265
1421
  name: entry.name,
1266
1422
  });
1267
1423
  return;
1268
1424
  }
1269
- if (definition.kind === "ollamaThinking") {
1425
+ if (definition.kind === "reasoningDisplay") {
1270
1426
  setScreen({
1271
- kind: "config-provider-ollama-thinking",
1427
+ kind: "config-provider-reasoning-display",
1272
1428
  name: entry.name,
1273
1429
  });
1274
1430
  return;
@@ -1357,73 +1513,75 @@ export function ConfigureApp({ config, mcpConfig, mcpManager, skills, onExit, })
1357
1513
  ? `Used by ${usageCount} model(s). Rename updates references automatically; delete is disabled while in use.`
1358
1514
  : "Edit shared provider settings or delete this provider.", items: items }));
1359
1515
  };
1360
- const renderAnthropicThinkingMenu = () => {
1361
- if (screen.kind !== "config-provider-anthropic-thinking") {
1516
+ const renderOpenAIApiMenu = () => {
1517
+ if (screen.kind !== "config-provider-openai-api") {
1362
1518
  return null;
1363
1519
  }
1364
1520
  const entry = config.providers.find((provider) => provider.name === screen.name);
1365
1521
  if (!entry) {
1366
1522
  return null;
1367
1523
  }
1368
- const current = "thinking" in entry.options ? entry.options.thinking : undefined;
1524
+ const current = "api" in entry.options ? entry.options.api : undefined;
1369
1525
  const items = [
1370
1526
  {
1371
- label: current === undefined ? "Not set • current" : "Not set (clear value)",
1372
- value: () => {
1373
- if (updateConfig({ providers: patchProvider(entry.name, { thinking: undefined }) }, `Cleared thinking for "${entry.name}".`)) {
1374
- setScreen({ kind: "config-provider-edit", name: entry.name });
1375
- }
1376
- },
1377
- },
1378
- {
1379
- label: current === "disabled" ? "disabled • current" : "disabled",
1527
+ label: current === undefined
1528
+ ? "Not set (responses) current"
1529
+ : "Not set (clear value)",
1380
1530
  value: () => {
1381
- if (updateConfig({
1382
- providers: patchProvider(entry.name, { thinking: "disabled" }),
1383
- }, `Updated thinking for "${entry.name}" to "disabled".`)) {
1531
+ if (updateConfig({ providers: patchProvider(entry.name, { api: undefined }) }, `Cleared API for "${entry.name}".`)) {
1384
1532
  setScreen({ kind: "config-provider-edit", name: entry.name });
1385
1533
  }
1386
1534
  },
1387
1535
  },
1388
- {
1389
- label: current === "adaptive" ? "adaptive • current" : "adaptive",
1536
+ ...["responses", "chat"].map((value) => ({
1537
+ label: current === value ? `${value} • current` : value,
1390
1538
  value: () => {
1391
- if (updateConfig({
1392
- providers: patchProvider(entry.name, { thinking: "adaptive" }),
1393
- }, `Updated thinking for "${entry.name}" to "adaptive".`)) {
1539
+ if (updateConfig({ providers: patchProvider(entry.name, { api: value }) }, `Updated API for "${entry.name}" to "${value}".`)) {
1394
1540
  setScreen({ kind: "config-provider-edit", name: entry.name });
1395
1541
  }
1396
1542
  },
1397
- },
1543
+ })),
1398
1544
  {
1399
1545
  label: "Back",
1400
1546
  value: () => setScreen({ kind: "config-provider-edit", name: entry.name }),
1401
1547
  },
1402
1548
  ];
1403
- return (_jsx(MenuScreen, { title: `Choose Thinking • ${entry.name}`, description: 'Pick one of the allowed values: "disabled", "adaptive", or clear it.', items: items }));
1549
+ return (_jsx(MenuScreen, { title: `Choose API • ${entry.name}`, description: 'Pick one: "responses" (default, streams reasoning) or "chat" (compatibility mode).', items: items }));
1404
1550
  };
1405
- const renderOllamaThinkingMenu = () => {
1406
- if (screen.kind !== "config-provider-ollama-thinking") {
1551
+ const renderOpenAIEnumMenu = (screenKind, subKey, title, description, values, clearedLabel) => {
1552
+ if (screen.kind !== screenKind) {
1407
1553
  return null;
1408
1554
  }
1409
1555
  const entry = config.providers.find((provider) => provider.name === screen.name);
1410
1556
  if (!entry) {
1411
1557
  return null;
1412
1558
  }
1413
- const current = "thinking" in entry.options ? entry.options.thinking : undefined;
1559
+ const reasoning = "reasoning" in entry.options
1560
+ ? entry.options.reasoning
1561
+ : undefined;
1562
+ const current = reasoning?.[subKey];
1563
+ // Merge into the sibling reasoning key, collapsing to `undefined` when the
1564
+ // object would end up empty so we never persist `"reasoning": {}`.
1565
+ const patchReasoning = (next) => {
1566
+ const merged = { ...(reasoning ?? {}), [subKey]: next };
1567
+ const hasValues = Object.values(merged).some((v) => v !== undefined);
1568
+ return patchProvider(entry.name, {
1569
+ reasoning: hasValues ? merged : undefined,
1570
+ });
1571
+ };
1414
1572
  const items = [
1415
1573
  {
1416
- label: current === undefined ? "Not set • current" : "Not set (clear value)",
1574
+ label: current === undefined ? clearedLabel : "Not set (clear value)",
1417
1575
  value: () => {
1418
- if (updateConfig({ providers: patchProvider(entry.name, { thinking: undefined }) }, `Cleared thinking for "${entry.name}".`)) {
1576
+ if (updateConfig({ providers: patchReasoning(undefined) }, `Cleared reasoning.${subKey} for "${entry.name}".`)) {
1419
1577
  setScreen({ kind: "config-provider-edit", name: entry.name });
1420
1578
  }
1421
1579
  },
1422
1580
  },
1423
- ...[false, true, "low", "medium", "high"].map((value) => ({
1424
- label: current === value ? `${String(value)} • current` : String(value),
1581
+ ...values.map((value) => ({
1582
+ label: current === value ? `${value} • current` : value,
1425
1583
  value: () => {
1426
- if (updateConfig({ providers: patchProvider(entry.name, { thinking: value }) }, `Updated thinking for "${entry.name}" to "${String(value)}".`)) {
1584
+ if (updateConfig({ providers: patchReasoning(value) }, `Updated reasoning.${subKey} for "${entry.name}" to "${value}".`)) {
1427
1585
  setScreen({ kind: "config-provider-edit", name: entry.name });
1428
1586
  }
1429
1587
  },
@@ -1433,7 +1591,7 @@ export function ConfigureApp({ config, mcpConfig, mcpManager, skills, onExit, })
1433
1591
  value: () => setScreen({ kind: "config-provider-edit", name: entry.name }),
1434
1592
  },
1435
1593
  ];
1436
- return (_jsx(MenuScreen, { title: `Choose Thinking • ${entry.name}`, description: 'Pick one of the allowed values: clear, false, true, "low", "medium", or "high".', items: items }));
1594
+ return _jsx(MenuScreen, { title: title, description: description, items: items });
1437
1595
  };
1438
1596
  const renderProviderTypeMenu = () => {
1439
1597
  if (screen.kind !== "config-provider-type") {
@@ -1762,7 +1920,7 @@ export function ConfigureApp({ config, mcpConfig, mcpManager, skills, onExit, })
1762
1920
  };
1763
1921
  const renderSearchProviderMenu = () => {
1764
1922
  const items = [
1765
- ...["brave", "exa", "firecrawl", "serper", "tavily"].map((provider) => ({
1923
+ ...["brave", "exa", "firecrawl", "litellm", "serper", "tavily"].map((provider) => ({
1766
1924
  label: provider === configData.search.provider
1767
1925
  ? `${SEARCH_PROVIDER_LABELS[provider]} • current`
1768
1926
  : SEARCH_PROVIDER_LABELS[provider],
@@ -1787,8 +1945,64 @@ export function ConfigureApp({ config, mcpConfig, mcpManager, skills, onExit, })
1787
1945
  const renderSearchConfigMenu = () => {
1788
1946
  const activeProvider = configData.search.provider;
1789
1947
  const activeProviderLabel = SEARCH_PROVIDER_LABELS[activeProvider];
1948
+ const isLiteLLM = activeProvider === "litellm";
1949
+ const credentialLabel = isLiteLLM ? "Virtual key" : "API key";
1790
1950
  const apiKey = configData.search[activeProvider].apiKey;
1791
1951
  const redacted = compactJson(maskSensitiveParamsForDisplay({ apiKey: apiKey ?? "" }));
1952
+ const litellmItems = isLiteLLM
1953
+ ? [
1954
+ {
1955
+ label: `Base URL • ${truncate(configData.search.litellm.baseURL ?? "(unset)", 44)}`,
1956
+ value: () => promptValue({
1957
+ title: "Update LiteLLM base URL",
1958
+ label: "Base URL",
1959
+ initialValue: configData.search.litellm.baseURL ?? "",
1960
+ onSubmit: async (value) => {
1961
+ const nextBaseURL = value.trim();
1962
+ if (!nextBaseURL) {
1963
+ throw new Error("Base URL is required.");
1964
+ }
1965
+ if (updateConfig({
1966
+ search: {
1967
+ ...config.search,
1968
+ litellm: {
1969
+ ...config.search.litellm,
1970
+ baseURL: nextBaseURL,
1971
+ },
1972
+ },
1973
+ }, "Updated LiteLLM base URL.")) {
1974
+ setPrompt(null);
1975
+ }
1976
+ },
1977
+ }),
1978
+ },
1979
+ {
1980
+ label: `Search tool • ${configData.search.litellm.tool ?? "(unset)"}`,
1981
+ value: () => promptValue({
1982
+ title: "Update LiteLLM search tool",
1983
+ label: "Search tool name",
1984
+ initialValue: configData.search.litellm.tool ?? "",
1985
+ onSubmit: async (value) => {
1986
+ const nextSearchTool = value.trim();
1987
+ if (!nextSearchTool) {
1988
+ throw new Error("Search tool name is required.");
1989
+ }
1990
+ if (updateConfig({
1991
+ search: {
1992
+ ...config.search,
1993
+ litellm: {
1994
+ ...config.search.litellm,
1995
+ tool: nextSearchTool,
1996
+ },
1997
+ },
1998
+ }, "Updated LiteLLM search tool.")) {
1999
+ setPrompt(null);
2000
+ }
2001
+ },
2002
+ }),
2003
+ },
2004
+ ]
2005
+ : [];
1792
2006
  const items = [
1793
2007
  {
1794
2008
  label: `Enabled • ${yesNo(configData.search.enabled)}`,
@@ -1807,16 +2021,17 @@ export function ConfigureApp({ config, mcpConfig, mcpManager, skills, onExit, })
1807
2021
  label: `Provider • ${activeProviderLabel}`,
1808
2022
  value: () => setScreen({ kind: "config-search-provider" }),
1809
2023
  },
2024
+ ...litellmItems,
1810
2025
  {
1811
- label: `${activeProviderLabel} API key • ${truncate(redacted, 44)}`,
2026
+ label: `${activeProviderLabel} ${credentialLabel.toLowerCase()} • ${truncate(redacted, 44)}`,
1812
2027
  value: () => promptValue({
1813
- title: `Update ${activeProviderLabel} API key`,
1814
- label: "API key",
2028
+ title: `Update ${activeProviderLabel} ${credentialLabel.toLowerCase()}`,
2029
+ label: credentialLabel,
1815
2030
  initialValue: apiKey ?? "",
1816
2031
  onSubmit: async (value) => {
1817
2032
  const nextApiKey = value.trim();
1818
2033
  if (!nextApiKey) {
1819
- throw new Error("API key is required.");
2034
+ throw new Error(`${credentialLabel} is required.`);
1820
2035
  }
1821
2036
  if (updateConfig({
1822
2037
  search: {
@@ -1826,7 +2041,7 @@ export function ConfigureApp({ config, mcpConfig, mcpManager, skills, onExit, })
1826
2041
  apiKey: nextApiKey,
1827
2042
  },
1828
2043
  },
1829
- }, `Updated ${activeProviderLabel} API key.`)) {
2044
+ }, `Updated ${activeProviderLabel} ${credentialLabel.toLowerCase()}.`)) {
1830
2045
  setPrompt(null);
1831
2046
  }
1832
2047
  },
@@ -2138,10 +2353,14 @@ export function ConfigureApp({ config, mcpConfig, mcpManager, skills, onExit, })
2138
2353
  return renderProviderEditMenu();
2139
2354
  case "config-provider-type":
2140
2355
  return renderProviderTypeMenu();
2141
- case "config-provider-anthropic-thinking":
2142
- return renderAnthropicThinkingMenu();
2143
- case "config-provider-ollama-thinking":
2144
- return renderOllamaThinkingMenu();
2356
+ case "config-provider-openai-api":
2357
+ return renderOpenAIApiMenu();
2358
+ case "config-provider-reasoning-effort":
2359
+ return renderOpenAIEnumMenu("config-provider-reasoning-effort", "effort", `Choose Reasoning effort • ${screen.name}`, 'Enables thinking/reasoning. Pick one: "minimal", "low", "medium", "high", or clear to disable. GPT-5 needs "medium"+ to show a summary.', ["minimal", "low", "medium", "high"], "Not set (off) • current");
2360
+ case "config-provider-reasoning-summary":
2361
+ return renderOpenAIEnumMenu("config-provider-reasoning-summary", "summary", `Choose Reasoning summary • ${screen.name}`, 'Responses API only. Pick one: "auto" (default), "concise", "detailed", or "none".', ["auto", "concise", "detailed", "none"], "Not set (auto) • current");
2362
+ case "config-provider-reasoning-display":
2363
+ return renderOpenAIEnumMenu("config-provider-reasoning-display", "display", `Choose Reasoning display • ${screen.name}`, 'Bedrock Claude / MiniMax only (not native Anthropic). "summarized" reveals reasoning on newer Bedrock Claude (Opus 4.7+) and switches to adaptive thinking; "omitted" hides it. Clear to keep the enabled+budget scheme.', ["summarized", "omitted"], "Not set • current");
2145
2364
  case "config-provider-delete-confirm":
2146
2365
  return renderProviderDeleteConfirm();
2147
2366
  case "config-llms":