@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/providers/index.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
const AnthropicProvider = require('./anthropic');
|
|
2
2
|
const OpenAIProvider = require('./openai');
|
|
3
3
|
const GoogleProvider = require('./google');
|
|
4
|
+
const OpencodeProvider = require('./opencode');
|
|
4
5
|
const { normalizeProviderName } = require('../../lib/provider-names');
|
|
5
6
|
|
|
6
7
|
const PROVIDERS = {
|
|
7
8
|
claude: AnthropicProvider,
|
|
8
9
|
codex: OpenAIProvider,
|
|
9
10
|
gemini: GoogleProvider,
|
|
11
|
+
opencode: OpencodeProvider,
|
|
10
12
|
};
|
|
11
13
|
|
|
12
14
|
function getProvider(name) {
|
|
@@ -35,10 +37,24 @@ function listProviders() {
|
|
|
35
37
|
|
|
36
38
|
function stripTimestampPrefix(line) {
|
|
37
39
|
if (!line || typeof line !== 'string') return '';
|
|
38
|
-
|
|
40
|
+
let trimmed = line.trim().replace(/\r$/, '');
|
|
39
41
|
if (!trimmed) return '';
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
|
|
43
|
+
const tsMatch = trimmed.match(/^\[(\d{13})\](.*)$/);
|
|
44
|
+
if (tsMatch) trimmed = (tsMatch[2] || '').trimStart();
|
|
45
|
+
|
|
46
|
+
// In cluster logs, lines are often prefixed like:
|
|
47
|
+
// "validator | {json...}"
|
|
48
|
+
// Strip the "<agent> | " prefix so provider parsers can JSON.parse the event.
|
|
49
|
+
if (!trimmed.startsWith('{') && !trimmed.startsWith('[')) {
|
|
50
|
+
const pipeMatch = trimmed.match(/^[^|]{1,40}\|\s*(.*)$/);
|
|
51
|
+
if (pipeMatch) {
|
|
52
|
+
const afterPipe = (pipeMatch[1] || '').trimStart();
|
|
53
|
+
if (afterPipe.startsWith('{') || afterPipe.startsWith('[')) return afterPipe;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return trimmed;
|
|
42
58
|
}
|
|
43
59
|
|
|
44
60
|
function parseChunkWithProvider(provider, chunk) {
|
|
@@ -3,10 +3,12 @@ const path = require('path');
|
|
|
3
3
|
const os = require('os');
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* OpenAI structured outputs have strict schema requirements:
|
|
7
|
+
* 1. additionalProperties: false on ALL object schemas
|
|
8
|
+
* 2. 'required' must include ALL keys in 'properties'
|
|
9
|
+
* This function recursively enforces both constraints.
|
|
8
10
|
* @param {Object} schema - JSON Schema object
|
|
9
|
-
* @returns {Object} - Modified schema with
|
|
11
|
+
* @returns {Object} - Modified schema compliant with OpenAI structured output requirements
|
|
10
12
|
*/
|
|
11
13
|
function enforceStrictSchema(schema) {
|
|
12
14
|
if (!schema || typeof schema !== 'object') return schema;
|
|
@@ -14,8 +16,12 @@ function enforceStrictSchema(schema) {
|
|
|
14
16
|
const result = { ...schema };
|
|
15
17
|
|
|
16
18
|
// Add additionalProperties: false to object types
|
|
19
|
+
// OpenAI also requires 'required' to include ALL property keys
|
|
17
20
|
if (result.type === 'object') {
|
|
18
21
|
result.additionalProperties = false;
|
|
22
|
+
if (result.properties) {
|
|
23
|
+
result.required = Object.keys(result.properties);
|
|
24
|
+
}
|
|
19
25
|
}
|
|
20
26
|
|
|
21
27
|
// Recurse into properties
|
|
@@ -72,6 +78,11 @@ function buildCommand(context, options = {}) {
|
|
|
72
78
|
args.push('--dangerously-bypass-approvals-and-sandbox');
|
|
73
79
|
}
|
|
74
80
|
|
|
81
|
+
// Always skip git repo check - zeroshot runs non-interactively and may run in any directory
|
|
82
|
+
if (cliFeatures.supportsSkipGitRepoCheck !== false) {
|
|
83
|
+
args.push('--skip-git-repo-check');
|
|
84
|
+
}
|
|
85
|
+
|
|
75
86
|
// Augment context with schema if CLI doesn't support native --output-schema
|
|
76
87
|
let finalContext = context;
|
|
77
88
|
if (jsonSchema && cliFeatures.supportsOutputSchema) {
|
|
@@ -54,6 +54,7 @@ class OpenAIProvider extends BaseProvider {
|
|
|
54
54
|
supportsCwd: unknown ? true : /\s-C\b/.test(help) || /--cwd\b/.test(help),
|
|
55
55
|
supportsConfigOverride: unknown ? true : /--config\b/.test(help),
|
|
56
56
|
supportsModel: unknown ? true : /\s-m\b/.test(help) || /--model\b/.test(help),
|
|
57
|
+
supportsSkipGitRepoCheck: unknown ? true : /--skip-git-repo-check\b/.test(help),
|
|
57
58
|
unknown,
|
|
58
59
|
};
|
|
59
60
|
|
|
@@ -6,24 +6,53 @@ function safeJsonParse(value, fallback) {
|
|
|
6
6
|
}
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
function parseAssistantMessage(item) {
|
|
10
|
+
const events = [];
|
|
11
|
+
const content = Array.isArray(item.content)
|
|
12
|
+
? item.content
|
|
13
|
+
: [{ type: 'text', text: item.content }];
|
|
14
|
+
const text = content
|
|
15
|
+
.filter((c) => c.type === 'text')
|
|
16
|
+
.map((c) => c.text)
|
|
17
|
+
.join('');
|
|
18
|
+
const thinking = content
|
|
19
|
+
.filter((c) => c.type === 'thinking' || c.type === 'reasoning')
|
|
20
|
+
.map((c) => c.text)
|
|
21
|
+
.join('');
|
|
22
|
+
if (text) events.push({ type: 'text', text });
|
|
23
|
+
if (thinking) events.push({ type: 'thinking', text: thinking });
|
|
24
|
+
return events;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function parseFunctionCall(item) {
|
|
28
|
+
const toolId = item.call_id || item.id || item.tool_call_id || item.tool_id;
|
|
29
|
+
const args =
|
|
30
|
+
typeof item.arguments === 'string' ? safeJsonParse(item.arguments, {}) : item.arguments || {};
|
|
31
|
+
return {
|
|
32
|
+
type: 'tool_call',
|
|
33
|
+
toolName: item.name,
|
|
34
|
+
toolId,
|
|
35
|
+
input: args,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function parseFunctionCallOutput(item) {
|
|
40
|
+
const toolId = item.call_id || item.id || item.tool_call_id || item.tool_id;
|
|
41
|
+
const content = item.output ?? item.result ?? item.content ?? '';
|
|
42
|
+
return {
|
|
43
|
+
type: 'tool_result',
|
|
44
|
+
toolId,
|
|
45
|
+
content,
|
|
46
|
+
isError: !!item.error,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
9
50
|
function parseItem(item) {
|
|
10
51
|
const events = [];
|
|
11
52
|
|
|
12
53
|
// Handle assistant messages (Claude-style: type=message, role=assistant)
|
|
13
54
|
if (item.type === 'message' && item.role === 'assistant') {
|
|
14
|
-
|
|
15
|
-
? item.content
|
|
16
|
-
: [{ type: 'text', text: item.content }];
|
|
17
|
-
const text = content
|
|
18
|
-
.filter((c) => c.type === 'text')
|
|
19
|
-
.map((c) => c.text)
|
|
20
|
-
.join('');
|
|
21
|
-
const thinking = content
|
|
22
|
-
.filter((c) => c.type === 'thinking' || c.type === 'reasoning')
|
|
23
|
-
.map((c) => c.text)
|
|
24
|
-
.join('');
|
|
25
|
-
if (text) events.push({ type: 'text', text });
|
|
26
|
-
if (thinking) events.push({ type: 'thinking', text: thinking });
|
|
55
|
+
events.push(...parseAssistantMessage(item));
|
|
27
56
|
}
|
|
28
57
|
|
|
29
58
|
// Handle agent messages (Codex-style: type=agent_message, text=string)
|
|
@@ -32,26 +61,11 @@ function parseItem(item) {
|
|
|
32
61
|
}
|
|
33
62
|
|
|
34
63
|
if (item.type === 'function_call') {
|
|
35
|
-
|
|
36
|
-
const args =
|
|
37
|
-
typeof item.arguments === 'string' ? safeJsonParse(item.arguments, {}) : item.arguments || {};
|
|
38
|
-
events.push({
|
|
39
|
-
type: 'tool_call',
|
|
40
|
-
toolName: item.name,
|
|
41
|
-
toolId,
|
|
42
|
-
input: args,
|
|
43
|
-
});
|
|
64
|
+
events.push(parseFunctionCall(item));
|
|
44
65
|
}
|
|
45
66
|
|
|
46
67
|
if (item.type === 'function_call_output') {
|
|
47
|
-
|
|
48
|
-
const content = item.output ?? item.result ?? item.content ?? '';
|
|
49
|
-
events.push({
|
|
50
|
-
type: 'tool_result',
|
|
51
|
-
toolId,
|
|
52
|
-
content,
|
|
53
|
-
isError: !!item.error,
|
|
54
|
-
});
|
|
68
|
+
events.push(parseFunctionCallOutput(item));
|
|
55
69
|
}
|
|
56
70
|
|
|
57
71
|
if (events.length === 1) return events[0];
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
function buildCommand(context, options = {}) {
|
|
2
|
+
const { modelSpec, outputFormat, jsonSchema, cwd, cliFeatures = {} } = options;
|
|
3
|
+
|
|
4
|
+
let finalContext = context;
|
|
5
|
+
if (jsonSchema) {
|
|
6
|
+
const schemaStr =
|
|
7
|
+
typeof jsonSchema === 'string' ? jsonSchema : JSON.stringify(jsonSchema, null, 2);
|
|
8
|
+
finalContext =
|
|
9
|
+
context +
|
|
10
|
+
`\n\n## OUTPUT FORMAT (CRITICAL - REQUIRED)\n\nYou MUST respond with a JSON object that exactly matches this schema. NO markdown, NO explanation, NO code blocks. ONLY the raw JSON object.\n\nSchema:\n\`\`\`json\n${schemaStr}\n\`\`\`\n\nYour response must be ONLY valid JSON. Start with { and end with }. Nothing else.`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const args = ['run'];
|
|
14
|
+
|
|
15
|
+
if ((outputFormat === 'stream-json' || outputFormat === 'json') && cliFeatures.supportsJson) {
|
|
16
|
+
args.push('--format', 'json');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (modelSpec?.model) {
|
|
20
|
+
args.push('--model', modelSpec.model);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (modelSpec?.reasoningEffort && cliFeatures.supportsVariant) {
|
|
24
|
+
args.push('--variant', modelSpec.reasoningEffort);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (cwd && cliFeatures.supportsCwd) {
|
|
28
|
+
args.push('--cwd', cwd);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
args.push(finalContext);
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
binary: 'opencode',
|
|
35
|
+
args,
|
|
36
|
+
env: {},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = {
|
|
41
|
+
buildCommand,
|
|
42
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
const BaseProvider = require('../base-provider');
|
|
2
|
+
const { commandExists, getCommandPath, getHelpOutput } = require('../../../lib/provider-detection');
|
|
3
|
+
const { buildCommand } = require('./cli-builder');
|
|
4
|
+
const { parseEvent } = require('./output-parser');
|
|
5
|
+
const {
|
|
6
|
+
MODEL_CATALOG,
|
|
7
|
+
LEVEL_MAPPING,
|
|
8
|
+
DEFAULT_LEVEL,
|
|
9
|
+
DEFAULT_MAX_LEVEL,
|
|
10
|
+
DEFAULT_MIN_LEVEL,
|
|
11
|
+
} = require('./models');
|
|
12
|
+
|
|
13
|
+
const warned = new Set();
|
|
14
|
+
|
|
15
|
+
class OpencodeProvider extends BaseProvider {
|
|
16
|
+
constructor() {
|
|
17
|
+
super({ name: 'opencode', displayName: 'Opencode', cliCommand: 'opencode' });
|
|
18
|
+
this._cliFeatures = null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
isAvailable() {
|
|
22
|
+
return commandExists(this.cliCommand);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
getCliPath() {
|
|
26
|
+
return getCommandPath(this.cliCommand) || this.cliCommand;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
getInstallInstructions() {
|
|
30
|
+
return 'See https://opencode.ai for installation instructions.';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
getAuthInstructions() {
|
|
34
|
+
return 'opencode auth login';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
getCliFeatures() {
|
|
38
|
+
if (this._cliFeatures) return this._cliFeatures;
|
|
39
|
+
const help = getHelpOutput(this.cliCommand, ['run']);
|
|
40
|
+
const unknown = !help;
|
|
41
|
+
|
|
42
|
+
const features = {
|
|
43
|
+
supportsJson: unknown ? true : /--format\b/.test(help),
|
|
44
|
+
supportsModel: unknown ? true : /--model\b/.test(help),
|
|
45
|
+
supportsVariant: unknown ? true : /--variant\b/.test(help),
|
|
46
|
+
supportsCwd: unknown ? false : /--cwd\b/.test(help),
|
|
47
|
+
supportsAutoApprove: false,
|
|
48
|
+
unknown,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
this._cliFeatures = features;
|
|
52
|
+
return features;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
getCredentialPaths() {
|
|
56
|
+
return ['~/.local/share/opencode'];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
buildCommand(context, options) {
|
|
60
|
+
const cliFeatures = options.cliFeatures || {};
|
|
61
|
+
|
|
62
|
+
if (options.modelSpec?.reasoningEffort && cliFeatures.supportsVariant === false) {
|
|
63
|
+
this._warnOnce(
|
|
64
|
+
'opencode-variant',
|
|
65
|
+
'Opencode CLI does not support --variant; skipping reasoningEffort.'
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return buildCommand(context, { ...options, cliFeatures });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
parseEvent(line) {
|
|
73
|
+
return parseEvent(line);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
getModelCatalog() {
|
|
77
|
+
return MODEL_CATALOG;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
getLevelMapping() {
|
|
81
|
+
return LEVEL_MAPPING;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
getDefaultLevel() {
|
|
85
|
+
return DEFAULT_LEVEL;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
getDefaultMaxLevel() {
|
|
89
|
+
return DEFAULT_MAX_LEVEL;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
getDefaultMinLevel() {
|
|
93
|
+
return DEFAULT_MIN_LEVEL;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
_warnOnce(key, message) {
|
|
97
|
+
if (warned.has(key)) return;
|
|
98
|
+
warned.add(key);
|
|
99
|
+
console.warn(`⚠️ ${message}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = OpencodeProvider;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const MODEL_CATALOG = {
|
|
2
|
+
'opencode/big-pickle': { rank: 1 },
|
|
3
|
+
'opencode/glm-4.7-free': { rank: 1 },
|
|
4
|
+
'opencode/gpt-5-nano': { rank: 1 },
|
|
5
|
+
'opencode/grok-code': { rank: 1 },
|
|
6
|
+
'opencode/minimax-m2.1-free': { rank: 1 },
|
|
7
|
+
'google/gemini-1.5-flash': { rank: 1 },
|
|
8
|
+
'google/gemini-1.5-flash-8b': { rank: 1 },
|
|
9
|
+
'google/gemini-1.5-pro': { rank: 1 },
|
|
10
|
+
'google/gemini-2.0-flash': { rank: 1 },
|
|
11
|
+
'google/gemini-2.0-flash-lite': { rank: 1 },
|
|
12
|
+
'google/gemini-2.5-flash': { rank: 1 },
|
|
13
|
+
'google/gemini-2.5-flash-image': { rank: 1 },
|
|
14
|
+
'google/gemini-2.5-flash-image-preview': { rank: 1 },
|
|
15
|
+
'google/gemini-2.5-flash-lite': { rank: 1 },
|
|
16
|
+
'google/gemini-2.5-flash-lite-preview-06-17': { rank: 1 },
|
|
17
|
+
'google/gemini-2.5-flash-lite-preview-09-2025': { rank: 1 },
|
|
18
|
+
'google/gemini-2.5-flash-preview-04-17': { rank: 1 },
|
|
19
|
+
'google/gemini-2.5-flash-preview-05-20': { rank: 1 },
|
|
20
|
+
'google/gemini-2.5-flash-preview-09-2025': { rank: 1 },
|
|
21
|
+
'google/gemini-2.5-flash-preview-tts': { rank: 1 },
|
|
22
|
+
'google/gemini-2.5-pro': { rank: 1 },
|
|
23
|
+
'google/gemini-2.5-pro-preview-05-06': { rank: 1 },
|
|
24
|
+
'google/gemini-2.5-pro-preview-06-05': { rank: 1 },
|
|
25
|
+
'google/gemini-2.5-pro-preview-tts': { rank: 1 },
|
|
26
|
+
'google/gemini-3-flash-preview': { rank: 1 },
|
|
27
|
+
'google/gemini-3-pro-preview': { rank: 1 },
|
|
28
|
+
'google/gemini-embedding-001': { rank: 1 },
|
|
29
|
+
'google/gemini-flash-latest': { rank: 1 },
|
|
30
|
+
'google/gemini-flash-lite-latest': { rank: 1 },
|
|
31
|
+
'google/gemini-live-2.5-flash': { rank: 1 },
|
|
32
|
+
'google/gemini-live-2.5-flash-preview-native-audio': { rank: 1 },
|
|
33
|
+
'openai/gpt-5.1-codex-max': { rank: 1 },
|
|
34
|
+
'openai/gpt-5.1-codex-mini': { rank: 1 },
|
|
35
|
+
'openai/gpt-5.2': { rank: 1 },
|
|
36
|
+
'openai/gpt-5.2-codex': { rank: 1 },
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const LEVEL_MAPPING = {
|
|
40
|
+
level1: { rank: 1, model: null, reasoningEffort: 'low' },
|
|
41
|
+
level2: { rank: 2, model: null, reasoningEffort: 'medium' },
|
|
42
|
+
level3: { rank: 3, model: null, reasoningEffort: 'high' },
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const DEFAULT_LEVEL = 'level2';
|
|
46
|
+
const DEFAULT_MAX_LEVEL = 'level3';
|
|
47
|
+
const DEFAULT_MIN_LEVEL = 'level1';
|
|
48
|
+
|
|
49
|
+
module.exports = {
|
|
50
|
+
MODEL_CATALOG,
|
|
51
|
+
LEVEL_MAPPING,
|
|
52
|
+
DEFAULT_LEVEL,
|
|
53
|
+
DEFAULT_MAX_LEVEL,
|
|
54
|
+
DEFAULT_MIN_LEVEL,
|
|
55
|
+
};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
function parseToolPart(part) {
|
|
2
|
+
const state = part.state || {};
|
|
3
|
+
|
|
4
|
+
if (state.status === 'pending' || state.status === 'running') {
|
|
5
|
+
return {
|
|
6
|
+
type: 'tool_call',
|
|
7
|
+
toolName: part.tool,
|
|
8
|
+
toolId: part.callID,
|
|
9
|
+
input: state.input || {},
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (state.status === 'completed') {
|
|
14
|
+
return {
|
|
15
|
+
type: 'tool_result',
|
|
16
|
+
toolId: part.callID,
|
|
17
|
+
content: state.output || '',
|
|
18
|
+
isError: false,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (state.status === 'error') {
|
|
23
|
+
return {
|
|
24
|
+
type: 'tool_result',
|
|
25
|
+
toolId: part.callID,
|
|
26
|
+
content: state.error || '',
|
|
27
|
+
isError: true,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function parseStepFinish(part) {
|
|
35
|
+
const tokens = part.tokens || {};
|
|
36
|
+
return {
|
|
37
|
+
type: 'result',
|
|
38
|
+
success: true,
|
|
39
|
+
inputTokens: tokens.input || 0,
|
|
40
|
+
outputTokens: tokens.output || 0,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function parsePart(part) {
|
|
45
|
+
if (!part || typeof part !== 'object') return null;
|
|
46
|
+
|
|
47
|
+
if (part.type === 'text' && part.text) {
|
|
48
|
+
return { type: 'text', text: part.text };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (part.type === 'reasoning' && part.text) {
|
|
52
|
+
return { type: 'thinking', text: part.text };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (part.type === 'tool') {
|
|
56
|
+
return parseToolPart(part);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (part.type === 'step-finish') {
|
|
60
|
+
return parseStepFinish(part);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function parseErrorEvent(event) {
|
|
67
|
+
const error = event.error || {};
|
|
68
|
+
return {
|
|
69
|
+
type: 'result',
|
|
70
|
+
success: false,
|
|
71
|
+
error: error.data?.message || error.message || error.name || 'Unknown error',
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function parseEvent(line) {
|
|
76
|
+
let event;
|
|
77
|
+
try {
|
|
78
|
+
event = JSON.parse(line);
|
|
79
|
+
} catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!event || typeof event !== 'object') return null;
|
|
84
|
+
|
|
85
|
+
if (event.type === 'error') {
|
|
86
|
+
return parseErrorEvent(event);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (event.type === 'text' || event.type === 'step_start' || event.type === 'step_finish') {
|
|
90
|
+
return parsePart(event.part || event);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (event.type === 'message.part.updated') {
|
|
94
|
+
return parsePart(event.properties?.part);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function parseChunk(chunk) {
|
|
101
|
+
const events = [];
|
|
102
|
+
const lines = chunk.split('\n');
|
|
103
|
+
|
|
104
|
+
for (const line of lines) {
|
|
105
|
+
if (!line.trim()) continue;
|
|
106
|
+
const event = parseEvent(line);
|
|
107
|
+
if (!event) continue;
|
|
108
|
+
if (Array.isArray(event)) {
|
|
109
|
+
events.push(...event);
|
|
110
|
+
} else {
|
|
111
|
+
events.push(event);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return events;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = {
|
|
119
|
+
parseEvent,
|
|
120
|
+
parseChunk,
|
|
121
|
+
parsePart,
|
|
122
|
+
};
|
|
@@ -30,16 +30,18 @@
|
|
|
30
30
|
* @param {Number} depth - Current nesting depth (for recursion limit)
|
|
31
31
|
* @returns {{ valid: boolean, errors: string[], warnings: string[] }}
|
|
32
32
|
*/
|
|
33
|
+
const MAX_SUBCLUSTER_DEPTH = 5;
|
|
34
|
+
|
|
33
35
|
function validateSubCluster(agentConfig, depth = 0) {
|
|
34
36
|
const errors = [];
|
|
35
37
|
const warnings = [];
|
|
36
38
|
|
|
37
39
|
// Max nesting depth to prevent infinite recursion
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return
|
|
40
|
+
if (depth > MAX_SUBCLUSTER_DEPTH) {
|
|
41
|
+
errors.push(
|
|
42
|
+
`Sub-cluster '${agentConfig.id}' exceeds max nesting depth (${MAX_SUBCLUSTER_DEPTH})`
|
|
43
|
+
);
|
|
44
|
+
return buildValidationResult(errors, warnings);
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
// Validate required fields
|
|
@@ -47,19 +49,44 @@ function validateSubCluster(agentConfig, depth = 0) {
|
|
|
47
49
|
errors.push(`Agent '${agentConfig.id}' must have type: 'subcluster'`);
|
|
48
50
|
}
|
|
49
51
|
|
|
52
|
+
if (!validateSubClusterConfig(agentConfig, depth, errors, warnings)) {
|
|
53
|
+
return buildValidationResult(errors, warnings);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Validate triggers (sub-cluster must have triggers to activate)
|
|
57
|
+
validateSubClusterTriggers(agentConfig, errors);
|
|
58
|
+
|
|
59
|
+
// Validate hooks structure
|
|
60
|
+
validateSubClusterHooks(agentConfig, errors);
|
|
61
|
+
|
|
62
|
+
// Check for context bridging configuration
|
|
63
|
+
validateContextStrategy(agentConfig, errors);
|
|
64
|
+
|
|
65
|
+
return buildValidationResult(errors, warnings);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function buildValidationResult(errors, warnings) {
|
|
69
|
+
return {
|
|
70
|
+
valid: errors.length === 0,
|
|
71
|
+
errors,
|
|
72
|
+
warnings,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function validateSubClusterConfig(agentConfig, depth, errors, warnings) {
|
|
50
77
|
if (!agentConfig.config) {
|
|
51
78
|
errors.push(`Sub-cluster '${agentConfig.id}' missing config field`);
|
|
52
|
-
return
|
|
79
|
+
return false;
|
|
53
80
|
}
|
|
54
81
|
|
|
55
82
|
if (!agentConfig.config.agents || !Array.isArray(agentConfig.config.agents)) {
|
|
56
83
|
errors.push(`Sub-cluster '${agentConfig.id}' config.agents must be an array`);
|
|
57
|
-
return
|
|
84
|
+
return false;
|
|
58
85
|
}
|
|
59
86
|
|
|
60
87
|
if (agentConfig.config.agents.length === 0) {
|
|
61
88
|
errors.push(`Sub-cluster '${agentConfig.id}' config.agents cannot be empty`);
|
|
62
|
-
return
|
|
89
|
+
return false;
|
|
63
90
|
}
|
|
64
91
|
|
|
65
92
|
// Recursively validate nested cluster config
|
|
@@ -71,46 +98,48 @@ function validateSubCluster(agentConfig, depth = 0) {
|
|
|
71
98
|
}
|
|
72
99
|
|
|
73
100
|
warnings.push(...childValidation.warnings.map((w) => `Sub-cluster '${agentConfig.id}': ${w}`));
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
74
103
|
|
|
75
|
-
|
|
104
|
+
function validateSubClusterTriggers(agentConfig, errors) {
|
|
76
105
|
if (!agentConfig.triggers || agentConfig.triggers.length === 0) {
|
|
77
106
|
errors.push(`Sub-cluster '${agentConfig.id}' must have triggers to activate`);
|
|
78
107
|
}
|
|
108
|
+
}
|
|
79
109
|
|
|
80
|
-
|
|
81
|
-
if (agentConfig.hooks) {
|
|
82
|
-
|
|
83
|
-
const hook = agentConfig.hooks.onComplete;
|
|
84
|
-
if (!hook.action) {
|
|
85
|
-
errors.push(`Sub-cluster '${agentConfig.id}' onComplete hook missing action`);
|
|
86
|
-
}
|
|
87
|
-
if (hook.action === 'publish_message' && !hook.config?.topic) {
|
|
88
|
-
errors.push(`Sub-cluster '${agentConfig.id}' onComplete hook missing config.topic`);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
110
|
+
function validateSubClusterHooks(agentConfig, errors) {
|
|
111
|
+
if (!agentConfig.hooks?.onComplete) {
|
|
112
|
+
return;
|
|
91
113
|
}
|
|
92
114
|
|
|
93
|
-
|
|
94
|
-
if (
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
for (const topic of agentConfig.contextStrategy.parentTopics) {
|
|
100
|
-
if (typeof topic !== 'string') {
|
|
101
|
-
errors.push(
|
|
102
|
-
`Sub-cluster '${agentConfig.id}' parentTopics must contain strings, got ${typeof topic}`
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
115
|
+
const hook = agentConfig.hooks.onComplete;
|
|
116
|
+
if (!hook.action) {
|
|
117
|
+
errors.push(`Sub-cluster '${agentConfig.id}' onComplete hook missing action`);
|
|
118
|
+
}
|
|
119
|
+
if (hook.action === 'publish_message' && !hook.config?.topic) {
|
|
120
|
+
errors.push(`Sub-cluster '${agentConfig.id}' onComplete hook missing config.topic`);
|
|
107
121
|
}
|
|
122
|
+
}
|
|
108
123
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
124
|
+
function validateContextStrategy(agentConfig, errors) {
|
|
125
|
+
const parentTopics = agentConfig.contextStrategy?.parentTopics;
|
|
126
|
+
if (!parentTopics) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (!Array.isArray(parentTopics)) {
|
|
131
|
+
errors.push(`Sub-cluster '${agentConfig.id}' contextStrategy.parentTopics must be an array`);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Validate each parent topic is a string
|
|
136
|
+
for (const topic of parentTopics) {
|
|
137
|
+
if (typeof topic !== 'string') {
|
|
138
|
+
errors.push(
|
|
139
|
+
`Sub-cluster '${agentConfig.id}' parentTopics must contain strings, got ${typeof topic}`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
114
143
|
}
|
|
115
144
|
|
|
116
145
|
/**
|