@abelxiaoxing/cadence 1.8.2 → 1.8.4
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/README.md +71 -531
- package/agents/design-explorer.md +15 -23
- package/agents/diagnosis-worker.md +12 -23
- package/agents/implementation-worker.md +16 -34
- package/config/.env.example +2 -5
- package/package.json +1 -1
- package/prompts/abel-diagnose.md +5 -5
- package/src/candidate-context.ts +289 -0
- package/src/contracts.ts +12 -22
- package/src/durable-contracts.ts +1 -5
- package/src/durable-workflow.ts +3 -159
- package/src/evidence-draft.ts +1 -1
- package/src/index.ts +12 -16
- package/src/package-candidate-process.ts +206 -0
- package/src/package-candidate.ts +11 -214
- package/src/package-workflow.ts +5 -42
- package/src/packet-runtime.ts +144 -255
- package/src/phase-execution.ts +134 -230
- package/src/startup-card.ts +17 -23
- package/src/subagent-process.ts +476 -0
- package/src/transport-budget.ts +1 -3
- package/src/workflow-policy.ts +9 -18
- package/src/workflow-state-machine.ts +1 -15
- package/config/routes.example.json +0 -39
- package/src/child-budget.ts +0 -58
- package/src/child-model.ts +0 -142
- package/src/child-session.ts +0 -640
- package/src/parent-provider.ts +0 -375
- package/src/route-policy.ts +0 -545
- package/src/submit-tool.ts +0 -950
- package/src/worker-broker.ts +0 -806
package/src/route-policy.ts
DELETED
|
@@ -1,545 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { lstatSync, readFileSync } from "node:fs";
|
|
3
|
-
import { homedir } from "node:os";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import { canonicalJson } from "./canonical.ts";
|
|
6
|
-
import { ROLES } from "./contracts.ts";
|
|
7
|
-
|
|
8
|
-
export const ROUTE_DIALECTS = [
|
|
9
|
-
"openai-completions",
|
|
10
|
-
"openai-responses",
|
|
11
|
-
"anthropic-messages",
|
|
12
|
-
] as const;
|
|
13
|
-
|
|
14
|
-
export type WorkerRole = (typeof ROLES)[number];
|
|
15
|
-
export type RouteDialect = (typeof ROUTE_DIALECTS)[number];
|
|
16
|
-
|
|
17
|
-
export interface RouteCapabilities {
|
|
18
|
-
roles: WorkerRole[];
|
|
19
|
-
dialects: RouteDialect[];
|
|
20
|
-
contextWindow: number;
|
|
21
|
-
maxTokens: number;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface ParentModelCapabilities {
|
|
25
|
-
contextWindow: number;
|
|
26
|
-
maxTokens: number;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
interface RouteBase {
|
|
30
|
-
id: string;
|
|
31
|
-
kind: "inherited" | "custom";
|
|
32
|
-
capabilities: RouteCapabilities;
|
|
33
|
-
fingerprint: string;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface InheritedRoutePolicy extends RouteBase {
|
|
37
|
-
kind: "inherited";
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export interface CustomRoutePolicy extends RouteBase {
|
|
41
|
-
kind: "custom";
|
|
42
|
-
url: string;
|
|
43
|
-
model: string;
|
|
44
|
-
dialect: RouteDialect;
|
|
45
|
-
apiKeyEnv?: string;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export type WorkerRoutePolicy = InheritedRoutePolicy | CustomRoutePolicy;
|
|
49
|
-
|
|
50
|
-
export interface RoutePolicy {
|
|
51
|
-
routes: Readonly<Record<string, WorkerRoutePolicy>>;
|
|
52
|
-
roles: Readonly<Record<WorkerRole, readonly string[]>>;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export type RoutePolicySource =
|
|
56
|
-
| { kind: "project" | "user"; path: string }
|
|
57
|
-
| { kind: "default" };
|
|
58
|
-
|
|
59
|
-
export type RoutePolicyDiagnosticCode =
|
|
60
|
-
| "policy-unreadable"
|
|
61
|
-
| "policy-invalid-json"
|
|
62
|
-
| "policy-invalid"
|
|
63
|
-
| "policy-version-unsupported"
|
|
64
|
-
| "route-invalid"
|
|
65
|
-
| "route-duplicate"
|
|
66
|
-
| "role-invalid"
|
|
67
|
-
| "route-reference-invalid";
|
|
68
|
-
|
|
69
|
-
export interface RoutePolicyDiagnostic {
|
|
70
|
-
code: RoutePolicyDiagnosticCode;
|
|
71
|
-
field?: string;
|
|
72
|
-
routeId?: string;
|
|
73
|
-
role?: WorkerRole;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export type ParsedRoutePolicy =
|
|
77
|
-
| { ok: true; policy: RoutePolicy }
|
|
78
|
-
| { ok: false; diagnostics: RoutePolicyDiagnostic[] };
|
|
79
|
-
|
|
80
|
-
export type RoutePolicyResolution =
|
|
81
|
-
| {
|
|
82
|
-
ok: true;
|
|
83
|
-
source: RoutePolicySource;
|
|
84
|
-
policy: RoutePolicy;
|
|
85
|
-
}
|
|
86
|
-
| {
|
|
87
|
-
ok: false;
|
|
88
|
-
source: RoutePolicySource;
|
|
89
|
-
diagnostics: RoutePolicyDiagnostic[];
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Internal fail-closed policy used while a selected explicit policy is
|
|
94
|
-
* invalid. It deliberately contains no inherited-parent route:
|
|
95
|
-
* the broker therefore reports endpoint-unavailable without making a Worker
|
|
96
|
-
* request, while the local durable control plane remains available.
|
|
97
|
-
*
|
|
98
|
-
* This value is not accepted by parseRoutePolicy and is never persisted as a
|
|
99
|
-
* user policy. A corrected external policy replaces it before the next
|
|
100
|
-
* control operation.
|
|
101
|
-
*/
|
|
102
|
-
export function unavailableRoutePolicy(): RoutePolicy {
|
|
103
|
-
return {
|
|
104
|
-
routes: Object.freeze({}),
|
|
105
|
-
roles: Object.freeze(
|
|
106
|
-
Object.fromEntries(
|
|
107
|
-
ROLES.map((role) => [role, Object.freeze([] as string[])]),
|
|
108
|
-
),
|
|
109
|
-
) as unknown as RoutePolicy["roles"],
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const ROUTE_ID = /^[a-z0-9](?:[a-z0-9._-]{0,126}[a-z0-9])?$/iu;
|
|
114
|
-
const MODEL_ID = /^[a-z0-9][a-z0-9._:/-]{0,255}$/iu;
|
|
115
|
-
const ENV_NAME = /^[A-Z_][A-Z0-9_]*$/u;
|
|
116
|
-
const MAX_POLICY_BYTES = 1024 * 1024;
|
|
117
|
-
|
|
118
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
119
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function hasExactKeys(
|
|
123
|
-
value: Record<string, unknown>,
|
|
124
|
-
required: readonly string[],
|
|
125
|
-
optional: readonly string[] = [],
|
|
126
|
-
): boolean {
|
|
127
|
-
const allowed = new Set([...required, ...optional]);
|
|
128
|
-
return (
|
|
129
|
-
required.every((key) => Object.hasOwn(value, key)) &&
|
|
130
|
-
Object.keys(value).every((key) => allowed.has(key))
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function uniqueStringArray(
|
|
135
|
-
value: unknown,
|
|
136
|
-
allowed?: readonly string[],
|
|
137
|
-
): value is string[] {
|
|
138
|
-
return (
|
|
139
|
-
Array.isArray(value) &&
|
|
140
|
-
value.length > 0 &&
|
|
141
|
-
value.length <= 64 &&
|
|
142
|
-
value.every(
|
|
143
|
-
(entry) =>
|
|
144
|
-
typeof entry === "string" &&
|
|
145
|
-
ROUTE_ID.test(entry) &&
|
|
146
|
-
(!allowed || allowed.includes(entry)),
|
|
147
|
-
) &&
|
|
148
|
-
new Set(value).size === value.length
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function positiveInteger(value: unknown): value is number {
|
|
153
|
-
return Number.isSafeInteger(value) && (value as number) > 0;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function httpUrl(value: unknown): value is string {
|
|
157
|
-
if (typeof value !== "string" || value.length > 2048) return false;
|
|
158
|
-
try {
|
|
159
|
-
const parsed = new URL(value);
|
|
160
|
-
return (
|
|
161
|
-
(parsed.protocol === "http:" || parsed.protocol === "https:") &&
|
|
162
|
-
parsed.username === "" &&
|
|
163
|
-
parsed.password === ""
|
|
164
|
-
);
|
|
165
|
-
} catch {
|
|
166
|
-
return false;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function routeFingerprint(route: Record<string, unknown>): string {
|
|
171
|
-
return createHash("sha256").update(canonicalJson(route)).digest("hex");
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function parseCapabilities(value: unknown): RouteCapabilities | null {
|
|
175
|
-
if (
|
|
176
|
-
!isRecord(value) ||
|
|
177
|
-
!hasExactKeys(value, ["roles", "dialects", "contextWindow", "maxTokens"]) ||
|
|
178
|
-
!uniqueStringArray(value.roles, ROLES) ||
|
|
179
|
-
!uniqueStringArray(value.dialects, ROUTE_DIALECTS) ||
|
|
180
|
-
!positiveInteger(value.contextWindow) ||
|
|
181
|
-
!positiveInteger(value.maxTokens) ||
|
|
182
|
-
value.maxTokens > value.contextWindow
|
|
183
|
-
) {
|
|
184
|
-
return null;
|
|
185
|
-
}
|
|
186
|
-
return {
|
|
187
|
-
roles: [...value.roles] as WorkerRole[],
|
|
188
|
-
dialects: [...value.dialects] as RouteDialect[],
|
|
189
|
-
contextWindow: value.contextWindow,
|
|
190
|
-
maxTokens: value.maxTokens,
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function parseRoute(
|
|
195
|
-
id: string,
|
|
196
|
-
value: unknown,
|
|
197
|
-
): WorkerRoutePolicy | RoutePolicyDiagnostic {
|
|
198
|
-
if (!ROUTE_ID.test(id) || !isRecord(value)) {
|
|
199
|
-
return {
|
|
200
|
-
code: "route-invalid",
|
|
201
|
-
routeId: ROUTE_ID.test(id) ? id : undefined,
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
const capabilities = parseCapabilities(value.capabilities);
|
|
205
|
-
if (!capabilities) {
|
|
206
|
-
return { code: "route-invalid", routeId: id, field: "capabilities" };
|
|
207
|
-
}
|
|
208
|
-
if (value.kind === "inherited") {
|
|
209
|
-
if (!hasExactKeys(value, ["kind", "capabilities"])) {
|
|
210
|
-
return { code: "route-invalid", routeId: id };
|
|
211
|
-
}
|
|
212
|
-
const raw = { kind: "inherited" as const, capabilities };
|
|
213
|
-
return {
|
|
214
|
-
id,
|
|
215
|
-
...raw,
|
|
216
|
-
fingerprint: routeFingerprint(raw),
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
if (value.kind !== "custom") {
|
|
220
|
-
return { code: "route-invalid", routeId: id, field: "kind" };
|
|
221
|
-
}
|
|
222
|
-
if (
|
|
223
|
-
!hasExactKeys(
|
|
224
|
-
value,
|
|
225
|
-
["kind", "url", "model", "dialect", "capabilities"],
|
|
226
|
-
["apiKeyEnv"],
|
|
227
|
-
) ||
|
|
228
|
-
!httpUrl(value.url) ||
|
|
229
|
-
typeof value.model !== "string" ||
|
|
230
|
-
!MODEL_ID.test(value.model) ||
|
|
231
|
-
!(ROUTE_DIALECTS as readonly unknown[]).includes(value.dialect) ||
|
|
232
|
-
!capabilities.dialects.includes(value.dialect as RouteDialect) ||
|
|
233
|
-
(value.apiKeyEnv !== undefined &&
|
|
234
|
-
(typeof value.apiKeyEnv !== "string" || !ENV_NAME.test(value.apiKeyEnv)))
|
|
235
|
-
) {
|
|
236
|
-
const field = !httpUrl(value.url)
|
|
237
|
-
? "url"
|
|
238
|
-
: typeof value.model !== "string" || !MODEL_ID.test(value.model)
|
|
239
|
-
? "model"
|
|
240
|
-
: !(ROUTE_DIALECTS as readonly unknown[]).includes(value.dialect)
|
|
241
|
-
? "dialect"
|
|
242
|
-
: value.apiKeyEnv !== undefined &&
|
|
243
|
-
(typeof value.apiKeyEnv !== "string" ||
|
|
244
|
-
!ENV_NAME.test(value.apiKeyEnv))
|
|
245
|
-
? "apiKeyEnv"
|
|
246
|
-
: undefined;
|
|
247
|
-
return { code: "route-invalid", routeId: id, ...(field ? { field } : {}) };
|
|
248
|
-
}
|
|
249
|
-
const raw = {
|
|
250
|
-
kind: "custom" as const,
|
|
251
|
-
url: value.url,
|
|
252
|
-
model: value.model,
|
|
253
|
-
dialect: value.dialect as RouteDialect,
|
|
254
|
-
...(typeof value.apiKeyEnv === "string"
|
|
255
|
-
? { apiKeyEnv: value.apiKeyEnv }
|
|
256
|
-
: {}),
|
|
257
|
-
capabilities,
|
|
258
|
-
};
|
|
259
|
-
return { id, ...raw, fingerprint: routeFingerprint(raw) };
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
export function parseRoutePolicy(value: unknown): ParsedRoutePolicy {
|
|
263
|
-
if (!isRecord(value)) {
|
|
264
|
-
return { ok: false, diagnostics: [{ code: "policy-invalid" }] };
|
|
265
|
-
}
|
|
266
|
-
const legacyVersion = Object.hasOwn(value, "version")
|
|
267
|
-
? value.version
|
|
268
|
-
: undefined;
|
|
269
|
-
if (legacyVersion !== undefined && legacyVersion !== 2) {
|
|
270
|
-
return {
|
|
271
|
-
ok: false,
|
|
272
|
-
diagnostics: [{ code: "policy-version-unsupported", field: "version" }],
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
if (!hasExactKeys(value, ["routes", "roles"], ["version"])) {
|
|
276
|
-
return { ok: false, diagnostics: [{ code: "policy-invalid" }] };
|
|
277
|
-
}
|
|
278
|
-
if (!isRecord(value.routes) || !isRecord(value.roles)) {
|
|
279
|
-
return { ok: false, diagnostics: [{ code: "policy-invalid" }] };
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
const diagnostics: RoutePolicyDiagnostic[] = [];
|
|
283
|
-
const routes: Record<string, WorkerRoutePolicy> = {};
|
|
284
|
-
for (const [id, candidate] of Object.entries(value.routes)) {
|
|
285
|
-
const parsed = parseRoute(id, candidate);
|
|
286
|
-
if ("code" in parsed) diagnostics.push(parsed);
|
|
287
|
-
else routes[id] = parsed;
|
|
288
|
-
}
|
|
289
|
-
if (Object.keys(routes).length !== Object.keys(value.routes).length) {
|
|
290
|
-
return { ok: false, diagnostics };
|
|
291
|
-
}
|
|
292
|
-
const fingerprints = new Map<string, string>();
|
|
293
|
-
for (const route of Object.values(routes)) {
|
|
294
|
-
const prior = fingerprints.get(route.fingerprint);
|
|
295
|
-
if (prior) {
|
|
296
|
-
diagnostics.push({
|
|
297
|
-
code: "route-duplicate",
|
|
298
|
-
routeId: route.id,
|
|
299
|
-
field: prior,
|
|
300
|
-
});
|
|
301
|
-
} else {
|
|
302
|
-
fingerprints.set(route.fingerprint, route.id);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
const roleMap = value.roles as Record<string, unknown>;
|
|
306
|
-
if (
|
|
307
|
-
Object.keys(value.roles).length !== ROLES.length ||
|
|
308
|
-
!ROLES.every((role) => Object.hasOwn(roleMap, role))
|
|
309
|
-
) {
|
|
310
|
-
diagnostics.push({ code: "role-invalid" });
|
|
311
|
-
} else {
|
|
312
|
-
for (const role of ROLES) {
|
|
313
|
-
const routeIds = roleMap[role];
|
|
314
|
-
if (!uniqueStringArray(routeIds)) {
|
|
315
|
-
diagnostics.push({ code: "role-invalid", role });
|
|
316
|
-
continue;
|
|
317
|
-
}
|
|
318
|
-
for (const routeId of routeIds) {
|
|
319
|
-
const route = routes[routeId];
|
|
320
|
-
if (!route?.capabilities.roles.includes(role)) {
|
|
321
|
-
diagnostics.push({
|
|
322
|
-
code: "route-reference-invalid",
|
|
323
|
-
role,
|
|
324
|
-
routeId,
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
if (diagnostics.length > 0) return { ok: false, diagnostics };
|
|
331
|
-
return {
|
|
332
|
-
ok: true,
|
|
333
|
-
policy: {
|
|
334
|
-
routes: Object.freeze(structuredClone(routes)),
|
|
335
|
-
roles: Object.freeze(
|
|
336
|
-
Object.fromEntries(
|
|
337
|
-
ROLES.map((role) => [
|
|
338
|
-
role,
|
|
339
|
-
Object.freeze([...(roleMap[role] as string[])]),
|
|
340
|
-
]),
|
|
341
|
-
),
|
|
342
|
-
) as unknown as RoutePolicy["roles"],
|
|
343
|
-
},
|
|
344
|
-
};
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
function constrainInheritedRoutes(
|
|
348
|
-
policy: RoutePolicy,
|
|
349
|
-
parentModel?: ParentModelCapabilities,
|
|
350
|
-
): RoutePolicy {
|
|
351
|
-
if (!parentModel) return policy;
|
|
352
|
-
const routes = Object.fromEntries(
|
|
353
|
-
Object.entries(policy.routes).map(([routeId, route]) => {
|
|
354
|
-
if (route.kind !== "inherited") return [routeId, structuredClone(route)];
|
|
355
|
-
const capabilities = {
|
|
356
|
-
...structuredClone(route.capabilities),
|
|
357
|
-
contextWindow: Math.min(
|
|
358
|
-
route.capabilities.contextWindow,
|
|
359
|
-
parentModel.contextWindow,
|
|
360
|
-
),
|
|
361
|
-
maxTokens: Math.min(
|
|
362
|
-
route.capabilities.maxTokens,
|
|
363
|
-
parentModel.maxTokens,
|
|
364
|
-
route.capabilities.contextWindow,
|
|
365
|
-
parentModel.contextWindow,
|
|
366
|
-
),
|
|
367
|
-
};
|
|
368
|
-
const raw = { kind: "inherited" as const, capabilities };
|
|
369
|
-
return [
|
|
370
|
-
routeId,
|
|
371
|
-
{
|
|
372
|
-
id: route.id,
|
|
373
|
-
...raw,
|
|
374
|
-
fingerprint: routeFingerprint(raw),
|
|
375
|
-
},
|
|
376
|
-
];
|
|
377
|
-
}),
|
|
378
|
-
);
|
|
379
|
-
return {
|
|
380
|
-
routes: Object.freeze(routes),
|
|
381
|
-
roles: Object.freeze(
|
|
382
|
-
Object.fromEntries(
|
|
383
|
-
ROLES.map((role) => [role, Object.freeze([...policy.roles[role]])]),
|
|
384
|
-
),
|
|
385
|
-
) as unknown as RoutePolicy["roles"],
|
|
386
|
-
};
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
/** Default parent-model route used when no explicit policy file exists. */
|
|
390
|
-
export function parentRoutePolicy(
|
|
391
|
-
parentModel?: ParentModelCapabilities,
|
|
392
|
-
): RoutePolicy {
|
|
393
|
-
const contextWindow = positiveInteger(parentModel?.contextWindow)
|
|
394
|
-
? parentModel.contextWindow
|
|
395
|
-
: 1;
|
|
396
|
-
const maxTokens = positiveInteger(parentModel?.maxTokens)
|
|
397
|
-
? Math.min(parentModel.maxTokens, contextWindow)
|
|
398
|
-
: 1;
|
|
399
|
-
const parsed = parseRoutePolicy({
|
|
400
|
-
routes: {
|
|
401
|
-
parent: {
|
|
402
|
-
kind: "inherited",
|
|
403
|
-
capabilities: {
|
|
404
|
-
roles: [...ROLES],
|
|
405
|
-
dialects: [...ROUTE_DIALECTS],
|
|
406
|
-
contextWindow,
|
|
407
|
-
maxTokens,
|
|
408
|
-
},
|
|
409
|
-
},
|
|
410
|
-
},
|
|
411
|
-
roles: Object.fromEntries(ROLES.map((role) => [role, ["parent"]])),
|
|
412
|
-
});
|
|
413
|
-
if (!parsed.ok) throw new Error("parent route policy is invalid");
|
|
414
|
-
return parsed.policy;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
function selectedSource(
|
|
418
|
-
cwd: string,
|
|
419
|
-
home: string,
|
|
420
|
-
): { kind: "project" | "user"; path: string } | { kind: "none" } {
|
|
421
|
-
const mayExist = (candidate: string): boolean => {
|
|
422
|
-
try {
|
|
423
|
-
return lstatSync(candidate, { throwIfNoEntry: false }) !== undefined;
|
|
424
|
-
} catch {
|
|
425
|
-
// Permission and path-observation failures are explicit unreadable
|
|
426
|
-
// policy candidates, not proof that the policy is absent.
|
|
427
|
-
return true;
|
|
428
|
-
}
|
|
429
|
-
};
|
|
430
|
-
const projectPath = path.join(cwd, ".pi", "cadence", "routes.json");
|
|
431
|
-
if (mayExist(projectPath)) return { kind: "project", path: projectPath };
|
|
432
|
-
const userPath = path.join(home, ".pi", "agent", "cadence", "routes.json");
|
|
433
|
-
if (mayExist(userPath)) return { kind: "user", path: userPath };
|
|
434
|
-
return { kind: "none" };
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
export function loadRoutePolicy(
|
|
438
|
-
options: {
|
|
439
|
-
cwd?: string;
|
|
440
|
-
home?: string;
|
|
441
|
-
parentModel?: ParentModelCapabilities;
|
|
442
|
-
} = {},
|
|
443
|
-
): RoutePolicyResolution {
|
|
444
|
-
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
445
|
-
const home = path.resolve(options.home ?? homedir());
|
|
446
|
-
const source = selectedSource(cwd, home);
|
|
447
|
-
if (source.kind === "none") {
|
|
448
|
-
return {
|
|
449
|
-
ok: true,
|
|
450
|
-
source: { kind: "default" },
|
|
451
|
-
policy: parentRoutePolicy(options.parentModel),
|
|
452
|
-
};
|
|
453
|
-
}
|
|
454
|
-
let bytes: string;
|
|
455
|
-
try {
|
|
456
|
-
const stat = lstatSync(source.path);
|
|
457
|
-
if (
|
|
458
|
-
!stat.isFile() ||
|
|
459
|
-
stat.isSymbolicLink() ||
|
|
460
|
-
stat.size > MAX_POLICY_BYTES
|
|
461
|
-
) {
|
|
462
|
-
throw new Error("unsafe policy file");
|
|
463
|
-
}
|
|
464
|
-
bytes = readFileSync(source.path, "utf8");
|
|
465
|
-
} catch {
|
|
466
|
-
return {
|
|
467
|
-
ok: false,
|
|
468
|
-
source,
|
|
469
|
-
diagnostics: [{ code: "policy-unreadable" }],
|
|
470
|
-
};
|
|
471
|
-
}
|
|
472
|
-
let value: unknown;
|
|
473
|
-
try {
|
|
474
|
-
value = JSON.parse(bytes);
|
|
475
|
-
} catch {
|
|
476
|
-
return {
|
|
477
|
-
ok: false,
|
|
478
|
-
source,
|
|
479
|
-
diagnostics: [{ code: "policy-invalid-json" }],
|
|
480
|
-
};
|
|
481
|
-
}
|
|
482
|
-
const parsed = parseRoutePolicy(value);
|
|
483
|
-
return parsed.ok
|
|
484
|
-
? {
|
|
485
|
-
ok: true,
|
|
486
|
-
source,
|
|
487
|
-
policy: constrainInheritedRoutes(parsed.policy, options.parentModel),
|
|
488
|
-
}
|
|
489
|
-
: { ok: false, source, diagnostics: parsed.diagnostics };
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
export function inspectRoutePolicy(
|
|
493
|
-
resolution: RoutePolicyResolution,
|
|
494
|
-
health: Readonly<Record<string, { state: string; retryAt?: number }>> = {},
|
|
495
|
-
): Record<string, unknown> {
|
|
496
|
-
if (!resolution.ok) {
|
|
497
|
-
return {
|
|
498
|
-
ok: false,
|
|
499
|
-
source: { kind: resolution.source.kind },
|
|
500
|
-
diagnostics: structuredClone(resolution.diagnostics),
|
|
501
|
-
};
|
|
502
|
-
}
|
|
503
|
-
return {
|
|
504
|
-
ok: true,
|
|
505
|
-
source: { kind: resolution.source.kind },
|
|
506
|
-
routes: Object.values(resolution.policy.routes).map((route) => ({
|
|
507
|
-
id: route.id,
|
|
508
|
-
kind: route.kind,
|
|
509
|
-
fingerprint: route.fingerprint,
|
|
510
|
-
capabilities: structuredClone(route.capabilities),
|
|
511
|
-
health: health[route.id]?.state ?? "healthy",
|
|
512
|
-
...(health[route.id]?.retryAt === undefined
|
|
513
|
-
? {}
|
|
514
|
-
: { retryAt: health[route.id]?.retryAt }),
|
|
515
|
-
})),
|
|
516
|
-
roles: structuredClone(resolution.policy.roles),
|
|
517
|
-
};
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
export interface ResolvedCustomRoute {
|
|
521
|
-
url: string;
|
|
522
|
-
model: string;
|
|
523
|
-
dialect: RouteDialect;
|
|
524
|
-
contextWindow: number;
|
|
525
|
-
maxTokens: number;
|
|
526
|
-
apiKey?: string;
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
export function resolveCustomRoute(
|
|
530
|
-
route: CustomRoutePolicy,
|
|
531
|
-
environment: Readonly<Record<string, string | undefined>> = process.env,
|
|
532
|
-
): ResolvedCustomRoute {
|
|
533
|
-
const apiKey = route.apiKeyEnv ? environment[route.apiKeyEnv] : undefined;
|
|
534
|
-
if (route.apiKeyEnv && !apiKey) {
|
|
535
|
-
throw new Error("route-credential-unavailable:apiKeyEnv");
|
|
536
|
-
}
|
|
537
|
-
return {
|
|
538
|
-
url: route.url,
|
|
539
|
-
model: route.model,
|
|
540
|
-
dialect: route.dialect,
|
|
541
|
-
contextWindow: route.capabilities.contextWindow,
|
|
542
|
-
maxTokens: route.capabilities.maxTokens,
|
|
543
|
-
...(apiKey ? { apiKey } : {}),
|
|
544
|
-
};
|
|
545
|
-
}
|