nexus-agents 8.102.1 → 8.102.2
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/{chunk-I4YI4J3V.js → chunk-2ON7ZOBD.js} +8 -8
- package/dist/{chunk-N6TZK7LR.js → chunk-JASILDK4.js} +2 -2
- package/dist/chunk-L265O6SP.js +322 -0
- package/dist/chunk-L265O6SP.js.map +1 -0
- package/dist/{chunk-XJTA5BHT.js → chunk-TBTXBJ7T.js} +2 -2
- package/dist/{chunk-3LKDZORV.js → chunk-XWMT6ZSS.js} +14 -133
- package/dist/chunk-XWMT6ZSS.js.map +1 -0
- package/dist/{chunk-HUMPSYCP.js → chunk-YBV7HIRX.js} +3 -3
- package/dist/{chunk-JQAXGIDL.js → chunk-YSLHLLRK.js} +2 -2
- package/dist/cli.js +8 -8
- package/dist/{consensus-vote-L6D5Q5II.js → consensus-vote-4ET2GP6B.js} +4 -4
- package/dist/doctor-gateway-report-MRSOR4TR.js +17 -0
- package/dist/{improvement-review-ABVCXCST.js → improvement-review-WQYWT3GP.js} +4 -4
- package/dist/index.js +7 -7
- package/dist/{setup-data-dir-4PF7KCEJ.js → setup-data-dir-2EVIVSKU.js} +4 -4
- package/dist/setup-data-dir-2EVIVSKU.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-3LKDZORV.js.map +0 -1
- package/dist/chunk-ZYMOKZ5S.js +0 -81
- package/dist/chunk-ZYMOKZ5S.js.map +0 -1
- package/dist/doctor-gateway-report-6L7K7KQR.js +0 -77
- package/dist/doctor-gateway-report-6L7K7KQR.js.map +0 -1
- /package/dist/{chunk-I4YI4J3V.js.map → chunk-2ON7ZOBD.js.map} +0 -0
- /package/dist/{chunk-N6TZK7LR.js.map → chunk-JASILDK4.js.map} +0 -0
- /package/dist/{chunk-XJTA5BHT.js.map → chunk-TBTXBJ7T.js.map} +0 -0
- /package/dist/{chunk-HUMPSYCP.js.map → chunk-YBV7HIRX.js.map} +0 -0
- /package/dist/{chunk-JQAXGIDL.js.map → chunk-YSLHLLRK.js.map} +0 -0
- /package/dist/{consensus-vote-L6D5Q5II.js.map → consensus-vote-4ET2GP6B.js.map} +0 -0
- /package/dist/{improvement-review-ABVCXCST.js.map → doctor-gateway-report-MRSOR4TR.js.map} +0 -0
- /package/dist/{setup-data-dir-4PF7KCEJ.js.map → improvement-review-WQYWT3GP.js.map} +0 -0
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
} from "./chunk-GVSSUYBR.js";
|
|
10
10
|
import {
|
|
11
11
|
withPrerequisite
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-JASILDK4.js";
|
|
13
13
|
import {
|
|
14
14
|
generateSecurityPlan
|
|
15
15
|
} from "./chunk-MYLHKD32.js";
|
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
import {
|
|
37
37
|
DEFAULT_TASK_TTL_MS,
|
|
38
38
|
clampTaskTtl
|
|
39
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-XWMT6ZSS.js";
|
|
40
40
|
import {
|
|
41
41
|
executeExpert
|
|
42
42
|
} from "./chunk-5ML3THNH.js";
|
|
@@ -90,7 +90,7 @@ import {
|
|
|
90
90
|
withFileLock,
|
|
91
91
|
withWarnings,
|
|
92
92
|
writeJobCancelled
|
|
93
|
-
} from "./chunk-
|
|
93
|
+
} from "./chunk-YBV7HIRX.js";
|
|
94
94
|
import {
|
|
95
95
|
NOOP_NOTIFIER,
|
|
96
96
|
RateLimiter,
|
|
@@ -118,10 +118,10 @@ import {
|
|
|
118
118
|
toolSuccessStructured,
|
|
119
119
|
withProgressHeartbeat,
|
|
120
120
|
wrapToolWithTimeout
|
|
121
|
-
} from "./chunk-
|
|
121
|
+
} from "./chunk-TBTXBJ7T.js";
|
|
122
122
|
import {
|
|
123
123
|
VERSION
|
|
124
|
-
} from "./chunk-
|
|
124
|
+
} from "./chunk-YSLHLLRK.js";
|
|
125
125
|
import {
|
|
126
126
|
allOf,
|
|
127
127
|
anyOf
|
|
@@ -46819,7 +46819,7 @@ function buildVotingInput(plan, config) {
|
|
|
46819
46819
|
}
|
|
46820
46820
|
async function executeSingleVote(plan, config, log) {
|
|
46821
46821
|
try {
|
|
46822
|
-
const { executeVoting } = await import("./consensus-vote-
|
|
46822
|
+
const { executeVoting } = await import("./consensus-vote-4ET2GP6B.js");
|
|
46823
46823
|
const input = buildVotingInput(plan, config);
|
|
46824
46824
|
const result = await executeVoting(input, log);
|
|
46825
46825
|
return parseVotingResult(result);
|
|
@@ -47794,7 +47794,7 @@ function createVoteStage({ config, startStage }) {
|
|
|
47794
47794
|
const strategy = config.votingStrategy ?? "higher_order";
|
|
47795
47795
|
await postProgress(config, "Vote", `Running consensus with ${strategy} strategy...`);
|
|
47796
47796
|
try {
|
|
47797
|
-
const { executeVoting } = await import("./consensus-vote-
|
|
47797
|
+
const { executeVoting } = await import("./consensus-vote-4ET2GP6B.js");
|
|
47798
47798
|
const votingResult = await executeVoting(
|
|
47799
47799
|
{
|
|
47800
47800
|
proposal: buildVoteProposal(plan, research),
|
|
@@ -57057,4 +57057,4 @@ export {
|
|
|
57057
57057
|
startPipelineEventBridge,
|
|
57058
57058
|
shutdownPipelineEventBridge
|
|
57059
57059
|
};
|
|
57060
|
-
//# sourceMappingURL=chunk-
|
|
57060
|
+
//# sourceMappingURL=chunk-2ON7ZOBD.js.map
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
toolStructuredError,
|
|
8
8
|
toolSuccessStructured,
|
|
9
9
|
wrapToolWithTimeout
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-TBTXBJ7T.js";
|
|
11
11
|
import {
|
|
12
12
|
JsonlStore,
|
|
13
13
|
SECURITY_KEYWORDS,
|
|
@@ -2284,4 +2284,4 @@ export {
|
|
|
2284
2284
|
runImprovementReview,
|
|
2285
2285
|
registerImprovementReviewTool
|
|
2286
2286
|
};
|
|
2287
|
-
//# sourceMappingURL=chunk-
|
|
2287
|
+
//# sourceMappingURL=chunk-JASILDK4.js.map
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import {
|
|
2
|
+
checkGatewayHost,
|
|
3
|
+
createOpenAICompatAdapter,
|
|
4
|
+
discoverGatewayCatalog,
|
|
5
|
+
readOpenAICompatEnv
|
|
6
|
+
} from "./chunk-QHMUW6G7.js";
|
|
7
|
+
import {
|
|
8
|
+
gatewayProxyUrl
|
|
9
|
+
} from "./chunk-D5EDP3EQ.js";
|
|
10
|
+
import {
|
|
11
|
+
DEFAULT_OPENAI_COMPAT_ENDPOINT,
|
|
12
|
+
GATEWAY_COST_ENV,
|
|
13
|
+
OPENAI_COMPAT_ENDPOINT_ENV,
|
|
14
|
+
OPENAI_COMPAT_KEY_ENV,
|
|
15
|
+
OPENAI_COMPAT_MODELS_ENV,
|
|
16
|
+
OPENAI_COMPAT_URL_ENV,
|
|
17
|
+
colors,
|
|
18
|
+
describeGatewayCostDeclaration,
|
|
19
|
+
gatewayModelFamily,
|
|
20
|
+
gatewaySlotMapping,
|
|
21
|
+
getErrorMessage,
|
|
22
|
+
getTimeProvider,
|
|
23
|
+
symbols,
|
|
24
|
+
writeLine
|
|
25
|
+
} from "./chunk-ZR5JWLC5.js";
|
|
26
|
+
|
|
27
|
+
// src/cli/doctor-gateway.ts
|
|
28
|
+
var PROBE_FAMILIES = ["anthropic", "openai", "google"];
|
|
29
|
+
var PROBE_SLOT = {
|
|
30
|
+
anthropic: "claude",
|
|
31
|
+
openai: "codex",
|
|
32
|
+
google: "gemini"
|
|
33
|
+
};
|
|
34
|
+
var PROBE_MAX_TOKENS = 256;
|
|
35
|
+
var PROBE_TIMEOUT_MS = 3e4;
|
|
36
|
+
async function checkGatewayHealth(options = {}) {
|
|
37
|
+
const config = options.config !== void 0 ? options.config : readOpenAICompatEnv();
|
|
38
|
+
if (config === null) return { state: "not_configured" };
|
|
39
|
+
const env = options.env ?? process.env;
|
|
40
|
+
const proxy = proxyStatus(config, env);
|
|
41
|
+
const guard = await checkGatewayHost(config.baseUrl);
|
|
42
|
+
if (guard.state === "refused_private_host") {
|
|
43
|
+
const { host: host2, reason, remedy } = guard;
|
|
44
|
+
return { state: "refused_private_host", host: host2, reason, remedy, proxy };
|
|
45
|
+
}
|
|
46
|
+
const host = guard.host;
|
|
47
|
+
const discovered = await discoverGatewayCatalog(config);
|
|
48
|
+
if (!discovered.ok) {
|
|
49
|
+
return {
|
|
50
|
+
state: "discovery_failed",
|
|
51
|
+
host,
|
|
52
|
+
error: redact(discovered.error.message, config),
|
|
53
|
+
proxy
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const { listedCount, models } = discovered.value;
|
|
57
|
+
if (models.length === 0) return { state: "no_chat_models", host, listedCount, proxy };
|
|
58
|
+
const adapters = models.map((m) => createOpenAICompatAdapter(m.id, config, m.created));
|
|
59
|
+
const slots = gatewaySlotMapping(adapters, env);
|
|
60
|
+
return {
|
|
61
|
+
state: "healthy",
|
|
62
|
+
host,
|
|
63
|
+
listedCount,
|
|
64
|
+
chatCount: models.length,
|
|
65
|
+
allowlistActive: (config.modelAllowlist ?? []).length > 0,
|
|
66
|
+
census: censusOf(models.map((m) => m.id)),
|
|
67
|
+
slots,
|
|
68
|
+
proxy,
|
|
69
|
+
probes: options.probe === true ? await probeFamilies(adapters, slots, config) : "skipped"
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function censusOf(ids) {
|
|
73
|
+
const census = {
|
|
74
|
+
anthropic: 0,
|
|
75
|
+
openai: 0,
|
|
76
|
+
google: 0,
|
|
77
|
+
unknown: 0
|
|
78
|
+
};
|
|
79
|
+
for (const id of ids) {
|
|
80
|
+
const family = gatewayModelFamily(id);
|
|
81
|
+
const bucket = family === void 0 || family === "other" ? "unknown" : family;
|
|
82
|
+
census[bucket] += 1;
|
|
83
|
+
}
|
|
84
|
+
return census;
|
|
85
|
+
}
|
|
86
|
+
function proxyStatus(config, env) {
|
|
87
|
+
if (config.proxyUrl !== void 0) return { kind: "proxy", proxyHost: proxyHostOf(config) };
|
|
88
|
+
if (gatewayProxyUrl(config.baseUrl, env) !== void 0) return { kind: "ignored" };
|
|
89
|
+
const withoutNoProxy = { ...env, no_proxy: void 0, NO_PROXY: void 0 };
|
|
90
|
+
if (gatewayProxyUrl(config.baseUrl, withoutNoProxy) !== void 0) return { kind: "exempt" };
|
|
91
|
+
return { kind: "direct" };
|
|
92
|
+
}
|
|
93
|
+
function proxyHostOf(config) {
|
|
94
|
+
try {
|
|
95
|
+
return new URL(config.proxyUrl ?? "").host;
|
|
96
|
+
} catch {
|
|
97
|
+
return "<unparseable proxy>";
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async function probeFamilies(adapters, slots, config) {
|
|
101
|
+
const probes = [];
|
|
102
|
+
for (const family of PROBE_FAMILIES) {
|
|
103
|
+
const model = slots[PROBE_SLOT[family]];
|
|
104
|
+
const adapter = adapters.find((a) => a.modelId === model);
|
|
105
|
+
if (adapter === void 0) {
|
|
106
|
+
probes.push({ family, outcome: "no_model" });
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
probes.push(await probeOne(family, adapter, config));
|
|
110
|
+
}
|
|
111
|
+
return probes;
|
|
112
|
+
}
|
|
113
|
+
async function probeOne(family, adapter, config) {
|
|
114
|
+
const clock = getTimeProvider();
|
|
115
|
+
const started = clock.now();
|
|
116
|
+
const base = { family, model: adapter.modelId };
|
|
117
|
+
try {
|
|
118
|
+
const result = await adapter.complete({
|
|
119
|
+
messages: [{ role: "user", content: "Reply with the single word OK." }],
|
|
120
|
+
maxTokens: PROBE_MAX_TOKENS,
|
|
121
|
+
timeoutMs: PROBE_TIMEOUT_MS
|
|
122
|
+
});
|
|
123
|
+
const latencyMs = clock.now() - started;
|
|
124
|
+
if (result.ok) return { ...base, outcome: "ok", latencyMs };
|
|
125
|
+
return { ...base, outcome: "failed", latencyMs, error: redact(result.error.message, config) };
|
|
126
|
+
} catch (e) {
|
|
127
|
+
const latencyMs = clock.now() - started;
|
|
128
|
+
return { ...base, outcome: "failed", latencyMs, error: redact(getErrorMessage(e), config) };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function redact(message, config) {
|
|
132
|
+
const secrets = [config.apiKey, ...Object.values(config.extraHeaders ?? {})];
|
|
133
|
+
return secrets.filter((s) => s !== "").reduce((text, secret) => text.replaceAll(secret, "<redacted>"), message);
|
|
134
|
+
}
|
|
135
|
+
function unservedGatewaySlots(health) {
|
|
136
|
+
if (health.state !== "healthy") return [];
|
|
137
|
+
const { slots } = health;
|
|
138
|
+
return PROBE_FAMILIES.map((family) => PROBE_SLOT[family]).filter(
|
|
139
|
+
(slot) => slots[slot] === "unavailable"
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
function gatewayVerdict(health) {
|
|
143
|
+
if (health.state === "not_configured") return "absent";
|
|
144
|
+
if (health.state !== "healthy") return "fail";
|
|
145
|
+
const failed = health.probes !== "skipped" && health.probes.some((p) => p.outcome === "failed");
|
|
146
|
+
if (failed) return "fail";
|
|
147
|
+
return unservedGatewaySlots(health).length < PROBE_FAMILIES.length ? "pass" : "fail";
|
|
148
|
+
}
|
|
149
|
+
function gatewaySlotWarnings(health, clis) {
|
|
150
|
+
if (gatewayVerdict(health) !== "pass") return [];
|
|
151
|
+
const unserved = unservedGatewaySlots(health);
|
|
152
|
+
const missing = (name) => clis.some((c) => c.name === name && !c.installed);
|
|
153
|
+
const warnings = PROBE_FAMILIES.filter((family) => {
|
|
154
|
+
const slot = PROBE_SLOT[family];
|
|
155
|
+
return unserved.includes(slot) && missing(slot);
|
|
156
|
+
}).map((family) => {
|
|
157
|
+
const slot = PROBE_SLOT[family];
|
|
158
|
+
return `${slot} slot unavailable: not installed, and the gateway has no ${family} model`;
|
|
159
|
+
});
|
|
160
|
+
if (missing("opencode")) {
|
|
161
|
+
warnings.push("opencode slot unavailable: not installed, and the gateway has no opencode slot");
|
|
162
|
+
}
|
|
163
|
+
return warnings;
|
|
164
|
+
}
|
|
165
|
+
function unservedSlotLines(health) {
|
|
166
|
+
const unserved = unservedGatewaySlots(health);
|
|
167
|
+
if (health.state !== "healthy" || unserved.length === 0) return [];
|
|
168
|
+
if (unserved.length === PROBE_FAMILIES.length) {
|
|
169
|
+
return [
|
|
170
|
+
"no slot has a gateway model: every pinned claude, codex or gemini slot is unavailable"
|
|
171
|
+
];
|
|
172
|
+
}
|
|
173
|
+
return PROBE_FAMILIES.filter((f) => unserved.includes(PROBE_SLOT[f])).map(
|
|
174
|
+
(f) => `${PROBE_SLOT[f]} slot unavailable: the gateway has no ${f} model`
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
function cliFailsVerdict(cli, gateway) {
|
|
178
|
+
if (!cli.installed) return gateway !== "pass";
|
|
179
|
+
return !cli.authenticated || cli.versionStatus === "unsupported";
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// src/cli/doctor-voter-transport.ts
|
|
183
|
+
var CHECK = `${colors.green}${symbols.check}${colors.reset}`;
|
|
184
|
+
var WARN = `${colors.yellow}${symbols.warn}${colors.reset}`;
|
|
185
|
+
var CROSS = `${colors.red}${symbols.cross}${colors.reset}`;
|
|
186
|
+
function printVoterTransportCheck(check, gateway) {
|
|
187
|
+
if (check.configured) {
|
|
188
|
+
writeLine(gatewayTransportLine(gateway));
|
|
189
|
+
printGatewayCostLine(check.cost);
|
|
190
|
+
printDeprecatedEnvLines(check.deprecatedEnv);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
writeLine(`${CHECK} Voter transport: ${colors.dim}CLI subprocess${colors.reset}`);
|
|
194
|
+
writeLine(
|
|
195
|
+
` ${colors.dim}Set ${OPENAI_COMPAT_URL_ENV} and ${OPENAI_COMPAT_KEY_ENV} for faster in-process voting${colors.reset}`
|
|
196
|
+
);
|
|
197
|
+
printDeprecatedEnvLines(check.deprecatedEnv);
|
|
198
|
+
}
|
|
199
|
+
function printDeprecatedEnvLines(deprecatedEnv) {
|
|
200
|
+
if (deprecatedEnv === void 0 || deprecatedEnv.length === 0) return;
|
|
201
|
+
for (const d of deprecatedEnv) {
|
|
202
|
+
const status = d.shadowed ? `ignored because ${d.replacement} is set` : "honoured";
|
|
203
|
+
writeLine(
|
|
204
|
+
`${WARN} ${d.name} is deprecated \u2014 use ${d.replacement} (alias until the next major, #6291); ${status}`
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
writeLine(
|
|
208
|
+
` ${colors.dim}The legacy names configure only the single-model custom-openai path; renaming to NEXUS_OPENAI_COMPAT_* opts into the gateway path (model discovery, in-process voter transport, api:<endpoint> arm)${colors.reset}`
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
var CEILING_CONSEQUENCE = "the task-class cost ceiling and the per-task budget exclude this gateway until declared";
|
|
212
|
+
var GAP_LINES = {
|
|
213
|
+
unset: `Gateway cost: UNSET \u2014 set ${GATEWAY_COST_ENV}=free|local|priced[:<in>,<out>]; ${CEILING_CONSEQUENCE}`,
|
|
214
|
+
invalid: `Gateway cost: INVALID \u2014 ${GATEWAY_COST_ENV} does not parse (the startup env warning names the reason); ${CEILING_CONSEQUENCE}`,
|
|
215
|
+
"no-default": `Gateway cost: NOT DECLARED for the voter gateway \u2014 ${GATEWAY_COST_ENV} names neither a bare declaration nor this gateway's endpoint (${OPENAI_COMPAT_ENDPOINT_ENV}, default ${DEFAULT_OPENAI_COMPAT_ENDPOINT}); add a bare or <endpoint>= entry; ${CEILING_CONSEQUENCE}`
|
|
216
|
+
};
|
|
217
|
+
function printGatewayCostLine(cost) {
|
|
218
|
+
if (cost === void 0) return;
|
|
219
|
+
if (typeof cost === "string") {
|
|
220
|
+
writeLine(`${WARN} ${GAP_LINES[cost]}`);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
writeLine(`${CHECK} Gateway cost: ${describeGatewayCostDeclaration(cost)}`);
|
|
224
|
+
}
|
|
225
|
+
function gatewayTransportLine(gateway) {
|
|
226
|
+
switch (gateway.state) {
|
|
227
|
+
case "healthy":
|
|
228
|
+
return `${CHECK} Voter transport: In-process gateway (${gateway.host}: ${String(gateway.chatCount)} chat models answered /models)`;
|
|
229
|
+
case "not_configured":
|
|
230
|
+
return `${WARN} Voter transport: In-process gateway (not measured)`;
|
|
231
|
+
default:
|
|
232
|
+
return `${CROSS} Voter transport: In-process gateway at ${gateway.host} FAILED (${gatewayFailureReason(gateway)}); voters fall back to CLI subprocesses`;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function gatewayFailureReason(gateway) {
|
|
236
|
+
switch (gateway.state) {
|
|
237
|
+
case "refused_private_host":
|
|
238
|
+
return `private-address guard refused it: ${gateway.reason}`;
|
|
239
|
+
case "discovery_failed":
|
|
240
|
+
return gateway.error;
|
|
241
|
+
case "no_chat_models":
|
|
242
|
+
return `/models listed ${String(gateway.listedCount)} models, none of them chat models`;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// src/cli/doctor-gateway-report.ts
|
|
247
|
+
var CHECK2 = `${colors.green}${symbols.check}${colors.reset}`;
|
|
248
|
+
var CROSS2 = `${colors.red}${symbols.cross}${colors.reset}`;
|
|
249
|
+
var WARN2 = `${colors.yellow}${symbols.warn}${colors.reset}`;
|
|
250
|
+
var PROBE_COST_NOTE = "one tiny completion per family; this spends gateway tokens";
|
|
251
|
+
function formatGatewayReport(health) {
|
|
252
|
+
const lines = [`${colors.cyan}Checking gateway (doctor --gateway)...${colors.reset}`, ""];
|
|
253
|
+
if (health.state === "not_configured") {
|
|
254
|
+
lines.push(
|
|
255
|
+
`${WARN2} Gateway: not configured \u2014 set ${OPENAI_COMPAT_URL_ENV} and ${OPENAI_COMPAT_KEY_ENV}`
|
|
256
|
+
);
|
|
257
|
+
return lines;
|
|
258
|
+
}
|
|
259
|
+
if (health.state !== "healthy") {
|
|
260
|
+
lines.push(`${CROSS2} Gateway ${health.host}: FAILED \u2014 ${gatewayFailureReason(health)}`);
|
|
261
|
+
if (health.state === "refused_private_host") lines.push(` ${health.remedy}`);
|
|
262
|
+
lines.push(` Proxy: ${formatProxy(health.proxy)}`);
|
|
263
|
+
return lines;
|
|
264
|
+
}
|
|
265
|
+
const filter = health.allowlistActive ? `after the chat filter and ${OPENAI_COMPAT_MODELS_ENV}` : "after the chat filter";
|
|
266
|
+
const { census, slots } = health;
|
|
267
|
+
lines.push(
|
|
268
|
+
`${CHECK2} Gateway ${health.host}: /models answered`,
|
|
269
|
+
` Private-address guard: allowed`,
|
|
270
|
+
` Proxy: ${formatProxy(health.proxy)}`,
|
|
271
|
+
` Models: ${String(health.listedCount)} listed, ${String(health.chatCount)} chat models ${filter}`,
|
|
272
|
+
` Families: anthropic ${String(census.anthropic)}, openai ${String(census.openai)}, google ${String(census.google)}, unknown ${String(census.unknown)}`,
|
|
273
|
+
` Slots (used when the CLI is not available): claude \u2192 ${slots.claude}, codex \u2192 ${slots.codex}, gemini \u2192 ${slots.gemini}`,
|
|
274
|
+
...formatProbes(health.probes),
|
|
275
|
+
...formatUnservedSlots(health)
|
|
276
|
+
);
|
|
277
|
+
return lines;
|
|
278
|
+
}
|
|
279
|
+
function formatGatewaySlotWarnings(health, clis) {
|
|
280
|
+
return gatewaySlotWarnings(health, clis).map((warning) => `${WARN2} ${warning}`);
|
|
281
|
+
}
|
|
282
|
+
function formatUnservedSlots(health) {
|
|
283
|
+
const glyph = gatewayVerdict(health) === "fail" ? CROSS2 : WARN2;
|
|
284
|
+
return unservedSlotLines(health).map((line) => ` ${glyph} ${line}`);
|
|
285
|
+
}
|
|
286
|
+
function formatProxy(proxy) {
|
|
287
|
+
switch (proxy.kind) {
|
|
288
|
+
case "direct":
|
|
289
|
+
return "direct (no proxy variable applies)";
|
|
290
|
+
case "proxy":
|
|
291
|
+
return `via ${proxy.proxyHost}`;
|
|
292
|
+
case "exempt":
|
|
293
|
+
return "direct (NO_PROXY exempts the gateway host)";
|
|
294
|
+
case "ignored":
|
|
295
|
+
return "direct (the proxy variable is not a usable http(s) URL; see the startup warning)";
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
function formatProbes(probes) {
|
|
299
|
+
if (probes === "skipped") {
|
|
300
|
+
return [` Completion probe: skipped \u2014 pass --probe to send ${PROBE_COST_NOTE}`];
|
|
301
|
+
}
|
|
302
|
+
return [` Completion probe (${PROBE_COST_NOTE}):`, ...probes.map(formatProbe)];
|
|
303
|
+
}
|
|
304
|
+
function formatProbe(probe) {
|
|
305
|
+
if (probe.outcome === "no_model") {
|
|
306
|
+
return ` ${WARN2} ${probe.family}: no gateway model of this family, not probed`;
|
|
307
|
+
}
|
|
308
|
+
const ms = `${String(probe.latencyMs)} ms`;
|
|
309
|
+
if (probe.outcome === "ok")
|
|
310
|
+
return ` ${CHECK2} ${probe.family}: ${probe.model} answered (${ms})`;
|
|
311
|
+
return ` ${CROSS2} ${probe.family}: ${probe.model} FAILED (${ms}) \u2014 ${probe.error ?? "no error message"}`;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export {
|
|
315
|
+
printVoterTransportCheck,
|
|
316
|
+
checkGatewayHealth,
|
|
317
|
+
gatewayVerdict,
|
|
318
|
+
cliFailsVerdict,
|
|
319
|
+
formatGatewayReport,
|
|
320
|
+
formatGatewaySlotWarnings
|
|
321
|
+
};
|
|
322
|
+
//# sourceMappingURL=chunk-L265O6SP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/doctor-gateway.ts","../src/cli/doctor-voter-transport.ts","../src/cli/doctor-gateway-report.ts"],"sourcesContent":["/**\n * The gateway half of `nexus-agents doctor` (#6609, epic #6612).\n *\n * Before this, doctor printed \"✓ Voter transport: In-process gateway\" from the\n * presence of two env vars, and its verdict counted only vendor API keys and\n * installed CLIs — so a host served entirely by a working gateway always\n * exited 1, and a host whose gateway was down could still print a check mark.\n *\n * {@link checkGatewayHealth} MEASURES the gateway with the calls the server\n * itself makes: the private-address guard (`checkGatewayHost`) and discovery\n * (`GET /models`, chat filter, allowlist). The completion probe is opt-in\n * because it spends tokens. {@link gatewayVerdict} turns the measurement into\n * the one term `isAllHealthy` and `failingVerdictTerms` read.\n *\n * Secrets: the key, the extra header values and the proxy URL's userinfo\n * never reach the result. Errors are redacted of the key and every header\n * value before they are stored.\n *\n * @module cli/doctor-gateway\n */\n\nimport type { IModelAdapter } from '../core/index.js';\nimport { getErrorMessage, getTimeProvider } from '../core/index.js';\nimport {\n createOpenAICompatAdapter,\n discoverGatewayCatalog,\n readOpenAICompatEnv,\n type OpenAICompatConfig,\n} from '../adapters/openai-compat-adapter.js';\nimport { checkGatewayHost } from '../adapters/gateway-host-status.js';\nimport { gatewayProxyUrl } from '../adapters/gateway-http.js';\nimport {\n gatewayModelFamily,\n gatewaySlotMapping,\n type GatewayFamily,\n type GatewaySlotMapping,\n} from '../adapters/gateway-family-slots.js';\nimport type { CliCheckResult } from './doctor.js';\n\n/** Census buckets: the three slot families, and every model outside them. */\nexport type GatewayCensusBucket = GatewayFamily | 'unknown';\n\n/**\n * Chat models per family. `unknown` counts every model not classified as one\n * of the three: an unrecognised vendor and a recognised fourth vendor alike.\n */\nexport type GatewayCensus = Readonly<Record<GatewayCensusBucket, number>>;\n\n/**\n * How gateway calls leave the host. Never carries the proxy URL: it can hold\n * `user:password@`. `proxyHost` is host and port only.\n */\nexport type GatewayProxyStatus =\n | { readonly kind: 'direct' }\n | { readonly kind: 'proxy'; readonly proxyHost: string }\n /** A proxy variable is set, but `NO_PROXY` exempts the gateway host. */\n | { readonly kind: 'exempt' }\n /** A proxy variable is set but unusable (not http/https): calls go direct. */\n | { readonly kind: 'ignored' };\n\n/** One family's completion probe. */\nexport type GatewayCompletionProbe =\n | { readonly family: GatewayFamily; readonly outcome: 'no_model' }\n | {\n readonly family: GatewayFamily;\n readonly outcome: 'ok' | 'failed';\n readonly model: string;\n readonly latencyMs: number;\n /** Redacted of the key and header values. Present when `failed`. */\n readonly error?: string;\n };\n\n/** The measured gateway. Every state but `not_configured` names the host. */\nexport type GatewayHealth =\n | { readonly state: 'not_configured' }\n | {\n readonly state: 'refused_private_host';\n readonly host: string;\n readonly reason: string;\n readonly remedy: string;\n readonly proxy: GatewayProxyStatus;\n }\n | {\n /** `/models` failed: unreachable, refused, timed out, rejected the key, or over the cap. */\n readonly state: 'discovery_failed';\n readonly host: string;\n readonly error: string;\n readonly proxy: GatewayProxyStatus;\n }\n | {\n /** `/models` answered, but no model survived the chat filter and allowlist. */\n readonly state: 'no_chat_models';\n readonly host: string;\n readonly listedCount: number;\n readonly proxy: GatewayProxyStatus;\n }\n | {\n readonly state: 'healthy';\n readonly host: string;\n /** Rows `/models` returned, before the chat filter. */\n readonly listedCount: number;\n /** Chat models kept after the chat filter (and the allowlist, when set). */\n readonly chatCount: number;\n readonly allowlistActive: boolean;\n readonly census: GatewayCensus;\n readonly slots: GatewaySlotMapping;\n readonly proxy: GatewayProxyStatus;\n /** `skipped` unless the operator passed `--probe`. */\n readonly probes: readonly GatewayCompletionProbe[] | 'skipped';\n };\n\n/** The verdict term: no gateway, a gateway that works, or one that does not. */\nexport type GatewayVerdict = 'absent' | 'pass' | 'fail';\n\nconst PROBE_FAMILIES: readonly GatewayFamily[] = ['anthropic', 'openai', 'google'];\nconst PROBE_SLOT: Readonly<Record<GatewayFamily, keyof GatewaySlotMapping>> = {\n anthropic: 'claude',\n openai: 'codex',\n google: 'gemini',\n};\n/** Room for a reasoning model to think and still answer one word. */\nconst PROBE_MAX_TOKENS = 256;\nconst PROBE_TIMEOUT_MS = 30_000;\n\nexport interface GatewayHealthOptions {\n /** Send one completion per family. Spends tokens; off unless `--probe`. */\n readonly probe?: boolean;\n /** Injectable for tests; defaults to the server's reader. */\n readonly config?: OpenAICompatConfig | null;\n readonly env?: NodeJS.ProcessEnv;\n}\n\n/** Measure the configured gateway. `not_configured` makes no network call. */\nexport async function checkGatewayHealth(\n options: GatewayHealthOptions = {}\n): Promise<GatewayHealth> {\n const config = options.config !== undefined ? options.config : readOpenAICompatEnv();\n if (config === null) return { state: 'not_configured' };\n const env = options.env ?? process.env;\n const proxy = proxyStatus(config, env);\n const guard = await checkGatewayHost(config.baseUrl);\n if (guard.state === 'refused_private_host') {\n const { host, reason, remedy } = guard;\n return { state: 'refused_private_host', host, reason, remedy, proxy };\n }\n const host = guard.host;\n const discovered = await discoverGatewayCatalog(config);\n if (!discovered.ok) {\n return {\n state: 'discovery_failed',\n host,\n error: redact(discovered.error.message, config),\n proxy,\n };\n }\n const { listedCount, models } = discovered.value;\n if (models.length === 0) return { state: 'no_chat_models', host, listedCount, proxy };\n const adapters = models.map((m) => createOpenAICompatAdapter(m.id, config, m.created));\n const slots = gatewaySlotMapping(adapters, env);\n return {\n state: 'healthy',\n host,\n listedCount,\n chatCount: models.length,\n allowlistActive: (config.modelAllowlist ?? []).length > 0,\n census: censusOf(models.map((m) => m.id)),\n slots,\n proxy,\n probes: options.probe === true ? await probeFamilies(adapters, slots, config) : 'skipped',\n };\n}\n\n/** Chat models per family. */\nfunction censusOf(ids: readonly string[]): GatewayCensus {\n const census: Record<GatewayCensusBucket, number> = {\n anthropic: 0,\n openai: 0,\n google: 0,\n unknown: 0,\n };\n for (const id of ids) {\n const family = gatewayModelFamily(id);\n const bucket = family === undefined || family === 'other' ? 'unknown' : family;\n census[bucket] += 1;\n }\n return census;\n}\n\n/** The proxy the gateway's calls use, told apart from its three look-alikes. */\nfunction proxyStatus(config: OpenAICompatConfig, env: NodeJS.ProcessEnv): GatewayProxyStatus {\n if (config.proxyUrl !== undefined) return { kind: 'proxy', proxyHost: proxyHostOf(config) };\n if (gatewayProxyUrl(config.baseUrl, env) !== undefined) return { kind: 'ignored' };\n const withoutNoProxy = { ...env, no_proxy: undefined, NO_PROXY: undefined };\n if (gatewayProxyUrl(config.baseUrl, withoutNoProxy) !== undefined) return { kind: 'exempt' };\n return { kind: 'direct' };\n}\n\nfunction proxyHostOf(config: OpenAICompatConfig): string {\n try {\n return new URL(config.proxyUrl ?? '').host;\n } catch {\n return '<unparseable proxy>';\n }\n}\n\n/** One completion per family, in family order; a family with no model is not called. */\nasync function probeFamilies(\n adapters: readonly IModelAdapter[],\n slots: GatewaySlotMapping,\n config: OpenAICompatConfig\n): Promise<readonly GatewayCompletionProbe[]> {\n const probes: GatewayCompletionProbe[] = [];\n for (const family of PROBE_FAMILIES) {\n const model = slots[PROBE_SLOT[family]];\n const adapter = adapters.find((a) => a.modelId === model);\n if (adapter === undefined) {\n probes.push({ family, outcome: 'no_model' });\n continue;\n }\n probes.push(await probeOne(family, adapter, config));\n }\n return probes;\n}\n\nasync function probeOne(\n family: GatewayFamily,\n adapter: IModelAdapter,\n config: OpenAICompatConfig\n): Promise<GatewayCompletionProbe> {\n const clock = getTimeProvider();\n const started = clock.now();\n const base = { family, model: adapter.modelId };\n try {\n const result = await adapter.complete({\n messages: [{ role: 'user', content: 'Reply with the single word OK.' }],\n maxTokens: PROBE_MAX_TOKENS,\n timeoutMs: PROBE_TIMEOUT_MS,\n });\n const latencyMs = clock.now() - started;\n if (result.ok) return { ...base, outcome: 'ok', latencyMs };\n return { ...base, outcome: 'failed', latencyMs, error: redact(result.error.message, config) };\n } catch (e: unknown) {\n const latencyMs = clock.now() - started;\n return { ...base, outcome: 'failed', latencyMs, error: redact(getErrorMessage(e), config) };\n }\n}\n\n/** `message` with the key and every extra header value replaced. */\nfunction redact(message: string, config: OpenAICompatConfig): string {\n const secrets = [config.apiKey, ...Object.values(config.extraHeaders ?? {})];\n return secrets\n .filter((s) => s !== '')\n .reduce((text, secret) => text.replaceAll(secret, '<redacted>'), message);\n}\n\n/** A vendor slot the gateway can serve: one per family. */\ntype GatewaySlot = keyof GatewaySlotMapping;\n\n/**\n * The family slots a healthy gateway does NOT serve, in family order (#6658):\n * each slot that resolved to `unavailable`. This is also exactly the set of\n * `--probe` `no_model` results — `probeFamilies` reads the same mapping — so a\n * `no_model` family counts against the verdict the same way. Empty for any\n * other state: there is no slot mapping to read.\n */\nfunction unservedGatewaySlots(health: GatewayHealth): readonly GatewaySlot[] {\n if (health.state !== 'healthy') return [];\n const { slots } = health;\n return PROBE_FAMILIES.map((family) => PROBE_SLOT[family]).filter(\n (slot) => slots[slot] === 'unavailable'\n );\n}\n\n/**\n * The verdict term. A requested probe that failed fails the gateway: it was\n * asked whether the gateway serves, and it said no. A gateway that serves NO\n * family slot fails too (#6658): it answered `/models` but every pinned slot\n * would throw \"unavailable\" at use. One that serves SOME slots passes, and\n * {@link gatewaySlotWarnings} names each slot it does not serve.\n */\nexport function gatewayVerdict(health: GatewayHealth): GatewayVerdict {\n if (health.state === 'not_configured') return 'absent';\n if (health.state !== 'healthy') return 'fail';\n const failed = health.probes !== 'skipped' && health.probes.some((p) => p.outcome === 'failed');\n if (failed) return 'fail';\n return unservedGatewaySlots(health).length < PROBE_FAMILIES.length ? 'pass' : 'fail';\n}\n\n/**\n * One warning per missing CLI whose slot the passing gateway does not serve\n * (#6658). Such a CLI does not fail the verdict — other slots work — but a\n * task pinned to it throws \"unavailable\" at use, so it is named rather than\n * silently excused. opencode has no gateway slot, so a missing opencode is\n * always named. No warning when the gateway does not pass: the missing CLIs\n * then fail the verdict themselves ({@link cliFailsVerdict}).\n */\nexport function gatewaySlotWarnings(\n health: GatewayHealth,\n clis: readonly CliCheckResult[]\n): string[] {\n if (gatewayVerdict(health) !== 'pass') return [];\n const unserved = unservedGatewaySlots(health);\n const missing = (name: CliCheckResult['name']): boolean =>\n clis.some((c) => c.name === name && !c.installed);\n const warnings = PROBE_FAMILIES.filter((family) => {\n const slot = PROBE_SLOT[family];\n return unserved.includes(slot) && missing(slot);\n }).map((family) => {\n const slot = PROBE_SLOT[family];\n return `${slot} slot unavailable: not installed, and the gateway has no ${family} model`;\n });\n if (missing('opencode')) {\n warnings.push('opencode slot unavailable: not installed, and the gateway has no opencode slot');\n }\n return warnings;\n}\n\n/**\n * The `doctor --gateway` line for each family slot the gateway does not serve,\n * in family order — or one line saying no slot is served, the case\n * {@link gatewayVerdict} fails on.\n */\nexport function unservedSlotLines(health: GatewayHealth): string[] {\n const unserved = unservedGatewaySlots(health);\n if (health.state !== 'healthy' || unserved.length === 0) return [];\n if (unserved.length === PROBE_FAMILIES.length) {\n return [\n 'no slot has a gateway model: every pinned claude, codex or gemini slot is unavailable',\n ];\n }\n return PROBE_FAMILIES.filter((f) => unserved.includes(PROBE_SLOT[f])).map(\n (f) => `${PROBE_SLOT[f]} slot unavailable: the gateway has no ${f} model`\n );\n}\n\n/** The gateway host for a verdict line, or undefined when none is configured. */\nexport function gatewayHostOf(health: GatewayHealth): string | undefined {\n return health.state === 'not_configured' ? undefined : health.host;\n}\n\n/**\n * Whether one CLI counts against the verdict. With a passing gateway — one\n * that serves at least one family slot — a CLI that is not installed does\n * not. When its own slot has a gateway model it is excused outright; when it\n * has none (or it is opencode, which never has one) it is a named warning\n * from {@link gatewaySlotWarnings}, not a failure (#6658). A host with zero\n * served slots has a failing gateway, so every missing CLI counts. An\n * installed CLI still has to be authenticated and supported.\n */\nexport function cliFailsVerdict(cli: CliCheckResult, gateway: GatewayVerdict): boolean {\n if (!cli.installed) return gateway !== 'pass';\n return !cli.authenticated || cli.versionStatus === 'unsupported';\n}\n","/**\n * Doctor lines for the voter transport (#4255) and the gateway's cost\n * declaration (#4392 increment 2). A sibling of `doctor-formatting.ts` for\n * the file cap, in the same shape as `doctor-claude-model.ts`.\n *\n * @module cli/doctor-voter-transport\n */\n\nimport { describeGatewayCostDeclaration } from '../adapters/sdk/gateway-cost.js';\nimport {\n DEFAULT_OPENAI_COMPAT_ENDPOINT,\n GATEWAY_COST_ENV,\n OPENAI_COMPAT_ENDPOINT_ENV,\n OPENAI_COMPAT_KEY_ENV,\n OPENAI_COMPAT_URL_ENV,\n} from '../adapters/sdk/types.js';\nimport type { VoterTransportCheck } from './doctor.js';\nimport type { GatewayHealth } from './doctor-gateway.js';\nimport { colors, symbols, writeLine } from './ansi-output.js';\n\nconst CHECK = `${colors.green}${symbols.check}${colors.reset}`;\nconst WARN = `${colors.yellow}${symbols.warn}${colors.reset}`;\nconst CROSS = `${colors.red}${symbols.cross}${colors.reset}`;\n\n/**\n * Prints which transport voter/consensus calls will use (#4255): an\n * in-process OpenAI-compatible gateway when configured, else the CLI\n * subprocess round-robin fallback. A configured gateway is followed by its\n * cost line. The gateway line reports the MEASURED gateway (#6609), not the\n * presence of its env vars: a gateway whose discovery fails is not used, and\n * the server falls back to CLI subprocesses.\n */\nexport function printVoterTransportCheck(check: VoterTransportCheck, gateway: GatewayHealth): void {\n if (check.configured) {\n writeLine(gatewayTransportLine(gateway));\n printGatewayCostLine(check.cost);\n printDeprecatedEnvLines(check.deprecatedEnv);\n return;\n }\n writeLine(`${CHECK} Voter transport: ${colors.dim}CLI subprocess${colors.reset}`);\n writeLine(\n ` ${colors.dim}Set ${OPENAI_COMPAT_URL_ENV} and ${OPENAI_COMPAT_KEY_ENV} for faster in-process voting${colors.reset}`\n );\n printDeprecatedEnvLines(check.deprecatedEnv);\n}\n\n/**\n * One warning per deprecated gateway alias in use (#4392 increment 3),\n * naming the replacement and whether the alias is honoured or ignored, then\n * the option-C consequence once: renaming is the opt-in to the gateway path.\n * Warnings only; `allHealthy` is untouched. No line when none is set.\n */\nfunction printDeprecatedEnvLines(deprecatedEnv: VoterTransportCheck['deprecatedEnv']): void {\n if (deprecatedEnv === undefined || deprecatedEnv.length === 0) return;\n for (const d of deprecatedEnv) {\n const status = d.shadowed ? `ignored because ${d.replacement} is set` : 'honoured';\n writeLine(\n `${WARN} ${d.name} is deprecated — use ${d.replacement} (alias until the next major, #6291); ${status}`\n );\n }\n writeLine(\n ` ${colors.dim}The legacy names configure only the single-model custom-openai path; renaming to ` +\n `NEXUS_OPENAI_COMPAT_* opts into the gateway path (model discovery, in-process voter ` +\n `transport, api:<endpoint> arm)${colors.reset}`\n );\n}\n\nconst CEILING_CONSEQUENCE =\n 'the task-class cost ceiling and the per-task budget exclude this gateway until declared';\n\n/** One warning per gap, each naming its own fix (see `VoterTransportCheck.cost`). */\nconst GAP_LINES: Record<'unset' | 'invalid' | 'no-default', string> = {\n unset: `Gateway cost: UNSET — set ${GATEWAY_COST_ENV}=free|local|priced[:<in>,<out>]; ${CEILING_CONSEQUENCE}`,\n invalid: `Gateway cost: INVALID — ${GATEWAY_COST_ENV} does not parse (the startup env warning names the reason); ${CEILING_CONSEQUENCE}`,\n 'no-default': `Gateway cost: NOT DECLARED for the voter gateway — ${GATEWAY_COST_ENV} names neither a bare declaration nor this gateway's endpoint (${OPENAI_COMPAT_ENDPOINT_ENV}, default ${DEFAULT_OPENAI_COMPAT_ENDPOINT}); add a bare or <endpoint>= entry; ${CEILING_CONSEQUENCE}`,\n};\n\n/**\n * The gateway's cost declaration. Each gap is a warning that names its fix\n * and the consequence; none touches `allHealthy`. No line at all when no\n * gateway is configured (`cost` absent).\n */\nfunction printGatewayCostLine(cost: VoterTransportCheck['cost']): void {\n if (cost === undefined) return;\n if (typeof cost === 'string') {\n writeLine(`${WARN} ${GAP_LINES[cost]}`);\n return;\n }\n writeLine(`${CHECK} Gateway cost: ${describeGatewayCostDeclaration(cost)}`);\n}\n\n/** The voter-transport line for a configured gateway, from its measurement. */\nfunction gatewayTransportLine(gateway: GatewayHealth): string {\n switch (gateway.state) {\n case 'healthy':\n return (\n `${CHECK} Voter transport: In-process gateway (${gateway.host}: ` +\n `${String(gateway.chatCount)} chat models answered /models)`\n );\n case 'not_configured':\n // The env names a gateway but its config did not read: nothing was measured.\n return `${WARN} Voter transport: In-process gateway (not measured)`;\n default:\n return (\n `${CROSS} Voter transport: In-process gateway at ${gateway.host} FAILED ` +\n `(${gatewayFailureReason(gateway)}); voters fall back to CLI subprocesses`\n );\n }\n}\n\n/** Why a configured gateway is not in use, naming no secret. */\nexport function gatewayFailureReason(\n gateway: Exclude<GatewayHealth, { state: 'healthy' | 'not_configured' }>\n): string {\n switch (gateway.state) {\n case 'refused_private_host':\n return `private-address guard refused it: ${gateway.reason}`;\n case 'discovery_failed':\n return gateway.error;\n case 'no_chat_models':\n return `/models listed ${String(gateway.listedCount)} models, none of them chat models`;\n }\n}\n","/**\n * The `doctor --gateway` section (#6609): what the gateway measurement in\n * `doctor-gateway.ts` found, line by line. Rendering only; the verdict lives\n * in `gatewayVerdict`. Prints no key, header value or proxy credential — the\n * measurement never holds them.\n *\n * @module cli/doctor-gateway-report\n */\n\nimport {\n OPENAI_COMPAT_KEY_ENV,\n OPENAI_COMPAT_MODELS_ENV,\n OPENAI_COMPAT_URL_ENV,\n} from '../adapters/sdk/types.js';\nimport type { CliCheckResult } from './doctor.js';\nimport {\n gatewaySlotWarnings,\n gatewayVerdict,\n unservedSlotLines,\n type GatewayCompletionProbe,\n type GatewayHealth,\n type GatewayProxyStatus,\n} from './doctor-gateway.js';\nimport { gatewayFailureReason } from './doctor-voter-transport.js';\nimport { colors, symbols } from './ansi-output.js';\n\nconst CHECK = `${colors.green}${symbols.check}${colors.reset}`;\nconst CROSS = `${colors.red}${symbols.cross}${colors.reset}`;\nconst WARN = `${colors.yellow}${symbols.warn}${colors.reset}`;\n\n/** Says what the probe costs whether or not it ran, so the flag is never a surprise. */\nconst PROBE_COST_NOTE = 'one tiny completion per family; this spends gateway tokens';\n\n/** The `doctor --gateway` section as lines. */\nexport function formatGatewayReport(health: GatewayHealth): string[] {\n const lines = [`${colors.cyan}Checking gateway (doctor --gateway)...${colors.reset}`, ''];\n if (health.state === 'not_configured') {\n lines.push(\n `${WARN} Gateway: not configured — set ${OPENAI_COMPAT_URL_ENV} and ${OPENAI_COMPAT_KEY_ENV}`\n );\n return lines;\n }\n if (health.state !== 'healthy') {\n lines.push(`${CROSS} Gateway ${health.host}: FAILED — ${gatewayFailureReason(health)}`);\n if (health.state === 'refused_private_host') lines.push(` ${health.remedy}`);\n lines.push(` Proxy: ${formatProxy(health.proxy)}`);\n return lines;\n }\n const filter = health.allowlistActive\n ? `after the chat filter and ${OPENAI_COMPAT_MODELS_ENV}`\n : 'after the chat filter';\n const { census, slots } = health;\n lines.push(\n `${CHECK} Gateway ${health.host}: /models answered`,\n ` Private-address guard: allowed`,\n ` Proxy: ${formatProxy(health.proxy)}`,\n ` Models: ${String(health.listedCount)} listed, ${String(health.chatCount)} chat models ${filter}`,\n ` Families: anthropic ${String(census.anthropic)}, openai ${String(census.openai)}, ` +\n `google ${String(census.google)}, unknown ${String(census.unknown)}`,\n ` Slots (used when the CLI is not available): claude → ${slots.claude}, ` +\n `codex → ${slots.codex}, gemini → ${slots.gemini}`,\n ...formatProbes(health.probes),\n ...formatUnservedSlots(health)\n );\n return lines;\n}\n\n/**\n * The missing CLIs a passing gateway cannot stand in for, one warning line\n * each (#6658). Printed with every `doctor` run, `--gateway` or not: they are\n * not failures — other slots work — but they are never silently excused.\n */\nexport function formatGatewaySlotWarnings(\n health: GatewayHealth,\n clis: readonly CliCheckResult[]\n): string[] {\n return gatewaySlotWarnings(health, clis).map((warning) => `${WARN} ${warning}`);\n}\n\n/**\n * Each family slot the gateway does not serve (#6658): a warning while some\n * slot works, a failure when none does — the same line `gatewayVerdict` fails on.\n */\nfunction formatUnservedSlots(health: GatewayHealth): string[] {\n const glyph = gatewayVerdict(health) === 'fail' ? CROSS : WARN;\n return unservedSlotLines(health).map((line) => ` ${glyph} ${line}`);\n}\n\nfunction formatProxy(proxy: GatewayProxyStatus): string {\n switch (proxy.kind) {\n case 'direct':\n return 'direct (no proxy variable applies)';\n case 'proxy':\n return `via ${proxy.proxyHost}`;\n case 'exempt':\n return 'direct (NO_PROXY exempts the gateway host)';\n case 'ignored':\n return 'direct (the proxy variable is not a usable http(s) URL; see the startup warning)';\n }\n}\n\nfunction formatProbes(probes: readonly GatewayCompletionProbe[] | 'skipped'): string[] {\n if (probes === 'skipped') {\n return [` Completion probe: skipped — pass --probe to send ${PROBE_COST_NOTE}`];\n }\n return [` Completion probe (${PROBE_COST_NOTE}):`, ...probes.map(formatProbe)];\n}\n\nfunction formatProbe(probe: GatewayCompletionProbe): string {\n if (probe.outcome === 'no_model') {\n return ` ${WARN} ${probe.family}: no gateway model of this family, not probed`;\n }\n const ms = `${String(probe.latencyMs)} ms`;\n if (probe.outcome === 'ok')\n return ` ${CHECK} ${probe.family}: ${probe.model} answered (${ms})`;\n return ` ${CROSS} ${probe.family}: ${probe.model} FAILED (${ms}) — ${probe.error ?? 'no error message'}`;\n}\n"],"mappings":"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","names":["host","CHECK","CROSS","WARN"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
VERSION
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-YSLHLLRK.js";
|
|
4
4
|
import {
|
|
5
5
|
DEFAULT_EXECUTION_MODE
|
|
6
6
|
} from "./chunk-ECJ52NSK.js";
|
|
@@ -2342,4 +2342,4 @@ export {
|
|
|
2342
2342
|
DEFAULT_CONSENSUS_CONFIG,
|
|
2343
2343
|
ConsensusMetricsSchema
|
|
2344
2344
|
};
|
|
2345
|
-
//# sourceMappingURL=chunk-
|
|
2345
|
+
//# sourceMappingURL=chunk-TBTXBJ7T.js.map
|