@zergai/ztc 0.4.0-beta.1 → 0.4.1-dev.36919234045

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.md +8 -1
  2. package/THIRD_PARTY_NOTICES.md +4 -0
  3. package/dist/App.js +2 -2
  4. package/dist/agent/agent.js +41 -5
  5. package/dist/agent/backends/anthropic.js +5 -10
  6. package/dist/agent/backends/gemini.js +2 -0
  7. package/dist/agent/backends/index.d.ts +1 -0
  8. package/dist/agent/backends/index.js +10 -1
  9. package/dist/agent/backends/openai.d.ts +0 -6
  10. package/dist/agent/backends/openai.js +5 -2
  11. package/dist/agent/backends/openai_compatible.d.ts +2 -0
  12. package/dist/agent/backends/openai_compatible.js +18 -8
  13. package/dist/agent/backends/provider_ids.d.ts +1 -0
  14. package/dist/agent/backends/provider_ids.js +4 -0
  15. package/dist/agent/backends/types.d.ts +1 -0
  16. package/dist/agent/backends/zrouter_stream.d.ts +5 -1
  17. package/dist/agent/backends/zrouter_stream.js +141 -37
  18. package/dist/agent/commands/compact.js +6 -5
  19. package/dist/agent/commands/config.js +4 -1
  20. package/dist/agent/commands/context.js +6 -1
  21. package/dist/agent/commands/effort.js +14 -6
  22. package/dist/agent/commands/model.js +12 -2
  23. package/dist/agent/commands/profile.js +21 -2
  24. package/dist/agent/commands/provider_selection.js +14 -1
  25. package/dist/agent/commands/settings.js +45 -7
  26. package/dist/agent/commands/types.d.ts +19 -0
  27. package/dist/agent/factory.js +14 -68
  28. package/dist/agent/subagent.d.ts +0 -10
  29. package/dist/agent/subagent.js +22 -0
  30. package/dist/agent/tool_response_store.d.ts +1 -1
  31. package/dist/agent/tool_response_store.js +4 -2
  32. package/dist/agent/tools/pane.d.ts +0 -7
  33. package/dist/agent/tools/pane.js +5 -4
  34. package/dist/app/command_context.js +33 -10
  35. package/dist/app/model_catalog_cache.d.ts +5 -0
  36. package/dist/app/model_catalog_cache.js +138 -0
  37. package/dist/app/restart.d.ts +1 -1
  38. package/dist/app/restart.js +16 -5
  39. package/dist/app/selection_completion.d.ts +17 -2
  40. package/dist/app/selection_completion.js +74 -6
  41. package/dist/app/session_manager.js +13 -8
  42. package/dist/cli_args.js +2 -16
  43. package/dist/cli_main.js +25 -6
  44. package/dist/cli_ratatui.js +70 -33
  45. package/dist/config/defaults.d.ts +2 -1
  46. package/dist/config/defaults.js +9 -2
  47. package/dist/config/resolved.d.ts +2 -2
  48. package/dist/config.d.ts +35 -3
  49. package/dist/config.js +227 -29
  50. package/dist/headless.js +2 -0
  51. package/dist/onboarding/first_run.js +5 -1
  52. package/dist/panes/model_catalog.d.ts +3 -3
  53. package/dist/panes/model_catalog.js +28 -4
  54. package/dist/panes/pane_registry.js +6 -2
  55. package/dist/panes/types.d.ts +2 -0
  56. package/dist/panes/ztc_launch.js +12 -0
  57. package/dist/session.d.ts +6 -2
  58. package/dist/session.js +65 -10
  59. package/dist/ui/views/completion_menu.js +10 -5
  60. package/dist/ui/views/status_bar.js +9 -5
  61. package/dist/ui/widgets/resume_panel.d.ts +2 -0
  62. package/dist/ui/widgets/resume_panel.js +17 -15
  63. package/dist/utils/compaction.d.ts +1 -1
  64. package/dist/utils/compaction.js +2 -2
  65. package/dist/utils/context.d.ts +4 -4
  66. package/dist/utils/context.js +14 -12
  67. package/dist/utils/deepseek.d.ts +2 -2
  68. package/dist/utils/deepseek.js +13 -5
  69. package/dist/utils/model_cache.d.ts +15 -0
  70. package/dist/utils/model_cache.js +143 -0
  71. package/dist/utils/models.d.ts +4 -2
  72. package/dist/utils/models.js +12 -16
  73. package/dist/utils/reasoning.d.ts +8 -1
  74. package/dist/utils/reasoning.js +31 -11
  75. package/dist/utils/router_catalog.d.ts +47 -0
  76. package/dist/utils/router_catalog.js +222 -0
  77. package/dist/zlm-embed.mjs +1180 -514
  78. package/node_modules/@zerg/model-contracts/LICENSE.md +375 -0
  79. package/node_modules/@zerg/model-contracts/README.md +78 -0
  80. package/node_modules/@zerg/model-contracts/index.d.ts +18 -0
  81. package/node_modules/@zerg/model-contracts/index.js +220 -0
  82. package/node_modules/@zerg/model-contracts/package.json +8 -0
  83. package/node_modules/glob/node_modules/brace-expansion/index.js +78 -22
  84. package/node_modules/glob/node_modules/brace-expansion/package.json +1 -1
  85. package/node_modules/readdir-glob/node_modules/brace-expansion/index.js +78 -22
  86. package/node_modules/readdir-glob/node_modules/brace-expansion/package.json +1 -1
  87. package/package.json +9 -6
  88. package/packages/model-contracts/LICENSE.md +375 -0
  89. package/packages/model-contracts/README.md +78 -0
  90. package/packages/model-contracts/index.d.ts +18 -0
  91. package/packages/model-contracts/index.js +220 -0
  92. package/packages/model-contracts/package.json +8 -0
@@ -16,6 +16,200 @@ var __export = (target, all) => {
16
16
  __defProp(target, name, { get: all[name], enumerable: true });
17
17
  };
18
18
 
19
+ // packages/model-contracts/index.js
20
+ function normalizeContractProvider(provider) {
21
+ const normalized = provider.trim().toLowerCase();
22
+ return normalized === "moonshot" ? "kimi" : normalized === "claude" ? "anthropic" : normalized;
23
+ }
24
+ function normalizeContractModel(provider, model) {
25
+ const canonicalProvider = normalizeContractProvider(provider);
26
+ let id = model.trim().toLowerCase();
27
+ if (id.startsWith(`${canonicalProvider}/`)) id = id.slice(canonicalProvider.length + 1);
28
+ if (canonicalProvider === "fireworks" && id.startsWith("accounts/fireworks/models/")) {
29
+ id = id.slice("accounts/fireworks/models/".length);
30
+ }
31
+ return id;
32
+ }
33
+ function modelContract(provider, model, surface = "native_api") {
34
+ const canonicalProvider = normalizeContractProvider(provider);
35
+ const id = normalizeContractModel(canonicalProvider, model);
36
+ const contracts = MODEL_CONTRACTS.get(canonicalProvider);
37
+ const contract = contracts?.get(id) ?? (canonicalProvider === "anthropic" ? contracts?.get(id.replace(/-\d{8}$/, "")) : void 0);
38
+ if (contract === void 0) return void 0;
39
+ return {
40
+ reasoningEfforts: [...surface === "chat_tools" ? contract.chatToolReasoningEfforts ?? contract.reasoningEfforts : contract.reasoningEfforts],
41
+ ...contract.chatToolReasoningEfforts ? { chatToolReasoningEfforts: [...contract.chatToolReasoningEfforts] } : {},
42
+ maxOutputTokens: contract.maxOutputTokens === null ? null : { ...contract.maxOutputTokens },
43
+ fallbackRouting: contract.fallbackRouting
44
+ };
45
+ }
46
+ function reasoningRequestParams(provider, model, effort) {
47
+ const canonicalProvider = normalizeContractProvider(provider);
48
+ const id = normalizeContractModel(canonicalProvider, model);
49
+ const contract = modelContract(canonicalProvider, id);
50
+ const selected = typeof effort === "string" && contract?.reasoningEfforts.includes(effort) ? effort : null;
51
+ if (canonicalProvider === "deepseek" && contract?.reasoningEfforts.length) {
52
+ if (selected === "none") return { thinking: { type: "disabled" } };
53
+ return { thinking: { type: "enabled" }, reasoning_effort: selected ?? "high" };
54
+ }
55
+ if (selected === null) return {};
56
+ if (canonicalProvider === "anthropic") return { output_config: { effort: selected } };
57
+ if (canonicalProvider === "gemini") {
58
+ const thinkingConfig = id.startsWith("gemini-2.5-") ? { thinkingBudget: selected === "none" ? 0 : selected === "low" ? 1024 : selected === "medium" ? 8192 : id === "gemini-2.5-pro" ? 32768 : 24576 } : { thinkingLevel: selected };
59
+ return { generationConfig: { thinkingConfig } };
60
+ }
61
+ if (canonicalProvider === "zai") {
62
+ const thinking = { type: selected === "none" ? "disabled" : "enabled" };
63
+ return id === "glm-5.2" || id === "glm-5.3" || id === "glm-5.3-flash" ? { thinking, ...selected === "none" ? {} : { reasoning_effort: selected } } : { thinking };
64
+ }
65
+ if (canonicalProvider === "cohere") return { thinking: { type: selected === "none" ? "disabled" : "enabled" } };
66
+ return { reasoning_effort: selected };
67
+ }
68
+ function fireworksRequestModel(model) {
69
+ const upstream = model.trim().replace(/^fireworks\//i, "");
70
+ if (upstream.startsWith("accounts/")) return upstream;
71
+ return `accounts/fireworks/models/${normalizeContractModel("fireworks", model)}`;
72
+ }
73
+ var MODEL_CONTRACTS, CONTRACT_PROVIDERS;
74
+ var init_model_contracts = __esm({
75
+ "packages/model-contracts/index.js"() {
76
+ "use strict";
77
+ MODEL_CONTRACTS = /* @__PURE__ */ new Map([
78
+ ["openai", /* @__PURE__ */ new Map([
79
+ ["gpt-4o", { reasoningEfforts: [], maxOutputTokens: { "min": 256, "max": 16384 }, fallbackRouting: true }],
80
+ ["gpt-4o-mini", { reasoningEfforts: [], maxOutputTokens: { "min": 256, "max": 16384 }, fallbackRouting: true }],
81
+ ["gpt-5", { reasoningEfforts: ["minimal", "low", "medium", "high"], maxOutputTokens: { "min": 256, "max": 128e3 }, fallbackRouting: true }],
82
+ ["gpt-5-mini", { reasoningEfforts: ["minimal", "low", "medium", "high"], maxOutputTokens: { "min": 256, "max": 128e3 }, fallbackRouting: true }],
83
+ ["gpt-5.2", { reasoningEfforts: ["none", "low", "medium", "high", "xhigh", "max"], maxOutputTokens: { "min": 256, "max": 128e3 }, fallbackRouting: true }],
84
+ ["gpt-5.2-codex", { reasoningEfforts: [], maxOutputTokens: null, fallbackRouting: false }],
85
+ ["gpt-5.3-codex", { reasoningEfforts: ["low", "medium", "high", "xhigh"], maxOutputTokens: { "min": 256, "max": 128e3 }, fallbackRouting: false }],
86
+ ["gpt-5.4", { reasoningEfforts: ["none", "low", "medium", "high", "xhigh", "max"], chatToolReasoningEfforts: ["none"], maxOutputTokens: { "min": 256, "max": 128e3 }, fallbackRouting: true }],
87
+ ["gpt-5.4-mini", { reasoningEfforts: ["none", "low", "medium", "high", "xhigh", "max"], chatToolReasoningEfforts: ["none"], maxOutputTokens: { "min": 256, "max": 128e3 }, fallbackRouting: true }],
88
+ ["gpt-5.5", { reasoningEfforts: ["none", "low", "medium", "high", "xhigh", "max"], chatToolReasoningEfforts: ["none"], maxOutputTokens: { "min": 256, "max": 128e3 }, fallbackRouting: true }],
89
+ ["gpt-5.6", { reasoningEfforts: ["none", "low", "medium", "high", "xhigh", "max"], chatToolReasoningEfforts: ["none"], maxOutputTokens: { "min": 256, "max": 128e3 }, fallbackRouting: true }],
90
+ ["gpt-5.6-current", { reasoningEfforts: [], maxOutputTokens: null, fallbackRouting: true }],
91
+ ["gpt-5.6-luna", { reasoningEfforts: ["none", "low", "medium", "high", "xhigh", "max"], chatToolReasoningEfforts: ["none"], maxOutputTokens: { "min": 256, "max": 128e3 }, fallbackRouting: true }],
92
+ ["gpt-5.6-sol", { reasoningEfforts: ["none", "low", "medium", "high", "xhigh", "max"], chatToolReasoningEfforts: ["none"], maxOutputTokens: { "min": 256, "max": 128e3 }, fallbackRouting: true }],
93
+ ["gpt-5.6-terra", { reasoningEfforts: ["none", "low", "medium", "high", "xhigh", "max"], chatToolReasoningEfforts: ["none"], maxOutputTokens: { "min": 256, "max": 128e3 }, fallbackRouting: true }],
94
+ ["gpt-6-sol", { reasoningEfforts: ["none", "low", "medium", "high", "xhigh", "max"], chatToolReasoningEfforts: ["none"], maxOutputTokens: { "min": 256, "max": 128e3 }, fallbackRouting: true }],
95
+ ["gpt-6-luna", { reasoningEfforts: ["none", "low", "medium", "high", "xhigh", "max"], chatToolReasoningEfforts: ["none"], maxOutputTokens: { "min": 256, "max": 128e3 }, fallbackRouting: true }]
96
+ ])],
97
+ ["anthropic", /* @__PURE__ */ new Map([
98
+ ["claude-3-5-haiku-20241022", { reasoningEfforts: [], maxOutputTokens: { "min": 256, "max": 8192 }, fallbackRouting: true }],
99
+ ["claude-3-5-sonnet-20241022", { reasoningEfforts: [], maxOutputTokens: { "min": 256, "max": 8192 }, fallbackRouting: true }],
100
+ ["claude-opus-4-6", { reasoningEfforts: ["low", "medium", "high", "max"], maxOutputTokens: { "min": 256, "max": 8192 }, fallbackRouting: true }],
101
+ ["claude-opus-4.8", { reasoningEfforts: ["low", "medium", "high", "xhigh", "max"], maxOutputTokens: { "min": 256, "max": 8192 }, fallbackRouting: true }],
102
+ ["claude-sonnet-4", { reasoningEfforts: [], maxOutputTokens: { "min": 256, "max": 64e3 }, fallbackRouting: true }],
103
+ ["claude-sonnet-4-5", { reasoningEfforts: [], maxOutputTokens: { "min": 256, "max": 64e3 }, fallbackRouting: true }],
104
+ ["claude-sonnet-4-6", { reasoningEfforts: ["low", "medium", "high", "max"], maxOutputTokens: { "min": 256, "max": 64e3 }, fallbackRouting: true }],
105
+ ["claude-sonnet-4-20250514", { reasoningEfforts: [], maxOutputTokens: { "min": 256, "max": 64e3 }, fallbackRouting: true }],
106
+ ["claude-opus-4-5", { reasoningEfforts: ["low", "medium", "high", "max"], maxOutputTokens: null, fallbackRouting: true }],
107
+ ["claude-opus-4-8", { reasoningEfforts: ["low", "medium", "high", "xhigh", "max"], maxOutputTokens: null, fallbackRouting: true }],
108
+ ["claude-opus-4-7", { reasoningEfforts: ["low", "medium", "high", "xhigh", "max"], maxOutputTokens: null, fallbackRouting: true }],
109
+ ["claude-opus-5", { reasoningEfforts: ["low", "medium", "high", "xhigh", "max"], maxOutputTokens: null, fallbackRouting: true }],
110
+ ["claude-opus-5-5", { reasoningEfforts: ["low", "medium", "high", "xhigh", "max"], maxOutputTokens: null, fallbackRouting: true }],
111
+ ["claude-sonnet-5", { reasoningEfforts: ["low", "medium", "high", "xhigh", "max"], maxOutputTokens: null, fallbackRouting: true }],
112
+ ["claude-sonnet-5-5", { reasoningEfforts: ["low", "medium", "high", "xhigh", "max"], maxOutputTokens: null, fallbackRouting: true }],
113
+ ["claude-fable-5", { reasoningEfforts: ["low", "medium", "high", "xhigh", "max"], maxOutputTokens: null, fallbackRouting: true }],
114
+ ["claude-fable-5-1", { reasoningEfforts: ["low", "medium", "high", "xhigh", "max"], maxOutputTokens: null, fallbackRouting: true }],
115
+ ["claude-mythos-5", { reasoningEfforts: ["low", "medium", "high", "xhigh", "max"], maxOutputTokens: null, fallbackRouting: true }],
116
+ ["claude-mythos-5-1", { reasoningEfforts: ["low", "medium", "high", "xhigh", "max"], maxOutputTokens: null, fallbackRouting: true }]
117
+ ])],
118
+ ["gemini", /* @__PURE__ */ new Map([
119
+ ["gemini-2.0-flash", { reasoningEfforts: [], maxOutputTokens: { "min": 256, "max": 8192 }, fallbackRouting: true }],
120
+ ["gemini-2.5-flash", { reasoningEfforts: ["none", "low", "medium", "high"], maxOutputTokens: { "min": 256, "max": 65536 }, fallbackRouting: true }],
121
+ ["gemini-2.5-pro", { reasoningEfforts: ["low", "medium", "high"], maxOutputTokens: { "min": 256, "max": 65536 }, fallbackRouting: true }],
122
+ ["gemini-3.6-flash", { reasoningEfforts: [], maxOutputTokens: { "min": 256, "max": 8192 }, fallbackRouting: true }],
123
+ ["gemini-2.5-flash-lite", { reasoningEfforts: ["none", "low", "medium", "high"], maxOutputTokens: { "min": 256, "max": 65536 }, fallbackRouting: true }],
124
+ ["gemini-3-flash-preview", { reasoningEfforts: ["minimal", "low", "medium", "high"], maxOutputTokens: { "min": 256, "max": 65536 }, fallbackRouting: true }],
125
+ ["gemini-3.5-flash", { reasoningEfforts: ["minimal", "low", "medium", "high"], maxOutputTokens: { "min": 256, "max": 65536 }, fallbackRouting: true }],
126
+ ["gemini-3.8-flash", { reasoningEfforts: ["minimal", "low", "medium", "high"], maxOutputTokens: { "min": 256, "max": 65536 }, fallbackRouting: true }],
127
+ ["gemini-3.1-pro-preview", { reasoningEfforts: ["low", "high"], maxOutputTokens: { "min": 256, "max": 65536 }, fallbackRouting: true }]
128
+ ])],
129
+ ["deepseek", /* @__PURE__ */ new Map([
130
+ ["deepseek-flash", { reasoningEfforts: ["none", "low", "high", "max"], maxOutputTokens: { "min": 256, "max": 384e3 }, fallbackRouting: true }],
131
+ ["deepseek-chat", { reasoningEfforts: [], maxOutputTokens: { "min": 256, "max": 8192 }, fallbackRouting: true }],
132
+ ["deepseek-reasoner", { reasoningEfforts: [], maxOutputTokens: { "min": 256, "max": 8192 }, fallbackRouting: true }],
133
+ ["deepseek-v4-flash", { reasoningEfforts: ["none", "low", "high", "max"], maxOutputTokens: { "min": 256, "max": 384e3 }, fallbackRouting: true }],
134
+ ["deepseek-v4-flash-vision-exp", { reasoningEfforts: ["none", "low", "high", "max"], maxOutputTokens: { "min": 256, "max": 384e3 }, fallbackRouting: true }],
135
+ ["deepseek-v4-pro", { reasoningEfforts: ["none", "low", "high", "max"], maxOutputTokens: { "min": 256, "max": 384e3 }, fallbackRouting: true }]
136
+ ])],
137
+ ["kimi", /* @__PURE__ */ new Map([
138
+ ["kimi-for-coding", { reasoningEfforts: [], maxOutputTokens: { "min": 256, "max": 32e3 }, fallbackRouting: true }],
139
+ ["kimi-for-coding-highspeed", { reasoningEfforts: [], maxOutputTokens: { "min": 256, "max": 32e3 }, fallbackRouting: true }],
140
+ ["kimi-k2.6", { reasoningEfforts: [], maxOutputTokens: { "min": 256, "max": 32e3 }, fallbackRouting: true }],
141
+ ["kimi-k2.7-code", { reasoningEfforts: [], maxOutputTokens: null, fallbackRouting: true }],
142
+ ["kimi-k2.7-code-highspeed", { reasoningEfforts: [], maxOutputTokens: null, fallbackRouting: true }],
143
+ ["kimi-k3", { reasoningEfforts: ["low", "high", "max"], maxOutputTokens: { "min": 256, "max": 64e3 }, fallbackRouting: true }]
144
+ ])],
145
+ ["cohere", /* @__PURE__ */ new Map([
146
+ ["command-a-03-2025", { reasoningEfforts: [], maxOutputTokens: { "min": 256, "max": 4096 }, fallbackRouting: true }],
147
+ ["command-r", { reasoningEfforts: [], maxOutputTokens: { "min": 256, "max": 4096 }, fallbackRouting: true }],
148
+ ["command-r-plus", { reasoningEfforts: [], maxOutputTokens: { "min": 256, "max": 4096 }, fallbackRouting: true }],
149
+ ["command-a-reasoning-08-2025", { reasoningEfforts: ["none", "high"], maxOutputTokens: { "min": 256, "max": 32e3 }, fallbackRouting: true }]
150
+ ])],
151
+ ["zai", /* @__PURE__ */ new Map([
152
+ ["glm-4-air", { reasoningEfforts: [], maxOutputTokens: { "min": 256, "max": 4096 }, fallbackRouting: true }],
153
+ ["glm-4-flash", { reasoningEfforts: [], maxOutputTokens: { "min": 256, "max": 4096 }, fallbackRouting: true }],
154
+ ["glm-4-plus", { reasoningEfforts: [], maxOutputTokens: { "min": 256, "max": 4096 }, fallbackRouting: true }],
155
+ ["glm-4.5", { reasoningEfforts: ["none", "high"], maxOutputTokens: { "min": 256, "max": 4096 }, fallbackRouting: true }],
156
+ ["glm-4.6", { reasoningEfforts: ["none", "high"], maxOutputTokens: { "min": 256, "max": 4096 }, fallbackRouting: true }],
157
+ ["glm-4.5-air", { reasoningEfforts: ["none", "high"], maxOutputTokens: null, fallbackRouting: true }],
158
+ ["glm-4.7", { reasoningEfforts: ["none", "high"], maxOutputTokens: null, fallbackRouting: true }],
159
+ ["glm-5", { reasoningEfforts: ["none", "high"], maxOutputTokens: null, fallbackRouting: true }],
160
+ ["glm-5.1", { reasoningEfforts: ["none", "high"], maxOutputTokens: null, fallbackRouting: true }],
161
+ ["glm-5.2", { reasoningEfforts: ["none", "high", "max"], maxOutputTokens: null, fallbackRouting: true }],
162
+ ["glm-5.3", { reasoningEfforts: ["low", "high", "max"], maxOutputTokens: null, fallbackRouting: true }],
163
+ ["glm-5.3-flash", { reasoningEfforts: ["low", "high", "max"], maxOutputTokens: null, fallbackRouting: true }]
164
+ ])],
165
+ ["codex", /* @__PURE__ */ new Map([
166
+ ["gpt-5.2", { reasoningEfforts: ["low", "medium", "high", "xhigh"], maxOutputTokens: null, fallbackRouting: false }],
167
+ ["gpt-5.3-codex", { reasoningEfforts: ["low", "medium", "high", "xhigh"], maxOutputTokens: null, fallbackRouting: false }],
168
+ ["gpt-5.3-codex-spark", { reasoningEfforts: [], maxOutputTokens: null, fallbackRouting: false }],
169
+ ["gpt-5.4", { reasoningEfforts: ["low", "medium", "high", "xhigh"], maxOutputTokens: null, fallbackRouting: false }],
170
+ ["gpt-5.4-mini", { reasoningEfforts: ["low", "medium", "high", "xhigh"], maxOutputTokens: null, fallbackRouting: false }],
171
+ ["gpt-5.5", { reasoningEfforts: ["low", "medium", "high", "xhigh"], maxOutputTokens: null, fallbackRouting: false }],
172
+ ["gpt-5.5-codex", { reasoningEfforts: [], maxOutputTokens: null, fallbackRouting: false }],
173
+ ["gpt-5.6", { reasoningEfforts: ["low", "medium", "high", "xhigh", "max", "ultra"], maxOutputTokens: null, fallbackRouting: false }],
174
+ ["gpt-5.6-luna", { reasoningEfforts: ["low", "medium", "high", "xhigh", "max"], maxOutputTokens: null, fallbackRouting: false }],
175
+ ["gpt-5.6-sol", { reasoningEfforts: ["low", "medium", "high", "xhigh", "max", "ultra"], maxOutputTokens: null, fallbackRouting: false }],
176
+ ["gpt-5.6-terra", { reasoningEfforts: ["low", "medium", "high", "xhigh", "max", "ultra"], maxOutputTokens: null, fallbackRouting: false }]
177
+ ])],
178
+ ["openai_compatible", /* @__PURE__ */ new Map([
179
+ ["gpt-6-sol", { reasoningEfforts: ["none", "low", "medium", "high", "xhigh", "max"], chatToolReasoningEfforts: ["none"], maxOutputTokens: null, fallbackRouting: false }],
180
+ ["gpt-6-luna", { reasoningEfforts: ["none", "low", "medium", "high", "xhigh", "max"], chatToolReasoningEfforts: ["none"], maxOutputTokens: null, fallbackRouting: false }]
181
+ ])],
182
+ ["modal", /* @__PURE__ */ new Map([
183
+ ["deepseek-v4.1-flash", { reasoningEfforts: ["none", "low", "high", "xhigh", "max"], maxOutputTokens: { "min": 256, "max": 1048576 }, fallbackRouting: false }],
184
+ ["epoch--ep-deepseek-v4-1-flash-server.us-west.modal.direct", { reasoningEfforts: ["none", "low", "high", "xhigh", "max"], maxOutputTokens: { "min": 256, "max": 1048576 }, fallbackRouting: false }],
185
+ ["kimi-k3", { reasoningEfforts: ["low", "high", "max"], maxOutputTokens: null, fallbackRouting: false }],
186
+ ["moonshot--kimi-k3-fast.modal.run", { reasoningEfforts: ["low", "high", "max"], maxOutputTokens: null, fallbackRouting: false }]
187
+ ])],
188
+ ["fireworks", /* @__PURE__ */ new Map([
189
+ ["deepseek-v4p1-flash", { reasoningEfforts: ["none", "low", "high", "max"], maxOutputTokens: { "min": 256, "max": 384e3 }, fallbackRouting: true }],
190
+ ["deepseek-v4-pro-0813", { reasoningEfforts: ["none", "low", "high", "max"], maxOutputTokens: { "min": 256, "max": 384e3 }, fallbackRouting: true }],
191
+ ["deepseek-v4-flash-0731", { reasoningEfforts: ["none", "low", "high", "max"], maxOutputTokens: { "min": 256, "max": 384e3 }, fallbackRouting: true }],
192
+ ["deepseek-v4-flash-vision-exp", { reasoningEfforts: ["none", "low", "high", "max"], maxOutputTokens: { "min": 256, "max": 384e3 }, fallbackRouting: true }],
193
+ ["kimi-k3", { reasoningEfforts: ["none", "low", "high", "max"], maxOutputTokens: null, fallbackRouting: true }],
194
+ ["gpt-oss-120b", { reasoningEfforts: ["low", "medium", "high"], maxOutputTokens: null, fallbackRouting: true }],
195
+ ["qwen3p8-max", { reasoningEfforts: ["none", "low", "medium", "high", "xhigh", "max"], maxOutputTokens: null, fallbackRouting: true }],
196
+ ["glm-5p2", { reasoningEfforts: ["none", "high", "max"], maxOutputTokens: null, fallbackRouting: true }],
197
+ ["glm-5p3", { reasoningEfforts: ["low", "high", "max"], maxOutputTokens: null, fallbackRouting: true }],
198
+ ["glm-5p3-flash", { reasoningEfforts: ["low", "high", "max"], maxOutputTokens: null, fallbackRouting: true }],
199
+ ["ember-1", { reasoningEfforts: ["none", "high"], maxOutputTokens: null, fallbackRouting: true }],
200
+ ["minimax-m3", { reasoningEfforts: ["none", "high"], maxOutputTokens: null, fallbackRouting: true }],
201
+ ["nemotron-lightning-3p5-30b-a3b", { reasoningEfforts: ["none", "high"], maxOutputTokens: null, fallbackRouting: true }],
202
+ ["muse-glimmer-30b", { reasoningEfforts: [], maxOutputTokens: null, fallbackRouting: false }],
203
+ ["kimi-k2p7-code", { reasoningEfforts: [], maxOutputTokens: null, fallbackRouting: false }],
204
+ ["nemotron-3-ultra-nvfp4", { reasoningEfforts: [], maxOutputTokens: null, fallbackRouting: false }],
205
+ ["inkling", { reasoningEfforts: [], maxOutputTokens: null, fallbackRouting: false }],
206
+ ["kimi-k2p6", { reasoningEfforts: [], maxOutputTokens: null, fallbackRouting: false }]
207
+ ])]
208
+ ]);
209
+ CONTRACT_PROVIDERS = Object.freeze(["anthropic", "openai", "gemini", "deepseek", "fireworks", "kimi", "voyage", "cohere", "elevenlabs", "replicate", "fal", "zai", "modal", "codex", "self_hosted", "openai_compatible", "typesafe"]);
210
+ }
211
+ });
212
+
19
213
  // src/agent/backends/sse.ts
20
214
  async function* parseSSEEvents(body) {
21
215
  const decoder = new TextDecoder();
@@ -78,255 +272,6 @@ var init_sse = __esm({
78
272
  }
79
273
  });
80
274
 
81
- // src/agent/backends/provider_ids.ts
82
- function normalizeProviderId(provider) {
83
- const normalized = provider.trim().toLowerCase();
84
- if (CODEX_APP_SERVER_ALIASES.has(normalized)) {
85
- return CODEX_APP_SERVER_PROVIDER;
86
- }
87
- if (CODEX_LEGACY_ALIASES.has(normalized)) {
88
- return CODEX_LEGACY_PROVIDER;
89
- }
90
- return normalized;
91
- }
92
- function isCodexAppServerProvider(provider) {
93
- return normalizeProviderId(provider) === CODEX_APP_SERVER_PROVIDER;
94
- }
95
- function isCodexLegacyProvider(provider) {
96
- return normalizeProviderId(provider) === CODEX_LEGACY_PROVIDER;
97
- }
98
- var CODEX_APP_SERVER_PROVIDER, CODEX_LEGACY_PROVIDER, CODEX_LEGACY_ALIAS, CODEX_APP_SERVER_SENTINEL, CODEX_APP_SERVER_ALIASES, CODEX_LEGACY_ALIASES;
99
- var init_provider_ids = __esm({
100
- "src/agent/backends/provider_ids.ts"() {
101
- "use strict";
102
- CODEX_APP_SERVER_PROVIDER = "codex_app_server";
103
- CODEX_LEGACY_PROVIDER = "codex_legacy";
104
- CODEX_LEGACY_ALIAS = "codex";
105
- CODEX_APP_SERVER_SENTINEL = "codex-app-server";
106
- CODEX_APP_SERVER_ALIASES = /* @__PURE__ */ new Set([
107
- CODEX_APP_SERVER_PROVIDER,
108
- "codex-official"
109
- ]);
110
- CODEX_LEGACY_ALIASES = /* @__PURE__ */ new Set([
111
- CODEX_LEGACY_PROVIDER,
112
- CODEX_LEGACY_ALIAS,
113
- "codex-chatgpt-legacy"
114
- ]);
115
- }
116
- });
117
-
118
- // src/utils/deepseek.ts
119
- function isModalDeepSeekFlashModel(model) {
120
- return model?.toLowerCase().replace(/^modal\//, "") === MODAL_DEEPSEEK_FLASH_MODEL;
121
- }
122
- function isDeepSeekFlashModel(model) {
123
- if (isModalDeepSeekFlashModel(model)) return true;
124
- return model !== void 0 && [
125
- DEEPSEEK_FLASH_MODEL,
126
- "deepseek-v4-flash",
127
- "deepseek-v4-flash-vision-exp"
128
- ].includes(model.toLowerCase().replace(/^deepseek\//, ""));
129
- }
130
- function deepseekRequestParams(request) {
131
- if (!isDeepSeekFlashModel(request.model)) return {};
132
- if (isModalDeepSeekFlashModel(request.model)) {
133
- const effort2 = request.codexEffort;
134
- return effort2 && MODAL_DEEPSEEK_FLASH_EFFORTS.includes(effort2) ? { reasoning_effort: effort2 } : {};
135
- }
136
- if (request.codexEffort === "none") return { thinking: { type: "disabled" } };
137
- const effort = request.codexEffort;
138
- const reasoning = effort === "minimal" || effort === "low" ? "low" : effort === "max" || effort === "ultra" ? "max" : "high";
139
- return { thinking: { type: "enabled" }, reasoning_effort: reasoning };
140
- }
141
- var DEEPSEEK_FLASH_MODEL, MODAL_DEEPSEEK_FLASH_MODEL, MODAL_DEEPSEEK_FLASH_CONTEXT, MODAL_DEEPSEEK_FLASH_MAX_OUTPUT, MODAL_DEEPSEEK_FLASH_EFFORTS, DEEPSEEK_FLASH_CONTEXT, DEEPSEEK_FLASH_MAX_OUTPUT, DEEPSEEK_FLASH_EFFORTS;
142
- var init_deepseek = __esm({
143
- "src/utils/deepseek.ts"() {
144
- "use strict";
145
- DEEPSEEK_FLASH_MODEL = "deepseek-flash";
146
- MODAL_DEEPSEEK_FLASH_MODEL = "deepseek-v4.1-flash";
147
- MODAL_DEEPSEEK_FLASH_CONTEXT = 1048576;
148
- MODAL_DEEPSEEK_FLASH_MAX_OUTPUT = 1048576;
149
- MODAL_DEEPSEEK_FLASH_EFFORTS = ["none", "low", "high", "xhigh", "max"];
150
- DEEPSEEK_FLASH_CONTEXT = 1e6;
151
- DEEPSEEK_FLASH_MAX_OUTPUT = 384e3;
152
- DEEPSEEK_FLASH_EFFORTS = ["none", "low", "high", "max"];
153
- }
154
- });
155
-
156
- // src/utils/models.ts
157
- function getKimiModelInfo(model) {
158
- if (!model) return void 0;
159
- const id = KIMI_ALIAS_TO_ID.get(model.toLowerCase());
160
- return id ? KIMI_MODEL_INFO[id] : void 0;
161
- }
162
- function getOfficialCodexModelInfo(model) {
163
- if (!model) return void 0;
164
- const normalized = model.toLowerCase() === "gpt-5.6" ? "gpt-5.6-sol" : model.toLowerCase();
165
- if (!OFFICIAL_CODEX_MODEL_IDS.includes(normalized)) return void 0;
166
- return OFFICIAL_CODEX_MODEL_INFO[normalized];
167
- }
168
- function getSupportedClaudeReasoningEfforts(model) {
169
- if (!model) return void 0;
170
- const id = model.toLowerCase();
171
- if (!id.startsWith("claude")) return void 0;
172
- if (id.includes("haiku")) return [];
173
- if (id.includes("opus") || id.includes("sonnet")) return CLAUDE_REASONING_EFFORTS;
174
- return void 0;
175
- }
176
- function getSupportedCodexReasoningEfforts(model, provider) {
177
- if (isModalDeepSeekFlashModel(model)) return MODAL_DEEPSEEK_FLASH_EFFORTS;
178
- if (isDeepSeekFlashModel(model)) return DEEPSEEK_FLASH_EFFORTS;
179
- const kimiInfo = getKimiModelInfo(model);
180
- if (kimiInfo) return kimiInfo.supportedReasoningEfforts ?? [];
181
- const appServerEfforts = getOfficialCodexModelInfo(model)?.supportedReasoningEfforts;
182
- if (!appServerEfforts) return getSupportedClaudeReasoningEfforts(model);
183
- if ((provider === "openai" || provider === "openai_compatible") && (model === "gpt-6-sol" || model === "gpt-6-luna")) {
184
- return GPT_6_API_EFFORTS;
185
- }
186
- if (provider && isCodexLegacyProvider(provider)) return CODEX_LEGACY_EFFORTS;
187
- if (provider === void 0) {
188
- return [.../* @__PURE__ */ new Set([...appServerEfforts, ...CODEX_LEGACY_EFFORTS])];
189
- }
190
- return appServerEfforts;
191
- }
192
- function isCodexReasoningEffortSupported(provider, model, effort) {
193
- const supported = getSupportedCodexReasoningEfforts(model, provider);
194
- return supported === void 0 || supported.includes(effort);
195
- }
196
- var OFFICIAL_CODEX_MODEL_IDS, STANDARD_CODEX_EFFORTS, GPT_5_6_EFFORTS, GPT_5_6_ULTRA_EFFORTS, GPT_6_API_EFFORTS, CODEX_LEGACY_EFFORTS, OFFICIAL_CODEX_MODEL_INFO, KIMI_REASONING_EFFORTS, KIMI_MODEL_INFO, KIMI_ALIAS_TO_ID, CLAUDE_REASONING_EFFORTS;
197
- var init_models = __esm({
198
- "src/utils/models.ts"() {
199
- "use strict";
200
- init_provider_ids();
201
- init_deepseek();
202
- OFFICIAL_CODEX_MODEL_IDS = [
203
- "gpt-6-sol",
204
- "gpt-6-luna",
205
- "gpt-5.6-sol",
206
- "gpt-5.6-terra",
207
- "gpt-5.6-luna",
208
- "gpt-5.5",
209
- "gpt-5.4",
210
- "gpt-5.4-mini",
211
- "gpt-5.3-codex",
212
- "gpt-5.2"
213
- ];
214
- STANDARD_CODEX_EFFORTS = ["low", "medium", "high", "xhigh"];
215
- GPT_5_6_EFFORTS = [...STANDARD_CODEX_EFFORTS, "max"];
216
- GPT_5_6_ULTRA_EFFORTS = [...GPT_5_6_EFFORTS, "ultra"];
217
- GPT_6_API_EFFORTS = ["none", "low", "medium", "high", "xhigh", "max"];
218
- CODEX_LEGACY_EFFORTS = ["none", "minimal", ...STANDARD_CODEX_EFFORTS];
219
- OFFICIAL_CODEX_MODEL_INFO = {
220
- "gpt-6-sol": {
221
- displayName: "GPT-6 Sol",
222
- description: "Frontier agentic coding model.",
223
- defaultReasoningEffort: "medium",
224
- supportedReasoningEfforts: GPT_5_6_ULTRA_EFFORTS
225
- },
226
- "gpt-6-luna": {
227
- displayName: "GPT-6 Luna",
228
- description: "Fast and affordable model for everyday work.",
229
- defaultReasoningEffort: "medium",
230
- supportedReasoningEfforts: GPT_5_6_EFFORTS
231
- },
232
- "gpt-5.6-sol": {
233
- displayName: "GPT-5.6 Sol",
234
- description: "Latest frontier agentic coding model.",
235
- defaultReasoningEffort: "low",
236
- supportedReasoningEfforts: GPT_5_6_ULTRA_EFFORTS
237
- },
238
- "gpt-5.6-terra": {
239
- displayName: "GPT-5.6 Terra",
240
- description: "Balanced agentic coding model for everyday work.",
241
- defaultReasoningEffort: "medium",
242
- supportedReasoningEfforts: GPT_5_6_ULTRA_EFFORTS
243
- },
244
- "gpt-5.6-luna": {
245
- displayName: "GPT-5.6 Luna",
246
- description: "Fast and affordable agentic coding model.",
247
- defaultReasoningEffort: "medium",
248
- supportedReasoningEfforts: GPT_5_6_EFFORTS
249
- },
250
- "gpt-5.5": {
251
- displayName: "GPT-5.5",
252
- description: "Frontier model for complex coding, research, and real-world work.",
253
- defaultReasoningEffort: "medium",
254
- supportedReasoningEfforts: STANDARD_CODEX_EFFORTS
255
- },
256
- "gpt-5.4": {
257
- displayName: "GPT-5.4",
258
- description: "Strong model for everyday coding.",
259
- defaultReasoningEffort: "medium",
260
- supportedReasoningEfforts: STANDARD_CODEX_EFFORTS
261
- },
262
- "gpt-5.4-mini": {
263
- displayName: "GPT-5.4 Mini",
264
- description: "Small, fast, and cost-efficient model for simpler coding tasks.",
265
- defaultReasoningEffort: "medium",
266
- supportedReasoningEfforts: STANDARD_CODEX_EFFORTS
267
- },
268
- "gpt-5.3-codex": {
269
- displayName: "GPT-5.3 Codex",
270
- description: "Codex-tuned agentic coding model.",
271
- defaultReasoningEffort: "medium",
272
- supportedReasoningEfforts: STANDARD_CODEX_EFFORTS
273
- },
274
- "gpt-5.2": {
275
- displayName: "GPT-5.2",
276
- description: "Earlier general-purpose Codex model.",
277
- defaultReasoningEffort: "medium",
278
- supportedReasoningEfforts: STANDARD_CODEX_EFFORTS
279
- }
280
- };
281
- KIMI_REASONING_EFFORTS = ["low", "high", "max"];
282
- KIMI_MODEL_INFO = {
283
- "kimi-k3": {
284
- displayName: "Kimi K3",
285
- description: "Moonshot flagship \u2014 1M context, reasoning on by default, vision and tools.",
286
- supportedReasoningEfforts: KIMI_REASONING_EFFORTS,
287
- defaultReasoningEffort: "max",
288
- aliases: ["k3", "kimi k3", "moonshot-kimi-k3", "moonshotai/kimi-k3"]
289
- },
290
- "kimi-k2.6": {
291
- displayName: "Kimi K2.6",
292
- description: "Smaller, cheaper Kimi \u2014 256K context, multimodal.",
293
- // No supportedReasoningEfforts: Moonshot lists `reasoning_effort` as "Not
294
- // supported" for K2.6, which instead takes a `thinking: {type}` object. ZTC
295
- // does not send that object yet, so the picker must not offer a depth control
296
- // here — the effort ladder and the thinking switch are different contracts.
297
- aliases: ["k2.6", "kimi k2.6", "moonshotai/kimi-k2.6"]
298
- },
299
- "kimi-for-coding": {
300
- displayName: "Kimi K2.7 Coding",
301
- description: "Kimi Code subscription coding model \u2014 256K context, always-thinking, vision and tools.",
302
- // No supportedReasoningEfforts: the subscription coding models are
303
- // always-thinking with no documented effort ladder, so /effort stays
304
- // rejected rather than forwarding a value the API silently ignores.
305
- aliases: ["k2.7", "kimi k2.7", "kimi-k2.7-coding"]
306
- },
307
- "kimi-for-coding-highspeed": {
308
- displayName: "Kimi K2.7 Coding Highspeed",
309
- description: "High-speed variant of the Kimi Code subscription coding model \u2014 256K context.",
310
- aliases: ["k2.7-highspeed", "kimi-for-coding-fast"]
311
- }
312
- };
313
- KIMI_ALIAS_TO_ID = /* @__PURE__ */ new Map();
314
- for (const id of Object.keys(KIMI_MODEL_INFO)) {
315
- KIMI_ALIAS_TO_ID.set(id, id);
316
- for (const alias of KIMI_MODEL_INFO[id].aliases) {
317
- KIMI_ALIAS_TO_ID.set(alias.toLowerCase(), id);
318
- }
319
- }
320
- CLAUDE_REASONING_EFFORTS = [
321
- "low",
322
- "medium",
323
- "high",
324
- "xhigh",
325
- "max"
326
- ];
327
- }
328
- });
329
-
330
275
  // src/agent/backends/anthropic.ts
331
276
  function anthropicInputTokens(usage) {
332
277
  if (!usage) return 0;
@@ -336,8 +281,8 @@ var EXTENDED_CACHE_BETA, AnthropicBackend, AnthropicApiError;
336
281
  var init_anthropic = __esm({
337
282
  "src/agent/backends/anthropic.ts"() {
338
283
  "use strict";
284
+ init_model_contracts();
339
285
  init_sse();
340
- init_models();
341
286
  EXTENDED_CACHE_BETA = "extended-cache-ttl-2025-04-11";
342
287
  AnthropicBackend = class {
343
288
  apiKey;
@@ -416,8 +361,8 @@ var init_anthropic = __esm({
416
361
  }
417
362
  }
418
363
  const effort = request.codexEffort;
419
- const supportedEfforts = getSupportedClaudeReasoningEfforts(request.model);
420
- const modelAllowsEffort = supportedEfforts === void 0 || effort !== void 0 && supportedEfforts.includes(effort);
364
+ const supportedEfforts = modelContract("anthropic", request.model)?.reasoningEfforts ?? [];
365
+ const modelAllowsEffort = effort !== void 0 && supportedEfforts.includes(effort);
421
366
  const outputConfig = effort && ["low", "medium", "high", "xhigh", "max"].includes(effort) && modelAllowsEffort ? { output_config: { effort } } : {};
422
367
  return {
423
368
  model: request.model,
@@ -854,52 +799,495 @@ function buildOpenAIMessages(request) {
854
799
  if (msg.reasoning) {
855
800
  assistantMsg.reasoning_content = msg.reasoning;
856
801
  }
857
- out.push(assistantMsg);
858
- continue;
859
- }
860
- if (toolResults.length > 0) {
861
- for (const tr of toolResults) {
862
- const resultText = typeof tr.content === "string" ? tr.content : tr.content.map((b) => b.type === "text" ? b.text || "" : "[image]").join("\n");
863
- out.push({
864
- role: "tool",
865
- tool_call_id: tr.tool_use_id,
866
- content: tr.is_error && !/^error\b/i.test(resultText) ? `Error: ${resultText}` : resultText
867
- });
802
+ out.push(assistantMsg);
803
+ continue;
804
+ }
805
+ if (toolResults.length > 0) {
806
+ for (const tr of toolResults) {
807
+ const resultText = typeof tr.content === "string" ? tr.content : tr.content.map((b) => b.type === "text" ? b.text || "" : "[image]").join("\n");
808
+ out.push({
809
+ role: "tool",
810
+ tool_call_id: tr.tool_use_id,
811
+ content: tr.is_error && !/^error\b/i.test(resultText) ? `Error: ${resultText}` : resultText
812
+ });
813
+ }
814
+ if (textParts.length > 0 || mediaParts.length > 0) {
815
+ if (mediaParts.length > 0) {
816
+ const parts = textParts.map((t) => ({ type: "text", text: t }));
817
+ parts.push(...mediaParts);
818
+ out.push({ role: "user", content: parts });
819
+ } else {
820
+ out.push({ role: "user", content: textParts.join("\n") });
821
+ }
822
+ }
823
+ continue;
824
+ }
825
+ if (mediaParts.length > 0) {
826
+ const parts = textParts.map((t) => ({ type: "text", text: t }));
827
+ parts.push(...mediaParts);
828
+ out.push(withReasoning({ role: msg.role, content: parts }, msg));
829
+ } else {
830
+ out.push(withReasoning({ role: msg.role, content: textParts.join("\n") }, msg));
831
+ }
832
+ }
833
+ return out;
834
+ }
835
+ function buildOpenAIToolDefs(tools) {
836
+ return tools.map((t) => ({
837
+ type: "function",
838
+ function: {
839
+ name: t.name,
840
+ description: t.description,
841
+ parameters: t.parameters
842
+ }
843
+ }));
844
+ }
845
+ var init_openai_format = __esm({
846
+ "src/agent/backends/openai_format.ts"() {
847
+ "use strict";
848
+ }
849
+ });
850
+
851
+ // src/config/profiles.ts
852
+ import { homedir } from "node:os";
853
+ import { join } from "node:path";
854
+ import { existsSync } from "node:fs";
855
+ import { readdir } from "node:fs/promises";
856
+ function builtinProfileEmulation(name) {
857
+ const builtin = BUILTIN_CONFIG_PROFILES.find((profile) => profile === name);
858
+ if (!builtin) return void 0;
859
+ const root = process.env.ZTC_CONFIG_DIR || join(homedir(), ".ztc");
860
+ return existsSync(join(root, "profiles", name)) ? void 0 : builtin;
861
+ }
862
+ function validateProfileName(name) {
863
+ if (name !== "default" && !/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(name)) {
864
+ throw new Error("Profile name must be 1\u201364 characters: letters, numbers, dots, underscores, or hyphens; it must start with a letter or number.");
865
+ }
866
+ if (name === "." || name === "..") {
867
+ throw new Error("Profile name cannot be . or ..");
868
+ }
869
+ return name;
870
+ }
871
+ function activeProfile() {
872
+ return validateProfileName(process.env.ZTC_PROFILE || "default");
873
+ }
874
+ function profileConfigDir(profile = activeProfile()) {
875
+ const root = process.env.ZTC_CONFIG_DIR || join(homedir(), ".ztc");
876
+ const name = validateProfileName(profile);
877
+ if (name === "default") return root;
878
+ const namedDir = join(root, "profiles", name);
879
+ return builtinProfileEmulation(name) ? root : namedDir;
880
+ }
881
+ async function listConfigProfiles() {
882
+ const root = process.env.ZTC_CONFIG_DIR || join(homedir(), ".ztc");
883
+ let entries;
884
+ try {
885
+ entries = await readdir(join(root, "profiles"), { withFileTypes: true });
886
+ } catch (error) {
887
+ if (error.code === "ENOENT") {
888
+ return [...BUILTIN_CONFIG_PROFILES, "default", activeProfile()].filter((name, index, names) => names.indexOf(name) === index).sort(profileOrder);
889
+ }
890
+ throw error;
891
+ }
892
+ const named = entries.filter((entry) => entry.isDirectory() && entry.name !== "default").map((entry) => entry.name).filter((name) => {
893
+ try {
894
+ validateProfileName(name);
895
+ return true;
896
+ } catch {
897
+ return false;
898
+ }
899
+ });
900
+ const profiles = /* @__PURE__ */ new Set([activeProfile(), ...BUILTIN_CONFIG_PROFILES, ...named]);
901
+ profiles.delete("default");
902
+ return ["default", ...profiles].sort(profileOrder);
903
+ }
904
+ function profileOrder(left, right) {
905
+ const rank = (name) => {
906
+ if (name === "default") return 0;
907
+ const index = BUILTIN_CONFIG_PROFILES.findIndex((profile) => profile === name);
908
+ return index < 0 ? Number.MAX_SAFE_INTEGER : index + 1;
909
+ };
910
+ return rank(left) - rank(right) || left.localeCompare(right);
911
+ }
912
+ var BUILTIN_CONFIG_PROFILES;
913
+ var init_profiles = __esm({
914
+ "src/config/profiles.ts"() {
915
+ "use strict";
916
+ BUILTIN_CONFIG_PROFILES = ["ztc", "ztc-coder", "ztc-datascience", "scientist"];
917
+ }
918
+ });
919
+
920
+ // src/utils/router_catalog.ts
921
+ import { createHash, randomUUID as randomUUID2 } from "node:crypto";
922
+ import { statSync, readFileSync } from "node:fs";
923
+ import { dirname, join as join2 } from "node:path";
924
+ function record2(value) {
925
+ return value !== null && typeof value === "object" && !Array.isArray(value);
926
+ }
927
+ function identifier(value) {
928
+ return typeof value === "string" && value.length > 0 && value.length <= 512 && !/[\x00-\x1f\x7f]/.test(value);
929
+ }
930
+ function identifiers(value, max) {
931
+ return Array.isArray(value) && value.length <= max && value.every(identifier);
932
+ }
933
+ function descriptor(value, legacy) {
934
+ if (!record2(value)) return void 0;
935
+ const id = value.id ?? (legacy ? value.name : void 0);
936
+ if (!identifier(id) || !identifier(value.owned_by)) return void 0;
937
+ const result = { id, owned_by: value.owned_by };
938
+ if (value.object !== void 0 && value.object !== "model") return void 0;
939
+ if (value.object === "model") result.object = "model";
940
+ for (const key of ["display_name", "provider_display_name"]) {
941
+ if (value[key] !== void 0) {
942
+ if (!identifier(value[key])) return void 0;
943
+ result[key] = value[key];
944
+ }
945
+ }
946
+ for (const key of ["is_default", "available"]) {
947
+ if (value[key] !== void 0) {
948
+ if (typeof value[key] !== "boolean") return void 0;
949
+ result[key] = value[key];
950
+ }
951
+ }
952
+ if (value.aliases !== void 0) {
953
+ if (!identifiers(value.aliases, 32)) return void 0;
954
+ result.aliases = value.aliases;
955
+ }
956
+ if (value.capabilities !== void 0) {
957
+ const caps = value.capabilities;
958
+ if (!record2(caps) || typeof caps.chat !== "boolean") return void 0;
959
+ const capabilities = { chat: caps.chat };
960
+ for (const key of ["tools", "images"]) {
961
+ if (caps[key] !== void 0) {
962
+ if (typeof caps[key] !== "boolean") return void 0;
963
+ capabilities[key] = caps[key];
964
+ }
965
+ }
966
+ for (const key of ["context_window", "max_output_tokens"]) {
967
+ if (caps[key] !== void 0) {
968
+ if (typeof caps[key] !== "number" || !Number.isSafeInteger(caps[key]) || caps[key] <= 0) return void 0;
969
+ capabilities[key] = caps[key];
970
+ }
971
+ }
972
+ if (caps.reasoning !== void 0) {
973
+ const reasoning = caps.reasoning;
974
+ if (!record2(reasoning) || !identifiers(reasoning.choices, 32) || reasoning.default !== void 0 && !identifier(reasoning.default)) return void 0;
975
+ capabilities.reasoning = { choices: reasoning.choices, ...reasoning.default === void 0 ? {} : { default: reasoning.default } };
976
+ }
977
+ result.capabilities = capabilities;
978
+ }
979
+ return result;
980
+ }
981
+ function resolveRouterModel(catalog, provider, model) {
982
+ const owner = provider === "moonshot" ? "kimi" : provider;
983
+ const rows = catalog.models.filter((row) => row.owned_by === owner);
984
+ const exact = rows.find((row) => row.id === model);
985
+ if (exact) return exact;
986
+ const matches = rows.filter((row) => row.id === `${owner}/${model}` || row.aliases?.includes(model));
987
+ return matches.length === 1 ? matches[0] : void 0;
988
+ }
989
+ function routerCatalogScope(connection) {
990
+ const key = createHash("sha256").update(JSON.stringify([connection.origin.replace(/\/+$/, ""), connection.token, connection.workspaceId ?? ""])).digest("hex");
991
+ return join2(profileConfigDir(), `router-models-${key}.json`);
992
+ }
993
+ function parseCatalog(value) {
994
+ if (!record2(value)) throw new Error("Invalid Router catalog");
995
+ if (value.model_contract_version !== void 0 && value.model_contract_version !== 1) throw new Error("Unsupported Router model contract");
996
+ const data = value.data ?? value.models ?? value.items ?? [];
997
+ if (!Array.isArray(data) || data.length > 4096) throw new Error("Invalid Router catalog");
998
+ const version = value.model_contract_version === 1 ? 1 : 0;
999
+ return { version, models: data.flatMap((value2) => {
1000
+ const row = descriptor(value2, version === 0);
1001
+ return row ? [row] : [];
1002
+ }) };
1003
+ }
1004
+ function readRouterCatalog(connection) {
1005
+ const file = routerCatalogScope(connection);
1006
+ try {
1007
+ const stat3 = statSync(file, { bigint: true });
1008
+ const stamp = `${stat3.mtimeNs}:${stat3.size}:${stat3.ino}`;
1009
+ const previous = memo.get(file);
1010
+ if (previous?.stamp === stamp) return previous.catalog;
1011
+ const saved = JSON.parse(readFileSync(file, "utf8"));
1012
+ const parsed = parseCatalog(saved);
1013
+ const catalog = { ...parsed, cached: true, fetchedAt: Number(saved.fetchedAt) || 0 };
1014
+ if (memo.size >= 32) memo.delete(memo.keys().next().value);
1015
+ memo.set(file, { stamp, catalog });
1016
+ return catalog;
1017
+ } catch {
1018
+ memo.delete(file);
1019
+ return void 0;
1020
+ }
1021
+ }
1022
+ var memo;
1023
+ var init_router_catalog = __esm({
1024
+ "src/utils/router_catalog.ts"() {
1025
+ "use strict";
1026
+ init_profiles();
1027
+ memo = /* @__PURE__ */ new Map();
1028
+ }
1029
+ });
1030
+
1031
+ // src/agent/backends/provider_ids.ts
1032
+ function normalizeProviderId(provider) {
1033
+ const normalized = provider.trim().toLowerCase();
1034
+ if (CODEX_APP_SERVER_ALIASES.has(normalized)) {
1035
+ return CODEX_APP_SERVER_PROVIDER;
1036
+ }
1037
+ if (CODEX_LEGACY_ALIASES.has(normalized)) {
1038
+ return CODEX_LEGACY_PROVIDER;
1039
+ }
1040
+ return normalized;
1041
+ }
1042
+ function isCodexAppServerProvider(provider) {
1043
+ return normalizeProviderId(provider) === CODEX_APP_SERVER_PROVIDER;
1044
+ }
1045
+ function isCodexLegacyProvider(provider) {
1046
+ return normalizeProviderId(provider) === CODEX_LEGACY_PROVIDER;
1047
+ }
1048
+ function isLocalSubscriptionProvider(provider) {
1049
+ return ["codex_app_server", "codex_legacy", "claude_code", "claude-code", "claude_code_direct", "claude-code-direct", "kimi_code", "kimi-code"].includes(normalizeProviderId(provider));
1050
+ }
1051
+ var CODEX_APP_SERVER_PROVIDER, CODEX_LEGACY_PROVIDER, CODEX_LEGACY_ALIAS, CODEX_APP_SERVER_SENTINEL, CODEX_APP_SERVER_ALIASES, CODEX_LEGACY_ALIASES;
1052
+ var init_provider_ids = __esm({
1053
+ "src/agent/backends/provider_ids.ts"() {
1054
+ "use strict";
1055
+ CODEX_APP_SERVER_PROVIDER = "codex_app_server";
1056
+ CODEX_LEGACY_PROVIDER = "codex_legacy";
1057
+ CODEX_LEGACY_ALIAS = "codex";
1058
+ CODEX_APP_SERVER_SENTINEL = "codex-app-server";
1059
+ CODEX_APP_SERVER_ALIASES = /* @__PURE__ */ new Set([
1060
+ CODEX_APP_SERVER_PROVIDER,
1061
+ "codex-official"
1062
+ ]);
1063
+ CODEX_LEGACY_ALIASES = /* @__PURE__ */ new Set([
1064
+ CODEX_LEGACY_PROVIDER,
1065
+ CODEX_LEGACY_ALIAS,
1066
+ "codex-chatgpt-legacy"
1067
+ ]);
1068
+ }
1069
+ });
1070
+
1071
+ // src/utils/deepseek.ts
1072
+ function isModalDeepSeekFlashModel(model) {
1073
+ return model?.toLowerCase().replace(/^modal\//, "") === MODAL_DEEPSEEK_FLASH_MODEL;
1074
+ }
1075
+ function isDeepSeekFlashModel(model) {
1076
+ if (isModalDeepSeekFlashModel(model)) return true;
1077
+ return model !== void 0 && [
1078
+ DEEPSEEK_FLASH_MODEL,
1079
+ "deepseek-v4-flash",
1080
+ "deepseek-v4-flash-vision-exp"
1081
+ ].includes(model.toLowerCase().replace(/^deepseek\//, "")) || model !== void 0 && ["deepseek-v4p1-flash", "deepseek-v4-flash-0731"].includes(normalizeContractModel("fireworks", model));
1082
+ }
1083
+ function deepseekRequestParams(request) {
1084
+ if (!isDeepSeekFlashModel(request.model)) {
1085
+ return reasoningRequestParams("deepseek", request.model, request.codexEffort);
1086
+ }
1087
+ if (normalizeContractModel("fireworks", request.model) === "deepseek-v4p1-flash" || request.model.toLowerCase().startsWith("fireworks/")) {
1088
+ return reasoningRequestParams("fireworks", request.model, request.codexEffort);
1089
+ }
1090
+ if (isModalDeepSeekFlashModel(request.model)) {
1091
+ const effort2 = request.codexEffort;
1092
+ return effort2 && MODAL_DEEPSEEK_FLASH_EFFORTS.includes(effort2) ? { reasoning_effort: effort2 } : {};
1093
+ }
1094
+ if (request.codexEffort === "none") return { thinking: { type: "disabled" } };
1095
+ const effort = request.codexEffort;
1096
+ const reasoning = effort === "minimal" || effort === "low" ? "low" : effort === "max" || effort === "ultra" ? "max" : "high";
1097
+ return { thinking: { type: "enabled" }, reasoning_effort: reasoning };
1098
+ }
1099
+ var DEEPSEEK_FLASH_MODEL, MODAL_DEEPSEEK_FLASH_MODEL, MODAL_DEEPSEEK_FLASH_CONTEXT, MODAL_DEEPSEEK_FLASH_MAX_OUTPUT, MODAL_DEEPSEEK_FLASH_EFFORTS, DEEPSEEK_FLASH_CONTEXT, DEEPSEEK_FLASH_MAX_OUTPUT, DEEPSEEK_FLASH_EFFORTS;
1100
+ var init_deepseek = __esm({
1101
+ "src/utils/deepseek.ts"() {
1102
+ "use strict";
1103
+ init_model_contracts();
1104
+ DEEPSEEK_FLASH_MODEL = "deepseek-flash";
1105
+ MODAL_DEEPSEEK_FLASH_MODEL = "deepseek-v4.1-flash";
1106
+ MODAL_DEEPSEEK_FLASH_CONTEXT = 1048576;
1107
+ MODAL_DEEPSEEK_FLASH_MAX_OUTPUT = 1048576;
1108
+ MODAL_DEEPSEEK_FLASH_EFFORTS = modelContract("modal", MODAL_DEEPSEEK_FLASH_MODEL).reasoningEfforts;
1109
+ DEEPSEEK_FLASH_CONTEXT = 1e6;
1110
+ DEEPSEEK_FLASH_MAX_OUTPUT = 384e3;
1111
+ DEEPSEEK_FLASH_EFFORTS = modelContract("deepseek", DEEPSEEK_FLASH_MODEL).reasoningEfforts;
1112
+ }
1113
+ });
1114
+
1115
+ // src/utils/models.ts
1116
+ function getKimiModelInfo(model) {
1117
+ if (!model) return void 0;
1118
+ const id = KIMI_ALIAS_TO_ID.get(model.toLowerCase());
1119
+ return id ? KIMI_MODEL_INFO[id] : void 0;
1120
+ }
1121
+ function getOfficialCodexModelInfo(model) {
1122
+ if (!model) return void 0;
1123
+ const normalized = model.toLowerCase() === "gpt-5.6" ? "gpt-5.6-sol" : model.toLowerCase();
1124
+ if (!OFFICIAL_CODEX_MODEL_IDS.includes(normalized)) return void 0;
1125
+ return OFFICIAL_CODEX_MODEL_INFO[normalized];
1126
+ }
1127
+ function getSupportedClaudeReasoningEfforts(model) {
1128
+ if (!model) return void 0;
1129
+ const id = model.toLowerCase();
1130
+ if (!id.startsWith("claude")) return void 0;
1131
+ if (id.includes("haiku")) return [];
1132
+ if (id.includes("opus") || id.includes("sonnet")) return CLAUDE_REASONING_EFFORTS;
1133
+ return void 0;
1134
+ }
1135
+ function getSupportedCodexReasoningEfforts(model, provider, surface = provider === "openai_compatible" ? "chat_tools" : "native_api") {
1136
+ if (provider && model && CONTRACT_PROVIDERS.includes(normalizeContractProvider(provider))) {
1137
+ return modelContract(provider, model, surface)?.reasoningEfforts ?? [];
1138
+ }
1139
+ if (isModalDeepSeekFlashModel(model)) return MODAL_DEEPSEEK_FLASH_EFFORTS;
1140
+ if (isDeepSeekFlashModel(model)) return DEEPSEEK_FLASH_EFFORTS;
1141
+ const kimiInfo = getKimiModelInfo(model);
1142
+ if (kimiInfo) return kimiInfo.supportedReasoningEfforts ?? [];
1143
+ const appServerEfforts = getOfficialCodexModelInfo(model)?.supportedReasoningEfforts;
1144
+ if (!appServerEfforts) return getSupportedClaudeReasoningEfforts(model);
1145
+ if ((provider === "openai" || provider === "openai_compatible") && (model === "gpt-6-sol" || model === "gpt-6-luna")) {
1146
+ return GPT_6_API_EFFORTS;
1147
+ }
1148
+ if (provider && isCodexLegacyProvider(provider)) return CODEX_LEGACY_EFFORTS;
1149
+ if (provider === void 0) {
1150
+ return [.../* @__PURE__ */ new Set([...appServerEfforts, ...CODEX_LEGACY_EFFORTS])];
1151
+ }
1152
+ return appServerEfforts;
1153
+ }
1154
+ function isCodexReasoningEffortSupported(provider, model, effort, surface) {
1155
+ const supported = getSupportedCodexReasoningEfforts(model, provider, surface);
1156
+ return supported === void 0 || supported.includes(effort);
1157
+ }
1158
+ var OFFICIAL_CODEX_MODEL_IDS, STANDARD_CODEX_EFFORTS, GPT_5_6_EFFORTS, GPT_5_6_ULTRA_EFFORTS, GPT_6_API_EFFORTS, CODEX_LEGACY_EFFORTS, OFFICIAL_CODEX_MODEL_INFO, KIMI_REASONING_EFFORTS, KIMI_MODEL_INFO, KIMI_ALIAS_TO_ID, CLAUDE_REASONING_EFFORTS;
1159
+ var init_models = __esm({
1160
+ "src/utils/models.ts"() {
1161
+ "use strict";
1162
+ init_model_contracts();
1163
+ init_router_catalog();
1164
+ init_provider_ids();
1165
+ init_deepseek();
1166
+ OFFICIAL_CODEX_MODEL_IDS = [
1167
+ "gpt-6-sol",
1168
+ "gpt-6-luna",
1169
+ "gpt-5.6-sol",
1170
+ "gpt-5.6-terra",
1171
+ "gpt-5.6-luna",
1172
+ "gpt-5.5",
1173
+ "gpt-5.4",
1174
+ "gpt-5.4-mini",
1175
+ "gpt-5.3-codex",
1176
+ "gpt-5.2"
1177
+ ];
1178
+ STANDARD_CODEX_EFFORTS = ["low", "medium", "high", "xhigh"];
1179
+ GPT_5_6_EFFORTS = [...STANDARD_CODEX_EFFORTS, "max"];
1180
+ GPT_5_6_ULTRA_EFFORTS = [...GPT_5_6_EFFORTS, "ultra"];
1181
+ GPT_6_API_EFFORTS = ["none", "low", "medium", "high", "xhigh", "max"];
1182
+ CODEX_LEGACY_EFFORTS = ["none", "minimal", ...STANDARD_CODEX_EFFORTS];
1183
+ OFFICIAL_CODEX_MODEL_INFO = {
1184
+ "gpt-6-sol": {
1185
+ displayName: "GPT-6 Sol",
1186
+ description: "Frontier agentic coding model.",
1187
+ defaultReasoningEffort: "medium",
1188
+ supportedReasoningEfforts: GPT_5_6_ULTRA_EFFORTS
1189
+ },
1190
+ "gpt-6-luna": {
1191
+ displayName: "GPT-6 Luna",
1192
+ description: "Fast and affordable model for everyday work.",
1193
+ defaultReasoningEffort: "medium",
1194
+ supportedReasoningEfforts: GPT_5_6_EFFORTS
1195
+ },
1196
+ "gpt-5.6-sol": {
1197
+ displayName: "GPT-5.6 Sol",
1198
+ description: "Latest frontier agentic coding model.",
1199
+ defaultReasoningEffort: "low",
1200
+ supportedReasoningEfforts: GPT_5_6_ULTRA_EFFORTS
1201
+ },
1202
+ "gpt-5.6-terra": {
1203
+ displayName: "GPT-5.6 Terra",
1204
+ description: "Balanced agentic coding model for everyday work.",
1205
+ defaultReasoningEffort: "medium",
1206
+ supportedReasoningEfforts: GPT_5_6_ULTRA_EFFORTS
1207
+ },
1208
+ "gpt-5.6-luna": {
1209
+ displayName: "GPT-5.6 Luna",
1210
+ description: "Fast and affordable agentic coding model.",
1211
+ defaultReasoningEffort: "medium",
1212
+ supportedReasoningEfforts: GPT_5_6_EFFORTS
1213
+ },
1214
+ "gpt-5.5": {
1215
+ displayName: "GPT-5.5",
1216
+ description: "Frontier model for complex coding, research, and real-world work.",
1217
+ defaultReasoningEffort: "medium",
1218
+ supportedReasoningEfforts: STANDARD_CODEX_EFFORTS
1219
+ },
1220
+ "gpt-5.4": {
1221
+ displayName: "GPT-5.4",
1222
+ description: "Strong model for everyday coding.",
1223
+ defaultReasoningEffort: "medium",
1224
+ supportedReasoningEfforts: STANDARD_CODEX_EFFORTS
1225
+ },
1226
+ "gpt-5.4-mini": {
1227
+ displayName: "GPT-5.4 Mini",
1228
+ description: "Small, fast, and cost-efficient model for simpler coding tasks.",
1229
+ defaultReasoningEffort: "medium",
1230
+ supportedReasoningEfforts: STANDARD_CODEX_EFFORTS
1231
+ },
1232
+ "gpt-5.3-codex": {
1233
+ displayName: "GPT-5.3 Codex",
1234
+ description: "Codex-tuned agentic coding model.",
1235
+ defaultReasoningEffort: "medium",
1236
+ supportedReasoningEfforts: STANDARD_CODEX_EFFORTS
1237
+ },
1238
+ "gpt-5.2": {
1239
+ displayName: "GPT-5.2",
1240
+ description: "Earlier general-purpose Codex model.",
1241
+ defaultReasoningEffort: "medium",
1242
+ supportedReasoningEfforts: STANDARD_CODEX_EFFORTS
1243
+ }
1244
+ };
1245
+ KIMI_REASONING_EFFORTS = ["low", "high", "max"];
1246
+ KIMI_MODEL_INFO = {
1247
+ "kimi-k3": {
1248
+ displayName: "Kimi K3",
1249
+ description: "Moonshot flagship \u2014 1M context, reasoning on by default, vision and tools.",
1250
+ supportedReasoningEfforts: KIMI_REASONING_EFFORTS,
1251
+ defaultReasoningEffort: "max",
1252
+ aliases: ["k3", "kimi k3", "moonshot-kimi-k3", "moonshotai/kimi-k3"]
1253
+ },
1254
+ "kimi-k2.6": {
1255
+ displayName: "Kimi K2.6",
1256
+ description: "Smaller, cheaper Kimi \u2014 256K context, multimodal.",
1257
+ // No supportedReasoningEfforts: Moonshot lists `reasoning_effort` as "Not
1258
+ // supported" for K2.6, which instead takes a `thinking: {type}` object. ZTC
1259
+ // does not send that object yet, so the picker must not offer a depth control
1260
+ // here — the effort ladder and the thinking switch are different contracts.
1261
+ aliases: ["k2.6", "kimi k2.6", "moonshotai/kimi-k2.6"]
1262
+ },
1263
+ "kimi-for-coding": {
1264
+ displayName: "Kimi K2.7 Coding",
1265
+ description: "Kimi Code subscription coding model \u2014 256K context, always-thinking, vision and tools.",
1266
+ // No supportedReasoningEfforts: the subscription coding models are
1267
+ // always-thinking with no documented effort ladder, so /effort stays
1268
+ // rejected rather than forwarding a value the API silently ignores.
1269
+ aliases: ["k2.7", "kimi k2.7", "kimi-k2.7-coding"]
1270
+ },
1271
+ "kimi-for-coding-highspeed": {
1272
+ displayName: "Kimi K2.7 Coding Highspeed",
1273
+ description: "High-speed variant of the Kimi Code subscription coding model \u2014 256K context.",
1274
+ aliases: ["k2.7-highspeed", "kimi-for-coding-fast"]
868
1275
  }
869
- if (textParts.length > 0 || mediaParts.length > 0) {
870
- if (mediaParts.length > 0) {
871
- const parts = textParts.map((t) => ({ type: "text", text: t }));
872
- parts.push(...mediaParts);
873
- out.push({ role: "user", content: parts });
874
- } else {
875
- out.push({ role: "user", content: textParts.join("\n") });
876
- }
1276
+ };
1277
+ KIMI_ALIAS_TO_ID = /* @__PURE__ */ new Map();
1278
+ for (const id of Object.keys(KIMI_MODEL_INFO)) {
1279
+ KIMI_ALIAS_TO_ID.set(id, id);
1280
+ for (const alias of KIMI_MODEL_INFO[id].aliases) {
1281
+ KIMI_ALIAS_TO_ID.set(alias.toLowerCase(), id);
877
1282
  }
878
- continue;
879
- }
880
- if (mediaParts.length > 0) {
881
- const parts = textParts.map((t) => ({ type: "text", text: t }));
882
- parts.push(...mediaParts);
883
- out.push(withReasoning({ role: msg.role, content: parts }, msg));
884
- } else {
885
- out.push(withReasoning({ role: msg.role, content: textParts.join("\n") }, msg));
886
- }
887
- }
888
- return out;
889
- }
890
- function buildOpenAIToolDefs(tools) {
891
- return tools.map((t) => ({
892
- type: "function",
893
- function: {
894
- name: t.name,
895
- description: t.description,
896
- parameters: t.parameters
897
1283
  }
898
- }));
899
- }
900
- var init_openai_format = __esm({
901
- "src/agent/backends/openai_format.ts"() {
902
- "use strict";
1284
+ CLAUDE_REASONING_EFFORTS = [
1285
+ "low",
1286
+ "medium",
1287
+ "high",
1288
+ "xhigh",
1289
+ "max"
1290
+ ];
903
1291
  }
904
1292
  });
905
1293
 
@@ -961,6 +1349,7 @@ var OpenAICompatibleApiError, OpenAICompatibleBackend;
961
1349
  var init_openai_compatible = __esm({
962
1350
  "src/agent/backends/openai_compatible.ts"() {
963
1351
  "use strict";
1352
+ init_model_contracts();
964
1353
  init_request_metrics();
965
1354
  init_sse();
966
1355
  init_token_usage();
@@ -984,6 +1373,7 @@ var init_openai_compatible = __esm({
984
1373
  apiPath;
985
1374
  headers;
986
1375
  extraBody;
1376
+ provider;
987
1377
  requestBodyExtras;
988
1378
  authTokenProvider;
989
1379
  constructor(config) {
@@ -992,6 +1382,7 @@ var init_openai_compatible = __esm({
992
1382
  this.apiPath = config.apiPath || "/chat/completions";
993
1383
  this.headers = config.headers || {};
994
1384
  this.extraBody = config.extraBody || {};
1385
+ this.provider = config.provider;
995
1386
  this.requestBodyExtras = config.requestBodyExtras;
996
1387
  this.authTokenProvider = config.authTokenProvider;
997
1388
  }
@@ -999,22 +1390,29 @@ var init_openai_compatible = __esm({
999
1390
  * and generateStream(). generateStream() adds `stream`/`stream_options`. */
1000
1391
  async buildCall(request) {
1001
1392
  const url = `${this.baseUrl}${this.apiPath}`;
1002
- const useCompletionTokens = /^o\d/.test(request.model) || /^gpt-[56]/.test(request.model) || isKimiModel(request.model);
1393
+ const useCompletionTokens = this.provider === "fireworks" ? false : /^o\d/.test(request.model) || /^gpt-[56]/.test(request.model) || isKimiModel(request.model);
1003
1394
  const isGpt4o = request.model.startsWith("gpt-4o");
1004
- const limit = isGpt4o ? 16384 : isDeepSeekFlashModel(request.model) ? DEEPSEEK_FLASH_MAX_OUTPUT : Infinity;
1395
+ const reviewedLimit = this.provider ? modelContract(this.provider, request.model)?.maxOutputTokens?.max : void 0;
1396
+ const limit = reviewedLimit ?? (isGpt4o ? 16384 : isDeepSeekFlashModel(request.model) ? DEEPSEEK_FLASH_MAX_OUTPUT : Infinity);
1005
1397
  const effectiveMaxTokens = request.maxTokens === void 0 ? void 0 : Math.min(request.maxTokens, limit);
1006
1398
  const tokenParam = effectiveMaxTokens === void 0 ? {} : useCompletionTokens ? { max_completion_tokens: effectiveMaxTokens } : { max_tokens: effectiveMaxTokens };
1007
- const isGpt6Model = /^gpt-6-(?:sol|luna)$/i.test(request.model);
1008
- const reasoningEffort = (request.model === "gpt-5.6-sol" || isGpt6Model) && request.tools.length > 0 ? "none" : isGpt6Model ? request.codexEffort : void 0;
1399
+ const policyProvider = this.provider ?? "openai";
1400
+ const chatEfforts = modelContract(policyProvider, request.model)?.chatToolReasoningEfforts;
1401
+ const toolRestricted = request.tools.length > 0 && chatEfforts !== void 0;
1402
+ if (toolRestricted && request.codexEffort !== void 0 && !chatEfforts.includes(request.codexEffort)) {
1403
+ throw new Error("Use Responses for graded reasoning with tools on this model. Chat Completions supports effort none.");
1404
+ }
1405
+ const reasoningEffort = toolRestricted ? request.codexEffort ?? "none" : request.codexEffort;
1009
1406
  const body = {
1010
1407
  ...this.extraBody,
1408
+ ...reasoningRequestParams(policyProvider, request.model, reasoningEffort),
1011
1409
  ...this.requestBodyExtras?.(request) ?? {},
1012
- model: request.model,
1410
+ model: this.provider === "fireworks" ? fireworksRequestModel(request.model) : request.model,
1013
1411
  ...tokenParam,
1014
1412
  // Chat Completions accepts GPT-6 Sol/Luna function tools only with
1015
1413
  // reasoning disabled. The direct OpenAI backend uses Responses instead;
1016
1414
  // keep this rule for explicitly configured compatible endpoints.
1017
- ...reasoningEffort ? { reasoning_effort: reasoningEffort } : {},
1415
+ ...toolRestricted ? { reasoning_effort: reasoningEffort } : {},
1018
1416
  messages: buildOpenAIMessages(request),
1019
1417
  ...request.tools.length > 0 ? { tools: buildOpenAIToolDefs(request.tools) } : {}
1020
1418
  };
@@ -1712,12 +2110,13 @@ var init_openai_responses = __esm({
1712
2110
 
1713
2111
  // src/agent/backends/openai.ts
1714
2112
  function needsResponsesAPI(model) {
1715
- return RESPONSES_API_PATTERNS.some((p) => p.test(model));
2113
+ return modelContract("openai", model)?.chatToolReasoningEfforts !== void 0 || RESPONSES_API_PATTERNS.some((p) => p.test(model));
1716
2114
  }
1717
2115
  var RESPONSES_API_PATTERNS, OpenAIBackend;
1718
2116
  var init_openai = __esm({
1719
2117
  "src/agent/backends/openai.ts"() {
1720
2118
  "use strict";
2119
+ init_model_contracts();
1721
2120
  init_openai_compatible();
1722
2121
  init_openai_responses();
1723
2122
  RESPONSES_API_PATTERNS = [
@@ -1733,7 +2132,8 @@ var init_openai = __esm({
1733
2132
  this.chatBackend = new OpenAICompatibleBackend({
1734
2133
  apiKey: config.apiKey,
1735
2134
  baseUrl,
1736
- apiPath: "/chat/completions"
2135
+ apiPath: "/chat/completions",
2136
+ provider: "openai"
1737
2137
  });
1738
2138
  this.responsesBackend = new OpenAIResponsesBackend({
1739
2139
  apiKey: config.apiKey,
@@ -1787,6 +2187,7 @@ var GeminiBackend;
1787
2187
  var init_gemini = __esm({
1788
2188
  "src/agent/backends/gemini.ts"() {
1789
2189
  "use strict";
2190
+ init_model_contracts();
1790
2191
  init_base();
1791
2192
  GeminiBackend = class extends BaseBackend {
1792
2193
  apiKey;
@@ -1817,6 +2218,7 @@ var init_gemini = __esm({
1817
2218
  });
1818
2219
  };
1819
2220
  const body = {
2221
+ ...reasoningRequestParams("gemini", request.model, request.codexEffort),
1820
2222
  systemInstruction: { parts: [{ text: request.system }] },
1821
2223
  contents: request.messages.map((m) => ({
1822
2224
  role: m.role === "assistant" ? "model" : "user",
@@ -1901,43 +2303,6 @@ var init_kimi = __esm({
1901
2303
  }
1902
2304
  });
1903
2305
 
1904
- // src/config/profiles.ts
1905
- import { homedir } from "node:os";
1906
- import { join } from "node:path";
1907
- import { existsSync } from "node:fs";
1908
- function builtinProfileEmulation(name) {
1909
- const builtin = BUILTIN_CONFIG_PROFILES.find((profile) => profile === name);
1910
- if (!builtin) return void 0;
1911
- const root = process.env.ZTC_CONFIG_DIR || join(homedir(), ".ztc");
1912
- return existsSync(join(root, "profiles", name)) ? void 0 : builtin;
1913
- }
1914
- function validateProfileName(name) {
1915
- if (name !== "default" && !/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(name)) {
1916
- throw new Error("Profile name must be 1\u201364 characters: letters, numbers, dots, underscores, or hyphens; it must start with a letter or number.");
1917
- }
1918
- if (name === "." || name === "..") {
1919
- throw new Error("Profile name cannot be . or ..");
1920
- }
1921
- return name;
1922
- }
1923
- function activeProfile() {
1924
- return validateProfileName(process.env.ZTC_PROFILE || "default");
1925
- }
1926
- function profileConfigDir(profile = activeProfile()) {
1927
- const root = process.env.ZTC_CONFIG_DIR || join(homedir(), ".ztc");
1928
- const name = validateProfileName(profile);
1929
- if (name === "default") return root;
1930
- const namedDir = join(root, "profiles", name);
1931
- return builtinProfileEmulation(name) ? root : namedDir;
1932
- }
1933
- var BUILTIN_CONFIG_PROFILES;
1934
- var init_profiles = __esm({
1935
- "src/config/profiles.ts"() {
1936
- "use strict";
1937
- BUILTIN_CONFIG_PROFILES = ["ztc", "ztc-coder", "ztc-datascience", "scientist"];
1938
- }
1939
- });
1940
-
1941
2306
  // src/agent/backends/rate_limit.ts
1942
2307
  function parseResetAt(opts) {
1943
2308
  const { now } = opts;
@@ -1990,13 +2355,13 @@ var init_rate_limit = __esm({
1990
2355
  });
1991
2356
 
1992
2357
  // src/agent/backends/claude_code_auth.ts
1993
- import { existsSync as existsSync2, readFileSync } from "fs";
2358
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
1994
2359
  import { homedir as homedir2, platform, userInfo } from "os";
1995
- import { join as join2 } from "path";
2360
+ import { join as join3 } from "path";
1996
2361
  import { execFileSync } from "child_process";
1997
2362
  function getClaudeCodeCredentialsFilePath() {
1998
- const configDir2 = process.env.CLAUDE_CONFIG_DIR || join2(homedir2(), ".claude");
1999
- return join2(configDir2, ".credentials.json");
2363
+ const configDir2 = process.env.CLAUDE_CONFIG_DIR || join3(homedir2(), ".claude");
2364
+ return join3(configDir2, ".credentials.json");
2000
2365
  }
2001
2366
  function parseClaudeCodeCredentials(raw) {
2002
2367
  const data = JSON.parse(raw);
@@ -2078,7 +2443,7 @@ var init_claude_code_auth = __esm({
2078
2443
  return;
2079
2444
  }
2080
2445
  if (existsSync2(this.credentialsFilePath)) {
2081
- this.credentials = parseClaudeCodeCredentials(readFileSync(this.credentialsFilePath, "utf8"));
2446
+ this.credentials = parseClaudeCodeCredentials(readFileSync2(this.credentialsFilePath, "utf8"));
2082
2447
  return;
2083
2448
  }
2084
2449
  const keychainRaw = this.keychainReader ? this.keychainReader() : readMacOSKeychainCredentials(this.keychainAccount);
@@ -2150,9 +2515,9 @@ var init_claude_code_auth = __esm({
2150
2515
 
2151
2516
  // src/agent/backends/codex_auth.ts
2152
2517
  import { randomBytes } from "crypto";
2153
- import { chmodSync, readFileSync as readFileSync2, renameSync, unlinkSync, writeFileSync } from "fs";
2518
+ import { chmodSync, readFileSync as readFileSync3, renameSync, unlinkSync, writeFileSync } from "fs";
2154
2519
  import { homedir as homedir3 } from "os";
2155
- import { join as join3 } from "path";
2520
+ import { join as join4 } from "path";
2156
2521
  function resolveClientId(fileClientId) {
2157
2522
  return process.env.CODEX_OAUTH_CLIENT_ID || fileClientId || DEFAULT_CODEX_CLIENT_ID;
2158
2523
  }
@@ -2235,7 +2600,7 @@ function hasUsableCodexAuth(authFilePath = CODEX_AUTH_FILE_PATH) {
2235
2600
  }
2236
2601
  function _probeAuthFile(path) {
2237
2602
  try {
2238
- parseCodexAuthFile(readFileSync2(path, "utf-8"));
2603
+ parseCodexAuthFile(readFileSync3(path, "utf-8"));
2239
2604
  return true;
2240
2605
  } catch {
2241
2606
  return false;
@@ -2248,7 +2613,7 @@ var init_codex_auth = __esm({
2248
2613
  init_openai_responses();
2249
2614
  init_rate_limit();
2250
2615
  init_provider_ids();
2251
- CODEX_AUTH_FILE_PATH = join3(homedir3(), ".codex", "auth.json");
2616
+ CODEX_AUTH_FILE_PATH = join4(homedir3(), ".codex", "auth.json");
2252
2617
  CODEX_AUTH_SENTINEL = "codex-subscription-auth";
2253
2618
  OAUTH_TOKEN_URL = "https://auth.openai.com/oauth/token";
2254
2619
  DEFAULT_CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
@@ -2271,7 +2636,7 @@ var init_codex_auth = __esm({
2271
2636
  loadCredentialsSync() {
2272
2637
  let raw;
2273
2638
  try {
2274
- raw = readFileSync2(this.authFilePath, "utf-8");
2639
+ raw = readFileSync3(this.authFilePath, "utf-8");
2275
2640
  } catch {
2276
2641
  throw new Error(
2277
2642
  "Codex auth not found. Run 'codex' to authenticate with ChatGPT first."
@@ -2591,7 +2956,7 @@ var init_codex_auth = __esm({
2591
2956
 
2592
2957
  // src/agent/backends/codex_app_server.ts
2593
2958
  import { spawn } from "node:child_process";
2594
- import { createHash } from "node:crypto";
2959
+ import { createHash as createHash2 } from "node:crypto";
2595
2960
  function buildCodexAppServerEnv(codexHome, base = process.env) {
2596
2961
  const env = { ...base };
2597
2962
  if (codexHome) env.CODEX_HOME = codexHome;
@@ -2607,7 +2972,7 @@ function buildCodexAppServerEnv(codexHome, base = process.env) {
2607
2972
  return env;
2608
2973
  }
2609
2974
  function sha1(value) {
2610
- return createHash("sha1").update(value).digest("hex");
2975
+ return createHash2("sha1").update(value).digest("hex");
2611
2976
  }
2612
2977
  function nonEmptyId(value) {
2613
2978
  return typeof value === "string" && value.trim().length > 0 ? value : null;
@@ -3826,7 +4191,7 @@ var init_codex_app_server = __esm({
3826
4191
 
3827
4192
  // src/utils/debug_log.ts
3828
4193
  import { appendFileSync, writeFileSync as writeFileSync2 } from "fs";
3829
- import { join as join4 } from "path";
4194
+ import { join as join5 } from "path";
3830
4195
  import { tmpdir } from "os";
3831
4196
  function debugLog(message) {
3832
4197
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
@@ -3856,7 +4221,7 @@ var init_debug_log = __esm({
3856
4221
  "src/utils/debug_log.ts"() {
3857
4222
  "use strict";
3858
4223
  DEBUG_ENABLED = process.env.ZTC_DEBUG === "1";
3859
- DEBUG_LOG_FILE = join4(tmpdir(), `ztc-debug-${process.pid}.log`);
4224
+ DEBUG_LOG_FILE = join5(tmpdir(), `ztc-debug-${process.pid}.log`);
3860
4225
  if (DEBUG_ENABLED) {
3861
4226
  writeFileSync2(DEBUG_LOG_FILE, `[${(/* @__PURE__ */ new Date()).toISOString()}] ZTC starting, PID=${process.pid}
3862
4227
  `);
@@ -3869,14 +4234,14 @@ var init_debug_log = __esm({
3869
4234
  });
3870
4235
 
3871
4236
  // src/agent/backends/kimi_code_auth.ts
3872
- import { mkdirSync, chmodSync as chmodSync2, readFileSync as readFileSync3, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "fs";
4237
+ import { mkdirSync, chmodSync as chmodSync2, readFileSync as readFileSync4, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "fs";
3873
4238
  import { homedir as homedir4 } from "os";
3874
- import { dirname, join as join5 } from "path";
4239
+ import { dirname as dirname2, join as join6 } from "path";
3875
4240
  function kimiCodeHome() {
3876
- return process.env.KIMI_CODE_HOME || join5(homedir4(), ".kimi-code");
4241
+ return process.env.KIMI_CODE_HOME || join6(homedir4(), ".kimi-code");
3877
4242
  }
3878
4243
  function defaultKimiCodeAuthFilePath() {
3879
- return join5(kimiCodeHome(), "credentials", "kimi-code.json");
4244
+ return join6(kimiCodeHome(), "credentials", "kimi-code.json");
3880
4245
  }
3881
4246
  function kimiCodeOAuthHost() {
3882
4247
  return (process.env.KIMI_CODE_OAUTH_HOST || process.env.KIMI_OAUTH_HOST || DEFAULT_OAUTH_HOST).replace(/\/+$/, "");
@@ -3905,14 +4270,14 @@ function parseKimiCodeAuthFile(raw) {
3905
4270
  }
3906
4271
  function hasUsableKimiCodeAuth(authFilePath = defaultKimiCodeAuthFilePath()) {
3907
4272
  try {
3908
- parseKimiCodeAuthFile(readFileSync3(authFilePath, "utf-8"));
4273
+ parseKimiCodeAuthFile(readFileSync4(authFilePath, "utf-8"));
3909
4274
  return true;
3910
4275
  } catch {
3911
4276
  return false;
3912
4277
  }
3913
4278
  }
3914
4279
  function writeKimiCodeCredentials(authFilePath, creds) {
3915
- const dir = dirname(authFilePath);
4280
+ const dir = dirname2(authFilePath);
3916
4281
  mkdirSync(dir, { recursive: true, mode: 448 });
3917
4282
  try {
3918
4283
  chmodSync2(dir, 448);
@@ -3938,7 +4303,7 @@ function writeKimiCodeCredentials(authFilePath, creds) {
3938
4303
  try {
3939
4304
  renameSync2(tmp, authFilePath);
3940
4305
  } catch {
3941
- writeFileSync3(authFilePath, readFileSync3(tmp, "utf-8"), { encoding: "utf-8", mode: 384 });
4306
+ writeFileSync3(authFilePath, readFileSync4(tmp, "utf-8"), { encoding: "utf-8", mode: 384 });
3942
4307
  }
3943
4308
  chmodSync2(authFilePath, 384);
3944
4309
  }
@@ -4055,7 +4420,7 @@ var init_kimi_code_auth = __esm({
4055
4420
  loadCredentialsSync() {
4056
4421
  let raw;
4057
4422
  try {
4058
- raw = readFileSync3(this.authFilePath, "utf-8");
4423
+ raw = readFileSync4(this.authFilePath, "utf-8");
4059
4424
  } catch {
4060
4425
  throw new Error(
4061
4426
  "Kimi Code auth not found. Run 'kimi' and use /login, or sign in via /accounts (kimi_code provider)."
@@ -4131,7 +4496,7 @@ var init_kimi_code_auth = __esm({
4131
4496
  const detail = typeof data.error_description === "string" ? data.error_description : typeof data.error === "string" ? data.error : `HTTP ${status}`;
4132
4497
  if (status === 401 || status === 403 || data.error === "invalid_grant") {
4133
4498
  try {
4134
- const onDisk = parseKimiCodeAuthFile(readFileSync3(this.authFilePath, "utf-8"));
4499
+ const onDisk = parseKimiCodeAuthFile(readFileSync4(this.authFilePath, "utf-8"));
4135
4500
  if (onDisk.refreshToken !== refreshToken && !this.isExpiringSoon(onDisk.expiresAt)) {
4136
4501
  this.credentials = onDisk;
4137
4502
  return;
@@ -4194,7 +4559,7 @@ var init_kimi_code_auth = __esm({
4194
4559
 
4195
4560
  // src/agent/backends/account_pool.ts
4196
4561
  import { homedir as homedir5 } from "node:os";
4197
- import { basename, dirname as dirname2, join as join6, resolve } from "node:path";
4562
+ import { basename, dirname as dirname3, join as join7, resolve } from "node:path";
4198
4563
  function canonicalAccountProvider(provider) {
4199
4564
  const norm = normalizeProviderId(provider);
4200
4565
  switch (norm) {
@@ -4247,7 +4612,7 @@ function probeAccountIsolated(account) {
4247
4612
  switch (source.kind) {
4248
4613
  case "claude_config_dir":
4249
4614
  return hasUsableClaudeCodeAuth({
4250
- credentialsFilePath: join6(source.path, ".credentials.json"),
4615
+ credentialsFilePath: join7(source.path, ".credentials.json"),
4251
4616
  envToken: null,
4252
4617
  keychainReader: () => void 0
4253
4618
  });
@@ -4258,7 +4623,7 @@ function probeAccountIsolated(account) {
4258
4623
  keychainReader: () => void 0
4259
4624
  });
4260
4625
  case "codex_home":
4261
- return hasUsableCodexAuth(join6(source.path, "auth.json"));
4626
+ return hasUsableCodexAuth(join7(source.path, "auth.json"));
4262
4627
  case "codex_auth_file":
4263
4628
  return hasUsableCodexAuth(source.path);
4264
4629
  case "kimi_creds_file":
@@ -4276,7 +4641,7 @@ var init_account_pool = __esm({
4276
4641
  init_kimi_code_auth();
4277
4642
  init_debug_log();
4278
4643
  init_profiles();
4279
- ACCOUNTS_ROOT = join6(homedir5(), ".ztc", "accounts");
4644
+ ACCOUNTS_ROOT = join7(homedir5(), ".ztc", "accounts");
4280
4645
  DEFAULT_ACCOUNT_ROTATION = {
4281
4646
  enabled: false,
4282
4647
  mode: "failover",
@@ -4334,7 +4699,7 @@ var init_trace = __esm({
4334
4699
 
4335
4700
  // src/agent/backends/claude_cli.ts
4336
4701
  import { spawn as spawn2, spawnSync } from "child_process";
4337
- import { randomUUID as randomUUID2 } from "node:crypto";
4702
+ import { randomUUID as randomUUID3 } from "node:crypto";
4338
4703
  function endStdinToleratingBrokenPipe(child, prompt) {
4339
4704
  const stdin = child.stdin;
4340
4705
  if (!stdin) return;
@@ -4699,7 +5064,7 @@ ${detail}` : ""}`,
4699
5064
  const call = extractToolCall(text);
4700
5065
  if (!call) return resp;
4701
5066
  return {
4702
- content: [{ type: "tool_use", id: `cli_${randomUUID2()}`, name: call.name, input: call.input }],
5067
+ content: [{ type: "tool_use", id: `cli_${randomUUID3()}`, name: call.name, input: call.input }],
4703
5068
  stopReason: "tool_use",
4704
5069
  usage: resp.usage
4705
5070
  };
@@ -4832,7 +5197,7 @@ ${detail}` : ""}`, "claude_cli_exit", { exitCode: code ?? void 0, stderr: detail
4832
5197
  if (call) {
4833
5198
  const open = fullText.indexOf(TOOL_CALL_OPEN);
4834
5199
  if (open > emittedLen) push({ type: "text_delta", text: fullText.slice(emittedLen, open) });
4835
- const id = `cli_${randomUUID2()}`;
5200
+ const id = `cli_${randomUUID3()}`;
4836
5201
  push({ type: "tool_use_start", id, name: call.name });
4837
5202
  push({ type: "tool_use_delta", id, input_json: JSON.stringify(call.input) });
4838
5203
  push({ type: "tool_use_end", id });
@@ -4878,14 +5243,16 @@ var init_fireworks = __esm({
4878
5243
  });
4879
5244
 
4880
5245
  // src/config/defaults.ts
4881
- function resolveCodexEffort(config, provider, model) {
5246
+ function resolveCodexEffort(config, provider, model, contract = config.authMode === "zrouter" || provider === "openai_compatible" ? "chat_tools" : "native_api") {
4882
5247
  const candidates = [
4883
5248
  model ? config.codexEffortByModel?.[model] : void 0,
4884
5249
  provider ? config.codexEffortByProvider?.[provider] : void 0,
4885
5250
  config.codexEffort
4886
5251
  ];
4887
5252
  for (const candidate of candidates) {
4888
- if (candidate && isCodexReasoningEffortSupported(provider, model, candidate)) return candidate;
5253
+ if (!candidate) continue;
5254
+ const supported = typeof contract === "string" ? isCodexReasoningEffortSupported(provider, model, candidate, contract) : contract.reasoning?.choices.includes(candidate);
5255
+ if (candidate && supported) return candidate;
4889
5256
  }
4890
5257
  return void 0;
4891
5258
  }
@@ -4984,6 +5351,8 @@ function getDefaultApiKeyForProvider(provider) {
4984
5351
  case "modal":
4985
5352
  return void 0;
4986
5353
  // Modal credentials belong to the Router platform.
5354
+ case "fireworks":
5355
+ return process.env.FIREWORKS_API_KEY;
4987
5356
  default:
4988
5357
  return process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY || process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY || process.env.KIMI_API_KEY || process.env.MOONSHOT_API_KEY || process.env.OPENAI_COMPAT_API_KEY || process.env.INCEPTION_API_KEY;
4989
5358
  }
@@ -5078,17 +5447,17 @@ var init_atomic_write = __esm({
5078
5447
  });
5079
5448
 
5080
5449
  // src/config/repo_config.ts
5081
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync4 } from "fs";
5082
- import { dirname as dirname3, join as join7 } from "path";
5450
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync5 } from "fs";
5451
+ import { dirname as dirname4, join as join8 } from "path";
5083
5452
  function repoConfigPath(cwd) {
5084
- return join7(cwd, REPO_CONFIG_RELATIVE_PATH);
5453
+ return join8(cwd, REPO_CONFIG_RELATIVE_PATH);
5085
5454
  }
5086
5455
  function readRepoConfig(cwd) {
5087
5456
  if (isExplicitExtensionDiscovery()) return null;
5088
5457
  const path = repoConfigPath(cwd);
5089
5458
  if (!existsSync3(path)) return null;
5090
5459
  try {
5091
- const parsed = JSON.parse(readFileSync4(path, "utf8"));
5460
+ const parsed = JSON.parse(readFileSync5(path, "utf8"));
5092
5461
  return parsed && typeof parsed === "object" ? parsed : null;
5093
5462
  } catch {
5094
5463
  return null;
@@ -5107,19 +5476,19 @@ var init_repo_config = __esm({
5107
5476
  "use strict";
5108
5477
  init_extension_discovery();
5109
5478
  init_atomic_write();
5110
- REPO_CONFIG_RELATIVE_PATH = join7(".ztc", "config.json");
5479
+ REPO_CONFIG_RELATIVE_PATH = join8(".ztc", "config.json");
5111
5480
  }
5112
5481
  });
5113
5482
 
5114
5483
  // src/utils/version.ts
5115
- import { readFileSync as readFileSync5 } from "fs";
5116
- import { dirname as dirname4, resolve as resolve3 } from "path";
5484
+ import { readFileSync as readFileSync6 } from "fs";
5485
+ import { dirname as dirname5, resolve as resolve3 } from "path";
5117
5486
  import { fileURLToPath } from "url";
5118
5487
  function getVersion() {
5119
5488
  try {
5120
- const here = dirname4(fileURLToPath(import.meta.url));
5489
+ const here = dirname5(fileURLToPath(import.meta.url));
5121
5490
  const pkgPath = resolve3(here, "../../package.json");
5122
- const raw = readFileSync5(pkgPath, "utf-8");
5491
+ const raw = readFileSync6(pkgPath, "utf-8");
5123
5492
  const parsed = JSON.parse(raw);
5124
5493
  return parsed.version || "0.0.0";
5125
5494
  } catch {
@@ -5134,12 +5503,12 @@ var init_version = __esm({
5134
5503
 
5135
5504
  // src/router_auth.ts
5136
5505
  import { chmod, mkdir, readFile, rename as rename2, unlink as unlink2, writeFile as writeFile2 } from "node:fs/promises";
5137
- import { join as join8 } from "node:path";
5506
+ import { join as join9 } from "node:path";
5138
5507
  function credentialDirectory() {
5139
5508
  return profileConfigDir();
5140
5509
  }
5141
5510
  function credentialPath() {
5142
- return join8(credentialDirectory(), "router-auth.json");
5511
+ return join9(credentialDirectory(), "router-auth.json");
5143
5512
  }
5144
5513
  function routerOrigin(value) {
5145
5514
  const url = new URL(value);
@@ -5149,7 +5518,7 @@ function routerOrigin(value) {
5149
5518
  }
5150
5519
  return url.origin;
5151
5520
  }
5152
- function record2(value) {
5521
+ function record3(value) {
5153
5522
  if (!value || typeof value !== "object" || Array.isArray(value)) {
5154
5523
  throw new Error("Router returned an invalid response.");
5155
5524
  }
@@ -5175,10 +5544,10 @@ async function loadRouterCredentials() {
5175
5544
  throw error;
5176
5545
  }
5177
5546
  try {
5178
- const saved = record2(JSON.parse(data));
5547
+ const saved = record3(JSON.parse(data));
5179
5548
  let previousConfig;
5180
5549
  if (saved.previousConfig !== void 0) {
5181
- const previous = record2(saved.previousConfig);
5550
+ const previous = record3(saved.previousConfig);
5182
5551
  if (previous.authMode !== void 0 && !["auto", "direct", "zrouter"].includes(String(previous.authMode))) {
5183
5552
  throw new Error("Invalid previous authentication mode.");
5184
5553
  }
@@ -5251,7 +5620,9 @@ function estimateTokens(text) {
5251
5620
  if (!text) return 0;
5252
5621
  return Math.ceil(text.length / 4);
5253
5622
  }
5254
- function getContextWindowSize(model) {
5623
+ function getContextWindowSize(model, windowOverride) {
5624
+ if (windowOverride === null) return 0;
5625
+ if (typeof windowOverride === "number" && windowOverride > 0) return windowOverride;
5255
5626
  if (MODEL_CONTEXT_WINDOWS[model]) {
5256
5627
  return MODEL_CONTEXT_WINDOWS[model];
5257
5628
  }
@@ -5413,7 +5784,7 @@ var init_loop_policy = __esm({
5413
5784
  });
5414
5785
 
5415
5786
  // src/agent/tool_compression.ts
5416
- import { createHash as createHash2 } from "crypto";
5787
+ import { createHash as createHash3 } from "crypto";
5417
5788
  function isReadToolName(name) {
5418
5789
  return READ_TOOL_NAMES.has(name);
5419
5790
  }
@@ -5427,7 +5798,7 @@ function extractSourcePath(toolName, args) {
5427
5798
  return null;
5428
5799
  }
5429
5800
  function hashToolResultBody(content) {
5430
- return createHash2("sha256").update(stripRepeatGuard(content), "utf-8").digest("hex").slice(0, 16);
5801
+ return createHash3("sha256").update(stripRepeatGuard(content), "utf-8").digest("hex").slice(0, 16);
5431
5802
  }
5432
5803
  function compressionMarker(mode, toolUseId, savedTokensApprox) {
5433
5804
  return {
@@ -5694,8 +6065,8 @@ var init_deferred = __esm({
5694
6065
  });
5695
6066
 
5696
6067
  // src/agent/tool_response_store.ts
5697
- import { mkdir as mkdir2, readFile as readFile2, readdir } from "fs/promises";
5698
- import { join as join9 } from "path";
6068
+ import { mkdir as mkdir2, readFile as readFile2, readdir as readdir2 } from "fs/promises";
6069
+ import { join as join10 } from "path";
5699
6070
  function formatOffloadPlaceholder(meta) {
5700
6071
  const partialNote = meta.partial ? ", partial=true" : "";
5701
6072
  const base = `[tool result offloaded \u2014 tool_use_id=${meta.tool_use_id}, tool=${meta.tool_name}, size_tokens\u2248${meta.size_tokens}, turn=${meta.turn}${partialNote}. Call recall_tool_response with tool_use_id to retrieve full content.]`;
@@ -5827,9 +6198,10 @@ var init_tool_response_store = __esm({
5827
6198
  * fraction of the active model's context window so a small-context model
5828
6199
  * (gpt-4 8k → 2k effective) doesn't get crushed by a Claude-tuned default.
5829
6200
  */
5830
- effectiveBudget(model) {
6201
+ effectiveBudget(model, windowOverride) {
5831
6202
  const configured = this.config.configuredInlineBudgetTokens;
5832
- const window = getContextWindowSize(model);
6203
+ const window = getContextWindowSize(model, windowOverride);
6204
+ if (window === 0) return configured;
5833
6205
  return Math.min(configured, Math.floor(window * PER_MODEL_BUDGET_FRACTION));
5834
6206
  }
5835
6207
  cacheBudgetTokens() {
@@ -5854,7 +6226,7 @@ var init_tool_response_store = __esm({
5854
6226
  await mkdir2(dir, { recursive: true });
5855
6227
  let files;
5856
6228
  try {
5857
- files = await readdir(dir);
6229
+ files = await readdir2(dir);
5858
6230
  } catch {
5859
6231
  return;
5860
6232
  }
@@ -5862,7 +6234,7 @@ var init_tool_response_store = __esm({
5862
6234
  const records = await Promise.all(
5863
6235
  jsonFiles.map(async (f) => {
5864
6236
  try {
5865
- const raw = await readFile2(join9(dir, f), "utf-8");
6237
+ const raw = await readFile2(join10(dir, f), "utf-8");
5866
6238
  const rec = JSON.parse(raw);
5867
6239
  return rec?.meta?.tool_use_id ? rec.meta : null;
5868
6240
  } catch {
@@ -5931,7 +6303,7 @@ var init_tool_response_store = __esm({
5931
6303
  this.renderMemo.delete(toolUseId);
5932
6304
  this.cacheContent(toolUseId, content);
5933
6305
  if (this.dir) {
5934
- const path = join9(this.dir, `${toolUseId}.json`);
6306
+ const path = join10(this.dir, `${toolUseId}.json`);
5935
6307
  const write = writeFileAtomic(path, JSON.stringify({ meta, content })).catch((err) => {
5936
6308
  const message = err instanceof Error ? err.message : String(err);
5937
6309
  debugLog2(`[ToolResponseStore] atomic write failed for ${toolUseId}: ${message}`);
@@ -5949,7 +6321,7 @@ var init_tool_response_store = __esm({
5949
6321
  if (cached2 !== void 0) return cached2;
5950
6322
  if (!this.dir) return null;
5951
6323
  try {
5952
- const raw = await readFile2(join9(this.dir, `${toolUseId}.json`), "utf-8");
6324
+ const raw = await readFile2(join10(this.dir, `${toolUseId}.json`), "utf-8");
5953
6325
  const rec = JSON.parse(raw);
5954
6326
  if (rec && typeof rec.content === "string") {
5955
6327
  if (rec.meta && !this.meta.has(toolUseId)) {
@@ -6032,8 +6404,8 @@ var init_tool_response_store = __esm({
6032
6404
  renderInline(e) {
6033
6405
  if (!this.compressionActive()) return e.content;
6034
6406
  if (e.meta.recalledAtTurn !== void 0) return e.content;
6035
- const memo = this.renderMemo.get(e.id);
6036
- if (memo) return memo.text;
6407
+ const memo2 = this.renderMemo.get(e.id);
6408
+ if (memo2) return memo2.text;
6037
6409
  const mode = this.config.toolCompression === "aggressive" ? "aggressive" : "safe";
6038
6410
  const stub = this.duplicateStubFor(e.meta);
6039
6411
  const text = stub ?? compressToolResult({
@@ -6060,8 +6432,8 @@ var init_tool_response_store = __esm({
6060
6432
  }
6061
6433
  /** Cost of an inline render: the memoized token count when one was prepared. */
6062
6434
  inlineCost(e, rendered) {
6063
- const memo = this.renderMemo.get(e.id);
6064
- if (memo && memo.text === rendered) return memo.tokens;
6435
+ const memo2 = this.renderMemo.get(e.id);
6436
+ if (memo2 && memo2.text === rendered) return memo2.tokens;
6065
6437
  return e.meta.size_tokens;
6066
6438
  }
6067
6439
  /**
@@ -6620,9 +6992,9 @@ var init_status_line_config = __esm({
6620
6992
  });
6621
6993
 
6622
6994
  // src/config.ts
6623
- import { join as join10 } from "path";
6624
- import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3, chmod as chmod2, unlink as unlink3, rename as rename3, realpath } from "fs/promises";
6625
- import { randomUUID as randomUUID3 } from "crypto";
6995
+ import { dirname as dirname6, join as join11 } from "path";
6996
+ import { stat, readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3, chmod as chmod2, rm, unlink as unlink3, rename as rename3, realpath } from "fs/promises";
6997
+ import { randomUUID as randomUUID4 } from "crypto";
6626
6998
  function isLocalEndpoint(url) {
6627
6999
  try {
6628
7000
  const parsed = new URL(url);
@@ -6746,6 +7118,20 @@ function build_default_config() {
6746
7118
  function normalizedOrigin(value) {
6747
7119
  return value?.trim().replace(/\/+$/, "") || void 0;
6748
7120
  }
7121
+ function routerOriginFromEnvironment() {
7122
+ if (process.env.ZERG_PROXY_URL) return process.env.ZERG_PROXY_URL;
7123
+ if (process.env.ZERG_ROUTER_BASE_URL) return normalizedOrigin(process.env.ZERG_ROUTER_BASE_URL);
7124
+ const tiers = {
7125
+ local: "http://host.docker.internal:8000/api/zrouter",
7126
+ dev: "https://api-dev.zergai.com/api/zrouter",
7127
+ development: "https://api-dev.zergai.com/api/zrouter",
7128
+ prod: "https://api.zergai.com/api/zrouter",
7129
+ production: "https://api.zergai.com/api/zrouter",
7130
+ main: "https://api.zergai.com/api/zrouter"
7131
+ };
7132
+ const tier = process.env.ZERG_DEPLOY_TIER || "";
7133
+ return Object.hasOwn(tiers, tier) ? tiers[tier] : void 0;
7134
+ }
6749
7135
  function savedRouteIsUsable(saved) {
6750
7136
  if (!saved.provider) return false;
6751
7137
  const provider = normalizeProviderId(saved.provider);
@@ -6761,12 +7147,12 @@ function savedRouteIsUsable(saved) {
6761
7147
  function migrateSettingsDefaults(saved) {
6762
7148
  const defaults = build_default_config();
6763
7149
  const migrated = { ...saved, settingsDefaultsVersion: SETTINGS_DEFAULTS_VERSION };
6764
- const record3 = migrated;
7150
+ const record4 = migrated;
6765
7151
  const fresh = defaults;
6766
7152
  const adopted = savedRouteIsUsable(saved) ? ADOPTED_DEFAULT_KEYS : [...ADOPTED_DEFAULT_KEYS, ...ROUTING_SETTING_KEYS];
6767
7153
  for (const key of adopted) {
6768
- if (fresh[key] === void 0) delete record3[key];
6769
- else record3[key] = fresh[key];
7154
+ if (fresh[key] === void 0) delete record4[key];
7155
+ else record4[key] = fresh[key];
6770
7156
  }
6771
7157
  if (saved.apiKey !== void 0) {
6772
7158
  const oldProvider = saved.provider || inferDefaultProvider();
@@ -6791,7 +7177,7 @@ async function writeMigratedSettings(path, settings, original) {
6791
7177
  } catch (error) {
6792
7178
  if (error.code !== "EEXIST") throw error;
6793
7179
  }
6794
- const temporary = `${target}.${process.pid}.${randomUUID3()}.tmp`;
7180
+ const temporary = `${target}.${process.pid}.${randomUUID4()}.tmp`;
6795
7181
  try {
6796
7182
  await writeFile3(temporary, JSON.stringify(settings, null, 2), { encoding: "utf-8", mode: 384, flag: "wx" });
6797
7183
  await rename3(temporary, target);
@@ -6807,12 +7193,15 @@ function configDir() {
6807
7193
  return profileConfigDir();
6808
7194
  }
6809
7195
  function configFile() {
6810
- return join10(configDir(), "config.json");
7196
+ return join11(configDir(), "config.json");
6811
7197
  }
6812
7198
  var API_ENDPOINTS, DEFAULT_TOOL_PERMISSIONS, DEFAULT_PERMISSION_PROFILES, DEFAULT_INTENTS, DEFAULT_SANDBOX, DEFAULT_CONTEXT_PROGRESS_BAR, DEFAULT_WORKFLOWS, SETTINGS_DEFAULTS_VERSION, PERSONAL_DEFAULT_PROVIDER, PERSONAL_DEFAULT_MODEL, PERSONAL_DEFAULT_PROXY_URL, ROUTE_CREDENTIAL_ENV, ADOPTED_DEFAULT_KEYS, ROUTING_SETTING_KEYS, SUPERUSER_STATUS_TIMEOUT_MS, ConfigStore, configStore;
6813
7199
  var init_config = __esm({
6814
7200
  "src/config.ts"() {
6815
7201
  "use strict";
7202
+ init_model_contracts();
7203
+ init_provider_ids();
7204
+ init_router_catalog();
6816
7205
  init_profiles();
6817
7206
  init_account_pool();
6818
7207
  init_account_events();
@@ -6960,6 +7349,12 @@ var init_config = __esm({
6960
7349
  // The legacy slot also carries runtime defaults. Only a configured key may
6961
7350
  // be migrated into apiKeys or written to disk.
6962
7351
  legacyApiKeyFromEnvironment = true;
7352
+ /**
7353
+ * Whether the routing in `this.config` came from a saved file or an explicit
7354
+ * choice, rather than the built-in default. A config-less process that never
7355
+ * picked a route must not hard-fail on the preset Router route it inherited.
7356
+ */
7357
+ routingIsConfigured = false;
6963
7358
  routerCredentials = null;
6964
7359
  routerCredentialsError = null;
6965
7360
  routerFallbackWarning = null;
@@ -6967,6 +7362,7 @@ var init_config = __esm({
6967
7362
  configScope = "global";
6968
7363
  /** Session-local overrides. Never persisted to config.json. */
6969
7364
  sessionOverrides = {};
7365
+ restoredSessionSelection = false;
6970
7366
  /** CLI-only overrides. Never persisted to config.json. */
6971
7367
  cliOverrides = {};
6972
7368
  /**
@@ -7020,15 +7416,9 @@ var init_config = __esm({
7020
7416
  else if (tier === "dev" || tier === "development") this.config.apiEnvironment = "development";
7021
7417
  else if (tier === "prod" || tier === "production" || tier === "main") this.config.apiEnvironment = "production";
7022
7418
  }
7023
- if (process.env.ZERG_PROXY_URL) {
7024
- this.config.zergProxyUrl = process.env.ZERG_PROXY_URL;
7025
- } else if (process.env.ZERG_ROUTER_BASE_URL) {
7026
- this.config.zergProxyUrl = process.env.ZERG_ROUTER_BASE_URL.replace(/\/+$/, "");
7027
- } else if (process.env.ZERG_DEPLOY_TIER) {
7028
- const tier = process.env.ZERG_DEPLOY_TIER;
7029
- if (tier === "local") this.config.zergProxyUrl = "http://host.docker.internal:8000/api/zrouter";
7030
- else if (tier === "dev" || tier === "development") this.config.zergProxyUrl = "https://api-dev.zergai.com/api/zrouter";
7031
- else if (tier === "prod" || tier === "production" || tier === "main") this.config.zergProxyUrl = "https://api.zergai.com/api/zrouter";
7419
+ const originOverride = routerOriginFromEnvironment();
7420
+ if (originOverride) {
7421
+ this.config.zergProxyUrl = originOverride;
7032
7422
  } else if (!this.config.zergProxyUrl) {
7033
7423
  this.config.zergProxyUrl = this.routerCredentials?.origin || this.deriveZRouterUrl(this.getApiBaseUrl());
7034
7424
  }
@@ -7119,12 +7509,59 @@ var init_config = __esm({
7119
7509
  this.sessionOverrides = sessionOverrides;
7120
7510
  this.configScope = scope;
7121
7511
  }
7512
+ /** Copy saved settings and Router credentials, then activate them transactionally.
7513
+ * Account registrations retain their credential paths; account homes are not cloned.
7514
+ */
7515
+ async duplicateProfile(requestedName, source = activeProfile(), activate = () => {
7516
+ }) {
7517
+ const name = validateProfileName(requestedName.trim());
7518
+ const from = validateProfileName(source);
7519
+ if (name === "default" || builtinProfileEmulation(name)) {
7520
+ throw new Error(`Cannot duplicate onto "${name}": it is a built-in profile sharing the default settings. Pick another name.`);
7521
+ }
7522
+ if (name === from || (await listConfigProfiles()).includes(name)) {
7523
+ throw new Error(`Config profile "${name}" already exists. Pick another name.`);
7524
+ }
7525
+ const sourceDir = profileConfigDir(from);
7526
+ const emulation = builtinProfileEmulation(from);
7527
+ if (from !== "default" && !emulation && !(await stat(sourceDir)).isDirectory()) {
7528
+ throw new Error(`Config profile "${from}" does not exist.`);
7529
+ }
7530
+ let saved = {};
7531
+ try {
7532
+ const parsed = JSON.parse(await readFile3(join11(sourceDir, "config.json"), "utf8"));
7533
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
7534
+ throw new Error(`Invalid config profile "${from}": expected an object.`);
7535
+ }
7536
+ saved = parsed;
7537
+ } catch (error) {
7538
+ if (error.code !== "ENOENT") throw error;
7539
+ }
7540
+ if (emulation) saved.emulationId = emulation;
7541
+ const destination = profileConfigDir(name);
7542
+ await mkdir3(dirname6(destination), { recursive: true, mode: 448 });
7543
+ await mkdir3(destination, { mode: 448 });
7544
+ try {
7545
+ await writeFile3(join11(destination, "config.json"), JSON.stringify(saved, null, 2), { flag: "wx", mode: 384 });
7546
+ try {
7547
+ const auth = await readFile3(join11(sourceDir, "router-auth.json"));
7548
+ await writeFile3(join11(destination, "router-auth.json"), auth, { flag: "wx", mode: 384 });
7549
+ } catch (error) {
7550
+ if (error.code !== "ENOENT") throw error;
7551
+ }
7552
+ await this.switchProfile(name, activate);
7553
+ } catch (error) {
7554
+ await rm(destination, { recursive: true, force: true });
7555
+ throw error;
7556
+ }
7557
+ return name;
7558
+ }
7122
7559
  /** Load a profile in this process; restore the old view if runtime activation fails. */
7123
7560
  async switchProfile(name, activate) {
7124
7561
  const next = validateProfileName(name);
7125
7562
  if (next === activeProfile()) return;
7126
7563
  const sameConfigDirectory = profileConfigDir(next) === profileConfigDir();
7127
- const target = join10(profileConfigDir(next), "config.json");
7564
+ const target = join11(profileConfigDir(next), "config.json");
7128
7565
  try {
7129
7566
  const saved = JSON.parse(await readFile3(target, "utf-8"));
7130
7567
  if (!saved || typeof saved !== "object" || Array.isArray(saved)) {
@@ -7145,6 +7582,7 @@ var init_config = __esm({
7145
7582
  routerCredentialsError: this.routerCredentialsError,
7146
7583
  routerFallbackWarning: this.routerFallbackWarning,
7147
7584
  sessionOverrides: this.sessionOverrides,
7585
+ restoredSessionSelection: this.restoredSessionSelection,
7148
7586
  cliOverrides: this.cliOverrides,
7149
7587
  runtimeActivePerProvider: this.runtimeActivePerProvider,
7150
7588
  configScope: this.configScope
@@ -7155,6 +7593,7 @@ var init_config = __esm({
7155
7593
  this.sessionOverrides = remainingOverrides;
7156
7594
  } else {
7157
7595
  this.sessionOverrides = {};
7596
+ this.restoredSessionSelection = false;
7158
7597
  this.cliOverrides = {};
7159
7598
  this.runtimeActivePerProvider = {};
7160
7599
  this.configScope = "global";
@@ -7193,7 +7632,7 @@ var init_config = __esm({
7193
7632
  }
7194
7633
  /** Clear the active profile's saved settings and reset this process's settings view. */
7195
7634
  async factoryReset() {
7196
- for (const path of [configFile(), join10(configDir(), "router-auth.json")]) {
7635
+ for (const path of [configFile(), join11(configDir(), "router-auth.json")]) {
7197
7636
  try {
7198
7637
  await unlink3(path);
7199
7638
  } catch (error) {
@@ -7235,7 +7674,9 @@ var init_config = __esm({
7235
7674
  cliOverridesWithoutEffort() {
7236
7675
  return {
7237
7676
  ...this.cliOverrides.provider !== void 0 ? { provider: this.cliOverrides.provider } : {},
7238
- ...this.cliOverrides.model !== void 0 ? { model: this.cliOverrides.model } : {}
7677
+ ...this.cliOverrides.model !== void 0 ? { model: this.cliOverrides.model } : {},
7678
+ ...this.cliOverrides.authMode !== void 0 ? { authMode: this.cliOverrides.authMode } : {},
7679
+ ...this.cliOverrides.zergProxyUrl !== void 0 ? { zergProxyUrl: this.cliOverrides.zergProxyUrl } : {}
7239
7680
  };
7240
7681
  }
7241
7682
  getMaxTokensSource() {
@@ -7250,6 +7691,7 @@ var init_config = __esm({
7250
7691
  }
7251
7692
  clearSessionOverrides() {
7252
7693
  this.sessionOverrides = {};
7694
+ this.restoredSessionSelection = false;
7253
7695
  }
7254
7696
  async withConfigScope(scope, fn) {
7255
7697
  const previous = this.configScope;
@@ -7260,7 +7702,13 @@ var init_config = __esm({
7260
7702
  this.configScope = previous;
7261
7703
  }
7262
7704
  }
7705
+ releaseRestoredSelection(key) {
7706
+ if (!this.restoredSessionSelection || !["provider", "model", "authMode", "zergProxyUrl"].includes(key)) return;
7707
+ for (const route of ["provider", "model", "authMode", "zergProxyUrl"]) delete this.sessionOverrides[route];
7708
+ this.restoredSessionSelection = false;
7709
+ }
7263
7710
  set(key, value) {
7711
+ this.releaseRestoredSelection(key);
7264
7712
  if (this.configScope === "session" || key === "emulationId" && this.profileEmulation) {
7265
7713
  this.sessionOverrides[key] = value;
7266
7714
  return;
@@ -7269,10 +7717,12 @@ var init_config = __esm({
7269
7717
  if (key === "apiKey") this.legacyApiKeyFromEnvironment = false;
7270
7718
  }
7271
7719
  setGlobal(key, value) {
7720
+ this.releaseRestoredSelection(key);
7272
7721
  this.config[key] = value;
7273
7722
  if (key === "apiKey") this.legacyApiKeyFromEnvironment = false;
7274
7723
  }
7275
7724
  setAll(next) {
7725
+ for (const key of Object.keys(next)) this.releaseRestoredSelection(key);
7276
7726
  this.config = { ...build_default_config(), ...this.config, ...next };
7277
7727
  if (Object.hasOwn(next, "apiKey")) this.legacyApiKeyFromEnvironment = false;
7278
7728
  if (this.config.model === "claude-opus-4-5-20250514" || this.config.model === "claude-opus-4-20250514") {
@@ -7487,8 +7937,28 @@ var init_config = __esm({
7487
7937
  * Effective Codex effort for the active (or given) provider/model. Priority:
7488
7938
  * per-model > per-provider > global. Defaults to the current provider/model.
7489
7939
  */
7940
+ getReasoningWireSurface(provider = this.getProvider(), model = this.getModel()) {
7941
+ if (provider === "openai_compatible") return "chat_tools";
7942
+ if (modelContract(provider, model)?.chatToolReasoningEfforts === void 0) return "native_api";
7943
+ try {
7944
+ return this.resolveLLMBackendConfig({ provider, model }).useProxy ? "chat_tools" : "native_api";
7945
+ } catch (error) {
7946
+ if (error instanceof RouterConfigurationError) return "chat_tools";
7947
+ throw error;
7948
+ }
7949
+ }
7490
7950
  getCodexEffort(provider, model) {
7491
- return resolveCodexEffort(this.get(), provider ?? this.getProvider(), model ?? this.getModel());
7951
+ const selectedProvider = provider ?? this.getProvider();
7952
+ const selectedModel = model ?? this.getModel();
7953
+ const catalog = this.getRoutedModelCatalog();
7954
+ const capabilities = catalog ? resolveRouterModel(catalog, selectedProvider, selectedModel)?.capabilities : void 0;
7955
+ return resolveCodexEffort(this.get(), selectedProvider, selectedModel, capabilities ?? this.getReasoningWireSurface(selectedProvider, selectedModel));
7956
+ }
7957
+ /** The active transport owns the model's choices, aliases and advertised default. */
7958
+ getReasoningContract(provider = this.getProvider(), model = this.getModel()) {
7959
+ const catalog = this.getRoutedModelCatalog();
7960
+ if (catalog?.version === 1) return resolveRouterModel(catalog, provider, model)?.capabilities?.reasoning ?? [];
7961
+ return this.getReasoningWireSurface(provider, model);
7492
7962
  }
7493
7963
  /** Saved effort the active (or given) model rejects; undefined when it applies. */
7494
7964
  getUnsupportedCodexEffort(provider, model) {
@@ -7647,6 +8117,69 @@ var init_config = __esm({
7647
8117
  authMode: this.getAuthMode()
7648
8118
  };
7649
8119
  }
8120
+ restoreSessionSelection(metadata) {
8121
+ if (!metadata.authMode || this.cliOverrides.provider || this.cliOverrides.model || metadata.configProfile && metadata.configProfile !== activeProfile()) return;
8122
+ this.restoredSessionSelection = true;
8123
+ this.sessionOverrides.provider = metadata.provider;
8124
+ this.sessionOverrides.model = metadata.model;
8125
+ this.sessionOverrides.authMode = metadata.authMode;
8126
+ if (metadata.authMode !== "direct" && metadata.routerOrigin && !routerOriginFromEnvironment()) this.sessionOverrides.zergProxyUrl = metadata.routerOrigin;
8127
+ }
8128
+ getRouterCatalogConnection(destination = this.get().zergProxyUrl) {
8129
+ const origin = normalizedOrigin(destination);
8130
+ if (!origin) return void 0;
8131
+ const credentials = this.routerCredentials;
8132
+ if (credentials && !this.routerCredentialsError && credentials.origin === origin) {
8133
+ return { origin, token: credentials.token, workspaceId: credentials.workspaceId };
8134
+ }
8135
+ const token = origin === this.deriveZRouterUrl(this.getApiBaseUrl()) ? this.getAuthToken() : void 0;
8136
+ return token ? { origin, token, workspaceId: this.getActiveWorkspace()?.id.toString() } : void 0;
8137
+ }
8138
+ /** New children use configured routing, not a parent's restored inference route. */
8139
+ getChildAuthMode() {
8140
+ if (!this.restoredSessionSelection) return this.getAuthMode();
8141
+ const { authMode: _restored, ...session } = this.sessionOverrides;
8142
+ return resolveAuthMode({ ...this.config, ...session, ...this.cliOverrides });
8143
+ }
8144
+ async adoptRouterCatalog(catalog) {
8145
+ if (catalog.version !== 1 || catalog.authRequired || this.getResolvedAuthMode() !== "zrouter") return;
8146
+ const provider = this.getProvider(), model = this.getModel();
8147
+ const row = resolveRouterModel(catalog, provider, model);
8148
+ if (!row || row.id === model && row.owned_by === provider) return;
8149
+ if (this.cliOverrides.provider || this.cliOverrides.model) {
8150
+ this.cliOverrides.provider = row.owned_by;
8151
+ this.cliOverrides.model = row.id;
8152
+ } else if (this.sessionOverrides.provider || this.sessionOverrides.model) {
8153
+ this.sessionOverrides.provider = row.owned_by;
8154
+ this.sessionOverrides.model = row.id;
8155
+ } else {
8156
+ this.config.provider = row.owned_by;
8157
+ this.config.model = row.id;
8158
+ await this.save();
8159
+ }
8160
+ }
8161
+ getRouterCatalog() {
8162
+ const connection = this.getRouterCatalogConnection();
8163
+ return connection ? readRouterCatalog(connection) : void 0;
8164
+ }
8165
+ getResolvedAuthMode() {
8166
+ const mode = this.getAuthMode();
8167
+ if (mode !== "auto") return mode;
8168
+ try {
8169
+ return this.resolveLLMBackendConfig().useProxy ? "zrouter" : "direct";
8170
+ } catch {
8171
+ return mode;
8172
+ }
8173
+ }
8174
+ getRoutedModelCatalog() {
8175
+ if (isLocalSubscriptionProvider(this.getProvider())) return void 0;
8176
+ return this.getResolvedAuthMode() === "zrouter" ? this.getRouterCatalog() : void 0;
8177
+ }
8178
+ getModelContextWindow(model = this.getModel(), provider = this.getProvider()) {
8179
+ const catalog = this.getRoutedModelCatalog();
8180
+ if (catalog?.version !== 1) return void 0;
8181
+ return resolveRouterModel(catalog, provider, model)?.capabilities?.context_window ?? null;
8182
+ }
7650
8183
  /**
7651
8184
  * Resolve everything a backend factory needs into one struct. Folds in the
7652
8185
  * provider/model override precedence, the direct-key precedence
@@ -7661,17 +8194,16 @@ var init_config = __esm({
7661
8194
  *
7662
8195
  * Reads through `get()` and the env-aware getters (never raw `this.config`)
7663
8196
  * so cli/session overrides stay honored. `apiKey` is the DIRECT provider key;
7664
- * `authToken`/`useProxy` describe the proxy route separately, so a caller can
7665
- * still prefer a direct key (e.g. Gemini) over the proxy.
8197
+ * `authToken`/`useProxy` describe the selected proxy route separately.
7666
8198
  */
7667
8199
  resolveLLMBackendConfig(opts = {}) {
7668
8200
  const cfg = opts.config ?? this.get();
7669
8201
  const repoOverride = !opts.provider && !opts.model && !this.hasExplicitModelOverride() && opts.cwd ? resolveRepoModelOverride(opts.cwd) : null;
7670
- const provider = repoOverride?.provider ?? normalizeProviderId(opts.provider || cfg.provider || this.getProvider());
8202
+ let provider = repoOverride?.provider ?? normalizeProviderId(opts.provider || cfg.provider || this.getProvider());
7671
8203
  const configProvider = cfg.provider ? normalizeProviderId(cfg.provider) : void 0;
7672
- const model = repoOverride?.model ?? opts.model ?? (configProvider === provider ? cfg.model : void 0) ?? getDefaultModelForProvider(provider);
7673
- const authMode = resolveAuthMode(cfg);
7674
- const proxyUrl = cfg.zergProxyUrl;
8204
+ let model = repoOverride?.model ?? opts.model ?? (configProvider === provider ? cfg.model : void 0) ?? getDefaultModelForProvider(provider);
8205
+ const authMode = isLocalSubscriptionProvider(provider) ? "direct" : resolveAuthMode(cfg);
8206
+ const proxyUrl = normalizedOrigin(cfg.zergProxyUrl);
7675
8207
  const platformProxyUrl = this.deriveZRouterUrl(this.getApiBaseUrl());
7676
8208
  let authToken = provider !== "modal" && proxyUrl?.replace(/\/+$/, "") === platformProxyUrl ? this.getAuthToken() : void 0;
7677
8209
  let routerError = this.routerCredentialsError;
@@ -7704,7 +8236,16 @@ var init_config = __esm({
7704
8236
  const apiKey = opts.apiKeyOverride || providerScopedKey || this.getApiKey(provider);
7705
8237
  const rawCompatBaseUrl = opts.compatBaseUrlOverride || this.getOpenAICompatibleSettings(cfg).baseUrl || cfg.openaiCompatibleBaseUrl;
7706
8238
  const proxyAvailable = Boolean(proxyUrl && authToken);
7707
- const useProxy = authMode === "direct" ? false : proxyAvailable;
8239
+ const localSubscription = isLocalSubscriptionProvider(provider);
8240
+ const useProxy = authMode === "direct" ? false : proxyAvailable && !localSubscription && !(authMode === "auto" && provider === "gemini" && Boolean(apiKey));
8241
+ if (useProxy) {
8242
+ const catalog = readRouterCatalog({ origin: proxyUrl, token: authToken, workspaceId: this.getRouterCatalogConnection(proxyUrl)?.workspaceId });
8243
+ const descriptor2 = catalog?.version === 1 ? resolveRouterModel(catalog, provider, model) : void 0;
8244
+ if (descriptor2) {
8245
+ provider = descriptor2.owned_by;
8246
+ model = descriptor2.id;
8247
+ }
8248
+ }
7708
8249
  const compatBaseUrl = provider === "openai_compatible" && rawCompatBaseUrl ? compatEndpoint(rawCompatBaseUrl, { apiKey }) : rawCompatBaseUrl;
7709
8250
  const activeAccount = resolveActiveAccount(cfg, provider);
7710
8251
  const accountOptions = activeAccount ? accountSourceToBackendOptions(activeAccount.source) : {};
@@ -7723,7 +8264,7 @@ var init_config = __esm({
7723
8264
  codexHome: accountOptions.codexHome,
7724
8265
  codexAuthFilePath: accountOptions.codexAuthFilePath,
7725
8266
  kimiCredsFilePath: accountOptions.kimiCredsFilePath,
7726
- codexEffort: this.getCodexEffort(provider, model)
8267
+ codexEffort: resolveCodexEffort(cfg, provider, model, (useProxy ? this.getRouterCatalog()?.models.find((row) => row.id === model && row.owned_by === provider)?.capabilities : void 0) ?? (useProxy || provider === "openai_compatible" ? "chat_tools" : "native_api"))
7727
8268
  };
7728
8269
  }
7729
8270
  getEnvKey(provider) {
@@ -7743,6 +8284,8 @@ var init_config = __esm({
7743
8284
  return process.env.OPENAI_API_KEY;
7744
8285
  case "deepseek":
7745
8286
  return process.env.DEEPSEEK_API_KEY;
8287
+ case "fireworks":
8288
+ return process.env.FIREWORKS_API_KEY;
7746
8289
  case "gemini":
7747
8290
  return process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY;
7748
8291
  case "inception":
@@ -8120,7 +8663,7 @@ var init_zerg_api = __esm({
8120
8663
  });
8121
8664
 
8122
8665
  // src/agent/backends/zrouter_stream.ts
8123
- import { randomUUID as randomUUID4 } from "node:crypto";
8666
+ import { randomUUID as randomUUID5 } from "node:crypto";
8124
8667
  function isPinnableDecoration(value) {
8125
8668
  return value !== void 0 && PINNABLE_DECORATION.test(value);
8126
8669
  }
@@ -8137,7 +8680,7 @@ function resolveReconnectPolicy() {
8137
8680
  };
8138
8681
  }
8139
8682
  function withTransportTurn(request) {
8140
- return request.transportTurnId ? request : { ...request, transportTurnId: randomUUID4() };
8683
+ return request.transportTurnId ? request : { ...request, transportTurnId: randomUUID5() };
8141
8684
  }
8142
8685
  async function fetchWithHeadersTimeout(fetcher, input, init, timeoutMs) {
8143
8686
  const timeoutController = new AbortController();
@@ -8172,6 +8715,14 @@ async function waitForReconnect(delayMs, signal) {
8172
8715
  signal?.addEventListener("abort", abort, { once: true });
8173
8716
  });
8174
8717
  }
8718
+ function failureHeaderSuffix(response) {
8719
+ const parts = [];
8720
+ for (const name of ["fly-request-id", "x-request-id", "cf-ray", "x-zerg-error-code"]) {
8721
+ const value = response.headers.get(name)?.trim();
8722
+ if (value && value.length <= 128 && !/[\r\n\0]/.test(value)) parts.push(`${name}=${value}`);
8723
+ }
8724
+ return parts.length > 0 ? ` [${parts.join(" ")}]` : "";
8725
+ }
8175
8726
  function isRetryableResumeStatus(error) {
8176
8727
  if (!(error instanceof Error)) return false;
8177
8728
  const match = error.message.match(/ZRouter API error \((\d{3})\)/);
@@ -8190,10 +8741,22 @@ function normalizeHeaderValue(value, label, maxLength) {
8190
8741
  }
8191
8742
  return normalized;
8192
8743
  }
8744
+ function chatOnlyRequest(request) {
8745
+ return { ...request, tools: [], messages: request.messages.map((message) => ({
8746
+ ...message,
8747
+ content: typeof message.content === "string" ? message.content : message.content.map((block) => {
8748
+ if (block.type === "tool_use") return { type: "text", text: `Previous tool call ${block.name} (${block.id}): ${JSON.stringify(block.input)}` };
8749
+ if (block.type === "tool_result") return { type: "text", text: `Previous tool result (${block.tool_use_id}): ${typeof block.content === "string" ? block.content : block.content.map((part) => part.type === "text" ? part.text : "[image]").join("\n")}` };
8750
+ return block;
8751
+ })
8752
+ })) };
8753
+ }
8193
8754
  var ZROUTER_HEADERS_TIMEOUT_MS, routerClientSession, ROUTER_PIN_MAX_LENGTH, PINNABLE_DECORATION, DEFAULT_RECONNECT_ATTEMPTS, DEFAULT_RECONNECT_BASE_MS, DEFAULT_RECONNECT_CAP_MS, MAX_RECONNECT_ATTEMPTS, MAX_RECONNECT_MS, ZRouterStreamBackend;
8194
8755
  var init_zrouter_stream = __esm({
8195
8756
  "src/agent/backends/zrouter_stream.ts"() {
8196
8757
  "use strict";
8758
+ init_model_contracts();
8759
+ init_router_catalog();
8197
8760
  init_request_metrics();
8198
8761
  init_sse();
8199
8762
  init_openai_format();
@@ -8205,15 +8768,16 @@ var init_zrouter_stream = __esm({
8205
8768
  init_version();
8206
8769
  init_transport_errors();
8207
8770
  ZROUTER_HEADERS_TIMEOUT_MS = 9e4;
8208
- routerClientSession = randomUUID4();
8771
+ routerClientSession = randomUUID5();
8209
8772
  ROUTER_PIN_MAX_LENGTH = 128;
8210
8773
  PINNABLE_DECORATION = /^[A-Za-z0-9._:-]{1,64}$/;
8211
- DEFAULT_RECONNECT_ATTEMPTS = 6;
8212
- DEFAULT_RECONNECT_BASE_MS = 500;
8213
- DEFAULT_RECONNECT_CAP_MS = 1e4;
8214
- MAX_RECONNECT_ATTEMPTS = 20;
8774
+ DEFAULT_RECONNECT_ATTEMPTS = 120;
8775
+ DEFAULT_RECONNECT_BASE_MS = 1e3;
8776
+ DEFAULT_RECONNECT_CAP_MS = 3e4;
8777
+ MAX_RECONNECT_ATTEMPTS = 120;
8215
8778
  MAX_RECONNECT_MS = 3e5;
8216
8779
  ZRouterStreamBackend = class {
8780
+ catalog;
8217
8781
  apiKey;
8218
8782
  proxyUrl;
8219
8783
  provider;
@@ -8222,6 +8786,7 @@ var init_zrouter_stream = __esm({
8222
8786
  conversationId;
8223
8787
  clientName;
8224
8788
  constructor(config) {
8789
+ this.catalog = config.catalog ? structuredClone(config.catalog) : void 0;
8225
8790
  this.apiKey = config.apiKey;
8226
8791
  this.proxyUrl = config.proxyUrl.replace(/\/+$/, "");
8227
8792
  this.provider = config.provider;
@@ -8242,10 +8807,38 @@ var init_zrouter_stream = __esm({
8242
8807
  handlesTransportRetries() {
8243
8808
  return true;
8244
8809
  }
8810
+ getModelCapabilities(model) {
8811
+ return this.catalog?.version === 1 ? resolveRouterModel(this.catalog, this.provider, model)?.capabilities : void 0;
8812
+ }
8245
8813
  buildOpenAIBody(request) {
8814
+ if (this.catalog?.version === 1) {
8815
+ const descriptor2 = resolveRouterModel(this.catalog, this.provider, request.model);
8816
+ if (!descriptor2?.capabilities?.chat) throw new Error("This model is no longer in the Router chat catalog. Refresh /model.");
8817
+ const caps = descriptor2.capabilities;
8818
+ if (request.codexEffort && caps.reasoning && !caps.reasoning.choices.includes(request.codexEffort)) {
8819
+ throw new Error(`Unsupported reasoning effort: ${request.codexEffort}`);
8820
+ }
8821
+ if (request.maxTokens !== void 0 && caps.max_output_tokens !== void 0 && request.maxTokens > caps.max_output_tokens) {
8822
+ throw new Error(`Max output tokens exceeds the advertised limit (${caps.max_output_tokens}).`);
8823
+ }
8824
+ if (caps.images === false && request.messages.some((message) => Array.isArray(message.content) && message.content.some((block) => block.type === "image" || block.type === "video"))) throw new Error("This model does not support images.");
8825
+ const prepared = caps.tools === false ? chatOnlyRequest(request) : request;
8826
+ return {
8827
+ provider: descriptor2.owned_by,
8828
+ model: descriptor2.id,
8829
+ ...request.routePlan ? { route: request.routePlan } : {},
8830
+ ...request.maxTokens === void 0 ? {} : { max_output_tokens: request.maxTokens },
8831
+ ...caps.reasoning && request.codexEffort ? { reasoning_effort: request.codexEffort } : {},
8832
+ messages: buildOpenAIMessages(prepared),
8833
+ ...prepared.tools.length ? { tools: buildOpenAIToolDefs(prepared.tools) } : {}
8834
+ };
8835
+ }
8246
8836
  if (this.provider === "modal" && !/^modal\//i.test(request.model)) {
8247
8837
  request = { ...request, model: `modal/${request.model}` };
8248
8838
  }
8839
+ if (this.provider === "fireworks") {
8840
+ request = { ...request, model: `fireworks/${normalizeContractModel("fireworks", request.model)}` };
8841
+ }
8249
8842
  const useCompletionTokens = /^o\d/.test(request.model) || /^gpt-5/.test(request.model) || isKimiModel(request.model);
8250
8843
  const isGpt4o = request.model.startsWith("gpt-4o");
8251
8844
  const limit = isGpt4o ? 16384 : isModalDeepSeekFlashModel(request.model) ? MODAL_DEEPSEEK_FLASH_MAX_OUTPUT : isDeepSeekFlashModel(request.model) ? DEEPSEEK_FLASH_MAX_OUTPUT : Infinity;
@@ -8256,18 +8849,32 @@ var init_zrouter_stream = __esm({
8256
8849
  model: request.model,
8257
8850
  ...request.routePlan ? { route: request.routePlan } : {},
8258
8851
  ...tokenParam,
8259
- // Kimi honors a reasoning_effort the picker exposes; without this the same
8260
- // selection applied on a direct key and vanished through the proxy.
8261
- // No-op for every non-Kimi model.
8262
- // Modal uses Kimi's effort ladder but keeps its own max_tokens contract.
8263
- ...buildKimiRequestParams(this.provider === "modal" ? { ...request, model: request.model.slice("modal/".length) } : request),
8264
- ...deepseekRequestParams(request),
8852
+ ...request.codexEffort && modelContract(this.provider, request.model, "chat_tools")?.reasoningEfforts.includes(request.codexEffort) ? { reasoning_effort: request.codexEffort } : {},
8265
8853
  messages: buildOpenAIMessages(request),
8266
8854
  ...request.tools.length > 0 ? { tools: buildOpenAIToolDefs(request.tools) } : {}
8267
8855
  };
8268
8856
  }
8269
- buildBody(request) {
8270
- return this.buildOpenAIBody(request);
8857
+ serializeBody(request, stream = false) {
8858
+ const body = {
8859
+ ...this.buildOpenAIBody(request),
8860
+ ...stream ? { stream: true } : {}
8861
+ };
8862
+ const serialized = JSON.stringify(body);
8863
+ if (body.model === "modal/deepseek-v4.1-flash" || body.model === "fireworks/deepseek-v4p1-flash") {
8864
+ const pending = [JSON.parse(serialized)];
8865
+ let values = 1;
8866
+ while (pending.length > 0) {
8867
+ const value = pending.pop();
8868
+ if (value === null || typeof value !== "object") continue;
8869
+ for (const child of Object.values(value)) {
8870
+ if (++values > 1e4) {
8871
+ throw new Error("Prompt is too long for Router's context structure limit (10,000 JSON values). Compact the conversation before retrying.");
8872
+ }
8873
+ pending.push(child);
8874
+ }
8875
+ }
8876
+ }
8877
+ return serialized;
8271
8878
  }
8272
8879
  async buildHeaders(request) {
8273
8880
  const token = this.authTokenProvider ? await this.authTokenProvider() : this.apiKey;
@@ -8278,6 +8885,7 @@ var init_zrouter_stream = __esm({
8278
8885
  const conversationPin = this.conversationPin(request);
8279
8886
  return {
8280
8887
  "Content-Type": "application/json",
8888
+ ...this.catalog?.version === 1 ? { "X-Zerg-Model-Contract": "1" } : {},
8281
8889
  "Authorization": `Bearer ${normalizedToken}`,
8282
8890
  "X-Zerg-Client": this.clientName,
8283
8891
  "X-Zerg-Client-Version": getVersion(),
@@ -8319,7 +8927,7 @@ var init_zrouter_stream = __esm({
8319
8927
  return measureResponse(routedRequest, (fetcher) => this.generateResponse(routedRequest, fetcher));
8320
8928
  }
8321
8929
  async generateResponse(request, fetcher) {
8322
- const body = this.buildBody(request);
8930
+ const body = this.serializeBody(request);
8323
8931
  const headers = await this.buildHeaders(request);
8324
8932
  runtimeDiag("info", `[ztc] zrouter dispatch ${JSON.stringify({
8325
8933
  endpoint: this.endpointFor(),
@@ -8330,15 +8938,19 @@ var init_zrouter_stream = __esm({
8330
8938
  const response = await fetcher(this.endpointFor(), {
8331
8939
  method: "POST",
8332
8940
  headers,
8333
- body: JSON.stringify(body),
8941
+ body,
8334
8942
  signal: request.signal
8335
8943
  });
8336
8944
  if (!response.ok) {
8337
8945
  const errorText = await response.text();
8338
- throw new Error(`ZRouter API error (${response.status}): ${errorText}`);
8946
+ throw new Error(`ZRouter API error (${response.status}): ${errorText}${failureHeaderSuffix(response)}`);
8339
8947
  }
8340
8948
  const data = await response.json();
8341
- return this.parseOpenAIResponse(data);
8949
+ const parsed = this.parseOpenAIResponse(data);
8950
+ if (this.getModelCapabilities(request.model)?.tools === false && parsed.content.some((block) => block.type === "tool_use")) {
8951
+ throw new Error("Chat-only model returned tools; execution is disabled.");
8952
+ }
8953
+ return parsed;
8342
8954
  }
8343
8955
  parseOpenAIResponse(data) {
8344
8956
  const choices = data.choices || [];
@@ -8376,12 +8988,15 @@ var init_zrouter_stream = __esm({
8376
8988
  return measureStream(routedRequest, (fetcher) => this.streamResponse(routedRequest, fetcher));
8377
8989
  }
8378
8990
  async *streamResponse(request, fetcher) {
8379
- const body = { ...this.buildBody(request), stream: true };
8991
+ const body = this.serializeBody(request, true);
8380
8992
  const mapEvent = createZrouterEventMapper();
8381
8993
  let resumeAfter;
8382
8994
  let emittedMappedEvent = false;
8383
8995
  let resumeSupported = false;
8384
8996
  const policy = resolveReconnectPolicy();
8997
+ let reconnectStarted;
8998
+ const budget = () => resumeAfter ? 3e5 : 3e4;
8999
+ const remaining = () => budget() - (reconnectStarted === void 0 ? 0 : Date.now() - reconnectStarted);
8385
9000
  for (let attempt = 1; attempt <= policy.attempts; attempt++) {
8386
9001
  try {
8387
9002
  const headers = await this.buildHeaders(request);
@@ -8389,12 +9004,12 @@ var init_zrouter_stream = __esm({
8389
9004
  const response = await fetchWithHeadersTimeout(fetcher, this.endpointFor(), {
8390
9005
  method: "POST",
8391
9006
  headers,
8392
- body: JSON.stringify(body),
9007
+ body,
8393
9008
  signal: request.signal
8394
- }, ZROUTER_HEADERS_TIMEOUT_MS);
9009
+ }, Math.min(ZROUTER_HEADERS_TIMEOUT_MS, Math.max(1, remaining())));
8395
9010
  if (!response.ok) {
8396
9011
  const errorText = await response.text();
8397
- throw new Error(`ZRouter API error (${response.status}): ${errorText}`);
9012
+ throw new Error(`ZRouter API error (${response.status}): ${errorText}${failureHeaderSuffix(response)}`);
8398
9013
  }
8399
9014
  if (!response.body) {
8400
9015
  throw new Error("ZRouter API error: no response body for streaming request");
@@ -8415,6 +9030,9 @@ var init_zrouter_stream = __esm({
8415
9030
  throw new Error(`ZRouter API error (${status}): ${JSON.stringify(ev.body ?? {})}`);
8416
9031
  }
8417
9032
  const mapped = mapEvent(ev);
9033
+ if (this.getModelCapabilities(request.model)?.tools === false && mapped.some((event2) => event2.type.startsWith("tool_use"))) {
9034
+ throw new Error("Chat-only model returned tools; execution is disabled.");
9035
+ }
8418
9036
  if (mapped.length > 0) {
8419
9037
  if (mapped.some((event2) => event2.type !== "heartbeat")) emittedMappedEvent = true;
8420
9038
  if (!validEventId) resumeSupported = false;
@@ -8427,12 +9045,16 @@ var init_zrouter_stream = __esm({
8427
9045
  if (request.signal?.aborted || !retryable || emittedMappedEvent && (!resumeSupported || !resumeAfter)) {
8428
9046
  throw error;
8429
9047
  }
8430
- if (attempt === policy.attempts) throw error;
8431
- const delayMs = reconnectDelay(policy, attempt);
9048
+ reconnectStarted ??= Date.now();
9049
+ const attempts = resumeAfter ? policy.attempts : Math.min(6, policy.attempts);
9050
+ if (attempt >= attempts || remaining() <= 0) throw error;
9051
+ const delayMs = Math.min(reconnectDelay(policy, attempt), remaining());
8432
9052
  const reason = error instanceof Error ? error.message : String(error);
8433
9053
  runtimeDiag("warn", `[ztc] zrouter stream dropped (${reason}); reconnecting in ${delayMs}ms (attempt ${attempt + 1}/${policy.attempts})`);
8434
- request.onTransportRetry?.({ attempt, maxAttempts: policy.attempts, delayMs, reason });
9054
+ const publicReason = isRetryableResumeStatus(error) ? `Router temporarily unavailable ${reason.match(/\(\d{3}\)/)?.[0] ?? ""}. Resuming the response.` : reason;
9055
+ request.onTransportRetry?.({ attempt, maxAttempts: attempts, delayMs, reason: publicReason });
8435
9056
  await waitForReconnect(delayMs, request.signal);
9057
+ if (remaining() <= 0) throw error;
8436
9058
  }
8437
9059
  }
8438
9060
  }
@@ -8759,10 +9381,17 @@ function createBackendForProvider(provider, opts) {
8759
9381
  return traced(provider, new KimiBackend({ apiKey: opts.apiKey }));
8760
9382
  case "deepseek":
8761
9383
  return traced(provider, new OpenAICompatibleBackend({
9384
+ provider: "deepseek",
8762
9385
  apiKey: opts.apiKey,
8763
9386
  baseUrl: opts.baseUrl || "https://api.deepseek.com/v1",
8764
9387
  requestBodyExtras: deepseekRequestParams
8765
9388
  }));
9389
+ case "fireworks":
9390
+ return traced(provider, new OpenAICompatibleBackend({
9391
+ apiKey: opts.apiKey,
9392
+ baseUrl: opts.baseUrl || "https://api.fireworks.ai/inference/v1",
9393
+ provider: "fireworks"
9394
+ }));
8766
9395
  case "clarifai":
8767
9396
  return traced(provider, new ClarifaiBackend({ apiKey: opts.apiKey, headers: opts.extraHeaders }));
8768
9397
  case CODEX_APP_SERVER_PROVIDER:
@@ -8802,8 +9431,9 @@ function createBackendForProvider(provider, opts) {
8802
9431
  }
8803
9432
  case "anthropic":
8804
9433
  case "claude":
8805
- default:
8806
9434
  return traced(provider, new AnthropicBackend({ apiKey: opts.apiKey, apiEndpoint: opts.apiEndpoint }));
9435
+ default:
9436
+ throw new Error(`Unknown provider: ${provider}. Select a Router integration or a supported direct provider.`);
8807
9437
  }
8808
9438
  }
8809
9439
  var TracedBackend;
@@ -8850,6 +9480,9 @@ var init_backends = __esm({
8850
9480
  }
8851
9481
  provider;
8852
9482
  backend;
9483
+ getModelCapabilities(model) {
9484
+ return this.backend.getModelCapabilities?.(model);
9485
+ }
8853
9486
  async steerUserMessage(message) {
8854
9487
  return this.backend.steerUserMessage?.(message) ?? false;
8855
9488
  }
@@ -9315,8 +9948,8 @@ init_backends();
9315
9948
  init_token_usage();
9316
9949
  init_transport_errors();
9317
9950
  init_defaults();
9318
- import { randomUUID as randomUUID6 } from "node:crypto";
9319
- import { existsSync as existsSync5, readFileSync as readFileSync8 } from "fs";
9951
+ import { randomUUID as randomUUID7 } from "node:crypto";
9952
+ import { existsSync as existsSync5, readFileSync as readFileSync9 } from "fs";
9320
9953
  import { extname } from "path";
9321
9954
 
9322
9955
  // src/config/api_retry.ts
@@ -9427,10 +10060,10 @@ function createRecoveryGuard(settings) {
9427
10060
  }
9428
10061
 
9429
10062
  // src/agent/runtime/command_jobs.ts
9430
- import { randomUUID as randomUUID5, createHash as createHash3 } from "node:crypto";
9431
- import { appendFileSync as appendFileSync4, closeSync, mkdirSync as mkdirSync3, openSync, readFileSync as readFileSync6, readSync, readdirSync, rmSync, rmdirSync, statSync, writeFileSync as writeFileSync5 } from "node:fs";
10063
+ import { randomUUID as randomUUID6, createHash as createHash4 } from "node:crypto";
10064
+ import { appendFileSync as appendFileSync4, closeSync, mkdirSync as mkdirSync3, openSync, readFileSync as readFileSync7, readSync, readdirSync, rmSync, rmdirSync, statSync as statSync2, writeFileSync as writeFileSync5 } from "node:fs";
9432
10065
  import { homedir as homedir6 } from "node:os";
9433
- import { join as join11 } from "node:path";
10066
+ import { join as join12 } from "node:path";
9434
10067
 
9435
10068
  // src/utils/subprocess_env.ts
9436
10069
  var USER_SUBPROCESS_STRIPPED_ZTC_ENV = [
@@ -9466,14 +10099,14 @@ var MAX_LOG_BYTES = 16 * 1024 * 1024;
9466
10099
  var RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
9467
10100
  var sessions = /* @__PURE__ */ new Map();
9468
10101
  function directory(sessionId) {
9469
- return join11(
9470
- process.env.ZTC_CONFIG_DIR || join11(homedir6(), ".ztc"),
10102
+ return join12(
10103
+ process.env.ZTC_CONFIG_DIR || join12(homedir6(), ".ztc"),
9471
10104
  "command-jobs",
9472
- createHash3("sha256").update(sessionId).digest("hex").slice(0, 32)
10105
+ createHash4("sha256").update(sessionId).digest("hex").slice(0, 32)
9473
10106
  );
9474
10107
  }
9475
10108
  function save(sessionId, receipt) {
9476
- writeFileSync5(join11(directory(sessionId), `${receipt.job_id}.json`), JSON.stringify(receipt), { mode: 384 });
10109
+ writeFileSync5(join12(directory(sessionId), `${receipt.job_id}.json`), JSON.stringify(receipt), { mode: 384 });
9477
10110
  }
9478
10111
  function resolveJob(sessionId, jobId) {
9479
10112
  if (!/^[0-9a-f-]{36}$/.test(jobId)) throw new Error("Command job not found in this session");
@@ -9481,7 +10114,7 @@ function resolveJob(sessionId, jobId) {
9481
10114
  if (live) return live;
9482
10115
  let receipt;
9483
10116
  try {
9484
- receipt = JSON.parse(readFileSync6(join11(directory(sessionId), `${jobId}.json`), "utf8"));
10117
+ receipt = JSON.parse(readFileSync7(join12(directory(sessionId), `${jobId}.json`), "utf8"));
9485
10118
  } catch {
9486
10119
  throw new Error("Command job not found in this session");
9487
10120
  }
@@ -9489,7 +10122,7 @@ function resolveJob(sessionId, jobId) {
9489
10122
  receipt = { ...receipt, status: "interrupted", ended_at: (/* @__PURE__ */ new Date()).toISOString(), error: "Owning ZTC process ended; inspect output before retrying." };
9490
10123
  save(sessionId, receipt);
9491
10124
  }
9492
- return { receipt, controller: new AbortController(), done: Promise.resolve(), loggedBytes: statSync(receipt.log_path).size };
10125
+ return { receipt, controller: new AbortController(), done: Promise.resolve(), loggedBytes: statSync2(receipt.log_path).size };
9493
10126
  }
9494
10127
  function listCommandJobs(sessionId) {
9495
10128
  const jobs = [...sessions.get(sessionId)?.values() ?? []];
@@ -9859,8 +10492,8 @@ init_tool_response_store();
9859
10492
 
9860
10493
  // src/utils/mirror_media.ts
9861
10494
  import { tmpdir as tmpdir2 } from "os";
9862
- import { join as join12, resolve as resolve5, sep as sep2 } from "path";
9863
- var MIRROR_MEDIA_ROOT = join12(tmpdir2(), "ztc-mirror-media");
10495
+ import { join as join13, resolve as resolve5, sep as sep2 } from "path";
10496
+ var MIRROR_MEDIA_ROOT = join13(tmpdir2(), "ztc-mirror-media");
9864
10497
  function isMirrorMediaPath(localPath) {
9865
10498
  if (!localPath) return false;
9866
10499
  const root = resolve5(MIRROR_MEDIA_ROOT);
@@ -10089,13 +10722,13 @@ init_tool_compression();
10089
10722
  // src/agent/runtime/tool_runtime.ts
10090
10723
  init_capabilities();
10091
10724
  init_config();
10092
- import { basename as basename3, dirname as dirname6, join as join14, relative as relative2, resolve as resolve7 } from "path";
10725
+ import { basename as basename3, dirname as dirname8, join as join15, relative as relative2, resolve as resolve7 } from "path";
10093
10726
  import { realpathSync } from "node:fs";
10094
10727
  import { appendFile } from "node:fs/promises";
10095
10728
 
10096
10729
  // src/context/intents_loader.ts
10097
- import { appendFileSync as appendFileSync5, existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync7, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
10098
- import { basename as basename2, dirname as dirname5, join as join13, relative, resolve as resolve6 } from "path";
10730
+ import { appendFileSync as appendFileSync5, existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync8, statSync as statSync3, writeFileSync as writeFileSync6 } from "fs";
10731
+ import { basename as basename2, dirname as dirname7, join as join14, relative, resolve as resolve6 } from "path";
10099
10732
  var DEFAULT_MAX_BYTES = 32 * 1024;
10100
10733
  var DEFAULT_FILE_INTENT_THRESHOLD_LINES = 400;
10101
10734
  function loadIntentsContext(cwd = process.cwd(), settings, options = {}) {
@@ -10166,7 +10799,7 @@ function buildIntentCandidates(cwd, relatedPaths, includeHierarchy) {
10166
10799
  if (includeHierarchy) {
10167
10800
  const roots = findRootToCwd(resolve6(cwd));
10168
10801
  for (const dir of roots) {
10169
- pushUnique(candidates, seen, join13(dir, ".ztc-intents.md"));
10802
+ pushUnique(candidates, seen, join14(dir, ".ztc-intents.md"));
10170
10803
  }
10171
10804
  }
10172
10805
  for (const relatedPath of relatedPaths) {
@@ -10180,10 +10813,10 @@ function findRootToCwd(cwd) {
10180
10813
  let current = cwd;
10181
10814
  for (; ; ) {
10182
10815
  chain.push(current);
10183
- if (existsSync4(join13(current, ".git"))) {
10816
+ if (existsSync4(join14(current, ".git"))) {
10184
10817
  break;
10185
10818
  }
10186
- const parent = dirname5(current);
10819
+ const parent = dirname7(current);
10187
10820
  if (parent === current) {
10188
10821
  break;
10189
10822
  }
@@ -10195,16 +10828,16 @@ function selectIntentTarget(cwd, absolutePath, threshold) {
10195
10828
  if (isLargeFile(absolutePath, threshold)) {
10196
10829
  return fileIntentPath(absolutePath);
10197
10830
  }
10198
- const dir = existsSync4(absolutePath) && statSync2(absolutePath).isDirectory() ? absolutePath : dirname5(absolutePath);
10831
+ const dir = existsSync4(absolutePath) && statSync3(absolutePath).isDirectory() ? absolutePath : dirname7(absolutePath);
10199
10832
  const normalizedCwd = resolve6(cwd);
10200
10833
  const normalizedDir = resolve6(dir);
10201
10834
  if (!isInside(normalizedDir, normalizedCwd)) {
10202
- return join13(normalizedCwd, ".ztc-intents.md");
10835
+ return join14(normalizedCwd, ".ztc-intents.md");
10203
10836
  }
10204
- return join13(normalizedDir, ".ztc-intents.md");
10837
+ return join14(normalizedDir, ".ztc-intents.md");
10205
10838
  }
10206
10839
  function ensureIntentFile(path) {
10207
- mkdirSync4(dirname5(path), { recursive: true });
10840
+ mkdirSync4(dirname7(path), { recursive: true });
10208
10841
  if (existsSync4(path)) {
10209
10842
  return;
10210
10843
  }
@@ -10219,14 +10852,14 @@ function ensureIntentFile(path) {
10219
10852
  ].join("\n") + "\n", "utf-8");
10220
10853
  }
10221
10854
  function fileIntentPath(absolutePath) {
10222
- return join13(dirname5(absolutePath), `.ztc-intents-${basename2(absolutePath)}.md`);
10855
+ return join14(dirname7(absolutePath), `.ztc-intents-${basename2(absolutePath)}.md`);
10223
10856
  }
10224
10857
  function isLargeFile(path, threshold) {
10225
10858
  try {
10226
- if (!existsSync4(path) || statSync2(path).isDirectory()) {
10859
+ if (!existsSync4(path) || statSync3(path).isDirectory()) {
10227
10860
  return false;
10228
10861
  }
10229
- const raw = readFileSync7(path, "utf-8");
10862
+ const raw = readFileSync8(path, "utf-8");
10230
10863
  return raw.split("\n").length >= threshold;
10231
10864
  } catch {
10232
10865
  return false;
@@ -10237,7 +10870,7 @@ function loadBudgetedFile(path, maxBytes) {
10237
10870
  return null;
10238
10871
  }
10239
10872
  try {
10240
- const raw = readFileSync7(path, "utf-8");
10873
+ const raw = readFileSync8(path, "utf-8");
10241
10874
  const bytes = Buffer.byteLength(raw, "utf-8");
10242
10875
  if (bytes <= maxBytes) {
10243
10876
  return {
@@ -10282,7 +10915,7 @@ function isHostControlPlaneWrite(tool, args, cwd) {
10282
10915
  try {
10283
10916
  const noFollowLeaf = (path) => {
10284
10917
  const absolute = resolve7(path);
10285
- return join14(realpathSync(dirname6(absolute)), basename3(absolute));
10918
+ return join15(realpathSync(dirname8(absolute)), basename3(absolute));
10286
10919
  };
10287
10920
  return noFollowLeaf(resolve7(cwd, args.path)) === noFollowLeaf(tool.hostControlPlaneWritePath);
10288
10921
  } catch {
@@ -10350,7 +10983,7 @@ ${intentContext}`;
10350
10983
  async function recordToolTrace(entry) {
10351
10984
  const configured = process.env.ZTC_TOOL_TRACE;
10352
10985
  if (!configured) return;
10353
- const target = configured === "1" ? join14(process.cwd(), "ztc-tool-trace.jsonl") : configured;
10986
+ const target = configured === "1" ? join15(process.cwd(), "ztc-tool-trace.jsonl") : configured;
10354
10987
  try {
10355
10988
  await appendFile(target, `${JSON.stringify(entry)}
10356
10989
  `, "utf-8");
@@ -10803,7 +11436,7 @@ var Agent = class _Agent {
10803
11436
  routePlan: config.routePlan,
10804
11437
  maxIterations: config.maxIterations || 10,
10805
11438
  execution: resolveExecutionSettings(config.execution),
10806
- sessionId: config.sessionId ?? randomUUID6(),
11439
+ sessionId: config.sessionId ?? randomUUID7(),
10807
11440
  runtimeFacts: config.runtimeFacts ?? {},
10808
11441
  apiRetry: resolveApiRetrySettings({ apiRetry: config.apiRetry }),
10809
11442
  llmRequestTimeoutMs: resolveLlmRequestTimeoutMs(
@@ -10829,6 +11462,10 @@ var Agent = class _Agent {
10829
11462
  this.alwaysLoadedToolNames.add(name);
10830
11463
  }
10831
11464
  this.backend = this.config.backend;
11465
+ if (this.backend.getModelCapabilities?.(this.config.model)?.tools === false) {
11466
+ this.config.tools = [];
11467
+ this.config.autoContinue = false;
11468
+ }
10832
11469
  this.policy = this.config.policy;
10833
11470
  this.tracer = this.config.tracer;
10834
11471
  this._cwd = this.config.cwd;
@@ -10843,7 +11480,7 @@ var Agent = class _Agent {
10843
11480
  this._hasCustomSystemPrompt = typeof config.systemPrompt === "string" && config.systemPrompt.trim().length > 0;
10844
11481
  }
10845
11482
  setAutoContinue(enabled2) {
10846
- this._autoContinue = enabled2;
11483
+ this._autoContinue = enabled2 && this.backend.getModelCapabilities?.(this.config.model)?.tools !== false;
10847
11484
  }
10848
11485
  /** Override the agent's system prompt (e.g., for simulation persona injection). */
10849
11486
  setSystemPrompt(prompt) {
@@ -10978,7 +11615,7 @@ INVESTIGATION STRATEGY \u2014 before making tool calls to explore or debug, foll
10978
11615
  this._currentRequestToolIds = currentTurnToolIds;
10979
11616
  if (allToolIds.length > 0) {
10980
11617
  const activeModel = this.config.model || "default";
10981
- const budget = this.toolResponseStore.effectiveBudget(activeModel);
11618
+ const budget = this.toolResponseStore.effectiveBudget(activeModel, this.backend.getModelCapabilities?.(activeModel) ? this.backend.getModelCapabilities(activeModel)?.context_window ?? null : void 0);
10982
11619
  this._selectorVerdict = this.toolResponseStore.selectInlineSet(
10983
11620
  allToolIds,
10984
11621
  currentTurnToolIds,
@@ -11033,7 +11670,7 @@ INVESTIGATION STRATEGY \u2014 before making tool calls to explore or debug, foll
11033
11670
  const ext = extname(path).toLowerCase();
11034
11671
  const mediaType = this.getImageMediaType(ext);
11035
11672
  if (!mediaType) return null;
11036
- const buffer = readFileSync8(path);
11673
+ const buffer = readFileSync9(path);
11037
11674
  return {
11038
11675
  type: "image",
11039
11676
  mediaType,
@@ -11093,7 +11730,7 @@ INVESTIGATION STRATEGY \u2014 before making tool calls to explore or debug, foll
11093
11730
  const ext = extname(path).toLowerCase();
11094
11731
  const mediaType = this.getVideoMediaType(ext);
11095
11732
  if (!mediaType) return null;
11096
- const buffer = readFileSync8(path);
11733
+ const buffer = readFileSync9(path);
11097
11734
  return {
11098
11735
  type: "video",
11099
11736
  mediaType,
@@ -11538,7 +12175,7 @@ INVESTIGATION STRATEGY \u2014 before making tool calls to explore or debug, foll
11538
12175
  }
11539
12176
  const result = await this.runCore(workingMessages, signal);
11540
12177
  const assistantMessage = {
11541
- id: `host_assistant_${randomUUID6()}`,
12178
+ id: `host_assistant_${randomUUID7()}`,
11542
12179
  role: "assistant",
11543
12180
  content: result.content,
11544
12181
  timestamp: /* @__PURE__ */ new Date(),
@@ -11647,6 +12284,28 @@ INVESTIGATION STRATEGY \u2014 before making tool calls to explore or debug, foll
11647
12284
  finalReasoning = response.reasoning;
11648
12285
  }
11649
12286
  if (!this._lastResponseStreamed) await this.emitStreamText(iterationText, signal);
12287
+ if (iterations < this.config.maxIterations) {
12288
+ const steering = takeSteering?.() ?? [];
12289
+ if (steering.length > 0) {
12290
+ if (iterationText.trim()) {
12291
+ const answer = {
12292
+ id: `assistant_${Date.now()}_${iterations}`,
12293
+ role: "assistant",
12294
+ content: iterationText,
12295
+ timestamp: /* @__PURE__ */ new Date(),
12296
+ ...response.reasoning ? { metadata: { reasoningContent: response.reasoning } } : {}
12297
+ };
12298
+ messages.push(answer);
12299
+ turnMessages.push(answer);
12300
+ }
12301
+ messages.push(...steering);
12302
+ turnMessages.push(...steering);
12303
+ this.emit({ type: "history_checkpoint", messages: [...turnMessages] });
12304
+ finalContent = "";
12305
+ finalReasoning = void 0;
12306
+ continue;
12307
+ }
12308
+ }
11650
12309
  if (this.shouldContinue(iterationText) && this._consecutiveAutoContinues < this.config.maxConsecutiveAutoContinues) {
11651
12310
  this._consecutiveAutoContinues++;
11652
12311
  const acTodos = getLatestTodos();
@@ -11880,7 +12539,7 @@ INVESTIGATION STRATEGY \u2014 before making tool calls to explore or debug, foll
11880
12539
  if (recoveryAction === "stop") throw new AgentError("Automatic recovery budget exhausted after repeated tool failures. Inspect the retained results and current capabilities before retrying.", "RECOVERY_EXHAUSTED");
11881
12540
  if (recoveryAction === "recover") {
11882
12541
  const recoveryMessage = {
11883
- id: `recovery_${randomUUID6()}`,
12542
+ id: `recovery_${randomUUID7()}`,
11884
12543
  role: "user",
11885
12544
  content: "[Recovery] Repeated failures share the same cause. Stop that sequence. Inspect current capabilities and active jobs, then make one changed recovery attempt. Use file/stdin tools for scripts and raw Ctrl+C for stuck shell input, then read the pane in the same response or immediately afterward. A silent running-job poll allows one further inspection before stopping; do not repeat empty polls. Continue authorized work; use ask_user only for a concrete missing prerequisite. Do not repeat an acknowledgement as proof of success.",
11886
12545
  metadata: { synthetic: true },
@@ -12042,7 +12701,7 @@ INVESTIGATION STRATEGY \u2014 before making tool calls to explore or debug, foll
12042
12701
  const { maxAttempts, baseDelayMs: baseDelay } = this.config.apiRetry;
12043
12702
  const timeoutMs = this.config.llmRequestTimeoutMs;
12044
12703
  if (this.backend.handlesTransportRetries?.(request) === true) {
12045
- request = { ...request, transportTurnId: request.transportTurnId ?? randomUUID6() };
12704
+ request = { ...request, transportTurnId: request.transportTurnId ?? randomUUID7() };
12046
12705
  }
12047
12706
  const useStream = process.env.ZTC_NO_STREAM !== "1" && !this.backend.streamsSyntheticallyFor?.(request);
12048
12707
  let answerOutputEmitted = false;
@@ -12191,7 +12850,8 @@ INVESTIGATION STRATEGY \u2014 before making tool calls to explore or debug, foll
12191
12850
  let errorMessage = "";
12192
12851
  let admissionMessage = "";
12193
12852
  try {
12194
- const jsonMatch = message.match(/:\s*(\{[\s\S]*\})\s*$/);
12853
+ const bodyMessage = message.replace(/ \[(?:(?:fly-request-id|x-request-id|cf-ray|x-zerg-error-code)=[^\]\r\n ]+ ?)+\]$/, "");
12854
+ const jsonMatch = bodyMessage.match(/:\s*(\{[\s\S]*\})\s*$/);
12195
12855
  if (jsonMatch) {
12196
12856
  const parsed = JSON.parse(jsonMatch[1]);
12197
12857
  const errorPayload = parsed.error;
@@ -12218,6 +12878,9 @@ INVESTIGATION STRATEGY \u2014 before making tool calls to explore or debug, foll
12218
12878
  }
12219
12879
  } catch {
12220
12880
  }
12881
+ if (statusCode === 410 && message.startsWith("ZRouter API error (") && ["stream_producer_lost", "stream_producer_failed", "stream_resume_expired"].includes(errorCode)) {
12882
+ return { retryable: false, reason: "The Router stream was interrupted and cannot be resumed. Send a message to continue from the existing conversation." };
12883
+ }
12221
12884
  if (errorCode === "context_length_exceeded" || isContextOverflowMessage(`${errorMessage} ${message}`)) {
12222
12885
  return { retryable: false, reason: "Context window exceeded", kind: "context_overflow" };
12223
12886
  }
@@ -12228,7 +12891,10 @@ INVESTIGATION STRATEGY \u2014 before making tool calls to explore or debug, foll
12228
12891
  return { retryable: true, reason: "Rate limit exceeded" };
12229
12892
  }
12230
12893
  if (statusCode === 503 || statusCode === 502 || statusCode === 500 || errorType === "UNAVAILABLE" || errorType === "INTERNAL") {
12231
- return { retryable: true, reason: "Server temporarily unavailable" };
12894
+ const requestId = /(?:fly-request-id|x-request-id)=([A-Za-z0-9._:-]{1,128})(?:[ \]]|$)/.exec(message)?.[1];
12895
+ const detail = [admissionMessage.trim().replace(/\s+/g, " "), requestId && `request_id: ${requestId}`].filter(Boolean).join("; ");
12896
+ const cause = detail ? `: ${detail.length > 160 ? `${detail.slice(0, 159)}\u2026` : detail}` : "";
12897
+ return { retryable: true, reason: `Upstream server error (${statusCode || "no status"})${cause}` };
12232
12898
  }
12233
12899
  if (statusCode === 401 || errorType === "authentication_error" || errorType === "UNAUTHENTICATED") {
12234
12900
  return {
@@ -12239,7 +12905,7 @@ INVESTIGATION STRATEGY \u2014 before making tool calls to explore or debug, foll
12239
12905
  if (statusCode === 403 || errorType === "PERMISSION_DENIED") {
12240
12906
  return { retryable: false, reason: admissionMessage || "Permission denied" };
12241
12907
  }
12242
- if (statusCode === 402) {
12908
+ if (statusCode === 402 || errorCode === "billing_required") {
12243
12909
  const guidance = "Subscribe in Zerg Router (Dashboard \u2192 Billing), or choose another model with /model.";
12244
12910
  return {
12245
12911
  retryable: false,
@@ -12274,8 +12940,8 @@ init_capabilities();
12274
12940
  init_logger();
12275
12941
  init_extension_discovery();
12276
12942
  import { homedir as homedir8 } from "os";
12277
- import { join as join15, resolve as resolve8, basename as basename4 } from "path";
12278
- import { readdir as readdir2, readFile as readFile4 } from "fs/promises";
12943
+ import { join as join16, resolve as resolve8, basename as basename4 } from "path";
12944
+ import { readdir as readdir3, readFile as readFile4 } from "fs/promises";
12279
12945
  var MAX_DEPTH = 4;
12280
12946
  function defaultMarkdownRoots() {
12281
12947
  const explicitRoots = extensionPathsFromEnvironment("ZTC_SKILL_PATHS");
@@ -12283,20 +12949,20 @@ function defaultMarkdownRoots() {
12283
12949
  const home = homedir8();
12284
12950
  return [
12285
12951
  ...explicitRoots,
12286
- join15(process.cwd(), ".ztc", "skills"),
12287
- join15(process.cwd(), ".claude", "skills"),
12288
- join15(home, ".ztc", "skills"),
12289
- join15(home, ".claude", "skills")
12952
+ join16(process.cwd(), ".ztc", "skills"),
12953
+ join16(process.cwd(), ".claude", "skills"),
12954
+ join16(home, ".ztc", "skills"),
12955
+ join16(home, ".claude", "skills")
12290
12956
  ];
12291
12957
  }
12292
12958
  async function walk(dir, depth = 0) {
12293
12959
  if (depth > MAX_DEPTH) return [];
12294
12960
  try {
12295
- const entries = await readdir2(dir, { withFileTypes: true });
12961
+ const entries = await readdir3(dir, { withFileTypes: true });
12296
12962
  const results = [];
12297
12963
  for (const entry of entries) {
12298
12964
  if (entry.name.startsWith(".")) continue;
12299
- const full = join15(dir, entry.name);
12965
+ const full = join16(dir, entry.name);
12300
12966
  if (entry.isDirectory()) {
12301
12967
  results.push(...await walk(full, depth + 1));
12302
12968
  } else if (entry.isFile() && entry.name.toLowerCase() === "skill.md") {
@@ -12438,12 +13104,12 @@ async function loadSkills(roots = defaultMarkdownRoots()) {
12438
13104
  }
12439
13105
 
12440
13106
  // src/skills/bundled/index.ts
12441
- import { dirname as dirname7, resolve as resolve9 } from "path";
13107
+ import { dirname as dirname9, resolve as resolve9 } from "path";
12442
13108
  import { fileURLToPath as fileURLToPath2 } from "url";
12443
13109
 
12444
13110
  // src/skills/bundled/init-context.ts
12445
- import { readdir as readdir3, readFile as readFile5, stat } from "fs/promises";
12446
- import { join as join16 } from "path";
13111
+ import { readdir as readdir4, readFile as readFile5, stat as stat2 } from "fs/promises";
13112
+ import { join as join17 } from "path";
12447
13113
  var MAX_ENTRIES = 60;
12448
13114
  var TOP_LEVEL_DIRS_OF_INTEREST = [
12449
13115
  "src",
@@ -12487,7 +13153,7 @@ var MANIFESTS = [
12487
13153
  var CLAUDE_MD_CANDIDATES = ["CLAUDE.md", "AGENTS.md", "claude.md"];
12488
13154
  async function fileExists(path) {
12489
13155
  try {
12490
- await stat(path);
13156
+ await stat2(path);
12491
13157
  return true;
12492
13158
  } catch {
12493
13159
  return false;
@@ -12495,7 +13161,7 @@ async function fileExists(path) {
12495
13161
  }
12496
13162
  async function safeReadDir(path) {
12497
13163
  try {
12498
- const entries = await readdir3(path, { withFileTypes: true });
13164
+ const entries = await readdir4(path, { withFileTypes: true });
12499
13165
  return entries.filter((e) => !e.name.startsWith(".")).map((e) => e.isDirectory() ? `${e.name}/` : e.name);
12500
13166
  } catch {
12501
13167
  return [];
@@ -12504,7 +13170,7 @@ async function safeReadDir(path) {
12504
13170
  async function detectManifests(cwd) {
12505
13171
  const found = [];
12506
13172
  for (const { file, label } of MANIFESTS) {
12507
- if (await fileExists(join16(cwd, file))) {
13173
+ if (await fileExists(join17(cwd, file))) {
12508
13174
  found.push(`- ${file} \u2014 ${label}`);
12509
13175
  }
12510
13176
  }
@@ -12512,7 +13178,7 @@ async function detectManifests(cwd) {
12512
13178
  }
12513
13179
  async function readClaudeMd(cwd) {
12514
13180
  for (const name of CLAUDE_MD_CANDIDATES) {
12515
- const path = join16(cwd, name);
13181
+ const path = join17(cwd, name);
12516
13182
  if (await fileExists(path)) {
12517
13183
  try {
12518
13184
  const contents = await readFile5(path, "utf-8");
@@ -12592,7 +13258,7 @@ var bundledCodeSkills = [
12592
13258
  initContextSkill
12593
13259
  ];
12594
13260
  function bundledMarkdownRoot() {
12595
- const here = dirname7(fileURLToPath2(import.meta.url));
13261
+ const here = dirname9(fileURLToPath2(import.meta.url));
12596
13262
  return resolve9(here);
12597
13263
  }
12598
13264
 
@@ -12771,7 +13437,7 @@ var invokeSkillTool = makeInvokeSkillTool(resolveAmbientSkills);
12771
13437
  // src/zlm.ts
12772
13438
  var ZLM_EMBED_API_VERSION = "zerglang.ztc-embed/1";
12773
13439
  var ZLM_STDIO_PROTOCOL = "zerglang.ztc-host/1";
12774
- var ZLM_ZTC_VERSION = true ? "0.4.0-beta.1" : "development";
13440
+ var ZLM_ZTC_VERSION = true ? "0.4.1-dev.36919234045" : "development";
12775
13441
  var ZLM_MAX_HOST_TOOLS = 64;
12776
13442
  var ZLM_MAX_SYSTEM_PROMPT_BYTES = 128 * 1024;
12777
13443
  var ZLM_HOST_APPROVAL_CAPABILITIES = Object.freeze([