@covibes/zeroshot 5.2.1 → 5.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/CHANGELOG.md +174 -189
  2. package/README.md +226 -195
  3. package/cli/commands/providers.js +149 -0
  4. package/cli/index.js +3145 -2366
  5. package/cli/lib/first-run.js +40 -3
  6. package/cli/message-formatters-normal.js +28 -6
  7. package/cluster-templates/base-templates/debug-workflow.json +24 -78
  8. package/cluster-templates/base-templates/full-workflow.json +99 -316
  9. package/cluster-templates/base-templates/single-worker.json +23 -15
  10. package/cluster-templates/base-templates/worker-validator.json +105 -36
  11. package/cluster-templates/conductor-bootstrap.json +9 -7
  12. package/lib/docker-config.js +14 -1
  13. package/lib/git-remote-utils.js +165 -0
  14. package/lib/id-detector.js +10 -7
  15. package/lib/provider-defaults.js +62 -0
  16. package/lib/provider-detection.js +59 -0
  17. package/lib/provider-names.js +57 -0
  18. package/lib/settings/claude-auth.js +78 -0
  19. package/lib/settings.js +298 -15
  20. package/lib/stream-json-parser.js +4 -238
  21. package/package.json +27 -6
  22. package/scripts/setup-merge-queue.sh +170 -0
  23. package/scripts/validate-templates.js +100 -0
  24. package/src/agent/agent-config.js +140 -63
  25. package/src/agent/agent-context-builder.js +336 -165
  26. package/src/agent/agent-hook-executor.js +337 -67
  27. package/src/agent/agent-lifecycle.js +386 -287
  28. package/src/agent/agent-stuck-detector.js +7 -7
  29. package/src/agent/agent-task-executor.js +944 -683
  30. package/src/agent/output-extraction.js +217 -0
  31. package/src/agent/output-reformatter.js +175 -0
  32. package/src/agent/schema-utils.js +146 -0
  33. package/src/agent-wrapper.js +112 -31
  34. package/src/agents/git-pusher-template.js +285 -0
  35. package/src/claude-task-runner.js +145 -44
  36. package/src/config-router.js +13 -13
  37. package/src/config-validator.js +1049 -563
  38. package/src/input-helpers.js +65 -0
  39. package/src/isolation-manager.js +499 -320
  40. package/src/issue-providers/README.md +305 -0
  41. package/src/issue-providers/azure-devops-provider.js +273 -0
  42. package/src/issue-providers/base-provider.js +232 -0
  43. package/src/issue-providers/github-provider.js +179 -0
  44. package/src/issue-providers/gitlab-provider.js +241 -0
  45. package/src/issue-providers/index.js +196 -0
  46. package/src/issue-providers/jira-provider.js +239 -0
  47. package/src/ledger.js +50 -11
  48. package/src/lib/safe-exec.js +88 -0
  49. package/src/orchestrator.js +1348 -757
  50. package/src/preflight.js +306 -149
  51. package/src/process-metrics.js +98 -56
  52. package/src/providers/anthropic/cli-builder.js +73 -0
  53. package/src/providers/anthropic/index.js +204 -0
  54. package/src/providers/anthropic/models.js +23 -0
  55. package/src/providers/anthropic/output-parser.js +177 -0
  56. package/src/providers/base-provider.js +251 -0
  57. package/src/providers/capabilities.js +60 -0
  58. package/src/providers/google/cli-builder.js +55 -0
  59. package/src/providers/google/index.js +116 -0
  60. package/src/providers/google/models.js +24 -0
  61. package/src/providers/google/output-parser.js +101 -0
  62. package/src/providers/index.js +91 -0
  63. package/src/providers/openai/cli-builder.js +133 -0
  64. package/src/providers/openai/index.js +136 -0
  65. package/src/providers/openai/models.js +21 -0
  66. package/src/providers/openai/output-parser.js +143 -0
  67. package/src/providers/opencode/cli-builder.js +42 -0
  68. package/src/providers/opencode/index.js +103 -0
  69. package/src/providers/opencode/models.js +55 -0
  70. package/src/providers/opencode/output-parser.js +122 -0
  71. package/src/schemas/sub-cluster.js +68 -39
  72. package/src/status-footer.js +94 -48
  73. package/src/sub-cluster-wrapper.js +92 -36
  74. package/src/task-runner.js +8 -6
  75. package/src/template-resolver.js +12 -9
  76. package/src/tui/data-poller.js +123 -99
  77. package/src/tui/formatters.js +4 -3
  78. package/src/tui/keybindings.js +259 -318
  79. package/src/tui/layout.js +20 -3
  80. package/src/tui/renderer.js +11 -21
  81. package/task-lib/attachable-watcher.js +150 -111
  82. package/task-lib/claude-recovery.js +156 -0
  83. package/task-lib/commands/episodes.js +105 -0
  84. package/task-lib/commands/list.js +3 -3
  85. package/task-lib/commands/logs.js +231 -189
  86. package/task-lib/commands/resume.js +3 -2
  87. package/task-lib/commands/run.js +12 -3
  88. package/task-lib/commands/schedules.js +111 -61
  89. package/task-lib/config.js +0 -2
  90. package/task-lib/runner.js +128 -50
  91. package/task-lib/scheduler.js +3 -3
  92. package/task-lib/store.js +464 -152
  93. package/task-lib/tui/formatters.js +94 -45
  94. package/task-lib/tui/renderer.js +13 -3
  95. package/task-lib/tui.js +73 -32
  96. package/task-lib/watcher.js +157 -100
  97. package/src/agents/git-pusher-agent.json +0 -20
  98. package/src/github.js +0 -103
@@ -13,7 +13,7 @@
13
13
  * - macOS: ps + lsof
14
14
  */
15
15
 
16
- const { execSync } = require('child_process');
16
+ const { execSync } = require('./lib/safe-exec'); // Enforces timeouts
17
17
  const fs = require('fs');
18
18
 
19
19
  const PLATFORM = process.platform;
@@ -56,41 +56,13 @@ function getChildPids(pid) {
56
56
  const children = [];
57
57
 
58
58
  try {
59
- if (PLATFORM === 'darwin') {
60
- // macOS: Use pgrep with -P flag
61
- const output = execSync(`pgrep -P ${escapeShell(String(pid))} 2>/dev/null`, {
62
- encoding: 'utf8',
63
- timeout: 2000,
64
- });
65
- const pids = output.trim().split('\n').filter(Boolean).map(Number);
66
- children.push(...pids);
67
-
68
- // Recursively get grandchildren
69
- for (const childPid of pids) {
70
- children.push(...getChildPids(childPid));
71
- }
72
- } else {
73
- // Linux: Read /proc/{pid}/task/{tid}/children
74
- const taskPath = `/proc/${pid}/task`;
75
- if (fs.existsSync(taskPath)) {
76
- const tids = fs.readdirSync(taskPath);
77
- for (const tid of tids) {
78
- const childrenPath = `/proc/${pid}/task/${tid}/children`;
79
- if (fs.existsSync(childrenPath)) {
80
- const childPids = fs.readFileSync(childrenPath, 'utf8')
81
- .trim()
82
- .split(/\s+/)
83
- .filter(Boolean)
84
- .map(Number);
85
- children.push(...childPids);
86
-
87
- // Recursively get grandchildren
88
- for (const childPid of childPids) {
89
- children.push(...getChildPids(childPid));
90
- }
91
- }
92
- }
93
- }
59
+ const childPids =
60
+ PLATFORM === 'darwin' ? collectDarwinChildPids(pid) : collectLinuxChildPids(pid);
61
+ children.push(...childPids);
62
+
63
+ // Recursively get grandchildren
64
+ for (const childPid of childPids) {
65
+ children.push(...getChildPids(childPid));
94
66
  }
95
67
  } catch {
96
68
  // Ignore errors (process may have exited)
@@ -99,6 +71,45 @@ function getChildPids(pid) {
99
71
  return [...new Set(children)]; // Dedupe
100
72
  }
101
73
 
74
+ function collectDarwinChildPids(pid) {
75
+ const output = execSync(`pgrep -P ${escapeShell(String(pid))} 2>/dev/null`, {
76
+ encoding: 'utf8',
77
+ timeout: 2000,
78
+ });
79
+
80
+ return output.trim().split('\n').filter(Boolean).map(Number);
81
+ }
82
+
83
+ function collectLinuxChildPids(pid) {
84
+ const taskPath = `/proc/${pid}/task`;
85
+ if (!fs.existsSync(taskPath)) {
86
+ return [];
87
+ }
88
+
89
+ const tids = fs.readdirSync(taskPath);
90
+ const childPids = [];
91
+
92
+ for (const tid of tids) {
93
+ const childrenPath = `/proc/${pid}/task/${tid}/children`;
94
+ childPids.push(...readChildPidFile(childrenPath));
95
+ }
96
+
97
+ return childPids;
98
+ }
99
+
100
+ function readChildPidFile(childrenPath) {
101
+ if (!fs.existsSync(childrenPath)) {
102
+ return [];
103
+ }
104
+
105
+ const raw = fs.readFileSync(childrenPath, 'utf8').trim();
106
+ if (!raw) {
107
+ return [];
108
+ }
109
+
110
+ return raw.split(/\s+/).filter(Boolean).map(Number);
111
+ }
112
+
102
113
  /**
103
114
  * Get metrics for a single process (Linux)
104
115
  * @param {number} pid - Process ID
@@ -187,17 +198,20 @@ function getNetworkStateLinux(pid) {
187
198
  hasActivity: false,
188
199
  sendQueueBytes: 0,
189
200
  recvQueueBytes: 0,
190
- bytesSent: 0, // Cumulative bytes sent across all sockets
191
- bytesReceived: 0, // Cumulative bytes received across all sockets
201
+ bytesSent: 0, // Cumulative bytes sent across all sockets
202
+ bytesReceived: 0, // Cumulative bytes received across all sockets
192
203
  };
193
204
 
194
205
  try {
195
206
  // Use ss -tip to get extended TCP info with bytes_sent/bytes_received
196
207
  // -t = TCP only, -i = show internal TCP info, -p = show process
197
- const output = execSync(`ss -tip 2>/dev/null | grep -A1 "pid=${escapeShell(String(pid))}," || true`, {
198
- encoding: 'utf8',
199
- timeout: 3000,
200
- });
208
+ const output = execSync(
209
+ `ss -tip 2>/dev/null | grep -A1 "pid=${escapeShell(String(pid))}," || true`,
210
+ {
211
+ encoding: 'utf8',
212
+ timeout: 3000,
213
+ }
214
+ );
201
215
 
202
216
  if (!output.trim()) {
203
217
  return result;
@@ -261,8 +275,8 @@ function getNetworkStateDarwin(pid) {
261
275
  hasActivity: false,
262
276
  sendQueueBytes: 0,
263
277
  recvQueueBytes: 0,
264
- bytesSent: 0, // Not available on macOS without root/dtrace
265
- bytesReceived: 0, // Not available on macOS without root/dtrace
278
+ bytesSent: 0, // Not available on macOS without root/dtrace
279
+ bytesReceived: 0, // Not available on macOS without root/dtrace
266
280
  };
267
281
 
268
282
  try {
@@ -281,7 +295,7 @@ function getNetworkStateDarwin(pid) {
281
295
  for (const line of lines) {
282
296
  const parts = line.split(/\s+/);
283
297
  // Look for ESTABLISHED connections
284
- if (parts.includes('ESTABLISHED') || parts.some(p => p.includes('->'))) {
298
+ if (parts.includes('ESTABLISHED') || parts.some((p) => p.includes('->'))) {
285
299
  result.established++;
286
300
  result.hasActivity = true; // lsof doesn't show queue sizes, assume activity
287
301
  }
@@ -334,14 +348,21 @@ async function getProcessMetricsLinuxAggregated(pid, samplePeriodMs) {
334
348
  memoryMB: 0,
335
349
  state: 'X',
336
350
  threads: 0,
337
- network: { established: 0, hasActivity: false, sendQueueBytes: 0, recvQueueBytes: 0, bytesSent: 0, bytesReceived: 0 },
351
+ network: {
352
+ established: 0,
353
+ hasActivity: false,
354
+ sendQueueBytes: 0,
355
+ recvQueueBytes: 0,
356
+ bytesSent: 0,
357
+ bytesReceived: 0,
358
+ },
338
359
  childCount: 0,
339
360
  timestamp: Date.now(),
340
361
  };
341
362
  }
342
363
 
343
364
  // Wait for sample period
344
- await new Promise(r => setTimeout(r, samplePeriodMs));
365
+ await new Promise((r) => setTimeout(r, samplePeriodMs));
345
366
 
346
367
  // Get second CPU sample
347
368
  const t1Metrics = {};
@@ -385,7 +406,14 @@ async function getProcessMetricsLinuxAggregated(pid, samplePeriodMs) {
385
406
  const cpuPercent = Math.min(100, rawCpuPercent / cpuCores);
386
407
 
387
408
  // Get network state for all processes
388
- let network = { established: 0, hasActivity: false, sendQueueBytes: 0, recvQueueBytes: 0, bytesSent: 0, bytesReceived: 0 };
409
+ let network = {
410
+ established: 0,
411
+ hasActivity: false,
412
+ sendQueueBytes: 0,
413
+ recvQueueBytes: 0,
414
+ bytesSent: 0,
415
+ bytesReceived: 0,
416
+ };
389
417
  for (const p of Object.keys(t1Metrics)) {
390
418
  const netState = getNetworkStateLinux(parseInt(p, 10));
391
419
  network.established += netState.established;
@@ -451,7 +479,14 @@ function getProcessMetricsDarwinAggregated(pid) {
451
479
  }
452
480
 
453
481
  // Get network state
454
- let network = { established: 0, hasActivity: false, sendQueueBytes: 0, recvQueueBytes: 0, bytesSent: 0, bytesReceived: 0 };
482
+ let network = {
483
+ established: 0,
484
+ hasActivity: false,
485
+ sendQueueBytes: 0,
486
+ recvQueueBytes: 0,
487
+ bytesSent: 0,
488
+ bytesReceived: 0,
489
+ };
455
490
  for (const p of allPids) {
456
491
  const netState = getNetworkStateDarwin(p);
457
492
  network.established += netState.established;
@@ -518,13 +553,20 @@ function formatMetrics(metrics) {
518
553
  */
519
554
  function getStateIcon(state) {
520
555
  switch (state) {
521
- case 'R': return '🟢'; // Running
522
- case 'S': return '🔵'; // Sleeping
523
- case 'D': return '🟡'; // Disk wait
524
- case 'Z': return '💀'; // Zombie
525
- case 'T': return '⏸️'; // Stopped
526
- case 'X': return ''; // Dead
527
- default: return '';
556
+ case 'R':
557
+ return '🟢'; // Running
558
+ case 'S':
559
+ return '🔵'; // Sleeping
560
+ case 'D':
561
+ return '🟡'; // Disk wait
562
+ case 'Z':
563
+ return '💀'; // Zombie
564
+ case 'T':
565
+ return '⏸️'; // Stopped
566
+ case 'X':
567
+ return '❌'; // Dead
568
+ default:
569
+ return '⚪';
528
570
  }
529
571
  }
530
572
 
@@ -0,0 +1,73 @@
1
+ function buildCommand(context, options = {}, commandConfig = {}) {
2
+ const {
3
+ modelSpec,
4
+ outputFormat,
5
+ jsonSchema,
6
+ autoApprove,
7
+ cliFeatures = {},
8
+ authEnv = {},
9
+ } = options;
10
+
11
+ const command = commandConfig.command || 'claude';
12
+ const extraArgs = commandConfig.args || [];
13
+ const args = [...extraArgs, '--print', '--input-format', 'text'];
14
+
15
+ addOutputFormatArgs(args, outputFormat, cliFeatures);
16
+ addStreamJsonArgs(args, outputFormat, cliFeatures);
17
+ addJsonSchemaArgs(args, outputFormat, jsonSchema, cliFeatures);
18
+ addModelArgs(args, modelSpec, cliFeatures);
19
+ addAutoApproveArgs(args, autoApprove, cliFeatures);
20
+
21
+ args.push(context);
22
+
23
+ return {
24
+ binary: command,
25
+ args,
26
+ env: authEnv,
27
+ };
28
+ }
29
+
30
+ function addOutputFormatArgs(args, outputFormat, cliFeatures) {
31
+ if (!outputFormat || cliFeatures.supportsOutputFormat === false) {
32
+ return;
33
+ }
34
+ args.push('--output-format', outputFormat);
35
+ }
36
+
37
+ function addStreamJsonArgs(args, outputFormat, cliFeatures) {
38
+ if (outputFormat !== 'stream-json') {
39
+ return;
40
+ }
41
+ if (cliFeatures.supportsVerbose !== false) {
42
+ args.push('--verbose');
43
+ }
44
+ if (cliFeatures.supportsIncludePartials !== false) {
45
+ args.push('--include-partial-messages');
46
+ }
47
+ }
48
+
49
+ function addJsonSchemaArgs(args, outputFormat, jsonSchema, cliFeatures) {
50
+ if (!jsonSchema || outputFormat !== 'json' || cliFeatures.supportsJsonSchema === false) {
51
+ return;
52
+ }
53
+ const schemaString = typeof jsonSchema === 'string' ? jsonSchema : JSON.stringify(jsonSchema);
54
+ args.push('--json-schema', schemaString);
55
+ }
56
+
57
+ function addModelArgs(args, modelSpec, cliFeatures) {
58
+ if (!modelSpec?.model || cliFeatures.supportsModel === false) {
59
+ return;
60
+ }
61
+ args.push('--model', modelSpec.model);
62
+ }
63
+
64
+ function addAutoApproveArgs(args, autoApprove, cliFeatures) {
65
+ if (!autoApprove || cliFeatures.supportsAutoApprove === false) {
66
+ return;
67
+ }
68
+ args.push('--dangerously-skip-permissions');
69
+ }
70
+
71
+ module.exports = {
72
+ buildCommand,
73
+ };
@@ -0,0 +1,204 @@
1
+ const BaseProvider = require('../base-provider');
2
+ const { getClaudeCommand, loadSettings } = require('../../../lib/settings');
3
+ const { commandExists, getCommandPath, getHelpOutput } = require('../../../lib/provider-detection');
4
+ const { resolveClaudeAuth } = require('../../../lib/settings/claude-auth');
5
+ const { buildCommand } = require('./cli-builder');
6
+ const { parseEvent } = require('./output-parser');
7
+ const {
8
+ MODEL_CATALOG,
9
+ LEVEL_MAPPING,
10
+ DEFAULT_LEVEL,
11
+ DEFAULT_MAX_LEVEL,
12
+ DEFAULT_MIN_LEVEL,
13
+ } = require('./models');
14
+
15
+ const warned = new Set();
16
+
17
+ class AnthropicProvider extends BaseProvider {
18
+ constructor() {
19
+ super({ name: 'claude', displayName: 'Claude', cliCommand: 'claude' });
20
+ this._cliFeatures = null;
21
+ }
22
+
23
+ // SDK not implemented - uses CLI only
24
+ // See BaseProvider for SDK extension point documentation
25
+
26
+ isAvailable() {
27
+ const { command } = getClaudeCommand();
28
+ return commandExists(command);
29
+ }
30
+
31
+ getCliPath() {
32
+ const { command } = getClaudeCommand();
33
+ return getCommandPath(command) || command;
34
+ }
35
+
36
+ getInstallInstructions() {
37
+ return ['npm install -g @anthropic-ai/claude-code', 'Or (macOS): brew install claude'].join(
38
+ '\n'
39
+ );
40
+ }
41
+
42
+ getAuthInstructions() {
43
+ return 'claude login';
44
+ }
45
+
46
+ getCliFeatures() {
47
+ if (this._cliFeatures) return this._cliFeatures;
48
+
49
+ const { command, args } = getClaudeCommand();
50
+ const help = getHelpOutput(command, args);
51
+ const unknown = !help;
52
+
53
+ const features = {
54
+ supportsOutputFormat: unknown ? true : /--output-format/.test(help),
55
+ supportsStreamJson: unknown ? true : /stream-json/.test(help),
56
+ supportsJsonSchema: unknown ? true : /--json-schema/.test(help),
57
+ supportsAutoApprove: unknown ? true : /--dangerously-skip-permissions/.test(help),
58
+ supportsIncludePartials: unknown ? true : /--include-partial-messages/.test(help),
59
+ supportsVerbose: unknown ? true : /--verbose/.test(help),
60
+ supportsModel: unknown ? true : /--model/.test(help),
61
+ unknown,
62
+ };
63
+
64
+ this._cliFeatures = features;
65
+ return features;
66
+ }
67
+
68
+ getCredentialPaths() {
69
+ return ['~/.claude'];
70
+ }
71
+
72
+ /**
73
+ * Resolve authentication environment variables for Claude CLI.
74
+ * Handles Bedrock, API key, and OAuth authentication.
75
+ * @returns {Object} Environment variables for authentication
76
+ */
77
+ resolveAuthEnv() {
78
+ const settings = loadSettings();
79
+ return resolveClaudeAuth(settings);
80
+ }
81
+
82
+ buildCommand(context, options) {
83
+ const { command, args } = getClaudeCommand();
84
+ const cliFeatures = options.cliFeatures || {};
85
+
86
+ if (options.jsonSchema && options.outputFormat !== 'json' && !options.strictSchema) {
87
+ this._warnOnce(
88
+ 'claude-jsonschema-stream',
89
+ 'jsonSchema requested with stream output; schema enforcement will be post-validated.'
90
+ );
91
+ }
92
+
93
+ if (
94
+ options.jsonSchema &&
95
+ options.outputFormat === 'json' &&
96
+ cliFeatures.supportsJsonSchema === false
97
+ ) {
98
+ this._warnOnce(
99
+ 'claude-jsonschema-flag',
100
+ 'Claude CLI does not support --json-schema; skipping schema flag.'
101
+ );
102
+ }
103
+
104
+ if (options.autoApprove && cliFeatures.supportsAutoApprove === false) {
105
+ this._warnOnce(
106
+ 'claude-auto-approve',
107
+ 'Claude CLI does not support --dangerously-skip-permissions; continuing without auto-approve.'
108
+ );
109
+ }
110
+
111
+ const authEnv = this.resolveAuthEnv();
112
+ const resolvedOptions = { ...options, cliFeatures, authEnv };
113
+
114
+ return buildCommand(context, resolvedOptions, { command, args });
115
+ }
116
+
117
+ parseEvent(line) {
118
+ return parseEvent(line);
119
+ }
120
+
121
+ getModelCatalog() {
122
+ return MODEL_CATALOG;
123
+ }
124
+
125
+ getLevelMapping() {
126
+ return LEVEL_MAPPING;
127
+ }
128
+
129
+ getDefaultLevel() {
130
+ return DEFAULT_LEVEL;
131
+ }
132
+
133
+ getDefaultMaxLevel() {
134
+ return DEFAULT_MAX_LEVEL;
135
+ }
136
+
137
+ getDefaultMinLevel() {
138
+ return DEFAULT_MIN_LEVEL;
139
+ }
140
+
141
+ _warnOnce(key, message) {
142
+ if (warned.has(key)) return;
143
+ warned.add(key);
144
+ console.warn(`⚠️ ${message}`);
145
+ }
146
+
147
+ /**
148
+ * Get default settings including Claude-specific auth fields
149
+ * @override
150
+ */
151
+ getDefaultSettings() {
152
+ return {
153
+ ...super.getDefaultSettings(),
154
+ // Authentication (optional persistent storage)
155
+ anthropicApiKey: null, // sk-ant-* key
156
+ bedrockApiKey: null, // AWS_BEARER_TOKEN_BEDROCK value
157
+ bedrockRegion: null, // AWS_REGION for Bedrock
158
+ };
159
+ }
160
+
161
+ /**
162
+ * Validate Claude-specific settings including auth fields
163
+ * @override
164
+ */
165
+ validateSettings(settings) {
166
+ // First validate base provider settings (levels, etc.)
167
+ const baseError = super.validateSettings(settings);
168
+ if (baseError) return baseError;
169
+
170
+ // Claude-specific auth field validation
171
+ const {
172
+ isValidAnthropicKey,
173
+ ANTHROPIC_KEY_PREFIX,
174
+ } = require('../../../lib/settings/claude-auth');
175
+
176
+ // Validate string-or-null fields
177
+ for (const field of ['anthropicApiKey', 'bedrockApiKey', 'bedrockRegion']) {
178
+ if (
179
+ settings[field] !== undefined &&
180
+ settings[field] !== null &&
181
+ typeof settings[field] !== 'string'
182
+ ) {
183
+ return `providerSettings.claude.${field} must be a string or null`;
184
+ }
185
+ }
186
+
187
+ // Additional prefix validation for Anthropic API key
188
+ if (settings.anthropicApiKey && !isValidAnthropicKey(settings.anthropicApiKey)) {
189
+ return `providerSettings.claude.anthropicApiKey must start with ${ANTHROPIC_KEY_PREFIX}`;
190
+ }
191
+
192
+ return null;
193
+ }
194
+
195
+ /**
196
+ * Get Claude-specific setting field names
197
+ * @override
198
+ */
199
+ getSettingsFields() {
200
+ return [...super.getSettingsFields(), 'anthropicApiKey', 'bedrockApiKey', 'bedrockRegion'];
201
+ }
202
+ }
203
+
204
+ module.exports = AnthropicProvider;
@@ -0,0 +1,23 @@
1
+ const MODEL_CATALOG = {
2
+ haiku: { rank: 1 },
3
+ sonnet: { rank: 2 },
4
+ opus: { rank: 3 },
5
+ };
6
+
7
+ const LEVEL_MAPPING = {
8
+ level1: { rank: 1, model: 'haiku' },
9
+ level2: { rank: 2, model: 'sonnet' },
10
+ level3: { rank: 3, model: 'opus' },
11
+ };
12
+
13
+ const DEFAULT_LEVEL = 'level2';
14
+ const DEFAULT_MAX_LEVEL = 'level3';
15
+ const DEFAULT_MIN_LEVEL = 'level1';
16
+
17
+ module.exports = {
18
+ MODEL_CATALOG,
19
+ LEVEL_MAPPING,
20
+ DEFAULT_LEVEL,
21
+ DEFAULT_MAX_LEVEL,
22
+ DEFAULT_MIN_LEVEL,
23
+ };
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Stream JSON Parser for Claude Code output
3
+ */
4
+
5
+ /**
6
+ * Parse result event type
7
+ * @param {Object} event
8
+ * @returns {Object}
9
+ */
10
+ function parseResultEvent(event) {
11
+ const usage = event.usage || {};
12
+ return {
13
+ type: 'result',
14
+ success: event.subtype === 'success',
15
+ result: event.result,
16
+ error: event.is_error ? event.result : null,
17
+ cost: event.total_cost_usd,
18
+ duration: event.duration_ms,
19
+ inputTokens: usage.input_tokens || 0,
20
+ outputTokens: usage.output_tokens || 0,
21
+ cacheReadInputTokens: usage.cache_read_input_tokens || 0,
22
+ cacheCreationInputTokens: usage.cache_creation_input_tokens || 0,
23
+ modelUsage: event.modelUsage || null,
24
+ };
25
+ }
26
+
27
+ /**
28
+ * Parse a single JSON line and extract displayable content
29
+ * @param {string} line
30
+ * @returns {Object|null}
31
+ */
32
+ function parseEvent(line) {
33
+ const trimmed = line.trim();
34
+ if (!trimmed || !trimmed.startsWith('{') || !trimmed.endsWith('}')) {
35
+ return null;
36
+ }
37
+
38
+ let event;
39
+ try {
40
+ event = JSON.parse(trimmed);
41
+ } catch {
42
+ return null;
43
+ }
44
+
45
+ if (event.type === 'stream_event' && event.event) {
46
+ return parseStreamEvent(event.event);
47
+ }
48
+
49
+ if (event.type === 'assistant' && event.message?.content) {
50
+ return parseAssistantMessage(event.message);
51
+ }
52
+
53
+ if (event.type === 'user' && event.message?.content) {
54
+ return parseUserMessage(event.message);
55
+ }
56
+
57
+ if (event.type === 'result') {
58
+ return parseResultEvent(event);
59
+ }
60
+
61
+ if (event.type === 'system') {
62
+ return null;
63
+ }
64
+
65
+ return null;
66
+ }
67
+
68
+ function parseStreamEvent(inner) {
69
+ if (inner.type === 'content_block_delta' && inner.delta) {
70
+ const delta = inner.delta;
71
+
72
+ if (delta.type === 'text_delta' && delta.text) {
73
+ return {
74
+ type: 'text',
75
+ text: delta.text,
76
+ };
77
+ }
78
+
79
+ if (delta.type === 'thinking_delta' && delta.thinking) {
80
+ return {
81
+ type: 'thinking',
82
+ text: delta.thinking,
83
+ };
84
+ }
85
+ }
86
+
87
+ return null;
88
+ }
89
+
90
+ function parseAssistantMessage(message) {
91
+ const results = [];
92
+
93
+ for (const block of message.content) {
94
+ // Handle text content blocks (CRITICAL for Haiku/weaker models that return JSON in text)
95
+ // Issue #52: Haiku returns JSON in type:text blocks, not in result wrapper
96
+ if (block.type === 'text' && block.text) {
97
+ results.push({
98
+ type: 'text',
99
+ text: block.text,
100
+ });
101
+ }
102
+
103
+ if (block.type === 'tool_use') {
104
+ results.push({
105
+ type: 'tool_call',
106
+ toolName: block.name,
107
+ toolId: block.id,
108
+ input: block.input,
109
+ });
110
+ }
111
+
112
+ if (block.type === 'thinking' && block.thinking) {
113
+ results.push({
114
+ type: 'thinking',
115
+ text: block.thinking,
116
+ });
117
+ }
118
+ }
119
+
120
+ if (results.length === 1) {
121
+ return results[0];
122
+ }
123
+
124
+ if (results.length > 1) {
125
+ return results;
126
+ }
127
+
128
+ return null;
129
+ }
130
+
131
+ function parseUserMessage(message) {
132
+ const results = [];
133
+
134
+ for (const block of message.content) {
135
+ if (block.type === 'tool_result') {
136
+ results.push({
137
+ type: 'tool_result',
138
+ toolId: block.tool_use_id,
139
+ content: typeof block.content === 'string' ? block.content : JSON.stringify(block.content),
140
+ isError: block.is_error || false,
141
+ });
142
+ }
143
+ }
144
+
145
+ if (results.length === 1) {
146
+ return results[0];
147
+ }
148
+
149
+ if (results.length > 1) {
150
+ return results;
151
+ }
152
+
153
+ return null;
154
+ }
155
+
156
+ function parseChunk(chunk) {
157
+ const events = [];
158
+ const lines = chunk.split('\n');
159
+
160
+ for (const line of lines) {
161
+ const event = parseEvent(line);
162
+ if (event) {
163
+ if (Array.isArray(event)) {
164
+ events.push(...event);
165
+ } else {
166
+ events.push(event);
167
+ }
168
+ }
169
+ }
170
+
171
+ return events;
172
+ }
173
+
174
+ module.exports = {
175
+ parseEvent,
176
+ parseChunk,
177
+ };