@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
|
@@ -6,12 +6,14 @@ import { OllamaRemoteProvider } from "../providers/OllamaRemoteProvider.js";
|
|
|
6
6
|
import { CodexCliProvider } from "../providers/CodexCliProvider.js";
|
|
7
7
|
import { MswarmWorkerProvider } from "../providers/MswarmWorkerProvider.js";
|
|
8
8
|
import { DocdexClient, normalizeDocdexRuntimeOperation, } from "../docdex/DocdexClient.js";
|
|
9
|
+
import { AppToolGatewayDispatchError, dispatchAppToolGateway, } from "../gateway/AppToolGatewayDispatcher.js";
|
|
9
10
|
import { createDiffTool } from "../tools/diff/DiffTool.js";
|
|
10
11
|
import { createDocdexTools } from "../tools/docdex/DocdexTools.js";
|
|
11
12
|
import { createFileTools } from "../tools/filesystem/FileTools.js";
|
|
12
13
|
import { createSearchTool } from "../tools/search/SearchTool.js";
|
|
13
14
|
import { createShellTool } from "../tools/shell/ShellTool.js";
|
|
14
15
|
import { ToolRegistry } from "../tools/ToolRegistry.js";
|
|
16
|
+
import { ToolExecutionError, } from "../tools/ToolTypes.js";
|
|
15
17
|
import { formatInstructionBlocks, loadInstructionBlocks } from "../session/InstructionLoader.js";
|
|
16
18
|
import { SessionStore } from "../session/SessionStore.js";
|
|
17
19
|
import { SubagentOrchestrator, } from "../subagents/SubagentOrchestrator.js";
|
|
@@ -30,6 +32,33 @@ const WEB_TOOL_NAMES = new Set(["docdex_web_research"]);
|
|
|
30
32
|
const MEMORY_WRITE_TOOL_NAMES = new Set(["docdex_memory_save"]);
|
|
31
33
|
const PROFILE_WRITE_TOOL_NAMES = new Set(["docdex_save_preference"]);
|
|
32
34
|
const INDEX_REBUILD_TOOL_NAMES = new Set(["docdex_index_rebuild", "docdex_index_ingest"]);
|
|
35
|
+
const READ_ONLY_DYNAMIC_BACKING_TOOLS = new Set([
|
|
36
|
+
"docdex_search",
|
|
37
|
+
"docdex_batch_search",
|
|
38
|
+
"docdex_open",
|
|
39
|
+
"docdex_files",
|
|
40
|
+
"docdex_tree",
|
|
41
|
+
"docdex_stats",
|
|
42
|
+
]);
|
|
43
|
+
const FORBIDDEN_DYNAMIC_ARG_KEYS = new Set([
|
|
44
|
+
"authorization",
|
|
45
|
+
"apiKey",
|
|
46
|
+
"api_key",
|
|
47
|
+
"baseUrl",
|
|
48
|
+
"base_url",
|
|
49
|
+
"credential",
|
|
50
|
+
"credentials",
|
|
51
|
+
"credentialSource",
|
|
52
|
+
"credential_source",
|
|
53
|
+
"repoId",
|
|
54
|
+
"repo_id",
|
|
55
|
+
"repoRoot",
|
|
56
|
+
"repo_root",
|
|
57
|
+
"tenantId",
|
|
58
|
+
"tenant_id",
|
|
59
|
+
"workspaceRoot",
|
|
60
|
+
"workspace_root",
|
|
61
|
+
]);
|
|
33
62
|
const DOCDEX_TOOL_OPERATIONS = new Map([
|
|
34
63
|
["docdex_health", ["health"]],
|
|
35
64
|
["docdex_initialize", ["initialize"]],
|
|
@@ -103,6 +132,424 @@ const stripUndefined = (input) => {
|
|
|
103
132
|
const isRecord = (value) => {
|
|
104
133
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
105
134
|
};
|
|
135
|
+
const createDynamicToolRegistryState = () => ({
|
|
136
|
+
consideredTools: [],
|
|
137
|
+
registeredDynamicTools: [],
|
|
138
|
+
skippedDynamicTools: [],
|
|
139
|
+
dynamicToolCalls: [],
|
|
140
|
+
});
|
|
141
|
+
const pushUnique = (target, value) => {
|
|
142
|
+
if (!target.includes(value))
|
|
143
|
+
target.push(value);
|
|
144
|
+
};
|
|
145
|
+
const recordSkippedDynamicTool = (state, name, reason) => {
|
|
146
|
+
if (!state.skippedDynamicTools.some((entry) => entry.name === name && entry.reason === reason)) {
|
|
147
|
+
state.skippedDynamicTools.push({ name, reason });
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
const stringArrayFromUnknown = (value) => {
|
|
151
|
+
if (Array.isArray(value)) {
|
|
152
|
+
return value.filter((entry) => typeof entry === "string" && entry.trim().length > 0);
|
|
153
|
+
}
|
|
154
|
+
if (typeof value === "string" && value.trim()) {
|
|
155
|
+
return [value.trim()];
|
|
156
|
+
}
|
|
157
|
+
return [];
|
|
158
|
+
};
|
|
159
|
+
const toolNameFromRecord = (value) => {
|
|
160
|
+
for (const key of ["name", "tool", "toolName", "tool_name", "id"]) {
|
|
161
|
+
const entry = value[key];
|
|
162
|
+
if (typeof entry === "string" && entry.trim())
|
|
163
|
+
return entry.trim();
|
|
164
|
+
}
|
|
165
|
+
return undefined;
|
|
166
|
+
};
|
|
167
|
+
const toolNamesFromManifestEntries = (entries) => {
|
|
168
|
+
if (!Array.isArray(entries))
|
|
169
|
+
return [];
|
|
170
|
+
const names = [];
|
|
171
|
+
for (const entry of entries) {
|
|
172
|
+
if (typeof entry === "string" && entry.trim()) {
|
|
173
|
+
pushUnique(names, entry.trim());
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (isRecord(entry)) {
|
|
177
|
+
const name = toolNameFromRecord(entry);
|
|
178
|
+
if (name)
|
|
179
|
+
pushUnique(names, name);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return names;
|
|
183
|
+
};
|
|
184
|
+
const toolNamesFromManifest = (manifest) => {
|
|
185
|
+
if (!manifest)
|
|
186
|
+
return [];
|
|
187
|
+
const names = [];
|
|
188
|
+
for (const list of [
|
|
189
|
+
manifest.actualTools,
|
|
190
|
+
manifest.virtualTools,
|
|
191
|
+
manifest.actual_tools,
|
|
192
|
+
manifest.virtual_tools,
|
|
193
|
+
]) {
|
|
194
|
+
for (const name of toolNamesFromManifestEntries(list))
|
|
195
|
+
pushUnique(names, name);
|
|
196
|
+
}
|
|
197
|
+
return names;
|
|
198
|
+
};
|
|
199
|
+
const normalizeRuntimeToolContractEntries = (contracts) => {
|
|
200
|
+
if (!contracts)
|
|
201
|
+
return [];
|
|
202
|
+
if (Array.isArray(contracts)) {
|
|
203
|
+
const entries = [];
|
|
204
|
+
for (const contract of contracts) {
|
|
205
|
+
if (!isRecord(contract))
|
|
206
|
+
continue;
|
|
207
|
+
const name = toolNameFromRecord(contract);
|
|
208
|
+
if (name)
|
|
209
|
+
entries.push([name, contract]);
|
|
210
|
+
}
|
|
211
|
+
return entries;
|
|
212
|
+
}
|
|
213
|
+
if (!isRecord(contracts))
|
|
214
|
+
return [];
|
|
215
|
+
const entries = [];
|
|
216
|
+
for (const [key, value] of Object.entries(contracts)) {
|
|
217
|
+
if (!key.trim())
|
|
218
|
+
continue;
|
|
219
|
+
const contract = isRecord(value)
|
|
220
|
+
? { name: key, ...value }
|
|
221
|
+
: { name: key, metadata: { valueType: typeof value } };
|
|
222
|
+
entries.push([key, contract]);
|
|
223
|
+
}
|
|
224
|
+
return entries;
|
|
225
|
+
};
|
|
226
|
+
const contractString = (contract, camelKey, snakeKey) => {
|
|
227
|
+
const value = contract[camelKey] ?? contract[snakeKey];
|
|
228
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
229
|
+
};
|
|
230
|
+
const contractBoolean = (contract, camelKey, snakeKey) => {
|
|
231
|
+
const value = contract[camelKey] ?? contract[snakeKey];
|
|
232
|
+
return typeof value === "boolean" ? value : undefined;
|
|
233
|
+
};
|
|
234
|
+
const contractStringArray = (contract, camelKey, snakeKey) => {
|
|
235
|
+
return stringArrayFromUnknown(contract[camelKey] ?? contract[snakeKey]);
|
|
236
|
+
};
|
|
237
|
+
const contractCallSchema = (contract) => {
|
|
238
|
+
const schema = contract.callSchema ?? contract.call_schema;
|
|
239
|
+
if (isRecord(schema) && (schema.type === undefined || schema.type === "object")) {
|
|
240
|
+
return {
|
|
241
|
+
...schema,
|
|
242
|
+
type: "object",
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
return { type: "object", additionalProperties: true };
|
|
246
|
+
};
|
|
247
|
+
const findForbiddenDynamicArgKeys = (value, path = "$", matches = []) => {
|
|
248
|
+
if (Array.isArray(value)) {
|
|
249
|
+
value.forEach((entry, index) => findForbiddenDynamicArgKeys(entry, `${path}[${index}]`, matches));
|
|
250
|
+
return matches;
|
|
251
|
+
}
|
|
252
|
+
if (!isRecord(value))
|
|
253
|
+
return matches;
|
|
254
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
255
|
+
const childPath = `${path}.${key}`;
|
|
256
|
+
if (FORBIDDEN_DYNAMIC_ARG_KEYS.has(key)) {
|
|
257
|
+
matches.push(childPath);
|
|
258
|
+
}
|
|
259
|
+
findForbiddenDynamicArgKeys(entry, childPath, matches);
|
|
260
|
+
}
|
|
261
|
+
return matches;
|
|
262
|
+
};
|
|
263
|
+
const assertDynamicArgsStayInScope = (toolName, args) => {
|
|
264
|
+
const forbidden = findForbiddenDynamicArgKeys(args);
|
|
265
|
+
if (forbidden.length) {
|
|
266
|
+
throw new ToolExecutionError("tool_permission_denied", "Dynamic tool arguments cannot override tenant or repo scope", {
|
|
267
|
+
retryable: false,
|
|
268
|
+
details: { tool: toolName, forbidden },
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
const readStringArg = (args, keys) => {
|
|
273
|
+
for (const key of keys) {
|
|
274
|
+
const value = args[key];
|
|
275
|
+
if (typeof value === "string" && value.trim())
|
|
276
|
+
return value.trim();
|
|
277
|
+
}
|
|
278
|
+
return undefined;
|
|
279
|
+
};
|
|
280
|
+
const readStringArrayArg = (args, keys) => {
|
|
281
|
+
for (const key of keys) {
|
|
282
|
+
const values = stringArrayFromUnknown(args[key]);
|
|
283
|
+
if (values.length)
|
|
284
|
+
return values;
|
|
285
|
+
}
|
|
286
|
+
return [];
|
|
287
|
+
};
|
|
288
|
+
const readNumberArg = (args, keys) => {
|
|
289
|
+
for (const key of keys) {
|
|
290
|
+
const value = args[key];
|
|
291
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
292
|
+
return value;
|
|
293
|
+
}
|
|
294
|
+
return undefined;
|
|
295
|
+
};
|
|
296
|
+
const readBooleanArg = (args, keys) => {
|
|
297
|
+
for (const key of keys) {
|
|
298
|
+
const value = args[key];
|
|
299
|
+
if (typeof value === "boolean")
|
|
300
|
+
return value;
|
|
301
|
+
}
|
|
302
|
+
return undefined;
|
|
303
|
+
};
|
|
304
|
+
const selectDocdexBackingCall = (toolName, backingTools, args) => {
|
|
305
|
+
const record = isRecord(args) ? args : {};
|
|
306
|
+
const limit = readNumberArg(record, ["limit", "maxResults", "max_results"]);
|
|
307
|
+
const queries = readStringArrayArg(record, ["queries"]);
|
|
308
|
+
if (queries.length && backingTools.includes("docdex_batch_search")) {
|
|
309
|
+
return {
|
|
310
|
+
toolName: "docdex_batch_search",
|
|
311
|
+
args: stripUndefined({
|
|
312
|
+
queries,
|
|
313
|
+
limit,
|
|
314
|
+
includeLibs: readBooleanArg(record, ["includeLibs", "include_libs"]),
|
|
315
|
+
}),
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
const query = readStringArg(record, ["query", "q", "search", "question", "prompt", "text"]);
|
|
319
|
+
if (query && backingTools.includes("docdex_search")) {
|
|
320
|
+
return { toolName: "docdex_search", args: stripUndefined({ query, limit }) };
|
|
321
|
+
}
|
|
322
|
+
const path = readStringArg(record, ["path", "relPath", "rel_path", "file", "sourcePath", "source_path"]);
|
|
323
|
+
const docId = readStringArg(record, ["docId", "doc_id"]);
|
|
324
|
+
if ((path || docId) && backingTools.includes("docdex_open")) {
|
|
325
|
+
return {
|
|
326
|
+
toolName: "docdex_open",
|
|
327
|
+
args: stripUndefined({
|
|
328
|
+
path,
|
|
329
|
+
docId,
|
|
330
|
+
window: readNumberArg(record, ["window"]),
|
|
331
|
+
textOnly: readBooleanArg(record, ["textOnly", "text_only"]),
|
|
332
|
+
startLine: readNumberArg(record, ["startLine", "start_line"]),
|
|
333
|
+
endLine: readNumberArg(record, ["endLine", "end_line"]),
|
|
334
|
+
head: readNumberArg(record, ["head"]),
|
|
335
|
+
clamp: readBooleanArg(record, ["clamp"]),
|
|
336
|
+
}),
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
if (backingTools.includes("docdex_tree")) {
|
|
340
|
+
return {
|
|
341
|
+
toolName: "docdex_tree",
|
|
342
|
+
args: stripUndefined({
|
|
343
|
+
path,
|
|
344
|
+
maxDepth: readNumberArg(record, ["maxDepth", "max_depth"]),
|
|
345
|
+
dirsOnly: readBooleanArg(record, ["dirsOnly", "dirs_only"]),
|
|
346
|
+
includeHidden: readBooleanArg(record, ["includeHidden", "include_hidden"]),
|
|
347
|
+
}),
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
if (backingTools.includes("docdex_files")) {
|
|
351
|
+
return {
|
|
352
|
+
toolName: "docdex_files",
|
|
353
|
+
args: stripUndefined({
|
|
354
|
+
limit,
|
|
355
|
+
offset: readNumberArg(record, ["offset"]),
|
|
356
|
+
}),
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
if (backingTools.includes("docdex_stats")) {
|
|
360
|
+
return { toolName: "docdex_stats", args: {} };
|
|
361
|
+
}
|
|
362
|
+
throw new ToolExecutionError("tool_invalid_args", "Dynamic tool arguments do not match its read-only backing tools", {
|
|
363
|
+
retryable: false,
|
|
364
|
+
details: { tool: toolName, backingTools },
|
|
365
|
+
});
|
|
366
|
+
};
|
|
367
|
+
const createDocdexBackedDynamicTool = (options) => {
|
|
368
|
+
const { name, contract, registry, backingTools, state } = options;
|
|
369
|
+
const executionMode = contractString(contract, "executionMode", "execution_mode") ?? "server_supplied_snapshot_plus_docdex";
|
|
370
|
+
return {
|
|
371
|
+
name,
|
|
372
|
+
description: contract.description ??
|
|
373
|
+
`Read-only runtime tool. Uses ${backingTools.join(", ")} and returns ${contract.resultContract ?? contract.result_contract ?? "contracted app context"}.`,
|
|
374
|
+
inputSchema: contractCallSchema(contract),
|
|
375
|
+
handler: async (args, context) => {
|
|
376
|
+
const startedAt = Date.now();
|
|
377
|
+
let backingTool;
|
|
378
|
+
let recorded = false;
|
|
379
|
+
try {
|
|
380
|
+
assertDynamicArgsStayInScope(name, args);
|
|
381
|
+
const backingCall = selectDocdexBackingCall(name, backingTools, args);
|
|
382
|
+
backingTool = backingCall.toolName;
|
|
383
|
+
const result = await registry.execute(backingCall.toolName, backingCall.args, context);
|
|
384
|
+
if (!result.ok) {
|
|
385
|
+
state.dynamicToolCalls.push({
|
|
386
|
+
name,
|
|
387
|
+
backingTool,
|
|
388
|
+
status: "failed",
|
|
389
|
+
latencyMs: Date.now() - startedAt,
|
|
390
|
+
errorCode: result.error?.code,
|
|
391
|
+
errorMessage: result.error?.message,
|
|
392
|
+
});
|
|
393
|
+
recorded = true;
|
|
394
|
+
throw new ToolExecutionError(result.error?.code ?? "tool_execution_failed", result.error?.message ?? "Backing tool failed", {
|
|
395
|
+
retryable: result.error?.retryable,
|
|
396
|
+
details: { tool: name, backingTool, backingError: result.error?.details },
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
const data = {
|
|
400
|
+
tool: name,
|
|
401
|
+
executionMode,
|
|
402
|
+
resultContract: contract.resultContract ?? contract.result_contract,
|
|
403
|
+
resultSources: contractStringArray(contract, "resultSources", "result_sources"),
|
|
404
|
+
sourcePaths: contractStringArray(contract, "sourcePaths", "source_paths"),
|
|
405
|
+
sourceTypes: contractStringArray(contract, "sourceTypes", "source_types"),
|
|
406
|
+
suppliedSnapshots: contractStringArray(contract, "suppliedSnapshots", "supplied_snapshots"),
|
|
407
|
+
backingTool,
|
|
408
|
+
result: payloadForToolResult(result),
|
|
409
|
+
};
|
|
410
|
+
state.dynamicToolCalls.push({
|
|
411
|
+
name,
|
|
412
|
+
backingTool,
|
|
413
|
+
status: "success",
|
|
414
|
+
latencyMs: Date.now() - startedAt,
|
|
415
|
+
});
|
|
416
|
+
recorded = true;
|
|
417
|
+
return {
|
|
418
|
+
output: JSON.stringify(data, null, 2),
|
|
419
|
+
data,
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
catch (error) {
|
|
423
|
+
if (!recorded) {
|
|
424
|
+
state.dynamicToolCalls.push({
|
|
425
|
+
name,
|
|
426
|
+
backingTool,
|
|
427
|
+
status: "failed",
|
|
428
|
+
latencyMs: Date.now() - startedAt,
|
|
429
|
+
errorCode: error instanceof ToolExecutionError ? error.code : "tool_execution_failed",
|
|
430
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
throw error;
|
|
434
|
+
}
|
|
435
|
+
},
|
|
436
|
+
};
|
|
437
|
+
};
|
|
438
|
+
const resolveGatewayContract = (contract, policyGateway) => {
|
|
439
|
+
const gateway = contract.gateway;
|
|
440
|
+
if (!gateway && !policyGateway)
|
|
441
|
+
return undefined;
|
|
442
|
+
return { ...(policyGateway ?? {}), ...(gateway ?? {}) };
|
|
443
|
+
};
|
|
444
|
+
const gatewayString = (gateway, keys) => {
|
|
445
|
+
if (!gateway)
|
|
446
|
+
return undefined;
|
|
447
|
+
for (const key of keys) {
|
|
448
|
+
const value = gateway[key];
|
|
449
|
+
if (typeof value === "string" && value.trim()) {
|
|
450
|
+
return value.trim();
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
return undefined;
|
|
454
|
+
};
|
|
455
|
+
const gatewayBoolean = (gateway, keys) => {
|
|
456
|
+
if (!gateway)
|
|
457
|
+
return undefined;
|
|
458
|
+
for (const key of keys) {
|
|
459
|
+
const value = gateway[key];
|
|
460
|
+
if (typeof value === "boolean") {
|
|
461
|
+
return value;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
return undefined;
|
|
465
|
+
};
|
|
466
|
+
const gatewayEndpoint = (gateway) => gatewayString(gateway, ["endpoint"]);
|
|
467
|
+
const gatewaySigningSecret = (gateway) => gatewayString(gateway, [
|
|
468
|
+
"signatureSecret",
|
|
469
|
+
"signature_secret",
|
|
470
|
+
"signingSecret",
|
|
471
|
+
"signing_secret",
|
|
472
|
+
"secret",
|
|
473
|
+
"signature",
|
|
474
|
+
]);
|
|
475
|
+
const appToolGatewayTenantScope = (input) => stripUndefined({
|
|
476
|
+
tenant_id: input.metadata?.tenantId,
|
|
477
|
+
docdex_repo_id: input.docdex?.repoId,
|
|
478
|
+
});
|
|
479
|
+
const appToolGatewayRequesterScope = (input) => stripUndefined({
|
|
480
|
+
request_id: input.metadata?.requestId,
|
|
481
|
+
owner_user_id: input.metadata?.ownerUserId,
|
|
482
|
+
api_key_id: input.metadata?.apiKeyId,
|
|
483
|
+
agent_slug: input.metadata?.agentSlug,
|
|
484
|
+
});
|
|
485
|
+
const gatewayDispatchToolErrorCode = (error) => {
|
|
486
|
+
if (error.code === "GATEWAY_INVALID_ARGS")
|
|
487
|
+
return "tool_invalid_args";
|
|
488
|
+
if (error.code === "GATEWAY_HTTP_FAILED" ||
|
|
489
|
+
error.code === "GATEWAY_RESPONSE_MALFORMED") {
|
|
490
|
+
return "tool_execution_failed";
|
|
491
|
+
}
|
|
492
|
+
return "tool_permission_denied";
|
|
493
|
+
};
|
|
494
|
+
const createGatewayDynamicTool = (options) => {
|
|
495
|
+
const { name, contract, gateway, input, state } = options;
|
|
496
|
+
return {
|
|
497
|
+
name,
|
|
498
|
+
description: contract.description ??
|
|
499
|
+
`Read-only app tool dispatched through the runtime app_tool_gateway for ${name}.`,
|
|
500
|
+
inputSchema: contractCallSchema(contract),
|
|
501
|
+
handler: async (args, context) => {
|
|
502
|
+
const startedAt = Date.now();
|
|
503
|
+
try {
|
|
504
|
+
assertDynamicArgsStayInScope(name, args);
|
|
505
|
+
const dispatched = await dispatchAppToolGateway({
|
|
506
|
+
runId: context.runId ?? input.metadata?.requestId ?? input.metadata?.jobId ?? "codali-runtime",
|
|
507
|
+
sessionId: input.session?.id,
|
|
508
|
+
requestId: input.metadata?.requestId,
|
|
509
|
+
tenantScope: appToolGatewayTenantScope(input),
|
|
510
|
+
requesterScope: appToolGatewayRequesterScope(input),
|
|
511
|
+
toolName: name,
|
|
512
|
+
args,
|
|
513
|
+
contract,
|
|
514
|
+
gateway,
|
|
515
|
+
allowedTools: input.policy.allowedTools,
|
|
516
|
+
deniedTools: input.policy.deniedTools,
|
|
517
|
+
});
|
|
518
|
+
state.dynamicToolCalls.push({
|
|
519
|
+
name,
|
|
520
|
+
backingTool: "app_tool_gateway",
|
|
521
|
+
status: "success",
|
|
522
|
+
latencyMs: Date.now() - startedAt,
|
|
523
|
+
});
|
|
524
|
+
return {
|
|
525
|
+
output: JSON.stringify(dispatched.evidencePayload, null, 2),
|
|
526
|
+
data: dispatched.evidencePayload,
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
catch (error) {
|
|
530
|
+
const toolError = error instanceof AppToolGatewayDispatchError
|
|
531
|
+
? new ToolExecutionError(gatewayDispatchToolErrorCode(error), error.message, {
|
|
532
|
+
retryable: error.retryable,
|
|
533
|
+
details: stripUndefined({
|
|
534
|
+
tool: name,
|
|
535
|
+
gatewayErrorCode: error.code,
|
|
536
|
+
gatewayDetails: error.details,
|
|
537
|
+
}),
|
|
538
|
+
})
|
|
539
|
+
: error;
|
|
540
|
+
state.dynamicToolCalls.push({
|
|
541
|
+
name,
|
|
542
|
+
backingTool: "app_tool_gateway",
|
|
543
|
+
status: "failed",
|
|
544
|
+
latencyMs: Date.now() - startedAt,
|
|
545
|
+
errorCode: toolError instanceof ToolExecutionError ? toolError.code : "tool_execution_failed",
|
|
546
|
+
errorMessage: toolError instanceof Error ? toolError.message : String(toolError),
|
|
547
|
+
});
|
|
548
|
+
throw toolError;
|
|
549
|
+
}
|
|
550
|
+
},
|
|
551
|
+
};
|
|
552
|
+
};
|
|
106
553
|
const parseJsonPayload = (output) => {
|
|
107
554
|
const trimmed = output.trim();
|
|
108
555
|
if (!trimmed || (!trimmed.startsWith("{") && !trimmed.startsWith("[")))
|
|
@@ -619,13 +1066,137 @@ const isDocdexToolAllowed = (toolName, docdex) => {
|
|
|
619
1066
|
}
|
|
620
1067
|
return true;
|
|
621
1068
|
};
|
|
1069
|
+
const isImmutableDocdexRuntimeContext = (docdex) => docdex?.immutableRuntimeContext === true ||
|
|
1070
|
+
docdex?.credentialSource === "attached_mswarm_api_key";
|
|
622
1071
|
const registerRuntimeTool = (registry, tool, policy, docdex) => {
|
|
623
1072
|
if (isRuntimeToolAllowed(tool.name, policy) && isDocdexToolAllowed(tool.name, docdex)) {
|
|
624
1073
|
registry.register(tool);
|
|
625
1074
|
}
|
|
626
1075
|
};
|
|
627
|
-
const
|
|
1076
|
+
const collectRuntimeToolContractCandidates = (input) => {
|
|
1077
|
+
const contracts = new Map();
|
|
1078
|
+
for (const [name, contract] of normalizeRuntimeToolContractEntries(input.policy.okacamToolContracts)) {
|
|
1079
|
+
contracts.set(name, contract);
|
|
1080
|
+
}
|
|
1081
|
+
for (const [name, contract] of normalizeRuntimeToolContractEntries(input.policy.appToolContracts)) {
|
|
1082
|
+
contracts.set(name, contract);
|
|
1083
|
+
}
|
|
1084
|
+
const names = new Set();
|
|
1085
|
+
for (const name of toolNamesFromManifest(input.docdex?.toolManifest))
|
|
1086
|
+
names.add(name);
|
|
1087
|
+
for (const name of stringArrayFromUnknown(input.policy.okacamVirtualTools))
|
|
1088
|
+
names.add(name);
|
|
1089
|
+
for (const name of stringArrayFromUnknown(input.policy.appVirtualTools))
|
|
1090
|
+
names.add(name);
|
|
1091
|
+
for (const name of contracts.keys())
|
|
1092
|
+
names.add(name);
|
|
1093
|
+
return Array.from(names)
|
|
1094
|
+
.sort()
|
|
1095
|
+
.map((name) => ({ name, contract: contracts.get(name) }));
|
|
1096
|
+
};
|
|
1097
|
+
const registerRuntimeContractTools = (registry, input, state, warnings) => {
|
|
1098
|
+
const candidates = collectRuntimeToolContractCandidates(input);
|
|
1099
|
+
if (!candidates.length)
|
|
1100
|
+
return;
|
|
1101
|
+
const registryNames = new Set(registry.list().map((tool) => tool.name));
|
|
1102
|
+
for (const candidate of candidates) {
|
|
1103
|
+
const name = candidate.name.trim();
|
|
1104
|
+
if (!name)
|
|
1105
|
+
continue;
|
|
1106
|
+
pushUnique(state.consideredTools, name);
|
|
1107
|
+
if (!candidate.contract) {
|
|
1108
|
+
if (!registryNames.has(name))
|
|
1109
|
+
recordSkippedDynamicTool(state, name, "missing_contract");
|
|
1110
|
+
continue;
|
|
1111
|
+
}
|
|
1112
|
+
if (candidate.contract.enabled === false) {
|
|
1113
|
+
recordSkippedDynamicTool(state, name, "contract_disabled");
|
|
1114
|
+
continue;
|
|
1115
|
+
}
|
|
1116
|
+
if (!isRuntimeToolAllowed(name, input.policy) || !isDocdexToolAllowed(name, input.docdex)) {
|
|
1117
|
+
recordSkippedDynamicTool(state, name, "policy_disallowed");
|
|
1118
|
+
continue;
|
|
1119
|
+
}
|
|
1120
|
+
if (registryNames.has(name)) {
|
|
1121
|
+
recordSkippedDynamicTool(state, name, "already_registered");
|
|
1122
|
+
continue;
|
|
1123
|
+
}
|
|
1124
|
+
const readOnly = contractBoolean(candidate.contract, "readOnly", "read_only");
|
|
1125
|
+
if (readOnly === false) {
|
|
1126
|
+
recordSkippedDynamicTool(state, name, "not_read_only");
|
|
1127
|
+
continue;
|
|
1128
|
+
}
|
|
1129
|
+
const executionMode = contractString(candidate.contract, "executionMode", "execution_mode") ??
|
|
1130
|
+
"server_supplied_snapshot_plus_docdex";
|
|
1131
|
+
const gateway = resolveGatewayContract(candidate.contract, input.policy.appToolGateway);
|
|
1132
|
+
const resolvedGatewayEndpoint = gatewayEndpoint(gateway);
|
|
1133
|
+
if (executionMode === "app_tool_gateway" || resolvedGatewayEndpoint) {
|
|
1134
|
+
if (!gateway) {
|
|
1135
|
+
recordSkippedDynamicTool(state, name, "gateway_not_configured");
|
|
1136
|
+
continue;
|
|
1137
|
+
}
|
|
1138
|
+
if (readOnly !== true) {
|
|
1139
|
+
recordSkippedDynamicTool(state, name, "not_read_only");
|
|
1140
|
+
continue;
|
|
1141
|
+
}
|
|
1142
|
+
if (gatewayBoolean(gateway, ["readOnly", "read_only"]) !== true) {
|
|
1143
|
+
recordSkippedDynamicTool(state, name, "gateway_not_read_only");
|
|
1144
|
+
continue;
|
|
1145
|
+
}
|
|
1146
|
+
if (!resolvedGatewayEndpoint) {
|
|
1147
|
+
recordSkippedDynamicTool(state, name, "gateway_endpoint_missing");
|
|
1148
|
+
continue;
|
|
1149
|
+
}
|
|
1150
|
+
if (!gatewaySigningSecret(gateway)) {
|
|
1151
|
+
recordSkippedDynamicTool(state, name, "gateway_signature_missing");
|
|
1152
|
+
continue;
|
|
1153
|
+
}
|
|
1154
|
+
registerRuntimeTool(registry, createGatewayDynamicTool({
|
|
1155
|
+
name,
|
|
1156
|
+
contract: candidate.contract,
|
|
1157
|
+
gateway: { ...gateway, endpoint: resolvedGatewayEndpoint },
|
|
1158
|
+
input,
|
|
1159
|
+
state,
|
|
1160
|
+
}), input.policy, input.docdex);
|
|
1161
|
+
registryNames.add(name);
|
|
1162
|
+
pushUnique(state.registeredDynamicTools, name);
|
|
1163
|
+
continue;
|
|
1164
|
+
}
|
|
1165
|
+
const declaredBackingTools = contractStringArray(candidate.contract, "backingTools", "backing_tools");
|
|
1166
|
+
if (!declaredBackingTools.length) {
|
|
1167
|
+
recordSkippedDynamicTool(state, name, "missing_backing_tools");
|
|
1168
|
+
continue;
|
|
1169
|
+
}
|
|
1170
|
+
const unsafeBackingTool = declaredBackingTools.find((toolName) => !READ_ONLY_DYNAMIC_BACKING_TOOLS.has(toolName));
|
|
1171
|
+
if (unsafeBackingTool) {
|
|
1172
|
+
recordSkippedDynamicTool(state, name, `unsafe_backing_tool:${unsafeBackingTool}`);
|
|
1173
|
+
continue;
|
|
1174
|
+
}
|
|
1175
|
+
const availableBackingTools = declaredBackingTools.filter((toolName) => registryNames.has(toolName));
|
|
1176
|
+
if (!availableBackingTools.length) {
|
|
1177
|
+
recordSkippedDynamicTool(state, name, "backing_tool_unavailable");
|
|
1178
|
+
continue;
|
|
1179
|
+
}
|
|
1180
|
+
registerRuntimeTool(registry, createDocdexBackedDynamicTool({
|
|
1181
|
+
name,
|
|
1182
|
+
contract: candidate.contract,
|
|
1183
|
+
registry,
|
|
1184
|
+
backingTools: availableBackingTools,
|
|
1185
|
+
state,
|
|
1186
|
+
}), input.policy, input.docdex);
|
|
1187
|
+
registryNames.add(name);
|
|
1188
|
+
pushUnique(state.registeredDynamicTools, name);
|
|
1189
|
+
}
|
|
1190
|
+
const warningSkips = state.skippedDynamicTools.filter((entry) => entry.reason !== "already_registered");
|
|
1191
|
+
if (warningSkips.length) {
|
|
1192
|
+
warnings.push(`Runtime tool contracts skipped: ${warningSkips
|
|
1193
|
+
.map((entry) => `${entry.name}:${entry.reason}`)
|
|
1194
|
+
.join(", ")}`);
|
|
1195
|
+
}
|
|
1196
|
+
};
|
|
1197
|
+
const buildRuntimeToolRegistry = (input, dynamicToolState, warnings) => {
|
|
628
1198
|
if (input.toolRegistry) {
|
|
1199
|
+
registerRuntimeContractTools(input.toolRegistry, input, dynamicToolState, warnings);
|
|
629
1200
|
return input.toolRegistry;
|
|
630
1201
|
}
|
|
631
1202
|
const registry = new ToolRegistry();
|
|
@@ -634,6 +1205,7 @@ const buildRuntimeToolRegistry = (input) => {
|
|
|
634
1205
|
if (explicitTools) {
|
|
635
1206
|
for (const tool of explicitTools)
|
|
636
1207
|
register(tool);
|
|
1208
|
+
registerRuntimeContractTools(registry, input, dynamicToolState, warnings);
|
|
637
1209
|
return registry;
|
|
638
1210
|
}
|
|
639
1211
|
for (const tool of createFileTools())
|
|
@@ -643,19 +1215,26 @@ const buildRuntimeToolRegistry = (input) => {
|
|
|
643
1215
|
if (input.policy.allowShell) {
|
|
644
1216
|
register(createShellTool());
|
|
645
1217
|
}
|
|
1218
|
+
const immutableDocdexContext = isImmutableDocdexRuntimeContext(input.docdex);
|
|
646
1219
|
const docdexClient = new DocdexClient({
|
|
647
|
-
baseUrl:
|
|
1220
|
+
baseUrl: immutableDocdexContext
|
|
1221
|
+
? input.docdex?.baseUrl ?? ""
|
|
1222
|
+
: input.docdex?.baseUrl ?? DEFAULT_DOCDEX_BASE_URL,
|
|
648
1223
|
repoId: input.docdex?.repoId,
|
|
649
|
-
repoRoot:
|
|
1224
|
+
repoRoot: immutableDocdexContext
|
|
1225
|
+
? undefined
|
|
1226
|
+
: input.docdex?.repoRoot ?? input.workspace.root,
|
|
650
1227
|
dagSessionId: input.docdex?.dagSessionId ?? input.metadata?.requestId,
|
|
651
1228
|
apiKey: input.docdex?.apiKey,
|
|
652
1229
|
credentialSource: input.docdex?.credentialSource,
|
|
653
1230
|
required: input.docdex?.required,
|
|
654
1231
|
allowedOperations: input.docdex?.allowedOperations,
|
|
655
1232
|
capabilities: input.docdex?.capabilities,
|
|
1233
|
+
immutableRuntimeContext: immutableDocdexContext,
|
|
656
1234
|
});
|
|
657
1235
|
for (const tool of createDocdexTools(docdexClient))
|
|
658
1236
|
register(tool);
|
|
1237
|
+
registerRuntimeContractTools(registry, input, dynamicToolState, warnings);
|
|
659
1238
|
return registry;
|
|
660
1239
|
};
|
|
661
1240
|
const buildResponseFormat = (response) => {
|
|
@@ -930,6 +1509,25 @@ const emitRuntimeEvent = (event, sink, warnings) => {
|
|
|
930
1509
|
warnings.push(error instanceof Error ? error.message : String(error));
|
|
931
1510
|
}
|
|
932
1511
|
};
|
|
1512
|
+
const buildRuntimeTelemetry = (options) => {
|
|
1513
|
+
const calledTools = [];
|
|
1514
|
+
for (const event of options.events) {
|
|
1515
|
+
if (event.type === "tool_call")
|
|
1516
|
+
pushUnique(calledTools, event.name);
|
|
1517
|
+
}
|
|
1518
|
+
return {
|
|
1519
|
+
runId: options.runId,
|
|
1520
|
+
runtime: "codali",
|
|
1521
|
+
mode: options.input.policy.mode,
|
|
1522
|
+
toolCallCount: options.toolCallsExecuted,
|
|
1523
|
+
calledTools,
|
|
1524
|
+
consideredTools: [...options.dynamicToolState.consideredTools],
|
|
1525
|
+
registeredDynamicTools: [...options.dynamicToolState.registeredDynamicTools],
|
|
1526
|
+
skippedDynamicTools: [...options.dynamicToolState.skippedDynamicTools],
|
|
1527
|
+
dynamicToolCalls: [...options.dynamicToolState.dynamicToolCalls],
|
|
1528
|
+
warnings: [...options.warnings],
|
|
1529
|
+
};
|
|
1530
|
+
};
|
|
933
1531
|
const createSubagentRunner = (options) => {
|
|
934
1532
|
const { input, registry, toolContext, runId, remainingToolBudget } = options;
|
|
935
1533
|
return async ({ spec }) => {
|
|
@@ -1322,6 +1920,7 @@ export const runCodaliTask = async (input) => {
|
|
|
1322
1920
|
const touchedFiles = new Set();
|
|
1323
1921
|
const warnings = [];
|
|
1324
1922
|
const events = [];
|
|
1923
|
+
const dynamicToolState = createDynamicToolRegistryState();
|
|
1325
1924
|
let eventSequence = 0;
|
|
1326
1925
|
const session = await initializeRuntimeSession(input, runId, warnings);
|
|
1327
1926
|
const emit = (event) => {
|
|
@@ -1329,7 +1928,7 @@ export const runCodaliTask = async (input) => {
|
|
|
1329
1928
|
emitRuntimeEvent(event, input.onEvent, warnings);
|
|
1330
1929
|
};
|
|
1331
1930
|
const provider = input.providerInstance ?? createRuntimeProvider(input.provider);
|
|
1332
|
-
const registry = buildRuntimeToolRegistry(input);
|
|
1931
|
+
const registry = buildRuntimeToolRegistry(input, dynamicToolState, warnings);
|
|
1333
1932
|
const originalRecordTouchedFile = input.toolContext?.recordTouchedFile;
|
|
1334
1933
|
const toolContext = {
|
|
1335
1934
|
...input.toolContext,
|
|
@@ -1385,6 +1984,14 @@ export const runCodaliTask = async (input) => {
|
|
|
1385
1984
|
warnings,
|
|
1386
1985
|
events,
|
|
1387
1986
|
runId,
|
|
1987
|
+
telemetry: buildRuntimeTelemetry({
|
|
1988
|
+
input,
|
|
1989
|
+
runId,
|
|
1990
|
+
toolCallsExecuted: result.toolCallsExecuted,
|
|
1991
|
+
events,
|
|
1992
|
+
warnings,
|
|
1993
|
+
dynamicToolState,
|
|
1994
|
+
}),
|
|
1388
1995
|
session: toSessionSummary(finalSession),
|
|
1389
1996
|
};
|
|
1390
1997
|
}
|
|
@@ -1436,6 +2043,14 @@ export const runCodaliTask = async (input) => {
|
|
|
1436
2043
|
warnings,
|
|
1437
2044
|
events,
|
|
1438
2045
|
runId,
|
|
2046
|
+
telemetry: buildRuntimeTelemetry({
|
|
2047
|
+
input,
|
|
2048
|
+
runId,
|
|
2049
|
+
toolCallsExecuted: result.toolCallsExecuted,
|
|
2050
|
+
events,
|
|
2051
|
+
warnings,
|
|
2052
|
+
dynamicToolState,
|
|
2053
|
+
}),
|
|
1439
2054
|
session: toSessionSummary(finalSession),
|
|
1440
2055
|
};
|
|
1441
2056
|
}
|