salmon-loop 0.3.2 → 0.4.1

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 (121) hide show
  1. package/dist/cli/authorization/non-interactive.js +9 -13
  2. package/dist/cli/chat.js +12 -6
  3. package/dist/cli/commands/allowlist.js +1 -1
  4. package/dist/cli/commands/chat.js +13 -13
  5. package/dist/cli/commands/parallel.js +1 -1
  6. package/dist/cli/commands/run/handler.js +6 -3
  7. package/dist/cli/commands/run/loop-params.js +1 -0
  8. package/dist/cli/commands/run/parse-options.js +14 -26
  9. package/dist/cli/commands/run/runtime-llm.js +15 -12
  10. package/dist/cli/headless/openai-responses-canonical-applier.js +1 -7
  11. package/dist/cli/reporters/standard.js +2 -3
  12. package/dist/cli/reporters/stream-json.js +2 -1
  13. package/dist/cli/slash/runtime.js +2 -2
  14. package/dist/cli/ui/hooks/useLoopEvents.js +1 -1
  15. package/dist/cli/ui/hooks/useLoopState.js +1 -1
  16. package/dist/core/ast/parser.js +18 -9
  17. package/dist/core/config/schema.js +738 -0
  18. package/dist/core/config/validate.js +11 -922
  19. package/dist/core/context/gatherers/ast-gatherer.js +4 -12
  20. package/dist/core/context/gatherers/ghost-dependency-gatherer.js +0 -1
  21. package/dist/core/context/gatherers/knowledge-gatherer.js +3 -0
  22. package/dist/core/context/service.js +8 -0
  23. package/dist/core/context/token/encoding-registry.js +7 -6
  24. package/dist/core/extensions/index.js +48 -3
  25. package/dist/core/extensions/load.js +3 -2
  26. package/dist/core/extensions/merge.js +5 -1
  27. package/dist/core/extensions/paths.js +6 -0
  28. package/dist/core/extensions/schemas.js +21 -0
  29. package/dist/core/facades/cli-command-chat.js +2 -0
  30. package/dist/core/facades/cli-run-handler.js +1 -0
  31. package/dist/core/facades/cli-utils-serialize.js +2 -0
  32. package/dist/core/grizzco/dsl/llm-strategy.js +3 -2
  33. package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +15 -10
  34. package/dist/core/grizzco/engine/pipeline/pipeline.js +149 -240
  35. package/dist/core/grizzco/engine/transaction/attempt-failure.js +5 -4
  36. package/dist/core/grizzco/engine/transaction/authorization-summary.js +2 -1
  37. package/dist/core/grizzco/runtime/apply-back-runtime.js +2 -1
  38. package/dist/core/grizzco/services/registry.js +18 -0
  39. package/dist/core/grizzco/steps/audit.js +20 -10
  40. package/dist/core/grizzco/steps/display-report.js +4 -11
  41. package/dist/core/grizzco/steps/explore.js +9 -2
  42. package/dist/core/grizzco/steps/patch/prompt-input.js +4 -1
  43. package/dist/core/grizzco/steps/patch.js +1 -0
  44. package/dist/core/grizzco/steps/plan.js +58 -49
  45. package/dist/core/grizzco/steps/tool-runtime.js +3 -0
  46. package/dist/core/grizzco/workers/strata-sync-worker.js +2 -1
  47. package/dist/core/llm/ai-sdk/message-mapper.js +24 -18
  48. package/dist/core/llm/ai-sdk/request-params.js +1 -3
  49. package/dist/core/llm/ai-sdk/result-mapper.js +14 -8
  50. package/dist/core/llm/ai-sdk/retry-classifier.js +6 -4
  51. package/dist/core/llm/contracts/repair.js +16 -8
  52. package/dist/core/llm/errors.js +13 -10
  53. package/dist/core/llm/output-policy.js +8 -0
  54. package/dist/core/llm/redact.js +1 -3
  55. package/dist/core/llm/sub-agent-factory.js +48 -0
  56. package/dist/core/llm/tool-calling-stub.js +48 -0
  57. package/dist/core/llm/utils.js +17 -6
  58. package/dist/core/mcp/bridge/prompt-command-provider.js +4 -3
  59. package/dist/core/mcp/bridge/tool-bridge.js +5 -14
  60. package/dist/core/mcp/client/connection-manager.js +3 -2
  61. package/dist/core/mcp/host/sampling-provider.js +1 -1
  62. package/dist/core/mcp/schema/json-schema-to-zod.js +2 -1
  63. package/dist/core/memory/relevant-retrieval.js +6 -4
  64. package/dist/core/observability/authorization-decisions.js +13 -12
  65. package/dist/core/observability/error-mapping.js +2 -1
  66. package/dist/core/observability/token-usage.js +5 -4
  67. package/dist/core/plugin/loader.js +5 -4
  68. package/dist/core/prompts/registry.js +11 -29
  69. package/dist/core/protocols/a2a/sdk/server.js +2 -3
  70. package/dist/core/protocols/acp/formal-agent.js +10 -4
  71. package/dist/core/protocols/acp/stdio-server.js +6 -6
  72. package/dist/core/runtime/agent-server-runtime.js +3 -2
  73. package/dist/core/runtime/initialize.js +70 -6
  74. package/dist/core/session/compaction/index.js +4 -3
  75. package/dist/core/session/manager.js +24 -37
  76. package/dist/core/session/token-tracker.js +18 -7
  77. package/dist/core/skills/parser.js +3 -2
  78. package/dist/core/skills/runtime/MicroTaskRunner.js +1 -1
  79. package/dist/core/skills/runtime/SkillRunner.js +5 -2
  80. package/dist/core/slash/steps/slash-execute.js +7 -5
  81. package/dist/core/slash/strategy.js +1 -1
  82. package/dist/core/strata/layers/worktree.js +7 -9
  83. package/dist/core/strata/runtime/synchronizer.js +10 -9
  84. package/dist/core/streaming/canonical/parts-from-llm-stream-chunk.js +1 -11
  85. package/dist/core/structured-output/json-schema-validator.js +1 -13
  86. package/dist/core/sub-agent/context-snapshot.js +12 -6
  87. package/dist/core/sub-agent/controller.js +70 -1
  88. package/dist/core/sub-agent/core/loop.js +25 -3
  89. package/dist/core/sub-agent/core/manager.js +319 -116
  90. package/dist/core/sub-agent/registry-defaults.js +12 -0
  91. package/dist/core/sub-agent/registry.js +8 -0
  92. package/dist/core/sub-agent/team.js +98 -0
  93. package/dist/core/sub-agent/tools/task-await.js +109 -0
  94. package/dist/core/sub-agent/tools/task-spawn.js +49 -7
  95. package/dist/core/sub-agent/tools/team.js +92 -0
  96. package/dist/core/sub-agent/types.js +11 -2
  97. package/dist/core/tools/budget.js +4 -11
  98. package/dist/core/tools/builtin/code-search/executor.js +46 -43
  99. package/dist/core/tools/builtin/fs.js +14 -6
  100. package/dist/core/tools/builtin/index.js +41 -107
  101. package/dist/core/tools/builtin/interaction.js +13 -15
  102. package/dist/core/tools/builtin/proposal.js +11 -2
  103. package/dist/core/tools/capability/executor.js +5 -5
  104. package/dist/core/tools/headless-payload.js +1 -3
  105. package/dist/core/tools/mapper.js +8 -42
  106. package/dist/core/tools/parallel/persistence.js +17 -5
  107. package/dist/core/tools/parallel/scheduler.js +23 -21
  108. package/dist/core/tools/permissions/permission-rules.js +66 -114
  109. package/dist/core/tools/plugins/loader.js +4 -3
  110. package/dist/core/tools/router.js +24 -53
  111. package/dist/core/tools/session.js +54 -97
  112. package/dist/core/tools/streaming/ToolCallAccumulator.js +1 -3
  113. package/dist/core/tools/tool-visibility.js +2 -1
  114. package/dist/core/tools/types.js +10 -0
  115. package/dist/core/utils/error.js +79 -0
  116. package/dist/core/utils/serialize.js +63 -0
  117. package/dist/core/utils/zod.js +29 -0
  118. package/dist/core/workspace/capabilities.js +3 -2
  119. package/dist/integrations/langfuse/litellm-langfuse-outcome-reporter.js +9 -8
  120. package/dist/locales/en.js +2 -1
  121. package/package.json +1 -1
@@ -1,925 +1,14 @@
1
- import { LLM_OUTPUT_KINDS } from '../types/index.js';
2
- import { ConfigError } from './errors.js';
3
- import { normalizePermissionMode, normalizeUiLogMode, normalizeUiLogView } from './normalize.js';
4
- import { MARKDOWN_RENDER_MODES, MARKDOWN_THEMES, } from './types.js';
5
- function isRecord(value) {
6
- return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
7
- }
8
- function isString(value) {
9
- return typeof value === 'string';
10
- }
11
- function isNumber(value) {
12
- return typeof value === 'number' && Number.isFinite(value);
13
- }
14
- function isBoolean(value) {
15
- return typeof value === 'boolean';
16
- }
17
- function isNull(value) {
18
- return value === null;
19
- }
20
- function isValidLlmOutputKind(value) {
21
- return typeof value === 'string' && LLM_OUTPUT_KINDS.includes(value);
22
- }
23
- function isValidMarkdownTheme(value) {
24
- return typeof value === 'string' && MARKDOWN_THEMES.includes(value);
25
- }
26
- function isValidMarkdownRenderMode(value) {
27
- return typeof value === 'string' && MARKDOWN_RENDER_MODES.includes(value);
28
- }
29
- function validateLlmCapabilities(input, context) {
30
- if (input === undefined)
31
- return undefined;
32
- if (!isRecord(input)) {
33
- throw new ConfigError('CONFIG_INVALID_LLM_CAPABILITIES', {
34
- ...context,
35
- expected: 'object',
36
- });
37
- }
38
- const allowedKeys = new Set(['toolCalling', 'responseFormatJsonObject', 'streaming']);
39
- for (const key of Object.keys(input)) {
40
- if (!allowedKeys.has(key)) {
41
- throw new ConfigError('CONFIG_INVALID_LLM_CAPABILITY', {
42
- ...context,
43
- capability: key,
44
- expected: 'toolCalling|responseFormatJsonObject|streaming',
45
- });
46
- }
47
- }
48
- const cfg = {};
49
- for (const key of ['toolCalling', 'responseFormatJsonObject', 'streaming']) {
50
- if (input[key] !== undefined && !isBoolean(input[key])) {
51
- throw new ConfigError('CONFIG_INVALID_LLM_CAPABILITY', {
52
- ...context,
53
- capability: key,
54
- expected: 'boolean',
55
- });
56
- }
57
- if (input[key] !== undefined) {
58
- cfg[key] = input[key];
59
- }
60
- }
61
- return cfg;
62
- }
63
- function normalizeLlmModelParams(input, context) {
64
- if (!isRecord(input))
65
- return undefined;
66
- for (const key of [
67
- 'capabilities',
68
- 'toolCalling',
69
- 'responseFormatJsonObject',
70
- 'streaming',
71
- ]) {
72
- if (input[key] !== undefined) {
73
- throw new ConfigError('CONFIG_INVALID_LLM_CAPABILITY_LOCATION', {
74
- ...context,
75
- capability: key,
76
- expected: 'model.capabilities',
77
- });
78
- }
79
- }
80
- return input;
81
- }
1
+ import { configFileV1Schema, zodIssueToConfigError } from './schema.js';
2
+ /**
3
+ * Validate and normalize a raw config file object against the ConfigFileV1 schema.
4
+ *
5
+ * Uses Zod for declarative validation with full error code mapping.
6
+ * First-error-wins semantics: only the first validation issue is reported.
7
+ */
82
8
  export function validateConfigFileV1(input) {
83
- if (!isRecord(input)) {
84
- throw new ConfigError('CONFIG_INVALID_ROOT', { expected: 'object' });
85
- }
86
- const version = input.version;
87
- if (version !== undefined && version !== 1) {
88
- throw new ConfigError('CONFIG_UNSUPPORTED', { version: String(version) });
89
- }
90
- const cfg = { version: 1 };
91
- if (input.mode !== undefined) {
92
- if (!isString(input.mode)) {
93
- throw new ConfigError('CONFIG_INVALID_MODE', { expected: 'interactive|yolo' });
94
- }
95
- const normalized = normalizePermissionMode(input.mode);
96
- if (!normalized) {
97
- throw new ConfigError('CONFIG_INVALID_MODE', { mode: String(input.mode) });
98
- }
99
- cfg.mode = normalized;
100
- }
101
- if (input.observability !== undefined) {
102
- if (!isRecord(input.observability)) {
103
- throw new ConfigError('CONFIG_INVALID_OBSERVABILITY', { expected: 'object' });
104
- }
105
- const obs = input.observability;
106
- cfg.observability = {};
107
- if (obs.langfuse !== undefined) {
108
- if (!isRecord(obs.langfuse)) {
109
- throw new ConfigError('CONFIG_INVALID_OBSERVABILITY_LANGFUSE', { expected: 'object' });
110
- }
111
- const lf = obs.langfuse;
112
- const out = {};
113
- if (lf.enabled !== undefined && !isBoolean(lf.enabled)) {
114
- throw new ConfigError('CONFIG_INVALID_LANGFUSE_ENABLED', { expected: 'boolean' });
115
- }
116
- if (lf.outcome !== undefined && !isBoolean(lf.outcome)) {
117
- throw new ConfigError('CONFIG_INVALID_LANGFUSE_OUTCOME', { expected: 'boolean' });
118
- }
119
- if (lf.endpoint !== undefined && !isString(lf.endpoint)) {
120
- throw new ConfigError('CONFIG_INVALID_LANGFUSE_ENDPOINT', { expected: 'string' });
121
- }
122
- if (lf.apiKey !== undefined && !(isString(lf.apiKey) || isNull(lf.apiKey))) {
123
- throw new ConfigError('CONFIG_INVALID_LANGFUSE_API_KEY', {
124
- expected: 'string|null',
125
- });
126
- }
127
- if (lf.sessionId !== undefined && !isString(lf.sessionId)) {
128
- throw new ConfigError('CONFIG_INVALID_LANGFUSE_SESSION_ID', { expected: 'string' });
129
- }
130
- if (lf.userId !== undefined && !isString(lf.userId)) {
131
- throw new ConfigError('CONFIG_INVALID_LANGFUSE_USER_ID', { expected: 'string' });
132
- }
133
- out.enabled = lf.enabled;
134
- out.outcome = lf.outcome;
135
- out.endpoint = lf.endpoint;
136
- out.apiKey = lf.apiKey;
137
- out.sessionId = lf.sessionId;
138
- out.userId = lf.userId;
139
- cfg.observability.langfuse = out;
140
- }
141
- if (obs.audit !== undefined) {
142
- const auditRaw = obs.audit;
143
- if (!isRecord(auditRaw)) {
144
- throw new ConfigError('CONFIG_INVALID_OBSERVABILITY_AUDIT', { expected: 'object' });
145
- }
146
- const scopeRaw = auditRaw.scope;
147
- const bufferRaw = auditRaw.buffer;
148
- const auditOut = {};
149
- if (scopeRaw !== undefined) {
150
- if (!isString(scopeRaw) || (scopeRaw !== 'repo' && scopeRaw !== 'user')) {
151
- throw new ConfigError('CONFIG_INVALID_OBSERVABILITY_AUDIT_SCOPE', {
152
- expected: 'repo|user',
153
- });
154
- }
155
- auditOut.scope = scopeRaw;
156
- }
157
- if (bufferRaw !== undefined) {
158
- if (!isRecord(bufferRaw)) {
159
- throw new ConfigError('CONFIG_INVALID_OBSERVABILITY_AUDIT_BUFFER', {
160
- expected: 'object',
161
- });
162
- }
163
- if (bufferRaw.maxEvents !== undefined && !isNumber(bufferRaw.maxEvents)) {
164
- throw new ConfigError('CONFIG_INVALID_OBSERVABILITY_AUDIT_MAX_EVENTS', {
165
- expected: 'number',
166
- });
167
- }
168
- if (bufferRaw.maxBytes !== undefined && !isNumber(bufferRaw.maxBytes)) {
169
- throw new ConfigError('CONFIG_INVALID_OBSERVABILITY_AUDIT_MAX_BYTES', {
170
- expected: 'number',
171
- });
172
- }
173
- if (bufferRaw.droppedWarn !== undefined && !isNumber(bufferRaw.droppedWarn)) {
174
- throw new ConfigError('CONFIG_INVALID_OBSERVABILITY_AUDIT_DROPPED_WARN', {
175
- expected: 'number',
176
- });
177
- }
178
- auditOut.buffer = {
179
- maxEvents: bufferRaw.maxEvents,
180
- maxBytes: bufferRaw.maxBytes,
181
- droppedWarn: bufferRaw.droppedWarn,
182
- };
183
- }
184
- if (auditOut.scope || auditOut.buffer) {
185
- cfg.observability.audit = auditOut;
186
- }
187
- }
188
- }
189
- if (input.cli !== undefined) {
190
- if (!isRecord(input.cli)) {
191
- throw new ConfigError('CONFIG_INVALID_CLI', { expected: 'object' });
192
- }
193
- if (input.cli.defaults !== undefined) {
194
- if (!isRecord(input.cli.defaults)) {
195
- throw new ConfigError('CONFIG_INVALID_CLI_DEFAULTS', { expected: 'object' });
196
- }
197
- cfg.cli = { defaults: {} };
198
- const d = input.cli.defaults;
199
- if (d.verbosity !== undefined && !isString(d.verbosity)) {
200
- throw new ConfigError('CONFIG_INVALID_VERBOSITY', { expected: 'string' });
201
- }
202
- if (d.strategy !== undefined && !isString(d.strategy)) {
203
- throw new ConfigError('CONFIG_INVALID_STRATEGY', { expected: 'string' });
204
- }
205
- if (d.dryRun !== undefined && !isBoolean(d.dryRun)) {
206
- throw new ConfigError('CONFIG_INVALID_DRY_RUN', { expected: 'boolean' });
207
- }
208
- cfg.cli.defaults = {
209
- verbosity: d.verbosity,
210
- strategy: d.strategy,
211
- dryRun: d.dryRun,
212
- };
213
- }
214
- }
215
- if (input.server !== undefined) {
216
- const serverRaw = input.server;
217
- if (!isRecord(serverRaw)) {
218
- throw new ConfigError('CONFIG_INVALID_SERVER', { expected: 'object' });
219
- }
220
- for (const key of Object.keys(serverRaw)) {
221
- if (key !== 'a2a' && key !== 'acp') {
222
- throw new ConfigError('CONFIG_INVALID_SERVER_UNKNOWN_KEY', { key });
223
- }
224
- }
225
- const server = {};
226
- if (serverRaw.a2a !== undefined) {
227
- if (!isRecord(serverRaw.a2a)) {
228
- throw new ConfigError('CONFIG_INVALID_SERVER_A2A', { expected: 'object' });
229
- }
230
- const a2aRaw = serverRaw.a2a;
231
- if (a2aRaw.host !== undefined && !isString(a2aRaw.host)) {
232
- throw new ConfigError('CONFIG_INVALID_SERVER_A2A_HOST', { expected: 'string' });
233
- }
234
- if (a2aRaw.port !== undefined && !isNumber(a2aRaw.port)) {
235
- throw new ConfigError('CONFIG_INVALID_SERVER_A2A_PORT', { expected: 'number' });
236
- }
237
- if (a2aRaw.tokens !== undefined) {
238
- if (!Array.isArray(a2aRaw.tokens) || !a2aRaw.tokens.every(isString)) {
239
- throw new ConfigError('CONFIG_INVALID_SERVER_A2A_TOKENS', { expected: 'string[]' });
240
- }
241
- }
242
- server.a2a = {
243
- host: a2aRaw.host,
244
- port: a2aRaw.port,
245
- tokens: a2aRaw.tokens,
246
- };
247
- }
248
- if (serverRaw.acp !== undefined) {
249
- if (!isRecord(serverRaw.acp)) {
250
- throw new ConfigError('CONFIG_INVALID_SERVER_ACP', { expected: 'object' });
251
- }
252
- const acpRaw = serverRaw.acp;
253
- const acp = {};
254
- if (acpRaw.sessionStore !== undefined) {
255
- if (!isRecord(acpRaw.sessionStore)) {
256
- throw new ConfigError('CONFIG_INVALID_SERVER_ACP_SESSION_STORE', { expected: 'object' });
257
- }
258
- const sessionStoreRaw = acpRaw.sessionStore;
259
- const validateOptionalNumber = (value, code) => {
260
- if (value !== undefined && !isNumber(value)) {
261
- throw new ConfigError(code, { expected: 'number' });
262
- }
263
- };
264
- validateOptionalNumber(sessionStoreRaw.maxEntries, 'CONFIG_INVALID_SERVER_ACP_SESSION_STORE_MAX_ENTRIES');
265
- validateOptionalNumber(sessionStoreRaw.maxAgeMs, 'CONFIG_INVALID_SERVER_ACP_SESSION_STORE_MAX_AGE_MS');
266
- validateOptionalNumber(sessionStoreRaw.historyMaxEntries, 'CONFIG_INVALID_SERVER_ACP_SESSION_STORE_HISTORY_MAX_ENTRIES');
267
- validateOptionalNumber(sessionStoreRaw.lockStaleMs, 'CONFIG_INVALID_SERVER_ACP_SESSION_STORE_LOCK_STALE_MS');
268
- validateOptionalNumber(sessionStoreRaw.lockHeartbeatMs, 'CONFIG_INVALID_SERVER_ACP_SESSION_STORE_LOCK_HEARTBEAT_MS');
269
- acp.sessionStore = {
270
- maxEntries: sessionStoreRaw.maxEntries,
271
- maxAgeMs: sessionStoreRaw.maxAgeMs,
272
- historyMaxEntries: sessionStoreRaw.historyMaxEntries,
273
- lockStaleMs: sessionStoreRaw.lockStaleMs,
274
- lockHeartbeatMs: sessionStoreRaw.lockHeartbeatMs,
275
- };
276
- }
277
- if (acpRaw.checkpointManifest !== undefined) {
278
- if (!isRecord(acpRaw.checkpointManifest)) {
279
- throw new ConfigError('CONFIG_INVALID_SERVER_ACP_CHECKPOINT_MANIFEST', {
280
- expected: 'object',
281
- });
282
- }
283
- const manifestRaw = acpRaw.checkpointManifest;
284
- const validateOptionalNumber = (value, code) => {
285
- if (value !== undefined && !isNumber(value)) {
286
- throw new ConfigError(code, { expected: 'number' });
287
- }
288
- };
289
- validateOptionalNumber(manifestRaw.lockStaleMs, 'CONFIG_INVALID_SERVER_ACP_CHECKPOINT_MANIFEST_LOCK_STALE_MS');
290
- validateOptionalNumber(manifestRaw.lockHeartbeatMs, 'CONFIG_INVALID_SERVER_ACP_CHECKPOINT_MANIFEST_LOCK_HEARTBEAT_MS');
291
- acp.checkpointManifest = {
292
- lockStaleMs: manifestRaw.lockStaleMs,
293
- lockHeartbeatMs: manifestRaw.lockHeartbeatMs,
294
- };
295
- }
296
- server.acp = acp;
297
- }
298
- cfg.server = server;
299
- }
300
- if (input.ui !== undefined) {
301
- const uiRaw = input.ui;
302
- if (!isRecord(uiRaw)) {
303
- throw new ConfigError('CONFIG_INVALID_UI', { expected: 'object' });
304
- }
305
- const ui = {};
306
- const logRaw = uiRaw.log;
307
- if (logRaw !== undefined) {
308
- if (!isRecord(logRaw)) {
309
- throw new ConfigError('CONFIG_INVALID_UI_LOG', { expected: 'object' });
310
- }
311
- const viewRaw = logRaw.view;
312
- const modeRaw = logRaw.mode;
313
- if (viewRaw !== undefined) {
314
- if (!isString(viewRaw)) {
315
- throw new ConfigError('CONFIG_INVALID_UI_LOG_VIEW', { expected: 'string' });
316
- }
317
- const normalized = normalizeUiLogView(viewRaw);
318
- if (!normalized) {
319
- throw new ConfigError('CONFIG_INVALID_UI_LOG_VIEW', { view: String(viewRaw) });
320
- }
321
- ui.log = { ...(ui.log ?? {}), view: normalized };
322
- }
323
- else {
324
- ui.log = {};
325
- }
326
- if (modeRaw !== undefined) {
327
- if (!isString(modeRaw)) {
328
- throw new ConfigError('CONFIG_INVALID_UI_LOG_MODE', { expected: 'string' });
329
- }
330
- const normalized = normalizeUiLogMode(modeRaw);
331
- if (!normalized) {
332
- throw new ConfigError('CONFIG_INVALID_UI_LOG_MODE', { mode: String(modeRaw) });
333
- }
334
- ui.log = { ...(ui.log ?? {}), mode: normalized };
335
- }
336
- }
337
- cfg.ui = ui;
338
- }
339
- if (input.context !== undefined) {
340
- const contextRaw = input.context;
341
- if (!isRecord(contextRaw)) {
342
- throw new ConfigError('CONFIG_INVALID_CONTEXT', { expected: 'object' });
343
- }
344
- cfg.context = {};
345
- if (contextRaw.useTokenBudget !== undefined && !isBoolean(contextRaw.useTokenBudget)) {
346
- throw new ConfigError('CONFIG_INVALID_USE_TOKEN_BUDGET', { expected: 'boolean' });
347
- }
348
- if (contextRaw.useTokenBudget !== undefined) {
349
- cfg.context.useTokenBudget = contextRaw.useTokenBudget;
350
- }
351
- if (contextRaw.cache !== undefined) {
352
- if (!isRecord(contextRaw.cache)) {
353
- throw new ConfigError('CONFIG_INVALID_CONTEXT_CACHE', { expected: 'object' });
354
- }
355
- const cacheRaw = contextRaw.cache;
356
- const cache = {};
357
- if (cacheRaw.mode !== undefined &&
358
- cacheRaw.mode !== 'memory' &&
359
- cacheRaw.mode !== 'persistent') {
360
- throw new ConfigError('CONFIG_INVALID_CONTEXT_CACHE_MODE', { mode: String(cacheRaw.mode) });
361
- }
362
- if (cacheRaw.path !== undefined && !isString(cacheRaw.path)) {
363
- throw new ConfigError('CONFIG_INVALID_CONTEXT_CACHE_PATH', { expected: 'string' });
364
- }
365
- if (cacheRaw.allowedRoots !== undefined &&
366
- (!Array.isArray(cacheRaw.allowedRoots) || cacheRaw.allowedRoots.some((v) => !isString(v)))) {
367
- throw new ConfigError('CONFIG_INVALID_CONTEXT_CACHE_ALLOWED_ROOTS', {
368
- expected: 'string[]',
369
- });
370
- }
371
- if (cacheRaw.maxEntries !== undefined && !isNumber(cacheRaw.maxEntries)) {
372
- throw new ConfigError('CONFIG_INVALID_CONTEXT_CACHE_MAX_ENTRIES', { expected: 'number' });
373
- }
374
- if (cacheRaw.ttlMs !== undefined && !isNumber(cacheRaw.ttlMs)) {
375
- throw new ConfigError('CONFIG_INVALID_CONTEXT_CACHE_TTL', { expected: 'number' });
376
- }
377
- if (cacheRaw.maxPayloadBytes !== undefined && !isNumber(cacheRaw.maxPayloadBytes)) {
378
- throw new ConfigError('CONFIG_INVALID_CONTEXT_CACHE_MAX_PAYLOAD', { expected: 'number' });
379
- }
380
- if (cacheRaw.mode === 'persistent') {
381
- if (!isString(cacheRaw.path) || cacheRaw.path.length === 0) {
382
- throw new ConfigError('CONFIG_INVALID_CONTEXT_CACHE_PATH', {
383
- expected: 'non-empty string',
384
- });
385
- }
386
- if (!Array.isArray(cacheRaw.allowedRoots) ||
387
- cacheRaw.allowedRoots.length === 0 ||
388
- cacheRaw.allowedRoots.some((v) => !isString(v) || v.length === 0)) {
389
- throw new ConfigError('CONFIG_INVALID_CONTEXT_CACHE_ALLOWED_ROOTS', {
390
- expected: 'non-empty string[]',
391
- });
392
- }
393
- }
394
- cache.mode = cacheRaw.mode;
395
- cache.path = cacheRaw.path;
396
- cache.allowedRoots = cacheRaw.allowedRoots;
397
- cache.maxEntries = cacheRaw.maxEntries;
398
- cache.ttlMs = cacheRaw.ttlMs;
399
- cache.maxPayloadBytes = cacheRaw.maxPayloadBytes;
400
- cfg.context.cache = cache;
401
- }
402
- if (contextRaw.churn !== undefined) {
403
- if (!isRecord(contextRaw.churn)) {
404
- throw new ConfigError('CONFIG_INVALID_CHURN', { expected: 'object' });
405
- }
406
- const churnRaw = contextRaw.churn;
407
- const churn = {};
408
- if (churnRaw.weight !== undefined) {
409
- if (!isRecord(churnRaw.weight)) {
410
- throw new ConfigError('CONFIG_INVALID_CHURN_WEIGHT', { expected: 'object' });
411
- }
412
- const weightRaw = churnRaw.weight;
413
- if (weightRaw.primary !== undefined && !isNumber(weightRaw.primary)) {
414
- throw new ConfigError('CONFIG_INVALID_CHURN_WEIGHT_PRIMARY', { expected: 'number' });
415
- }
416
- if (weightRaw.rerank !== undefined && !isNumber(weightRaw.rerank)) {
417
- throw new ConfigError('CONFIG_INVALID_CHURN_WEIGHT_RERANK', { expected: 'number' });
418
- }
419
- if (weightRaw.tiebreak !== undefined && !isNumber(weightRaw.tiebreak)) {
420
- throw new ConfigError('CONFIG_INVALID_CHURN_WEIGHT_TIEBREAK', { expected: 'number' });
421
- }
422
- churn.weight = {
423
- primary: weightRaw.primary,
424
- rerank: weightRaw.rerank,
425
- tiebreak: weightRaw.tiebreak,
426
- };
427
- }
428
- cfg.context.churn = churn;
429
- }
430
- if (contextRaw.dynamicBudget !== undefined) {
431
- if (!isRecord(contextRaw.dynamicBudget)) {
432
- throw new ConfigError('CONFIG_INVALID_DYNAMIC_BUDGET', { expected: 'object' });
433
- }
434
- const dynamicBudgetRaw = contextRaw.dynamicBudget;
435
- const dynamicBudget = {};
436
- if (dynamicBudgetRaw.enabled !== undefined && !isBoolean(dynamicBudgetRaw.enabled)) {
437
- throw new ConfigError('CONFIG_INVALID_DYNAMIC_BUDGET_ENABLED', { expected: 'boolean' });
438
- }
439
- if (dynamicBudgetRaw.minBudget !== undefined && !isNumber(dynamicBudgetRaw.minBudget)) {
440
- throw new ConfigError('CONFIG_INVALID_DYNAMIC_BUDGET_MIN', { expected: 'number' });
441
- }
442
- if (dynamicBudgetRaw.maxBudget !== undefined && !isNumber(dynamicBudgetRaw.maxBudget)) {
443
- throw new ConfigError('CONFIG_INVALID_DYNAMIC_BUDGET_MAX', { expected: 'number' });
444
- }
445
- if (dynamicBudgetRaw.adjustmentStep !== undefined &&
446
- !isNumber(dynamicBudgetRaw.adjustmentStep)) {
447
- throw new ConfigError('CONFIG_INVALID_DYNAMIC_BUDGET_STEP', { expected: 'number' });
448
- }
449
- dynamicBudget.enabled = dynamicBudgetRaw.enabled;
450
- dynamicBudget.minBudget = dynamicBudgetRaw.minBudget;
451
- dynamicBudget.maxBudget = dynamicBudgetRaw.maxBudget;
452
- dynamicBudget.adjustmentStep = dynamicBudgetRaw.adjustmentStep;
453
- if (dynamicBudgetRaw.alerts !== undefined) {
454
- if (!isRecord(dynamicBudgetRaw.alerts)) {
455
- throw new ConfigError('CONFIG_INVALID_DYNAMIC_BUDGET_ALERTS', { expected: 'object' });
456
- }
457
- const alertsRaw = dynamicBudgetRaw.alerts;
458
- if (alertsRaw.truncationRateWarn !== undefined && !isNumber(alertsRaw.truncationRateWarn)) {
459
- throw new ConfigError('CONFIG_INVALID_DYNAMIC_BUDGET_ALERT_TRUNCATION', {
460
- expected: 'number',
461
- });
462
- }
463
- if (alertsRaw.criticalDropRateWarn !== undefined &&
464
- !isNumber(alertsRaw.criticalDropRateWarn)) {
465
- throw new ConfigError('CONFIG_INVALID_DYNAMIC_BUDGET_ALERT_CRITICAL_DROP', {
466
- expected: 'number',
467
- });
468
- }
469
- dynamicBudget.alerts = {
470
- truncationRateWarn: alertsRaw.truncationRateWarn,
471
- criticalDropRateWarn: alertsRaw.criticalDropRateWarn,
472
- };
473
- }
474
- cfg.context.dynamicBudget = dynamicBudget;
475
- }
476
- }
477
- if (input.verify !== undefined) {
478
- if (!isRecord(input.verify)) {
479
- throw new ConfigError('CONFIG_INVALID_VERIFY', { expected: 'object' });
480
- }
481
- if (input.verify.command !== undefined && !isString(input.verify.command)) {
482
- throw new ConfigError('CONFIG_INVALID_VERIFY_COMMAND', { expected: 'string' });
483
- }
484
- if (input.verify.timeoutMs !== undefined && !isNumber(input.verify.timeoutMs)) {
485
- throw new ConfigError('CONFIG_INVALID_VERIFY_TIMEOUT', { expected: 'number' });
486
- }
487
- cfg.verify = { command: input.verify.command, timeoutMs: input.verify.timeoutMs };
488
- }
489
- if (input.astValidation !== undefined) {
490
- const astValidationRaw = input.astValidation;
491
- if (!isRecord(astValidationRaw)) {
492
- throw new ConfigError('CONFIG_INVALID_AST_VALIDATION', { expected: 'object' });
493
- }
494
- const strictnessRaw = astValidationRaw.strictness;
495
- if (strictnessRaw !== undefined && strictnessRaw !== 'lenient' && strictnessRaw !== 'strict') {
496
- throw new ConfigError('CONFIG_INVALID_AST_VALIDATION_STRICTNESS', {
497
- strictness: String(strictnessRaw),
498
- });
499
- }
500
- cfg.astValidation = {
501
- strictness: strictnessRaw,
502
- };
503
- }
504
- if (input.llm !== undefined) {
505
- if (!isRecord(input.llm)) {
506
- throw new ConfigError('CONFIG_INVALID_LLM', { expected: 'object' });
507
- }
508
- cfg.llm = {};
509
- if (input.llm.activeModel !== undefined && !isString(input.llm.activeModel)) {
510
- throw new ConfigError('CONFIG_INVALID_LLM_ACTIVE_MODEL', { expected: 'string' });
511
- }
512
- if (input.llm.activeModel !== undefined)
513
- cfg.llm.activeModel = input.llm.activeModel;
514
- const llmAny = input.llm;
515
- if (llmAny.simpleModel !== undefined && !isString(llmAny.simpleModel)) {
516
- throw new ConfigError('CONFIG_INVALID_LLM_SIMPLE_MODEL', { expected: 'string' });
517
- }
518
- if (llmAny.simpleModel !== undefined)
519
- cfg.llm.simpleModel = llmAny.simpleModel;
520
- if (llmAny.mediumModel !== undefined && !isString(llmAny.mediumModel)) {
521
- throw new ConfigError('CONFIG_INVALID_LLM_MEDIUM_MODEL', { expected: 'string' });
522
- }
523
- if (llmAny.mediumModel !== undefined)
524
- cfg.llm.mediumModel = llmAny.mediumModel;
525
- if (llmAny.complexModel !== undefined && !isString(llmAny.complexModel)) {
526
- throw new ConfigError('CONFIG_INVALID_LLM_COMPLEX_MODEL', { expected: 'string' });
527
- }
528
- if (llmAny.complexModel !== undefined)
529
- cfg.llm.complexModel = llmAny.complexModel;
530
- if (llmAny.reasoningModel !== undefined && !isString(llmAny.reasoningModel)) {
531
- throw new ConfigError('CONFIG_INVALID_LLM_REASONING_MODEL', { expected: 'string' });
532
- }
533
- if (llmAny.reasoningModel !== undefined)
534
- cfg.llm.reasoningModel = llmAny.reasoningModel;
535
- if (input.llm.providers !== undefined) {
536
- if (!isRecord(input.llm.providers)) {
537
- throw new ConfigError('CONFIG_INVALID_LLM_PROVIDERS', { expected: 'object' });
538
- }
539
- cfg.llm.providers = {};
540
- for (const [id, rawProvider] of Object.entries(input.llm.providers)) {
541
- if (!isRecord(rawProvider)) {
542
- throw new ConfigError('CONFIG_INVALID_PROVIDER', { provider: id, expected: 'object' });
543
- }
544
- const p = { type: String(rawProvider.type) };
545
- if (!isString(rawProvider.type)) {
546
- throw new ConfigError('CONFIG_INVALID_TYPE', { provider: id, expected: 'string' });
547
- }
548
- if (rawProvider.client !== undefined) {
549
- if (!isRecord(rawProvider.client)) {
550
- throw new ConfigError('CONFIG_INVALID_CLIENT', { provider: id, expected: 'object' });
551
- }
552
- if (rawProvider.client.package !== undefined && !isString(rawProvider.client.package)) {
553
- throw new ConfigError('CONFIG_INVALID_CLIENT_PACKAGE', {
554
- provider: id,
555
- expected: 'string',
556
- });
557
- }
558
- p.client = { package: rawProvider.client.package };
559
- }
560
- if (rawProvider.api !== undefined) {
561
- if (!isRecord(rawProvider.api)) {
562
- throw new ConfigError('CONFIG_INVALID_API', { provider: id, expected: 'object' });
563
- }
564
- const api = rawProvider.api;
565
- if (api.baseUrl !== undefined && !isString(api.baseUrl)) {
566
- throw new ConfigError('CONFIG_INVALID_BASE_URL', { provider: id, expected: 'string' });
567
- }
568
- if (api.apiKey !== undefined && api.apiKey !== null && !isString(api.apiKey)) {
569
- throw new ConfigError('CONFIG_INVALID_API_KEY', {
570
- provider: id,
571
- expected: 'string_or_null',
572
- });
573
- }
574
- if (api.timeoutMs !== undefined && !isNumber(api.timeoutMs)) {
575
- throw new ConfigError('CONFIG_INVALID_TIMEOUT', { provider: id, expected: 'number' });
576
- }
577
- if (api.headers !== undefined) {
578
- if (!isRecord(api.headers)) {
579
- throw new ConfigError('CONFIG_INVALID_HEADERS', { provider: id, expected: 'object' });
580
- }
581
- for (const [k, v] of Object.entries(api.headers)) {
582
- if (!isString(v)) {
583
- throw new ConfigError('CONFIG_INVALID_HEADER_VALUE', {
584
- provider: id,
585
- header: k,
586
- expected: 'string',
587
- });
588
- }
589
- }
590
- }
591
- p.api = {
592
- baseUrl: api.baseUrl,
593
- apiKey: api.apiKey,
594
- timeoutMs: api.timeoutMs,
595
- headers: api.headers,
596
- };
597
- }
598
- if (rawProvider.models !== undefined) {
599
- throw new ConfigError('CONFIG_LLM_PROVIDER_MODELS_NOT_SUPPORTED', {
600
- provider: id,
601
- hint: 'use llm.models with provider references',
602
- });
603
- }
604
- const providerCapabilities = validateLlmCapabilities(rawProvider.capabilities, {
605
- provider: id,
606
- });
607
- if (providerCapabilities) {
608
- p.capabilities = providerCapabilities;
609
- }
610
- cfg.llm.providers[id] = p;
611
- }
612
- }
613
- if (input.llm.models !== undefined) {
614
- if (!isRecord(input.llm.models)) {
615
- throw new ConfigError('CONFIG_INVALID_LLM_MODELS', { expected: 'object' });
616
- }
617
- cfg.llm.models = {};
618
- for (const [slot, rawModel] of Object.entries(input.llm.models)) {
619
- if (!isRecord(rawModel)) {
620
- throw new ConfigError('CONFIG_INVALID_LLM_MODEL_PROFILE', {
621
- model: slot,
622
- expected: 'object',
623
- });
624
- }
625
- const provider = rawModel.provider;
626
- if (!isString(provider) &&
627
- !(Array.isArray(provider) && provider.length > 0 && provider.every(isString))) {
628
- throw new ConfigError('CONFIG_INVALID_LLM_MODEL_PROVIDER', {
629
- model: slot,
630
- expected: 'string_or_non_empty_string_array',
631
- });
632
- }
633
- if (!isString(rawModel.id) || !rawModel.id.trim()) {
634
- throw new ConfigError('CONFIG_INVALID_LLM_MODEL_ID', {
635
- model: slot,
636
- expected: 'non_empty_string',
637
- });
638
- }
639
- const params = normalizeLlmModelParams(rawModel.params, {
640
- model: slot,
641
- location: 'params',
642
- });
643
- const capabilities = validateLlmCapabilities(rawModel.capabilities, {
644
- model: slot,
645
- });
646
- cfg.llm.models[slot] = {
647
- provider: provider,
648
- id: rawModel.id,
649
- params,
650
- capabilities,
651
- };
652
- }
653
- }
654
- if (input.llm.routing !== undefined) {
655
- if (!isRecord(input.llm.routing)) {
656
- throw new ConfigError('CONFIG_INVALID_ROUTING', { expected: 'object' });
657
- }
658
- const r = input.llm.routing;
659
- cfg.llm.routing = {};
660
- if (r.fallbackProviders !== undefined) {
661
- if (!Array.isArray(r.fallbackProviders) || !r.fallbackProviders.every(isString)) {
662
- throw new ConfigError('CONFIG_INVALID_FALLBACK_PROVIDERS', { expected: 'string_array' });
663
- }
664
- cfg.llm.routing.fallbackProviders = r.fallbackProviders;
665
- }
666
- if (r.taskToModel !== undefined) {
667
- if (!isRecord(r.taskToModel)) {
668
- throw new ConfigError('CONFIG_INVALID_TASK_TO_MODEL', { expected: 'object' });
669
- }
670
- for (const [k, v] of Object.entries(r.taskToModel)) {
671
- if (!isString(v)) {
672
- throw new ConfigError('CONFIG_INVALID_TASK_TO_MODEL_VALUE', {
673
- task: k,
674
- expected: 'string',
675
- });
676
- }
677
- }
678
- cfg.llm.routing.taskToModel = r.taskToModel;
679
- }
680
- if (r.phaseToModel !== undefined) {
681
- if (!isRecord(r.phaseToModel)) {
682
- throw new ConfigError('CONFIG_INVALID_PHASE_TO_MODEL', { expected: 'object' });
683
- }
684
- for (const [k, v] of Object.entries(r.phaseToModel)) {
685
- if (!isString(v)) {
686
- throw new ConfigError('CONFIG_INVALID_PHASE_TO_MODEL_VALUE', {
687
- phase: k,
688
- expected: 'string',
689
- });
690
- }
691
- }
692
- cfg.llm.routing.phaseToModel = r.phaseToModel;
693
- }
694
- }
695
- }
696
- if (input.security !== undefined) {
697
- const securityRaw = input.security;
698
- if (!isRecord(securityRaw)) {
699
- throw new ConfigError('CONFIG_INVALID_SECURITY', { expected: 'object' });
700
- }
701
- if (securityRaw.redaction !== undefined) {
702
- if (!isRecord(securityRaw.redaction)) {
703
- throw new ConfigError('CONFIG_INVALID_SECURITY_REDACTION', { expected: 'object' });
704
- }
705
- const redactionRaw = securityRaw.redaction;
706
- if (redactionRaw.enabled !== undefined && !isBoolean(redactionRaw.enabled)) {
707
- throw new ConfigError('CONFIG_INVALID_SECURITY_REDACTION_ENABLED', {
708
- expected: 'boolean',
709
- });
710
- }
711
- if (redactionRaw.mark !== undefined && !isString(redactionRaw.mark)) {
712
- throw new ConfigError('CONFIG_INVALID_SECURITY_REDACTION_MARK', { expected: 'string' });
713
- }
714
- if (redactionRaw.maxDepth !== undefined && !isNumber(redactionRaw.maxDepth)) {
715
- throw new ConfigError('CONFIG_INVALID_SECURITY_REDACTION_MAX_DEPTH', {
716
- expected: 'number',
717
- });
718
- }
719
- if (redactionRaw.keyAllowlist !== undefined &&
720
- (!Array.isArray(redactionRaw.keyAllowlist) ||
721
- redactionRaw.keyAllowlist.some((v) => !isString(v)))) {
722
- throw new ConfigError('CONFIG_INVALID_SECURITY_REDACTION_KEY_ALLOWLIST', {
723
- expected: 'string[]',
724
- });
725
- }
726
- if (redactionRaw.keyDenylist !== undefined &&
727
- (!Array.isArray(redactionRaw.keyDenylist) ||
728
- redactionRaw.keyDenylist.some((v) => !isString(v)))) {
729
- throw new ConfigError('CONFIG_INVALID_SECURITY_REDACTION_KEY_DENYLIST', {
730
- expected: 'string[]',
731
- });
732
- }
733
- if (redactionRaw.patterns !== undefined &&
734
- (!Array.isArray(redactionRaw.patterns) || redactionRaw.patterns.some((v) => !isString(v)))) {
735
- throw new ConfigError('CONFIG_INVALID_SECURITY_REDACTION_PATTERNS', {
736
- expected: 'string[]',
737
- });
738
- }
739
- if (redactionRaw.disableDefaults !== undefined && !isBoolean(redactionRaw.disableDefaults)) {
740
- throw new ConfigError('CONFIG_INVALID_SECURITY_REDACTION_DISABLE_DEFAULTS', {
741
- expected: 'boolean',
742
- });
743
- }
744
- cfg.security = {
745
- redaction: {
746
- enabled: redactionRaw.enabled,
747
- mark: redactionRaw.mark,
748
- maxDepth: redactionRaw.maxDepth,
749
- keyAllowlist: redactionRaw.keyAllowlist,
750
- keyDenylist: redactionRaw.keyDenylist,
751
- patterns: redactionRaw.patterns,
752
- disableDefaults: redactionRaw.disableDefaults,
753
- },
754
- };
755
- }
756
- }
757
- if (input.output !== undefined) {
758
- if (!isRecord(input.output)) {
759
- throw new ConfigError('CONFIG_INVALID_OUTPUT', { expected: 'object' });
760
- }
761
- const output = input.output;
762
- cfg.output = {};
763
- if (output.llm !== undefined) {
764
- if (!isRecord(output.llm)) {
765
- throw new ConfigError('CONFIG_INVALID_LLM_OUTPUT', { expected: 'object' });
766
- }
767
- const llmOutput = output.llm;
768
- if (llmOutput.kinds !== undefined) {
769
- if (!Array.isArray(llmOutput.kinds) || !llmOutput.kinds.every(isString)) {
770
- throw new ConfigError('CONFIG_INVALID_LLM_OUTPUT_KINDS', { expected: 'string_array' });
771
- }
772
- const invalidKind = llmOutput.kinds.find((kind) => !isValidLlmOutputKind(kind));
773
- if (invalidKind) {
774
- throw new ConfigError('CONFIG_INVALID_LLM_OUTPUT_KIND', { kind: String(invalidKind) });
775
- }
776
- }
777
- cfg.output.llm = {
778
- kinds: llmOutput.kinds,
779
- };
780
- }
781
- if (output.markdown !== undefined) {
782
- if (!isRecord(output.markdown)) {
783
- throw new ConfigError('CONFIG_INVALID_OUTPUT_MARKDOWN', { expected: 'object' });
784
- }
785
- const markdown = output.markdown;
786
- if (markdown.theme !== undefined) {
787
- if (!isString(markdown.theme)) {
788
- throw new ConfigError('CONFIG_INVALID_MARKDOWN_THEME', { expected: 'string' });
789
- }
790
- if (!isValidMarkdownTheme(markdown.theme)) {
791
- throw new ConfigError('CONFIG_INVALID_MARKDOWN_THEME', { theme: String(markdown.theme) });
792
- }
793
- }
794
- if (markdown.mode !== undefined) {
795
- if (!isString(markdown.mode)) {
796
- throw new ConfigError('CONFIG_INVALID_MARKDOWN_RENDER_MODE', { expected: 'string' });
797
- }
798
- if (!isValidMarkdownRenderMode(markdown.mode)) {
799
- throw new ConfigError('CONFIG_INVALID_MARKDOWN_RENDER_MODE', {
800
- mode: String(markdown.mode),
801
- });
802
- }
803
- }
804
- cfg.output.markdown = {
805
- theme: markdown.theme,
806
- mode: markdown.mode,
807
- };
808
- }
809
- }
810
- if (input.toolAuthorization !== undefined) {
811
- if (!isRecord(input.toolAuthorization)) {
812
- throw new ConfigError('CONFIG_INVALID_TOOL_AUTH', { expected: 'object' });
813
- }
814
- cfg.toolAuthorization = {};
815
- const t = input.toolAuthorization;
816
- if (t.sessionTtlMs !== undefined && !isNumber(t.sessionTtlMs)) {
817
- throw new ConfigError('CONFIG_INVALID_TOOL_AUTH_TTL', { expected: 'number' });
818
- }
819
- if (t.nonInteractive !== undefined) {
820
- if (!isRecord(t.nonInteractive)) {
821
- throw new ConfigError('CONFIG_INVALID_TOOL_AUTH_NON_INTERACTIVE', { expected: 'object' });
822
- }
823
- const ni = t.nonInteractive;
824
- if (ni.strategy !== undefined && !isString(ni.strategy)) {
825
- throw new ConfigError('CONFIG_INVALID_TOOL_AUTH_NON_INTERACTIVE_STRATEGY', {
826
- expected: 'string',
827
- });
828
- }
829
- const strategy = ni.strategy ?? undefined;
830
- if (strategy !== undefined &&
831
- strategy !== 'deny' &&
832
- strategy !== 'command' &&
833
- strategy !== 'mcp') {
834
- throw new ConfigError('CONFIG_INVALID_TOOL_AUTH_NON_INTERACTIVE_STRATEGY', {
835
- strategy: String(strategy),
836
- });
837
- }
838
- if (ni.command !== undefined) {
839
- if (!isRecord(ni.command)) {
840
- throw new ConfigError('CONFIG_INVALID_TOOL_AUTH_NON_INTERACTIVE_COMMAND', {
841
- expected: 'object',
842
- });
843
- }
844
- if (!isString(ni.command.cmd)) {
845
- throw new ConfigError('CONFIG_INVALID_TOOL_AUTH_NON_INTERACTIVE_COMMAND_CMD', {
846
- expected: 'string',
847
- });
848
- }
849
- if (ni.command.timeoutMs !== undefined && !isNumber(ni.command.timeoutMs)) {
850
- throw new ConfigError('CONFIG_INVALID_TOOL_AUTH_NON_INTERACTIVE_COMMAND_TIMEOUT', {
851
- expected: 'number',
852
- });
853
- }
854
- }
855
- if (ni.mcp !== undefined) {
856
- if (!isRecord(ni.mcp)) {
857
- throw new ConfigError('CONFIG_INVALID_TOOL_AUTH_NON_INTERACTIVE_MCP', {
858
- expected: 'object',
859
- });
860
- }
861
- if (!isString(ni.mcp.server)) {
862
- throw new ConfigError('CONFIG_INVALID_TOOL_AUTH_NON_INTERACTIVE_MCP_SERVER', {
863
- expected: 'string',
864
- });
865
- }
866
- if (!isString(ni.mcp.tool)) {
867
- throw new ConfigError('CONFIG_INVALID_TOOL_AUTH_NON_INTERACTIVE_MCP_TOOL', {
868
- expected: 'string',
869
- });
870
- }
871
- if (ni.mcp.timeoutMs !== undefined && !isNumber(ni.mcp.timeoutMs)) {
872
- throw new ConfigError('CONFIG_INVALID_TOOL_AUTH_NON_INTERACTIVE_MCP_TIMEOUT', {
873
- expected: 'number',
874
- });
875
- }
876
- }
877
- cfg.toolAuthorization.nonInteractive = {
878
- strategy: strategy,
879
- command: ni.command,
880
- mcp: ni.mcp,
881
- };
882
- }
883
- if (t.autoAllowRisk !== undefined) {
884
- if (!isRecord(t.autoAllowRisk)) {
885
- throw new ConfigError('CONFIG_INVALID_TOOL_AUTH_RISK', { expected: 'object' });
886
- }
887
- const r = t.autoAllowRisk;
888
- if (r.low !== undefined && !isBoolean(r.low)) {
889
- throw new ConfigError('CONFIG_INVALID_TOOL_AUTH_RISK_LOW', { expected: 'boolean' });
890
- }
891
- if (r.medium !== undefined && !isBoolean(r.medium)) {
892
- throw new ConfigError('CONFIG_INVALID_TOOL_AUTH_RISK_MEDIUM', { expected: 'boolean' });
893
- }
894
- if (r.high !== undefined && !isBoolean(r.high)) {
895
- throw new ConfigError('CONFIG_INVALID_TOOL_AUTH_RISK_HIGH', { expected: 'boolean' });
896
- }
897
- cfg.toolAuthorization.autoAllowRisk = {
898
- low: r.low,
899
- medium: r.medium,
900
- high: r.high,
901
- };
902
- }
903
- if (t.allowlist !== undefined) {
904
- if (!isRecord(t.allowlist)) {
905
- throw new ConfigError('CONFIG_INVALID_TOOL_AUTH_ALLOWLIST', { expected: 'object' });
906
- }
907
- const a = t.allowlist;
908
- if (a.repoFile !== undefined && !isString(a.repoFile)) {
909
- throw new ConfigError('CONFIG_INVALID_TOOL_AUTH_REPO_FILE', { expected: 'string' });
910
- }
911
- if (a.userFile !== undefined && !isString(a.userFile)) {
912
- throw new ConfigError('CONFIG_INVALID_TOOL_AUTH_USER_FILE', { expected: 'string' });
913
- }
914
- cfg.toolAuthorization.allowlist = {
915
- repoFile: a.repoFile,
916
- userFile: a.userFile,
917
- };
918
- }
919
- if (t.sessionTtlMs !== undefined) {
920
- cfg.toolAuthorization.sessionTtlMs = t.sessionTtlMs;
921
- }
922
- }
923
- return cfg;
9
+ const result = configFileV1Schema.safeParse(input);
10
+ if (result.success)
11
+ return result.data;
12
+ throw zodIssueToConfigError(result.error.issues[0]);
924
13
  }
925
14
  //# sourceMappingURL=validate.js.map