create-quorum-router 0.1.5
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/LICENSE +21 -0
- package/README.md +89 -0
- package/bin/create-quorum-router.js +161 -0
- package/package.json +19 -0
- package/templates/basic/README.md +168 -0
- package/templates/basic/deno.json +19 -0
- package/templates/basic/gitignore +5 -0
- package/templates/basic/main.ts +3 -0
- package/templates/basic/out/.gitkeep +0 -0
- package/templates/basic/router.config.example.json +15 -0
- package/templates/basic/src/agent_chat.ts +262 -0
- package/templates/basic/src/auth.ts +58 -0
- package/templates/basic/src/auth_env_fallback.ts +78 -0
- package/templates/basic/src/auth_oauth.ts +19 -0
- package/templates/basic/src/auth_session.ts +271 -0
- package/templates/basic/src/best_route.ts +322 -0
- package/templates/basic/src/cli.ts +158 -0
- package/templates/basic/src/context.ts +345 -0
- package/templates/basic/src/env.ts +10 -0
- package/templates/basic/src/fixture_smoke.ts +29 -0
- package/templates/basic/src/intake.ts +65 -0
- package/templates/basic/src/model_inventory.ts +59 -0
- package/templates/basic/src/provider_client.ts +12 -0
- package/templates/basic/src/provider_registry.ts +254 -0
- package/templates/basic/src/redact.ts +65 -0
- package/templates/basic/src/schema.ts +138 -0
- package/templates/basic/src/trace.ts +140 -0
- package/templates/basic/src/wrapper_client.ts +224 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import {
|
|
2
|
+
discoverInventoryWithModelListing,
|
|
3
|
+
invokableEntries,
|
|
4
|
+
} from "./auth_session.ts";
|
|
5
|
+
import { callEnvFallback } from "./auth_env_fallback.ts";
|
|
6
|
+
import { readRouterEnv } from "./env.ts";
|
|
7
|
+
import {
|
|
8
|
+
modelSelectionMatches,
|
|
9
|
+
normalizeSelectionValue,
|
|
10
|
+
providerSelectionMatches,
|
|
11
|
+
type ProviderSelectionRequest,
|
|
12
|
+
readProviderSelectionRequest,
|
|
13
|
+
} from "./provider_registry.ts";
|
|
14
|
+
import {
|
|
15
|
+
assertOptIn,
|
|
16
|
+
type DogfoodTrace,
|
|
17
|
+
type ModelInventoryEntry,
|
|
18
|
+
parseAuthMode,
|
|
19
|
+
type ProviderResult,
|
|
20
|
+
} from "./schema.ts";
|
|
21
|
+
import { buildTrace, score, writeTrace } from "./trace.ts";
|
|
22
|
+
import { callWrapper } from "./wrapper_client.ts";
|
|
23
|
+
import { preparePromptWithContext } from "./context.ts";
|
|
24
|
+
|
|
25
|
+
function hasExplicitSelection(request: ProviderSelectionRequest): boolean {
|
|
26
|
+
return Boolean(
|
|
27
|
+
normalizeSelectionValue(request.providerLabel) ||
|
|
28
|
+
normalizeSelectionValue(request.model),
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function availableCandidate(entry: ModelInventoryEntry): string {
|
|
33
|
+
const listed = entry.listed_models?.length
|
|
34
|
+
? ` listed=[${entry.listed_models.join(",")}]`
|
|
35
|
+
: "";
|
|
36
|
+
const blocker = entry.available && entry.can_invoke
|
|
37
|
+
? "ready"
|
|
38
|
+
: `blocked=${
|
|
39
|
+
entry.blocked_reason ?? entry.list_blocked_reason ?? "not invokable"
|
|
40
|
+
}`;
|
|
41
|
+
return `${entry.provider}/${entry.model} (${entry.model_id}) source=${entry.source} command=${
|
|
42
|
+
entry.command ?? "n/a"
|
|
43
|
+
} ${blocker}${listed}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function requestedText(request: ProviderSelectionRequest): string {
|
|
47
|
+
return [
|
|
48
|
+
request.providerLabel ? `provider=${request.providerLabel}` : undefined,
|
|
49
|
+
request.model ? `model=${request.model}` : undefined,
|
|
50
|
+
].filter(Boolean).join(" ") || "none";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function autoCandidateScore(entry: ModelInventoryEntry): number {
|
|
54
|
+
let score = 0;
|
|
55
|
+
if (entry.can_list_models && entry.listed_models?.length) score += 100;
|
|
56
|
+
if (entry.source === "env_fallback") score -= 100;
|
|
57
|
+
return score;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function rankAutoCandidates(
|
|
61
|
+
candidates: ModelInventoryEntry[],
|
|
62
|
+
): ModelInventoryEntry[] {
|
|
63
|
+
return candidates.map((entry, index) => ({ entry, index })).sort((a, b) =>
|
|
64
|
+
autoCandidateScore(b.entry) - autoCandidateScore(a.entry) ||
|
|
65
|
+
a.index - b.index
|
|
66
|
+
).map(({ entry }) => entry);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function withPreferredAutoListedModel(
|
|
70
|
+
entry: ModelInventoryEntry,
|
|
71
|
+
): ModelInventoryEntry {
|
|
72
|
+
const listed = entry.listed_models ?? [];
|
|
73
|
+
const preferred =
|
|
74
|
+
listed.find((model) => model === "grok-composer-2.5-fast") ??
|
|
75
|
+
listed.find((model) => model.includes("composer"));
|
|
76
|
+
if (!preferred) return entry;
|
|
77
|
+
return {
|
|
78
|
+
...entry,
|
|
79
|
+
model: preferred,
|
|
80
|
+
model_id: `${entry.provider.toLowerCase()}/${preferred}`,
|
|
81
|
+
invocation_model: preferred,
|
|
82
|
+
notes: [
|
|
83
|
+
...entry.notes,
|
|
84
|
+
`Auto-selected listed model ${preferred} for this invocation.`,
|
|
85
|
+
],
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function withRequestedListedModel(
|
|
90
|
+
entry: ModelInventoryEntry,
|
|
91
|
+
requestedModel: string | undefined,
|
|
92
|
+
): ModelInventoryEntry {
|
|
93
|
+
const normalizedRequested = normalizeSelectionValue(requestedModel);
|
|
94
|
+
if (!normalizedRequested) return entry;
|
|
95
|
+
const providerPrefix = normalizeSelectionValue(entry.provider)?.replace(
|
|
96
|
+
/[^a-z0-9]+/g,
|
|
97
|
+
"",
|
|
98
|
+
);
|
|
99
|
+
const listed = entry.listed_models ?? [];
|
|
100
|
+
const selected = listed.find((model) => {
|
|
101
|
+
const normalizedModel = normalizeSelectionValue(model);
|
|
102
|
+
return normalizedModel === normalizedRequested ||
|
|
103
|
+
(providerPrefix
|
|
104
|
+
? `${providerPrefix}/${normalizedModel}` === normalizedRequested
|
|
105
|
+
: false);
|
|
106
|
+
});
|
|
107
|
+
if (!selected) return entry;
|
|
108
|
+
return {
|
|
109
|
+
...entry,
|
|
110
|
+
model: selected,
|
|
111
|
+
model_id: `${entry.provider.toLowerCase()}/${selected}`,
|
|
112
|
+
invocation_model: selected,
|
|
113
|
+
notes: [
|
|
114
|
+
...entry.notes,
|
|
115
|
+
`Selected listed model ${selected} for this invocation.`,
|
|
116
|
+
],
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function selectionHonored(
|
|
121
|
+
request: ProviderSelectionRequest,
|
|
122
|
+
selected: ModelInventoryEntry | ProviderResult,
|
|
123
|
+
): boolean {
|
|
124
|
+
if (!hasExplicitSelection(request)) return true;
|
|
125
|
+
const entryLike = {
|
|
126
|
+
provider: selected.provider,
|
|
127
|
+
model: selected.model,
|
|
128
|
+
model_id: selected.model_id ?? selected.model,
|
|
129
|
+
source: selected.source ?? "selected",
|
|
130
|
+
command: selected.command,
|
|
131
|
+
listed_models: selected.listed_models,
|
|
132
|
+
};
|
|
133
|
+
return providerSelectionMatches(entryLike, request.providerLabel) &&
|
|
134
|
+
modelSelectionMatches(entryLike, request.model);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function selectInvokableCandidates(
|
|
138
|
+
candidates: ModelInventoryEntry[],
|
|
139
|
+
_authMode = parseAuthMode(readRouterEnv("QUORUM_ROUTER_AUTH_MODE")),
|
|
140
|
+
request = readProviderSelectionRequest(),
|
|
141
|
+
allEntries: ModelInventoryEntry[] = candidates,
|
|
142
|
+
): ModelInventoryEntry[] {
|
|
143
|
+
if (!hasExplicitSelection(request)) {
|
|
144
|
+
return rankAutoCandidates(candidates).map(withPreferredAutoListedModel);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const filtered = candidates.filter((entry) => {
|
|
148
|
+
const providerMatches = providerSelectionMatches(
|
|
149
|
+
entry,
|
|
150
|
+
request.providerLabel,
|
|
151
|
+
);
|
|
152
|
+
const modelMatches = modelSelectionMatches(entry, request.model);
|
|
153
|
+
return providerMatches && modelMatches;
|
|
154
|
+
}).map((entry) => withRequestedListedModel(entry, request.model));
|
|
155
|
+
|
|
156
|
+
if (filtered.length > 0) return filtered;
|
|
157
|
+
|
|
158
|
+
throw new Error(
|
|
159
|
+
`QuorumRouter blocked: requested provider/model unavailable (${
|
|
160
|
+
requestedText(request)
|
|
161
|
+
}). ` +
|
|
162
|
+
`Available candidates and blockers: ${
|
|
163
|
+
allEntries.map(availableCandidate).join("; ")
|
|
164
|
+
}`,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function discoverCandidates(authMode: ReturnType<typeof parseAuthMode>) {
|
|
169
|
+
const request = readProviderSelectionRequest();
|
|
170
|
+
const inventory = await discoverInventoryWithModelListing(authMode, request);
|
|
171
|
+
const candidates = selectInvokableCandidates(
|
|
172
|
+
invokableEntries(inventory),
|
|
173
|
+
authMode,
|
|
174
|
+
request,
|
|
175
|
+
inventory.entries,
|
|
176
|
+
);
|
|
177
|
+
return { request, inventory, candidates };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export async function invokeSelected(
|
|
181
|
+
prompt: string,
|
|
182
|
+
): Promise<
|
|
183
|
+
{ results: ProviderResult[]; tracePath: string; trace: DogfoodTrace }
|
|
184
|
+
> {
|
|
185
|
+
assertOptIn();
|
|
186
|
+
const authMode = parseAuthMode(readRouterEnv("QUORUM_ROUTER_AUTH_MODE"));
|
|
187
|
+
const { request, candidates } = await discoverCandidates(authMode);
|
|
188
|
+
if (candidates.length === 0) {
|
|
189
|
+
throw new Error(
|
|
190
|
+
"OAuth/session-first provider unavailable. No usable OAuth/session/wrapper provider is available yet. Next: deno task auth:login",
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
const prepared = await preparePromptWithContext(prompt);
|
|
194
|
+
const safePrompt = prepared.prompt;
|
|
195
|
+
const errors: string[] = [];
|
|
196
|
+
let selected: ModelInventoryEntry | undefined;
|
|
197
|
+
let result: ProviderResult | undefined;
|
|
198
|
+
let selectedIndex = -1;
|
|
199
|
+
|
|
200
|
+
for (const [index, candidate] of candidates.entries()) {
|
|
201
|
+
if (!selectionHonored(request, candidate)) {
|
|
202
|
+
throw new Error(
|
|
203
|
+
`QuorumRouter blocked: provider selection was not honored (${
|
|
204
|
+
requestedText(request)
|
|
205
|
+
} selected=${candidate.provider}/${candidate.model})`,
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
try {
|
|
209
|
+
const candidateResult = candidate.source === "env_fallback"
|
|
210
|
+
? await callEnvFallback(safePrompt)
|
|
211
|
+
: await callWrapper(candidate, safePrompt);
|
|
212
|
+
if (!selectionHonored(request, candidateResult)) {
|
|
213
|
+
throw new Error(
|
|
214
|
+
`provider selection was ignored after invocation (${
|
|
215
|
+
requestedText(request)
|
|
216
|
+
} selected=${candidateResult.provider}/${candidateResult.model})`,
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
selected = candidate;
|
|
220
|
+
result = candidateResult;
|
|
221
|
+
selectedIndex = index;
|
|
222
|
+
break;
|
|
223
|
+
} catch (error) {
|
|
224
|
+
errors.push(error instanceof Error ? error.message : String(error));
|
|
225
|
+
if (hasExplicitSelection(request)) break;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (!selected || !result) {
|
|
230
|
+
throw new Error(
|
|
231
|
+
`OAuth/session-first provider unavailable. all candidates failed safely: ${
|
|
232
|
+
errors.join("; ")
|
|
233
|
+
}`,
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const row = score(result);
|
|
238
|
+
const trace = await buildTrace({
|
|
239
|
+
command: "route:once",
|
|
240
|
+
mode: "route_once",
|
|
241
|
+
authMode,
|
|
242
|
+
prompt: safePrompt,
|
|
243
|
+
promptContext: prepared.context,
|
|
244
|
+
results: [result],
|
|
245
|
+
selected: row,
|
|
246
|
+
scores: [row],
|
|
247
|
+
errors,
|
|
248
|
+
requestedProviderLabel: request.providerLabel,
|
|
249
|
+
requestedModel: request.model,
|
|
250
|
+
providerSelectionHonored: selectionHonored(request, result),
|
|
251
|
+
fallbackUsed: selectedIndex > 0 || selected.source === "env_fallback",
|
|
252
|
+
});
|
|
253
|
+
const tracePath = await writeTrace("route-once-trace", trace);
|
|
254
|
+
return { results: [result], tracePath, trace };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export async function runBestRoute(
|
|
258
|
+
prompt: string,
|
|
259
|
+
): Promise<
|
|
260
|
+
{ results: ProviderResult[]; tracePath: string; trace: DogfoodTrace }
|
|
261
|
+
> {
|
|
262
|
+
assertOptIn();
|
|
263
|
+
const authMode = parseAuthMode(readRouterEnv("QUORUM_ROUTER_AUTH_MODE"));
|
|
264
|
+
const { request, candidates } = await discoverCandidates(authMode);
|
|
265
|
+
if (candidates.length === 0) {
|
|
266
|
+
throw new Error(
|
|
267
|
+
"OAuth/session-first provider unavailable. best-route has no usable OAuth/session/wrapper provider yet. Next: deno task auth:login",
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
const prepared = await preparePromptWithContext(prompt);
|
|
271
|
+
const safePrompt = prepared.prompt;
|
|
272
|
+
const results: ProviderResult[] = [];
|
|
273
|
+
const errors: string[] = [];
|
|
274
|
+
let usedEnvFallback = false;
|
|
275
|
+
for (const candidate of candidates) {
|
|
276
|
+
try {
|
|
277
|
+
const result = candidate.source === "env_fallback"
|
|
278
|
+
? await callEnvFallback(safePrompt)
|
|
279
|
+
: await callWrapper(candidate, safePrompt);
|
|
280
|
+
usedEnvFallback = usedEnvFallback || candidate.source === "env_fallback";
|
|
281
|
+
if (!selectionHonored(request, result)) {
|
|
282
|
+
throw new Error(
|
|
283
|
+
`provider selection ignored (${
|
|
284
|
+
requestedText(request)
|
|
285
|
+
} selected=${result.provider}/${result.model})`,
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
results.push(result);
|
|
289
|
+
} catch (error) {
|
|
290
|
+
errors.push(error instanceof Error ? error.message : String(error));
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
if (results.length === 0) {
|
|
294
|
+
throw new Error(
|
|
295
|
+
`OAuth/session-first provider unavailable. all candidates failed safely: ${
|
|
296
|
+
errors.join("; ")
|
|
297
|
+
}`,
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
const scores = results.map(score).sort((a, b) =>
|
|
301
|
+
b.final_score - a.final_score
|
|
302
|
+
);
|
|
303
|
+
const trace = await buildTrace({
|
|
304
|
+
command: "best-route",
|
|
305
|
+
mode: "best_route",
|
|
306
|
+
authMode,
|
|
307
|
+
prompt: safePrompt,
|
|
308
|
+
promptContext: prepared.context,
|
|
309
|
+
results,
|
|
310
|
+
selected: scores[0],
|
|
311
|
+
scores,
|
|
312
|
+
errors,
|
|
313
|
+
requestedProviderLabel: request.providerLabel,
|
|
314
|
+
requestedModel: request.model,
|
|
315
|
+
providerSelectionHonored: results.every((result) =>
|
|
316
|
+
selectionHonored(request, result)
|
|
317
|
+
),
|
|
318
|
+
fallbackUsed: usedEnvFallback,
|
|
319
|
+
});
|
|
320
|
+
const tracePath = await writeTrace("best-route-trace", trace);
|
|
321
|
+
return { results, tracePath, trace };
|
|
322
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { runAgentChat } from "./agent_chat.ts";
|
|
2
|
+
import { readRouterEnv } from "./env.ts";
|
|
3
|
+
import { runAuthLogin, runAuthLogout, runAuthStatus } from "./auth.ts";
|
|
4
|
+
import { discoverInventory, invokableEntries } from "./auth_session.ts";
|
|
5
|
+
import { invokeSelected, runBestRoute } from "./best_route.ts";
|
|
6
|
+
import { runIntake } from "./intake.ts";
|
|
7
|
+
import {
|
|
8
|
+
inventoryCommand,
|
|
9
|
+
printInventoryTable,
|
|
10
|
+
writeInventory,
|
|
11
|
+
} from "./model_inventory.ts";
|
|
12
|
+
import { redact, summarize } from "./redact.ts";
|
|
13
|
+
import { parseAuthMode } from "./schema.ts";
|
|
14
|
+
import { buildTrace, writeTrace } from "./trace.ts";
|
|
15
|
+
|
|
16
|
+
const DEFAULT_PROMPT = "Review this README for risky claims.";
|
|
17
|
+
|
|
18
|
+
type CommandName =
|
|
19
|
+
| "intake"
|
|
20
|
+
| "auth:status"
|
|
21
|
+
| "auth:login"
|
|
22
|
+
| "auth:logout"
|
|
23
|
+
| "models:list"
|
|
24
|
+
| "health"
|
|
25
|
+
| "route:once"
|
|
26
|
+
| "best-route"
|
|
27
|
+
| "agent-chat";
|
|
28
|
+
|
|
29
|
+
function commandName(): CommandName {
|
|
30
|
+
const command = Deno.args[0];
|
|
31
|
+
if (
|
|
32
|
+
command === "intake" || command === "auth:status" ||
|
|
33
|
+
command === "auth:login" || command === "auth:logout" ||
|
|
34
|
+
command === "models:list" || command === "health" ||
|
|
35
|
+
command === "route:once" || command === "best-route" ||
|
|
36
|
+
command === "agent-chat"
|
|
37
|
+
) return command;
|
|
38
|
+
throw new Error(
|
|
39
|
+
"usage: deno task intake|auth:status|auth:login|auth:logout|models:list|health|route:once|best-route|agent-chat",
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function promptFromArgs(): string {
|
|
44
|
+
const promptIndex = Deno.args.indexOf("--prompt");
|
|
45
|
+
if (promptIndex >= 0) {
|
|
46
|
+
const value = Deno.args[promptIndex + 1]?.trim();
|
|
47
|
+
if (value) return value;
|
|
48
|
+
}
|
|
49
|
+
const passthroughIndex = Deno.args.indexOf("--");
|
|
50
|
+
if (passthroughIndex >= 0) {
|
|
51
|
+
const value = Deno.args.slice(passthroughIndex + 1).join(" ").trim();
|
|
52
|
+
if (value) return value;
|
|
53
|
+
}
|
|
54
|
+
return DEFAULT_PROMPT;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function runModelsList(): Promise<void> {
|
|
58
|
+
const inventory = await inventoryCommand();
|
|
59
|
+
console.log("QuorumRouter model inventory");
|
|
60
|
+
console.log("Provider request sent: false");
|
|
61
|
+
console.log("Generation endpoint called: false");
|
|
62
|
+
console.log("Credential values printed: false");
|
|
63
|
+
printInventoryTable(inventory);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function runHealth(): Promise<void> {
|
|
67
|
+
const authMode = parseAuthMode(readRouterEnv("QUORUM_ROUTER_AUTH_MODE"));
|
|
68
|
+
const inventory = discoverInventory(authMode);
|
|
69
|
+
await writeInventory(inventory);
|
|
70
|
+
const invokable = invokableEntries(inventory);
|
|
71
|
+
const trace = await buildTrace({
|
|
72
|
+
command: "health",
|
|
73
|
+
mode: "health",
|
|
74
|
+
authMode,
|
|
75
|
+
errors: invokable.length === 0
|
|
76
|
+
? ["no invokable OAuth/session/wrapper provider discovered"]
|
|
77
|
+
: [],
|
|
78
|
+
});
|
|
79
|
+
const tracePath = await writeTrace("health-trace", trace);
|
|
80
|
+
console.log("QuorumRouter health");
|
|
81
|
+
console.log(
|
|
82
|
+
`Config example present: ${await Deno.stat("router.config.example.json")
|
|
83
|
+
.then((s) => s.isFile).catch(
|
|
84
|
+
() => false,
|
|
85
|
+
)}`,
|
|
86
|
+
);
|
|
87
|
+
console.log(`Model inventory entries: ${inventory.entries.length}`);
|
|
88
|
+
console.log(`Usable providers: ${invokable.length}`);
|
|
89
|
+
console.log("Provider request sent: false");
|
|
90
|
+
console.log(`Trace: ${tracePath}`);
|
|
91
|
+
console.log(
|
|
92
|
+
'Recommended dogfood: RUN_EXTERNAL_MODEL_DOGFOOD=1 deno task route:once --prompt "Review this README for risky claims."',
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const command = commandName();
|
|
98
|
+
if (command === "intake") await runIntake();
|
|
99
|
+
else if (command === "auth:status") await runAuthStatus();
|
|
100
|
+
else if (command === "auth:login") runAuthLogin();
|
|
101
|
+
else if (command === "auth:logout") await runAuthLogout();
|
|
102
|
+
else if (command === "models:list") await runModelsList();
|
|
103
|
+
else if (command === "health") await runHealth();
|
|
104
|
+
else if (command === "route:once") {
|
|
105
|
+
const { results, tracePath, trace } = await invokeSelected(
|
|
106
|
+
promptFromArgs(),
|
|
107
|
+
);
|
|
108
|
+
console.log("QuorumRouter route:once");
|
|
109
|
+
console.log(`provider: ${results[0].provider}`);
|
|
110
|
+
console.log(`model: ${results[0].model}`);
|
|
111
|
+
console.log(`response_received: ${results[0].response_received}`);
|
|
112
|
+
console.log(`schema_valid: ${results[0].schema_valid}`);
|
|
113
|
+
console.log(`redaction_ok: ${trace.redaction_ok}`);
|
|
114
|
+
console.log(`credential_value_present: ${trace.credential_value_present}`);
|
|
115
|
+
console.log(`sensitive_value_present: ${trace.sensitive_value_present}`);
|
|
116
|
+
console.log(`final: ${summarize(results[0].response_summary, 500)}`);
|
|
117
|
+
console.log(`trace: ${tracePath}`);
|
|
118
|
+
} else if (command === "best-route") {
|
|
119
|
+
const { results, tracePath } = await runBestRoute(promptFromArgs());
|
|
120
|
+
console.log("QuorumRouter best-route");
|
|
121
|
+
console.log(`models_called: ${results.length}`);
|
|
122
|
+
console.log(`trace: ${tracePath}`);
|
|
123
|
+
} else if (command === "agent-chat") {
|
|
124
|
+
console.log("QuorumRouter live multi-model Agent Chat");
|
|
125
|
+
const { tracePath, turns } = await runAgentChat(
|
|
126
|
+
promptFromArgs(),
|
|
127
|
+
(progress) => {
|
|
128
|
+
if (progress.type === "start") {
|
|
129
|
+
console.log(
|
|
130
|
+
`agents: ${
|
|
131
|
+
progress.agents.map((agent) => `${agent.provider}/${agent.model}`)
|
|
132
|
+
.join(" ↔ ")
|
|
133
|
+
}`,
|
|
134
|
+
);
|
|
135
|
+
console.log("");
|
|
136
|
+
} else if (progress.type === "turn") {
|
|
137
|
+
const turn = progress.turn;
|
|
138
|
+
console.log(`Round ${turn.round}`);
|
|
139
|
+
console.log(
|
|
140
|
+
`${turn.provider}/${turn.model}${
|
|
141
|
+
turn.reply_to
|
|
142
|
+
? ` → replying to ${turn.reply_to.provider}/${turn.reply_to.model} (round ${turn.reply_to.round})`
|
|
143
|
+
: " → opening proposal"
|
|
144
|
+
}`,
|
|
145
|
+
);
|
|
146
|
+
console.log(turn.content);
|
|
147
|
+
console.log("");
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
);
|
|
151
|
+
console.log(`dialogue_complete: true`);
|
|
152
|
+
console.log(`turns: ${turns.length}`);
|
|
153
|
+
console.log(`trace: ${tracePath}`);
|
|
154
|
+
}
|
|
155
|
+
} catch (error) {
|
|
156
|
+
console.error(redact(error instanceof Error ? error.message : String(error)));
|
|
157
|
+
Deno.exit(1);
|
|
158
|
+
}
|