@covibes/zeroshot 5.2.0 → 5.3.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 +178 -186
- package/README.md +199 -248
- package/cli/commands/providers.js +150 -0
- package/cli/index.js +214 -58
- package/cli/lib/first-run.js +40 -3
- package/cluster-templates/base-templates/debug-workflow.json +24 -78
- package/cluster-templates/base-templates/full-workflow.json +44 -145
- package/cluster-templates/base-templates/single-worker.json +23 -15
- package/cluster-templates/base-templates/worker-validator.json +47 -34
- package/cluster-templates/conductor-bootstrap.json +7 -5
- package/lib/docker-config.js +6 -1
- package/lib/provider-detection.js +59 -0
- package/lib/provider-names.js +56 -0
- package/lib/settings.js +191 -6
- package/lib/stream-json-parser.js +4 -238
- package/package.json +21 -5
- package/scripts/validate-templates.js +100 -0
- package/src/agent/agent-config.js +37 -13
- package/src/agent/agent-context-builder.js +64 -2
- package/src/agent/agent-hook-executor.js +82 -9
- package/src/agent/agent-lifecycle.js +53 -14
- package/src/agent/agent-task-executor.js +196 -194
- package/src/agent/output-extraction.js +200 -0
- package/src/agent/output-reformatter.js +175 -0
- package/src/agent/schema-utils.js +111 -0
- package/src/agent-wrapper.js +102 -30
- package/src/agents/git-pusher-agent.json +2 -2
- package/src/claude-task-runner.js +80 -30
- package/src/config-router.js +13 -13
- package/src/config-validator.js +231 -10
- package/src/github.js +36 -0
- package/src/isolation-manager.js +243 -154
- package/src/ledger.js +28 -6
- package/src/orchestrator.js +391 -96
- package/src/preflight.js +85 -82
- package/src/providers/anthropic/cli-builder.js +45 -0
- package/src/providers/anthropic/index.js +134 -0
- package/src/providers/anthropic/models.js +23 -0
- package/src/providers/anthropic/output-parser.js +159 -0
- package/src/providers/base-provider.js +181 -0
- package/src/providers/capabilities.js +51 -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 +92 -0
- package/src/providers/index.js +75 -0
- package/src/providers/openai/cli-builder.js +122 -0
- package/src/providers/openai/index.js +135 -0
- package/src/providers/openai/models.js +21 -0
- package/src/providers/openai/output-parser.js +129 -0
- package/src/sub-cluster-wrapper.js +18 -3
- package/src/task-runner.js +8 -6
- package/src/tui/layout.js +20 -3
- package/task-lib/attachable-watcher.js +80 -78
- package/task-lib/claude-recovery.js +119 -0
- package/task-lib/commands/list.js +1 -1
- package/task-lib/commands/resume.js +3 -2
- package/task-lib/commands/run.js +12 -3
- package/task-lib/runner.js +59 -38
- package/task-lib/scheduler.js +2 -2
- package/task-lib/store.js +43 -30
- package/task-lib/watcher.js +81 -62
package/src/preflight.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Preflight Validation - Check all dependencies before starting
|
|
3
3
|
*
|
|
4
4
|
* Validates:
|
|
5
|
-
* -
|
|
5
|
+
* - Selected provider CLI installed
|
|
6
6
|
* - gh CLI installed and authenticated (if using issue numbers)
|
|
7
7
|
* - Docker available (if using --docker)
|
|
8
8
|
*
|
|
@@ -42,13 +42,15 @@ function formatError(title, detail, recovery) {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
|
-
* Check if a command exists
|
|
45
|
+
* Check if a command exists (cross-platform)
|
|
46
46
|
* @param {string} cmd - Command to check
|
|
47
47
|
* @returns {boolean}
|
|
48
48
|
*/
|
|
49
49
|
function commandExists(cmd) {
|
|
50
50
|
try {
|
|
51
|
-
|
|
51
|
+
// Windows uses 'where', Unix uses 'which'
|
|
52
|
+
const checkCmd = process.platform === 'win32' ? `where ${cmd}` : `which ${cmd}`;
|
|
53
|
+
execSync(checkCmd, { encoding: 'utf8', stdio: 'pipe' });
|
|
52
54
|
return true;
|
|
53
55
|
} catch {
|
|
54
56
|
return false;
|
|
@@ -280,57 +282,45 @@ function checkDocker() {
|
|
|
280
282
|
* @param {boolean} options.requireGit - Whether git repo is required (true if using --worktree)
|
|
281
283
|
* @param {boolean} options.quiet - Suppress success messages
|
|
282
284
|
* @param {string} options.claudeCommand - Custom Claude command (from settings)
|
|
285
|
+
* @param {string} options.provider - Provider override
|
|
283
286
|
* @returns {ValidationResult}
|
|
284
287
|
*/
|
|
285
288
|
function runPreflight(options = {}) {
|
|
286
289
|
const errors = [];
|
|
287
290
|
const warnings = [];
|
|
288
291
|
|
|
289
|
-
|
|
290
|
-
const {
|
|
291
|
-
const
|
|
292
|
-
const
|
|
292
|
+
const { loadSettings, getClaudeCommand } = require('../lib/settings.js');
|
|
293
|
+
const { normalizeProviderName } = require('../lib/provider-names');
|
|
294
|
+
const settings = loadSettings();
|
|
295
|
+
const providerName = normalizeProviderName(
|
|
296
|
+
options.provider || settings.defaultProvider || 'claude'
|
|
297
|
+
);
|
|
293
298
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
claude.error,
|
|
301
|
-
claudeCommand === 'claude'
|
|
302
|
-
? [
|
|
303
|
-
'Install Claude CLI: npm install -g @anthropic-ai/claude-code',
|
|
304
|
-
'Or: brew install claude (macOS)',
|
|
305
|
-
'Then run: claude --version',
|
|
306
|
-
]
|
|
307
|
-
: [
|
|
308
|
-
`Command '${claudeCommand}' not found`,
|
|
309
|
-
'Check settings: zeroshot settings',
|
|
310
|
-
'Update claudeCommand: zeroshot settings set claudeCommand "your-command"',
|
|
311
|
-
'Or install the missing command',
|
|
312
|
-
]
|
|
313
|
-
)
|
|
314
|
-
);
|
|
315
|
-
} else {
|
|
316
|
-
// 2. Check Claude CLI authentication
|
|
317
|
-
const auth = checkClaudeAuth();
|
|
318
|
-
if (!auth.authenticated) {
|
|
299
|
+
if (providerName === 'claude') {
|
|
300
|
+
const { command, args } = getClaudeCommand();
|
|
301
|
+
const claudeCommand = options.claudeCommand || [command, ...args].join(' ');
|
|
302
|
+
|
|
303
|
+
const claude = getClaudeVersion(claudeCommand);
|
|
304
|
+
if (!claude.installed) {
|
|
319
305
|
errors.push(
|
|
320
306
|
formatError(
|
|
321
|
-
'Claude
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
307
|
+
'Claude command not available',
|
|
308
|
+
claude.error,
|
|
309
|
+
claudeCommand === 'claude'
|
|
310
|
+
? [
|
|
311
|
+
'Install Claude CLI: npm install -g @anthropic-ai/claude-code',
|
|
312
|
+
'Or: brew install claude (macOS)',
|
|
313
|
+
'Then run: claude --version',
|
|
314
|
+
]
|
|
315
|
+
: [
|
|
316
|
+
`Command '${claudeCommand}' not found`,
|
|
317
|
+
'Check settings: zeroshot settings',
|
|
318
|
+
'Update claudeCommand: zeroshot settings set claudeCommand "your-command"',
|
|
319
|
+
'Or install the missing command',
|
|
320
|
+
]
|
|
328
321
|
)
|
|
329
322
|
);
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// Check version (warn if old)
|
|
333
|
-
if (claude.version) {
|
|
323
|
+
} else if (claude.version) {
|
|
334
324
|
const [major, minor] = claude.version.split('.').map(Number);
|
|
335
325
|
if (major < 1 || (major === 1 && minor < 0)) {
|
|
336
326
|
warnings.push(
|
|
@@ -338,21 +328,45 @@ function runPreflight(options = {}) {
|
|
|
338
328
|
);
|
|
339
329
|
}
|
|
340
330
|
}
|
|
341
|
-
}
|
|
342
331
|
|
|
343
|
-
|
|
344
|
-
|
|
332
|
+
// Claude CLI refuses --dangerously-skip-permissions when running as root.
|
|
333
|
+
if (process.getuid && process.getuid() === 0) {
|
|
334
|
+
errors.push(
|
|
335
|
+
formatError(
|
|
336
|
+
'Running as root',
|
|
337
|
+
'Claude CLI refuses --dangerously-skip-permissions flag when running as root (UID 0)',
|
|
338
|
+
[
|
|
339
|
+
'Run as non-root user in Docker: docker run --user 1000:1000 ...',
|
|
340
|
+
'Or create non-root user: adduser testuser && su - testuser',
|
|
341
|
+
'Or use existing node user: docker run --user node ...',
|
|
342
|
+
'Security: Claude CLI blocks this flag as root to prevent privilege escalation',
|
|
343
|
+
]
|
|
344
|
+
)
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
} else if (providerName === 'codex') {
|
|
348
|
+
if (!commandExists('codex')) {
|
|
349
|
+
errors.push(
|
|
350
|
+
formatError('Codex CLI not available', 'Command "codex" not installed', [
|
|
351
|
+
'Install Codex CLI: npm install -g @openai/codex',
|
|
352
|
+
'Then run: codex --version',
|
|
353
|
+
])
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
} else if (providerName === 'gemini') {
|
|
357
|
+
if (!commandExists('gemini')) {
|
|
358
|
+
errors.push(
|
|
359
|
+
formatError('Gemini CLI not available', 'Command "gemini" not installed', [
|
|
360
|
+
'Install Gemini CLI: npm install -g @google/gemini-cli',
|
|
361
|
+
'Then run: gemini --version',
|
|
362
|
+
])
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
} else {
|
|
345
366
|
errors.push(
|
|
346
|
-
formatError(
|
|
347
|
-
'
|
|
348
|
-
|
|
349
|
-
[
|
|
350
|
-
'Run as non-root user in Docker: docker run --user 1000:1000 ...',
|
|
351
|
-
'Or create non-root user: adduser testuser && su - testuser',
|
|
352
|
-
'Or use existing node user: docker run --user node ...',
|
|
353
|
-
'Security: Claude CLI blocks this flag as root to prevent privilege escalation',
|
|
354
|
-
]
|
|
355
|
-
)
|
|
367
|
+
formatError('Unknown provider', `Provider "${providerName}" is not supported`, [
|
|
368
|
+
'Use claude, codex, or gemini',
|
|
369
|
+
])
|
|
356
370
|
);
|
|
357
371
|
}
|
|
358
372
|
|
|
@@ -361,26 +375,18 @@ function runPreflight(options = {}) {
|
|
|
361
375
|
const gh = checkGhAuth();
|
|
362
376
|
if (!gh.installed) {
|
|
363
377
|
errors.push(
|
|
364
|
-
formatError(
|
|
365
|
-
'
|
|
366
|
-
'
|
|
367
|
-
|
|
368
|
-
'Install: brew install gh (macOS) or apt install gh (Linux)',
|
|
369
|
-
'Or download from: https://cli.github.com/',
|
|
370
|
-
]
|
|
371
|
-
)
|
|
378
|
+
formatError('GitHub CLI (gh) not installed', 'Required for fetching issues by number', [
|
|
379
|
+
'Install: brew install gh (macOS) or apt install gh (Linux)',
|
|
380
|
+
'Or download from: https://cli.github.com/',
|
|
381
|
+
])
|
|
372
382
|
);
|
|
373
383
|
} else if (!gh.authenticated) {
|
|
374
384
|
errors.push(
|
|
375
|
-
formatError(
|
|
376
|
-
'
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
'Select GitHub.com, HTTPS, and authenticate via browser',
|
|
381
|
-
'Then verify: gh auth status',
|
|
382
|
-
]
|
|
383
|
-
)
|
|
385
|
+
formatError('GitHub CLI (gh) not authenticated', gh.error, [
|
|
386
|
+
'Run: gh auth login',
|
|
387
|
+
'Select GitHub.com, HTTPS, and authenticate via browser',
|
|
388
|
+
'Then verify: gh auth status',
|
|
389
|
+
])
|
|
384
390
|
);
|
|
385
391
|
}
|
|
386
392
|
}
|
|
@@ -415,15 +421,11 @@ function runPreflight(options = {}) {
|
|
|
415
421
|
}
|
|
416
422
|
if (!isGitRepo) {
|
|
417
423
|
errors.push(
|
|
418
|
-
formatError(
|
|
419
|
-
'
|
|
420
|
-
'
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
'Or use --docker instead of --worktree for non-git directories',
|
|
424
|
-
'Initialize a repo with: git init',
|
|
425
|
-
]
|
|
426
|
-
)
|
|
424
|
+
formatError('Not in a git repository', 'Worktree isolation requires a git repository', [
|
|
425
|
+
'Run from within a git repository',
|
|
426
|
+
'Or use --docker instead of --worktree for non-git directories',
|
|
427
|
+
'Initialize a repo with: git init',
|
|
428
|
+
])
|
|
427
429
|
);
|
|
428
430
|
}
|
|
429
431
|
}
|
|
@@ -442,6 +444,7 @@ function runPreflight(options = {}) {
|
|
|
442
444
|
* @param {boolean} options.requireDocker - Whether Docker is required
|
|
443
445
|
* @param {boolean} options.requireGit - Whether git repo is required
|
|
444
446
|
* @param {boolean} options.quiet - Suppress success messages
|
|
447
|
+
* @param {string} options.provider - Provider override
|
|
445
448
|
*/
|
|
446
449
|
function requirePreflight(options = {}) {
|
|
447
450
|
const result = runPreflight(options);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
function buildCommand(context, options = {}, commandConfig = {}) {
|
|
2
|
+
const { modelSpec, outputFormat, jsonSchema, autoApprove, cliFeatures = {} } = options;
|
|
3
|
+
|
|
4
|
+
const command = commandConfig.command || 'claude';
|
|
5
|
+
const extraArgs = commandConfig.args || [];
|
|
6
|
+
const args = [...extraArgs, '--print', '--input-format', 'text'];
|
|
7
|
+
|
|
8
|
+
if (outputFormat && cliFeatures.supportsOutputFormat !== false) {
|
|
9
|
+
args.push('--output-format', outputFormat);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (outputFormat === 'stream-json') {
|
|
13
|
+
if (cliFeatures.supportsVerbose !== false) {
|
|
14
|
+
args.push('--verbose');
|
|
15
|
+
}
|
|
16
|
+
if (cliFeatures.supportsIncludePartials !== false) {
|
|
17
|
+
args.push('--include-partial-messages');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (jsonSchema && outputFormat === 'json' && cliFeatures.supportsJsonSchema !== false) {
|
|
22
|
+
const schemaString = typeof jsonSchema === 'string' ? jsonSchema : JSON.stringify(jsonSchema);
|
|
23
|
+
args.push('--json-schema', schemaString);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (modelSpec?.model && cliFeatures.supportsModel !== false) {
|
|
27
|
+
args.push('--model', modelSpec.model);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (autoApprove && cliFeatures.supportsAutoApprove !== false) {
|
|
31
|
+
args.push('--dangerously-skip-permissions');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
args.push(context);
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
binary: command,
|
|
38
|
+
args,
|
|
39
|
+
env: {},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = {
|
|
44
|
+
buildCommand,
|
|
45
|
+
};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
const BaseProvider = require('../base-provider');
|
|
2
|
+
const { getClaudeCommand } = require('../../../lib/settings');
|
|
3
|
+
const { commandExists, getCommandPath, getHelpOutput } = require('../../../lib/provider-detection');
|
|
4
|
+
const { buildCommand } = require('./cli-builder');
|
|
5
|
+
const { parseEvent } = require('./output-parser');
|
|
6
|
+
const {
|
|
7
|
+
MODEL_CATALOG,
|
|
8
|
+
LEVEL_MAPPING,
|
|
9
|
+
DEFAULT_LEVEL,
|
|
10
|
+
DEFAULT_MAX_LEVEL,
|
|
11
|
+
DEFAULT_MIN_LEVEL,
|
|
12
|
+
} = require('./models');
|
|
13
|
+
|
|
14
|
+
const warned = new Set();
|
|
15
|
+
|
|
16
|
+
class AnthropicProvider extends BaseProvider {
|
|
17
|
+
constructor() {
|
|
18
|
+
super({ name: 'claude', displayName: 'Claude', cliCommand: 'claude' });
|
|
19
|
+
this._cliFeatures = null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// SDK not implemented - uses CLI only
|
|
23
|
+
// See BaseProvider for SDK extension point documentation
|
|
24
|
+
|
|
25
|
+
isAvailable() {
|
|
26
|
+
const { command } = getClaudeCommand();
|
|
27
|
+
return commandExists(command);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
getCliPath() {
|
|
31
|
+
const { command } = getClaudeCommand();
|
|
32
|
+
return getCommandPath(command) || command;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
getInstallInstructions() {
|
|
36
|
+
return ['npm install -g @anthropic-ai/claude-code', 'Or (macOS): brew install claude'].join(
|
|
37
|
+
'\n'
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
getAuthInstructions() {
|
|
42
|
+
return 'claude login';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
getCliFeatures() {
|
|
46
|
+
if (this._cliFeatures) return this._cliFeatures;
|
|
47
|
+
|
|
48
|
+
const { command, args } = getClaudeCommand();
|
|
49
|
+
const help = getHelpOutput(command, args);
|
|
50
|
+
const unknown = !help;
|
|
51
|
+
|
|
52
|
+
const features = {
|
|
53
|
+
supportsOutputFormat: unknown ? true : /--output-format/.test(help),
|
|
54
|
+
supportsStreamJson: unknown ? true : /stream-json/.test(help),
|
|
55
|
+
supportsJsonSchema: unknown ? true : /--json-schema/.test(help),
|
|
56
|
+
supportsAutoApprove: unknown ? true : /--dangerously-skip-permissions/.test(help),
|
|
57
|
+
supportsIncludePartials: unknown ? true : /--include-partial-messages/.test(help),
|
|
58
|
+
supportsVerbose: unknown ? true : /--verbose/.test(help),
|
|
59
|
+
supportsModel: unknown ? true : /--model/.test(help),
|
|
60
|
+
unknown,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
this._cliFeatures = features;
|
|
64
|
+
return features;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
getCredentialPaths() {
|
|
68
|
+
return ['~/.claude'];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
buildCommand(context, options) {
|
|
72
|
+
const { command, args } = getClaudeCommand();
|
|
73
|
+
const cliFeatures = options.cliFeatures || {};
|
|
74
|
+
|
|
75
|
+
if (options.jsonSchema && options.outputFormat !== 'json' && !options.strictSchema) {
|
|
76
|
+
this._warnOnce(
|
|
77
|
+
'claude-jsonschema-stream',
|
|
78
|
+
'jsonSchema requested with stream output; schema enforcement will be post-validated.'
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (
|
|
83
|
+
options.jsonSchema &&
|
|
84
|
+
options.outputFormat === 'json' &&
|
|
85
|
+
cliFeatures.supportsJsonSchema === false
|
|
86
|
+
) {
|
|
87
|
+
this._warnOnce(
|
|
88
|
+
'claude-jsonschema-flag',
|
|
89
|
+
'Claude CLI does not support --json-schema; skipping schema flag.'
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (options.autoApprove && cliFeatures.supportsAutoApprove === false) {
|
|
94
|
+
this._warnOnce(
|
|
95
|
+
'claude-auto-approve',
|
|
96
|
+
'Claude CLI does not support --dangerously-skip-permissions; continuing without auto-approve.'
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return buildCommand(context, { ...options, cliFeatures }, { command, args });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
parseEvent(line) {
|
|
104
|
+
return parseEvent(line);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
getModelCatalog() {
|
|
108
|
+
return MODEL_CATALOG;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
getLevelMapping() {
|
|
112
|
+
return LEVEL_MAPPING;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
getDefaultLevel() {
|
|
116
|
+
return DEFAULT_LEVEL;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
getDefaultMaxLevel() {
|
|
120
|
+
return DEFAULT_MAX_LEVEL;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
getDefaultMinLevel() {
|
|
124
|
+
return DEFAULT_MIN_LEVEL;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
_warnOnce(key, message) {
|
|
128
|
+
if (warned.has(key)) return;
|
|
129
|
+
warned.add(key);
|
|
130
|
+
console.warn(`⚠️ ${message}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
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,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stream JSON Parser for Claude Code output
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Parse a single JSON line and extract displayable content
|
|
7
|
+
* @param {string} line
|
|
8
|
+
* @returns {Object|null}
|
|
9
|
+
*/
|
|
10
|
+
function parseEvent(line) {
|
|
11
|
+
const trimmed = line.trim();
|
|
12
|
+
if (!trimmed || !trimmed.startsWith('{') || !trimmed.endsWith('}')) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let event;
|
|
17
|
+
try {
|
|
18
|
+
event = JSON.parse(trimmed);
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (event.type === 'stream_event' && event.event) {
|
|
24
|
+
return parseStreamEvent(event.event);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (event.type === 'assistant' && event.message?.content) {
|
|
28
|
+
return parseAssistantMessage(event.message);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (event.type === 'user' && event.message?.content) {
|
|
32
|
+
return parseUserMessage(event.message);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (event.type === 'result') {
|
|
36
|
+
const usage = event.usage || {};
|
|
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
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (event.type === 'system') {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function parseStreamEvent(inner) {
|
|
60
|
+
if (inner.type === 'content_block_delta' && inner.delta) {
|
|
61
|
+
const delta = inner.delta;
|
|
62
|
+
|
|
63
|
+
if (delta.type === 'text_delta' && delta.text) {
|
|
64
|
+
return {
|
|
65
|
+
type: 'text',
|
|
66
|
+
text: delta.text,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (delta.type === 'thinking_delta' && delta.thinking) {
|
|
71
|
+
return {
|
|
72
|
+
type: 'thinking',
|
|
73
|
+
text: delta.thinking,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function parseAssistantMessage(message) {
|
|
82
|
+
const results = [];
|
|
83
|
+
|
|
84
|
+
for (const block of message.content) {
|
|
85
|
+
if (block.type === 'tool_use') {
|
|
86
|
+
results.push({
|
|
87
|
+
type: 'tool_call',
|
|
88
|
+
toolName: block.name,
|
|
89
|
+
toolId: block.id,
|
|
90
|
+
input: block.input,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (block.type === 'thinking' && block.thinking) {
|
|
95
|
+
results.push({
|
|
96
|
+
type: 'thinking',
|
|
97
|
+
text: block.thinking,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (results.length === 1) {
|
|
103
|
+
return results[0];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (results.length > 1) {
|
|
107
|
+
return results;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function parseUserMessage(message) {
|
|
114
|
+
const results = [];
|
|
115
|
+
|
|
116
|
+
for (const block of message.content) {
|
|
117
|
+
if (block.type === 'tool_result') {
|
|
118
|
+
results.push({
|
|
119
|
+
type: 'tool_result',
|
|
120
|
+
toolId: block.tool_use_id,
|
|
121
|
+
content: typeof block.content === 'string' ? block.content : JSON.stringify(block.content),
|
|
122
|
+
isError: block.is_error || false,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (results.length === 1) {
|
|
128
|
+
return results[0];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (results.length > 1) {
|
|
132
|
+
return results;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function parseChunk(chunk) {
|
|
139
|
+
const events = [];
|
|
140
|
+
const lines = chunk.split('\n');
|
|
141
|
+
|
|
142
|
+
for (const line of lines) {
|
|
143
|
+
const event = parseEvent(line);
|
|
144
|
+
if (event) {
|
|
145
|
+
if (Array.isArray(event)) {
|
|
146
|
+
events.push(...event);
|
|
147
|
+
} else {
|
|
148
|
+
events.push(event);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return events;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
module.exports = {
|
|
157
|
+
parseEvent,
|
|
158
|
+
parseChunk,
|
|
159
|
+
};
|