@proxysoul/soulforge 2.15.1 → 2.15.3

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/dist/index.js CHANGED
@@ -4544,6 +4544,566 @@ var init_protocol = __esm(() => {
4544
4544
  HEARTH_MAX_FRAME_BYTES = 1024 * 1024;
4545
4545
  });
4546
4546
 
4547
+ // src/core/utils/ensure-soulforge-dir.ts
4548
+ import { execSync as execSync2 } from "child_process";
4549
+ import { appendFileSync as appendFileSync2, existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
4550
+ import { join as join6 } from "path";
4551
+ function ensureSoulforgeDir(cwd) {
4552
+ const dir = join6(cwd, ENTRY);
4553
+ if (!existsSync6(dir))
4554
+ mkdirSync5(dir, { recursive: true });
4555
+ if (!patched.has(cwd)) {
4556
+ patched.add(cwd);
4557
+ try {
4558
+ ensureGitignore(cwd);
4559
+ } catch {}
4560
+ }
4561
+ return dir;
4562
+ }
4563
+ function ensureGitignore(cwd) {
4564
+ const status = gitCheckIgnoreStatus(cwd);
4565
+ if (status !== 1)
4566
+ return;
4567
+ const gitignorePath = join6(cwd, ".gitignore");
4568
+ if (existsSync6(gitignorePath)) {
4569
+ const content = readFileSync6(gitignorePath, "utf-8");
4570
+ const eol = content.includes(`\r
4571
+ `) ? `\r
4572
+ ` : `
4573
+ `;
4574
+ const prefix = content.length > 0 && !content.endsWith(`
4575
+ `) ? eol : "";
4576
+ appendFileSync2(gitignorePath, `${prefix}.soulforge${eol}`);
4577
+ } else {
4578
+ writeFileSync5(gitignorePath, `.soulforge
4579
+ `);
4580
+ }
4581
+ }
4582
+ function gitCheckIgnoreStatus(cwd) {
4583
+ try {
4584
+ execSync2("git check-ignore -q .soulforge", { cwd, stdio: "pipe", timeout: 3000 });
4585
+ return 0;
4586
+ } catch (err2) {
4587
+ return err2.status ?? 128;
4588
+ }
4589
+ }
4590
+ var ENTRY = ".soulforge", patched;
4591
+ var init_ensure_soulforge_dir = __esm(() => {
4592
+ patched = new Set;
4593
+ });
4594
+
4595
+ // src/config/index.ts
4596
+ var exports_config2 = {};
4597
+ __export(exports_config2, {
4598
+ stripConfigKeys: () => stripConfigKeys,
4599
+ saveProjectConfig: () => saveProjectConfig,
4600
+ saveGlobalConfig: () => saveGlobalConfig,
4601
+ saveConfig: () => saveConfig,
4602
+ removeProjectConfigKeys: () => removeProjectConfigKeys,
4603
+ removeGlobalConfigKeys: () => removeGlobalConfigKeys,
4604
+ mergeConfigs: () => mergeConfigs,
4605
+ loadProjectConfig: () => loadProjectConfig,
4606
+ loadConfig: () => loadConfig,
4607
+ applyConfigPatch: () => applyConfigPatch,
4608
+ DEFAULT_CONFIG: () => DEFAULT_CONFIG
4609
+ });
4610
+ import { existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
4611
+ import { homedir as homedir6 } from "os";
4612
+ import { join as join7 } from "path";
4613
+ function mergeProviders(base, overlay) {
4614
+ if (!base && !overlay)
4615
+ return;
4616
+ if (!overlay)
4617
+ return base;
4618
+ if (!base)
4619
+ return overlay;
4620
+ const map = new Map(base.map((p) => [p.id, p]));
4621
+ for (const p of overlay)
4622
+ map.set(p.id, p);
4623
+ return [...map.values()];
4624
+ }
4625
+ function mergeMCPServers(base, overlay) {
4626
+ if (!base && !overlay)
4627
+ return;
4628
+ if (!overlay)
4629
+ return base;
4630
+ if (!base)
4631
+ return overlay;
4632
+ const map = new Map(base.map((s) => [s.name, s]));
4633
+ for (const s of overlay)
4634
+ map.set(s.name, s);
4635
+ return [...map.values()];
4636
+ }
4637
+ function loadConfig() {
4638
+ if (!existsSync7(CONFIG_DIR)) {
4639
+ mkdirSync6(CONFIG_DIR, { recursive: true, mode: 448 });
4640
+ }
4641
+ if (!existsSync7(CONFIG_FILE)) {
4642
+ writeFileSync6(CONFIG_FILE, JSON.stringify(DEFAULT_CONFIG, null, 2));
4643
+ return DEFAULT_CONFIG;
4644
+ }
4645
+ try {
4646
+ const raw = readFileSync7(CONFIG_FILE, "utf-8");
4647
+ return { ...DEFAULT_CONFIG, ...JSON.parse(raw) };
4648
+ } catch (err2) {
4649
+ logBackgroundError("config", `Failed to parse ${CONFIG_FILE}: ${err2 instanceof Error ? err2.message : String(err2)} \u2014 using defaults`);
4650
+ return DEFAULT_CONFIG;
4651
+ }
4652
+ }
4653
+ function loadProjectConfig(cwd) {
4654
+ const projectFile = join7(cwd, ".soulforge", "config.json");
4655
+ if (!existsSync7(projectFile))
4656
+ return null;
4657
+ try {
4658
+ const raw = readFileSync7(projectFile, "utf-8");
4659
+ return JSON.parse(raw);
4660
+ } catch (err2) {
4661
+ logBackgroundError("config", `Failed to parse ${projectFile}: ${err2 instanceof Error ? err2.message : String(err2)} \u2014 ignoring project config`);
4662
+ return null;
4663
+ }
4664
+ }
4665
+ function mergeConfigs(global2, project) {
4666
+ const layers = [global2];
4667
+ if (project)
4668
+ layers.push(project);
4669
+ let merged = { ...global2 };
4670
+ for (const layer of layers.slice(1)) {
4671
+ const ei = layer.editorIntegration ? { ...merged.editorIntegration, ...layer.editorIntegration } : merged.editorIntegration;
4672
+ const ci = layer.codeIntelligence ? { ...merged.codeIntelligence, ...layer.codeIntelligence } : merged.codeIntelligence;
4673
+ const th = layer.thinking ? { ...merged.thinking, ...layer.thinking } : merged.thinking;
4674
+ const perf = layer.performance ? { ...merged.performance, ...layer.performance } : merged.performance;
4675
+ const cm = layer.contextManagement ? { ...merged.contextManagement, ...layer.contextManagement } : merged.contextManagement;
4676
+ const comp = layer.compaction ? { ...merged.compaction, ...layer.compaction } : merged.compaction;
4677
+ const retry = layer.retry ? { ...merged.retry, ...layer.retry } : merged.retry;
4678
+ const providers = mergeProviders(merged.providers, layer.providers);
4679
+ const mcpServers = mergeMCPServers(merged.mcpServers, layer.mcpServers);
4680
+ merged = {
4681
+ ...merged,
4682
+ ...layer,
4683
+ editor: { ...merged.editor, ...layer.editor },
4684
+ theme: { ...merged.theme, ...layer.theme },
4685
+ editorIntegration: ei,
4686
+ codeIntelligence: ci,
4687
+ thinking: th,
4688
+ performance: perf,
4689
+ contextManagement: cm,
4690
+ compaction: comp,
4691
+ retry,
4692
+ providers,
4693
+ mcpServers
4694
+ };
4695
+ }
4696
+ return merged;
4697
+ }
4698
+ function saveConfig(config) {
4699
+ if (!existsSync7(CONFIG_DIR)) {
4700
+ mkdirSync6(CONFIG_DIR, { recursive: true, mode: 448 });
4701
+ }
4702
+ writeFileSync6(CONFIG_FILE, JSON.stringify(config, null, 2));
4703
+ }
4704
+ function saveProjectConfig(cwd, patch) {
4705
+ const dir = ensureSoulforgeDir(cwd);
4706
+ const file = join7(dir, "config.json");
4707
+ let existing = {};
4708
+ try {
4709
+ existing = JSON.parse(readFileSync7(file, "utf-8"));
4710
+ } catch {}
4711
+ const merged = { ...existing, ...patch };
4712
+ if (patch.thinking)
4713
+ merged.thinking = { ...existing.thinking, ...patch.thinking };
4714
+ if (patch.performance)
4715
+ merged.performance = { ...existing.performance, ...patch.performance };
4716
+ if (patch.contextManagement)
4717
+ merged.contextManagement = { ...existing.contextManagement, ...patch.contextManagement };
4718
+ if (patch.agentFeatures)
4719
+ merged.agentFeatures = { ...existing.agentFeatures, ...patch.agentFeatures };
4720
+ if (patch.retry)
4721
+ merged.retry = { ...existing.retry, ...patch.retry };
4722
+ writeFileSync6(file, JSON.stringify(merged, null, 2));
4723
+ }
4724
+ function saveGlobalConfig(patch) {
4725
+ if (!existsSync7(CONFIG_DIR))
4726
+ mkdirSync6(CONFIG_DIR, { recursive: true, mode: 448 });
4727
+ let existing = DEFAULT_CONFIG;
4728
+ try {
4729
+ existing = { ...DEFAULT_CONFIG, ...JSON.parse(readFileSync7(CONFIG_FILE, "utf-8")) };
4730
+ } catch {}
4731
+ const merged = { ...existing, ...patch };
4732
+ if (patch.thinking)
4733
+ merged.thinking = { ...existing.thinking, ...patch.thinking };
4734
+ if (patch.performance)
4735
+ merged.performance = { ...existing.performance, ...patch.performance };
4736
+ if (patch.contextManagement)
4737
+ merged.contextManagement = { ...existing.contextManagement, ...patch.contextManagement };
4738
+ if (patch.agentFeatures)
4739
+ merged.agentFeatures = { ...existing.agentFeatures, ...patch.agentFeatures };
4740
+ if (patch.retry)
4741
+ merged.retry = { ...existing.retry, ...patch.retry };
4742
+ writeFileSync6(CONFIG_FILE, JSON.stringify(merged, null, 2));
4743
+ }
4744
+ function removeProjectConfigKeys(cwd, keys) {
4745
+ const file = join7(cwd, ".soulforge", "config.json");
4746
+ if (!existsSync7(file))
4747
+ return;
4748
+ try {
4749
+ const existing = JSON.parse(readFileSync7(file, "utf-8"));
4750
+ for (const k of keys)
4751
+ delete existing[k];
4752
+ writeFileSync6(file, JSON.stringify(existing, null, 2));
4753
+ } catch {}
4754
+ }
4755
+ function removeGlobalConfigKeys(keys) {
4756
+ if (!existsSync7(CONFIG_FILE))
4757
+ return;
4758
+ try {
4759
+ const existing = JSON.parse(readFileSync7(CONFIG_FILE, "utf-8"));
4760
+ for (const k of keys)
4761
+ delete existing[k];
4762
+ writeFileSync6(CONFIG_FILE, JSON.stringify(existing, null, 2));
4763
+ } catch {}
4764
+ }
4765
+ function applyConfigPatch(base, patch) {
4766
+ const result = { ...base, ...patch };
4767
+ for (const key of NESTED_KEYS) {
4768
+ const b = base[key];
4769
+ const p = patch[key];
4770
+ if (p && b && typeof b === "object" && typeof p === "object") {
4771
+ result[key] = { ...b, ...p };
4772
+ }
4773
+ }
4774
+ return result;
4775
+ }
4776
+ function stripConfigKeys(config, keys) {
4777
+ const result = { ...config };
4778
+ for (const k of keys)
4779
+ delete result[k];
4780
+ return result;
4781
+ }
4782
+ var CONFIG_DIR, CONFIG_FILE, DEFAULT_CONFIG, NESTED_KEYS;
4783
+ var init_config2 = __esm(() => {
4784
+ init_ensure_soulforge_dir();
4785
+ init_errors();
4786
+ CONFIG_DIR = join7(homedir6(), ".soulforge");
4787
+ CONFIG_FILE = join7(CONFIG_DIR, "config.json");
4788
+ DEFAULT_CONFIG = {
4789
+ defaultModel: "none",
4790
+ routerRules: [],
4791
+ editor: {
4792
+ command: "nvim",
4793
+ args: []
4794
+ },
4795
+ theme: {
4796
+ name: "dark",
4797
+ transparent: true
4798
+ },
4799
+ nvimConfig: "default",
4800
+ editorIntegration: {
4801
+ diagnostics: true,
4802
+ symbols: true,
4803
+ hover: true,
4804
+ references: true,
4805
+ definition: true,
4806
+ codeActions: true,
4807
+ editorContext: true,
4808
+ rename: true,
4809
+ lspStatus: true,
4810
+ format: true,
4811
+ syncEditorOnEdit: false
4812
+ },
4813
+ codeExecution: true,
4814
+ webSearch: true,
4815
+ compaction: {
4816
+ strategy: "v2",
4817
+ triggerThreshold: 0.7,
4818
+ resetThreshold: 0.4,
4819
+ keepRecent: 4,
4820
+ maxToolResults: 30,
4821
+ llmExtraction: true
4822
+ }
4823
+ };
4824
+ NESTED_KEYS = [
4825
+ "editor",
4826
+ "theme",
4827
+ "editorIntegration",
4828
+ "codeIntelligence",
4829
+ "thinking",
4830
+ "performance",
4831
+ "contextManagement",
4832
+ "agentFeatures",
4833
+ "compaction",
4834
+ "retry"
4835
+ ];
4836
+ });
4837
+
4838
+ // src/hearth/provider-commands.ts
4839
+ function showBool(v, fallback = false) {
4840
+ return v ?? fallback ? "on" : "off";
4841
+ }
4842
+ function parseBool(raw) {
4843
+ const v = raw.trim().toLowerCase();
4844
+ if (v === "on" || v === "true" || v === "1" || v === "yes")
4845
+ return true;
4846
+ if (v === "off" || v === "false" || v === "0" || v === "no")
4847
+ return false;
4848
+ return null;
4849
+ }
4850
+ function findSetting(cmdName) {
4851
+ const bare = cmdName.startsWith("/") ? cmdName.slice(1) : cmdName;
4852
+ return SETTINGS.find((s) => s.cmd === bare);
4853
+ }
4854
+ function validateSettingValue(setting, raw) {
4855
+ const v = raw.trim();
4856
+ if (!v)
4857
+ return { ok: false, error: "missing value" };
4858
+ if (setting.type === "toggle") {
4859
+ const b = parseBool(v);
4860
+ if (b === null)
4861
+ return { ok: false, error: `expected on|off, got "${v}"` };
4862
+ return { ok: true, value: b ? "on" : "off" };
4863
+ }
4864
+ if (setting.type === "cycle") {
4865
+ const lower = v.toLowerCase();
4866
+ const match = setting.options?.find((o) => o.toLowerCase() === lower);
4867
+ if (!match) {
4868
+ return {
4869
+ ok: false,
4870
+ error: `expected ${setting.options?.join("|") ?? "(none)"} \u2014 got "${v}"`
4871
+ };
4872
+ }
4873
+ return { ok: true, value: match };
4874
+ }
4875
+ const n = Number.parseInt(v, 10);
4876
+ if (!Number.isFinite(n) || n < 1024) {
4877
+ return { ok: false, error: "expected integer \u2265 1024" };
4878
+ }
4879
+ return { ok: true, value: String(n) };
4880
+ }
4881
+ function renderSettingsOverview(cfg) {
4882
+ const lines = ["\u2501\u2501 Provider settings \u2501\u2501"];
4883
+ for (const s of SETTINGS) {
4884
+ lines.push(`/${s.cmd.padEnd(15)} ${s.read(cfg).padEnd(10)} ${s.label}`);
4885
+ }
4886
+ lines.push("");
4887
+ lines.push("set: /<cmd> <value>");
4888
+ lines.push("show: /<cmd>");
4889
+ return lines.join(`
4890
+ `);
4891
+ }
4892
+ async function handleSettingsCommand(cmdName, args2, notify, opts = {}) {
4893
+ const load = opts.load ?? loadConfig;
4894
+ const save = opts.save ?? saveGlobalConfig;
4895
+ if (cmdName === "/settings") {
4896
+ await notify(renderSettingsOverview(load()));
4897
+ return true;
4898
+ }
4899
+ const setting = findSetting(cmdName);
4900
+ if (!setting)
4901
+ return false;
4902
+ const raw = args2.join(" ").trim();
4903
+ if (!raw) {
4904
+ const current = setting.read(load());
4905
+ const opts2 = setting.options?.join("|") ?? (setting.type === "toggle" ? "on|off" : "<int>");
4906
+ await notify(`/${setting.cmd}: ${current}
4907
+ options: ${opts2}`);
4908
+ return true;
4909
+ }
4910
+ const parsed = validateSettingValue(setting, raw);
4911
+ if (!parsed.ok) {
4912
+ await notify(`\u2717 ${parsed.error}`);
4913
+ return true;
4914
+ }
4915
+ try {
4916
+ save(setting.patch(parsed.value));
4917
+ } catch (err2) {
4918
+ await notify(`\u2717 save failed: ${err2 instanceof Error ? err2.message : String(err2)}`);
4919
+ return true;
4920
+ }
4921
+ await notify(`\u2713 ${setting.cmd} \u2192 ${parsed.value}`);
4922
+ return true;
4923
+ }
4924
+ function settingsHelpLines() {
4925
+ return [
4926
+ "\u2501\u2501 Provider settings \u2501\u2501",
4927
+ "/settings show all current values",
4928
+ "/thinking [mode] off | auto | enabled | adaptive | disabled",
4929
+ "/budget [N] thinking budget tokens (\u22651024)",
4930
+ "/effort [level] off | low | medium | high | xhigh | max",
4931
+ "/speed [mode] off | standard | fast (Opus 4.6)",
4932
+ "/reasoning [level] OpenAI reasoning effort",
4933
+ "/tier [tier] OpenAI service tier (flex|priority|auto\u2026)",
4934
+ "/sendreasoning on|off send reasoning across turns",
4935
+ "/toolstream on|off stream tool args incrementally",
4936
+ "/seqtools on|off sequential tool use",
4937
+ "/codeexec on|off code execution tool",
4938
+ "/websearch on|off web search tool",
4939
+ "/computeruse on|off computer use tool",
4940
+ "/compact on|off server-side context compaction",
4941
+ "/cleartools on|off clear old tool results (server)",
4942
+ "/clearthinking on|off preserve thinking blocks",
4943
+ "/pruning [target] none | main | subagents | both"
4944
+ ];
4945
+ }
4946
+ var THINKING_MODES, EFFORT_LEVELS, SPEEDS, OAI_EFFORTS, SERVICE_TIERS, PRUNING, SETTINGS, SETTINGS_COMMAND_NAMES;
4947
+ var init_provider_commands = __esm(() => {
4948
+ init_config2();
4949
+ THINKING_MODES = ["off", "disabled", "auto", "adaptive", "enabled"];
4950
+ EFFORT_LEVELS = ["off", "low", "medium", "high", "xhigh", "max"];
4951
+ SPEEDS = ["off", "standard", "fast"];
4952
+ OAI_EFFORTS = ["off", "none", "minimal", "low", "medium", "high", "xhigh"];
4953
+ SERVICE_TIERS = ["off", "auto", "default", "flex", "priority"];
4954
+ PRUNING = ["none", "main", "subagents", "both"];
4955
+ SETTINGS = [
4956
+ {
4957
+ cmd: "thinking",
4958
+ label: "thinking mode",
4959
+ type: "cycle",
4960
+ options: THINKING_MODES,
4961
+ read: (c) => c.thinking?.mode ?? "off",
4962
+ patch: (raw) => ({ thinking: { mode: raw } })
4963
+ },
4964
+ {
4965
+ cmd: "budget",
4966
+ label: "thinking budget tokens",
4967
+ type: "budget",
4968
+ read: (c) => String(c.thinking?.budgetTokens ?? 1e4),
4969
+ patch: (raw) => ({
4970
+ thinking: { mode: "enabled", budgetTokens: Number.parseInt(raw, 10) }
4971
+ })
4972
+ },
4973
+ {
4974
+ cmd: "effort",
4975
+ label: "reasoning effort",
4976
+ type: "cycle",
4977
+ options: EFFORT_LEVELS,
4978
+ read: (c) => c.performance?.effort ?? "off",
4979
+ patch: (raw) => ({
4980
+ performance: { effort: raw }
4981
+ })
4982
+ },
4983
+ {
4984
+ cmd: "speed",
4985
+ label: "speed (Opus 4.6)",
4986
+ type: "cycle",
4987
+ options: SPEEDS,
4988
+ read: (c) => c.performance?.speed ?? "off",
4989
+ patch: (raw) => ({
4990
+ performance: { speed: raw }
4991
+ })
4992
+ },
4993
+ {
4994
+ cmd: "reasoning",
4995
+ label: "OpenAI reasoning effort",
4996
+ type: "cycle",
4997
+ options: OAI_EFFORTS,
4998
+ read: (c) => c.performance?.openaiReasoningEffort ?? "off",
4999
+ patch: (raw) => ({
5000
+ performance: { openaiReasoningEffort: raw }
5001
+ })
5002
+ },
5003
+ {
5004
+ cmd: "tier",
5005
+ label: "OpenAI service tier",
5006
+ type: "cycle",
5007
+ options: SERVICE_TIERS,
5008
+ read: (c) => c.performance?.serviceTier ?? "off",
5009
+ patch: (raw) => ({
5010
+ performance: { serviceTier: raw }
5011
+ })
5012
+ },
5013
+ {
5014
+ cmd: "sendreasoning",
5015
+ label: "send reasoning across turns",
5016
+ type: "toggle",
5017
+ read: (c) => showBool(c.performance?.sendReasoning),
5018
+ patch: (raw) => ({
5019
+ performance: { sendReasoning: parseBool(raw) ?? false }
5020
+ })
5021
+ },
5022
+ {
5023
+ cmd: "toolstream",
5024
+ label: "tool arg streaming",
5025
+ type: "toggle",
5026
+ read: (c) => showBool(c.performance?.toolStreaming, true),
5027
+ patch: (raw) => ({
5028
+ performance: { toolStreaming: parseBool(raw) ?? true }
5029
+ })
5030
+ },
5031
+ {
5032
+ cmd: "seqtools",
5033
+ label: "sequential tool use",
5034
+ type: "toggle",
5035
+ read: (c) => showBool(c.performance?.disableParallelToolUse),
5036
+ patch: (raw) => ({
5037
+ performance: { disableParallelToolUse: parseBool(raw) ?? false }
5038
+ })
5039
+ },
5040
+ {
5041
+ cmd: "codeexec",
5042
+ label: "code execution tool",
5043
+ type: "toggle",
5044
+ read: (c) => showBool(c.codeExecution, true),
5045
+ patch: (raw) => ({ codeExecution: parseBool(raw) ?? true })
5046
+ },
5047
+ {
5048
+ cmd: "websearch",
5049
+ label: "web search tool",
5050
+ type: "toggle",
5051
+ read: (c) => showBool(c.webSearch, true),
5052
+ patch: (raw) => ({ webSearch: parseBool(raw) ?? true })
5053
+ },
5054
+ {
5055
+ cmd: "computeruse",
5056
+ label: "computer use tool",
5057
+ type: "toggle",
5058
+ read: (c) => showBool(c.computerUse),
5059
+ patch: (raw) => ({ computerUse: parseBool(raw) ?? false })
5060
+ },
5061
+ {
5062
+ cmd: "compact",
5063
+ label: "server-side compaction",
5064
+ type: "toggle",
5065
+ read: (c) => showBool(c.contextManagement?.compact),
5066
+ patch: (raw) => ({
5067
+ contextManagement: { compact: parseBool(raw) ?? false }
5068
+ })
5069
+ },
5070
+ {
5071
+ cmd: "cleartools",
5072
+ label: "clear old tool results (server)",
5073
+ type: "toggle",
5074
+ read: (c) => showBool(c.contextManagement?.clearToolUses),
5075
+ patch: (raw) => ({
5076
+ contextManagement: { clearToolUses: parseBool(raw) ?? false }
5077
+ })
5078
+ },
5079
+ {
5080
+ cmd: "clearthinking",
5081
+ label: "preserve thinking blocks",
5082
+ type: "toggle",
5083
+ read: (c) => showBool(c.contextManagement?.clearThinking, true),
5084
+ patch: (raw) => ({
5085
+ contextManagement: { clearThinking: parseBool(raw) ?? true }
5086
+ })
5087
+ },
5088
+ {
5089
+ cmd: "pruning",
5090
+ label: "tool-result pruning target",
5091
+ type: "cycle",
5092
+ options: PRUNING,
5093
+ read: (c) => c.contextManagement?.pruningTarget ?? "none",
5094
+ patch: (raw) => ({
5095
+ contextManagement: {
5096
+ pruningTarget: raw
5097
+ }
5098
+ })
5099
+ }
5100
+ ];
5101
+ SETTINGS_COMMAND_NAMES = [
5102
+ ...SETTINGS.map((s) => `/${s.cmd}`),
5103
+ "/settings"
5104
+ ];
5105
+ });
5106
+
4547
5107
  // src/hearth/redact.ts
4548
5108
  var exports_redact = {};
4549
5109
  __export(exports_redact, {
@@ -5678,9 +6238,9 @@ data: ${payload}
5678
6238
  });
5679
6239
 
5680
6240
  // src/hearth/adapters/telegram.ts
5681
- import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
5682
- import { homedir as homedir6 } from "os";
5683
- import { dirname as dirname3, join as join6 } from "path";
6241
+ import { existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "fs";
6242
+ import { homedir as homedir7 } from "os";
6243
+ import { dirname as dirname3, join as join8 } from "path";
5684
6244
  function sleep2(ms) {
5685
6245
  return new Promise((resolve2) => setTimeout(resolve2, ms));
5686
6246
  }
@@ -5801,14 +6361,14 @@ var init_telegram = __esm(() => {
5801
6361
  this.loadCallbackState();
5802
6362
  }
5803
6363
  callbackStatePath() {
5804
- return join6(homedir6(), ".soulforge", `hearth-callbacks-${this.botId}.json`);
6364
+ return join8(homedir7(), ".soulforge", `hearth-callbacks-${this.botId}.json`);
5805
6365
  }
5806
6366
  loadCallbackState() {
5807
6367
  const path = this.callbackStatePath();
5808
- if (!existsSync6(path))
6368
+ if (!existsSync8(path))
5809
6369
  return;
5810
6370
  try {
5811
- const raw = readFileSync6(path, "utf-8");
6371
+ const raw = readFileSync8(path, "utf-8");
5812
6372
  const parsed = JSON.parse(raw);
5813
6373
  for (const [chatId, entry] of Object.entries(parsed)) {
5814
6374
  this.lastCallbackByChat.set(chatId, entry);
@@ -5818,11 +6378,11 @@ var init_telegram = __esm(() => {
5818
6378
  persistCallbackState() {
5819
6379
  try {
5820
6380
  const path = this.callbackStatePath();
5821
- mkdirSync5(dirname3(path), { recursive: true, mode: 448 });
6381
+ mkdirSync7(dirname3(path), { recursive: true, mode: 448 });
5822
6382
  const obj = {};
5823
6383
  for (const [k, v] of this.lastCallbackByChat)
5824
6384
  obj[k] = v;
5825
- writeFileSync5(path, JSON.stringify(obj), { mode: 384 });
6385
+ writeFileSync7(path, JSON.stringify(obj), { mode: 384 });
5826
6386
  } catch {}
5827
6387
  }
5828
6388
  getRendererFor(externalId) {
@@ -6525,297 +7085,6 @@ var init_surface_host = __esm(() => {
6525
7085
  init_surface_factory();
6526
7086
  });
6527
7087
 
6528
- // src/core/utils/ensure-soulforge-dir.ts
6529
- import { execSync as execSync2 } from "child_process";
6530
- import { appendFileSync as appendFileSync2, existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
6531
- import { join as join7 } from "path";
6532
- function ensureSoulforgeDir(cwd) {
6533
- const dir = join7(cwd, ENTRY);
6534
- if (!existsSync7(dir))
6535
- mkdirSync6(dir, { recursive: true });
6536
- if (!patched.has(cwd)) {
6537
- patched.add(cwd);
6538
- try {
6539
- ensureGitignore(cwd);
6540
- } catch {}
6541
- }
6542
- return dir;
6543
- }
6544
- function ensureGitignore(cwd) {
6545
- const status = gitCheckIgnoreStatus(cwd);
6546
- if (status !== 1)
6547
- return;
6548
- const gitignorePath = join7(cwd, ".gitignore");
6549
- if (existsSync7(gitignorePath)) {
6550
- const content = readFileSync7(gitignorePath, "utf-8");
6551
- const eol = content.includes(`\r
6552
- `) ? `\r
6553
- ` : `
6554
- `;
6555
- const prefix = content.length > 0 && !content.endsWith(`
6556
- `) ? eol : "";
6557
- appendFileSync2(gitignorePath, `${prefix}.soulforge${eol}`);
6558
- } else {
6559
- writeFileSync6(gitignorePath, `.soulforge
6560
- `);
6561
- }
6562
- }
6563
- function gitCheckIgnoreStatus(cwd) {
6564
- try {
6565
- execSync2("git check-ignore -q .soulforge", { cwd, stdio: "pipe", timeout: 3000 });
6566
- return 0;
6567
- } catch (err2) {
6568
- return err2.status ?? 128;
6569
- }
6570
- }
6571
- var ENTRY = ".soulforge", patched;
6572
- var init_ensure_soulforge_dir = __esm(() => {
6573
- patched = new Set;
6574
- });
6575
-
6576
- // src/config/index.ts
6577
- var exports_config2 = {};
6578
- __export(exports_config2, {
6579
- stripConfigKeys: () => stripConfigKeys,
6580
- saveProjectConfig: () => saveProjectConfig,
6581
- saveGlobalConfig: () => saveGlobalConfig,
6582
- saveConfig: () => saveConfig,
6583
- removeProjectConfigKeys: () => removeProjectConfigKeys,
6584
- removeGlobalConfigKeys: () => removeGlobalConfigKeys,
6585
- mergeConfigs: () => mergeConfigs,
6586
- loadProjectConfig: () => loadProjectConfig,
6587
- loadConfig: () => loadConfig,
6588
- applyConfigPatch: () => applyConfigPatch,
6589
- DEFAULT_CONFIG: () => DEFAULT_CONFIG
6590
- });
6591
- import { existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "fs";
6592
- import { homedir as homedir7 } from "os";
6593
- import { join as join8 } from "path";
6594
- function mergeProviders(base, overlay) {
6595
- if (!base && !overlay)
6596
- return;
6597
- if (!overlay)
6598
- return base;
6599
- if (!base)
6600
- return overlay;
6601
- const map = new Map(base.map((p) => [p.id, p]));
6602
- for (const p of overlay)
6603
- map.set(p.id, p);
6604
- return [...map.values()];
6605
- }
6606
- function mergeMCPServers(base, overlay) {
6607
- if (!base && !overlay)
6608
- return;
6609
- if (!overlay)
6610
- return base;
6611
- if (!base)
6612
- return overlay;
6613
- const map = new Map(base.map((s) => [s.name, s]));
6614
- for (const s of overlay)
6615
- map.set(s.name, s);
6616
- return [...map.values()];
6617
- }
6618
- function loadConfig() {
6619
- if (!existsSync8(CONFIG_DIR)) {
6620
- mkdirSync7(CONFIG_DIR, { recursive: true, mode: 448 });
6621
- }
6622
- if (!existsSync8(CONFIG_FILE)) {
6623
- writeFileSync7(CONFIG_FILE, JSON.stringify(DEFAULT_CONFIG, null, 2));
6624
- return DEFAULT_CONFIG;
6625
- }
6626
- try {
6627
- const raw = readFileSync8(CONFIG_FILE, "utf-8");
6628
- return { ...DEFAULT_CONFIG, ...JSON.parse(raw) };
6629
- } catch (err2) {
6630
- logBackgroundError("config", `Failed to parse ${CONFIG_FILE}: ${err2 instanceof Error ? err2.message : String(err2)} \u2014 using defaults`);
6631
- return DEFAULT_CONFIG;
6632
- }
6633
- }
6634
- function loadProjectConfig(cwd) {
6635
- const projectFile = join8(cwd, ".soulforge", "config.json");
6636
- if (!existsSync8(projectFile))
6637
- return null;
6638
- try {
6639
- const raw = readFileSync8(projectFile, "utf-8");
6640
- return JSON.parse(raw);
6641
- } catch (err2) {
6642
- logBackgroundError("config", `Failed to parse ${projectFile}: ${err2 instanceof Error ? err2.message : String(err2)} \u2014 ignoring project config`);
6643
- return null;
6644
- }
6645
- }
6646
- function mergeConfigs(global2, project) {
6647
- const layers = [global2];
6648
- if (project)
6649
- layers.push(project);
6650
- let merged = { ...global2 };
6651
- for (const layer of layers.slice(1)) {
6652
- const ei = layer.editorIntegration ? { ...merged.editorIntegration, ...layer.editorIntegration } : merged.editorIntegration;
6653
- const ci = layer.codeIntelligence ? { ...merged.codeIntelligence, ...layer.codeIntelligence } : merged.codeIntelligence;
6654
- const th = layer.thinking ? { ...merged.thinking, ...layer.thinking } : merged.thinking;
6655
- const perf = layer.performance ? { ...merged.performance, ...layer.performance } : merged.performance;
6656
- const cm = layer.contextManagement ? { ...merged.contextManagement, ...layer.contextManagement } : merged.contextManagement;
6657
- const comp = layer.compaction ? { ...merged.compaction, ...layer.compaction } : merged.compaction;
6658
- const retry = layer.retry ? { ...merged.retry, ...layer.retry } : merged.retry;
6659
- const providers = mergeProviders(merged.providers, layer.providers);
6660
- const mcpServers = mergeMCPServers(merged.mcpServers, layer.mcpServers);
6661
- merged = {
6662
- ...merged,
6663
- ...layer,
6664
- editor: { ...merged.editor, ...layer.editor },
6665
- theme: { ...merged.theme, ...layer.theme },
6666
- editorIntegration: ei,
6667
- codeIntelligence: ci,
6668
- thinking: th,
6669
- performance: perf,
6670
- contextManagement: cm,
6671
- compaction: comp,
6672
- retry,
6673
- providers,
6674
- mcpServers
6675
- };
6676
- }
6677
- return merged;
6678
- }
6679
- function saveConfig(config) {
6680
- if (!existsSync8(CONFIG_DIR)) {
6681
- mkdirSync7(CONFIG_DIR, { recursive: true, mode: 448 });
6682
- }
6683
- writeFileSync7(CONFIG_FILE, JSON.stringify(config, null, 2));
6684
- }
6685
- function saveProjectConfig(cwd, patch) {
6686
- const dir = ensureSoulforgeDir(cwd);
6687
- const file = join8(dir, "config.json");
6688
- let existing = {};
6689
- try {
6690
- existing = JSON.parse(readFileSync8(file, "utf-8"));
6691
- } catch {}
6692
- const merged = { ...existing, ...patch };
6693
- if (patch.thinking)
6694
- merged.thinking = { ...existing.thinking, ...patch.thinking };
6695
- if (patch.performance)
6696
- merged.performance = { ...existing.performance, ...patch.performance };
6697
- if (patch.contextManagement)
6698
- merged.contextManagement = { ...existing.contextManagement, ...patch.contextManagement };
6699
- if (patch.agentFeatures)
6700
- merged.agentFeatures = { ...existing.agentFeatures, ...patch.agentFeatures };
6701
- if (patch.retry)
6702
- merged.retry = { ...existing.retry, ...patch.retry };
6703
- writeFileSync7(file, JSON.stringify(merged, null, 2));
6704
- }
6705
- function saveGlobalConfig(patch) {
6706
- if (!existsSync8(CONFIG_DIR))
6707
- mkdirSync7(CONFIG_DIR, { recursive: true, mode: 448 });
6708
- let existing = DEFAULT_CONFIG;
6709
- try {
6710
- existing = { ...DEFAULT_CONFIG, ...JSON.parse(readFileSync8(CONFIG_FILE, "utf-8")) };
6711
- } catch {}
6712
- const merged = { ...existing, ...patch };
6713
- if (patch.thinking)
6714
- merged.thinking = { ...existing.thinking, ...patch.thinking };
6715
- if (patch.performance)
6716
- merged.performance = { ...existing.performance, ...patch.performance };
6717
- if (patch.contextManagement)
6718
- merged.contextManagement = { ...existing.contextManagement, ...patch.contextManagement };
6719
- if (patch.agentFeatures)
6720
- merged.agentFeatures = { ...existing.agentFeatures, ...patch.agentFeatures };
6721
- if (patch.retry)
6722
- merged.retry = { ...existing.retry, ...patch.retry };
6723
- writeFileSync7(CONFIG_FILE, JSON.stringify(merged, null, 2));
6724
- }
6725
- function removeProjectConfigKeys(cwd, keys) {
6726
- const file = join8(cwd, ".soulforge", "config.json");
6727
- if (!existsSync8(file))
6728
- return;
6729
- try {
6730
- const existing = JSON.parse(readFileSync8(file, "utf-8"));
6731
- for (const k of keys)
6732
- delete existing[k];
6733
- writeFileSync7(file, JSON.stringify(existing, null, 2));
6734
- } catch {}
6735
- }
6736
- function removeGlobalConfigKeys(keys) {
6737
- if (!existsSync8(CONFIG_FILE))
6738
- return;
6739
- try {
6740
- const existing = JSON.parse(readFileSync8(CONFIG_FILE, "utf-8"));
6741
- for (const k of keys)
6742
- delete existing[k];
6743
- writeFileSync7(CONFIG_FILE, JSON.stringify(existing, null, 2));
6744
- } catch {}
6745
- }
6746
- function applyConfigPatch(base, patch) {
6747
- const result = { ...base, ...patch };
6748
- for (const key of NESTED_KEYS) {
6749
- const b = base[key];
6750
- const p = patch[key];
6751
- if (p && b && typeof b === "object" && typeof p === "object") {
6752
- result[key] = { ...b, ...p };
6753
- }
6754
- }
6755
- return result;
6756
- }
6757
- function stripConfigKeys(config, keys) {
6758
- const result = { ...config };
6759
- for (const k of keys)
6760
- delete result[k];
6761
- return result;
6762
- }
6763
- var CONFIG_DIR, CONFIG_FILE, DEFAULT_CONFIG, NESTED_KEYS;
6764
- var init_config2 = __esm(() => {
6765
- init_ensure_soulforge_dir();
6766
- init_errors();
6767
- CONFIG_DIR = join8(homedir7(), ".soulforge");
6768
- CONFIG_FILE = join8(CONFIG_DIR, "config.json");
6769
- DEFAULT_CONFIG = {
6770
- defaultModel: "none",
6771
- routerRules: [],
6772
- editor: {
6773
- command: "nvim",
6774
- args: []
6775
- },
6776
- theme: {
6777
- name: "dark",
6778
- transparent: true
6779
- },
6780
- nvimConfig: "default",
6781
- editorIntegration: {
6782
- diagnostics: true,
6783
- symbols: true,
6784
- hover: true,
6785
- references: true,
6786
- definition: true,
6787
- codeActions: true,
6788
- editorContext: true,
6789
- rename: true,
6790
- lspStatus: true,
6791
- format: true,
6792
- syncEditorOnEdit: false
6793
- },
6794
- codeExecution: true,
6795
- webSearch: true,
6796
- compaction: {
6797
- strategy: "v2",
6798
- triggerThreshold: 0.7,
6799
- resetThreshold: 0.4,
6800
- keepRecent: 4,
6801
- maxToolResults: 30,
6802
- llmExtraction: true
6803
- }
6804
- };
6805
- NESTED_KEYS = [
6806
- "editor",
6807
- "theme",
6808
- "editorIntegration",
6809
- "codeIntelligence",
6810
- "thinking",
6811
- "performance",
6812
- "contextManagement",
6813
- "agentFeatures",
6814
- "compaction",
6815
- "retry"
6816
- ];
6817
- });
6818
-
6819
7088
  // src/stores/workers.ts
6820
7089
  var exports_workers = {};
6821
7090
  __export(exports_workers, {
@@ -62231,7 +62500,7 @@ var package_default;
62231
62500
  var init_package = __esm(() => {
62232
62501
  package_default = {
62233
62502
  name: "@proxysoul/soulforge",
62234
- version: "2.15.1",
62503
+ version: "2.15.3",
62235
62504
  description: "Graph-powered code intelligence \u2014 multi-agent coding with codebase-aware AI",
62236
62505
  repository: {
62237
62506
  type: "git",
@@ -62680,6 +62949,74 @@ var init_version = __esm(() => {
62680
62949
  CURRENT_VERSION = _currentVersion;
62681
62950
  });
62682
62951
 
62952
+ // src/core/llm/compat-reasoning.ts
62953
+ function parseProvider(modelId) {
62954
+ const slash = modelId.indexOf("/");
62955
+ if (slash === -1)
62956
+ return { provider: "", model: modelId };
62957
+ return { provider: modelId.slice(0, slash), model: modelId.slice(slash + 1) };
62958
+ }
62959
+ function baseModel(modelId) {
62960
+ const slash = modelId.lastIndexOf("/");
62961
+ return (slash >= 0 ? modelId.slice(slash + 1) : modelId).toLowerCase();
62962
+ }
62963
+ function getCompatReasoningBody(modelId, config2) {
62964
+ const { provider } = parseProvider(modelId);
62965
+ if (!COMPAT_PROVIDERS.has(provider))
62966
+ return {};
62967
+ const base = baseModel(modelId);
62968
+ let effort = config2.performance?.compatReasoningEffort;
62969
+ if (provider === "groq") {
62970
+ const g = config2.performance?.groqReasoningEffort;
62971
+ if (g)
62972
+ effort = g === "off" ? "off" : g;
62973
+ }
62974
+ if (!effort || effort === "off") {
62975
+ const e = config2.performance?.effort;
62976
+ if (e && e !== "off") {
62977
+ effort = e === "max" ? "xhigh" : e;
62978
+ }
62979
+ }
62980
+ if (!effort || effort === "off")
62981
+ return {};
62982
+ const isClaude = base.startsWith("claude");
62983
+ if (isClaude && provider === "opencode-zen") {
62984
+ const explicitBudget = config2.thinking?.budgetTokens;
62985
+ const budget = explicitBudget ?? { low: 2048, medium: 5000, high: 1e4, xhigh: 20000 }[effort] ?? 5000;
62986
+ return { thinking: { type: "enabled", budget_tokens: budget } };
62987
+ }
62988
+ if (isClaude && provider !== "proxy") {
62989
+ return {};
62990
+ }
62991
+ if (provider === "groq" && /qwen3/.test(base)) {
62992
+ return { reasoning_effort: "default" };
62993
+ }
62994
+ const isDashscope = /qwen|glm-|kimi-/.test(base);
62995
+ const body2 = {
62996
+ reasoning_effort: effort,
62997
+ reasoning: { effort }
62998
+ };
62999
+ if (isDashscope)
63000
+ body2.enable_thinking = true;
63001
+ return body2;
63002
+ }
63003
+ var COMPAT_PROVIDERS;
63004
+ var init_compat_reasoning = __esm(() => {
63005
+ COMPAT_PROVIDERS = new Set([
63006
+ "deepseek",
63007
+ "groq",
63008
+ "fireworks",
63009
+ "minimax",
63010
+ "copilot",
63011
+ "github-models",
63012
+ "opencode-go",
63013
+ "opencode-zen",
63014
+ "lmstudio",
63015
+ "ollama",
63016
+ "proxy"
63017
+ ]);
63018
+ });
63019
+
62683
63020
  // src/core/llm/providers/copilot.ts
62684
63021
  async function exchangeToken(githubToken) {
62685
63022
  if (cachedBearer && Date.now() / 1000 < cachedBearer.expiresAt - 60) {
@@ -62736,7 +63073,8 @@ function detectInitiator(body2) {
62736
63073
  } catch {}
62737
63074
  return "user";
62738
63075
  }
62739
- function createCopilotFetch(githubToken) {
63076
+ function createCopilotFetch(githubToken, reasoningBody) {
63077
+ const injectReasoning = Object.keys(reasoningBody).length > 0;
62740
63078
  return async (url2, init2) => {
62741
63079
  let bearer;
62742
63080
  try {
@@ -62745,18 +63083,25 @@ function createCopilotFetch(githubToken) {
62745
63083
  invalidateBearer();
62746
63084
  bearer = await exchangeToken(githubToken);
62747
63085
  }
63086
+ let patchedBody = init2?.body;
63087
+ if (injectReasoning && typeof init2?.body === "string") {
63088
+ try {
63089
+ const parsed = JSON.parse(init2.body);
63090
+ patchedBody = JSON.stringify({ ...parsed, ...reasoningBody });
63091
+ } catch {}
63092
+ }
62748
63093
  const buildHeaders = (token) => {
62749
63094
  const h = new Headers(init2?.headers);
62750
63095
  h.set("Authorization", `Bearer ${token}`);
62751
63096
  h.set("X-Request-Id", crypto.randomUUID());
62752
- h.set("X-Initiator", detectInitiator(init2?.body));
63097
+ h.set("X-Initiator", detectInitiator(patchedBody));
62753
63098
  return h;
62754
63099
  };
62755
- const res = await fetch(url2, { ...init2, headers: buildHeaders(bearer) });
63100
+ const res = await fetch(url2, { ...init2, body: patchedBody, headers: buildHeaders(bearer) });
62756
63101
  if (res.status === 401) {
62757
63102
  invalidateBearer();
62758
63103
  const retryBearer = await exchangeToken(githubToken);
62759
- return fetch(url2, { ...init2, headers: buildHeaders(retryBearer) });
63104
+ return fetch(url2, { ...init2, body: patchedBody, headers: buildHeaders(retryBearer) });
62760
63105
  }
62761
63106
  return res;
62762
63107
  };
@@ -62773,7 +63118,8 @@ function assertChatCompletionsSupported(modelId) {
62773
63118
  function createCopilotModel(modelId) {
62774
63119
  assertChatCompletionsSupported(modelId);
62775
63120
  const githubToken = getGitHubToken();
62776
- const copilotFetch = createCopilotFetch(githubToken);
63121
+ const reasoningBody = getCompatReasoningBody(`copilot/${modelId}`, loadConfig());
63122
+ const copilotFetch = createCopilotFetch(githubToken, reasoningBody);
62777
63123
  const client = createOpenAI({
62778
63124
  baseURL: COPILOT_API,
62779
63125
  apiKey: "copilot",
@@ -62785,8 +63131,10 @@ function createCopilotModel(modelId) {
62785
63131
  var ENV_VAR = "COPILOT_API_KEY", COPILOT_API = "https://api.githubcopilot.com", TOKEN_EXCHANGE = "https://api.github.com/copilot_internal/v2/token", COPILOT_CHAT_VERSION = "0.26.7", COPILOT_API_VERSION = "2025-04-01", COPILOT_HEADERS, cachedBearer = null, bearerInflight = null, supportedEndpoints, copilot;
62786
63132
  var init_copilot = __esm(() => {
62787
63133
  init_dist8();
63134
+ init_config2();
62788
63135
  init_secrets();
62789
63136
  init_version();
63137
+ init_compat_reasoning();
62790
63138
  COPILOT_HEADERS = {
62791
63139
  "Editor-Version": "vscode/1.95.0",
62792
63140
  "Editor-Plugin-Version": `copilot-chat/${COPILOT_CHAT_VERSION}`,
@@ -64417,27 +64765,21 @@ var init_dist9 = __esm(() => {
64417
64765
  });
64418
64766
  });
64419
64767
 
64420
- // src/core/llm/providers/custom.ts
64421
- function normalizeModels(models) {
64422
- if (!models || models.length === 0)
64423
- return [];
64424
- return models.map((m) => typeof m === "string" ? { id: m, name: m } : m);
64425
- }
64426
- function buildReasoningBody(reasoning) {
64427
- if (!reasoning)
64428
- return {};
64768
+ // src/core/llm/providers/reasoning-fetch.ts
64769
+ function buildOpenAICompatReasoningBody(effort, extras) {
64429
64770
  const body2 = {};
64430
- if (reasoning.effort) {
64431
- body2.reasoning = { effort: reasoning.effort };
64771
+ if (effort && effort !== "off") {
64772
+ body2.reasoning_effort = effort;
64773
+ body2.reasoning = { effort };
64432
64774
  }
64433
- if (reasoning.enabled !== undefined) {
64434
- body2.enable_thinking = reasoning.enabled;
64775
+ if (extras?.enabled !== undefined) {
64776
+ body2.enable_thinking = extras.enabled;
64435
64777
  }
64436
- if (reasoning.budget !== undefined) {
64437
- body2.thinking_budget = reasoning.budget;
64778
+ if (extras?.budget !== undefined) {
64779
+ body2.thinking_budget = extras.budget;
64438
64780
  }
64439
- if (reasoning.extraParams) {
64440
- Object.assign(body2, reasoning.extraParams);
64781
+ if (extras?.extraParams) {
64782
+ Object.assign(body2, extras.extraParams);
64441
64783
  }
64442
64784
  return body2;
64443
64785
  }
@@ -64460,6 +64802,22 @@ function createReasoningFetchWrapper(reasoningBody) {
64460
64802
  return fetch(input, patchedInit);
64461
64803
  };
64462
64804
  }
64805
+
64806
+ // src/core/llm/providers/custom.ts
64807
+ function normalizeModels(models) {
64808
+ if (!models || models.length === 0)
64809
+ return [];
64810
+ return models.map((m) => typeof m === "string" ? { id: m, name: m } : m);
64811
+ }
64812
+ function buildReasoningBody(reasoning) {
64813
+ if (!reasoning)
64814
+ return {};
64815
+ return buildOpenAICompatReasoningBody(reasoning.effort, {
64816
+ enabled: reasoning.enabled,
64817
+ budget: reasoning.budget,
64818
+ extraParams: reasoning.extraParams
64819
+ });
64820
+ }
64463
64821
  function buildCustomProvider(config2) {
64464
64822
  const envVar = config2.envVar ?? "";
64465
64823
  const reasoningBody = buildReasoningBody(config2.reasoning);
@@ -64523,7 +64881,9 @@ var init_custom = __esm(() => {
64523
64881
  var deepseek;
64524
64882
  var init_deepseek = __esm(() => {
64525
64883
  init_dist9();
64884
+ init_config2();
64526
64885
  init_secrets();
64886
+ init_compat_reasoning();
64527
64887
  deepseek = {
64528
64888
  id: "deepseek",
64529
64889
  name: "DeepSeek",
@@ -64538,10 +64898,13 @@ var init_deepseek = __esm(() => {
64538
64898
  if (!apiKey) {
64539
64899
  throw new Error("DEEPSEEK_API_KEY is not set");
64540
64900
  }
64901
+ const reasoningBody = getCompatReasoningBody(`deepseek/${modelId}`, loadConfig());
64902
+ const reasoningFetch = createReasoningFetchWrapper(reasoningBody);
64541
64903
  const provider = createOpenAICompatible({
64542
64904
  name: "deepseek",
64543
64905
  baseURL: "https://api.deepseek.com/v1",
64544
- apiKey
64906
+ apiKey,
64907
+ ...reasoningFetch ? { fetch: reasoningFetch } : {}
64545
64908
  });
64546
64909
  return provider.chatModel(modelId);
64547
64910
  },
@@ -64892,7 +65255,9 @@ var init_dist10 = __esm(() => {
64892
65255
  var fireworks2;
64893
65256
  var init_fireworks = __esm(() => {
64894
65257
  init_dist10();
65258
+ init_config2();
64895
65259
  init_secrets();
65260
+ init_compat_reasoning();
64896
65261
  fireworks2 = {
64897
65262
  id: "fireworks",
64898
65263
  name: "Fireworks",
@@ -64907,7 +65272,12 @@ var init_fireworks = __esm(() => {
64907
65272
  if (!apiKey) {
64908
65273
  throw new Error("FIREWORKS_API_KEY is not set");
64909
65274
  }
64910
- return createFireworks({ apiKey })(modelId);
65275
+ const reasoningBody = getCompatReasoningBody(`fireworks/${modelId}`, loadConfig());
65276
+ const reasoningFetch = createReasoningFetchWrapper(reasoningBody);
65277
+ return createFireworks({
65278
+ apiKey,
65279
+ ...reasoningFetch ? { fetch: reasoningFetch } : {}
65280
+ })(modelId);
64911
65281
  },
64912
65282
  async fetchModels() {
64913
65283
  const apiKey = getProviderApiKey("FIREWORKS_API_KEY");
@@ -64947,7 +65317,9 @@ var init_fireworks = __esm(() => {
64947
65317
  var ENV_VAR2 = "GITHUB_MODELS_API_KEY", BASE_URL = "https://models.github.ai/inference", CATALOG_URL = "https://models.github.ai/catalog/models", GH_HEADERS, githubModels;
64948
65318
  var init_github_models = __esm(() => {
64949
65319
  init_dist8();
65320
+ init_config2();
64950
65321
  init_secrets();
65322
+ init_compat_reasoning();
64951
65323
  GH_HEADERS = {
64952
65324
  "X-GitHub-Api-Version": "2026-03-10",
64953
65325
  Accept: "application/vnd.github+json"
@@ -64966,7 +65338,14 @@ var init_github_models = __esm(() => {
64966
65338
  if (!apiKey) {
64967
65339
  throw new Error(`${ENV_VAR2} is not set. Create a fine-grained PAT with models:read at github.com/settings/tokens`);
64968
65340
  }
64969
- return createOpenAI({ baseURL: BASE_URL, apiKey, headers: GH_HEADERS }).chat(modelId);
65341
+ const reasoningBody = getCompatReasoningBody(`github-models/${modelId}`, loadConfig());
65342
+ const reasoningFetch = createReasoningFetchWrapper(reasoningBody);
65343
+ return createOpenAI({
65344
+ baseURL: BASE_URL,
65345
+ apiKey,
65346
+ headers: GH_HEADERS,
65347
+ ...reasoningFetch ? { fetch: reasoningFetch } : {}
65348
+ }).chat(modelId);
64970
65349
  },
64971
65350
  async fetchModels() {
64972
65351
  const apiKey = getProviderApiKey(ENV_VAR2);
@@ -68637,7 +69016,9 @@ var init_dist12 = __esm(() => {
68637
69016
  var groq2;
68638
69017
  var init_groq = __esm(() => {
68639
69018
  init_dist12();
69019
+ init_config2();
68640
69020
  init_secrets();
69021
+ init_compat_reasoning();
68641
69022
  groq2 = {
68642
69023
  id: "groq",
68643
69024
  name: "Groq",
@@ -68652,7 +69033,12 @@ var init_groq = __esm(() => {
68652
69033
  if (!apiKey) {
68653
69034
  throw new Error("GROQ_API_KEY is not set");
68654
69035
  }
68655
- return createGroq({ apiKey })(modelId);
69036
+ const reasoningBody = getCompatReasoningBody(`groq/${modelId}`, loadConfig());
69037
+ const reasoningFetch = createReasoningFetchWrapper(reasoningBody);
69038
+ return createGroq({
69039
+ apiKey,
69040
+ ...reasoningFetch ? { fetch: reasoningFetch } : {}
69041
+ })(modelId);
68656
69042
  },
68657
69043
  async fetchModels() {
68658
69044
  const apiKey = getProviderApiKey("GROQ_API_KEY");
@@ -72257,6 +72643,8 @@ function authHeaders() {
72257
72643
  var lmstudio;
72258
72644
  var init_lmstudio = __esm(() => {
72259
72645
  init_dist8();
72646
+ init_config2();
72647
+ init_compat_reasoning();
72260
72648
  lmstudio = {
72261
72649
  id: "lmstudio",
72262
72650
  name: "LM Studio",
@@ -72266,9 +72654,12 @@ var init_lmstudio = __esm(() => {
72266
72654
  asciiIcon: "L",
72267
72655
  description: "Local models via LM Studio \u2014 no key needed",
72268
72656
  createModel(modelId) {
72657
+ const reasoningBody = getCompatReasoningBody(`lmstudio/${modelId}`, loadConfig());
72658
+ const reasoningFetch = createReasoningFetchWrapper(reasoningBody);
72269
72659
  const client = createOpenAI({
72270
72660
  baseURL: openaiBase(),
72271
- apiKey: getApiToken()
72661
+ apiKey: getApiToken(),
72662
+ ...reasoningFetch ? { fetch: reasoningFetch } : {}
72272
72663
  });
72273
72664
  return client.chat(modelId);
72274
72665
  },
@@ -80526,7 +80917,9 @@ var init_dist19 = __esm(() => {
80526
80917
  var minimax2;
80527
80918
  var init_minimax = __esm(() => {
80528
80919
  init_dist19();
80920
+ init_config2();
80529
80921
  init_secrets();
80922
+ init_compat_reasoning();
80530
80923
  minimax2 = {
80531
80924
  id: "minimax",
80532
80925
  name: "MiniMax",
@@ -80541,7 +80934,12 @@ var init_minimax = __esm(() => {
80541
80934
  if (!apiKey) {
80542
80935
  throw new Error("MINIMAX_API_KEY is not set");
80543
80936
  }
80544
- return createMinimaxAnthropic({ apiKey })(modelId);
80937
+ const reasoningBody = getCompatReasoningBody(`minimax/${modelId}`, loadConfig());
80938
+ const reasoningFetch = createReasoningFetchWrapper(reasoningBody);
80939
+ return createMinimaxAnthropic({
80940
+ apiKey,
80941
+ ...reasoningFetch ? { fetch: reasoningFetch } : {}
80942
+ })(modelId);
80545
80943
  },
80546
80944
  async fetchModels() {
80547
80945
  return null;
@@ -81415,6 +81813,8 @@ function getOllamaHost() {
81415
81813
  var ollama;
81416
81814
  var init_ollama = __esm(() => {
81417
81815
  init_dist8();
81816
+ init_config2();
81817
+ init_compat_reasoning();
81418
81818
  ollama = {
81419
81819
  id: "ollama",
81420
81820
  name: "Ollama",
@@ -81423,9 +81823,12 @@ var init_ollama = __esm(() => {
81423
81823
  asciiIcon: "O",
81424
81824
  description: "Local models \u2014 no key needed",
81425
81825
  createModel(modelId) {
81826
+ const reasoningBody = getCompatReasoningBody(`ollama/${modelId}`, loadConfig());
81827
+ const reasoningFetch = createReasoningFetchWrapper(reasoningBody);
81426
81828
  const client = createOpenAI({
81427
81829
  baseURL: `${getOllamaHost()}/v1`,
81428
- apiKey: "ollama"
81830
+ apiKey: "ollama",
81831
+ ...reasoningFetch ? { fetch: reasoningFetch } : {}
81429
81832
  });
81430
81833
  return client.chat(modelId);
81431
81834
  },
@@ -81542,7 +81945,9 @@ var init_openai = __esm(() => {
81542
81945
  var BASE_URL2 = "https://opencode.ai/zen/go/v1", opencodeGo;
81543
81946
  var init_opencode_go = __esm(() => {
81544
81947
  init_dist9();
81948
+ init_config2();
81545
81949
  init_secrets();
81950
+ init_compat_reasoning();
81546
81951
  opencodeGo = {
81547
81952
  id: "opencode-go",
81548
81953
  name: "OpenCode Go",
@@ -81557,10 +81962,13 @@ var init_opencode_go = __esm(() => {
81557
81962
  if (!apiKey) {
81558
81963
  throw new Error("OPENCODE_GO_API_KEY is not set");
81559
81964
  }
81965
+ const reasoningBody = getCompatReasoningBody(`opencode-go/${modelId}`, loadConfig());
81966
+ const reasoningFetch = createReasoningFetchWrapper(reasoningBody);
81560
81967
  const provider = createOpenAICompatible({
81561
81968
  name: "opencode-go",
81562
81969
  baseURL: BASE_URL2,
81563
- apiKey
81970
+ apiKey,
81971
+ ...reasoningFetch ? { fetch: reasoningFetch } : {}
81564
81972
  });
81565
81973
  return provider.chatModel(modelId);
81566
81974
  },
@@ -81592,7 +82000,9 @@ var init_opencode_go = __esm(() => {
81592
82000
  var BASE_URL3 = "https://opencode.ai/zen/v1", opencodeZen;
81593
82001
  var init_opencode_zen = __esm(() => {
81594
82002
  init_dist9();
82003
+ init_config2();
81595
82004
  init_secrets();
82005
+ init_compat_reasoning();
81596
82006
  init_context_windows();
81597
82007
  opencodeZen = {
81598
82008
  id: "opencode-zen",
@@ -81609,10 +82019,13 @@ var init_opencode_zen = __esm(() => {
81609
82019
  if (!apiKey) {
81610
82020
  throw new Error("OPENCODE_ZEN_API_KEY is not set");
81611
82021
  }
82022
+ const reasoningBody = getCompatReasoningBody(`opencode-zen/${modelId}`, loadConfig());
82023
+ const reasoningFetch = createReasoningFetchWrapper(reasoningBody);
81612
82024
  const provider = createOpenAICompatible({
81613
82025
  name: "opencode-zen",
81614
82026
  baseURL: BASE_URL3,
81615
- apiKey
82027
+ apiKey,
82028
+ ...reasoningFetch ? { fetch: reasoningFetch } : {}
81616
82029
  });
81617
82030
  return provider.chatModel(modelId);
81618
82031
  },
@@ -87677,8 +88090,10 @@ var baseURL, proxy2;
87677
88090
  var init_proxy = __esm(() => {
87678
88091
  init_dist6();
87679
88092
  init_dist8();
88093
+ init_config2();
87680
88094
  init_key_resolver();
87681
88095
  init_lifecycle();
88096
+ init_compat_reasoning();
87682
88097
  init_context_windows();
87683
88098
  baseURL = process.env.PROXY_API_URL || "http://127.0.0.1:8317/v1";
87684
88099
  proxy2 = {
@@ -87693,7 +88108,13 @@ var init_proxy = __esm(() => {
87693
88108
  if (isAnthropicModel(modelId)) {
87694
88109
  return createAnthropic({ baseURL, apiKey })(modelId);
87695
88110
  }
87696
- return createOpenAI({ baseURL, apiKey }).chat(modelId);
88111
+ const reasoningBody = getCompatReasoningBody(`proxy/${modelId}`, loadConfig());
88112
+ const reasoningFetch = createReasoningFetchWrapper(reasoningBody);
88113
+ return createOpenAI({
88114
+ baseURL,
88115
+ apiKey,
88116
+ ...reasoningFetch ? { fetch: reasoningFetch } : {}
88117
+ }).chat(modelId);
87697
88118
  },
87698
88119
  async fetchModels() {
87699
88120
  return null;
@@ -91420,13 +91841,18 @@ function extractBaseModel(modelId) {
91420
91841
  return (slash >= 0 ? modelId.slice(slash + 1) : modelId).toLowerCase();
91421
91842
  }
91422
91843
  function getClaudeGen(model) {
91423
- if (!model.startsWith("claude"))
91844
+ let m = model;
91845
+ if (m.startsWith("us."))
91846
+ m = m.slice(3);
91847
+ if (m.startsWith("anthropic."))
91848
+ m = m.slice("anthropic.".length);
91849
+ if (!m.startsWith("claude"))
91424
91850
  return "non-claude";
91425
91851
  for (const p of LEGACY_PREFIXES) {
91426
- if (model.startsWith(p))
91852
+ if (m.startsWith(p))
91427
91853
  return "legacy";
91428
91854
  }
91429
- if (model.startsWith("claude-3.5") || model.startsWith("claude-3-5"))
91855
+ if (m.startsWith("claude-3.5") || m.startsWith("claude-3-5"))
91430
91856
  return "3.5";
91431
91857
  return "4+";
91432
91858
  }
@@ -91434,17 +91860,25 @@ function detectModelFamily(modelId) {
91434
91860
  const { provider } = parseModelId(modelId);
91435
91861
  if (provider === "anthropic")
91436
91862
  return "claude";
91437
- if (provider === "openai" || provider === "xai")
91863
+ if (provider === "openai")
91438
91864
  return "openai";
91865
+ if (provider === "xai")
91866
+ return "xai";
91439
91867
  if (provider === "google")
91440
91868
  return "google";
91869
+ if (provider === "deepseek")
91870
+ return "deepseek";
91441
91871
  const base = extractBaseModel(modelId);
91442
- if (base.startsWith("claude"))
91872
+ if (base.startsWith("claude") || base.startsWith("anthropic.claude"))
91443
91873
  return "claude";
91444
91874
  if (base.startsWith("gpt-") || base.startsWith("o1") || base.startsWith("o3") || base.startsWith("o4"))
91445
91875
  return "openai";
91446
91876
  if (base.startsWith("gemini"))
91447
91877
  return "google";
91878
+ if (base.startsWith("grok"))
91879
+ return "xai";
91880
+ if (base.startsWith("deepseek"))
91881
+ return "deepseek";
91448
91882
  const model = parseModelId(modelId).model;
91449
91883
  if (model.startsWith("anthropic/"))
91450
91884
  return "claude";
@@ -91452,88 +91886,96 @@ function detectModelFamily(modelId) {
91452
91886
  return "openai";
91453
91887
  if (model.startsWith("google/"))
91454
91888
  return "google";
91889
+ if (model.startsWith("x-ai/") || model.startsWith("xai/"))
91890
+ return "xai";
91891
+ if (model.startsWith("deepseek/"))
91892
+ return "deepseek";
91455
91893
  return "other";
91456
91894
  }
91457
91895
  function getModelCapabilities3(modelId) {
91458
91896
  const base = extractBaseModel(modelId);
91459
91897
  const family = detectModelFamily(modelId);
91898
+ const BASE = {
91899
+ provider: "other",
91900
+ thinking: false,
91901
+ adaptiveThinking: false,
91902
+ effort: false,
91903
+ speed: false,
91904
+ contextManagement: false,
91905
+ interleavedThinking: false,
91906
+ openaiReasoning: false,
91907
+ openaiServiceTier: false,
91908
+ googleThinking: false,
91909
+ googleThinkingLevel: false,
91910
+ xaiReasoning: false,
91911
+ deepseekThinking: false,
91912
+ openrouterReasoning: false,
91913
+ compatReasoning: false
91914
+ };
91460
91915
  if (family === "openai") {
91461
91916
  const isReasoning = base.startsWith("o1") || base.startsWith("o3") || base.startsWith("o4") || base.startsWith("gpt-5");
91462
91917
  return {
91918
+ ...BASE,
91463
91919
  provider: "openai",
91464
- thinking: false,
91465
- adaptiveThinking: false,
91466
- effort: false,
91467
- speed: false,
91468
- contextManagement: false,
91469
- interleavedThinking: false,
91470
91920
  openaiReasoning: isReasoning,
91471
91921
  openaiServiceTier: true
91472
91922
  };
91473
91923
  }
91474
91924
  if (family === "google") {
91925
+ const isGemini3 = /gemini-3(\.|-|$)/.test(base);
91926
+ const isGemini25 = /gemini-2\.5/.test(base);
91927
+ const supportsThinking = isGemini3 || isGemini25;
91475
91928
  return {
91929
+ ...BASE,
91476
91930
  provider: "google",
91477
- thinking: false,
91478
- adaptiveThinking: false,
91479
- effort: false,
91480
- speed: false,
91481
- contextManagement: false,
91482
- interleavedThinking: false,
91483
- openaiReasoning: false,
91484
- openaiServiceTier: false
91931
+ googleThinking: supportsThinking,
91932
+ googleThinkingLevel: isGemini3
91933
+ };
91934
+ }
91935
+ if (family === "xai") {
91936
+ const isReasoning = base.startsWith("grok-3-mini") || base.startsWith("grok-4") || base.includes("reasoning");
91937
+ return {
91938
+ ...BASE,
91939
+ provider: "xai",
91940
+ xaiReasoning: isReasoning
91941
+ };
91942
+ }
91943
+ if (family === "deepseek") {
91944
+ const isChat = base === "deepseek-chat" || base.startsWith("deepseek-v3");
91945
+ return {
91946
+ ...BASE,
91947
+ provider: "deepseek",
91948
+ deepseekThinking: isChat,
91949
+ compatReasoning: true
91485
91950
  };
91486
91951
  }
91487
91952
  if (family !== "claude") {
91953
+ const isCompatReasoning = /qwen3/.test(base) || /glm-(4\.[5-9]|[5-9])/.test(base) || /kimi-(k2|thinking)/.test(base) || /gpt-oss/.test(base) || /deepseek-r1/.test(base) || /minimax-m[2-9]/.test(base);
91488
91954
  return {
91489
- provider: "other",
91490
- thinking: false,
91491
- adaptiveThinking: false,
91492
- effort: false,
91493
- speed: false,
91494
- contextManagement: false,
91495
- interleavedThinking: false,
91496
- openaiReasoning: false,
91497
- openaiServiceTier: false
91955
+ ...BASE,
91956
+ compatReasoning: isCompatReasoning
91498
91957
  };
91499
91958
  }
91500
91959
  const gen = getClaudeGen(base);
91501
91960
  if (gen === "legacy") {
91502
- return {
91503
- provider: "anthropic",
91504
- thinking: false,
91505
- adaptiveThinking: false,
91506
- effort: false,
91507
- speed: false,
91508
- contextManagement: false,
91509
- interleavedThinking: false,
91510
- openaiReasoning: false,
91511
- openaiServiceTier: false
91512
- };
91961
+ return { ...BASE, provider: "anthropic" };
91513
91962
  }
91514
91963
  if (gen === "3.5") {
91515
91964
  return {
91965
+ ...BASE,
91516
91966
  provider: "anthropic",
91517
- thinking: true,
91518
- adaptiveThinking: false,
91519
- effort: false,
91520
- speed: false,
91521
- contextManagement: false,
91522
- interleavedThinking: false,
91523
- openaiReasoning: false,
91524
- openaiServiceTier: false
91967
+ thinking: true
91525
91968
  };
91526
91969
  }
91527
91970
  return {
91971
+ ...BASE,
91528
91972
  provider: "anthropic",
91529
91973
  thinking: true,
91530
91974
  adaptiveThinking: true,
91531
91975
  effort: !base.includes("haiku"),
91532
91976
  speed: base.includes("opus"),
91533
91977
  contextManagement: !base.includes("haiku"),
91534
- interleavedThinking: true,
91535
- openaiReasoning: false,
91536
- openaiServiceTier: false
91978
+ interleavedThinking: true
91537
91979
  };
91538
91980
  }
91539
91981
  function getProviderConstraints(providerId) {
@@ -91555,6 +91997,12 @@ function getEffectiveCaps(modelId) {
91555
91997
  ...model,
91556
91998
  anthropicOptions: pc.anthropicOptions && family === "claude",
91557
91999
  openaiOptions: pc.openaiOptions && family === "openai",
92000
+ googleOptions: pc.googleOptions && family === "google" && model.googleThinking,
92001
+ xaiOptions: pc.xaiOptions && family === "xai" && model.xaiReasoning,
92002
+ deepseekOptions: pc.deepseekOptions && family === "deepseek" && model.deepseekThinking,
92003
+ openrouterOptions: pc.openrouterOptions,
92004
+ bedrockOptions: pc.bedrockOptions && family === "claude",
92005
+ compatReasoningBody: pc.compatReasoningBody && (model.compatReasoning || model.deepseekThinking),
91558
92006
  adaptiveThinking: model.adaptiveThinking && pc.adaptiveThinking,
91559
92007
  effort: model.effort && pc.effort,
91560
92008
  speed: model.speed && pc.speed,
@@ -91657,9 +92105,6 @@ function getAnthropicToolVersions(modelId) {
91657
92105
  }
91658
92106
  return { computerUse, textEditor, programmaticToolCalling };
91659
92107
  }
91660
- function supportsAnthropicOptions(modelId) {
91661
- return getEffectiveCaps(modelId).anthropicOptions;
91662
- }
91663
92108
  function buildContextEdits(config2, contextWindow, thinkingEnabled) {
91664
92109
  const edits = [];
91665
92110
  if (config2?.clearThinking !== false && thinkingEnabled) {
@@ -91753,6 +92198,14 @@ function buildOpenAIOptions(caps, config2) {
91753
92198
  if (effort && effort !== "off") {
91754
92199
  opts.reasoningEffort = effort;
91755
92200
  }
92201
+ const summary = config2.performance?.openaiReasoningSummary;
92202
+ if (summary && summary !== "off") {
92203
+ opts.reasoningSummary = summary;
92204
+ }
92205
+ const verbosity = config2.performance?.openaiVerbosity;
92206
+ if (verbosity && verbosity !== "off") {
92207
+ opts.verbosity = verbosity;
92208
+ }
91756
92209
  }
91757
92210
  if (caps.openaiServiceTier) {
91758
92211
  const tier = config2.performance?.serviceTier;
@@ -91783,6 +92236,42 @@ async function buildProviderOptions(modelId, config2) {
91783
92236
  providerOptions.openai = result.opts;
91784
92237
  }
91785
92238
  }
92239
+ if (caps.googleOptions) {
92240
+ const result = buildGoogleOptions(modelId, caps, config2);
92241
+ if (Object.keys(result.opts).length > 0) {
92242
+ providerOptions.google = result.opts;
92243
+ }
92244
+ }
92245
+ if (caps.xaiOptions) {
92246
+ const result = buildXaiOptions(caps, config2);
92247
+ if (Object.keys(result.opts).length > 0) {
92248
+ providerOptions.xai = result.opts;
92249
+ }
92250
+ }
92251
+ if (caps.deepseekOptions) {
92252
+ const result = buildDeepseekOptions(caps, config2);
92253
+ if (Object.keys(result.opts).length > 0) {
92254
+ providerOptions.deepseek = result.opts;
92255
+ }
92256
+ }
92257
+ if (caps.openrouterOptions) {
92258
+ const result = buildOpenRouterOptions(caps, config2);
92259
+ if (Object.keys(result.opts).length > 0) {
92260
+ providerOptions.openrouter = result.opts;
92261
+ }
92262
+ }
92263
+ if (caps.bedrockOptions) {
92264
+ const result = buildBedrockOptions(modelId, caps, config2);
92265
+ if (Object.keys(result.opts).length > 0) {
92266
+ providerOptions.bedrock = result.opts;
92267
+ }
92268
+ }
92269
+ if (parseModelId(modelId).provider === "groq") {
92270
+ const result = buildGroqOptions(config2);
92271
+ if (Object.keys(result.opts).length > 0) {
92272
+ providerOptions.groq = result.opts;
92273
+ }
92274
+ }
91786
92275
  const { provider } = parseModelId(modelId);
91787
92276
  const customProvider2 = provider ? getProvider(provider) : null;
91788
92277
  if (customProvider2?.custom && customProvider2.customReasoning) {
@@ -91811,16 +92300,37 @@ async function buildProviderOptions(modelId, config2) {
91811
92300
  };
91812
92301
  }
91813
92302
  function degradeProviderOptions(modelId, level) {
91814
- if (level >= 2 || !supportsAnthropicOptions(modelId)) {
92303
+ if (level >= 2) {
91815
92304
  return { providerOptions: {}, headers: undefined, contextWindow: 0 };
91816
92305
  }
91817
- const caps = getModelCapabilities3(modelId);
91818
- const opts = {};
91819
- if (caps.thinking) {
91820
- opts.thinking = { type: "enabled", budgetTokens: 5000 };
92306
+ const caps = getEffectiveCaps(modelId);
92307
+ const providerOptions = {};
92308
+ if (caps.anthropicOptions && caps.thinking) {
92309
+ const opts = { thinking: { type: "enabled", budgetTokens: 5000 } };
92310
+ providerOptions.anthropic = opts;
92311
+ }
92312
+ if (caps.openaiOptions && caps.openaiReasoning) {
92313
+ providerOptions.openai = { reasoningEffort: "low" };
92314
+ }
92315
+ if (caps.googleOptions && caps.googleThinking) {
92316
+ providerOptions.google = caps.googleThinkingLevel ? { thinkingConfig: { thinkingLevel: "low" } } : { thinkingConfig: { thinkingBudget: 1024 } };
92317
+ }
92318
+ if (caps.xaiOptions && caps.xaiReasoning) {
92319
+ providerOptions.xai = { reasoningEffort: "low" };
92320
+ }
92321
+ if (caps.deepseekOptions && caps.deepseekThinking) {
92322
+ providerOptions.deepseek = { thinking: { type: "enabled" } };
92323
+ }
92324
+ if (caps.openrouterOptions) {
92325
+ providerOptions.openrouter = { reasoning: { effort: "low" } };
92326
+ }
92327
+ if (caps.bedrockOptions && caps.thinking) {
92328
+ providerOptions.bedrock = {
92329
+ reasoningConfig: { type: "enabled", budgetTokens: 5000 }
92330
+ };
91821
92331
  }
91822
92332
  return {
91823
- providerOptions: { anthropic: opts },
92333
+ providerOptions,
91824
92334
  headers: undefined,
91825
92335
  contextWindow: 0
91826
92336
  };
@@ -91830,13 +92340,167 @@ function isProviderOptionsError(error48) {
91830
92340
  const lower = msg.toLowerCase();
91831
92341
  return lower.includes("not supported") || lower.includes("not available") || lower.includes("does not support") || lower.includes("invalid parameter") || lower.includes("inputschema") || lower.includes("thinking is not supported") || lower.includes("adaptive thinking") || lower.includes("clear_thinking") || lower.includes("context management") || lower.includes("unknown parameter") || lower.includes("temperature is deprecated");
91832
92342
  }
91833
- var ANTHROPIC_FULL, OPENAI_FULL, GATEWAY_FULL, PROVIDER_CONSTRAINTS, NO_SUPPORT, LEGACY_PREFIXES, CACHE_EPHEMERAL, EPHEMERAL_CACHE;
92343
+ function buildGoogleOptions(modelId, caps, config2) {
92344
+ const opts = {};
92345
+ if (!caps.googleThinking)
92346
+ return { opts };
92347
+ const thinkingConfig = {};
92348
+ if (caps.googleThinkingLevel) {
92349
+ const level = config2.performance?.googleThinkingLevel;
92350
+ if (level && level !== "off") {
92351
+ thinkingConfig.thinkingLevel = level;
92352
+ } else if (config2.performance?.effort && config2.performance.effort !== "off") {
92353
+ const e = config2.performance.effort;
92354
+ const mapped = e === "max" || e === "xhigh" ? "high" : e === "high" || e === "medium" || e === "low" ? e : null;
92355
+ if (mapped)
92356
+ thinkingConfig.thinkingLevel = mapped;
92357
+ }
92358
+ } else {
92359
+ const budget = config2.performance?.googleThinkingBudget;
92360
+ if (typeof budget === "number") {
92361
+ thinkingConfig.thinkingBudget = budget;
92362
+ } else if (budget !== "off" && config2.performance?.effort && config2.performance.effort !== "off") {
92363
+ const map2 = {
92364
+ low: 1024,
92365
+ medium: 4096,
92366
+ high: 8192,
92367
+ xhigh: 16384,
92368
+ max: 24576
92369
+ };
92370
+ const v = map2[config2.performance.effort];
92371
+ if (v !== undefined)
92372
+ thinkingConfig.thinkingBudget = v;
92373
+ }
92374
+ }
92375
+ if (config2.performance?.googleIncludeThoughts) {
92376
+ thinkingConfig.includeThoughts = true;
92377
+ }
92378
+ if (Object.keys(thinkingConfig).length > 0) {
92379
+ opts.thinkingConfig = thinkingConfig;
92380
+ }
92381
+ return { opts };
92382
+ }
92383
+ function buildXaiOptions(caps, config2) {
92384
+ const opts = {};
92385
+ if (!caps.xaiReasoning)
92386
+ return { opts };
92387
+ const explicit = config2.performance?.xaiReasoningEffort;
92388
+ if (explicit && explicit !== "off") {
92389
+ opts.reasoningEffort = explicit === "medium" ? "high" : explicit;
92390
+ return { opts };
92391
+ }
92392
+ const e = config2.performance?.effort;
92393
+ if (e && e !== "off") {
92394
+ opts.reasoningEffort = e === "low" ? "low" : "high";
92395
+ }
92396
+ return { opts };
92397
+ }
92398
+ function buildDeepseekOptions(caps, config2) {
92399
+ const opts = {};
92400
+ if (!caps.deepseekThinking)
92401
+ return { opts };
92402
+ const explicit = config2.performance?.deepseekThinking;
92403
+ if (explicit === "enabled") {
92404
+ opts.thinking = { type: "enabled" };
92405
+ return { opts };
92406
+ }
92407
+ if (explicit === "off")
92408
+ return { opts };
92409
+ if (config2.performance?.effort && config2.performance.effort !== "off") {
92410
+ opts.thinking = { type: "enabled" };
92411
+ }
92412
+ return { opts };
92413
+ }
92414
+ function buildOpenRouterOptions(caps, config2) {
92415
+ const opts = {};
92416
+ if (!caps.openrouterOptions)
92417
+ return { opts };
92418
+ const reasoning = {};
92419
+ const explicitEffort = config2.performance?.openrouterReasoningEffort;
92420
+ const explicitMax = config2.performance?.openrouterReasoningMaxTokens;
92421
+ if (explicitMax !== undefined && explicitMax !== "off") {
92422
+ reasoning.max_tokens = explicitMax;
92423
+ } else if (explicitEffort && explicitEffort !== "off") {
92424
+ reasoning.effort = explicitEffort;
92425
+ } else if (config2.thinking?.mode === "enabled" && typeof config2.thinking?.budgetTokens === "number") {
92426
+ reasoning.max_tokens = config2.thinking.budgetTokens;
92427
+ } else {
92428
+ const e = config2.performance?.effort;
92429
+ if (e && e !== "off") {
92430
+ const mapped = {
92431
+ low: "low",
92432
+ medium: "medium",
92433
+ high: "high",
92434
+ xhigh: "xhigh",
92435
+ max: "xhigh"
92436
+ };
92437
+ const v = mapped[e];
92438
+ if (v)
92439
+ reasoning.effort = v;
92440
+ }
92441
+ }
92442
+ if (config2.performance?.openrouterExcludeReasoning) {
92443
+ reasoning.exclude = true;
92444
+ }
92445
+ if (Object.keys(reasoning).length > 0) {
92446
+ opts.reasoning = reasoning;
92447
+ }
92448
+ return { opts };
92449
+ }
92450
+ function buildBedrockOptions(modelId, caps, config2) {
92451
+ const opts = {};
92452
+ if (!caps.bedrockOptions)
92453
+ return { opts };
92454
+ const reasoningConfig = {};
92455
+ const mode = config2.thinking?.mode ?? "off";
92456
+ if (mode === "auto" || mode === "adaptive") {
92457
+ reasoningConfig.type = "adaptive";
92458
+ } else if (mode === "enabled") {
92459
+ reasoningConfig.type = "enabled";
92460
+ if (config2.thinking?.budgetTokens) {
92461
+ reasoningConfig.budgetTokens = config2.thinking.budgetTokens;
92462
+ }
92463
+ }
92464
+ if (caps.effort && config2.performance?.effort && config2.performance.effort !== "off") {
92465
+ const clamped = clampEffort(modelId, config2.performance.effort);
92466
+ if (clamped)
92467
+ reasoningConfig.maxReasoningEffort = clamped;
92468
+ }
92469
+ if (Object.keys(reasoningConfig).length > 0) {
92470
+ opts.reasoningConfig = reasoningConfig;
92471
+ }
92472
+ return { opts };
92473
+ }
92474
+ function buildGroqOptions(config2) {
92475
+ const opts = {};
92476
+ const fmt = config2.performance?.groqReasoningFormat;
92477
+ if (fmt && fmt !== "off") {
92478
+ opts.reasoningFormat = fmt;
92479
+ }
92480
+ return { opts };
92481
+ }
92482
+ var NO_SUPPORT, ANTHROPIC_FULL, OPENAI_FULL, GOOGLE_FULL, XAI_FULL, DEEPSEEK_FULL, OPENROUTER_FULL, GATEWAY_FULL, COMPAT_ONLY, PROVIDER_CONSTRAINTS, LEGACY_PREFIXES, CACHE_EPHEMERAL, EPHEMERAL_CACHE;
91834
92483
  var init_provider_options = __esm(() => {
91835
92484
  init_models();
91836
92485
  init_providers();
92486
+ NO_SUPPORT = {
92487
+ anthropicOptions: false,
92488
+ openaiOptions: false,
92489
+ googleOptions: false,
92490
+ xaiOptions: false,
92491
+ deepseekOptions: false,
92492
+ openrouterOptions: false,
92493
+ bedrockOptions: false,
92494
+ effort: false,
92495
+ speed: false,
92496
+ contextManagement: false,
92497
+ adaptiveThinking: false,
92498
+ interleavedThinking: false,
92499
+ compatReasoningBody: false
92500
+ };
91837
92501
  ANTHROPIC_FULL = {
92502
+ ...NO_SUPPORT,
91838
92503
  anthropicOptions: true,
91839
- openaiOptions: false,
91840
92504
  effort: true,
91841
92505
  speed: true,
91842
92506
  contextManagement: true,
@@ -91844,43 +92508,82 @@ var init_provider_options = __esm(() => {
91844
92508
  interleavedThinking: true
91845
92509
  };
91846
92510
  OPENAI_FULL = {
91847
- anthropicOptions: false,
92511
+ ...NO_SUPPORT,
92512
+ openaiOptions: true
92513
+ };
92514
+ GOOGLE_FULL = {
92515
+ ...NO_SUPPORT,
92516
+ googleOptions: true
92517
+ };
92518
+ XAI_FULL = {
92519
+ ...NO_SUPPORT,
92520
+ xaiOptions: true
92521
+ };
92522
+ DEEPSEEK_FULL = {
92523
+ ...NO_SUPPORT,
92524
+ deepseekOptions: true,
92525
+ compatReasoningBody: true
92526
+ };
92527
+ OPENROUTER_FULL = {
92528
+ ...NO_SUPPORT,
92529
+ openrouterOptions: true,
92530
+ anthropicOptions: true,
91848
92531
  openaiOptions: true,
91849
- effort: false,
91850
- speed: false,
91851
- contextManagement: false,
91852
- adaptiveThinking: false,
91853
- interleavedThinking: false
92532
+ googleOptions: true,
92533
+ xaiOptions: true,
92534
+ deepseekOptions: true,
92535
+ effort: true,
92536
+ speed: true,
92537
+ adaptiveThinking: true,
92538
+ interleavedThinking: true
91854
92539
  };
91855
92540
  GATEWAY_FULL = {
91856
92541
  anthropicOptions: true,
91857
92542
  openaiOptions: true,
92543
+ googleOptions: true,
92544
+ xaiOptions: true,
92545
+ deepseekOptions: true,
92546
+ openrouterOptions: false,
92547
+ bedrockOptions: false,
91858
92548
  effort: true,
91859
92549
  speed: true,
91860
92550
  contextManagement: true,
91861
92551
  adaptiveThinking: true,
91862
- interleavedThinking: true
92552
+ interleavedThinking: true,
92553
+ compatReasoningBody: false
92554
+ };
92555
+ COMPAT_ONLY = {
92556
+ ...NO_SUPPORT,
92557
+ compatReasoningBody: true
91863
92558
  };
91864
92559
  PROVIDER_CONSTRAINTS = {
91865
92560
  anthropic: ANTHROPIC_FULL,
91866
92561
  proxy: GATEWAY_FULL,
91867
92562
  openai: OPENAI_FULL,
91868
- xai: OPENAI_FULL,
92563
+ xai: XAI_FULL,
92564
+ google: GOOGLE_FULL,
92565
+ deepseek: DEEPSEEK_FULL,
92566
+ openrouter: OPENROUTER_FULL,
92567
+ groq: COMPAT_ONLY,
92568
+ fireworks: COMPAT_ONLY,
92569
+ minimax: COMPAT_ONLY,
92570
+ copilot: COMPAT_ONLY,
92571
+ "github-models": COMPAT_ONLY,
92572
+ "opencode-zen": { ...GATEWAY_FULL, compatReasoningBody: true },
92573
+ "opencode-go": COMPAT_ONLY,
92574
+ lmstudio: COMPAT_ONLY,
92575
+ ollama: COMPAT_ONLY,
91869
92576
  vercel_gateway: GATEWAY_FULL,
91870
92577
  llmgateway: GATEWAY_FULL,
91871
- opencode_zen: GATEWAY_FULL,
91872
- opencode_go: GATEWAY_FULL,
91873
- openrouter: GATEWAY_FULL,
91874
- bedrock: GATEWAY_FULL
91875
- };
91876
- NO_SUPPORT = {
91877
- anthropicOptions: false,
91878
- openaiOptions: false,
91879
- effort: false,
91880
- speed: false,
91881
- contextManagement: false,
91882
- adaptiveThinking: false,
91883
- interleavedThinking: false
92578
+ bedrock: {
92579
+ ...NO_SUPPORT,
92580
+ bedrockOptions: true,
92581
+ effort: true,
92582
+ speed: false,
92583
+ contextManagement: false,
92584
+ adaptiveThinking: true,
92585
+ interleavedThinking: false
92586
+ }
91884
92587
  };
91885
92588
  LEGACY_PREFIXES = [
91886
92589
  "claude-3-haiku",
@@ -406659,7 +407362,11 @@ For non-TS/JS files (JSON, YAML, Markdown, config) or raw text outside any symbo
406659
407362
 
406660
407363
  Recall fires automatically before each user turn \u2014 prompt + edited files \u2192 top-3 relevant memories injected as <recalled_memories> stubs (summary + id + signals + "\u21B3 has details" marker), \u2264600 chars typical. Cached, deduped, never re-injected in one session. When a stub's "\u21B3 has details" marker matters to the current task, call memory(action:"get", id:<8-char prefix>) to read the full body.
406661
407364
 
406662
- Auto-recall is signal-driven and misses generic or single-word prompts. Before any action where convention matters and nothing was surfaced \u2014 first commit of a session, adopting a framework/tool, changing config layout, writing tests in an unfamiliar area, picking a naming style \u2014 run memory(action:"search", query:<topic>) once. Cheap, deterministic, beats guessing. WRITE proactively, SEARCH when convention matters and nothing was recalled.
407365
+ Auto-recall is signal-driven and misses generic or single-word prompts. Before any action where convention matters and nothing was surfaced \u2014 first commit of a session, adopting a framework/tool, changing config layout, writing tests in an unfamiliar area, picking a naming style \u2014 run memory(action:"search", query:<topic>) once. Cheap, deterministic, beats guessing.
407366
+
407367
+ SEARCH KEYWORDS \u2014 fall back to memory(search) when about to do any of these and recall was empty: commit message shape, lint/format choice, test framework conventions, package manager (bun/npm/pnpm/yarn), import style, file naming, error-handling pattern, logger choice, state-management library, dispatch/agent setup, prompt-engineering rules. One search beats one wrong guess.
407368
+
407369
+ WRITE proactively, SEARCH when convention matters and nothing was recalled.
406663
407370
 
406664
407371
  WHY WRITES MATTER \u2014 the system multiplies them:
406665
407372
  - Soul Map stable file_id \u2192 memory on \`src/jwt.ts\` survives renames and refactors.
@@ -406670,6 +407377,13 @@ WHY WRITES MATTER \u2014 the system multiplies them:
406670
407377
 
406671
407378
  WHEN TO WRITE \u2014 the three triggers (fire on ANY of these, not just user-prompted ones):
406672
407379
  1. USER STATES A PREFERENCE OR DIRECTIVE. "use bun not npm", "be terse", "always run tests after edits" \u2192 pref. Write immediately, scope:"global" if it's not project-specific.
407380
+ INFER FROM CUES \u2014 don't wait for the word "remember". A preference exists whenever the user's correction or instruction implies a standing rule, not a one-shot fix. Cues that mean "this is durable":
407381
+ \u2022 Corrective tone about HOW you did something ("be more concise", "stop narrating", "use bullets") \u2014 the correction itself IS the rule.
407382
+ \u2022 Generalising language: "always", "never", "from now on", "by default", "prefer", "in this repo we\u2026", "we don't\u2026".
407383
+ \u2022 Imperative meta-instructions about workflow, style, tooling, formatting, naming \u2014 anything orthogonal to the current task.
407384
+ \u2022 User repeats or rephrases the same correction \u2192 it's a rule you missed last time. Write it now.
407385
+ \u2022 User asks "why didn't you\u2026?" about a behavior \u2014 they had an expectation. Capture the expectation.
407386
+ The literal word "remember" is just one cue among many. The test: "Would future-me want this surfaced next session?" If yes \u2192 write. Mid-instruction corrections ("commit it, and be concise") split into two acts: do the task, write the rule.
406673
407387
  2. A CHOICE GETS MADE WITH A REASON. "switching to zustand because redux is too much boilerplate", "postgres not mysql for the JSON ops" \u2192 decision. The WHY is what future you needs (the Soul Map shows the WHAT). Capture the rationale in details.
406674
407388
  3. SHARP-EDGE DISCOVERED. Bug that took >5min to diagnose, non-obvious quirk, "don't touch X because Y", a workaround for a flaky test \u2192 gotcha. Include the symptom + the fix location.
406675
407389
 
@@ -406731,7 +407445,7 @@ Use dedicated tools over shell for file reads, searches, definitions, and edits.
406731
407445
  For TS/JS (.ts/.tsx/.js/.jsx/.mts/.cts/.mjs/.cjs): \`ast_edit\` is the default \u2014 ts-morph locates symbols by {target, name}, no oldString/line drift. Use \`edit_file\`/\`multi_edit\` only for non-TS/JS or raw text outside any symbol (always pass \`lineStart\` from read output).
406732
407446
  Batch independent tool calls in one parallel block. Use the \`git\` tool for git, \`soul_vision\` for images.
406733
407447
 
406734
- \`memory\` is your across-session brain \u2014 auto-recall fires before each user turn (top-3 stubs, \u2264600 chars typical; call memory(get, id) to read full body when "\u21B3 has details" matters). Use it like a primary tool, not a last resort: every write earns its keep when a future ambiguous prompt triggers the right recall. WRITE on (1) user preference/directive \u2192 pref, (2) choice with rationale \u2192 decision, (3) sharp edge that took effort to find \u2192 gotcha. Always set \`file_paths\` for file-scoped memories \u2014 strongest recall signal, co-change-aware. On similar_hints (\u226585% cosine), \`get\` the existing entry; refinement \u2192 merge_topics:true, contradiction \u2192 supersede. On recall conflict with the current request, raise it before acting. Soft-delete only; \u22643 surfaced per turn hard cap means a bad write won't poison context.
407448
+ \`memory\` is your across-session brain \u2014 auto-recall fires before each user turn (top-3 stubs, \u2264600 chars typical; call memory(get, id) to read full body when "\u21B3 has details" matters). Use it like a primary tool, not a last resort: every write earns its keep when a future ambiguous prompt triggers the right recall. WRITE on (1) user preference/directive \u2192 pref. Infer from cues, don't wait for "remember": corrective tone about HOW you worked ("be terse", "stop narrating"), generalising language ("always/never/by default/we don't"), repeated corrections, or "why didn't you\u2026?" questions all signal a standing rule. Mid-instruction corrections ("commit it, and be concise") split into two acts: do the task, write the rule. (2) choice with rationale \u2192 decision, (3) sharp edge that took effort to find \u2192 gotcha. SEARCH fallback: when about to commit, pick a framework/lib, name a file, or apply any convention and recall was empty, run memory(search, query) once before guessing. Always set \`file_paths\` for file-scoped memories \u2014 strongest recall signal, co-change-aware. On similar_hints (\u226585% cosine), \`get\` the existing entry; refinement \u2192 merge_topics:true, contradiction \u2192 supersede. On recall conflict with the current request, raise it before acting. Soft-delete only; \u22643 surfaced per turn hard cap means a bad write won't poison context.
406735
407449
  </tool_usage>`;
406736
407450
 
406737
407451
  // src/core/prompts/shared/index.ts
@@ -406780,6 +407494,8 @@ var init_builder = __esm(() => {
406780
407494
  claude: CLAUDE_PROMPT,
406781
407495
  openai: OPENAI_PROMPT,
406782
407496
  google: GOOGLE_PROMPT,
407497
+ xai: OPENAI_PROMPT,
407498
+ deepseek: OPENAI_PROMPT,
406783
407499
  other: DEFAULT_PROMPT
406784
407500
  };
406785
407501
  });
@@ -429107,6 +429823,11 @@ class HearthDaemon {
429107
429823
  return;
429108
429824
  const ws = this.workspaces.get(surfaceId, msg.externalId);
429109
429825
  const hasBridge = hearthBridge.getBinding(surfaceId, msg.externalId) !== null;
429826
+ if (SETTINGS_COMMAND_NAMES.includes(cmd.name)) {
429827
+ const handled = await handleSettingsCommand(cmd.name, cmd.args, (text3) => surface.notify(msg.externalId, text3));
429828
+ if (handled)
429829
+ return;
429830
+ }
429110
429831
  if (hasBridge) {
429111
429832
  switch (cmd.name) {
429112
429833
  case "/tabs":
@@ -429499,6 +430220,9 @@ class HearthDaemon {
429499
430220
  "\u2501\u2501 Pairing \u2501\u2501",
429500
430221
  "/pair [CODE] pair this chat",
429501
430222
  "/unpair revoke",
430223
+ "",
430224
+ ...settingsHelpLines(),
430225
+ "",
429502
430226
  "/help this list"
429503
430227
  ].join(`
429504
430228
  `));
@@ -429714,6 +430438,7 @@ var init_daemon = __esm(() => {
429714
430438
  init_pairing();
429715
430439
  init_peer_auth();
429716
430440
  init_protocol();
430441
+ init_provider_commands();
429717
430442
  init_redact();
429718
430443
  init_surface_host();
429719
430444
  init_workspace();
@@ -477249,24 +477974,170 @@ Reopen the editor (Ctrl+E twice) for changes to take effect.`);
477249
477974
  }
477250
477975
  function handleTimeouts(_input, ctx) {
477251
477976
  const cfg = loadConfig();
477252
- const current = cfg.toolTimeout ?? 2;
477253
- const options = [
477254
- { value: "1", label: "1 min" },
477255
- { value: "2", label: "2 min", description: "default" },
477256
- { value: "5", label: "5 min" },
477257
- { value: "10", label: "10 min" },
477258
- { value: "20", label: "20 min" },
477259
- { value: "0", label: "No timeout", description: "tools run until completion" }
477260
- ];
477977
+ const currentToolTimeout = cfg.toolTimeout ?? 2;
477978
+ const watchdogEnabled = cfg.watchdog ?? false;
477979
+ const wd = cfg.watchdogTimeouts ?? {};
477980
+ const currentTool = `tool:${currentToolTimeout}`;
477981
+ const wdFirstSec = (wd.firstChunkMs ?? 180000) / 1000;
477982
+ const wdChunkSec = (wd.chunkMs ?? 120000) / 1000;
477983
+ const wdToolSec = (wd.toolMaxMs ?? 900000) / 1000;
477984
+ const wdForceSec = (wd.forceResolveMs ?? 5000) / 1000;
477985
+ const wdFirst = `wd-first:${wdFirstSec}`;
477986
+ const wdChunk = `wd-chunk:${wdChunkSec}`;
477987
+ const wdTool = `wd-tool:${wdToolSec}`;
477988
+ const wdForce = `wd-force:${wdForceSec}`;
477989
+ const timeoutPickers = {
477990
+ "tool-timeout": (_input2, ctx2) => {
477991
+ ctx2.openCommandPicker({
477992
+ title: "Tool Timeout",
477993
+ icon: icon("clock"),
477994
+ currentValue: currentTool,
477995
+ scopeEnabled: false,
477996
+ options: [
477997
+ { value: "tool:1", label: "1 min" },
477998
+ { value: "tool:2", label: "2 min", description: "default" },
477999
+ { value: "tool:5", label: "5 min" },
478000
+ { value: "tool:10", label: "10 min" },
478001
+ { value: "tool:20", label: "20 min" },
478002
+ { value: "tool:0", label: "No timeout", description: "tools run until completion" }
478003
+ ],
478004
+ onSelect: (value) => {
478005
+ const timeout = Number(value.split(":")[1]);
478006
+ ctx2.saveToScope({ toolTimeout: timeout }, "global");
478007
+ sysMsg(ctx2, `Tool timeout \u2192 ${timeout === 0 ? "none" : `${timeout}m`} (global)`);
478008
+ }
478009
+ });
478010
+ },
478011
+ "watchdog-toggle": (_input2, ctx2) => {
478012
+ ctx2.openCommandPicker({
478013
+ title: "Watchdog",
478014
+ icon: icon("clock"),
478015
+ currentValue: watchdogEnabled ? "watchdog:on" : "watchdog:off",
478016
+ scopeEnabled: false,
478017
+ options: [
478018
+ {
478019
+ value: "watchdog:on",
478020
+ label: "Watchdog: On",
478021
+ description: "enable auto-retry on stalls"
478022
+ },
478023
+ {
478024
+ value: "watchdog:off",
478025
+ label: "Watchdog: Off",
478026
+ description: "disable auto-retry on stalls"
478027
+ }
478028
+ ],
478029
+ onSelect: (value) => {
478030
+ const enabled = value === "watchdog:on";
478031
+ ctx2.saveToScope({ watchdog: enabled }, "global");
478032
+ sysMsg(ctx2, `Watchdog ${enabled ? "enabled" : "disabled"} (global)`);
478033
+ }
478034
+ });
478035
+ },
478036
+ "wd-first": (_input2, ctx2) => {
478037
+ ctx2.openCommandPicker({
478038
+ title: "Watchdog \u2014 First Chunk Timeout",
478039
+ icon: icon("clock"),
478040
+ currentValue: wdFirst,
478041
+ scopeEnabled: false,
478042
+ options: [
478043
+ { value: "wd-first:5", label: "5s" },
478044
+ { value: "wd-first:15", label: "15s" },
478045
+ { value: "wd-first:30", label: "30s" },
478046
+ { value: "wd-first:60", label: "60s" },
478047
+ { value: "wd-first:120", label: "120s" },
478048
+ { value: "wd-first:180", label: "180s", description: "default" }
478049
+ ],
478050
+ onSelect: (value) => {
478051
+ const sec = Number(value.split(":")[1]);
478052
+ ctx2.saveToScope({ watchdogTimeouts: { ...wd, firstChunkMs: sec * 1000 } }, "global");
478053
+ sysMsg(ctx2, `Watchdog first-chunk timeout \u2192 ${sec}s (global)`);
478054
+ }
478055
+ });
478056
+ },
478057
+ "wd-chunk": (_input2, ctx2) => {
478058
+ ctx2.openCommandPicker({
478059
+ title: "Watchdog \u2014 Chunk Timeout",
478060
+ icon: icon("clock"),
478061
+ currentValue: wdChunk,
478062
+ scopeEnabled: false,
478063
+ options: [
478064
+ { value: "wd-chunk:5", label: "5s" },
478065
+ { value: "wd-chunk:15", label: "15s" },
478066
+ { value: "wd-chunk:30", label: "30s" },
478067
+ { value: "wd-chunk:60", label: "60s" },
478068
+ { value: "wd-chunk:120", label: "120s", description: "default" },
478069
+ { value: "wd-chunk:180", label: "180s" }
478070
+ ],
478071
+ onSelect: (value) => {
478072
+ const sec = Number(value.split(":")[1]);
478073
+ ctx2.saveToScope({ watchdogTimeouts: { ...wd, chunkMs: sec * 1000 } }, "global");
478074
+ sysMsg(ctx2, `Watchdog chunk timeout \u2192 ${sec}s (global)`);
478075
+ }
478076
+ });
478077
+ },
478078
+ "wd-tool": (_input2, ctx2) => {
478079
+ ctx2.openCommandPicker({
478080
+ title: "Watchdog \u2014 Tool Max Timeout",
478081
+ icon: icon("clock"),
478082
+ currentValue: wdTool,
478083
+ scopeEnabled: false,
478084
+ options: [
478085
+ { value: "wd-tool:60", label: "1 min" },
478086
+ { value: "wd-tool:300", label: "5 min" },
478087
+ { value: "wd-tool:600", label: "10 min" },
478088
+ { value: "wd-tool:900", label: "15 min", description: "default" },
478089
+ { value: "wd-tool:1800", label: "30 min" },
478090
+ { value: "wd-tool:3600", label: "60 min" }
478091
+ ],
478092
+ onSelect: (value) => {
478093
+ const sec = Number(value.split(":")[1]);
478094
+ ctx2.saveToScope({ watchdogTimeouts: { ...wd, toolMaxMs: sec * 1000 } }, "global");
478095
+ sysMsg(ctx2, `Watchdog tool-max timeout \u2192 ${sec}s (global)`);
478096
+ }
478097
+ });
478098
+ },
478099
+ "wd-force": (_input2, ctx2) => {
478100
+ ctx2.openCommandPicker({
478101
+ title: "Watchdog \u2014 Force-Resolve Timeout",
478102
+ icon: icon("clock"),
478103
+ currentValue: wdForce,
478104
+ scopeEnabled: false,
478105
+ options: [
478106
+ { value: "wd-force:1", label: "1s" },
478107
+ { value: "wd-force:5", label: "5s", description: "default" },
478108
+ { value: "wd-force:10", label: "10s" },
478109
+ { value: "wd-force:30", label: "30s" }
478110
+ ],
478111
+ onSelect: (value) => {
478112
+ const sec = Number(value.split(":")[1]);
478113
+ ctx2.saveToScope({ watchdogTimeouts: { ...wd, forceResolveMs: sec * 1000 } }, "global");
478114
+ sysMsg(ctx2, `Watchdog force-resolve timeout \u2192 ${sec}s (global)`);
478115
+ }
478116
+ });
478117
+ }
478118
+ };
477261
478119
  ctx.openCommandPicker({
477262
- title: "Tool Timeout",
478120
+ title: "Timeouts & Watchdog",
477263
478121
  icon: icon("clock"),
477264
- currentValue: String(current),
478122
+ currentValue: currentTool,
477265
478123
  scopeEnabled: false,
477266
- options,
478124
+ options: [
478125
+ {
478126
+ value: "tool-timeout",
478127
+ label: "Tool Timeout",
478128
+ description: `${currentToolTimeout === 0 ? "none" : currentToolTimeout + "m"}`
478129
+ },
478130
+ { value: "watchdog-toggle", label: "Watchdog", description: watchdogEnabled ? "On" : "Off" },
478131
+ { value: "wd-first", label: "First Chunk Timeout", description: `${wdFirstSec}s` },
478132
+ { value: "wd-chunk", label: "Chunk Timeout", description: `${wdChunkSec}s` },
478133
+ { value: "wd-tool", label: "Tool Max Timeout", description: `${wdToolSec}s` },
478134
+ { value: "wd-force", label: "Force-Resolve Timeout", description: `${wdForceSec}s` }
478135
+ ],
477267
478136
  onSelect: (value) => {
477268
- ctx.saveToScope({ toolTimeout: Number(value) }, "global");
477269
- sysMsg(ctx, `Tool timeout \u2192 ${value === "0" ? "none" : `${value}m`} (global)`);
478137
+ const handler4 = timeoutPickers[value];
478138
+ if (handler4) {
478139
+ handler4("", ctx);
478140
+ }
477270
478141
  }
477271
478142
  });
477272
478143
  }
@@ -477613,8 +478484,8 @@ function register4(map2) {
477613
478484
  map2.set("/settings", handleSettingsHub);
477614
478485
  map2.set("/lock-in", handleLockIn);
477615
478486
  map2.set("/theme", handleTheme);
477616
- map2.set("/watchdog", handleWatchdog);
477617
478487
  map2.set("/timeouts", handleTimeouts);
478488
+ map2.set("/watchdog", handleTimeouts);
477618
478489
  }
477619
478490
  function matchConfigPrefix(cmd) {
477620
478491
  if (cmd === "/font nerd")
@@ -477631,7 +478502,7 @@ function matchConfigPrefix(cmd) {
477631
478502
  return handleTheme;
477632
478503
  return null;
477633
478504
  }
477634
- var handleVerbose, handleWatchdog, handleVimHints, settingsHandlers, OPACITY_LEVELS, OPACITY_OPTIONS, BORDER_STRENGTH_OPTIONS, BORDER_STRENGTH_LABELS, handleTheme = (input, ctx) => {
478505
+ var handleVerbose, handleVimHints, settingsHandlers, OPACITY_LEVELS, OPACITY_OPTIONS, BORDER_STRENGTH_OPTIONS, BORDER_STRENGTH_LABELS, handleTheme = (input, ctx) => {
477635
478506
  const arg = input.replace(/^\/theme\s*/, "").trim();
477636
478507
  const current = useThemeStore.getState().name;
477637
478508
  const isTransparent = useThemeStore.getState().tokens.bgApp === "transparent";
@@ -477751,18 +478622,6 @@ var init_config3 = __esm(() => {
477751
478622
  offDescription: "show compact tool call summaries",
477752
478623
  messageTemplate: (v4, s2) => `Verbose mode ${v4 === "on" ? "on" : "off"} (${s2})`
477753
478624
  });
477754
- handleWatchdog = createTogglePicker({
477755
- configKey: "watchdog",
477756
- title: "Stream Stall Watchdog",
477757
- iconName: "dog",
477758
- onValue: "on",
477759
- offValue: "off",
477760
- onLabel: "On",
477761
- offLabel: "Off",
477762
- onDescription: "auto-retry when stream stalls (connection hangs)",
477763
- offDescription: "no automatic stall detection or retry",
477764
- messageTemplate: (v4, s2) => `Watchdog ${v4 === "on" ? "enabled" : "disabled"} (${s2})`
477765
- });
477766
478625
  handleVimHints = createTogglePicker({
477767
478626
  configKey: "vimHints",
477768
478627
  title: "Vim Hints",
@@ -478306,6 +479165,11 @@ class TuiHost {
478306
479165
  const surface = this.host.getSurface(surfaceId);
478307
479166
  if (!surface)
478308
479167
  return;
479168
+ if (SETTINGS_COMMAND_NAMES.includes(cmd.name)) {
479169
+ const handled = await handleSettingsCommand(cmd.name, cmd.args, (text3) => surface.notify(msg.externalId, text3));
479170
+ if (handled)
479171
+ return;
479172
+ }
478309
479173
  switch (cmd.name) {
478310
479174
  case "/tabs":
478311
479175
  case "/list": {
@@ -478851,6 +479715,9 @@ class TuiHost {
478851
479715
  "\u2501\u2501 Pairing \u2501\u2501",
478852
479716
  "/pair [CODE] pair this chat \xB7 redeem a code",
478853
479717
  "/unpair revoke pairing",
479718
+ "",
479719
+ ...settingsHelpLines(),
479720
+ "",
478854
479721
  "/help this list"
478855
479722
  ].join(`
478856
479723
  `));
@@ -478928,6 +479795,7 @@ var init_tui_host = __esm(() => {
478928
479795
  init_config();
478929
479796
  init_pairing();
478930
479797
  init_protocol();
479798
+ init_provider_commands();
478931
479799
  init_redact();
478932
479800
  init_surface_host();
478933
479801
  });
@@ -484117,19 +484985,12 @@ var init_registry = __esm(() => {
484117
484985
  category: "System",
484118
484986
  tags: ["onboarding", "setup", "welcome"]
484119
484987
  },
484120
- {
484121
- cmd: "/watchdog",
484122
- ic: "dog",
484123
- desc: "Toggle stream stall watchdog (auto-retry on hangs)",
484124
- category: "System",
484125
- tags: ["stall", "retry", "timeout"]
484126
- },
484127
484988
  {
484128
484989
  cmd: "/timeouts",
484129
484990
  ic: "clock",
484130
- desc: "Set tool call timeout (shell, project, agents)",
484991
+ desc: "Configure tool and watchdog timeouts",
484131
484992
  category: "System",
484132
- tags: ["timeout", "shell", "agent"]
484993
+ tags: ["timeout", "shell", "agent", "watchdog", "stall"]
484133
484994
  },
484134
484995
  {
484135
484996
  cmd: "/checkpoint",
@@ -487635,6 +488496,21 @@ var init_compaction_logs = __esm(() => {
487635
488496
  })));
487636
488497
  });
487637
488498
 
488499
+ // src/types/index.ts
488500
+ function clampWatchdogTimeouts(raw2) {
488501
+ const clamp = (value, min, max, fallback) => {
488502
+ if (value === undefined)
488503
+ return fallback;
488504
+ return Math.max(min, Math.min(max, value));
488505
+ };
488506
+ return {
488507
+ firstChunkMs: clamp(raw2?.firstChunkMs, 30000, 600000, 180000),
488508
+ chunkMs: clamp(raw2?.chunkMs, 30000, 600000, 120000),
488509
+ toolMaxMs: clamp(raw2?.toolMaxMs, 120000, 1800000, 900000),
488510
+ forceResolveMs: clamp(raw2?.forceResolveMs, 1000, 30000, 5000)
488511
+ };
488512
+ }
488513
+
487638
488514
  // src/hooks/chat/message-processing.ts
487639
488515
  function safeParseArgs(raw2) {
487640
488516
  if (!raw2)
@@ -489488,9 +490364,10 @@ Proceeding without it will significantly reduce capabilities \u2014 no soul tool
489488
490364
  if (!result) {
489489
490365
  throw new Error("Stream aborted before result was assigned");
489490
490366
  }
489491
- const STALL_CHUNK_MS = 120000;
489492
- const STALL_FIRST_CHUNK_MS = 180000;
489493
- const STALL_TOOL_MAX_MS = 900000;
490367
+ const wd = clampWatchdogTimeouts(effectiveConfig2.watchdogTimeouts);
490368
+ const STALL_CHUNK_MS = wd.chunkMs;
490369
+ const STALL_FIRST_CHUNK_MS = wd.firstChunkMs;
490370
+ const STALL_TOOL_MAX_MS = wd.toolMaxMs;
489494
490371
  let lastActivityTs = Date.now();
489495
490372
  let lastToolActivityTs = Date.now();
489496
490373
  let toolsInFlight = 0;
@@ -489534,7 +490411,7 @@ Proceeding without it will significantly reduce capabilities \u2014 no soul tool
489534
490411
  lastToolActivityTs = Date.now();
489535
490412
  });
489536
490413
  let stallAbortedAt = 0;
489537
- const STALL_FORCE_RESOLVE_MS = 5000;
490414
+ const STALL_FORCE_RESOLVE_MS = wd.forceResolveMs;
489538
490415
  if (effectiveConfig2.watchdog)
489539
490416
  stallWatchdog = setInterval(() => {
489540
490417
  if (stallAborted && Date.now() - stallAbortedAt >= STALL_FORCE_RESOLVE_MS) {
@@ -496528,7 +497405,7 @@ var init_LockInStreamView = __esm(async () => {
496528
497405
  const dispatchCalls = import_react42.useMemo(() => {
496529
497406
  const next = liveToolCalls.filter((tc) => SUBAGENT_NAMES.has(tc.toolName));
496530
497407
  const prev = dispatchRef.current;
496531
- if (prev.length === next.length && prev.every((p2, i4) => p2 === next[i4] || p2.id === next[i4]?.id)) {
497408
+ if (prev.length === next.length && prev.every((p2, i4) => next[i4] !== undefined && p2.id === next[i4]?.id && p2.state === next[i4]?.state && p2.args === next[i4]?.args && p2.result === next[i4]?.result && p2.error === next[i4]?.error && p2.progressText === next[i4]?.progressText)) {
496532
497409
  return prev;
496533
497410
  }
496534
497411
  dispatchRef.current = next;
@@ -506080,6 +506957,18 @@ function OptionRow2({
506080
506957
  textFaint,
506081
506958
  successColor
506082
506959
  }) {
506960
+ if (option2.kind === "separator") {
506961
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
506962
+ flexDirection: "row",
506963
+ backgroundColor: popupBg,
506964
+ paddingX: 1,
506965
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
506966
+ fg: textFaint,
506967
+ bg: popupBg,
506968
+ children: "\u2500".repeat(innerW - 4)
506969
+ }, undefined, false, undefined, this)
506970
+ }, undefined, false, undefined, this);
506971
+ }
506083
506972
  const isDisabled = option2.disabled === true;
506084
506973
  const bg2 = isActive && !isDisabled ? popupHl : popupBg;
506085
506974
  const activeColor = option2.color ?? brandSecondary;
@@ -506206,9 +507095,11 @@ function CommandPicker({ visible, config: config2, onClose }) {
506206
507095
  initial[sel.key] = sel.value;
506207
507096
  setSelectorState(initial);
506208
507097
  }
506209
- let idx = filteredOptions.findIndex((o3) => o3.value === config2.currentValue);
506210
- if (idx < 0)
506211
- idx = filteredOptions.findIndex((o3) => !o3.disabled);
507098
+ const curVal = config2.currentValue;
507099
+ let idx = curVal ? Array.isArray(curVal) ? filteredOptions.findIndex((o3) => curVal.includes(o3.value)) : filteredOptions.findIndex((o3) => o3.value === curVal) : -1;
507100
+ if (idx < 0) {
507101
+ idx = filteredOptions.findIndex((o3) => !o3.disabled && o3.kind !== "separator");
507102
+ }
506212
507103
  const startIdx = idx >= 0 ? idx : 0;
506213
507104
  setCursor(startIdx);
506214
507105
  setScrollOffset(Math.max(0, startIdx - Math.floor(maxVisible / 2)));
@@ -506307,7 +507198,7 @@ function CommandPicker({ visible, config: config2, onClose }) {
506307
507198
  if (prev > 0) {
506308
507199
  let next = prev - 1;
506309
507200
  const start3 = next;
506310
- while (filteredOptions[next]?.disabled) {
507201
+ while (filteredOptions[next]?.kind === "separator" || filteredOptions[next]?.disabled) {
506311
507202
  next = next > 0 ? next - 1 : filteredOptions.length - 1;
506312
507203
  if (next === start3)
506313
507204
  break;
@@ -506329,7 +507220,7 @@ function CommandPicker({ visible, config: config2, onClose }) {
506329
507220
  if (prev < filteredOptions.length - 1) {
506330
507221
  let next = prev + 1;
506331
507222
  const start3 = next;
506332
- while (filteredOptions[next]?.disabled) {
507223
+ while (filteredOptions[next]?.kind === "separator" || filteredOptions[next]?.disabled) {
506333
507224
  next = next < filteredOptions.length - 1 ? next + 1 : 0;
506334
507225
  if (next === start3)
506335
507226
  break;
@@ -506404,7 +507295,7 @@ function CommandPicker({ visible, config: config2, onClose }) {
506404
507295
  }
506405
507296
  if (evt.name === "return" && focusZone === ZONE_LIST) {
506406
507297
  const option2 = filteredOptions[cursor];
506407
- if (option2 && !option2.disabled) {
507298
+ if (option2 && !option2.disabled && option2.kind !== "separator") {
506408
507299
  const cb = config2.onSelect;
506409
507300
  const val = option2.value;
506410
507301
  const s2 = config2.scopeEnabled ? scope : undefined;
@@ -506502,7 +507393,7 @@ function CommandPicker({ visible, config: config2, onClose }) {
506502
507393
  }, undefined, false, undefined, this) : filteredOptions.slice(clampedOffset, clampedOffset + maxVisible).map((option2, vi) => /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(OptionRow2, {
506503
507394
  option: option2,
506504
507395
  isActive: vi + clampedOffset === cursor,
506505
- isCurrent: option2.value === config2.currentValue,
507396
+ isCurrent: config2.currentValue ? Array.isArray(config2.currentValue) ? config2.currentValue.includes(option2.value) : option2.value === config2.currentValue : false,
506506
507397
  innerW,
506507
507398
  popupBg: POPUP_BG,
506508
507399
  popupHl: POPUP_HL,
@@ -518784,6 +519675,32 @@ function readValuesFromLayer(layer) {
518784
519675
  v4.openaiReasoningEffort = layer.performance.openaiReasoningEffort;
518785
519676
  if (layer.performance?.serviceTier !== undefined)
518786
519677
  v4.serviceTier = layer.performance.serviceTier;
519678
+ if (layer.performance?.googleThinkingLevel !== undefined)
519679
+ v4.googleThinkingLevel = layer.performance.googleThinkingLevel;
519680
+ if (layer.performance?.googleThinkingBudget !== undefined)
519681
+ v4.googleThinkingBudget = String(layer.performance.googleThinkingBudget);
519682
+ if (layer.performance?.googleIncludeThoughts !== undefined)
519683
+ v4.googleIncludeThoughts = layer.performance.googleIncludeThoughts;
519684
+ if (layer.performance?.xaiReasoningEffort !== undefined)
519685
+ v4.xaiReasoningEffort = layer.performance.xaiReasoningEffort;
519686
+ if (layer.performance?.deepseekThinking !== undefined)
519687
+ v4.deepseekThinking = layer.performance.deepseekThinking;
519688
+ if (layer.performance?.openrouterReasoningEffort !== undefined)
519689
+ v4.openrouterReasoningEffort = layer.performance.openrouterReasoningEffort;
519690
+ if (layer.performance?.openrouterReasoningMaxTokens !== undefined)
519691
+ v4.openrouterReasoningMaxTokens = String(layer.performance.openrouterReasoningMaxTokens);
519692
+ if (layer.performance?.openrouterExcludeReasoning !== undefined)
519693
+ v4.openrouterExcludeReasoning = layer.performance.openrouterExcludeReasoning;
519694
+ if (layer.performance?.compatReasoningEffort !== undefined)
519695
+ v4.compatReasoningEffort = layer.performance.compatReasoningEffort;
519696
+ if (layer.performance?.groqReasoningEffort !== undefined)
519697
+ v4.groqReasoningEffort = layer.performance.groqReasoningEffort;
519698
+ if (layer.performance?.openaiReasoningSummary !== undefined)
519699
+ v4.openaiReasoningSummary = layer.performance.openaiReasoningSummary;
519700
+ if (layer.performance?.openaiVerbosity !== undefined)
519701
+ v4.openaiVerbosity = layer.performance.openaiVerbosity;
519702
+ if (layer.performance?.groqReasoningFormat !== undefined)
519703
+ v4.groqReasoningFormat = layer.performance.groqReasoningFormat;
518787
519704
  if (layer.codeExecution !== undefined)
518788
519705
  v4.codeExecution = layer.codeExecution;
518789
519706
  if (layer.computerUse !== undefined)
@@ -518829,6 +519746,36 @@ function buildPatch(key3, value) {
518829
519746
  return { performance: { openaiReasoningEffort: value } };
518830
519747
  case "serviceTier":
518831
519748
  return { performance: { serviceTier: value } };
519749
+ case "googleThinkingLevel":
519750
+ return { performance: { googleThinkingLevel: value } };
519751
+ case "googleThinkingBudget": {
519752
+ const v4 = value === "off" ? "off" : Number(value);
519753
+ return { performance: { googleThinkingBudget: v4 } };
519754
+ }
519755
+ case "googleIncludeThoughts":
519756
+ return { performance: { googleIncludeThoughts: value } };
519757
+ case "xaiReasoningEffort":
519758
+ return { performance: { xaiReasoningEffort: value } };
519759
+ case "deepseekThinking":
519760
+ return { performance: { deepseekThinking: value } };
519761
+ case "openrouterReasoningEffort":
519762
+ return { performance: { openrouterReasoningEffort: value } };
519763
+ case "openrouterReasoningMaxTokens": {
519764
+ const v4 = value === "off" ? "off" : Number(value);
519765
+ return { performance: { openrouterReasoningMaxTokens: v4 } };
519766
+ }
519767
+ case "openrouterExcludeReasoning":
519768
+ return { performance: { openrouterExcludeReasoning: value } };
519769
+ case "compatReasoningEffort":
519770
+ return { performance: { compatReasoningEffort: value } };
519771
+ case "groqReasoningEffort":
519772
+ return { performance: { groqReasoningEffort: value } };
519773
+ case "openaiReasoningSummary":
519774
+ return { performance: { openaiReasoningSummary: value } };
519775
+ case "openaiVerbosity":
519776
+ return { performance: { openaiVerbosity: value } };
519777
+ case "groqReasoningFormat":
519778
+ return { performance: { groqReasoningFormat: value } };
518832
519779
  case "codeExecution":
518833
519780
  return { codeExecution: value };
518834
519781
  case "computerUse":
@@ -518873,7 +519820,7 @@ function ProviderSettings({
518873
519820
  const { width: termCols, height: termRows } = useTerminalDimensions();
518874
519821
  const containerRows = termRows - 2;
518875
519822
  const popupWidth = Math.min(MAX_POPUP_WIDTH4, Math.floor(termCols * 0.85));
518876
- const maxVisible = Math.max(4, Math.floor(containerRows * 0.85) - CHROME_ROWS6);
519823
+ const maxVisible = Math.max(6, Math.floor(containerRows * 0.85) - CHROME_ROWS6);
518877
519824
  const t2 = useTheme();
518878
519825
  const [tab, setTab] = import_react136.useState("claude");
518879
519826
  const [cursor, setCursor] = import_react136.useState(0);
@@ -518881,12 +519828,13 @@ function ProviderSettings({
518881
519828
  const vals = effectiveValues(globalConfig2, projectConfig);
518882
519829
  const items = TAB_ITEMS[tab];
518883
519830
  const tabIdx = TABS5.indexOf(tab);
519831
+ const firstRowIdx = items.findIndex((i4) => i4.type !== "section" && i4.type !== "info");
518884
519832
  import_react136.useEffect(() => {
518885
519833
  if (visible)
518886
519834
  setScope(detectInitialScope(projectConfig));
518887
519835
  }, [visible, projectConfig]);
518888
519836
  import_react136.useEffect(() => {
518889
- setCursor(0);
519837
+ setCursor(Math.max(0, firstRowIdx));
518890
519838
  }, [tab]);
518891
519839
  const isBudgetDisabled = vals.thinkingMode !== "enabled";
518892
519840
  const isThinkingDisabled = vals.thinkingMode === "off" || vals.thinkingMode === "disabled";
@@ -518897,6 +519845,19 @@ function ProviderSettings({
518897
519845
  return isThinkingDisabled;
518898
519846
  return false;
518899
519847
  };
519848
+ const stepCursor = (dir) => {
519849
+ if (items.length === 0)
519850
+ return;
519851
+ let next = cursor;
519852
+ for (let i4 = 0;i4 < items.length; i4++) {
519853
+ next = (next + dir + items.length) % items.length;
519854
+ const it = items[next];
519855
+ if (it && it.type !== "section" && it.type !== "info") {
519856
+ setCursor(next);
519857
+ return;
519858
+ }
519859
+ }
519860
+ };
518900
519861
  const cycleValue = (item) => {
518901
519862
  if (item.type === "toggle") {
518902
519863
  if (isItemDisabled(item.key))
@@ -518935,16 +519896,16 @@ function ProviderSettings({
518935
519896
  return;
518936
519897
  }
518937
519898
  if (evt.name === "up") {
518938
- setCursor((c) => c > 0 ? c - 1 : items.length - 1);
519899
+ stepCursor(-1);
518939
519900
  return;
518940
519901
  }
518941
519902
  if (evt.name === "down") {
518942
- setCursor((c) => c < items.length - 1 ? c + 1 : 0);
519903
+ stepCursor(1);
518943
519904
  return;
518944
519905
  }
518945
519906
  if (evt.name === "return" || evt.name === " ") {
518946
519907
  const item = items[cursor];
518947
- if (item)
519908
+ if (item && item.type !== "section" && item.type !== "info")
518948
519909
  cycleValue(item);
518949
519910
  return;
518950
519911
  }
@@ -518973,6 +519934,9 @@ function ProviderSettings({
518973
519934
  const sidebarW = 22;
518974
519935
  const contentW = popupWidth - sidebarW - 3;
518975
519936
  const labelW = 22;
519937
+ const focusedItem = items[cursor];
519938
+ const focusedRow = focusedItem && focusedItem.type !== "section" && focusedItem.type !== "info" ? focusedItem : null;
519939
+ const focusedDisabled = focusedRow ? isItemDisabled(focusedRow.key) : false;
518976
519940
  return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(PremiumPopup, {
518977
519941
  visible,
518978
519942
  width: popupWidth,
@@ -518981,14 +519945,25 @@ function ProviderSettings({
518981
519945
  titleIcon: "system",
518982
519946
  tabs: [
518983
519947
  { id: "claude", label: "Claude", icon: "ai", blurb: "thinking \xB7 reasoning \xB7 beta" },
518984
- { id: "openai", label: "OpenAI", icon: "ai", blurb: "reasoning effort \xB7 summary" },
518985
- { id: "general", label: "General", icon: "cloud", blurb: "shared provider options" }
519948
+ { id: "openai", label: "OpenAI", icon: "ai", blurb: "reasoning \xB7 service tier" },
519949
+ { id: "google", label: "Gemini", icon: "ai", blurb: "thinking level \xB7 budget" },
519950
+ { id: "xai", label: "Grok", icon: "ai", blurb: "reasoning effort" },
519951
+ { id: "deepseek", label: "DeepSeek", icon: "ai", blurb: "thinking toggle" },
519952
+ { id: "openrouter", label: "OpenRouter", icon: "cloud", blurb: "unified reasoning" },
519953
+ {
519954
+ id: "compat",
519955
+ label: "Other",
519956
+ icon: "cloud",
519957
+ blurb: "Groq \xB7 OpenCode \xB7 Fireworks \xB7 LM Studio \xB7 Ollama \u2026"
519958
+ },
519959
+ { id: "general", label: "General", icon: "cloud", blurb: "shared options" }
518986
519960
  ],
518987
519961
  activeTab: tab,
518988
519962
  footerHints: [
518989
- { key: "Tab", label: "switch tab" },
519963
+ { key: "Tab", label: "tab" },
518990
519964
  { key: "\u2191\u2193", label: "nav" },
518991
- { key: "Enter/\u2190\u2192", label: "cycle" },
519965
+ { key: "Enter", label: "cycle" },
519966
+ { key: "\u2190\u2192", label: "scope" },
518992
519967
  { key: "Esc", label: "close" }
518993
519968
  ],
518994
519969
  children: [
@@ -519006,140 +519981,144 @@ function ProviderSettings({
519006
519981
  items,
519007
519982
  selectedIndex: cursor,
519008
519983
  width: contentW,
519009
- maxRows: Math.max(1, Math.floor(maxVisible / 3)),
519010
- rowHeight: 3,
519011
- keyExtractor: (item) => item.key,
519984
+ maxRows: maxVisible,
519985
+ rowHeight: 1,
519986
+ keyExtractor: (item, idx) => item.type === "section" ? `sec-${idx}-${item.label}` : item.type === "info" ? `info-${idx}` : item.key,
519012
519987
  renderItem: (item, { selected }) => {
519988
+ if (item.type === "section") {
519989
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
519990
+ flexDirection: "row",
519991
+ backgroundColor: t2.bgPopup,
519992
+ paddingX: 1,
519993
+ children: [
519994
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
519995
+ bg: t2.bgPopup,
519996
+ fg: t2.textMuted,
519997
+ attributes: 1,
519998
+ children: item.label.toUpperCase()
519999
+ }, undefined, false, undefined, this),
520000
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
520001
+ bg: t2.bgPopup,
520002
+ fg: t2.textFaint,
520003
+ children: [
520004
+ " ",
520005
+ "\u2500".repeat(Math.max(0, contentW - item.label.length - 6))
520006
+ ]
520007
+ }, undefined, true, undefined, this)
520008
+ ]
520009
+ }, undefined, true, undefined, this);
520010
+ }
520011
+ if (item.type === "info") {
520012
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
520013
+ flexDirection: "row",
520014
+ backgroundColor: t2.bgPopup,
520015
+ paddingX: 1,
520016
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
520017
+ bg: t2.bgPopup,
520018
+ fg: t2.textFaint,
520019
+ children: [
520020
+ "\u2139 ",
520021
+ item.text
520022
+ ]
520023
+ }, undefined, true, undefined, this)
520024
+ }, undefined, false, undefined, this);
520025
+ }
519013
520026
  const disabled = isItemDisabled(item.key);
519014
520027
  const bg2 = selected ? t2.bgPopupHighlight : t2.bgPopup;
519015
520028
  const raw2 = vals[item.key];
519016
520029
  const srcScope = detectValueScope(item.key, projectConfig);
519017
- const srcTag = srcScope === "project" ? "proj" : "glob";
519018
- const srcColor = srcScope === "project" ? t2.info : t2.textMuted;
519019
- const caption = /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
519020
- bg: bg2,
519021
- fg: t2.textFaint,
519022
- children: [
519023
- " ",
519024
- item.desc
519025
- ]
519026
- }, undefined, true, undefined, this);
520030
+ const showScope = srcScope === "project";
519027
520031
  let body4;
519028
520032
  if (item.type === "toggle") {
520033
+ const on = !!raw2;
519029
520034
  body4 = /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
519030
520035
  flexDirection: "row",
519031
520036
  backgroundColor: bg2,
519032
520037
  children: [
519033
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(Toggle, {
519034
- label: item.label,
519035
- on: !!raw2,
519036
- focused: selected && !disabled,
519037
- bg: bg2
520038
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
520039
+ bg: bg2,
520040
+ fg: selected ? t2.brand : t2.textFaint,
520041
+ children: selected ? "\u25B8 " : " "
520042
+ }, undefined, false, undefined, this),
520043
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
520044
+ bg: bg2,
520045
+ fg: disabled ? t2.textDim : selected ? t2.brand : t2.textPrimary,
520046
+ attributes: selected ? 1 : undefined,
520047
+ children: item.label.padEnd(labelW)
520048
+ }, undefined, false, undefined, this),
520049
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
520050
+ bg: bg2,
520051
+ fg: disabled ? t2.textDim : on ? t2.success : t2.textDim,
520052
+ children: on ? "\u25CF on " : "\u25CB off"
519038
520053
  }, undefined, false, undefined, this),
519039
520054
  /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
519040
520055
  flexGrow: 1,
519041
520056
  backgroundColor: bg2
519042
520057
  }, undefined, false, undefined, this),
519043
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
520058
+ showScope ? /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
519044
520059
  bg: bg2,
519045
- fg: srcColor,
519046
- children: [
519047
- srcTag,
519048
- " "
519049
- ]
519050
- }, undefined, true, undefined, this)
520060
+ fg: t2.info,
520061
+ children: "proj"
520062
+ }, undefined, false, undefined, this) : null
519051
520063
  ]
519052
520064
  }, undefined, true, undefined, this);
519053
520065
  } else {
519054
520066
  const opts = item.options ?? [];
519055
520067
  const currentValue = item.type === "budget" ? String(vals.budgetTokens) : String(raw2);
519056
- const optsFit = opts.length > 0 && opts.join(" ").length + labelW + 4 <= contentW - 10;
519057
- if (optsFit) {
519058
- body4 = /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
519059
- flexDirection: "row",
519060
- backgroundColor: bg2,
519061
- children: [
519062
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(SegmentedControl, {
519063
- label: item.label,
519064
- labelWidth: labelW,
519065
- options: opts.map((o3) => ({ value: o3, label: o3 })),
519066
- value: currentValue,
519067
- focused: selected && !disabled,
519068
- bg: bg2
519069
- }, undefined, false, undefined, this),
519070
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
519071
- flexGrow: 1,
519072
- backgroundColor: bg2
519073
- }, undefined, false, undefined, this),
519074
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
519075
- bg: bg2,
519076
- fg: srcColor,
519077
- children: [
519078
- srcTag,
519079
- " "
519080
- ]
519081
- }, undefined, true, undefined, this)
519082
- ]
519083
- }, undefined, true, undefined, this);
519084
- } else {
519085
- const valColor = disabled ? t2.textFaint : raw2 === "off" ? t2.textMuted : t2.brandAlt;
519086
- body4 = /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
519087
- flexDirection: "row",
519088
- backgroundColor: bg2,
519089
- children: [
519090
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
519091
- bg: bg2,
519092
- fg: selected ? t2.brand : t2.textFaint,
519093
- children: selected ? "\u25B8 " : " "
519094
- }, undefined, false, undefined, this),
519095
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
519096
- bg: bg2,
519097
- fg: disabled ? t2.textFaint : selected ? t2.brand : t2.textPrimary,
519098
- attributes: selected ? 1 : undefined,
519099
- children: item.label.padEnd(labelW)
519100
- }, undefined, false, undefined, this),
519101
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
519102
- bg: bg2,
519103
- fg: valColor,
519104
- attributes: 1,
519105
- children: [
519106
- "[",
519107
- currentValue,
519108
- "]"
519109
- ]
519110
- }, undefined, true, undefined, this),
519111
- selected && !disabled ? /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
519112
- bg: bg2,
519113
- fg: t2.textDim,
519114
- children: " \u2190 \u2192"
519115
- }, undefined, false, undefined, this) : null,
519116
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
519117
- flexGrow: 1,
519118
- backgroundColor: bg2
519119
- }, undefined, false, undefined, this),
519120
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
519121
- bg: bg2,
519122
- fg: srcColor,
519123
- children: [
519124
- srcTag,
519125
- " "
519126
- ]
519127
- }, undefined, true, undefined, this)
519128
- ]
519129
- }, undefined, true, undefined, this);
519130
- }
520068
+ const valColor = disabled ? t2.textDim : currentValue === "off" ? t2.textMuted : t2.brandAlt;
520069
+ body4 = /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
520070
+ flexDirection: "row",
520071
+ backgroundColor: bg2,
520072
+ children: [
520073
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
520074
+ bg: bg2,
520075
+ fg: selected ? t2.brand : t2.textFaint,
520076
+ children: selected ? "\u25B8 " : " "
520077
+ }, undefined, false, undefined, this),
520078
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
520079
+ bg: bg2,
520080
+ fg: disabled ? t2.textDim : selected ? t2.brand : t2.textPrimary,
520081
+ attributes: selected ? 1 : undefined,
520082
+ children: item.label.padEnd(labelW)
520083
+ }, undefined, false, undefined, this),
520084
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
520085
+ bg: bg2,
520086
+ fg: valColor,
520087
+ attributes: 1,
520088
+ children: [
520089
+ "[",
520090
+ currentValue,
520091
+ "]"
520092
+ ]
520093
+ }, undefined, true, undefined, this),
520094
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
520095
+ bg: bg2,
520096
+ fg: t2.textFaint,
520097
+ children: [
520098
+ " ",
520099
+ opts.filter((o3) => o3 !== currentValue).slice(0, 4).join(" \xB7 ")
520100
+ ]
520101
+ }, undefined, true, undefined, this),
520102
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
520103
+ flexGrow: 1,
520104
+ backgroundColor: bg2
520105
+ }, undefined, false, undefined, this),
520106
+ showScope ? /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
520107
+ bg: bg2,
520108
+ fg: t2.info,
520109
+ children: "proj"
520110
+ }, undefined, false, undefined, this) : null
520111
+ ]
520112
+ }, undefined, true, undefined, this);
519131
520113
  }
519132
520114
  return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
519133
- flexDirection: "column",
520115
+ flexDirection: "row",
519134
520116
  flexShrink: 0,
519135
520117
  backgroundColor: bg2,
519136
520118
  paddingX: 1,
519137
- height: 3,
519138
- children: [
519139
- body4,
519140
- caption
519141
- ]
519142
- }, undefined, true, undefined, this);
520119
+ height: 1,
520120
+ children: body4
520121
+ }, undefined, false, undefined, this);
519143
520122
  }
519144
520123
  }, undefined, false, undefined, this)
519145
520124
  }, undefined, false, undefined, this),
@@ -519147,20 +520126,44 @@ function ProviderSettings({
519147
520126
  width: contentW
519148
520127
  }, undefined, false, undefined, this),
519149
520128
  /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
520129
+ flexDirection: "column",
519150
520130
  paddingX: 2,
519151
520131
  paddingY: 1,
519152
520132
  backgroundColor: t2.bgPopup,
519153
- children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(SegmentedControl, {
519154
- label: "Save to",
519155
- labelWidth: 8,
519156
- options: CONFIG_SCOPES.map((s2) => ({ value: s2, label: s2 })),
519157
- value: scope
519158
- }, undefined, false, undefined, this)
519159
- }, undefined, false, undefined, this)
520133
+ children: [
520134
+ focusedRow ? /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
520135
+ flexDirection: "row",
520136
+ backgroundColor: t2.bgPopup,
520137
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
520138
+ bg: t2.bgPopup,
520139
+ fg: focusedDisabled ? t2.textDim : t2.textMuted,
520140
+ children: focusedRow.desc
520141
+ }, undefined, false, undefined, this)
520142
+ }, undefined, false, undefined, this) : /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
520143
+ flexDirection: "row",
520144
+ backgroundColor: t2.bgPopup,
520145
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
520146
+ bg: t2.bgPopup,
520147
+ fg: t2.textFaint,
520148
+ children: " "
520149
+ }, undefined, false, undefined, this)
520150
+ }, undefined, false, undefined, this),
520151
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
520152
+ flexDirection: "row",
520153
+ backgroundColor: t2.bgPopup,
520154
+ children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(SegmentedControl, {
520155
+ label: "Save to",
520156
+ labelWidth: 8,
520157
+ options: CONFIG_SCOPES.map((s2) => ({ value: s2, label: s2 })),
520158
+ value: scope
520159
+ }, undefined, false, undefined, this)
520160
+ }, undefined, false, undefined, this)
520161
+ ]
520162
+ }, undefined, true, undefined, this)
519160
520163
  ]
519161
520164
  }, undefined, true, undefined, this);
519162
520165
  }
519163
- var import_react136, MAX_POPUP_WIDTH4 = 110, CHROME_ROWS6 = 10, TABS5, CLAUDE_ITEMS, OPENAI_ITEMS, GENERAL_ITEMS, TAB_ITEMS, DEFAULTS;
520166
+ var import_react136, MAX_POPUP_WIDTH4 = 110, CHROME_ROWS6 = 10, TABS5, CLAUDE_ITEMS, OPENAI_ITEMS, GENERAL_ITEMS, GOOGLE_ITEMS, XAI_ITEMS, DEEPSEEK_ITEMS, OPENROUTER_ITEMS, COMPAT_ITEMS, TAB_ITEMS, DEFAULTS;
519164
520167
  var init_ProviderSettings = __esm(async () => {
519165
520168
  init_theme();
519166
520169
  init_shared2();
@@ -519168,125 +520171,279 @@ var init_ProviderSettings = __esm(async () => {
519168
520171
  init_jsx_dev_runtime();
519169
520172
  await init_react2();
519170
520173
  import_react136 = __toESM(require_react(), 1);
519171
- TABS5 = ["claude", "openai", "general"];
520174
+ TABS5 = [
520175
+ "claude",
520176
+ "openai",
520177
+ "google",
520178
+ "xai",
520179
+ "deepseek",
520180
+ "openrouter",
520181
+ "compat",
520182
+ "general"
520183
+ ];
519172
520184
  CLAUDE_ITEMS = [
520185
+ { type: "section", label: "Reasoning" },
519173
520186
  {
519174
520187
  key: "thinkingMode",
519175
520188
  label: "Thinking",
519176
- desc: "off | auto (adaptive) | enabled (fixed budget)",
520189
+ desc: "Extended thinking mode. auto = adaptive \xB7 enabled = fixed budget",
519177
520190
  type: "cycle",
519178
- options: ["off", "disabled", "auto", "adaptive", "enabled"]
520191
+ options: ["off", "auto", "adaptive", "enabled"]
519179
520192
  },
519180
520193
  {
519181
520194
  key: "budgetTokens",
519182
- label: "Budget Tokens",
519183
- desc: "Token budget for enabled thinking mode",
520195
+ label: "Budget",
520196
+ desc: "Token budget when thinking = enabled",
519184
520197
  type: "budget",
519185
520198
  options: ["1024", "2048", "5000", "10000", "20000"]
519186
520199
  },
519187
- {
519188
- key: "clearThinking",
519189
- label: "Preserve Thinking",
519190
- desc: "Keep all thinking blocks across turns for better cache hits. Off = Anthropic default (only last turn kept). Requires thinking enabled.",
519191
- type: "toggle"
519192
- },
519193
520200
  {
519194
520201
  key: "effort",
519195
520202
  label: "Effort",
519196
- desc: "Reasoning depth \u2014 affects thinking, text, and tool calls",
520203
+ desc: "Reasoning depth across thinking, text, and tool calls",
519197
520204
  type: "cycle",
519198
520205
  options: ["off", "low", "medium", "high", "xhigh", "max"]
519199
520206
  },
520207
+ {
520208
+ key: "clearThinking",
520209
+ label: "Preserve thinking",
520210
+ desc: "Keep thinking blocks across turns for cache hits (requires thinking on)",
520211
+ type: "toggle"
520212
+ },
520213
+ { type: "section", label: "Performance" },
519200
520214
  {
519201
520215
  key: "speed",
519202
520216
  label: "Speed",
519203
- desc: "Opus 4.6 \u2014 2.5x faster output (standard | fast)",
520217
+ desc: "Opus 4.6 only \u2014 2.5\xD7 faster output",
519204
520218
  type: "cycle",
519205
520219
  options: ["off", "standard", "fast"]
519206
520220
  },
519207
520221
  {
519208
- key: "codeExecution",
519209
- label: "Code Execution",
519210
- desc: "Programmatic tool calling \u2014 batches reads in Python, saves tokens",
520222
+ key: "toolStreaming",
520223
+ label: "Tool streaming",
520224
+ desc: "Stream tool call args incrementally",
519211
520225
  type: "toggle"
519212
520226
  },
519213
520227
  {
519214
- key: "computerUse",
519215
- label: "Computer Use",
519216
- desc: "Keyboard/mouse/screenshot control",
520228
+ key: "sendReasoning",
520229
+ label: "Send reasoning",
520230
+ desc: "Include reasoning content in multi-turn requests",
519217
520231
  type: "toggle"
519218
520232
  },
520233
+ { type: "section", label: "Beta tools" },
519219
520234
  {
519220
- key: "anthropicTextEditor",
519221
- label: "Anthropic Text Editor",
519222
- desc: "Anthropic's str_replace editor tool",
520235
+ key: "codeExecution",
520236
+ label: "Code execution",
520237
+ desc: "Programmatic tool calling \u2014 batches reads in Python",
519223
520238
  type: "toggle"
519224
520239
  },
519225
520240
  {
519226
- key: "toolStreaming",
519227
- label: "Tool Streaming",
519228
- desc: "Stream tool call args incrementally (disable to debug)",
520241
+ key: "computerUse",
520242
+ label: "Computer use",
520243
+ desc: "Keyboard / mouse / screenshot control",
519229
520244
  type: "toggle"
519230
520245
  },
519231
520246
  {
519232
- key: "sendReasoning",
519233
- label: "Send Reasoning",
519234
- desc: "Include reasoning content in multi-turn requests",
520247
+ key: "anthropicTextEditor",
520248
+ label: "Text editor",
520249
+ desc: "Anthropic str_replace editor tool",
519235
520250
  type: "toggle"
519236
520251
  },
520252
+ { type: "section", label: "Server context" },
519237
520253
  {
519238
520254
  key: "compact",
519239
- label: "Server Compaction",
519240
- desc: "Anthropic server-side context compaction (200K+ models)",
520255
+ label: "Server compaction",
520256
+ desc: "Anthropic server-side compaction (200K+ models)",
519241
520257
  type: "toggle"
519242
520258
  },
519243
520259
  {
519244
520260
  key: "clearToolUses",
519245
- label: "Clear Tool Uses",
519246
- desc: "Server-side \u2014 clear old tool results at 65% context. \u26A0\uFE0F Busts prompt cache when triggered",
520261
+ label: "Clear old tool uses",
520262
+ desc: "Drop old tool results at 65% ctx. Busts prompt cache when it fires",
519247
520263
  type: "toggle"
519248
520264
  }
519249
520265
  ];
519250
520266
  OPENAI_ITEMS = [
520267
+ { type: "section", label: "Reasoning" },
519251
520268
  {
519252
520269
  key: "openaiReasoningEffort",
519253
- label: "Reasoning Effort",
519254
- desc: "For o3, o4, gpt-5 \u2014 controls reasoning depth",
520270
+ label: "Effort",
520271
+ desc: "o3 \xB7 o4 \xB7 gpt-5 \u2014 reasoning depth",
519255
520272
  type: "cycle",
519256
520273
  options: ["off", "none", "minimal", "low", "medium", "high", "xhigh"]
519257
520274
  },
520275
+ {
520276
+ key: "openaiReasoningSummary",
520277
+ label: "Reasoning summary",
520278
+ desc: "Include synthesized reasoning summary in the response",
520279
+ type: "cycle",
520280
+ options: ["off", "auto", "detailed"]
520281
+ },
520282
+ {
520283
+ key: "openaiVerbosity",
520284
+ label: "Verbosity",
520285
+ desc: "gpt-5+ \u2014 controls answer length",
520286
+ type: "cycle",
520287
+ options: ["off", "low", "medium", "high"]
520288
+ },
520289
+ { type: "section", label: "Service" },
519258
520290
  {
519259
520291
  key: "serviceTier",
519260
- label: "Service Tier",
519261
- desc: "flex = 50% cheaper | priority = fastest (Enterprise)",
520292
+ label: "Service tier",
520293
+ desc: "flex = 50% cheaper \xB7 priority = fastest (Enterprise)",
519262
520294
  type: "cycle",
519263
520295
  options: ["off", "auto", "default", "flex", "priority"]
519264
520296
  }
519265
520297
  ];
519266
520298
  GENERAL_ITEMS = [
520299
+ { type: "section", label: "Tools" },
519267
520300
  {
519268
520301
  key: "disableParallelToolUse",
519269
- label: "Sequential Tools",
519270
- desc: "One tool at a time instead of parallel (all providers)",
520302
+ label: "Sequential tools",
520303
+ desc: "Run tools one at a time instead of parallel (all providers)",
519271
520304
  type: "toggle"
519272
520305
  },
519273
520306
  {
519274
520307
  key: "webSearch",
519275
- label: "Web Search",
519276
- desc: "Allow web search tool",
520308
+ label: "Web search",
520309
+ desc: "Allow the web search tool",
519277
520310
  type: "toggle"
519278
520311
  },
520312
+ { type: "section", label: "Context" },
519279
520313
  {
519280
520314
  key: "pruning",
519281
- label: "Tool Result Pruning",
519282
- desc: "Client-side \u2014 compact old tool results: main | subagents | both | none",
520315
+ label: "Tool result pruning",
520316
+ desc: "Client-side \u2014 compact old tool results",
519283
520317
  type: "cycle",
519284
520318
  options: ["none", "main", "subagents", "both"]
519285
520319
  }
519286
520320
  ];
520321
+ GOOGLE_ITEMS = [
520322
+ {
520323
+ type: "info",
520324
+ text: "Gemini 3 / 3.1 use thinkingLevel \xB7 Gemini 2.5 uses thinkingBudget. Only one applies per model."
520325
+ },
520326
+ { type: "section", label: "Gemini 3+" },
520327
+ {
520328
+ key: "googleThinkingLevel",
520329
+ label: "Thinking level",
520330
+ desc: "Gemini 3 / 3.1 \u2014 depth of internal reasoning",
520331
+ type: "cycle",
520332
+ options: ["off", "minimal", "low", "medium", "high"]
520333
+ },
520334
+ { type: "section", label: "Gemini 2.5" },
520335
+ {
520336
+ key: "googleThinkingBudget",
520337
+ label: "Thinking budget",
520338
+ desc: "Token budget for Gemini 2.5 thinking. 0 = disabled",
520339
+ type: "cycle",
520340
+ options: ["off", "0", "1024", "4096", "8192", "16384", "24576"]
520341
+ },
520342
+ { type: "section", label: "Output" },
520343
+ {
520344
+ key: "googleIncludeThoughts",
520345
+ label: "Include thoughts",
520346
+ desc: "Return thought summaries in the response",
520347
+ type: "toggle"
520348
+ }
520349
+ ];
520350
+ XAI_ITEMS = [
520351
+ {
520352
+ type: "info",
520353
+ text: "Applies to: grok-3-mini \xB7 grok-4 \xB7 grok-4.1 \xB7 grok-4.20 \xB7 *-reasoning. Chat API clamps to low|high \u2014 medium auto-maps to high."
520354
+ },
520355
+ { type: "section", label: "Reasoning" },
520356
+ {
520357
+ key: "xaiReasoningEffort",
520358
+ label: "Effort",
520359
+ desc: "Chat: low/high \xB7 Responses API: low/medium/high",
520360
+ type: "cycle",
520361
+ options: ["off", "low", "medium", "high"]
520362
+ }
520363
+ ];
520364
+ DEEPSEEK_ITEMS = [
520365
+ {
520366
+ type: "info",
520367
+ text: "Applies to deepseek-chat only. deepseek-reasoner always thinks (no toggle)."
520368
+ },
520369
+ { type: "section", label: "Reasoning" },
520370
+ {
520371
+ key: "deepseekThinking",
520372
+ label: "Thinking",
520373
+ desc: "Enable chain-of-thought generation on deepseek-chat",
520374
+ type: "cycle",
520375
+ options: ["off", "enabled"]
520376
+ }
520377
+ ];
520378
+ OPENROUTER_ITEMS = [
520379
+ {
520380
+ type: "info",
520381
+ text: "Unified shape \u2014 OpenRouter maps to each upstream's native API (Anthropic budget, OpenAI effort, Gemini level). Inherits Claude budget by default."
520382
+ },
520383
+ { type: "section", label: "Unified reasoning" },
520384
+ {
520385
+ key: "openrouterReasoningEffort",
520386
+ label: "Effort",
520387
+ desc: "Mapped to upstream's native shape (Anthropic budget \xB7 OpenAI effort \xB7 Gemini level)",
520388
+ type: "cycle",
520389
+ options: ["off", "minimal", "low", "medium", "high", "xhigh", "none"]
520390
+ },
520391
+ {
520392
+ key: "openrouterReasoningMaxTokens",
520393
+ label: "Max tokens",
520394
+ desc: "Anthropic-style budget. Takes priority over Effort when set.",
520395
+ type: "cycle",
520396
+ options: ["off", "1024", "2048", "4096", "8192", "16384"]
520397
+ },
520398
+ { type: "section", label: "Output" },
520399
+ {
520400
+ key: "openrouterExcludeReasoning",
520401
+ label: "Hide reasoning",
520402
+ desc: "Model still reasons but tokens are not returned",
520403
+ type: "toggle"
520404
+ }
520405
+ ];
520406
+ COMPAT_ITEMS = [
520407
+ {
520408
+ type: "info",
520409
+ text: "Applies to: Groq \xB7 Fireworks \xB7 OpenCode Zen \xB7 OpenCode Go \xB7 LM Studio \xB7 Ollama \xB7 Copilot \xB7 GitHub Models \xB7 MiniMax \xB7 Proxy (non-Claude lane)"
520410
+ },
520411
+ { type: "section", label: "Shared effort" },
520412
+ {
520413
+ key: "compatReasoningEffort",
520414
+ label: "Effort",
520415
+ desc: "Single knob \u2014 propagates as reasoning_effort in the request body for every provider listed above.",
520416
+ type: "cycle",
520417
+ options: ["off", "low", "medium", "high", "xhigh"]
520418
+ },
520419
+ { type: "section", label: "Groq" },
520420
+ {
520421
+ type: "info",
520422
+ text: "Groq-specific overrides. qwen3 / gpt-oss / deepseek-r1 reasoning SKUs."
520423
+ },
520424
+ {
520425
+ key: "groqReasoningEffort",
520426
+ label: "Groq override",
520427
+ desc: "Takes priority over shared effort when set. qwen3 auto-maps to default.",
520428
+ type: "cycle",
520429
+ options: ["off", "low", "medium", "high"]
520430
+ },
520431
+ {
520432
+ key: "groqReasoningFormat",
520433
+ label: "Groq format",
520434
+ desc: "How reasoning appears in the Groq response payload",
520435
+ type: "cycle",
520436
+ options: ["off", "parsed", "raw", "hidden"]
520437
+ }
520438
+ ];
519287
520439
  TAB_ITEMS = {
519288
520440
  claude: CLAUDE_ITEMS,
519289
520441
  openai: OPENAI_ITEMS,
520442
+ google: GOOGLE_ITEMS,
520443
+ xai: XAI_ITEMS,
520444
+ deepseek: DEEPSEEK_ITEMS,
520445
+ openrouter: OPENROUTER_ITEMS,
520446
+ compat: COMPAT_ITEMS,
519290
520447
  general: GENERAL_ITEMS
519291
520448
  };
519292
520449
  DEFAULTS = {
@@ -519306,7 +520463,20 @@ var init_ProviderSettings = __esm(async () => {
519306
520463
  compact: false,
519307
520464
  clearToolUses: false,
519308
520465
  clearThinking: true,
519309
- pruning: "none"
520466
+ pruning: "none",
520467
+ googleThinkingLevel: "off",
520468
+ googleThinkingBudget: "off",
520469
+ googleIncludeThoughts: false,
520470
+ xaiReasoningEffort: "off",
520471
+ deepseekThinking: "off",
520472
+ openrouterReasoningEffort: "off",
520473
+ openrouterReasoningMaxTokens: "off",
520474
+ openrouterExcludeReasoning: false,
520475
+ compatReasoningEffort: "off",
520476
+ groqReasoningEffort: "off",
520477
+ openaiReasoningSummary: "off",
520478
+ openaiVerbosity: "off",
520479
+ groqReasoningFormat: "off"
519310
520480
  };
519311
520481
  });
519312
520482
 
@@ -519809,27 +520979,29 @@ function RouterSettings({
519809
520979
  for (const d3 of s2.defs) {
519810
520980
  if (d3.kind === "slot")
519811
520981
  out2.push({ kind: "slot", section: s2, def: d3 });
520982
+ else
520983
+ out2.push({ kind: "picker", section: s2, def: d3 });
519812
520984
  }
519813
520985
  }
519814
520986
  return out2;
519815
520987
  }, []);
519816
- const slotIndices = import_react140.useMemo(() => rows.map((r4, i4) => r4.kind === "slot" ? i4 : -1).filter((i4) => i4 >= 0), [rows]);
520988
+ const selectableIndices = import_react140.useMemo(() => rows.map((r4, i4) => r4.kind === "slot" || r4.kind === "picker" ? i4 : -1).filter((i4) => i4 >= 0), [rows]);
519817
520989
  const moveItem = (dir) => {
519818
- if (slotIndices.length === 0)
520990
+ if (selectableIndices.length === 0)
519819
520991
  return;
519820
- const cur = slotIndices.indexOf(cursor);
520992
+ const cur = selectableIndices.indexOf(cursor);
519821
520993
  const base = cur < 0 ? 0 : cur;
519822
- const nextPos = (base + dir + slotIndices.length) % slotIndices.length;
519823
- setCursor(slotIndices[nextPos] ?? slotIndices[0] ?? 0);
520994
+ const nextPos = (base + dir + selectableIndices.length) % selectableIndices.length;
520995
+ setCursor(selectableIndices[nextPos] ?? selectableIndices[0] ?? 0);
519824
520996
  };
519825
520997
  import_react140.useMemo(() => {
519826
- if (cursor === 0 && slotIndices.length > 0 && slotIndices[0] !== 0) {
519827
- setCursor(slotIndices[0] ?? 0);
520998
+ if (cursor === 0 && selectableIndices.length > 0 && selectableIndices[0] !== 0) {
520999
+ setCursor(selectableIndices[0] ?? 0);
519828
521000
  }
519829
- }, [cursor, slotIndices]);
521001
+ }, [cursor, selectableIndices]);
519830
521002
  const selectedRow = rows[cursor];
519831
- const selectedDef = selectedRow?.kind === "slot" ? selectedRow.def : null;
519832
- const pickerDefs = import_react140.useMemo(() => ALL_DEFS.filter((d3) => d3.kind === "picker"), []);
521003
+ const selectedSlot = selectedRow?.kind === "slot" ? selectedRow.def : null;
521004
+ const selectedPicker = selectedRow?.kind === "picker" ? selectedRow.def : null;
519833
521005
  useKeyboard((evt) => {
519834
521006
  if (!visible)
519835
521007
  return;
@@ -519846,16 +521018,28 @@ function RouterSettings({
519846
521018
  return;
519847
521019
  }
519848
521020
  if (evt.name === "return") {
519849
- if (selectedDef)
519850
- onPickSlot(selectedDef.key);
521021
+ if (selectedSlot)
521022
+ onPickSlot(selectedSlot.key);
519851
521023
  return;
519852
521024
  }
519853
521025
  if (evt.name === "d" || evt.name === "delete" || evt.name === "backspace") {
519854
- if (selectedDef)
519855
- onClearSlot(selectedDef.key);
521026
+ if (selectedSlot)
521027
+ onClearSlot(selectedSlot.key);
519856
521028
  return;
519857
521029
  }
519858
521030
  if (evt.name === "left" || evt.name === "right") {
521031
+ if (selectedPicker) {
521032
+ const cur = router2?.[selectedPicker.key];
521033
+ const curVal = typeof cur === "number" ? cur : selectedPicker.defaultValue;
521034
+ const opts = selectedPicker.options;
521035
+ const i4 = opts.indexOf(curVal);
521036
+ const base = i4 < 0 ? 0 : i4;
521037
+ const nextIdx = evt.name === "left" ? (base - 1 + opts.length) % opts.length : (base + 1) % opts.length;
521038
+ const next2 = opts[nextIdx];
521039
+ if (typeof next2 === "number" && next2 !== curVal)
521040
+ onPickerChange(selectedPicker.key, next2);
521041
+ return;
521042
+ }
519859
521043
  const sIdx = CONFIG_SCOPES.indexOf(scope);
519860
521044
  const next = evt.name === "left" ? CONFIG_SCOPES[(sIdx - 1 + CONFIG_SCOPES.length) % CONFIG_SCOPES.length] : CONFIG_SCOPES[(sIdx + 1) % CONFIG_SCOPES.length];
519861
521045
  if (next && next !== scope)
@@ -519886,7 +521070,7 @@ function RouterSettings({
519886
521070
  { key: "\u2191\u2193", label: "nav" },
519887
521071
  { key: "Enter", label: "set" },
519888
521072
  { key: "d", label: "reset" },
519889
- { key: "\u2190\u2192", label: "scope" },
521073
+ { key: "\u2190\u2192", label: selectedPicker ? "adjust" : "scope" },
519890
521074
  { key: "Esc", label: "close" }
519891
521075
  ],
519892
521076
  children: /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(Section, {
@@ -519914,6 +521098,17 @@ function RouterSettings({
519914
521098
  }, `h-${idx}`, true, undefined, this);
519915
521099
  }
519916
521100
  const isSelected = idx === cursor;
521101
+ if (row.kind === "picker") {
521102
+ const cur = router2?.[row.def.key];
521103
+ const num = typeof cur === "number" ? cur : row.def.defaultValue;
521104
+ return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(SegmentedControl, {
521105
+ label: row.def.label,
521106
+ labelWidth: labelCol,
521107
+ options: row.def.options.map((o3) => ({ value: o3, label: String(o3) })),
521108
+ value: num,
521109
+ focused: isSelected
521110
+ }, `p-${idx}`, false, undefined, this);
521111
+ }
519917
521112
  const rowBg = isSelected ? t2.bgPopupHighlight : t2.bgPopup;
519918
521113
  const raw2 = router2?.[row.def.key] ?? null;
519919
521114
  const modelId = typeof raw2 === "string" ? raw2 : null;
@@ -519965,17 +521160,6 @@ function RouterSettings({
519965
521160
  })
519966
521161
  }, undefined, false, undefined, this),
519967
521162
  /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(VSpacer, {}, undefined, false, undefined, this),
519968
- pickerDefs.map((def) => {
519969
- const cur = router2?.[def.key];
519970
- const num = typeof cur === "number" ? cur : def.defaultValue;
519971
- return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(SegmentedControl, {
519972
- label: def.label,
519973
- labelWidth: 14,
519974
- options: def.options.map((o3) => ({ value: o3, label: String(o3) })),
519975
- value: num
519976
- }, def.key, false, undefined, this);
519977
- }),
519978
- /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(VSpacer, {}, undefined, false, undefined, this),
519979
521163
  /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(SegmentedControl, {
519980
521164
  label: "Scope",
519981
521165
  labelWidth: 14,