@mcoda/codali 0.1.87 → 0.1.89
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/dist/cli/EvalCommand.d.ts +8 -0
- package/dist/cli/EvalCommand.d.ts.map +1 -1
- package/dist/cli/EvalCommand.js +93 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1 -0
- package/dist/docdex/DocdexClient.d.ts +8 -1
- package/dist/docdex/DocdexClient.d.ts.map +1 -1
- package/dist/docdex/DocdexClient.js +126 -33
- package/dist/eval/CodaliGatewayLiveHarness.d.ts +169 -0
- package/dist/eval/CodaliGatewayLiveHarness.d.ts.map +1 -0
- package/dist/eval/CodaliGatewayLiveHarness.js +824 -0
- package/dist/eval/GatewayEvalSuite.d.ts +202 -0
- package/dist/eval/GatewayEvalSuite.d.ts.map +1 -0
- package/dist/eval/GatewayEvalSuite.js +673 -0
- package/dist/gateway/AgentTierResolver.d.ts +74 -0
- package/dist/gateway/AgentTierResolver.d.ts.map +1 -0
- package/dist/gateway/AgentTierResolver.js +576 -0
- package/dist/gateway/AppToolGatewayDispatcher.d.ts +88 -0
- package/dist/gateway/AppToolGatewayDispatcher.d.ts.map +1 -0
- package/dist/gateway/AppToolGatewayDispatcher.js +381 -0
- package/dist/gateway/CodaliGateway.d.ts +73 -0
- package/dist/gateway/CodaliGateway.d.ts.map +1 -0
- package/dist/gateway/CodaliGateway.js +824 -0
- package/dist/gateway/CodaliGatewaySchemas.d.ts +21 -0
- package/dist/gateway/CodaliGatewaySchemas.d.ts.map +1 -0
- package/dist/gateway/CodaliGatewaySchemas.js +874 -0
- package/dist/gateway/CodaliGatewayStore.d.ts +157 -0
- package/dist/gateway/CodaliGatewayStore.d.ts.map +1 -0
- package/dist/gateway/CodaliGatewayStore.js +206 -0
- package/dist/gateway/CodaliGatewayTypes.d.ts +336 -0
- package/dist/gateway/CodaliGatewayTypes.d.ts.map +1 -0
- package/dist/gateway/CodaliGatewayTypes.js +1 -0
- package/dist/gateway/ContextPackBuilder.d.ts +43 -0
- package/dist/gateway/ContextPackBuilder.d.ts.map +1 -0
- package/dist/gateway/ContextPackBuilder.js +317 -0
- package/dist/gateway/EvidenceNormalizer.d.ts +42 -0
- package/dist/gateway/EvidenceNormalizer.d.ts.map +1 -0
- package/dist/gateway/EvidenceNormalizer.js +488 -0
- package/dist/gateway/GatewayPlanner.d.ts +195 -0
- package/dist/gateway/GatewayPlanner.d.ts.map +1 -0
- package/dist/gateway/GatewayPlanner.js +379 -0
- package/dist/gateway/GatewayPolicyCompiler.d.ts +30 -0
- package/dist/gateway/GatewayPolicyCompiler.d.ts.map +1 -0
- package/dist/gateway/GatewayPolicyCompiler.js +114 -0
- package/dist/gateway/GatewaySecurityPolicy.d.ts +14 -0
- package/dist/gateway/GatewaySecurityPolicy.d.ts.map +1 -0
- package/dist/gateway/GatewaySecurityPolicy.js +350 -0
- package/dist/gateway/GatewayStateMachine.d.ts +165 -0
- package/dist/gateway/GatewayStateMachine.d.ts.map +1 -0
- package/dist/gateway/GatewayStateMachine.js +790 -0
- package/dist/gateway/GatewayTraceReplay.d.ts +120 -0
- package/dist/gateway/GatewayTraceReplay.d.ts.map +1 -0
- package/dist/gateway/GatewayTraceReplay.js +273 -0
- package/dist/gateway/ToolCapabilityCompiler.d.ts +50 -0
- package/dist/gateway/ToolCapabilityCompiler.d.ts.map +1 -0
- package/dist/gateway/ToolCapabilityCompiler.js +442 -0
- package/dist/index.d.ts +33 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -0
- package/dist/runtime/CodaliJobRuntime.d.ts +211 -0
- package/dist/runtime/CodaliJobRuntime.d.ts.map +1 -0
- package/dist/runtime/CodaliJobRuntime.js +590 -0
- package/dist/runtime/CodaliRuntime.d.ts +81 -1
- package/dist/runtime/CodaliRuntime.d.ts.map +1 -1
- package/dist/runtime/CodaliRuntime.js +619 -4
- package/dist/tools/ToolRegistry.d.ts.map +1 -1
- package/dist/tools/ToolRegistry.js +4 -0
- package/dist/tools/ToolTypes.d.ts +1 -1
- package/dist/tools/ToolTypes.d.ts.map +1 -1
- package/dist/tools/ToolTypes.js +5 -1
- package/package.json +3 -3
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { runCodaliTask, } from "./CodaliRuntime.js";
|
|
3
|
+
const DEFAULT_JOB_TYPE = "codali_job";
|
|
4
|
+
const DEFAULT_MAX_RUNTIME_MS = 3600000;
|
|
5
|
+
const DEFAULT_MAX_FOLLOWUPS = 1;
|
|
6
|
+
const DEFAULT_MAX_PARALLEL_STAGES = 2;
|
|
7
|
+
const DEFAULT_STAGES = [
|
|
8
|
+
{
|
|
9
|
+
id: "router",
|
|
10
|
+
kind: "router",
|
|
11
|
+
goal: "Classify the request, identify the relevant tools, and decide whether clarification is needed.",
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
id: "planner",
|
|
15
|
+
kind: "planner",
|
|
16
|
+
dependsOn: ["router"],
|
|
17
|
+
goal: "Create a concise execution plan grounded in the available context and tool manifest.",
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: "worker",
|
|
21
|
+
kind: "worker",
|
|
22
|
+
dependsOn: ["planner"],
|
|
23
|
+
goal: "Answer the request or produce the requested artifact using only allowed read-only capabilities unless writes are explicitly enabled.",
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: "adjudicator",
|
|
27
|
+
kind: "adjudicator",
|
|
28
|
+
dependsOn: ["worker"],
|
|
29
|
+
goal: "Check whether the worker output follows the plan and policy.",
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: "synthesizer",
|
|
33
|
+
kind: "synthesizer",
|
|
34
|
+
dependsOn: ["worker", "adjudicator"],
|
|
35
|
+
goal: "Produce the final user-facing response from the prior stages.",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: "verifier",
|
|
39
|
+
kind: "verifier",
|
|
40
|
+
dependsOn: ["synthesizer"],
|
|
41
|
+
goal: "Verify the final response. Return JSON with passed, summary, issues, and repairPrompt when possible.",
|
|
42
|
+
response: { format: "json" },
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
const nowIso = () => new Date().toISOString();
|
|
46
|
+
const isRecord = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
47
|
+
const cleanText = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
|
48
|
+
const normalizeDependsOn = (stage) => {
|
|
49
|
+
const value = stage.dependsOn ??
|
|
50
|
+
stage.depends_on ??
|
|
51
|
+
[];
|
|
52
|
+
return Array.isArray(value)
|
|
53
|
+
? value.map((entry) => cleanText(entry)).filter((entry) => Boolean(entry))
|
|
54
|
+
: [];
|
|
55
|
+
};
|
|
56
|
+
const normalizeStage = (stage, index) => {
|
|
57
|
+
const id = cleanText(stage.id) ?? `${stage.kind || "stage"}-${index + 1}`;
|
|
58
|
+
const kind = cleanText(stage.kind) ?? "worker";
|
|
59
|
+
return {
|
|
60
|
+
...stage,
|
|
61
|
+
id: id.replace(/[^a-zA-Z0-9._-]+/g, "_"),
|
|
62
|
+
kind,
|
|
63
|
+
dependsOn: normalizeDependsOn(stage),
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
const normalizeStages = (stages) => (stages?.length ? stages : DEFAULT_STAGES).map(normalizeStage);
|
|
67
|
+
const validateStageDag = (stages) => {
|
|
68
|
+
const ids = new Set();
|
|
69
|
+
for (const stage of stages) {
|
|
70
|
+
if (ids.has(stage.id)) {
|
|
71
|
+
throw new Error(`Duplicate Codali job stage id: ${stage.id}`);
|
|
72
|
+
}
|
|
73
|
+
ids.add(stage.id);
|
|
74
|
+
}
|
|
75
|
+
for (const stage of stages) {
|
|
76
|
+
for (const dependency of stage.dependsOn ?? []) {
|
|
77
|
+
if (!ids.has(dependency)) {
|
|
78
|
+
throw new Error(`Codali job stage ${stage.id} depends on unknown stage ${dependency}`);
|
|
79
|
+
}
|
|
80
|
+
if (dependency === stage.id) {
|
|
81
|
+
throw new Error(`Codali job stage ${stage.id} depends on itself`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const visiting = new Set();
|
|
86
|
+
const visited = new Set();
|
|
87
|
+
const byId = new Map(stages.map((stage) => [stage.id, stage]));
|
|
88
|
+
const visit = (stage) => {
|
|
89
|
+
if (visited.has(stage.id))
|
|
90
|
+
return;
|
|
91
|
+
if (visiting.has(stage.id)) {
|
|
92
|
+
throw new Error(`Codali job stage dependency cycle includes ${stage.id}`);
|
|
93
|
+
}
|
|
94
|
+
visiting.add(stage.id);
|
|
95
|
+
for (const dependency of stage.dependsOn ?? []) {
|
|
96
|
+
const dep = byId.get(dependency);
|
|
97
|
+
if (dep)
|
|
98
|
+
visit(dep);
|
|
99
|
+
}
|
|
100
|
+
visiting.delete(stage.id);
|
|
101
|
+
visited.add(stage.id);
|
|
102
|
+
};
|
|
103
|
+
for (const stage of stages)
|
|
104
|
+
visit(stage);
|
|
105
|
+
};
|
|
106
|
+
const describeUnknown = (value) => {
|
|
107
|
+
if (value === undefined || value === null)
|
|
108
|
+
return "";
|
|
109
|
+
if (typeof value === "string")
|
|
110
|
+
return value;
|
|
111
|
+
try {
|
|
112
|
+
return JSON.stringify(value, null, 2);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return String(value);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
const truncate = (value, max = 6000) => value.length > max ? `${value.slice(0, max)}\n[truncated]` : value;
|
|
119
|
+
const formatPriorStageResults = (results) => {
|
|
120
|
+
if (!results.length)
|
|
121
|
+
return "None yet.";
|
|
122
|
+
return results
|
|
123
|
+
.map((result) => {
|
|
124
|
+
const header = `${result.id} (${result.kind}, ${result.status})`;
|
|
125
|
+
const error = result.error ? `\nError: ${result.error.code}: ${result.error.message}` : "";
|
|
126
|
+
return `### ${header}\n${truncate(result.output || "", 2000)}${error}`;
|
|
127
|
+
})
|
|
128
|
+
.join("\n\n");
|
|
129
|
+
};
|
|
130
|
+
const buildStageTask = (input) => {
|
|
131
|
+
const { request, stage, priorResults, evidence, toolManifest, repairPrompt } = input;
|
|
132
|
+
const lines = [
|
|
133
|
+
"Codali multi-stage job runtime",
|
|
134
|
+
`Job id: ${request.id ?? "unspecified"}`,
|
|
135
|
+
`Job type: ${request.jobType || DEFAULT_JOB_TYPE}`,
|
|
136
|
+
`Stage id: ${stage.id}`,
|
|
137
|
+
`Stage kind: ${stage.kind}`,
|
|
138
|
+
stage.role ? `Stage role: ${stage.role}` : "",
|
|
139
|
+
stage.title ? `Stage title: ${stage.title}` : "",
|
|
140
|
+
stage.goal ? `Stage goal: ${stage.goal}` : "",
|
|
141
|
+
stage.prompt ? `Stage instructions:\n${stage.prompt}` : "",
|
|
142
|
+
repairPrompt ? `Verifier repair request:\n${repairPrompt}` : "",
|
|
143
|
+
"Original job input:",
|
|
144
|
+
truncate(describeUnknown(request.input) || "(no explicit input)"),
|
|
145
|
+
request.context ? `Job context:\n${truncate(describeUnknown(request.context))}` : "",
|
|
146
|
+
request.tenant ? `Tenant scope:\n${truncate(describeUnknown(request.tenant), 2000)}` : "",
|
|
147
|
+
toolManifest ? `Runtime tool manifest:\n${truncate(describeUnknown(toolManifest), 3000)}` : "",
|
|
148
|
+
evidence.length ? `Evidence accumulated:\n${truncate(describeUnknown(evidence), 3000)}` : "",
|
|
149
|
+
"Prior stage results:",
|
|
150
|
+
formatPriorStageResults(priorResults),
|
|
151
|
+
request.response?.requireEvidence
|
|
152
|
+
? "When making factual claims, include evidence cards in a JSON evidence array when the response format allows it."
|
|
153
|
+
: "",
|
|
154
|
+
"Stay within the active runtime policy and tenant/workspace scope.",
|
|
155
|
+
];
|
|
156
|
+
return lines.filter(Boolean).join("\n\n");
|
|
157
|
+
};
|
|
158
|
+
const parseJsonLikeObject = (content) => {
|
|
159
|
+
const trimmed = content.trim();
|
|
160
|
+
if (!trimmed)
|
|
161
|
+
return undefined;
|
|
162
|
+
const fenced = trimmed.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/i);
|
|
163
|
+
const body = fenced?.[1]?.trim() ?? trimmed;
|
|
164
|
+
const tryParse = (value) => {
|
|
165
|
+
try {
|
|
166
|
+
const parsed = JSON.parse(value);
|
|
167
|
+
return isRecord(parsed) ? parsed : undefined;
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
const exact = tryParse(body);
|
|
174
|
+
if (exact)
|
|
175
|
+
return exact;
|
|
176
|
+
const first = body.indexOf("{");
|
|
177
|
+
const last = body.lastIndexOf("}");
|
|
178
|
+
if (first >= 0 && last > first) {
|
|
179
|
+
return tryParse(body.slice(first, last + 1));
|
|
180
|
+
}
|
|
181
|
+
return undefined;
|
|
182
|
+
};
|
|
183
|
+
const normalizeEvidence = (stageId, parsed) => {
|
|
184
|
+
const raw = parsed?.evidence;
|
|
185
|
+
if (!Array.isArray(raw))
|
|
186
|
+
return [];
|
|
187
|
+
return raw.filter(isRecord).map((entry, index) => ({
|
|
188
|
+
id: cleanText(entry.id) ?? `${stageId}-evidence-${index + 1}`,
|
|
189
|
+
stageId,
|
|
190
|
+
source: cleanText(entry.source),
|
|
191
|
+
title: cleanText(entry.title),
|
|
192
|
+
summary: cleanText(entry.summary) ?? cleanText(entry.text) ?? cleanText(entry.content),
|
|
193
|
+
confidence: typeof entry.confidence === "number" ? entry.confidence : undefined,
|
|
194
|
+
metadata: isRecord(entry.metadata) ? entry.metadata : undefined,
|
|
195
|
+
}));
|
|
196
|
+
};
|
|
197
|
+
const normalizeVerifier = (stage, parsed) => {
|
|
198
|
+
if (stage.kind !== "verifier" && parsed?.passed === undefined)
|
|
199
|
+
return undefined;
|
|
200
|
+
const issues = Array.isArray(parsed?.issues)
|
|
201
|
+
? parsed.issues.map((issue) => cleanText(issue)).filter((issue) => Boolean(issue))
|
|
202
|
+
: undefined;
|
|
203
|
+
return {
|
|
204
|
+
passed: parsed?.passed === true,
|
|
205
|
+
summary: cleanText(parsed?.summary) ?? cleanText(parsed?.message),
|
|
206
|
+
issues,
|
|
207
|
+
repairPrompt: cleanText(parsed?.repairPrompt) ?? cleanText(parsed?.repair_prompt),
|
|
208
|
+
metadata: isRecord(parsed?.metadata) ? parsed.metadata : undefined,
|
|
209
|
+
};
|
|
210
|
+
};
|
|
211
|
+
const needsClarification = (parsed) => parsed?.status === "needs_clarification" ||
|
|
212
|
+
parsed?.needsClarification === true ||
|
|
213
|
+
parsed?.needs_clarification === true ||
|
|
214
|
+
typeof parsed?.clarifyingQuestion === "string" ||
|
|
215
|
+
typeof parsed?.clarifying_question === "string";
|
|
216
|
+
const mergeUsage = (left, right) => {
|
|
217
|
+
if (!right)
|
|
218
|
+
return left;
|
|
219
|
+
const inputTokens = (left?.inputTokens ?? 0) + (right.inputTokens ?? 0);
|
|
220
|
+
const outputTokens = (left?.outputTokens ?? 0) + (right.outputTokens ?? 0);
|
|
221
|
+
const totalTokens = (left?.totalTokens ?? 0) + (right.totalTokens ?? right.inputTokens ?? 0) + (right.outputTokens ?? 0);
|
|
222
|
+
return { inputTokens, outputTokens, totalTokens };
|
|
223
|
+
};
|
|
224
|
+
const mergeUnique = (values) => Array.from(new Set(values.flat().filter(Boolean)));
|
|
225
|
+
const resolveStageAgent = (request, runtime, stage) => stage.agent ??
|
|
226
|
+
request.agentPolicy?.stageAgents?.[stage.id] ??
|
|
227
|
+
(stage.role ? request.agentPolicy?.stageAgents?.[stage.role] : undefined) ??
|
|
228
|
+
request.agentPolicy?.stageAgents?.[stage.kind] ??
|
|
229
|
+
request.agentPolicy?.defaultAgent ??
|
|
230
|
+
runtime.agent;
|
|
231
|
+
const resolveStageProvider = (request, runtime, stage) => stage.provider ??
|
|
232
|
+
request.agentPolicy?.stageProviders?.[stage.id] ??
|
|
233
|
+
(stage.role ? request.agentPolicy?.stageProviders?.[stage.role] : undefined) ??
|
|
234
|
+
request.agentPolicy?.stageProviders?.[stage.kind] ??
|
|
235
|
+
request.agentPolicy?.defaultProvider ??
|
|
236
|
+
runtime.provider;
|
|
237
|
+
const responseForStage = (request, runtime, stage) => {
|
|
238
|
+
if (stage.response)
|
|
239
|
+
return stage.response;
|
|
240
|
+
if (stage.outputSchema)
|
|
241
|
+
return { format: "json_schema", schema: stage.outputSchema };
|
|
242
|
+
if (request.response?.format === "json_schema") {
|
|
243
|
+
return { format: "json_schema", schema: request.response.schema };
|
|
244
|
+
}
|
|
245
|
+
if (request.response?.format === "json")
|
|
246
|
+
return { format: "json" };
|
|
247
|
+
return runtime.response;
|
|
248
|
+
};
|
|
249
|
+
const runWithLimit = async (items, maxParallel, worker) => {
|
|
250
|
+
const results = new Array(items.length);
|
|
251
|
+
let next = 0;
|
|
252
|
+
const workers = Array.from({ length: Math.min(Math.max(1, maxParallel), items.length) }, async () => {
|
|
253
|
+
while (next < items.length) {
|
|
254
|
+
const index = next;
|
|
255
|
+
next += 1;
|
|
256
|
+
results[index] = await worker(items[index], index);
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
await Promise.all(workers);
|
|
260
|
+
return results;
|
|
261
|
+
};
|
|
262
|
+
const errorStageResult = (input) => {
|
|
263
|
+
const ended = Date.now();
|
|
264
|
+
const started = Date.parse(input.startedAt);
|
|
265
|
+
return {
|
|
266
|
+
id: input.stage.id,
|
|
267
|
+
kind: input.stage.kind,
|
|
268
|
+
status: "failed",
|
|
269
|
+
attempt: input.attempt,
|
|
270
|
+
output: "",
|
|
271
|
+
messages: [],
|
|
272
|
+
toolCallsExecuted: 0,
|
|
273
|
+
touchedFiles: [],
|
|
274
|
+
warnings: [],
|
|
275
|
+
evidence: [],
|
|
276
|
+
startedAt: input.startedAt,
|
|
277
|
+
endedAt: new Date(ended).toISOString(),
|
|
278
|
+
durationMs: Number.isFinite(started) ? ended - started : 0,
|
|
279
|
+
agentSlug: input.agentSlug,
|
|
280
|
+
model: input.model,
|
|
281
|
+
error: input.error,
|
|
282
|
+
};
|
|
283
|
+
};
|
|
284
|
+
const buildTelemetry = (input) => ({
|
|
285
|
+
runId: input.runId,
|
|
286
|
+
runtime: "codali",
|
|
287
|
+
mode: "job",
|
|
288
|
+
jobId: input.jobId,
|
|
289
|
+
jobType: input.jobType,
|
|
290
|
+
status: input.status,
|
|
291
|
+
stageCount: input.stages.length,
|
|
292
|
+
toolCallCount: input.stages.reduce((sum, stage) => sum + stage.toolCallsExecuted, 0),
|
|
293
|
+
calledTools: mergeUnique(input.stages.map((stage) => stage.telemetry?.calledTools ?? [])),
|
|
294
|
+
consideredTools: mergeUnique(input.stages.map((stage) => stage.telemetry?.consideredTools ?? [])),
|
|
295
|
+
warnings: input.warnings,
|
|
296
|
+
errors: input.errors,
|
|
297
|
+
stages: input.stages.map((stage) => ({
|
|
298
|
+
id: stage.id,
|
|
299
|
+
kind: stage.kind,
|
|
300
|
+
status: stage.status,
|
|
301
|
+
attempt: stage.attempt,
|
|
302
|
+
durationMs: stage.durationMs,
|
|
303
|
+
toolCallsExecuted: stage.toolCallsExecuted,
|
|
304
|
+
agentSlug: stage.agentSlug,
|
|
305
|
+
model: stage.model,
|
|
306
|
+
errorCode: stage.error?.code,
|
|
307
|
+
})),
|
|
308
|
+
});
|
|
309
|
+
const pickFinalOutput = (stages) => {
|
|
310
|
+
for (let index = stages.length - 1; index >= 0; index -= 1) {
|
|
311
|
+
const stage = stages[index];
|
|
312
|
+
if ((stage.kind === "repair" || stage.kind === "synthesizer") && stage.status === "completed" && stage.output) {
|
|
313
|
+
return stage.output;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
for (let index = stages.length - 1; index >= 0; index -= 1) {
|
|
317
|
+
const stage = stages[index];
|
|
318
|
+
if (stage.status === "completed" && stage.output) {
|
|
319
|
+
return stage.output;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return "";
|
|
323
|
+
};
|
|
324
|
+
export const runCodaliJob = async (input) => {
|
|
325
|
+
const jobId = input.request.id ?? input.runtime.metadata?.jobId ?? randomUUID();
|
|
326
|
+
const jobType = input.request.jobType || DEFAULT_JOB_TYPE;
|
|
327
|
+
const runId = input.runtime.metadata?.requestId ?? jobId;
|
|
328
|
+
const runTask = input.runTask ?? runCodaliTask;
|
|
329
|
+
const stages = normalizeStages(input.request.stages);
|
|
330
|
+
validateStageDag(stages);
|
|
331
|
+
const warnings = [];
|
|
332
|
+
const errors = [];
|
|
333
|
+
const events = [];
|
|
334
|
+
const results = [];
|
|
335
|
+
const resultById = new Map();
|
|
336
|
+
const started = Date.now();
|
|
337
|
+
const maxRuntimeMs = input.request.budgets?.maxRuntimeMs ?? input.runtime.policy.timeoutMs ?? DEFAULT_MAX_RUNTIME_MS;
|
|
338
|
+
const deadline = started + maxRuntimeMs;
|
|
339
|
+
const maxParallel = input.request.budgets?.maxParallelStages ?? DEFAULT_MAX_PARALLEL_STAGES;
|
|
340
|
+
let remainingToolCalls = input.request.budgets?.maxToolCalls ?? input.runtime.policy.maxToolCalls;
|
|
341
|
+
let status = "succeeded";
|
|
342
|
+
let usage;
|
|
343
|
+
let stopped = false;
|
|
344
|
+
const emit = async (event) => {
|
|
345
|
+
events.push(event);
|
|
346
|
+
await input.onEvent?.(event);
|
|
347
|
+
};
|
|
348
|
+
await emit({ type: "job_start", runId, jobId, jobType, at: nowIso() });
|
|
349
|
+
const runStage = async (stage, allocatedToolCalls, attempt, repairPrompt) => {
|
|
350
|
+
const stageAgent = resolveStageAgent(input.request, input.runtime, stage);
|
|
351
|
+
const stageProvider = resolveStageProvider(input.request, input.runtime, stage);
|
|
352
|
+
const startedAt = nowIso();
|
|
353
|
+
await emit({ type: "stage_start", runId, jobId, stageId: stage.id, kind: stage.kind, attempt, at: startedAt });
|
|
354
|
+
if (Date.now() >= deadline) {
|
|
355
|
+
const error = { stageId: stage.id, code: "job_timeout", message: "Codali job runtime budget expired." };
|
|
356
|
+
await emit({ type: "stage_error", runId, jobId, kind: stage.kind, ...error, at: nowIso() });
|
|
357
|
+
return errorStageResult({ stage, attempt, startedAt, error, agentSlug: stageAgent?.slug, model: stageProvider.model });
|
|
358
|
+
}
|
|
359
|
+
if (allocatedToolCalls < 0) {
|
|
360
|
+
const error = { stageId: stage.id, code: "tool_budget_exhausted", message: "Codali job tool-call budget is exhausted." };
|
|
361
|
+
await emit({ type: "stage_error", runId, jobId, kind: stage.kind, ...error, at: nowIso() });
|
|
362
|
+
return errorStageResult({ stage, attempt, startedAt, error, agentSlug: stageAgent?.slug, model: stageProvider.model });
|
|
363
|
+
}
|
|
364
|
+
const priorResults = results.slice();
|
|
365
|
+
const task = buildStageTask({
|
|
366
|
+
request: input.request,
|
|
367
|
+
stage,
|
|
368
|
+
priorResults,
|
|
369
|
+
evidence: results.flatMap((result) => result.evidence),
|
|
370
|
+
toolManifest: input.request.toolManifest ?? input.runtime.docdex?.toolManifest,
|
|
371
|
+
repairPrompt,
|
|
372
|
+
});
|
|
373
|
+
const timeoutMs = Math.max(0, Math.min(stage.timeoutMs ?? input.runtime.policy.timeoutMs, deadline - Date.now()));
|
|
374
|
+
const policy = {
|
|
375
|
+
...input.runtime.policy,
|
|
376
|
+
mode: stage.mode ?? input.runtime.policy.mode,
|
|
377
|
+
maxSteps: stage.maxSteps ?? input.runtime.policy.maxSteps,
|
|
378
|
+
maxToolCalls: Math.max(0, Math.min(stage.maxToolCalls ?? allocatedToolCalls, allocatedToolCalls)),
|
|
379
|
+
timeoutMs,
|
|
380
|
+
};
|
|
381
|
+
try {
|
|
382
|
+
const runtimeResult = await runTask({
|
|
383
|
+
...input.runtime,
|
|
384
|
+
task,
|
|
385
|
+
messages: input.runtime.messages,
|
|
386
|
+
provider: stageProvider,
|
|
387
|
+
agent: stageAgent,
|
|
388
|
+
policy,
|
|
389
|
+
response: responseForStage(input.request, input.runtime, stage),
|
|
390
|
+
docdex: {
|
|
391
|
+
...input.runtime.docdex,
|
|
392
|
+
toolManifest: input.request.toolManifest ?? input.runtime.docdex?.toolManifest,
|
|
393
|
+
},
|
|
394
|
+
metadata: {
|
|
395
|
+
...input.runtime.metadata,
|
|
396
|
+
jobId,
|
|
397
|
+
requestId: `${runId}:${stage.id}:attempt-${attempt}`,
|
|
398
|
+
agentSlug: stageAgent?.slug ?? input.runtime.metadata?.agentSlug,
|
|
399
|
+
},
|
|
400
|
+
onEvent: async (event) => {
|
|
401
|
+
await input.runtime.onEvent?.(event);
|
|
402
|
+
await emit({ type: "runtime_event", runId, jobId, stageId: stage.id, event, at: nowIso() });
|
|
403
|
+
},
|
|
404
|
+
});
|
|
405
|
+
const ended = Date.now();
|
|
406
|
+
const parsedOutput = parseJsonLikeObject(runtimeResult.finalMessage);
|
|
407
|
+
const evidence = normalizeEvidence(stage.id, parsedOutput);
|
|
408
|
+
const verifier = normalizeVerifier(stage, parsedOutput);
|
|
409
|
+
const stageWarnings = [...runtimeResult.warnings];
|
|
410
|
+
if (runtimeResult.toolCallsExecuted > policy.maxToolCalls) {
|
|
411
|
+
stageWarnings.push(`Stage ${stage.id} reported ${runtimeResult.toolCallsExecuted} tool calls, above allocated budget ${policy.maxToolCalls}.`);
|
|
412
|
+
}
|
|
413
|
+
const stageResult = {
|
|
414
|
+
id: stage.id,
|
|
415
|
+
kind: stage.kind,
|
|
416
|
+
status: "completed",
|
|
417
|
+
attempt,
|
|
418
|
+
output: runtimeResult.finalMessage,
|
|
419
|
+
parsedOutput,
|
|
420
|
+
usage: runtimeResult.usage,
|
|
421
|
+
messages: runtimeResult.messages,
|
|
422
|
+
toolCallsExecuted: runtimeResult.toolCallsExecuted,
|
|
423
|
+
touchedFiles: runtimeResult.touchedFiles,
|
|
424
|
+
warnings: stageWarnings,
|
|
425
|
+
telemetry: runtimeResult.telemetry,
|
|
426
|
+
evidence,
|
|
427
|
+
verifier,
|
|
428
|
+
startedAt,
|
|
429
|
+
endedAt: new Date(ended).toISOString(),
|
|
430
|
+
durationMs: ended - Date.parse(startedAt),
|
|
431
|
+
agentSlug: stageAgent?.slug,
|
|
432
|
+
model: stageProvider.model,
|
|
433
|
+
metadata: stage.metadata,
|
|
434
|
+
};
|
|
435
|
+
await emit({
|
|
436
|
+
type: "stage_result",
|
|
437
|
+
runId,
|
|
438
|
+
jobId,
|
|
439
|
+
stageId: stage.id,
|
|
440
|
+
kind: stage.kind,
|
|
441
|
+
status: stageResult.status,
|
|
442
|
+
durationMs: stageResult.durationMs,
|
|
443
|
+
toolCallsExecuted: stageResult.toolCallsExecuted,
|
|
444
|
+
at: nowIso(),
|
|
445
|
+
});
|
|
446
|
+
return stageResult;
|
|
447
|
+
}
|
|
448
|
+
catch (error) {
|
|
449
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
450
|
+
const runtimeError = {
|
|
451
|
+
stageId: stage.id,
|
|
452
|
+
code: error instanceof Error ? error.name : "stage_error",
|
|
453
|
+
message,
|
|
454
|
+
};
|
|
455
|
+
await emit({ type: "stage_error", runId, jobId, kind: stage.kind, ...runtimeError, at: nowIso() });
|
|
456
|
+
return errorStageResult({
|
|
457
|
+
stage,
|
|
458
|
+
attempt,
|
|
459
|
+
startedAt,
|
|
460
|
+
error: runtimeError,
|
|
461
|
+
agentSlug: stageAgent?.slug,
|
|
462
|
+
model: stageProvider.model,
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
const pending = new Map(stages.map((stage) => [stage.id, stage]));
|
|
467
|
+
while (pending.size && !stopped) {
|
|
468
|
+
const ready = Array.from(pending.values()).filter((stage) => (stage.dependsOn ?? []).every((dependency) => resultById.has(dependency)));
|
|
469
|
+
if (!ready.length) {
|
|
470
|
+
throw new Error("Codali job stage scheduler stalled; dependency validation may be incomplete.");
|
|
471
|
+
}
|
|
472
|
+
const active = ready.slice(0, Math.max(1, maxParallel));
|
|
473
|
+
const allocations = new Map();
|
|
474
|
+
const requested = active.map((stage) => Math.max(0, stage.maxToolCalls ?? remainingToolCalls));
|
|
475
|
+
const requestedTotal = requested.reduce((sum, value) => sum + value, 0);
|
|
476
|
+
if (requestedTotal <= remainingToolCalls) {
|
|
477
|
+
active.forEach((stage, index) => allocations.set(stage.id, requested[index] ?? 0));
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
let rest = remainingToolCalls;
|
|
481
|
+
active.forEach((stage, index) => {
|
|
482
|
+
const slotsLeft = active.length - index;
|
|
483
|
+
const fairShare = Math.floor(rest / slotsLeft);
|
|
484
|
+
const allocation = Math.min(requested[index] ?? 0, fairShare);
|
|
485
|
+
allocations.set(stage.id, allocation);
|
|
486
|
+
rest -= allocation;
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
const waveResults = await runWithLimit(active, maxParallel, async (stage) => {
|
|
490
|
+
if (remainingToolCalls <= 0 && (stage.maxToolCalls ?? input.runtime.policy.maxToolCalls) > 0) {
|
|
491
|
+
const startedAt = nowIso();
|
|
492
|
+
const error = {
|
|
493
|
+
stageId: stage.id,
|
|
494
|
+
code: "tool_budget_exhausted",
|
|
495
|
+
message: "Codali job tool-call budget is exhausted.",
|
|
496
|
+
};
|
|
497
|
+
return errorStageResult({ stage, attempt: 1, startedAt, error });
|
|
498
|
+
}
|
|
499
|
+
return runStage(stage, allocations.get(stage.id) ?? 0, 1);
|
|
500
|
+
});
|
|
501
|
+
for (const stageResult of waveResults) {
|
|
502
|
+
pending.delete(stageResult.id);
|
|
503
|
+
results.push(stageResult);
|
|
504
|
+
resultById.set(stageResult.id, stageResult);
|
|
505
|
+
usage = mergeUsage(usage, stageResult.usage);
|
|
506
|
+
remainingToolCalls -= stageResult.toolCallsExecuted;
|
|
507
|
+
warnings.push(...stageResult.warnings);
|
|
508
|
+
if (stageResult.error) {
|
|
509
|
+
errors.push(stageResult.error);
|
|
510
|
+
const originalStage = stages.find((stage) => stage.id === stageResult.id);
|
|
511
|
+
if (!originalStage?.optional) {
|
|
512
|
+
status = "failed";
|
|
513
|
+
stopped = true;
|
|
514
|
+
}
|
|
515
|
+
else {
|
|
516
|
+
status = status === "succeeded" ? "partial" : status;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
if (needsClarification(stageResult.parsedOutput)) {
|
|
520
|
+
status = "needs_clarification";
|
|
521
|
+
stopped = true;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
const verifier = results.find((stage) => stage.verifier)?.verifier;
|
|
526
|
+
if (status === "succeeded" && verifier && !verifier.passed) {
|
|
527
|
+
status = "partial";
|
|
528
|
+
warnings.push(verifier.summary ?? "Verifier did not pass the synthesized result.");
|
|
529
|
+
const maxFollowups = input.request.budgets?.maxFollowups ?? DEFAULT_MAX_FOLLOWUPS;
|
|
530
|
+
if (maxFollowups > 0 && remainingToolCalls > 0) {
|
|
531
|
+
const repairStage = {
|
|
532
|
+
id: "repair-1",
|
|
533
|
+
kind: "repair",
|
|
534
|
+
goal: "Repair the synthesized answer using the verifier feedback.",
|
|
535
|
+
prompt: verifier.repairPrompt,
|
|
536
|
+
dependsOn: [],
|
|
537
|
+
maxToolCalls: remainingToolCalls,
|
|
538
|
+
};
|
|
539
|
+
const repairResult = await runStage(repairStage, remainingToolCalls, 1, verifier.repairPrompt);
|
|
540
|
+
results.push(repairResult);
|
|
541
|
+
usage = mergeUsage(usage, repairResult.usage);
|
|
542
|
+
remainingToolCalls -= repairResult.toolCallsExecuted;
|
|
543
|
+
warnings.push(...repairResult.warnings);
|
|
544
|
+
if (repairResult.error) {
|
|
545
|
+
errors.push(repairResult.error);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
if (status !== "failed" && remainingToolCalls < 0) {
|
|
550
|
+
status = "failed";
|
|
551
|
+
errors.push({
|
|
552
|
+
code: "tool_budget_exceeded",
|
|
553
|
+
message: "Codali job exceeded its total tool-call budget.",
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
const output = pickFinalOutput(results);
|
|
557
|
+
const evidence = results.flatMap((stage) => stage.evidence);
|
|
558
|
+
const messages = results.flatMap((stage) => stage.messages);
|
|
559
|
+
const touchedFiles = mergeUnique(results.map((stage) => stage.touchedFiles));
|
|
560
|
+
const toolCallsExecuted = results.reduce((sum, stage) => sum + stage.toolCallsExecuted, 0);
|
|
561
|
+
const telemetry = buildTelemetry({ runId, jobId, jobType, status, stages: results, warnings, errors });
|
|
562
|
+
await emit({
|
|
563
|
+
type: "job_result",
|
|
564
|
+
runId,
|
|
565
|
+
jobId,
|
|
566
|
+
status,
|
|
567
|
+
stageCount: results.length,
|
|
568
|
+
toolCallsExecuted,
|
|
569
|
+
at: nowIso(),
|
|
570
|
+
});
|
|
571
|
+
return {
|
|
572
|
+
output,
|
|
573
|
+
status,
|
|
574
|
+
runId,
|
|
575
|
+
jobId,
|
|
576
|
+
jobType,
|
|
577
|
+
stages: results,
|
|
578
|
+
evidence,
|
|
579
|
+
verifier,
|
|
580
|
+
messages,
|
|
581
|
+
usage,
|
|
582
|
+
toolCallsExecuted,
|
|
583
|
+
touchedFiles,
|
|
584
|
+
warnings,
|
|
585
|
+
errors,
|
|
586
|
+
events,
|
|
587
|
+
telemetry,
|
|
588
|
+
metadata: input.request.metadata,
|
|
589
|
+
};
|
|
590
|
+
};
|