harness-dispatch 0.5.0 → 0.6.1
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/CHANGELOG.md +177 -0
- package/README.md +5 -2
- package/config.default.yaml +10 -1
- package/dist/bin.d.ts.map +1 -1
- package/dist/bin.js +3 -176
- package/dist/bin.js.map +1 -1
- package/dist/config/protocol.d.ts.map +1 -1
- package/dist/config/protocol.js +10 -2
- package/dist/config/protocol.js.map +1 -1
- package/dist/config/route-fields.d.ts +72 -0
- package/dist/config/route-fields.d.ts.map +1 -0
- package/dist/config/route-fields.js +128 -0
- package/dist/config/route-fields.js.map +1 -0
- package/dist/config/validation.js +1 -1
- package/dist/config/validation.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +92 -173
- package/dist/config.js.map +1 -1
- package/dist/configure-yaml.d.ts +23 -0
- package/dist/configure-yaml.d.ts.map +1 -0
- package/dist/configure-yaml.js +201 -0
- package/dist/configure-yaml.js.map +1 -0
- package/dist/dispatchers/base.d.ts +3 -0
- package/dist/dispatchers/base.d.ts.map +1 -1
- package/dist/dispatchers/base.js.map +1 -1
- package/dist/dispatchers/generic-cli.d.ts.map +1 -1
- package/dist/dispatchers/generic-cli.js +22 -2
- package/dist/dispatchers/generic-cli.js.map +1 -1
- package/dist/dispatchers/openai-compatible.d.ts.map +1 -1
- package/dist/dispatchers/openai-compatible.js +18 -0
- package/dist/dispatchers/openai-compatible.js.map +1 -1
- package/dist/dispatchers/shared/stream-subprocess.d.ts +11 -0
- package/dist/dispatchers/shared/stream-subprocess.d.ts.map +1 -1
- package/dist/dispatchers/shared/stream-subprocess.js +57 -35
- package/dist/dispatchers/shared/stream-subprocess.js.map +1 -1
- package/dist/http/parse.d.ts +64 -0
- package/dist/http/parse.d.ts.map +1 -0
- package/dist/http/parse.js +230 -0
- package/dist/http/parse.js.map +1 -0
- package/dist/http/server.d.ts +2 -11
- package/dist/http/server.d.ts.map +1 -1
- package/dist/http/server.js +26 -232
- package/dist/http/server.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/jobs/context.d.ts +17 -0
- package/dist/jobs/context.d.ts.map +1 -0
- package/dist/jobs/context.js +84 -0
- package/dist/jobs/context.js.map +1 -0
- package/dist/jobs/store.d.ts +80 -0
- package/dist/jobs/store.d.ts.map +1 -0
- package/dist/jobs/store.js +279 -0
- package/dist/jobs/store.js.map +1 -0
- package/dist/jobs/types.d.ts +135 -0
- package/dist/jobs/types.d.ts.map +1 -0
- package/dist/jobs/types.js +11 -0
- package/dist/jobs/types.js.map +1 -0
- package/dist/jobs.d.ts +113 -123
- package/dist/jobs.d.ts.map +1 -1
- package/dist/jobs.js +314 -309
- package/dist/jobs.js.map +1 -1
- package/dist/mcp/tool-schemas.d.ts +194 -0
- package/dist/mcp/tool-schemas.d.ts.map +1 -0
- package/dist/mcp/tool-schemas.js +311 -0
- package/dist/mcp/tool-schemas.js.map +1 -0
- package/dist/mcp/tools.d.ts +15 -59
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +63 -239
- package/dist/mcp/tools.js.map +1 -1
- package/dist/quota.d.ts +19 -1
- package/dist/quota.d.ts.map +1 -1
- package/dist/quota.js +39 -4
- package/dist/quota.js.map +1 -1
- package/dist/router.d.ts +4 -0
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +8 -0
- package/dist/router.js.map +1 -1
- package/dist/status.d.ts +13 -0
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +6 -0
- package/dist/status.js.map +1 -1
- package/dist/types.d.ts +51 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/workspace-resolve.d.ts +98 -0
- package/dist/workspace-resolve.d.ts.map +1 -0
- package/dist/workspace-resolve.js +382 -0
- package/dist/workspace-resolve.js.map +1 -0
- package/dist/workspaces.d.ts +1 -0
- package/dist/workspaces.d.ts.map +1 -1
- package/dist/workspaces.js +67 -15
- package/dist/workspaces.js.map +1 -1
- package/package.json +5 -4
- package/dist/dashboard/live.d.ts +0 -49
- package/dist/dashboard/live.d.ts.map +0 -1
- package/dist/dashboard/live.js +0 -149
- package/dist/dashboard/live.js.map +0 -1
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One field contract shared by every route shape.
|
|
3
|
+
*
|
|
4
|
+
* THE DEFECT THIS EXISTS TO RETIRE. `clis:`, `endpoints:` and the legacy
|
|
5
|
+
* `services:` block each resolved route fields with their own hand-written
|
|
6
|
+
* list, so a key added to one was easy to forget in the others. That produced
|
|
7
|
+
* five separate silent-drop defects in one file, each the same shape: a
|
|
8
|
+
* correctly-spelled, correctly-valued setting written in config and read by
|
|
9
|
+
* nobody —
|
|
10
|
+
*
|
|
11
|
+
* workspace_policy honoured for services:/endpoints:, dropped for clis:
|
|
12
|
+
* api_keys honoured for clis:, dropped for endpoints:
|
|
13
|
+
* effective_safety honoured for clis:/services:, dropped for endpoints:
|
|
14
|
+
* escalate_model honoured for clis:, dropped for endpoints:
|
|
15
|
+
*
|
|
16
|
+
* These are safety and isolation controls, so "silently absent" means
|
|
17
|
+
* "silently less restrictive" — a route asked to run `read_only` running with
|
|
18
|
+
* write access, a route asked to run in an isolated copy running in the user's
|
|
19
|
+
* repository. Nothing warns, because nothing is wrong with what was written.
|
|
20
|
+
*
|
|
21
|
+
* WHY A TABLE RATHER THAN A SHARED FUNCTION. The obvious unification — have
|
|
22
|
+
* the CLI builder call the endpoint builder's `billingFields()` — is wrong,
|
|
23
|
+
* and tests/route-field-parity.test.ts says why: the CLI path resolves
|
|
24
|
+
* `entry.X ?? harnessDefaults.X` against the defaults shipped in
|
|
25
|
+
* config.default.yaml, and `billingFields(raw)` has no defaults layer at all.
|
|
26
|
+
* Collapsing them would silently drop the fallback every built-in harness
|
|
27
|
+
* relies on — trading five silent-drop bugs for a bigger one. A table carries
|
|
28
|
+
* the defaults layer explicitly: each field says how to parse it AND where its
|
|
29
|
+
* fallback comes from, and a shape with no defaults (endpoints) simply passes
|
|
30
|
+
* none.
|
|
31
|
+
*
|
|
32
|
+
* WHAT IS DELIBERATELY NOT HERE. Billing identity — provider, surface,
|
|
33
|
+
* auth_source, billing_kind, paid_usage_possible — resolves differently per
|
|
34
|
+
* shape for real reasons, not by accident: endpoints infer it from the base
|
|
35
|
+
* URL (localhost is local_compute), while CLI routes factor in whether an
|
|
36
|
+
* api_key is present (a key means a metered account exists whatever the
|
|
37
|
+
* declared kind says). Those stay in their builders. Pretending they are
|
|
38
|
+
* shared would be the same mistake in the other direction.
|
|
39
|
+
*
|
|
40
|
+
* tests/route-field-parity.test.ts still pins every key here against both
|
|
41
|
+
* shapes. It is no longer the only thing standing between a new field and a
|
|
42
|
+
* silent drop, but it is what proves this table is actually wired into both.
|
|
43
|
+
*/
|
|
44
|
+
import type { SafetyProfile, ServiceConfig } from "../types.js";
|
|
45
|
+
/**
|
|
46
|
+
* The subset of a harness's shipped defaults that shared fields fall back to.
|
|
47
|
+
*
|
|
48
|
+
* Structural on purpose: `CliDefaults` satisfies it without this module
|
|
49
|
+
* needing to import config.ts (which imports this one).
|
|
50
|
+
*/
|
|
51
|
+
export type RouteFieldDefaults = Partial<Pick<ServiceConfig, "leaderboardModel" | "thinkingLevel" | "maxOutputTokens" | "maxInputTokens" | "effectiveSafety" | "models" | "modelHint">>;
|
|
52
|
+
/**
|
|
53
|
+
* `effective_safety` is either one profile for every request, or a per-request
|
|
54
|
+
* map. Shared by all three shapes; lived in config.ts before this table.
|
|
55
|
+
*/
|
|
56
|
+
export declare function effectiveSafetyFrom(raw: unknown): SafetyProfile | Partial<Record<SafetyProfile, SafetyProfile>> | undefined;
|
|
57
|
+
/** The YAML keys this table owns — used by tests to prove both shapes are wired to it. */
|
|
58
|
+
export declare const SHARED_ROUTE_FIELD_KEYS: readonly string[];
|
|
59
|
+
/**
|
|
60
|
+
* Resolve every shared field for one route entry.
|
|
61
|
+
*
|
|
62
|
+
* A field is emitted only when it resolves to something, so callers can spread
|
|
63
|
+
* the result over a partially-built ServiceConfig without overwriting anything
|
|
64
|
+
* with `undefined`.
|
|
65
|
+
*
|
|
66
|
+
* @param raw the route entry as written in config.yaml
|
|
67
|
+
* @param defaults the harness's shipped defaults, for shapes that have them
|
|
68
|
+
* (`clis:`). Endpoint entries pass nothing — they have no defaults layer, and
|
|
69
|
+
* inventing one is the mistake this table's header warns about.
|
|
70
|
+
*/
|
|
71
|
+
export declare function resolveSharedRouteFields(raw: Record<string, unknown>, defaults?: RouteFieldDefaults): Partial<ServiceConfig>;
|
|
72
|
+
//# sourceMappingURL=route-fields.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-fields.d.ts","sourceRoot":"","sources":["../../src/config/route-fields.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAShE;;;;;GAKG;AACH,MAAM,MAAM,kBAAkB,GAAG,OAAO,CACtC,IAAI,CACF,aAAa,EACX,kBAAkB,GAClB,eAAe,GACf,iBAAiB,GACjB,gBAAgB,GAChB,iBAAiB,GACjB,QAAQ,GACR,WAAW,CACd,CACF,CAAC;AA0BF;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,OAAO,GACX,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC,GAAG,SAAS,CAW3E;AAiCD,0FAA0F;AAC1F,eAAO,MAAM,uBAAuB,EAAE,SAAS,MAAM,EAA0C,CAAC;AAEhG;;;;;;;;;;;GAWG;AACH,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,QAAQ,CAAC,EAAE,kBAAkB,GAC5B,OAAO,CAAC,aAAa,CAAC,CAQxB"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One field contract shared by every route shape.
|
|
3
|
+
*
|
|
4
|
+
* THE DEFECT THIS EXISTS TO RETIRE. `clis:`, `endpoints:` and the legacy
|
|
5
|
+
* `services:` block each resolved route fields with their own hand-written
|
|
6
|
+
* list, so a key added to one was easy to forget in the others. That produced
|
|
7
|
+
* five separate silent-drop defects in one file, each the same shape: a
|
|
8
|
+
* correctly-spelled, correctly-valued setting written in config and read by
|
|
9
|
+
* nobody —
|
|
10
|
+
*
|
|
11
|
+
* workspace_policy honoured for services:/endpoints:, dropped for clis:
|
|
12
|
+
* api_keys honoured for clis:, dropped for endpoints:
|
|
13
|
+
* effective_safety honoured for clis:/services:, dropped for endpoints:
|
|
14
|
+
* escalate_model honoured for clis:, dropped for endpoints:
|
|
15
|
+
*
|
|
16
|
+
* These are safety and isolation controls, so "silently absent" means
|
|
17
|
+
* "silently less restrictive" — a route asked to run `read_only` running with
|
|
18
|
+
* write access, a route asked to run in an isolated copy running in the user's
|
|
19
|
+
* repository. Nothing warns, because nothing is wrong with what was written.
|
|
20
|
+
*
|
|
21
|
+
* WHY A TABLE RATHER THAN A SHARED FUNCTION. The obvious unification — have
|
|
22
|
+
* the CLI builder call the endpoint builder's `billingFields()` — is wrong,
|
|
23
|
+
* and tests/route-field-parity.test.ts says why: the CLI path resolves
|
|
24
|
+
* `entry.X ?? harnessDefaults.X` against the defaults shipped in
|
|
25
|
+
* config.default.yaml, and `billingFields(raw)` has no defaults layer at all.
|
|
26
|
+
* Collapsing them would silently drop the fallback every built-in harness
|
|
27
|
+
* relies on — trading five silent-drop bugs for a bigger one. A table carries
|
|
28
|
+
* the defaults layer explicitly: each field says how to parse it AND where its
|
|
29
|
+
* fallback comes from, and a shape with no defaults (endpoints) simply passes
|
|
30
|
+
* none.
|
|
31
|
+
*
|
|
32
|
+
* WHAT IS DELIBERATELY NOT HERE. Billing identity — provider, surface,
|
|
33
|
+
* auth_source, billing_kind, paid_usage_possible — resolves differently per
|
|
34
|
+
* shape for real reasons, not by accident: endpoints infer it from the base
|
|
35
|
+
* URL (localhost is local_compute), while CLI routes factor in whether an
|
|
36
|
+
* api_key is present (a key means a metered account exists whatever the
|
|
37
|
+
* declared kind says). Those stay in their builders. Pretending they are
|
|
38
|
+
* shared would be the same mistake in the other direction.
|
|
39
|
+
*
|
|
40
|
+
* tests/route-field-parity.test.ts still pins every key here against both
|
|
41
|
+
* shapes. It is no longer the only thing standing between a new field and a
|
|
42
|
+
* silent drop, but it is what proves this table is actually wired into both.
|
|
43
|
+
*/
|
|
44
|
+
import { normalizeSafetyProfile } from "../safety.js";
|
|
45
|
+
import { confidenceFrom, str, thinkingFrom, workspacePolicyFrom, } from "./coercions.js";
|
|
46
|
+
import { stringArrayFrom } from "./protocol.js";
|
|
47
|
+
/** A number only when it really is one — `int()`/`num()` coerce, and these must not. */
|
|
48
|
+
function numberOnly(raw) {
|
|
49
|
+
return typeof raw === "number" ? raw : undefined;
|
|
50
|
+
}
|
|
51
|
+
/** Likewise for booleans: an absent flag must stay absent, not become false. */
|
|
52
|
+
function booleanOnly(raw) {
|
|
53
|
+
return typeof raw === "boolean" ? raw : undefined;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* `effective_safety` is either one profile for every request, or a per-request
|
|
57
|
+
* map. Shared by all three shapes; lived in config.ts before this table.
|
|
58
|
+
*/
|
|
59
|
+
export function effectiveSafetyFrom(raw) {
|
|
60
|
+
const single = normalizeSafetyProfile(raw);
|
|
61
|
+
if (single !== undefined)
|
|
62
|
+
return single;
|
|
63
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw))
|
|
64
|
+
return undefined;
|
|
65
|
+
const out = {};
|
|
66
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
67
|
+
const requested = normalizeSafetyProfile(key);
|
|
68
|
+
const floor = normalizeSafetyProfile(value);
|
|
69
|
+
if (requested !== undefined && floor !== undefined)
|
|
70
|
+
out[requested] = floor;
|
|
71
|
+
}
|
|
72
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Every optional field that means the same thing on every route shape.
|
|
76
|
+
*
|
|
77
|
+
* Adding a route setting means adding ONE row here (plus its key to
|
|
78
|
+
* KNOWN_ROUTE_KEYS in validation.ts, which is what catches misspellings) and
|
|
79
|
+
* it works on `clis:`, `endpoints:` and `services:` at once. That is the whole
|
|
80
|
+
* point: the old failure mode was a field that existed in one list and not the
|
|
81
|
+
* others.
|
|
82
|
+
*/
|
|
83
|
+
const SHARED_ROUTE_FIELDS = [
|
|
84
|
+
{ key: "leaderboard_model", field: "leaderboardModel", parse: str, fromDefaults: (d) => d.leaderboardModel },
|
|
85
|
+
{ key: "thinking_level", field: "thinkingLevel", parse: thinkingFrom, fromDefaults: (d) => d.thinkingLevel },
|
|
86
|
+
{ key: "escalate_model", field: "escalateModel", parse: str },
|
|
87
|
+
{ key: "max_output_tokens", field: "maxOutputTokens", parse: numberOnly, fromDefaults: (d) => d.maxOutputTokens },
|
|
88
|
+
{ key: "max_input_tokens", field: "maxInputTokens", parse: numberOnly, fromDefaults: (d) => d.maxInputTokens },
|
|
89
|
+
{ key: "timeout_ms", field: "timeoutMs", parse: numberOnly },
|
|
90
|
+
{ key: "resource_weight", field: "resourceWeight", parse: numberOnly },
|
|
91
|
+
// Safety and isolation. Four of the five original silent drops were here.
|
|
92
|
+
{ key: "safety_profile", field: "safetyProfile", parse: normalizeSafetyProfile },
|
|
93
|
+
{ key: "effective_safety", field: "effectiveSafety", parse: effectiveSafetyFrom, fromDefaults: (d) => d.effectiveSafety },
|
|
94
|
+
{ key: "workspace_policy", field: "workspacePolicy", parse: workspacePolicyFrom },
|
|
95
|
+
// Billing fields that are plain declarations. The INFERRED ones
|
|
96
|
+
// (provider/surface/auth_source/billing_kind/paid_usage_possible) stay in
|
|
97
|
+
// the builders — see the header.
|
|
98
|
+
{ key: "allow_paid_usage", field: "allowPaidUsage", parse: booleanOnly },
|
|
99
|
+
{ key: "billing_confidence", field: "billingConfidence", parse: confidenceFrom },
|
|
100
|
+
{ key: "billing_notes", field: "billingNotes", parse: str },
|
|
101
|
+
{ key: "models", field: "models", parse: stringArrayFrom, fromDefaults: (d) => d.models },
|
|
102
|
+
{ key: "model_hint", field: "modelHint", parse: str, fromDefaults: (d) => d.modelHint },
|
|
103
|
+
];
|
|
104
|
+
/** The YAML keys this table owns — used by tests to prove both shapes are wired to it. */
|
|
105
|
+
export const SHARED_ROUTE_FIELD_KEYS = SHARED_ROUTE_FIELDS.map((f) => f.key);
|
|
106
|
+
/**
|
|
107
|
+
* Resolve every shared field for one route entry.
|
|
108
|
+
*
|
|
109
|
+
* A field is emitted only when it resolves to something, so callers can spread
|
|
110
|
+
* the result over a partially-built ServiceConfig without overwriting anything
|
|
111
|
+
* with `undefined`.
|
|
112
|
+
*
|
|
113
|
+
* @param raw the route entry as written in config.yaml
|
|
114
|
+
* @param defaults the harness's shipped defaults, for shapes that have them
|
|
115
|
+
* (`clis:`). Endpoint entries pass nothing — they have no defaults layer, and
|
|
116
|
+
* inventing one is the mistake this table's header warns about.
|
|
117
|
+
*/
|
|
118
|
+
export function resolveSharedRouteFields(raw, defaults) {
|
|
119
|
+
const out = {};
|
|
120
|
+
for (const spec of SHARED_ROUTE_FIELDS) {
|
|
121
|
+
const declared = spec.parse(raw[spec.key]);
|
|
122
|
+
const value = declared ?? (defaults !== undefined ? spec.fromDefaults?.(defaults) : undefined);
|
|
123
|
+
if (value !== undefined)
|
|
124
|
+
out[spec.field] = value;
|
|
125
|
+
}
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=route-fields.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-fields.js","sourceRoot":"","sources":["../../src/config/route-fields.ts"],"names":[],"mappings":"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"}
|
|
@@ -108,7 +108,7 @@ export const KNOWN_ROUTE_KEYS = new Set([
|
|
|
108
108
|
"name", "harness", "type", "command", "enabled", "model", "models", "model_hint",
|
|
109
109
|
"tier", "weight", "cli_capability", "capabilities", "timeout_ms",
|
|
110
110
|
"max_input_tokens", "max_output_tokens", "thinking_level",
|
|
111
|
-
"leaderboard_model", "escalate_model", "escalate_on",
|
|
111
|
+
"leaderboard_model", "escalate_model", "escalate_on", "resource_weight",
|
|
112
112
|
"api_key", "base_url", "protocol", "filter",
|
|
113
113
|
"provider", "surface", "auth_source", "billing_kind", "billing_confidence",
|
|
114
114
|
"billing_notes", "paid_usage_possible", "allow_paid_usage",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/config/validation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,MAAM,eAAe,GAAsC;IAChE,wEAAwE;IACxE,wEAAwE;IACxE,4EAA4E;IAC5E,yEAAyE;IACzE,sEAAsE;IACtE,6EAA6E;IAC7E,WAAW;IACX,YAAY,EAAE;QACZ,eAAe;QACf,qBAAqB;QACrB,qCAAqC;QACrC,uCAAuC;QACvC,+BAA+B;QAC/B,aAAa;QACb,YAAY;QACZ,SAAS;KACV;IACD,kBAAkB,EAAE,CAAC,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,CAAC;IACxE,cAAc,EAAE,CAAC,WAAW,EAAE,gBAAgB,EAAE,WAAW,CAAC;IAC5D,gBAAgB,EAAE,CAAC,WAAW,EAAE,gBAAgB,EAAE,WAAW,CAAC;IAC9D,gBAAgB,EAAE,CAAC,QAAQ,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,CAAC;CACtE,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAC;IAC1C,SAAS;IACT,MAAM;IACN,WAAW;IACX,UAAU;IACV,UAAU;IACV,WAAW;IACX,UAAU;IACV,WAAW;IACX,QAAQ;IACR,WAAW;IACX,WAAW;IACX,aAAa;IACb,qBAAqB;IACrB,wBAAwB;IACxB,kBAAkB;IAClB,UAAU;CACX,CAAC,CAAC;AAEH;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC;IACtC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY;IAChF,MAAM,EAAE,QAAQ,EAAE,gBAAgB,EAAE,cAAc,EAAE,YAAY;IAChE,kBAAkB,EAAE,mBAAmB,EAAE,gBAAgB;IACzD,mBAAmB,EAAE,gBAAgB,EAAE,aAAa;
|
|
1
|
+
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/config/validation.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AA6BH,2EAA2E;AAC3E,MAAM,MAAM,OAAO,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;AA+B9D,OAAO,KAAK,EAOV,iBAAiB,EAGjB,YAAY,EAOb,MAAM,YAAY,CAAC;AAsEpB,iKAAiK;AACjK,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAI9D,CAAC;AAQF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAKvD,CAAC;AA0lBF,MAAM,WAAW,iBAAiB;IAChC,0EAA0E;IAC1E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;;;;;;;GAQG;AACH,wBAAsB,UAAU,CAC9B,IAAI,CAAC,EAAE,MAAM,EACb,IAAI,GAAE,iBAAsB,GAC3B,OAAO,CAAC,YAAY,CAAC,CAkHvB;AAMD,MAAM,WAAW,aAAa;IAC5B,IAAI,IAAI,IAAI,CAAC;CACd;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,CAAC,CAAC,EAAE,YAAY,KAAK,IAAI,EACnC,IAAI,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAO,GACpD,aAAa,CAkCf"}
|
package/dist/config.js
CHANGED
|
@@ -17,12 +17,12 @@ import path from "node:path";
|
|
|
17
17
|
import { fileURLToPath } from "node:url";
|
|
18
18
|
import yaml from "js-yaml";
|
|
19
19
|
import which from "which";
|
|
20
|
-
import { normalizeSafetyProfile, } from "./safety.js";
|
|
21
20
|
import { inferredPaidUsagePossible } from "./billing.js";
|
|
22
|
-
import { authSourceFrom, billingKindFrom, bool,
|
|
21
|
+
import { authSourceFrom, billingKindFrom, bool, endpointModeFrom, endpointProviderFrom, inferEndpointProvider, int, num, providerFrom, str, surfaceFrom, wireProtocolFrom, } from "./config/coercions.js";
|
|
23
22
|
import { warnUnknownRouteKeys, warnUnknownSafetyEnums, warnUnknownTopLevelKeys, } from "./config/validation.js";
|
|
24
23
|
import { parseProtocolFields, protocolFrom, stringArrayFrom } from "./config/protocol.js";
|
|
25
|
-
import {
|
|
24
|
+
import { effectiveSafetyFrom, resolveSharedRouteFields, } from "./config/route-fields.js";
|
|
25
|
+
import { ENV_VAR_RE, interpolateTree } from "./config/env-interpolation.js";
|
|
26
26
|
/**
|
|
27
27
|
* PATH lookups, memoised for the life of the process.
|
|
28
28
|
*
|
|
@@ -182,29 +182,22 @@ function topLevelSettings(raw, policyWarnings = []) {
|
|
|
182
182
|
* KEY or value inside the map is dropped individually — a typo must not
|
|
183
183
|
* silently widen the floor for a request it was meant to restrict.
|
|
184
184
|
*/
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
if (requested !== undefined && floor !== undefined)
|
|
196
|
-
out[requested] = floor;
|
|
197
|
-
}
|
|
198
|
-
return Object.keys(out).length > 0 ? out : undefined;
|
|
199
|
-
}
|
|
185
|
+
/**
|
|
186
|
+
* Billing IDENTITY for a route — the part that genuinely differs per shape.
|
|
187
|
+
*
|
|
188
|
+
* Everything that means the same thing everywhere (safety_profile,
|
|
189
|
+
* effective_safety, workspace_policy, billing_notes, models, …) now comes from
|
|
190
|
+
* resolveSharedRouteFields instead. What is left here is the set the caller
|
|
191
|
+
* has already inferred per shape: endpoints derive provider/surface/kind from
|
|
192
|
+
* the base URL, CLI routes derive them from whether an api_key is present.
|
|
193
|
+
* This function only reads what it is handed.
|
|
194
|
+
*/
|
|
200
195
|
function billingFields(raw) {
|
|
201
196
|
const out = {};
|
|
202
197
|
const provider = providerFrom(raw.provider);
|
|
203
198
|
const surface = surfaceFrom(raw.surface);
|
|
204
199
|
const authSource = authSourceFrom(raw.auth_source);
|
|
205
200
|
const billingKind = billingKindFrom(raw.billing_kind);
|
|
206
|
-
const billingConfidence = confidenceFrom(raw.billing_confidence);
|
|
207
|
-
const safetyProfile = normalizeSafetyProfile(raw.safety_profile);
|
|
208
201
|
if (provider !== undefined)
|
|
209
202
|
out.provider = provider;
|
|
210
203
|
if (surface !== undefined)
|
|
@@ -213,31 +206,9 @@ function billingFields(raw) {
|
|
|
213
206
|
out.authSource = authSource;
|
|
214
207
|
if (billingKind !== undefined)
|
|
215
208
|
out.billingKind = billingKind;
|
|
216
|
-
if (billingConfidence !== undefined)
|
|
217
|
-
out.billingConfidence = billingConfidence;
|
|
218
209
|
if (typeof raw.paid_usage_possible === "boolean") {
|
|
219
210
|
out.paidUsagePossible = raw.paid_usage_possible;
|
|
220
211
|
}
|
|
221
|
-
if (typeof raw.allow_paid_usage === "boolean")
|
|
222
|
-
out.allowPaidUsage = raw.allow_paid_usage;
|
|
223
|
-
const notes = str(raw.billing_notes);
|
|
224
|
-
if (notes !== undefined)
|
|
225
|
-
out.billingNotes = notes;
|
|
226
|
-
if (safetyProfile !== undefined)
|
|
227
|
-
out.safetyProfile = safetyProfile;
|
|
228
|
-
// effective_safety was handled by the `clis:` builder and the legacy
|
|
229
|
-
// `services:` path but not here, so an `endpoints:` entry declaring it was
|
|
230
|
-
// silently ignored — the fourth instance of this file's parallel-field-list
|
|
231
|
-
// defect, and the first one caught by a test rather than by a user or a
|
|
232
|
-
// reviewer. Practically fail-safe (openai_compatible routes are read_only by
|
|
233
|
-
// construction, so the ignored value could only have widened them), but the
|
|
234
|
-
// inconsistency is the bug.
|
|
235
|
-
const effectiveSafety = effectiveSafetyFrom(raw.effective_safety);
|
|
236
|
-
if (effectiveSafety !== undefined)
|
|
237
|
-
out.effectiveSafety = effectiveSafety;
|
|
238
|
-
const workspacePolicy = workspacePolicyFrom(raw.workspace_policy);
|
|
239
|
-
if (workspacePolicy !== undefined)
|
|
240
|
-
out.workspacePolicy = workspacePolicy;
|
|
241
212
|
return out;
|
|
242
213
|
}
|
|
243
214
|
function capsFrom(raw) {
|
|
@@ -300,31 +271,11 @@ function buildLegacyConfig(raw) {
|
|
|
300
271
|
tier: int(svc.tier, 1),
|
|
301
272
|
weight: num(svc.weight, 1.0),
|
|
302
273
|
cliCapability: num(svc.cli_capability, 1.0),
|
|
303
|
-
...(str(svc.leaderboard_model) !== undefined
|
|
304
|
-
? { leaderboardModel: str(svc.leaderboard_model) }
|
|
305
|
-
: {}),
|
|
306
|
-
...(() => {
|
|
307
|
-
const t = thinkingFrom(svc.thinking_level);
|
|
308
|
-
return t !== undefined ? { thinkingLevel: t } : {};
|
|
309
|
-
})(),
|
|
310
|
-
...(str(svc.escalate_model) !== undefined
|
|
311
|
-
? { escalateModel: str(svc.escalate_model) }
|
|
312
|
-
: {}),
|
|
313
274
|
escalateOn: escalateOnFrom(svc.escalate_on),
|
|
314
275
|
capabilities: capsFrom(svc.capabilities),
|
|
315
276
|
...(() => {
|
|
316
|
-
//
|
|
317
|
-
//
|
|
318
|
-
// a known harness (e.g. antigravity_cli's 2M context) must still
|
|
319
|
-
// inherit them — otherwise the boost silently becomes a no-op.
|
|
320
|
-
const v = typeof svc.max_output_tokens === "number" ? svc.max_output_tokens : harnessDefaults?.maxOutputTokens;
|
|
321
|
-
return v !== undefined ? { maxOutputTokens: v } : {};
|
|
322
|
-
})(),
|
|
323
|
-
...(() => {
|
|
324
|
-
const v = typeof svc.max_input_tokens === "number" ? svc.max_input_tokens : harnessDefaults?.maxInputTokens;
|
|
325
|
-
return v !== undefined ? { maxInputTokens: v } : {};
|
|
326
|
-
})(),
|
|
327
|
-
...(() => {
|
|
277
|
+
// Billing IDENTITY only — the rest of the named harness's defaults
|
|
278
|
+
// reach this entry through resolveSharedRouteFields below.
|
|
328
279
|
if (!harnessDefaults)
|
|
329
280
|
return {};
|
|
330
281
|
return {
|
|
@@ -337,30 +288,19 @@ function buildLegacyConfig(raw) {
|
|
|
337
288
|
...(harnessDefaults.paidUsagePossible !== undefined
|
|
338
289
|
? { paidUsagePossible: harnessDefaults.paidUsagePossible }
|
|
339
290
|
: {}),
|
|
340
|
-
...(harnessDefaults.effectiveSafety !== undefined
|
|
341
|
-
? { effectiveSafety: harnessDefaults.effectiveSafety }
|
|
342
|
-
: {}),
|
|
343
|
-
...(harnessDefaults.models !== undefined ? { models: harnessDefaults.models } : {}),
|
|
344
|
-
...(harnessDefaults.modelHint !== undefined
|
|
345
|
-
? { modelHint: harnessDefaults.modelHint }
|
|
346
|
-
: {}),
|
|
347
291
|
};
|
|
348
292
|
})(),
|
|
349
293
|
...billingFields(svc),
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
...(
|
|
359
|
-
const modelHint = str(svc.model_hint);
|
|
360
|
-
return modelHint !== undefined ? { modelHint } : {};
|
|
361
|
-
})(),
|
|
294
|
+
// The same table the `clis:` and `endpoints:` builders use. This shape
|
|
295
|
+
// previously read the shared keys through billingFields() plus four
|
|
296
|
+
// hand-written blocks, which is how it ended up with a DIFFERENT set
|
|
297
|
+
// again: it inherited maxInput/OutputTokens, effectiveSafety, models and
|
|
298
|
+
// modelHint from the named harness but not leaderboardModel or
|
|
299
|
+
// thinkingLevel, so `services: { x: { harness: antigravity_cli } }`
|
|
300
|
+
// silently lost its scoring key and its thinking level. Passing the
|
|
301
|
+
// defaults through one table fixes that too.
|
|
302
|
+
...resolveSharedRouteFields(svc, harnessDefaults),
|
|
362
303
|
...endpointFields(svc, type, str(svc.base_url)),
|
|
363
|
-
...(typeof svc.timeout_ms === "number" ? { timeoutMs: svc.timeout_ms } : {}),
|
|
364
304
|
...(() => {
|
|
365
305
|
// Falls back to the named harness's built-in default protocol (if
|
|
366
306
|
// any) when this legacy-format entry doesn't declare its own —
|
|
@@ -416,31 +356,13 @@ function buildCliServiceConfig(name, defaults, override, apiKeys, warnings = [])
|
|
|
416
356
|
weight: num(override.weight, 1.0),
|
|
417
357
|
tier: int(override.tier, defaults.tier),
|
|
418
358
|
cliCapability: num(override.cli_capability, defaults.cliCapability),
|
|
419
|
-
leaderboardModel: str(override.leaderboard_model) ?? defaults.leaderboardModel,
|
|
420
|
-
...(() => {
|
|
421
|
-
const overrideThinking = thinkingFrom(override.thinking_level);
|
|
422
|
-
if (overrideThinking !== undefined)
|
|
423
|
-
return { thinkingLevel: overrideThinking };
|
|
424
|
-
if (defaults.thinkingLevel !== undefined) {
|
|
425
|
-
return { thinkingLevel: defaults.thinkingLevel };
|
|
426
|
-
}
|
|
427
|
-
return {};
|
|
428
|
-
})(),
|
|
429
|
-
...(str(override.escalate_model) !== undefined
|
|
430
|
-
? { escalateModel: str(override.escalate_model) }
|
|
431
|
-
: {}),
|
|
432
359
|
escalateOn: escalateOnFrom(override.escalate_on),
|
|
433
360
|
capabilities: caps,
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
...(() => {
|
|
440
|
-
const m = override.max_input_tokens;
|
|
441
|
-
const v = typeof m === "number" ? m : defaults.maxInputTokens;
|
|
442
|
-
return v !== undefined ? { maxInputTokens: v } : {};
|
|
443
|
-
})(),
|
|
361
|
+
// Every field that means the same thing on every route shape, resolved
|
|
362
|
+
// from ONE table against this harness's shipped defaults. Spread early so
|
|
363
|
+
// the shape-specific resolutions below (billing identity, which depends on
|
|
364
|
+
// whether an api_key is present) still win.
|
|
365
|
+
...resolveSharedRouteFields(override, defaults),
|
|
444
366
|
provider: providerFrom(override.provider) ?? defaults.provider,
|
|
445
367
|
surface: surfaceFrom(override.surface) ?? defaults.surface,
|
|
446
368
|
authSource: authSourceFrom(override.auth_source) ?? (apiKey ? "api_key" : defaults.authSource),
|
|
@@ -472,47 +394,7 @@ function buildCliServiceConfig(name, defaults, override, apiKeys, warnings = [])
|
|
|
472
394
|
: defaults.paidUsagePossible;
|
|
473
395
|
return paidUsagePossible !== undefined ? { paidUsagePossible } : {};
|
|
474
396
|
})(),
|
|
475
|
-
...(typeof override.allow_paid_usage === "boolean"
|
|
476
|
-
? { allowPaidUsage: override.allow_paid_usage }
|
|
477
|
-
: {}),
|
|
478
|
-
...(() => {
|
|
479
|
-
const c = confidenceFrom(override.billing_confidence);
|
|
480
|
-
return c !== undefined ? { billingConfidence: c } : {};
|
|
481
|
-
})(),
|
|
482
|
-
...(() => {
|
|
483
|
-
const notes = str(override.billing_notes);
|
|
484
|
-
return notes !== undefined ? { billingNotes: notes } : {};
|
|
485
|
-
})(),
|
|
486
|
-
...(() => {
|
|
487
|
-
const safetyProfile = normalizeSafetyProfile(override.safety_profile);
|
|
488
|
-
return safetyProfile !== undefined ? { safetyProfile } : {};
|
|
489
|
-
})(),
|
|
490
|
-
...(() => {
|
|
491
|
-
// `clis:` entries never read this. It was handled for `services:` and
|
|
492
|
-
// `endpoints:` (both go through billingFields, which this builder does
|
|
493
|
-
// not call), so `workspace_policy: copy` on the primary, documented way
|
|
494
|
-
// to define a route was silently dropped and the route fell back to
|
|
495
|
-
// shared_locked — the LESS isolated default, with no warning, because
|
|
496
|
-
// the value itself is perfectly valid. The fail-open enum check added
|
|
497
|
-
// earlier cannot catch this: the key is right and the value is right;
|
|
498
|
-
// nothing was reading it.
|
|
499
|
-
const workspacePolicy = workspacePolicyFrom(override.workspace_policy);
|
|
500
|
-
return workspacePolicy !== undefined ? { workspacePolicy } : {};
|
|
501
|
-
})(),
|
|
502
|
-
...(() => {
|
|
503
|
-
const effectiveSafety = effectiveSafetyFrom(override.effective_safety) ?? defaults.effectiveSafety;
|
|
504
|
-
return effectiveSafety !== undefined ? { effectiveSafety } : {};
|
|
505
|
-
})(),
|
|
506
|
-
...(() => {
|
|
507
|
-
const models = stringArrayFrom(override.models) ?? defaults.models;
|
|
508
|
-
return models !== undefined ? { models } : {};
|
|
509
|
-
})(),
|
|
510
|
-
...(() => {
|
|
511
|
-
const modelHint = str(override.model_hint) ?? defaults.modelHint;
|
|
512
|
-
return modelHint !== undefined ? { modelHint } : {};
|
|
513
|
-
})(),
|
|
514
397
|
...endpointFields(override, "cli", str(override.base_url)),
|
|
515
|
-
...(typeof override.timeout_ms === "number" ? { timeoutMs: override.timeout_ms } : {}),
|
|
516
398
|
...(() => {
|
|
517
399
|
// Falls back to this harness's built-in default (if any) when no
|
|
518
400
|
// override is given, or when the override is malformed — protocolFrom
|
|
@@ -608,6 +490,53 @@ function addClis(services, raw, apiKeys, warnings) {
|
|
|
608
490
|
services[name] = buildCliServiceConfig(name, defaults, entry, apiKeys, warnings);
|
|
609
491
|
}
|
|
610
492
|
}
|
|
493
|
+
/**
|
|
494
|
+
* `api_key: ${VAR}` references keyed by route name, read from the RAW tree
|
|
495
|
+
* before interpolation.
|
|
496
|
+
*
|
|
497
|
+
* envRefs cannot cover this case. It maps a resolved value back to the
|
|
498
|
+
* reference that produced it, and an UNSET variable resolves to "" — which
|
|
499
|
+
* every unset variable shares, so the map would hand one route another route's
|
|
500
|
+
* variable name. interpolateEnv skips empty resolutions for exactly that
|
|
501
|
+
* reason.
|
|
502
|
+
*
|
|
503
|
+
* The consequence was silent: `configure --yes --force` run in a shell that
|
|
504
|
+
* had not exported the variable emitted the route with no `api_key` line at
|
|
505
|
+
* all, overwriting a correct config with one whose key was simply gone. Keyed
|
|
506
|
+
* by route name, which is unique per shape and is what configure has in hand.
|
|
507
|
+
*/
|
|
508
|
+
function collectApiKeyRefs(parsed) {
|
|
509
|
+
const refs = new Map();
|
|
510
|
+
const note = (name, value) => {
|
|
511
|
+
if (typeof name === "string" && typeof value === "string" && ENV_VAR_RE.test(value)) {
|
|
512
|
+
refs.set(name, value);
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
for (const key of ["clis", "endpoints"]) {
|
|
516
|
+
const list = parsed[key];
|
|
517
|
+
if (!Array.isArray(list))
|
|
518
|
+
continue;
|
|
519
|
+
for (const entry of list) {
|
|
520
|
+
if (entry !== null && typeof entry === "object") {
|
|
521
|
+
const e = entry;
|
|
522
|
+
note(e.name, e.api_key);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
for (const key of ["services", "api_keys"]) {
|
|
527
|
+
const block = parsed[key];
|
|
528
|
+
if (block === null || typeof block !== "object")
|
|
529
|
+
continue;
|
|
530
|
+
for (const [name, entry] of Object.entries(block)) {
|
|
531
|
+
if (key === "api_keys")
|
|
532
|
+
note(name, entry);
|
|
533
|
+
else if (entry !== null && typeof entry === "object") {
|
|
534
|
+
note(name, entry.api_key);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return refs;
|
|
539
|
+
}
|
|
611
540
|
function collectApiKeys(raw) {
|
|
612
541
|
const apiKeys = {};
|
|
613
542
|
const rawApiKeys = (raw.api_keys ?? {});
|
|
@@ -669,16 +598,6 @@ function addEndpoints(services, raw, apiKeys, warnings = []) {
|
|
|
669
598
|
weight: num(ep.weight, 0.6),
|
|
670
599
|
tier: int(ep.tier, 3),
|
|
671
600
|
cliCapability: num(ep.cli_capability, 1.0),
|
|
672
|
-
...(str(ep.leaderboard_model) !== undefined
|
|
673
|
-
? { leaderboardModel: str(ep.leaderboard_model) }
|
|
674
|
-
: {}),
|
|
675
|
-
// escalate_on was honoured here and escalate_model was not — the fifth
|
|
676
|
-
// instance of this file's parallel-field-list defect, and the second
|
|
677
|
-
// found by review rather than by the parity test, which pins 16 shared
|
|
678
|
-
// keys and did not include this one. It does now.
|
|
679
|
-
...(str(ep.escalate_model) !== undefined
|
|
680
|
-
? { escalateModel: str(ep.escalate_model) }
|
|
681
|
-
: {}),
|
|
682
601
|
escalateOn: escalateOnFrom(ep.escalate_on),
|
|
683
602
|
capabilities: capsFrom(ep.capabilities),
|
|
684
603
|
...billingFields({
|
|
@@ -695,21 +614,10 @@ function addEndpoints(services, raw, apiKeys, warnings = []) {
|
|
|
695
614
|
billing_confidence: ep.billing_confidence ?? (inferEndpointProvider(baseUrl) === "custom" ? undefined : "documented"),
|
|
696
615
|
}),
|
|
697
616
|
...endpointFields(ep, "openai_compatible", baseUrl),
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
...(
|
|
702
|
-
const t = thinkingFrom(ep.thinking_level);
|
|
703
|
-
return t !== undefined ? { thinkingLevel: t } : {};
|
|
704
|
-
})(),
|
|
705
|
-
...(() => {
|
|
706
|
-
const models = stringArrayFrom(ep.models);
|
|
707
|
-
return models !== undefined ? { models } : {};
|
|
708
|
-
})(),
|
|
709
|
-
...(() => {
|
|
710
|
-
const modelHint = str(ep.model_hint);
|
|
711
|
-
return modelHint !== undefined ? { modelHint } : {};
|
|
712
|
-
})(),
|
|
617
|
+
// No defaults argument: an endpoint has no harness whose shipped
|
|
618
|
+
// defaults it could fall back to. That absence is exactly why the CLI
|
|
619
|
+
// path could not simply reuse billingFields() — see route-fields.ts.
|
|
620
|
+
...resolveSharedRouteFields(ep),
|
|
713
621
|
};
|
|
714
622
|
services[name] = svc;
|
|
715
623
|
}
|
|
@@ -728,11 +636,17 @@ export async function loadConfig(path, opts = {}) {
|
|
|
728
636
|
let raw = {};
|
|
729
637
|
const unsetEnvVars = new Set();
|
|
730
638
|
const envRefs = new Map();
|
|
639
|
+
const apiKeyRefs = new Map();
|
|
731
640
|
if (path) {
|
|
732
641
|
try {
|
|
733
642
|
const text = await fs.readFile(path, "utf-8");
|
|
734
643
|
const parsed = yaml.load(text);
|
|
735
644
|
if (parsed && typeof parsed === "object") {
|
|
645
|
+
// Before interpolation: an unset ${VAR} is indistinguishable from
|
|
646
|
+
// every other unset ${VAR} once it has resolved to "".
|
|
647
|
+
for (const [name, ref] of collectApiKeyRefs(parsed)) {
|
|
648
|
+
apiKeyRefs.set(name, ref);
|
|
649
|
+
}
|
|
736
650
|
raw = interpolateTree(parsed, unsetEnvVars, envRefs);
|
|
737
651
|
}
|
|
738
652
|
}
|
|
@@ -777,7 +691,11 @@ export async function loadConfig(path, opts = {}) {
|
|
|
777
691
|
if (enumWarnings.length > 0) {
|
|
778
692
|
legacyCfg.configWarnings = [...(legacyCfg.configWarnings ?? []), ...enumWarnings];
|
|
779
693
|
}
|
|
780
|
-
const withRefs =
|
|
694
|
+
const withRefs = {
|
|
695
|
+
...legacyCfg,
|
|
696
|
+
...(envRefs.size > 0 ? { envRefs } : {}),
|
|
697
|
+
...(apiKeyRefs.size > 0 ? { apiKeyRefs } : {}),
|
|
698
|
+
};
|
|
781
699
|
if (envVarWarning !== undefined) {
|
|
782
700
|
return { ...withRefs, configWarnings: [...(withRefs.configWarnings ?? []), envVarWarning] };
|
|
783
701
|
}
|
|
@@ -815,6 +733,7 @@ export async function loadConfig(path, opts = {}) {
|
|
|
815
733
|
disabled,
|
|
816
734
|
...topLevelSettings(raw, warnings),
|
|
817
735
|
...(envRefs.size > 0 ? { envRefs } : {}),
|
|
736
|
+
...(apiKeyRefs.size > 0 ? { apiKeyRefs } : {}),
|
|
818
737
|
...(warnings.length > 0 ? { configWarnings: warnings } : {}),
|
|
819
738
|
};
|
|
820
739
|
return cfg;
|