@mcoda/codali 0.1.107 → 0.1.109
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/agents/AgentInventory.d.ts +56 -0
- package/dist/agents/AgentInventory.d.ts.map +1 -0
- package/dist/agents/AgentInventory.js +131 -0
- package/dist/agents/RoleResolution.d.ts +49 -0
- package/dist/agents/RoleResolution.d.ts.map +1 -0
- package/dist/agents/RoleResolution.js +166 -0
- package/dist/api/ChatCompletionsAdapter.d.ts +76 -0
- package/dist/api/ChatCompletionsAdapter.d.ts.map +1 -0
- package/dist/api/ChatCompletionsAdapter.js +54 -0
- package/dist/api/CodaliApi.d.ts +78 -0
- package/dist/api/CodaliApi.d.ts.map +1 -0
- package/dist/api/CodaliApi.js +273 -0
- package/dist/api/CodaliServer.d.ts +42 -0
- package/dist/api/CodaliServer.d.ts.map +1 -0
- package/dist/api/CodaliServer.js +118 -0
- package/dist/cli/AskCommand.d.ts +66 -0
- package/dist/cli/AskCommand.d.ts.map +1 -0
- package/dist/cli/AskCommand.js +536 -0
- package/dist/cli/AuthCommand.d.ts +19 -0
- package/dist/cli/AuthCommand.d.ts.map +1 -0
- package/dist/cli/AuthCommand.js +161 -0
- package/dist/cli/ServeCommand.d.ts +5 -0
- package/dist/cli/ServeCommand.d.ts.map +1 -0
- package/dist/cli/ServeCommand.js +87 -0
- package/dist/cli/ToolsCommand.d.ts +22 -0
- package/dist/cli/ToolsCommand.d.ts.map +1 -0
- package/dist/cli/ToolsCommand.js +208 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +29 -1
- package/dist/connectors/http/HttpToolDefinition.d.ts +74 -0
- package/dist/connectors/http/HttpToolDefinition.d.ts.map +1 -0
- package/dist/connectors/http/HttpToolDefinition.js +190 -0
- package/dist/connectors/http/HttpToolSource.d.ts +18 -0
- package/dist/connectors/http/HttpToolSource.d.ts.map +1 -0
- package/dist/connectors/http/HttpToolSource.js +29 -0
- package/dist/connectors/mcp/McpClient.d.ts +132 -0
- package/dist/connectors/mcp/McpClient.d.ts.map +1 -0
- package/dist/connectors/mcp/McpClient.js +215 -0
- package/dist/connectors/mcp/McpServerRegistry.d.ts +75 -0
- package/dist/connectors/mcp/McpServerRegistry.d.ts.map +1 -0
- package/dist/connectors/mcp/McpServerRegistry.js +180 -0
- package/dist/connectors/mcp/McpToolAdapter.d.ts +48 -0
- package/dist/connectors/mcp/McpToolAdapter.d.ts.map +1 -0
- package/dist/connectors/mcp/McpToolAdapter.js +101 -0
- package/dist/connectors/mcp/McpToolSource.d.ts +27 -0
- package/dist/connectors/mcp/McpToolSource.d.ts.map +1 -0
- package/dist/connectors/mcp/McpToolSource.js +33 -0
- package/dist/connectors/media/ImageGenerationTool.d.ts +43 -0
- package/dist/connectors/media/ImageGenerationTool.d.ts.map +1 -0
- package/dist/connectors/media/ImageGenerationTool.js +122 -0
- package/dist/connectors/oauth/DeviceCodeAuth.d.ts +74 -0
- package/dist/connectors/oauth/DeviceCodeAuth.d.ts.map +1 -0
- package/dist/connectors/oauth/DeviceCodeAuth.js +153 -0
- package/dist/docdex/DocdexClient.d.ts +2 -0
- package/dist/docdex/DocdexClient.d.ts.map +1 -1
- package/dist/docdex/DocdexClient.js +4 -0
- package/dist/gateway/CodaliGateway.d.ts +51 -3
- package/dist/gateway/CodaliGateway.d.ts.map +1 -1
- package/dist/gateway/CodaliGateway.js +226 -2
- package/dist/gateway/CodaliGatewayTypes.d.ts +34 -0
- package/dist/gateway/CodaliGatewayTypes.d.ts.map +1 -1
- package/dist/gateway/EvidenceNormalizer.d.ts.map +1 -1
- package/dist/gateway/EvidenceNormalizer.js +90 -1
- package/dist/gateway/Finalizer.d.ts +50 -0
- package/dist/gateway/Finalizer.d.ts.map +1 -0
- package/dist/gateway/Finalizer.js +79 -0
- package/dist/gateway/GatewayPlanner.d.ts +13 -0
- package/dist/gateway/GatewayPlanner.d.ts.map +1 -1
- package/dist/gateway/GatewayPlanner.js +81 -16
- package/dist/gateway/GatewayStateMachine.d.ts.map +1 -1
- package/dist/gateway/GatewayStateMachine.js +6 -0
- package/dist/gateway/GatewayTracer.d.ts +93 -0
- package/dist/gateway/GatewayTracer.d.ts.map +1 -0
- package/dist/gateway/GatewayTracer.js +214 -0
- package/dist/gateway/LocalGatewayProvider.d.ts +46 -0
- package/dist/gateway/LocalGatewayProvider.d.ts.map +1 -0
- package/dist/gateway/LocalGatewayProvider.js +161 -0
- package/dist/gateway/LocalGatewayTaskRunner.d.ts +79 -0
- package/dist/gateway/LocalGatewayTaskRunner.d.ts.map +1 -0
- package/dist/gateway/LocalGatewayTaskRunner.js +354 -0
- package/dist/gateway/RelevanceJudge.d.ts +14 -0
- package/dist/gateway/RelevanceJudge.d.ts.map +1 -0
- package/dist/gateway/RelevanceJudge.js +87 -0
- package/dist/gateway/ResponseSchema.d.ts +32 -0
- package/dist/gateway/ResponseSchema.d.ts.map +1 -0
- package/dist/gateway/ResponseSchema.js +120 -0
- package/dist/gateway/TemporalContext.d.ts +36 -0
- package/dist/gateway/TemporalContext.d.ts.map +1 -0
- package/dist/gateway/TemporalContext.js +115 -0
- package/dist/gateway/ToolCapabilityCompiler.d.ts +5 -0
- package/dist/gateway/ToolCapabilityCompiler.d.ts.map +1 -1
- package/dist/gateway/ToolCapabilityCompiler.js +27 -2
- package/dist/gateway/ToolDescriptorSource.d.ts +6 -0
- package/dist/gateway/ToolDescriptorSource.d.ts.map +1 -0
- package/dist/gateway/ToolDescriptorSource.js +30 -0
- package/dist/gateway/ToolExposure.d.ts +36 -0
- package/dist/gateway/ToolExposure.d.ts.map +1 -0
- package/dist/gateway/ToolExposure.js +154 -0
- package/dist/index.d.ts +57 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +45 -0
- package/dist/providers/ClaudeCliProvider.d.ts +2 -0
- package/dist/providers/ClaudeCliProvider.d.ts.map +1 -1
- package/dist/providers/ClaudeCliProvider.js +2 -0
- package/dist/providers/CodexCliProvider.d.ts +2 -0
- package/dist/providers/CodexCliProvider.d.ts.map +1 -1
- package/dist/providers/CodexCliProvider.js +2 -0
- package/dist/providers/MswarmWorkerProvider.d.ts +2 -0
- package/dist/providers/MswarmWorkerProvider.d.ts.map +1 -1
- package/dist/providers/MswarmWorkerProvider.js +2 -0
- package/dist/providers/OllamaRemoteProvider.d.ts +2 -0
- package/dist/providers/OllamaRemoteProvider.d.ts.map +1 -1
- package/dist/providers/OllamaRemoteProvider.js +2 -0
- package/dist/providers/OpenAiCompatibleProvider.d.ts +2 -0
- package/dist/providers/OpenAiCompatibleProvider.d.ts.map +1 -1
- package/dist/providers/OpenAiCompatibleProvider.js +2 -0
- package/dist/providers/ProviderTypes.d.ts +13 -0
- package/dist/providers/ProviderTypes.d.ts.map +1 -1
- package/dist/providers/ProviderTypes.js +1 -1
- package/dist/runcontext/CredentialFile.d.ts +12 -0
- package/dist/runcontext/CredentialFile.d.ts.map +1 -0
- package/dist/runcontext/CredentialFile.js +81 -0
- package/dist/runcontext/RunContextResolver.d.ts +126 -0
- package/dist/runcontext/RunContextResolver.d.ts.map +1 -0
- package/dist/runcontext/RunContextResolver.js +389 -0
- package/dist/storage/CodaliDatasetPrivacyEngine.d.ts +1 -1
- package/dist/storage/CodaliDatasetPrivacyEngine.d.ts.map +1 -1
- package/dist/storage/CodaliDatasetPrivacyEngine.js +12 -0
- package/dist/tools/ToolRegistry.d.ts +14 -1
- package/dist/tools/ToolRegistry.d.ts.map +1 -1
- package/dist/tools/ToolRegistry.js +38 -1
- package/dist/tools/ToolTypes.d.ts +35 -0
- package/dist/tools/ToolTypes.d.ts.map +1 -1
- package/dist/tools/ToolTypes.js +21 -0
- package/dist/tools/TruncateResult.d.ts +29 -0
- package/dist/tools/TruncateResult.d.ts.map +1 -0
- package/dist/tools/TruncateResult.js +91 -0
- package/dist/tools/diff/DiffTool.d.ts.map +1 -1
- package/dist/tools/diff/DiffTool.js +2 -0
- package/dist/tools/docdex/DocdexTools.d.ts +21 -1
- package/dist/tools/docdex/DocdexTools.d.ts.map +1 -1
- package/dist/tools/docdex/DocdexTools.js +108 -16
- package/dist/tools/filesystem/FileTools.d.ts.map +1 -1
- package/dist/tools/filesystem/FileTools.js +8 -0
- package/dist/tools/search/SearchTool.d.ts.map +1 -1
- package/dist/tools/search/SearchTool.js +3 -1
- package/dist/tools/shell/ShellTool.d.ts.map +1 -1
- package/dist/tools/shell/ShellTool.js +2 -0
- package/package.json +4 -3
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { filterLocallyDrivable, getAgentInventory } from "../agents/AgentInventory.js";
|
|
3
|
+
import { resolveConfigurableRoles } from "../agents/RoleResolution.js";
|
|
4
|
+
import { attachHttpTools } from "../connectors/http/HttpToolSource.js";
|
|
5
|
+
import { attachMcpTools } from "../connectors/mcp/McpToolSource.js";
|
|
6
|
+
import { DocdexClient } from "../docdex/DocdexClient.js";
|
|
7
|
+
import { createCodaliGateway } from "../gateway/CodaliGateway.js";
|
|
8
|
+
import { createLocalGatewayTaskRunner } from "../gateway/LocalGatewayTaskRunner.js";
|
|
9
|
+
import { createProviderForAssignment, } from "../gateway/LocalGatewayProvider.js";
|
|
10
|
+
import { gatewayToolDescriptorsFromRegistry } from "../gateway/ToolDescriptorSource.js";
|
|
11
|
+
import { GatewayTracer } from "../gateway/GatewayTracer.js";
|
|
12
|
+
import { createRelevanceJudge } from "../gateway/RelevanceJudge.js";
|
|
13
|
+
import { ToolRegistry } from "../tools/ToolRegistry.js";
|
|
14
|
+
import { createDocdexTools } from "../tools/docdex/DocdexTools.js";
|
|
15
|
+
import { LocalConfigRunContextResolver } from "../runcontext/RunContextResolver.js";
|
|
16
|
+
import { providerSupportsToolCalls } from "../providers/ProviderTypes.js";
|
|
17
|
+
/**
|
|
18
|
+
* `codali ask` — the terminal entry point to the orchestration gateway.
|
|
19
|
+
*
|
|
20
|
+
* Until this existed the gateway had exactly one caller (mswarm's cloud job
|
|
21
|
+
* path) and could not be exercised locally at all, which made every other
|
|
22
|
+
* improvement unverifiable.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* Default budgets. Deliberately small: an orchestrator that can quietly spend
|
|
26
|
+
* fifty tool calls on one question is not one you can put in front of users.
|
|
27
|
+
*/
|
|
28
|
+
export const DEFAULT_MAX_ROUNDS = 3;
|
|
29
|
+
export const DEFAULT_MAX_TOOL_CALLS = 20;
|
|
30
|
+
export const DEFAULT_MAX_MODEL_CALLS = 12;
|
|
31
|
+
/**
|
|
32
|
+
* Wall-clock budget. Generous because CLI-backed agents (codex, claude,
|
|
33
|
+
* gemini) commonly take 30-90s per call, and several tasks run per question.
|
|
34
|
+
*/
|
|
35
|
+
export const DEFAULT_DEADLINE_MS = 600000;
|
|
36
|
+
/**
|
|
37
|
+
* Independent worker tasks executed concurrently. Bounded rather than
|
|
38
|
+
* unlimited: each task holds a model call, and a tenant's connectors have their
|
|
39
|
+
* own rate limits.
|
|
40
|
+
*/
|
|
41
|
+
export const DEFAULT_MAX_PARALLEL_WORKERS = 3;
|
|
42
|
+
/** Read-only docdex tools offered by default. */
|
|
43
|
+
const DEFAULT_ALLOWED_TOOLS = [
|
|
44
|
+
"docdex_search",
|
|
45
|
+
"docdex_open",
|
|
46
|
+
"docdex_symbols",
|
|
47
|
+
"docdex_tree",
|
|
48
|
+
"docdex_files",
|
|
49
|
+
"docdex_impact_graph",
|
|
50
|
+
"docdex_batch_search",
|
|
51
|
+
"docdex_web_research",
|
|
52
|
+
"docdex_stats",
|
|
53
|
+
];
|
|
54
|
+
const HELP = `Usage: codali ask "<question>" [options]
|
|
55
|
+
|
|
56
|
+
Ask Codali a question. The orchestrator routes the request, gathers evidence
|
|
57
|
+
with the tools available to it, and returns a cited answer.
|
|
58
|
+
|
|
59
|
+
Options:
|
|
60
|
+
--workspace-root <path> Repository to run against (default: cwd)
|
|
61
|
+
--mode <mode> fast | balanced | deep | cheap (default: balanced)
|
|
62
|
+
--trace Stream progress and print the run trace
|
|
63
|
+
--json Emit the full result as JSON
|
|
64
|
+
--max-rounds <n> Orchestration rounds (default: ${DEFAULT_MAX_ROUNDS})
|
|
65
|
+
--max-tool-calls <n> Tool calls for the whole run (default: ${DEFAULT_MAX_TOOL_CALLS})
|
|
66
|
+
--deadline-ms <n> Wall-clock budget (default: ${DEFAULT_DEADLINE_MS})
|
|
67
|
+
--docdex-base-url <url> Docdex endpoint (default: http://127.0.0.1:28491)
|
|
68
|
+
--response-schema <path> JSON Schema the answer must satisfy
|
|
69
|
+
--format <text|json> Response format (default: text)
|
|
70
|
+
--no-tools Answer without tools, for a pure generation task
|
|
71
|
+
--help, -h Show this help
|
|
72
|
+
`;
|
|
73
|
+
const parseIntArg = (value, flag, fallback) => {
|
|
74
|
+
if (value === undefined)
|
|
75
|
+
return fallback;
|
|
76
|
+
const parsed = Number.parseInt(value, 10);
|
|
77
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
78
|
+
throw new Error(`${flag} expects a positive integer`);
|
|
79
|
+
}
|
|
80
|
+
return parsed;
|
|
81
|
+
};
|
|
82
|
+
export const parseAskArgs = (argv) => {
|
|
83
|
+
const queryParts = [];
|
|
84
|
+
let workspaceRoot = process.cwd();
|
|
85
|
+
let mode = "balanced";
|
|
86
|
+
let trace = false;
|
|
87
|
+
let json = false;
|
|
88
|
+
let maxRounds = DEFAULT_MAX_ROUNDS;
|
|
89
|
+
let maxToolCalls = DEFAULT_MAX_TOOL_CALLS;
|
|
90
|
+
let deadlineMs = DEFAULT_DEADLINE_MS;
|
|
91
|
+
let docdexBaseUrl;
|
|
92
|
+
let responseSchemaPath;
|
|
93
|
+
let responseFormat;
|
|
94
|
+
let noTools = false;
|
|
95
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
96
|
+
const arg = argv[index];
|
|
97
|
+
const next = argv[index + 1];
|
|
98
|
+
if (arg === "--workspace-root" && next) {
|
|
99
|
+
workspaceRoot = path.resolve(next);
|
|
100
|
+
index += 1;
|
|
101
|
+
}
|
|
102
|
+
else if (arg === "--mode" && next) {
|
|
103
|
+
mode = next;
|
|
104
|
+
index += 1;
|
|
105
|
+
}
|
|
106
|
+
else if (arg === "--trace") {
|
|
107
|
+
trace = true;
|
|
108
|
+
}
|
|
109
|
+
else if (arg === "--json") {
|
|
110
|
+
json = true;
|
|
111
|
+
}
|
|
112
|
+
else if (arg === "--no-tools") {
|
|
113
|
+
noTools = true;
|
|
114
|
+
}
|
|
115
|
+
else if (arg === "--max-rounds" && next) {
|
|
116
|
+
maxRounds = parseIntArg(next, "--max-rounds", DEFAULT_MAX_ROUNDS);
|
|
117
|
+
index += 1;
|
|
118
|
+
}
|
|
119
|
+
else if (arg === "--max-tool-calls" && next) {
|
|
120
|
+
maxToolCalls = parseIntArg(next, "--max-tool-calls", DEFAULT_MAX_TOOL_CALLS);
|
|
121
|
+
index += 1;
|
|
122
|
+
}
|
|
123
|
+
else if (arg === "--deadline-ms" && next) {
|
|
124
|
+
deadlineMs = parseIntArg(next, "--deadline-ms", DEFAULT_DEADLINE_MS);
|
|
125
|
+
index += 1;
|
|
126
|
+
}
|
|
127
|
+
else if (arg === "--docdex-base-url" && next) {
|
|
128
|
+
docdexBaseUrl = next;
|
|
129
|
+
index += 1;
|
|
130
|
+
}
|
|
131
|
+
else if (arg === "--response-schema" && next) {
|
|
132
|
+
responseSchemaPath = next;
|
|
133
|
+
index += 1;
|
|
134
|
+
}
|
|
135
|
+
else if (arg === "--format" && next) {
|
|
136
|
+
responseFormat = next === "json" ? "json" : "text";
|
|
137
|
+
index += 1;
|
|
138
|
+
}
|
|
139
|
+
else if (arg && !arg.startsWith("--")) {
|
|
140
|
+
queryParts.push(arg);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const query = queryParts.join(" ").trim();
|
|
144
|
+
if (!query) {
|
|
145
|
+
throw new Error("codali ask requires a question.\n\n" + HELP);
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
query,
|
|
149
|
+
workspaceRoot,
|
|
150
|
+
mode,
|
|
151
|
+
trace,
|
|
152
|
+
json,
|
|
153
|
+
maxRounds,
|
|
154
|
+
maxToolCalls,
|
|
155
|
+
deadlineMs,
|
|
156
|
+
docdexBaseUrl,
|
|
157
|
+
responseSchemaPath,
|
|
158
|
+
responseFormat,
|
|
159
|
+
noTools,
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
const buildDefaultRegistry = (options, context, judgeProvider) => {
|
|
163
|
+
const registry = new ToolRegistry();
|
|
164
|
+
if (options.noTools)
|
|
165
|
+
return registry;
|
|
166
|
+
const client = new DocdexClient({
|
|
167
|
+
baseUrl: options.docdexBaseUrl ?? context.docdex?.baseUrl ?? "http://127.0.0.1:28491",
|
|
168
|
+
repoRoot: context.repo?.root ?? options.workspaceRoot,
|
|
169
|
+
repoId: context.docdex?.repoId,
|
|
170
|
+
apiKey: context.docdex?.apiKey,
|
|
171
|
+
allowedOperations: context.docdex?.allowedOperations,
|
|
172
|
+
});
|
|
173
|
+
for (const tool of createDocdexTools(client, {
|
|
174
|
+
// Judged by the orchestrator model: small, fast, and already loaded.
|
|
175
|
+
judgeRelevance: judgeProvider
|
|
176
|
+
? createRelevanceJudge({ provider: judgeProvider })
|
|
177
|
+
: undefined,
|
|
178
|
+
})) {
|
|
179
|
+
registry.register(tool);
|
|
180
|
+
}
|
|
181
|
+
return registry;
|
|
182
|
+
};
|
|
183
|
+
export const runAsk = async (argv, deps = {}) => {
|
|
184
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
185
|
+
(deps.write ?? console.log)(HELP);
|
|
186
|
+
return { tracer: new GatewayTracer(""), exitCode: 0 };
|
|
187
|
+
}
|
|
188
|
+
const options = parseAskArgs(argv);
|
|
189
|
+
const write = deps.write ?? ((line) => process.stdout.write(`${line}\n`));
|
|
190
|
+
const tracer = new GatewayTracer(options.query, { verbose: options.trace });
|
|
191
|
+
// ---- Resolve what this run may touch --------------------------------
|
|
192
|
+
const context = deps.resolveRunContext
|
|
193
|
+
? await deps.resolveRunContext(options.workspaceRoot)
|
|
194
|
+
: await new LocalConfigRunContextResolver().resolve({
|
|
195
|
+
workspaceRoot: options.workspaceRoot,
|
|
196
|
+
});
|
|
197
|
+
for (const warning of context.warnings ?? [])
|
|
198
|
+
tracer.addWarning(warning);
|
|
199
|
+
// ---- Resolve models (inventory is loaded once per process) -----------
|
|
200
|
+
const inventory = await (deps.loadInventory ?? getAgentInventory)();
|
|
201
|
+
for (const warning of inventory.warnings)
|
|
202
|
+
tracer.addWarning(warning);
|
|
203
|
+
// Only consider agents this process can actually call. Selecting one it
|
|
204
|
+
// cannot drive produces a plausible plan and then a wall of provider errors.
|
|
205
|
+
const drivable = filterLocallyDrivable(inventory.agents);
|
|
206
|
+
if (drivable.length < inventory.agents.length) {
|
|
207
|
+
tracer.addWarning(`agents_not_locally_drivable:${inventory.agents.length - drivable.length}`);
|
|
208
|
+
}
|
|
209
|
+
const roles = resolveConfigurableRoles({
|
|
210
|
+
inventory: drivable,
|
|
211
|
+
bindings: context.agentRoles,
|
|
212
|
+
});
|
|
213
|
+
for (const warning of roles.warnings)
|
|
214
|
+
tracer.addWarning(warning);
|
|
215
|
+
tracer.setRoleBindings(roles.bindings);
|
|
216
|
+
const orchestrator = roles.assignments.orchestrator;
|
|
217
|
+
const synthesizer = roles.assignments.synthesizer;
|
|
218
|
+
if (!orchestrator && !synthesizer) {
|
|
219
|
+
write("No usable mcoda agent was found for the orchestrator or synthesizer role.\n" +
|
|
220
|
+
"Check `mcoda agent list --json` and bind roles in ~/.codali/config.json.");
|
|
221
|
+
tracer.finish({ status: "failed", completionReason: "no_agents_resolved" });
|
|
222
|
+
if (options.trace)
|
|
223
|
+
write(tracer.render());
|
|
224
|
+
return { tracer, exitCode: 1 };
|
|
225
|
+
}
|
|
226
|
+
const makeProvider = deps.createProvider ?? createProviderForAssignment;
|
|
227
|
+
const orchestratorProvider = orchestrator ? makeProvider(orchestrator) : undefined;
|
|
228
|
+
const synthesizerProvider = synthesizer ? makeProvider(synthesizer) : undefined;
|
|
229
|
+
// Either role can stand in for the other if only one resolved; the trace
|
|
230
|
+
// records which, so a degraded run is visible rather than silent.
|
|
231
|
+
const primary = (synthesizerProvider ?? orchestratorProvider);
|
|
232
|
+
if (!orchestrator || !synthesizer) {
|
|
233
|
+
tracer.addWarning("agent_role_substituted");
|
|
234
|
+
}
|
|
235
|
+
// Worker tasks need a provider that can emit structured tool calls. The
|
|
236
|
+
// agent's advertised `supportsTools` is not sufficient — a CLI-backed agent
|
|
237
|
+
// reports true because the CLI has tools, while this adapter drives it
|
|
238
|
+
// through text and can never surface a call. So probe the built provider.
|
|
239
|
+
const workerCandidates = [roles.toolCapable, roles.assignments.worker, orchestrator, synthesizer].filter((assignment) => Boolean(assignment));
|
|
240
|
+
let workerAssignment = workerCandidates[0];
|
|
241
|
+
let worker = workerAssignment ? makeProvider(workerAssignment) : primary;
|
|
242
|
+
for (const candidate of workerCandidates) {
|
|
243
|
+
const provider = makeProvider(candidate);
|
|
244
|
+
if (providerSupportsToolCalls(provider)) {
|
|
245
|
+
workerAssignment = candidate;
|
|
246
|
+
worker = provider;
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (workerAssignment) {
|
|
251
|
+
tracer.setRoleBindings({ worker: workerAssignment.candidate.slug });
|
|
252
|
+
}
|
|
253
|
+
// If nothing can call a tool, do not offer tools at all. Planning four
|
|
254
|
+
// retrieval tasks that physically cannot execute is worse than planning
|
|
255
|
+
// none: the run burns its budget and answers "I could not determine",
|
|
256
|
+
// with the real cause buried.
|
|
257
|
+
const toolsExecutable = providerSupportsToolCalls(worker);
|
|
258
|
+
if (!toolsExecutable && !options.noTools) {
|
|
259
|
+
tracer.addWarning(`tools_unavailable:no_tool_calling_provider:${workerAssignment?.candidate.slug ?? "none"}`);
|
|
260
|
+
}
|
|
261
|
+
// ---- Tools -----------------------------------------------------------
|
|
262
|
+
const registry = deps.buildRegistry
|
|
263
|
+
? deps.buildRegistry(options, context)
|
|
264
|
+
: buildDefaultRegistry(options, context, orchestratorProvider ?? worker);
|
|
265
|
+
// Connect the run's MCP servers and register what they expose into the same
|
|
266
|
+
// registry the built-in and docdex tools live in. A failing server degrades
|
|
267
|
+
// its own tools to unavailable and is reported; it never fails the run.
|
|
268
|
+
const mcp = options.noTools
|
|
269
|
+
? { registry: undefined, health: [], registered: [], warnings: [] }
|
|
270
|
+
: await (deps.attachMcp ?? attachMcpTools)({ context, toolRegistry: registry });
|
|
271
|
+
for (const warning of mcp.warnings)
|
|
272
|
+
tracer.addWarning(warning);
|
|
273
|
+
if (mcp.registered.length > 0) {
|
|
274
|
+
tracer.addWarning(`mcp_tools_registered:${mcp.registered.length}`);
|
|
275
|
+
}
|
|
276
|
+
// Hand-declared HTTP connectors register the same way MCP tools do.
|
|
277
|
+
const http = options.noTools
|
|
278
|
+
? { registered: [], warnings: [] }
|
|
279
|
+
: attachHttpTools({ context, toolRegistry: registry });
|
|
280
|
+
for (const warning of http.warnings)
|
|
281
|
+
tracer.addWarning(warning);
|
|
282
|
+
const registeredTools = registry.list().map((tool) => tool.name);
|
|
283
|
+
// Only read-only MCP tools enter the default set. Phase 2 keeps every
|
|
284
|
+
// external connector read-only, and since a server's self-description is not
|
|
285
|
+
// trusted, "read-only" means an operator declared it so — either with
|
|
286
|
+
// `readOnly: true` on the server or by naming tools in `allowTools`. Write
|
|
287
|
+
// tools stay registered and visible to `codali tools list`, but are not
|
|
288
|
+
// offered to a model unless the operator allow-lists them explicitly.
|
|
289
|
+
const readOnlyMcpTools = registry
|
|
290
|
+
.catalog(mcp.registered)
|
|
291
|
+
.filter((descriptor) => descriptor.readOnly)
|
|
292
|
+
.map((descriptor) => descriptor.name);
|
|
293
|
+
const withheldWriteTools = mcp.registered.length - readOnlyMcpTools.length;
|
|
294
|
+
if (withheldWriteTools > 0) {
|
|
295
|
+
tracer.addWarning(`mcp_write_tools_withheld:${withheldWriteTools}`);
|
|
296
|
+
}
|
|
297
|
+
// HTTP connector tools are GET/HEAD by construction, so they are read-only
|
|
298
|
+
// without needing an operator declaration.
|
|
299
|
+
const defaultAllowed = [...DEFAULT_ALLOWED_TOOLS, ...readOnlyMcpTools, ...http.registered];
|
|
300
|
+
const allowedTools = toolsExecutable
|
|
301
|
+
? (context.allowedTools ?? defaultAllowed).filter((tool) => registeredTools.includes(tool))
|
|
302
|
+
: [];
|
|
303
|
+
const toolDescriptors = gatewayToolDescriptorsFromRegistry(registry, allowedTools);
|
|
304
|
+
// ---- Build the request ------------------------------------------------
|
|
305
|
+
const limits = context.limits ?? {};
|
|
306
|
+
const maxToolCalls = Math.min(options.maxToolCalls, limits.maxToolCalls ?? Infinity);
|
|
307
|
+
const maxRounds = Math.min(options.maxRounds, limits.maxRounds ?? Infinity);
|
|
308
|
+
const deadlineMs = Math.min(options.deadlineMs, limits.deadlineMs ?? Infinity);
|
|
309
|
+
// Every tool must be declared to the capability compiler, which otherwise
|
|
310
|
+
// discards it as `not_declared`. That includes docdex tools outside the
|
|
311
|
+
// compiler's hardcoded backing-tool set — `docdex_web_research` among them,
|
|
312
|
+
// which meant the "web" capability was never offered to the classifier and
|
|
313
|
+
// no question could ever reach the public web.
|
|
314
|
+
const connectorTools = allowedTools.filter((tool) => registeredTools.includes(tool));
|
|
315
|
+
let responseSchema;
|
|
316
|
+
if (options.responseSchemaPath) {
|
|
317
|
+
const { readFile } = await import("node:fs/promises");
|
|
318
|
+
responseSchema = JSON.parse(await readFile(path.resolve(options.responseSchemaPath), "utf8"));
|
|
319
|
+
}
|
|
320
|
+
const request = {
|
|
321
|
+
query: options.query,
|
|
322
|
+
mode: options.mode,
|
|
323
|
+
product: { name: "codali-cli" },
|
|
324
|
+
tenant: context.tenant,
|
|
325
|
+
// Declare discovered MCP tools to the capability compiler. Without this it
|
|
326
|
+
// treats them as `not_declared` and silently drops every one: the compiler
|
|
327
|
+
// predates connectors and otherwise only recognizes built-ins, app
|
|
328
|
+
// contracts, and manifest entries. Discovery is the declaration — these
|
|
329
|
+
// names came from a live `tools/list` against a configured server.
|
|
330
|
+
tools: connectorTools.length > 0
|
|
331
|
+
? { actualTools: connectorTools, readOnlyTools: connectorTools }
|
|
332
|
+
: undefined,
|
|
333
|
+
docdex: {
|
|
334
|
+
enabled: !options.noTools,
|
|
335
|
+
baseUrl: options.docdexBaseUrl ?? context.docdex?.baseUrl,
|
|
336
|
+
repoRoot: context.repo?.root ?? options.workspaceRoot,
|
|
337
|
+
repoId: context.docdex?.repoId,
|
|
338
|
+
},
|
|
339
|
+
policy: {
|
|
340
|
+
allowedTools,
|
|
341
|
+
deniedTools: context.deniedTools,
|
|
342
|
+
maxIterations: maxRounds,
|
|
343
|
+
maxRuntimeMs: deadlineMs,
|
|
344
|
+
maxToolCalls,
|
|
345
|
+
maxModelCalls: DEFAULT_MAX_MODEL_CALLS,
|
|
346
|
+
maxEvidenceItems: 40,
|
|
347
|
+
maxContextPackTokens: 16000,
|
|
348
|
+
allowWrites: false,
|
|
349
|
+
allowShell: false,
|
|
350
|
+
allowDestructiveOperations: false,
|
|
351
|
+
allowOutsideWorkspace: false,
|
|
352
|
+
// Phase 1 resolves two roles; requiring a strictly "large" final model
|
|
353
|
+
// would fail on setups that only expose medium agents.
|
|
354
|
+
requireFinalLargeModel: false,
|
|
355
|
+
allowDegradedFinalAnswer: true,
|
|
356
|
+
},
|
|
357
|
+
response: {
|
|
358
|
+
format: responseSchema ? "json" : (options.responseFormat ?? "text"),
|
|
359
|
+
schema: responseSchema,
|
|
360
|
+
},
|
|
361
|
+
};
|
|
362
|
+
// ---- Run --------------------------------------------------------------
|
|
363
|
+
const gateway = createCodaliGateway({
|
|
364
|
+
provider: primary,
|
|
365
|
+
// Classify and plan on the orchestrator model; only the final answer pays
|
|
366
|
+
// for the large one.
|
|
367
|
+
plannerProvider: orchestratorProvider ?? primary,
|
|
368
|
+
// So the trace names the model that actually ran.
|
|
369
|
+
finalAgent: synthesizer ?? orchestrator,
|
|
370
|
+
agentInventory: inventory.agents,
|
|
371
|
+
toolDescriptors,
|
|
372
|
+
taskRunner: createLocalGatewayTaskRunner({
|
|
373
|
+
provider: worker,
|
|
374
|
+
registry,
|
|
375
|
+
toolContext: {
|
|
376
|
+
workspaceRoot: options.workspaceRoot,
|
|
377
|
+
allowShell: false,
|
|
378
|
+
allowDestructiveOperations: false,
|
|
379
|
+
allowOutsideWorkspace: false,
|
|
380
|
+
},
|
|
381
|
+
onEvent: (event) => {
|
|
382
|
+
if (event.type === "tool_call") {
|
|
383
|
+
tracer.recordToolCall({ taskId: event.taskId, tool: event.tool, args: event.args });
|
|
384
|
+
}
|
|
385
|
+
else if (event.type === "tool_result") {
|
|
386
|
+
tracer.recordToolResult({
|
|
387
|
+
taskId: event.taskId,
|
|
388
|
+
tool: event.tool,
|
|
389
|
+
ok: event.ok,
|
|
390
|
+
latencyMs: event.latencyMs,
|
|
391
|
+
errorCode: event.errorCode,
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
},
|
|
395
|
+
}),
|
|
396
|
+
workerOptions: {
|
|
397
|
+
maxRuntimeMs: deadlineMs,
|
|
398
|
+
maxToolCalls,
|
|
399
|
+
// The state machine defaults to 30s per task, which is fine for a hosted
|
|
400
|
+
// API and far too short for a CLI-backed agent — those routinely take a
|
|
401
|
+
// minute per call, and every task times out before it can call a tool.
|
|
402
|
+
// Derive it from the run deadline instead of guessing a constant.
|
|
403
|
+
perTaskTimeoutMs: Math.max(60000, Math.floor(deadlineMs / 2)),
|
|
404
|
+
// Independent read-only tasks run concurrently. The state machine
|
|
405
|
+
// defaults to serial, which turns a four-source question into four
|
|
406
|
+
// sequential model round trips.
|
|
407
|
+
maxParallelWorkers: DEFAULT_MAX_PARALLEL_WORKERS,
|
|
408
|
+
},
|
|
409
|
+
});
|
|
410
|
+
try {
|
|
411
|
+
const planning = await gateway.plan(request);
|
|
412
|
+
tracer.setRunId(planning.runId);
|
|
413
|
+
tracer.recordPlan({
|
|
414
|
+
capabilities: planning.classifier.capabilities,
|
|
415
|
+
tasks: planning.planner.workerTasks,
|
|
416
|
+
});
|
|
417
|
+
// An ambiguous request stops before any research budget is spent. Guessing
|
|
418
|
+
// which "Bekir" was meant yields a confident answer about the wrong person.
|
|
419
|
+
const clarification = planning.classifier.needsClarification?.trim();
|
|
420
|
+
if (clarification) {
|
|
421
|
+
const clarified = await gateway.buildClarificationResult(request, planning, clarification);
|
|
422
|
+
tracer.finish({
|
|
423
|
+
status: clarified.status,
|
|
424
|
+
completionReason: "needs_clarification",
|
|
425
|
+
});
|
|
426
|
+
if (options.json) {
|
|
427
|
+
write(JSON.stringify(clarified, null, 2));
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
write(`[needs clarification] ${clarified.answer}`);
|
|
431
|
+
}
|
|
432
|
+
if (options.trace) {
|
|
433
|
+
if (options.json)
|
|
434
|
+
process.stderr.write(`\n${tracer.render()}\n`);
|
|
435
|
+
else {
|
|
436
|
+
write("");
|
|
437
|
+
write(tracer.render());
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return { result: clarified, tracer, exitCode: 0 };
|
|
441
|
+
}
|
|
442
|
+
const workers = await gateway.executePlannedWorkerTasks(request, planning);
|
|
443
|
+
tracer.recordRound(1);
|
|
444
|
+
// Surface worker failures. Without this a run whose every task failed still
|
|
445
|
+
// reaches the synthesizer, which correctly reports it cannot answer — and
|
|
446
|
+
// the actual cause (a 400 from a provider, say) never appears anywhere.
|
|
447
|
+
for (const task of workers.workers.taskResults) {
|
|
448
|
+
if (task.status === "failed") {
|
|
449
|
+
tracer.addWarning(`worker_task_failed:${task.taskId}:${task.errorCode ?? "unknown"}:${(task.errorMessage ?? "").slice(0, 300)}`);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
for (const error of workers.workers.errors)
|
|
453
|
+
tracer.addWarning(`worker_error:${error}`);
|
|
454
|
+
const result = await gateway.synthesizeFinalAnswer({
|
|
455
|
+
runId: planning.runId,
|
|
456
|
+
request,
|
|
457
|
+
planning,
|
|
458
|
+
workers: workers.workers,
|
|
459
|
+
});
|
|
460
|
+
tracer.finish({
|
|
461
|
+
status: result.status,
|
|
462
|
+
finalizerMode: String(result.telemetry.finalizerMode ?? "synthesizer"),
|
|
463
|
+
completionReason: result.status === "succeeded" ? "complete" : result.status,
|
|
464
|
+
warnings: result.warnings,
|
|
465
|
+
});
|
|
466
|
+
if (options.json) {
|
|
467
|
+
write(JSON.stringify(result, null, 2));
|
|
468
|
+
}
|
|
469
|
+
else {
|
|
470
|
+
// A partial answer must never read like a complete one.
|
|
471
|
+
if (result.status !== "succeeded") {
|
|
472
|
+
write(`[${result.status}] This answer is incomplete. See warnings below.`);
|
|
473
|
+
write("");
|
|
474
|
+
}
|
|
475
|
+
write(result.answer);
|
|
476
|
+
if (result.sources.length > 0) {
|
|
477
|
+
write("");
|
|
478
|
+
write("Sources:");
|
|
479
|
+
for (const source of result.sources) {
|
|
480
|
+
write(` [${source.evidenceId}] ${source.title ?? source.sourceType}${source.uri ? ` — ${source.uri}` : ""}`);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
if (result.artifacts.length > 0) {
|
|
484
|
+
write("");
|
|
485
|
+
write("Artifacts:");
|
|
486
|
+
for (const artifact of result.artifacts) {
|
|
487
|
+
write(` ${artifact.type}: ${artifact.path ?? artifact.uri ?? artifact.id}`);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
if (result.status !== "succeeded" && result.warnings.length > 0 && !options.trace) {
|
|
491
|
+
write("");
|
|
492
|
+
write("Warnings:");
|
|
493
|
+
for (const warning of result.warnings)
|
|
494
|
+
write(` ${warning}`);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
// With --json, stdout is a machine-readable document. Appending the
|
|
498
|
+
// rendered trace after it makes the output unparseable, so the trace goes
|
|
499
|
+
// to stderr instead of being interleaved.
|
|
500
|
+
if (options.trace) {
|
|
501
|
+
if (options.json) {
|
|
502
|
+
process.stderr.write(`\n${tracer.render()}\n`);
|
|
503
|
+
}
|
|
504
|
+
else {
|
|
505
|
+
write("");
|
|
506
|
+
write(tracer.render());
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
return {
|
|
510
|
+
result,
|
|
511
|
+
tracer,
|
|
512
|
+
exitCode: result.status === "failed" ? 1 : 0,
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
catch (error) {
|
|
516
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
517
|
+
tracer.finish({ status: "failed", completionReason: message });
|
|
518
|
+
write(`Codali run failed: ${message}`);
|
|
519
|
+
if (options.trace)
|
|
520
|
+
write(tracer.render());
|
|
521
|
+
return { tracer, exitCode: 1 };
|
|
522
|
+
}
|
|
523
|
+
finally {
|
|
524
|
+
// stdio MCP servers are child processes. They must be shut down on the
|
|
525
|
+
// failure path too, or a failed run leaves orphans behind.
|
|
526
|
+
await mcp.registry?.close();
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
export const AskCommand = {
|
|
530
|
+
async run(argv) {
|
|
531
|
+
const outcome = await runAsk(argv);
|
|
532
|
+
if (outcome.exitCode !== 0) {
|
|
533
|
+
process.exitCode = outcome.exitCode;
|
|
534
|
+
}
|
|
535
|
+
},
|
|
536
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Writes a key into the credentials file, replacing any existing entry.
|
|
3
|
+
* Rewrites in place rather than appending, so re-authenticating does not leave
|
|
4
|
+
* a stale token above the new one — the parser takes the last occurrence, and
|
|
5
|
+
* relying on that is a trap.
|
|
6
|
+
*/
|
|
7
|
+
export declare const upsertCredential: (key: string, value: string, file?: string) => void;
|
|
8
|
+
export interface AuthDependencies {
|
|
9
|
+
write?: (line: string) => void;
|
|
10
|
+
fetchImpl?: typeof fetch;
|
|
11
|
+
/** Injected in tests so polling does not actually sleep. */
|
|
12
|
+
sleep?: (ms: number) => Promise<void>;
|
|
13
|
+
credsFile?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare const runAuth: (argv: string[], deps?: AuthDependencies) => Promise<number>;
|
|
16
|
+
export declare const AuthCommand: {
|
|
17
|
+
run(argv: string[]): Promise<void>;
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=AuthCommand.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AuthCommand.d.ts","sourceRoot":"","sources":["../../src/cli/AuthCommand.ts"],"names":[],"mappings":"AAmCA;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAC3B,KAAK,MAAM,EACX,OAAO,MAAM,EACb,OAAM,MAAc,KACnB,IAmBF,CAAC;AAEF,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,4DAA4D;IAC5D,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA+FD,eAAO,MAAM,OAAO,GAClB,MAAM,MAAM,EAAE,EACd,OAAM,gBAAqB,KAC1B,OAAO,CAAC,MAAM,CAahB,CAAC;AAEF,eAAO,MAAM,WAAW;cACN,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAIzC,CAAC"}
|