opencode-swarm-plugin 0.1.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/.beads/.local_version +1 -0
- package/.beads/README.md +81 -0
- package/.beads/config.yaml +62 -0
- package/.beads/issues.jsonl +549 -0
- package/.beads/metadata.json +4 -0
- package/.gitattributes +3 -0
- package/Dockerfile +30 -0
- package/README.md +312 -0
- package/bun.lock +212 -0
- package/dist/index.js +14627 -0
- package/dist/plugin.js +14562 -0
- package/docker/agent-mail/Dockerfile +23 -0
- package/docker/agent-mail/__pycache__/server.cpython-314.pyc +0 -0
- package/docker/agent-mail/requirements.txt +3 -0
- package/docker/agent-mail/server.py +879 -0
- package/docker-compose.yml +45 -0
- package/package.json +52 -0
- package/scripts/docker-entrypoint.sh +54 -0
- package/src/agent-mail.integration.test.ts +1321 -0
- package/src/agent-mail.ts +665 -0
- package/src/anti-patterns.ts +430 -0
- package/src/beads.integration.test.ts +688 -0
- package/src/beads.ts +603 -0
- package/src/index.ts +267 -0
- package/src/learning.integration.test.ts +1104 -0
- package/src/learning.ts +438 -0
- package/src/pattern-maturity.ts +487 -0
- package/src/plugin.ts +11 -0
- package/src/schemas/bead.ts +152 -0
- package/src/schemas/evaluation.ts +133 -0
- package/src/schemas/index.test.ts +199 -0
- package/src/schemas/index.ts +77 -0
- package/src/schemas/task.ts +129 -0
- package/src/structured.ts +708 -0
- package/src/swarm.integration.test.ts +763 -0
- package/src/swarm.ts +1411 -0
- package/tsconfig.json +28 -0
- package/vitest.integration.config.ts +13 -0
|
@@ -0,0 +1,708 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured Output Module - Validate and parse agent responses
|
|
3
|
+
*
|
|
4
|
+
* Agents frequently return malformed JSON, especially when streaming
|
|
5
|
+
* or under high load. This module provides robust extraction and
|
|
6
|
+
* validation with detailed error feedback for retry loops.
|
|
7
|
+
*
|
|
8
|
+
* Key patterns:
|
|
9
|
+
* - Multiple JSON extraction strategies (direct, code blocks, brace matching)
|
|
10
|
+
* - Zod schema validation with formatted error messages
|
|
11
|
+
* - Structured error types for programmatic handling
|
|
12
|
+
*/
|
|
13
|
+
import { tool } from "@opencode-ai/plugin";
|
|
14
|
+
import { z, type ZodSchema } from "zod";
|
|
15
|
+
import {
|
|
16
|
+
EvaluationSchema,
|
|
17
|
+
TaskDecompositionSchema,
|
|
18
|
+
BeadTreeSchema,
|
|
19
|
+
ValidationResultSchema,
|
|
20
|
+
type Evaluation,
|
|
21
|
+
type TaskDecomposition,
|
|
22
|
+
type BeadTree,
|
|
23
|
+
type ValidationResult,
|
|
24
|
+
} from "./schemas";
|
|
25
|
+
|
|
26
|
+
// ============================================================================
|
|
27
|
+
// Error Types
|
|
28
|
+
// ============================================================================
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Structured validation error with formatted feedback
|
|
32
|
+
*
|
|
33
|
+
* Contains both raw Zod errors for programmatic access and
|
|
34
|
+
* pre-formatted error bullets suitable for retry prompts.
|
|
35
|
+
*/
|
|
36
|
+
export class StructuredValidationError extends Error {
|
|
37
|
+
public readonly errorBullets: string[];
|
|
38
|
+
|
|
39
|
+
constructor(
|
|
40
|
+
message: string,
|
|
41
|
+
public readonly zodError: z.ZodError | null,
|
|
42
|
+
public readonly rawInput: string,
|
|
43
|
+
public readonly extractionMethod?: string,
|
|
44
|
+
) {
|
|
45
|
+
super(message);
|
|
46
|
+
this.name = "StructuredValidationError";
|
|
47
|
+
this.errorBullets = zodError ? formatZodErrors(zodError) : [message];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Format errors as bullet list for retry prompts
|
|
52
|
+
*/
|
|
53
|
+
toFeedback(): string {
|
|
54
|
+
return this.errorBullets.map((e) => `- ${e}`).join("\n");
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Error when JSON cannot be extracted from text
|
|
60
|
+
*/
|
|
61
|
+
export class JsonExtractionError extends Error {
|
|
62
|
+
constructor(
|
|
63
|
+
message: string,
|
|
64
|
+
public readonly rawInput: string,
|
|
65
|
+
public readonly attemptedStrategies: string[],
|
|
66
|
+
) {
|
|
67
|
+
super(message);
|
|
68
|
+
this.name = "JsonExtractionError";
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ============================================================================
|
|
73
|
+
// Helper Functions
|
|
74
|
+
// ============================================================================
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Format Zod validation errors as readable bullet points
|
|
78
|
+
*
|
|
79
|
+
* @param error - Zod error from schema validation
|
|
80
|
+
* @returns Array of error messages suitable for feedback
|
|
81
|
+
*/
|
|
82
|
+
function formatZodErrors(error: z.ZodError): string[] {
|
|
83
|
+
return error.issues.map((issue) => {
|
|
84
|
+
const path = issue.path.length > 0 ? `${issue.path.join(".")}: ` : "";
|
|
85
|
+
return `${path}${issue.message}`;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Schema registry for named schema lookups
|
|
91
|
+
*/
|
|
92
|
+
const SCHEMA_REGISTRY: Record<string, ZodSchema> = {
|
|
93
|
+
evaluation: EvaluationSchema,
|
|
94
|
+
task_decomposition: TaskDecompositionSchema,
|
|
95
|
+
bead_tree: BeadTreeSchema,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get schema by name from registry
|
|
100
|
+
*/
|
|
101
|
+
function getSchemaByName(name: string): ZodSchema {
|
|
102
|
+
const schema = SCHEMA_REGISTRY[name];
|
|
103
|
+
if (!schema) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
`Unknown schema: ${name}. Available: ${Object.keys(SCHEMA_REGISTRY).join(", ")}`,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
return schema;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Try to extract JSON from text using multiple strategies
|
|
113
|
+
*
|
|
114
|
+
* @param text - Raw text that may contain JSON
|
|
115
|
+
* @returns Tuple of [parsed object, extraction method used]
|
|
116
|
+
* @throws JsonExtractionError if no JSON can be extracted
|
|
117
|
+
*/
|
|
118
|
+
function extractJsonFromText(text: string): [unknown, string] {
|
|
119
|
+
const trimmed = text.trim();
|
|
120
|
+
const strategies: string[] = [];
|
|
121
|
+
|
|
122
|
+
// Strategy 1: Direct parse (entire string is valid JSON)
|
|
123
|
+
strategies.push("direct_parse");
|
|
124
|
+
try {
|
|
125
|
+
return [JSON.parse(trimmed), "direct_parse"];
|
|
126
|
+
} catch {
|
|
127
|
+
// Continue to other strategies
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Strategy 2: Extract from ```json code blocks
|
|
131
|
+
strategies.push("json_code_block");
|
|
132
|
+
const jsonBlockMatch = trimmed.match(/```json\s*([\s\S]*?)```/i);
|
|
133
|
+
if (jsonBlockMatch) {
|
|
134
|
+
try {
|
|
135
|
+
return [JSON.parse(jsonBlockMatch[1].trim()), "json_code_block"];
|
|
136
|
+
} catch {
|
|
137
|
+
// Continue to other strategies
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Strategy 3: Extract from any code block (```...```)
|
|
142
|
+
strategies.push("any_code_block");
|
|
143
|
+
const codeBlockMatch = trimmed.match(/```\s*([\s\S]*?)```/);
|
|
144
|
+
if (codeBlockMatch) {
|
|
145
|
+
try {
|
|
146
|
+
return [JSON.parse(codeBlockMatch[1].trim()), "any_code_block"];
|
|
147
|
+
} catch {
|
|
148
|
+
// Continue to other strategies
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Strategy 4: Find first balanced {...} object
|
|
153
|
+
strategies.push("brace_match_object");
|
|
154
|
+
const objectJson = findBalancedBraces(trimmed, "{", "}");
|
|
155
|
+
if (objectJson) {
|
|
156
|
+
try {
|
|
157
|
+
return [JSON.parse(objectJson), "brace_match_object"];
|
|
158
|
+
} catch {
|
|
159
|
+
// Continue to other strategies
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Strategy 5: Find first balanced [...] array
|
|
164
|
+
strategies.push("brace_match_array");
|
|
165
|
+
const arrayJson = findBalancedBraces(trimmed, "[", "]");
|
|
166
|
+
if (arrayJson) {
|
|
167
|
+
try {
|
|
168
|
+
return [JSON.parse(arrayJson), "brace_match_array"];
|
|
169
|
+
} catch {
|
|
170
|
+
// Continue to other strategies
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Strategy 6: Try to repair common JSON issues and parse
|
|
175
|
+
strategies.push("repair_json");
|
|
176
|
+
const repaired = attemptJsonRepair(trimmed);
|
|
177
|
+
if (repaired !== trimmed) {
|
|
178
|
+
try {
|
|
179
|
+
return [JSON.parse(repaired), "repair_json"];
|
|
180
|
+
} catch {
|
|
181
|
+
// All strategies failed
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
throw new JsonExtractionError(
|
|
186
|
+
"Could not extract valid JSON from response",
|
|
187
|
+
text,
|
|
188
|
+
strategies,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Find a balanced pair of braces/brackets
|
|
194
|
+
*/
|
|
195
|
+
function findBalancedBraces(
|
|
196
|
+
text: string,
|
|
197
|
+
open: string,
|
|
198
|
+
close: string,
|
|
199
|
+
): string | null {
|
|
200
|
+
const startIdx = text.indexOf(open);
|
|
201
|
+
if (startIdx === -1) return null;
|
|
202
|
+
|
|
203
|
+
let depth = 0;
|
|
204
|
+
let inString = false;
|
|
205
|
+
let escapeNext = false;
|
|
206
|
+
|
|
207
|
+
for (let i = startIdx; i < text.length; i++) {
|
|
208
|
+
const char = text[i];
|
|
209
|
+
|
|
210
|
+
if (escapeNext) {
|
|
211
|
+
escapeNext = false;
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (char === "\\") {
|
|
216
|
+
escapeNext = true;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (char === '"') {
|
|
221
|
+
inString = !inString;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (inString) continue;
|
|
226
|
+
|
|
227
|
+
if (char === open) {
|
|
228
|
+
depth++;
|
|
229
|
+
} else if (char === close) {
|
|
230
|
+
depth--;
|
|
231
|
+
if (depth === 0) {
|
|
232
|
+
return text.slice(startIdx, i + 1);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Attempt common JSON repairs
|
|
242
|
+
*
|
|
243
|
+
* Handles:
|
|
244
|
+
* - Trailing commas
|
|
245
|
+
* - Single quotes instead of double quotes
|
|
246
|
+
* - Unquoted keys
|
|
247
|
+
* - Newlines in strings
|
|
248
|
+
*/
|
|
249
|
+
function attemptJsonRepair(text: string): string {
|
|
250
|
+
let repaired = text;
|
|
251
|
+
|
|
252
|
+
// Find JSON-like content first
|
|
253
|
+
const match = repaired.match(/[\[{][\s\S]*[\]}]/);
|
|
254
|
+
if (!match) return text;
|
|
255
|
+
|
|
256
|
+
repaired = match[0];
|
|
257
|
+
|
|
258
|
+
// Replace single quotes with double quotes (but not inside strings)
|
|
259
|
+
// This is a simple heuristic - won't work for all cases
|
|
260
|
+
repaired = repaired.replace(/(?<![\\])'([^']*)'(?=\s*:)/g, '"$1"');
|
|
261
|
+
|
|
262
|
+
// Remove trailing commas before } or ]
|
|
263
|
+
repaired = repaired.replace(/,(\s*[}\]])/g, "$1");
|
|
264
|
+
|
|
265
|
+
// Replace literal newlines in strings with \n
|
|
266
|
+
repaired = repaired.replace(
|
|
267
|
+
/"([^"]*)\n([^"]*)"/g,
|
|
268
|
+
(_, before, after) => `"${before}\\n${after}"`,
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
return repaired;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ============================================================================
|
|
275
|
+
// Validation Result Types
|
|
276
|
+
// ============================================================================
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Result of a structured validation attempt
|
|
280
|
+
*/
|
|
281
|
+
interface StructuredValidationResult<T = unknown> {
|
|
282
|
+
success: boolean;
|
|
283
|
+
data?: T;
|
|
284
|
+
attempts: number;
|
|
285
|
+
errors?: string[];
|
|
286
|
+
extractionMethod?: string;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// ============================================================================
|
|
290
|
+
// Tool Definitions
|
|
291
|
+
// ============================================================================
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Extract JSON from markdown/text response
|
|
295
|
+
*
|
|
296
|
+
* Tries multiple extraction strategies in order:
|
|
297
|
+
* 1. Direct JSON parse
|
|
298
|
+
* 2. ```json code blocks
|
|
299
|
+
* 3. Any code blocks
|
|
300
|
+
* 4. Brace matching for objects
|
|
301
|
+
* 5. Bracket matching for arrays
|
|
302
|
+
* 6. JSON repair attempts
|
|
303
|
+
*/
|
|
304
|
+
export const structured_extract_json = tool({
|
|
305
|
+
description:
|
|
306
|
+
"Extract JSON from markdown/text response. Tries multiple strategies: direct parse, code blocks, brace matching, JSON repair.",
|
|
307
|
+
args: {
|
|
308
|
+
text: tool.schema.string().describe("Text containing JSON to extract"),
|
|
309
|
+
},
|
|
310
|
+
async execute(args, ctx) {
|
|
311
|
+
try {
|
|
312
|
+
const [parsed, method] = extractJsonFromText(args.text);
|
|
313
|
+
return JSON.stringify(
|
|
314
|
+
{
|
|
315
|
+
success: true,
|
|
316
|
+
data: parsed,
|
|
317
|
+
extraction_method: method,
|
|
318
|
+
},
|
|
319
|
+
null,
|
|
320
|
+
2,
|
|
321
|
+
);
|
|
322
|
+
} catch (error) {
|
|
323
|
+
if (error instanceof JsonExtractionError) {
|
|
324
|
+
return JSON.stringify(
|
|
325
|
+
{
|
|
326
|
+
success: false,
|
|
327
|
+
error: error.message,
|
|
328
|
+
attempted_strategies: error.attemptedStrategies,
|
|
329
|
+
raw_input_preview: args.text.slice(0, 200),
|
|
330
|
+
},
|
|
331
|
+
null,
|
|
332
|
+
2,
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
throw error;
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Validate agent response against a named schema
|
|
342
|
+
*
|
|
343
|
+
* Extracts JSON from the response using multiple strategies,
|
|
344
|
+
* then validates against the specified schema.
|
|
345
|
+
*/
|
|
346
|
+
export const structured_validate = tool({
|
|
347
|
+
description:
|
|
348
|
+
"Validate agent response against a schema. Extracts JSON and validates with Zod. Returns structured errors for retry feedback.",
|
|
349
|
+
args: {
|
|
350
|
+
response: tool.schema.string().describe("Agent response to validate"),
|
|
351
|
+
schema_name: tool.schema
|
|
352
|
+
.enum(["evaluation", "task_decomposition", "bead_tree"])
|
|
353
|
+
.describe("Schema to validate against"),
|
|
354
|
+
max_retries: tool.schema
|
|
355
|
+
.number()
|
|
356
|
+
.min(1)
|
|
357
|
+
.max(5)
|
|
358
|
+
.optional()
|
|
359
|
+
.describe("Max retries (for tracking - actual retry logic is external)"),
|
|
360
|
+
},
|
|
361
|
+
async execute(args, ctx) {
|
|
362
|
+
const maxRetries = args.max_retries ?? 3;
|
|
363
|
+
const result: ValidationResult = {
|
|
364
|
+
success: false,
|
|
365
|
+
attempts: 1,
|
|
366
|
+
errors: [],
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
// Step 1: Extract JSON
|
|
370
|
+
let extracted: unknown;
|
|
371
|
+
let extractionMethod: string;
|
|
372
|
+
|
|
373
|
+
try {
|
|
374
|
+
[extracted, extractionMethod] = extractJsonFromText(args.response);
|
|
375
|
+
result.extractionMethod = extractionMethod;
|
|
376
|
+
} catch (error) {
|
|
377
|
+
if (error instanceof JsonExtractionError) {
|
|
378
|
+
result.errors = [
|
|
379
|
+
`JSON extraction failed after trying: ${error.attemptedStrategies.join(", ")}`,
|
|
380
|
+
`Input preview: ${args.response.slice(0, 100)}...`,
|
|
381
|
+
];
|
|
382
|
+
return JSON.stringify(result, null, 2);
|
|
383
|
+
}
|
|
384
|
+
throw error;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Step 2: Validate against schema
|
|
388
|
+
try {
|
|
389
|
+
const schema = getSchemaByName(args.schema_name);
|
|
390
|
+
const validated = schema.parse(extracted);
|
|
391
|
+
|
|
392
|
+
result.success = true;
|
|
393
|
+
result.data = validated;
|
|
394
|
+
delete result.errors;
|
|
395
|
+
|
|
396
|
+
return JSON.stringify(result, null, 2);
|
|
397
|
+
} catch (error) {
|
|
398
|
+
if (error instanceof z.ZodError) {
|
|
399
|
+
const formatted = formatZodErrors(error);
|
|
400
|
+
result.errors = formatted;
|
|
401
|
+
|
|
402
|
+
// Add hint for retries
|
|
403
|
+
if (result.attempts < maxRetries) {
|
|
404
|
+
result.errors.push(
|
|
405
|
+
`\nFix these issues and try again (attempt ${result.attempts}/${maxRetries})`,
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return JSON.stringify(result, null, 2);
|
|
410
|
+
}
|
|
411
|
+
throw error;
|
|
412
|
+
}
|
|
413
|
+
},
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Parse and validate evaluation response from an agent
|
|
418
|
+
*
|
|
419
|
+
* Specialized tool for parsing self-evaluations. Returns
|
|
420
|
+
* the validated Evaluation or structured errors.
|
|
421
|
+
*/
|
|
422
|
+
export const structured_parse_evaluation = tool({
|
|
423
|
+
description:
|
|
424
|
+
"Parse and validate evaluation response from an agent. Uses EvaluationSchema.",
|
|
425
|
+
args: {
|
|
426
|
+
response: tool.schema
|
|
427
|
+
.string()
|
|
428
|
+
.describe("Agent response containing evaluation"),
|
|
429
|
+
},
|
|
430
|
+
async execute(args, ctx) {
|
|
431
|
+
try {
|
|
432
|
+
const [extracted, method] = extractJsonFromText(args.response);
|
|
433
|
+
const validated = EvaluationSchema.parse(extracted) as Evaluation;
|
|
434
|
+
|
|
435
|
+
return JSON.stringify(
|
|
436
|
+
{
|
|
437
|
+
success: true,
|
|
438
|
+
data: validated,
|
|
439
|
+
extraction_method: method,
|
|
440
|
+
summary: {
|
|
441
|
+
passed: validated.passed,
|
|
442
|
+
criteria_count: Object.keys(validated.criteria).length,
|
|
443
|
+
failed_criteria: Object.entries(validated.criteria)
|
|
444
|
+
.filter(([_, v]) => !(v as { passed: boolean }).passed)
|
|
445
|
+
.map(([k]) => k),
|
|
446
|
+
},
|
|
447
|
+
},
|
|
448
|
+
null,
|
|
449
|
+
2,
|
|
450
|
+
);
|
|
451
|
+
} catch (error) {
|
|
452
|
+
if (error instanceof JsonExtractionError) {
|
|
453
|
+
return JSON.stringify(
|
|
454
|
+
{
|
|
455
|
+
success: false,
|
|
456
|
+
error: "Failed to extract JSON from response",
|
|
457
|
+
details: error.message,
|
|
458
|
+
attempted_strategies: error.attemptedStrategies,
|
|
459
|
+
feedback: [
|
|
460
|
+
"- Response must contain valid JSON",
|
|
461
|
+
"- Use ```json code blocks for clarity",
|
|
462
|
+
"- Ensure all braces and brackets are balanced",
|
|
463
|
+
].join("\n"),
|
|
464
|
+
},
|
|
465
|
+
null,
|
|
466
|
+
2,
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (error instanceof z.ZodError) {
|
|
471
|
+
const bullets = formatZodErrors(error);
|
|
472
|
+
return JSON.stringify(
|
|
473
|
+
{
|
|
474
|
+
success: false,
|
|
475
|
+
error: "Evaluation does not match schema",
|
|
476
|
+
validation_errors: bullets,
|
|
477
|
+
feedback: bullets.map((e) => `- ${e}`).join("\n"),
|
|
478
|
+
expected_shape: {
|
|
479
|
+
passed: "boolean",
|
|
480
|
+
criteria: "Record<string, { passed: boolean, feedback: string }>",
|
|
481
|
+
overall_feedback: "string",
|
|
482
|
+
retry_suggestion: "string | null",
|
|
483
|
+
},
|
|
484
|
+
},
|
|
485
|
+
null,
|
|
486
|
+
2,
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
throw error;
|
|
491
|
+
}
|
|
492
|
+
},
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Parse and validate task decomposition response
|
|
497
|
+
*
|
|
498
|
+
* Specialized tool for parsing decomposition results.
|
|
499
|
+
* Validates the structure and returns file lists for reservations.
|
|
500
|
+
*/
|
|
501
|
+
export const structured_parse_decomposition = tool({
|
|
502
|
+
description:
|
|
503
|
+
"Parse and validate task decomposition response. Uses TaskDecompositionSchema. Returns validated decomposition with file lists.",
|
|
504
|
+
args: {
|
|
505
|
+
response: tool.schema
|
|
506
|
+
.string()
|
|
507
|
+
.describe("Agent response containing decomposition"),
|
|
508
|
+
},
|
|
509
|
+
async execute(args, ctx) {
|
|
510
|
+
try {
|
|
511
|
+
const [extracted, method] = extractJsonFromText(args.response);
|
|
512
|
+
const validated = TaskDecompositionSchema.parse(
|
|
513
|
+
extracted,
|
|
514
|
+
) as TaskDecomposition;
|
|
515
|
+
|
|
516
|
+
// Collect all files for reservation planning
|
|
517
|
+
const allFiles = validated.subtasks.flatMap((s) => s.files);
|
|
518
|
+
const uniqueFiles = [...new Set(allFiles)];
|
|
519
|
+
|
|
520
|
+
return JSON.stringify(
|
|
521
|
+
{
|
|
522
|
+
success: true,
|
|
523
|
+
data: validated,
|
|
524
|
+
extraction_method: method,
|
|
525
|
+
summary: {
|
|
526
|
+
task:
|
|
527
|
+
validated.task.slice(0, 50) +
|
|
528
|
+
(validated.task.length > 50 ? "..." : ""),
|
|
529
|
+
subtask_count: validated.subtasks.length,
|
|
530
|
+
dependency_count: validated.dependencies?.length ?? 0,
|
|
531
|
+
total_files: uniqueFiles.length,
|
|
532
|
+
files: uniqueFiles,
|
|
533
|
+
effort_breakdown: validated.subtasks.reduce(
|
|
534
|
+
(acc, s) => {
|
|
535
|
+
acc[s.estimated_effort] = (acc[s.estimated_effort] || 0) + 1;
|
|
536
|
+
return acc;
|
|
537
|
+
},
|
|
538
|
+
{} as Record<string, number>,
|
|
539
|
+
),
|
|
540
|
+
},
|
|
541
|
+
},
|
|
542
|
+
null,
|
|
543
|
+
2,
|
|
544
|
+
);
|
|
545
|
+
} catch (error) {
|
|
546
|
+
if (error instanceof JsonExtractionError) {
|
|
547
|
+
return JSON.stringify(
|
|
548
|
+
{
|
|
549
|
+
success: false,
|
|
550
|
+
error: "Failed to extract JSON from response",
|
|
551
|
+
details: error.message,
|
|
552
|
+
attempted_strategies: error.attemptedStrategies,
|
|
553
|
+
feedback: [
|
|
554
|
+
"- Response must contain valid JSON",
|
|
555
|
+
"- Use ```json code blocks for clarity",
|
|
556
|
+
"- Ensure all braces and brackets are balanced",
|
|
557
|
+
].join("\n"),
|
|
558
|
+
},
|
|
559
|
+
null,
|
|
560
|
+
2,
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
if (error instanceof z.ZodError) {
|
|
565
|
+
const bullets = formatZodErrors(error);
|
|
566
|
+
return JSON.stringify(
|
|
567
|
+
{
|
|
568
|
+
success: false,
|
|
569
|
+
error: "Decomposition does not match schema",
|
|
570
|
+
validation_errors: bullets,
|
|
571
|
+
feedback: bullets.map((e) => `- ${e}`).join("\n"),
|
|
572
|
+
expected_shape: {
|
|
573
|
+
task: "string (original task)",
|
|
574
|
+
reasoning: "string (optional)",
|
|
575
|
+
subtasks: [
|
|
576
|
+
{
|
|
577
|
+
title: "string",
|
|
578
|
+
description: "string",
|
|
579
|
+
files: ["string array of file paths"],
|
|
580
|
+
estimated_effort: "trivial | small | medium | large",
|
|
581
|
+
risks: ["optional string array"],
|
|
582
|
+
},
|
|
583
|
+
],
|
|
584
|
+
dependencies: [
|
|
585
|
+
{
|
|
586
|
+
from: "number (subtask index)",
|
|
587
|
+
to: "number (subtask index)",
|
|
588
|
+
type: "blocks | requires | related",
|
|
589
|
+
},
|
|
590
|
+
],
|
|
591
|
+
},
|
|
592
|
+
},
|
|
593
|
+
null,
|
|
594
|
+
2,
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
throw error;
|
|
599
|
+
}
|
|
600
|
+
},
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Parse and validate a bead tree (epic with subtasks)
|
|
605
|
+
*
|
|
606
|
+
* Validates the structure before creating beads.
|
|
607
|
+
*/
|
|
608
|
+
export const structured_parse_bead_tree = tool({
|
|
609
|
+
description:
|
|
610
|
+
"Parse and validate bead tree response. Uses BeadTreeSchema. Validates before creating epic with subtasks.",
|
|
611
|
+
args: {
|
|
612
|
+
response: tool.schema
|
|
613
|
+
.string()
|
|
614
|
+
.describe("Agent response containing bead tree"),
|
|
615
|
+
},
|
|
616
|
+
async execute(args, ctx) {
|
|
617
|
+
try {
|
|
618
|
+
const [extracted, method] = extractJsonFromText(args.response);
|
|
619
|
+
const validated = BeadTreeSchema.parse(extracted) as BeadTree;
|
|
620
|
+
|
|
621
|
+
// Collect all files for reservation planning
|
|
622
|
+
const allFiles = validated.subtasks.flatMap((s) => s.files);
|
|
623
|
+
const uniqueFiles = [...new Set(allFiles)];
|
|
624
|
+
|
|
625
|
+
return JSON.stringify(
|
|
626
|
+
{
|
|
627
|
+
success: true,
|
|
628
|
+
data: validated,
|
|
629
|
+
extraction_method: method,
|
|
630
|
+
summary: {
|
|
631
|
+
epic_title: validated.epic.title,
|
|
632
|
+
subtask_count: validated.subtasks.length,
|
|
633
|
+
total_files: uniqueFiles.length,
|
|
634
|
+
files: uniqueFiles,
|
|
635
|
+
complexity_total: validated.subtasks.reduce(
|
|
636
|
+
(sum, s) => sum + s.estimated_complexity,
|
|
637
|
+
0,
|
|
638
|
+
),
|
|
639
|
+
},
|
|
640
|
+
},
|
|
641
|
+
null,
|
|
642
|
+
2,
|
|
643
|
+
);
|
|
644
|
+
} catch (error) {
|
|
645
|
+
if (error instanceof JsonExtractionError) {
|
|
646
|
+
return JSON.stringify(
|
|
647
|
+
{
|
|
648
|
+
success: false,
|
|
649
|
+
error: "Failed to extract JSON from response",
|
|
650
|
+
details: error.message,
|
|
651
|
+
feedback: [
|
|
652
|
+
"- Response must contain valid JSON",
|
|
653
|
+
"- Use ```json code blocks for clarity",
|
|
654
|
+
].join("\n"),
|
|
655
|
+
},
|
|
656
|
+
null,
|
|
657
|
+
2,
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
if (error instanceof z.ZodError) {
|
|
662
|
+
const bullets = formatZodErrors(error);
|
|
663
|
+
return JSON.stringify(
|
|
664
|
+
{
|
|
665
|
+
success: false,
|
|
666
|
+
error: "Bead tree does not match schema",
|
|
667
|
+
validation_errors: bullets,
|
|
668
|
+
feedback: bullets.map((e) => `- ${e}`).join("\n"),
|
|
669
|
+
expected_shape: {
|
|
670
|
+
epic: { title: "string", description: "string (optional)" },
|
|
671
|
+
subtasks: [
|
|
672
|
+
{
|
|
673
|
+
title: "string",
|
|
674
|
+
description: "string (optional)",
|
|
675
|
+
files: ["string array"],
|
|
676
|
+
dependencies: ["number array of subtask indices"],
|
|
677
|
+
estimated_complexity: "1-5",
|
|
678
|
+
},
|
|
679
|
+
],
|
|
680
|
+
},
|
|
681
|
+
},
|
|
682
|
+
null,
|
|
683
|
+
2,
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
throw error;
|
|
688
|
+
}
|
|
689
|
+
},
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
// ============================================================================
|
|
693
|
+
// Utility Exports (for use by other modules)
|
|
694
|
+
// ============================================================================
|
|
695
|
+
|
|
696
|
+
export { extractJsonFromText, formatZodErrors, getSchemaByName };
|
|
697
|
+
|
|
698
|
+
// ============================================================================
|
|
699
|
+
// Tool Exports
|
|
700
|
+
// ============================================================================
|
|
701
|
+
|
|
702
|
+
export const structuredTools = {
|
|
703
|
+
"structured_extract_json": structured_extract_json,
|
|
704
|
+
"structured_validate": structured_validate,
|
|
705
|
+
"structured_parse_evaluation": structured_parse_evaluation,
|
|
706
|
+
"structured_parse_decomposition": structured_parse_decomposition,
|
|
707
|
+
"structured_parse_bead_tree": structured_parse_bead_tree,
|
|
708
|
+
};
|