@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.
- package/CHANGELOG.md +174 -189
- package/README.md +226 -195
- package/cli/commands/providers.js +149 -0
- package/cli/index.js +3145 -2366
- package/cli/lib/first-run.js +40 -3
- package/cli/message-formatters-normal.js +28 -6
- package/cluster-templates/base-templates/debug-workflow.json +24 -78
- package/cluster-templates/base-templates/full-workflow.json +99 -316
- package/cluster-templates/base-templates/single-worker.json +23 -15
- package/cluster-templates/base-templates/worker-validator.json +105 -36
- package/cluster-templates/conductor-bootstrap.json +9 -7
- package/lib/docker-config.js +14 -1
- package/lib/git-remote-utils.js +165 -0
- package/lib/id-detector.js +10 -7
- package/lib/provider-defaults.js +62 -0
- package/lib/provider-detection.js +59 -0
- package/lib/provider-names.js +57 -0
- package/lib/settings/claude-auth.js +78 -0
- package/lib/settings.js +298 -15
- package/lib/stream-json-parser.js +4 -238
- package/package.json +27 -6
- package/scripts/setup-merge-queue.sh +170 -0
- package/scripts/validate-templates.js +100 -0
- package/src/agent/agent-config.js +140 -63
- package/src/agent/agent-context-builder.js +336 -165
- package/src/agent/agent-hook-executor.js +337 -67
- package/src/agent/agent-lifecycle.js +386 -287
- package/src/agent/agent-stuck-detector.js +7 -7
- package/src/agent/agent-task-executor.js +944 -683
- package/src/agent/output-extraction.js +217 -0
- package/src/agent/output-reformatter.js +175 -0
- package/src/agent/schema-utils.js +146 -0
- package/src/agent-wrapper.js +112 -31
- package/src/agents/git-pusher-template.js +285 -0
- package/src/claude-task-runner.js +145 -44
- package/src/config-router.js +13 -13
- package/src/config-validator.js +1049 -563
- package/src/input-helpers.js +65 -0
- package/src/isolation-manager.js +499 -320
- package/src/issue-providers/README.md +305 -0
- package/src/issue-providers/azure-devops-provider.js +273 -0
- package/src/issue-providers/base-provider.js +232 -0
- package/src/issue-providers/github-provider.js +179 -0
- package/src/issue-providers/gitlab-provider.js +241 -0
- package/src/issue-providers/index.js +196 -0
- package/src/issue-providers/jira-provider.js +239 -0
- package/src/ledger.js +50 -11
- package/src/lib/safe-exec.js +88 -0
- package/src/orchestrator.js +1348 -757
- package/src/preflight.js +306 -149
- package/src/process-metrics.js +98 -56
- package/src/providers/anthropic/cli-builder.js +73 -0
- package/src/providers/anthropic/index.js +204 -0
- package/src/providers/anthropic/models.js +23 -0
- package/src/providers/anthropic/output-parser.js +177 -0
- package/src/providers/base-provider.js +251 -0
- package/src/providers/capabilities.js +60 -0
- package/src/providers/google/cli-builder.js +55 -0
- package/src/providers/google/index.js +116 -0
- package/src/providers/google/models.js +24 -0
- package/src/providers/google/output-parser.js +101 -0
- package/src/providers/index.js +91 -0
- package/src/providers/openai/cli-builder.js +133 -0
- package/src/providers/openai/index.js +136 -0
- package/src/providers/openai/models.js +21 -0
- package/src/providers/openai/output-parser.js +143 -0
- package/src/providers/opencode/cli-builder.js +42 -0
- package/src/providers/opencode/index.js +103 -0
- package/src/providers/opencode/models.js +55 -0
- package/src/providers/opencode/output-parser.js +122 -0
- package/src/schemas/sub-cluster.js +68 -39
- package/src/status-footer.js +94 -48
- package/src/sub-cluster-wrapper.js +92 -36
- package/src/task-runner.js +8 -6
- package/src/template-resolver.js +12 -9
- package/src/tui/data-poller.js +123 -99
- package/src/tui/formatters.js +4 -3
- package/src/tui/keybindings.js +259 -318
- package/src/tui/layout.js +20 -3
- package/src/tui/renderer.js +11 -21
- package/task-lib/attachable-watcher.js +150 -111
- package/task-lib/claude-recovery.js +156 -0
- package/task-lib/commands/episodes.js +105 -0
- package/task-lib/commands/list.js +3 -3
- package/task-lib/commands/logs.js +231 -189
- package/task-lib/commands/resume.js +3 -2
- package/task-lib/commands/run.js +12 -3
- package/task-lib/commands/schedules.js +111 -61
- package/task-lib/config.js +0 -2
- package/task-lib/runner.js +128 -50
- package/task-lib/scheduler.js +3 -3
- package/task-lib/store.js +464 -152
- package/task-lib/tui/formatters.js +94 -45
- package/task-lib/tui/renderer.js +13 -3
- package/task-lib/tui.js +73 -32
- package/task-lib/watcher.js +157 -100
- package/src/agents/git-pusher-agent.json +0 -20
- package/src/github.js +0 -103
package/src/process-metrics.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* - macOS: ps + lsof
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
const { execSync } = require('
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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,
|
|
191
|
-
bytesReceived: 0,
|
|
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(
|
|
198
|
-
|
|
199
|
-
|
|
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,
|
|
265
|
-
bytesReceived: 0,
|
|
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: {
|
|
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 = {
|
|
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 = {
|
|
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':
|
|
522
|
-
|
|
523
|
-
case '
|
|
524
|
-
|
|
525
|
-
case '
|
|
526
|
-
|
|
527
|
-
|
|
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
|
+
};
|