@covibes/zeroshot 5.3.0 → 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/README.md +94 -14
- package/cli/commands/providers.js +8 -9
- package/cli/index.js +3032 -2409
- package/cli/message-formatters-normal.js +28 -6
- package/cluster-templates/base-templates/debug-workflow.json +1 -1
- package/cluster-templates/base-templates/full-workflow.json +72 -188
- package/cluster-templates/base-templates/worker-validator.json +59 -3
- package/cluster-templates/conductor-bootstrap.json +4 -4
- package/lib/docker-config.js +8 -0
- 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-names.js +2 -1
- package/lib/settings/claude-auth.js +78 -0
- package/lib/settings.js +161 -63
- package/package.json +7 -2
- package/scripts/setup-merge-queue.sh +170 -0
- package/src/agent/agent-config.js +135 -82
- package/src/agent/agent-context-builder.js +297 -188
- package/src/agent/agent-hook-executor.js +310 -113
- package/src/agent/agent-lifecycle.js +385 -325
- package/src/agent/agent-stuck-detector.js +7 -7
- package/src/agent/agent-task-executor.js +824 -565
- package/src/agent/output-extraction.js +41 -24
- package/src/agent/output-reformatter.js +1 -1
- package/src/agent/schema-utils.js +108 -73
- package/src/agent-wrapper.js +10 -1
- package/src/agents/git-pusher-template.js +285 -0
- package/src/claude-task-runner.js +85 -34
- package/src/config-validator.js +922 -657
- package/src/input-helpers.js +65 -0
- package/src/isolation-manager.js +289 -199
- 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 +22 -5
- package/src/lib/safe-exec.js +88 -0
- package/src/orchestrator.js +1107 -811
- package/src/preflight.js +313 -159
- package/src/process-metrics.js +98 -56
- package/src/providers/anthropic/cli-builder.js +53 -25
- package/src/providers/anthropic/index.js +72 -2
- package/src/providers/anthropic/output-parser.js +32 -14
- package/src/providers/base-provider.js +70 -0
- package/src/providers/capabilities.js +9 -0
- package/src/providers/google/output-parser.js +47 -38
- package/src/providers/index.js +19 -3
- package/src/providers/openai/cli-builder.js +14 -3
- package/src/providers/openai/index.js +1 -0
- package/src/providers/openai/output-parser.js +44 -30
- 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 +76 -35
- 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/renderer.js +11 -21
- package/task-lib/attachable-watcher.js +118 -81
- package/task-lib/claude-recovery.js +61 -24
- package/task-lib/commands/episodes.js +105 -0
- package/task-lib/commands/list.js +2 -2
- package/task-lib/commands/logs.js +231 -189
- package/task-lib/commands/schedules.js +111 -61
- package/task-lib/config.js +0 -2
- package/task-lib/runner.js +84 -27
- package/task-lib/scheduler.js +1 -1
- package/task-lib/store.js +467 -168
- 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 +128 -90
- package/src/agents/git-pusher-agent.json +0 -20
- package/src/github.js +0 -139
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
|
|
|
@@ -1,43 +1,71 @@
|
|
|
1
1
|
function buildCommand(context, options = {}, commandConfig = {}) {
|
|
2
|
-
const {
|
|
2
|
+
const {
|
|
3
|
+
modelSpec,
|
|
4
|
+
outputFormat,
|
|
5
|
+
jsonSchema,
|
|
6
|
+
autoApprove,
|
|
7
|
+
cliFeatures = {},
|
|
8
|
+
authEnv = {},
|
|
9
|
+
} = options;
|
|
3
10
|
|
|
4
11
|
const command = commandConfig.command || 'claude';
|
|
5
12
|
const extraArgs = commandConfig.args || [];
|
|
6
13
|
const args = [...extraArgs, '--print', '--input-format', 'text'];
|
|
7
14
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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);
|
|
11
20
|
|
|
12
|
-
|
|
13
|
-
if (cliFeatures.supportsVerbose !== false) {
|
|
14
|
-
args.push('--verbose');
|
|
15
|
-
}
|
|
16
|
-
if (cliFeatures.supportsIncludePartials !== false) {
|
|
17
|
-
args.push('--include-partial-messages');
|
|
18
|
-
}
|
|
19
|
-
}
|
|
21
|
+
args.push(context);
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
args
|
|
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;
|
|
24
33
|
}
|
|
34
|
+
args.push('--output-format', outputFormat);
|
|
35
|
+
}
|
|
25
36
|
|
|
26
|
-
|
|
27
|
-
|
|
37
|
+
function addStreamJsonArgs(args, outputFormat, cliFeatures) {
|
|
38
|
+
if (outputFormat !== 'stream-json') {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (cliFeatures.supportsVerbose !== false) {
|
|
42
|
+
args.push('--verbose');
|
|
28
43
|
}
|
|
44
|
+
if (cliFeatures.supportsIncludePartials !== false) {
|
|
45
|
+
args.push('--include-partial-messages');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
29
48
|
|
|
30
|
-
|
|
31
|
-
|
|
49
|
+
function addJsonSchemaArgs(args, outputFormat, jsonSchema, cliFeatures) {
|
|
50
|
+
if (!jsonSchema || outputFormat !== 'json' || cliFeatures.supportsJsonSchema === false) {
|
|
51
|
+
return;
|
|
32
52
|
}
|
|
53
|
+
const schemaString = typeof jsonSchema === 'string' ? jsonSchema : JSON.stringify(jsonSchema);
|
|
54
|
+
args.push('--json-schema', schemaString);
|
|
55
|
+
}
|
|
33
56
|
|
|
34
|
-
|
|
57
|
+
function addModelArgs(args, modelSpec, cliFeatures) {
|
|
58
|
+
if (!modelSpec?.model || cliFeatures.supportsModel === false) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
args.push('--model', modelSpec.model);
|
|
62
|
+
}
|
|
35
63
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
64
|
+
function addAutoApproveArgs(args, autoApprove, cliFeatures) {
|
|
65
|
+
if (!autoApprove || cliFeatures.supportsAutoApprove === false) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
args.push('--dangerously-skip-permissions');
|
|
41
69
|
}
|
|
42
70
|
|
|
43
71
|
module.exports = {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const BaseProvider = require('../base-provider');
|
|
2
|
-
const { getClaudeCommand } = require('../../../lib/settings');
|
|
2
|
+
const { getClaudeCommand, loadSettings } = require('../../../lib/settings');
|
|
3
3
|
const { commandExists, getCommandPath, getHelpOutput } = require('../../../lib/provider-detection');
|
|
4
|
+
const { resolveClaudeAuth } = require('../../../lib/settings/claude-auth');
|
|
4
5
|
const { buildCommand } = require('./cli-builder');
|
|
5
6
|
const { parseEvent } = require('./output-parser');
|
|
6
7
|
const {
|
|
@@ -68,6 +69,16 @@ class AnthropicProvider extends BaseProvider {
|
|
|
68
69
|
return ['~/.claude'];
|
|
69
70
|
}
|
|
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
|
+
|
|
71
82
|
buildCommand(context, options) {
|
|
72
83
|
const { command, args } = getClaudeCommand();
|
|
73
84
|
const cliFeatures = options.cliFeatures || {};
|
|
@@ -97,7 +108,10 @@ class AnthropicProvider extends BaseProvider {
|
|
|
97
108
|
);
|
|
98
109
|
}
|
|
99
110
|
|
|
100
|
-
|
|
111
|
+
const authEnv = this.resolveAuthEnv();
|
|
112
|
+
const resolvedOptions = { ...options, cliFeatures, authEnv };
|
|
113
|
+
|
|
114
|
+
return buildCommand(context, resolvedOptions, { command, args });
|
|
101
115
|
}
|
|
102
116
|
|
|
103
117
|
parseEvent(line) {
|
|
@@ -129,6 +143,62 @@ class AnthropicProvider extends BaseProvider {
|
|
|
129
143
|
warned.add(key);
|
|
130
144
|
console.warn(`⚠️ ${message}`);
|
|
131
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
|
+
}
|
|
132
202
|
}
|
|
133
203
|
|
|
134
204
|
module.exports = AnthropicProvider;
|
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
* Stream JSON Parser for Claude Code output
|
|
3
3
|
*/
|
|
4
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
|
+
|
|
5
27
|
/**
|
|
6
28
|
* Parse a single JSON line and extract displayable content
|
|
7
29
|
* @param {string} line
|
|
@@ -33,20 +55,7 @@ function parseEvent(line) {
|
|
|
33
55
|
}
|
|
34
56
|
|
|
35
57
|
if (event.type === 'result') {
|
|
36
|
-
|
|
37
|
-
return {
|
|
38
|
-
type: 'result',
|
|
39
|
-
success: event.subtype === 'success',
|
|
40
|
-
result: event.result,
|
|
41
|
-
error: event.is_error ? event.result : null,
|
|
42
|
-
cost: event.total_cost_usd,
|
|
43
|
-
duration: event.duration_ms,
|
|
44
|
-
inputTokens: usage.input_tokens || 0,
|
|
45
|
-
outputTokens: usage.output_tokens || 0,
|
|
46
|
-
cacheReadInputTokens: usage.cache_read_input_tokens || 0,
|
|
47
|
-
cacheCreationInputTokens: usage.cache_creation_input_tokens || 0,
|
|
48
|
-
modelUsage: event.modelUsage || null,
|
|
49
|
-
};
|
|
58
|
+
return parseResultEvent(event);
|
|
50
59
|
}
|
|
51
60
|
|
|
52
61
|
if (event.type === 'system') {
|
|
@@ -82,6 +91,15 @@ function parseAssistantMessage(message) {
|
|
|
82
91
|
const results = [];
|
|
83
92
|
|
|
84
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
|
+
|
|
85
103
|
if (block.type === 'tool_use') {
|
|
86
104
|
results.push({
|
|
87
105
|
type: 'tool_call',
|
|
@@ -173,9 +173,79 @@ class BaseProvider {
|
|
|
173
173
|
return modelId;
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
+
/**
|
|
177
|
+
* Resolve a model name to its CLI-compatible identifier.
|
|
178
|
+
* Override in provider implementations that need model ID transformation
|
|
179
|
+
* (e.g., Anthropic Bedrock mapping).
|
|
180
|
+
* @param {string} model - Model name (e.g., 'opus', 'sonnet')
|
|
181
|
+
* @param {Object} _authEnv - Authentication environment variables
|
|
182
|
+
* @returns {string} CLI-compatible model identifier
|
|
183
|
+
*/
|
|
184
|
+
resolveCliModel(model, _authEnv = {}) {
|
|
185
|
+
return model;
|
|
186
|
+
}
|
|
187
|
+
|
|
176
188
|
getDefaultLevel() {
|
|
177
189
|
throw new Error('Not implemented');
|
|
178
190
|
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Get default settings for this provider
|
|
194
|
+
* Override in provider implementations to provide provider-specific defaults
|
|
195
|
+
* @returns {Object} Default settings object
|
|
196
|
+
*/
|
|
197
|
+
getDefaultSettings() {
|
|
198
|
+
return {
|
|
199
|
+
maxLevel: this.getDefaultMaxLevel?.() || 'level3',
|
|
200
|
+
minLevel: this.getDefaultMinLevel?.() || 'level1',
|
|
201
|
+
defaultLevel: this.getDefaultLevel() || 'level2',
|
|
202
|
+
levelOverrides: {},
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Validate provider-specific settings
|
|
208
|
+
* Override in provider implementations to add custom validation
|
|
209
|
+
* @param {Object} settings - Settings object to validate
|
|
210
|
+
* @returns {string|null} Error message if invalid, null if valid
|
|
211
|
+
*/
|
|
212
|
+
validateSettings(settings) {
|
|
213
|
+
if (typeof settings !== 'object' || settings === null) {
|
|
214
|
+
return `providerSettings.${this.name} must be an object`;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Validate level fields
|
|
218
|
+
const levelMapping = this.getLevelMapping();
|
|
219
|
+
const validLevels = Object.keys(levelMapping);
|
|
220
|
+
|
|
221
|
+
if (settings.maxLevel && !validLevels.includes(settings.maxLevel)) {
|
|
222
|
+
return `Invalid maxLevel for ${this.name}: ${settings.maxLevel}`;
|
|
223
|
+
}
|
|
224
|
+
if (settings.minLevel && !validLevels.includes(settings.minLevel)) {
|
|
225
|
+
return `Invalid minLevel for ${this.name}: ${settings.minLevel}`;
|
|
226
|
+
}
|
|
227
|
+
if (settings.defaultLevel && !validLevels.includes(settings.defaultLevel)) {
|
|
228
|
+
return `Invalid defaultLevel for ${this.name}: ${settings.defaultLevel}`;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (
|
|
232
|
+
settings.levelOverrides &&
|
|
233
|
+
(typeof settings.levelOverrides !== 'object' || Array.isArray(settings.levelOverrides))
|
|
234
|
+
) {
|
|
235
|
+
return `levelOverrides for ${this.name} must be an object`;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Get the list of setting field names specific to this provider
|
|
243
|
+
* Override in provider implementations to declare custom fields
|
|
244
|
+
* @returns {string[]} Array of field names
|
|
245
|
+
*/
|
|
246
|
+
getSettingsFields() {
|
|
247
|
+
return ['maxLevel', 'minLevel', 'defaultLevel', 'levelOverrides'];
|
|
248
|
+
}
|
|
179
249
|
}
|
|
180
250
|
|
|
181
251
|
module.exports = BaseProvider;
|
|
@@ -28,6 +28,15 @@ const CAPABILITIES = {
|
|
|
28
28
|
thinkingMode: true,
|
|
29
29
|
reasoningEffort: false,
|
|
30
30
|
},
|
|
31
|
+
opencode: {
|
|
32
|
+
dockerIsolation: true,
|
|
33
|
+
worktreeIsolation: true,
|
|
34
|
+
mcpServers: true,
|
|
35
|
+
jsonSchema: 'experimental',
|
|
36
|
+
streamJson: true,
|
|
37
|
+
thinkingMode: true,
|
|
38
|
+
reasoningEffort: true,
|
|
39
|
+
},
|
|
31
40
|
};
|
|
32
41
|
|
|
33
42
|
function checkCapability(provider, capability) {
|
|
@@ -9,6 +9,47 @@ function normalizeMessageContent(content) {
|
|
|
9
9
|
return '';
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
function parseMessageEvent(event) {
|
|
13
|
+
if (event.role === 'assistant') {
|
|
14
|
+
const text = normalizeMessageContent(event.content);
|
|
15
|
+
if (text) {
|
|
16
|
+
return { type: 'text', text };
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function parseToolUseEvent(event, state) {
|
|
23
|
+
const toolId = event.tool_call_id || event.tool_id || event.id || state.lastToolId;
|
|
24
|
+
const toolName = event.tool_name || event.name;
|
|
25
|
+
state.lastToolId = toolId;
|
|
26
|
+
return {
|
|
27
|
+
type: 'tool_call',
|
|
28
|
+
toolName,
|
|
29
|
+
toolId,
|
|
30
|
+
input: event.parameters || event.input || {},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function parseToolResultEvent(event, state) {
|
|
35
|
+
const toolId = event.tool_call_id || event.tool_id || event.id || state.lastToolId;
|
|
36
|
+
return {
|
|
37
|
+
type: 'tool_result',
|
|
38
|
+
toolId,
|
|
39
|
+
content: event.output ?? event.content ?? '',
|
|
40
|
+
isError: event.success === false,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function parseResultEvent(event) {
|
|
45
|
+
return {
|
|
46
|
+
type: 'result',
|
|
47
|
+
success: event.success !== false,
|
|
48
|
+
result: event.result || '',
|
|
49
|
+
error: event.success === false ? event.error || 'Result failed' : null,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
12
53
|
function parseEvent(line, state = {}, options = {}) {
|
|
13
54
|
let event;
|
|
14
55
|
try {
|
|
@@ -20,46 +61,14 @@ function parseEvent(line, state = {}, options = {}) {
|
|
|
20
61
|
switch (event.type) {
|
|
21
62
|
case 'init':
|
|
22
63
|
return null;
|
|
23
|
-
|
|
24
64
|
case 'message':
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
return null;
|
|
32
|
-
|
|
33
|
-
case 'tool_use': {
|
|
34
|
-
const toolId = event.tool_call_id || event.tool_id || event.id || state.lastToolId;
|
|
35
|
-
const toolName = event.tool_name || event.name;
|
|
36
|
-
state.lastToolId = toolId;
|
|
37
|
-
return {
|
|
38
|
-
type: 'tool_call',
|
|
39
|
-
toolName,
|
|
40
|
-
toolId,
|
|
41
|
-
input: event.parameters || event.input || {},
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
case 'tool_result': {
|
|
46
|
-
const toolId = event.tool_call_id || event.tool_id || event.id || state.lastToolId;
|
|
47
|
-
return {
|
|
48
|
-
type: 'tool_result',
|
|
49
|
-
toolId,
|
|
50
|
-
content: event.output ?? event.content ?? '',
|
|
51
|
-
isError: event.success === false,
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
65
|
+
return parseMessageEvent(event);
|
|
66
|
+
case 'tool_use':
|
|
67
|
+
return parseToolUseEvent(event, state);
|
|
68
|
+
case 'tool_result':
|
|
69
|
+
return parseToolResultEvent(event, state);
|
|
55
70
|
case 'result':
|
|
56
|
-
return
|
|
57
|
-
type: 'result',
|
|
58
|
-
success: event.success !== false,
|
|
59
|
-
result: event.result || '',
|
|
60
|
-
error: event.success === false ? event.error || 'Result failed' : null,
|
|
61
|
-
};
|
|
62
|
-
|
|
71
|
+
return parseResultEvent(event);
|
|
63
72
|
default:
|
|
64
73
|
if (options.onUnknown) {
|
|
65
74
|
options.onUnknown(event.type, event);
|