@alfe.ai/claude-code-host 0.1.15 → 0.1.17
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/cli.js +1 -1
- package/dist/host-config.cjs +595 -123
- package/dist/host-config.js +529 -117
- package/dist/index.cjs +12 -2
- package/dist/index.d.cts +254 -57
- package/dist/index.d.ts +254 -57
- package/dist/index.js +2 -2
- package/package.json +5 -5
package/dist/host-config.cjs
CHANGED
|
@@ -13,22 +13,35 @@ let _alfe_ai_config = require("@alfe.ai/config");
|
|
|
13
13
|
/**
|
|
14
14
|
* Child-process environment construction for spawned `claude` CLI processes.
|
|
15
15
|
*
|
|
16
|
-
*
|
|
16
|
+
* Every turn runs on exactly ONE auth source (`ChildAuth`), resolved by the
|
|
17
|
+
* host (see DEVELOPING.md "Auth source resolution"):
|
|
18
|
+
*
|
|
19
|
+
* - `credential` — the agent's stored Claude credential, set as exactly one
|
|
20
|
+
* variable: `CLAUDE_CODE_OAUTH_TOKEN` (subscription token from
|
|
21
|
+
* `claude setup-token`) or `ANTHROPIC_API_KEY` (Anthropic API key).
|
|
22
|
+
* - `local_login` — self-hosted only: no auth variable, so `claude` uses the
|
|
23
|
+
* machine's own login (and whatever provider the operator configured).
|
|
24
|
+
* - `ai_proxy` — the Alfe AI proxy via the daemon's loopback proxy:
|
|
25
|
+
* `ANTHROPIC_BASE_URL` = the local proxy, `ANTHROPIC_AUTH_TOKEN` = a
|
|
26
|
+
* placeholder the local proxy replaces with the daemon's agent key, and
|
|
27
|
+
* every model alias pinned to an Alfe registry id (the proxy routes exact
|
|
28
|
+
* registry ids only; the CLI's built-in aliases resolve to dated ids that
|
|
29
|
+
* are a 409 there).
|
|
30
|
+
*
|
|
31
|
+
* Invariants:
|
|
17
32
|
*
|
|
18
33
|
* 1. **Scrub inherited Claude auth.** `ANTHROPIC_API_KEY` / `ANTHROPIC_BASE_URL`
|
|
19
34
|
* / `ANTHROPIC_AUTH_TOKEN` / `CLAUDE_CODE_OAUTH_TOKEN` are removed from BOTH
|
|
20
35
|
* the parent env and any caller `extra`. A self-hosted Mac very often has
|
|
21
36
|
* these set for other tooling; if one reaches `claude` it silently switches
|
|
22
|
-
* accounts or billing plane
|
|
23
|
-
*
|
|
37
|
+
* accounts or billing plane. Credential and proxy modes also drop provider
|
|
38
|
+
* overrides (Bedrock/Vertex/Foundry); proxy mode additionally drops model
|
|
39
|
+
* and header overrides so nothing reaches the proxy that it cannot price.
|
|
24
40
|
*
|
|
25
|
-
* 2. **Inject the
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* else can override it. With no credential, no auth variable is set and
|
|
30
|
-
* `claude` uses the machine's own login (self-hosted fallback). The value
|
|
31
|
-
* lives only in the host's memory and this env — never on disk, never logged.
|
|
41
|
+
* 2. **Inject the source last.** Nothing in `base` or `extra` can override the
|
|
42
|
+
* injected credential or proxy settings. A credential lives only in the
|
|
43
|
+
* host's memory and this env, never on disk, never logged. Proxy settings
|
|
44
|
+
* never appear in a credential or local-login env.
|
|
32
45
|
*
|
|
33
46
|
* 3. **Set `ALFE_CLAUDE_BRIDGE=1`.** The Phase-1 CLI hooks
|
|
34
47
|
* (`alfe claude hook ...` in `packages/cli/src/commands/claude.ts`) check
|
|
@@ -46,10 +59,10 @@ const SCRUBBED_ANTHROPIC_ENV_VARS = [
|
|
|
46
59
|
];
|
|
47
60
|
/**
|
|
48
61
|
* Provider-selection and endpoint overrides that would route `claude` away from
|
|
49
|
-
* the Anthropic API. Dropped whenever
|
|
50
|
-
* inherited `CLAUDE_CODE_USE_BEDROCK=1` (say) can't send the turn to
|
|
51
|
-
* and ignore the
|
|
52
|
-
* the machine's own `claude` setup is the
|
|
62
|
+
* the Anthropic API. Dropped whenever a credential or the AI proxy is in use,
|
|
63
|
+
* so an inherited `CLAUDE_CODE_USE_BEDROCK=1` (say) can't send the turn to
|
|
64
|
+
* Bedrock and ignore the chosen source. On a local login they are left alone:
|
|
65
|
+
* the machine's own `claude` setup is the source, whatever provider it uses.
|
|
53
66
|
*/
|
|
54
67
|
const PROVIDER_OVERRIDE_ENV_VARS = [
|
|
55
68
|
"CLAUDE_CODE_USE_BEDROCK",
|
|
@@ -68,6 +81,60 @@ const PROVIDER_OVERRIDE_ENV_VARS = [
|
|
|
68
81
|
];
|
|
69
82
|
/** Cross-phase contract marker (see header + packages/cli/src/commands/claude.ts). */
|
|
70
83
|
const BRIDGE_ENV_VAR = "ALFE_CLAUDE_BRIDGE";
|
|
84
|
+
/**
|
|
85
|
+
* Placeholder `ANTHROPIC_AUTH_TOKEN` (sent as `Authorization: Bearer`) for
|
|
86
|
+
* proxy mode. `@alfe.ai/ai-proxy-local` keeps an incoming `alfe_…` key and
|
|
87
|
+
* replaces anything else with the daemon's agent key (as `x-api-key`, dropping
|
|
88
|
+
* `authorization`), so the agent key itself never enters the `claude` env (a
|
|
89
|
+
* managed agent has Bash). Never `alfe_…`.
|
|
90
|
+
*/
|
|
91
|
+
const PROXY_API_KEY_PLACEHOLDER = "alfe-local-proxy";
|
|
92
|
+
/**
|
|
93
|
+
* Default local AI proxy (`DEFAULT_AI_PROXY_PORT` in `@alfe.ai/ai-proxy-local`).
|
|
94
|
+
* The daemon passes the URL it actually bound as `ALFE_LOCAL_AI_PROXY_URL`;
|
|
95
|
+
* this is the fallback. Bare host, no `/v1`: `claude` appends `/v1/messages`.
|
|
96
|
+
*/
|
|
97
|
+
const DEFAULT_LOCAL_AI_PROXY_URL = "http://127.0.0.1:18193";
|
|
98
|
+
/**
|
|
99
|
+
* The model `claude -p --model` runs in proxy mode when the agent has no
|
|
100
|
+
* `defaultModel` (or the daemon's lookup timed out). `claude`'s own default is
|
|
101
|
+
* a dated id the proxy cannot route. Haiku 4.5 is the one Claude model seeded
|
|
102
|
+
* on every stage (the memory integration depends on it), so it is the only
|
|
103
|
+
* id guaranteed to route; the dashboard always sets a model, so this is rare.
|
|
104
|
+
*/
|
|
105
|
+
const PROXY_DEFAULT_MODEL = "claude-haiku-4-5";
|
|
106
|
+
/**
|
|
107
|
+
* Fixed model env in proxy mode, Alfe registry ids (`AnthropicModel` in
|
|
108
|
+
* `@alfe/types`; the child-env test asserts it). `ANTHROPIC_SMALL_FAST_MODEL`
|
|
109
|
+
* drives background calls (titles, summaries); `ANTHROPIC_DEFAULT_HAIKU_MODEL`
|
|
110
|
+
* resolves the `haiku` alias. Haiku 4.5 because it is seeded on every stage.
|
|
111
|
+
* claude 2.1.284/2.1.287 read these and {@link PROXY_AGENT_MODEL_ENV_VARS}.
|
|
112
|
+
*/
|
|
113
|
+
const PROXY_MODEL_ENV = {
|
|
114
|
+
ANTHROPIC_SMALL_FAST_MODEL: "claude-haiku-4-5",
|
|
115
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: "claude-haiku-4-5"
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* The `sonnet` / `opus` aliases (subagents) resolve to the agent's OWN model
|
|
119
|
+
* in proxy mode: the agents service validated it against the registry, while a
|
|
120
|
+
* fixed 5.x id may not be enabled on the workspace.
|
|
121
|
+
*/
|
|
122
|
+
const PROXY_AGENT_MODEL_ENV_VARS = ["ANTHROPIC_DEFAULT_SONNET_MODEL", "ANTHROPIC_DEFAULT_OPUS_MODEL"];
|
|
123
|
+
/**
|
|
124
|
+
* Inherited overrides that would send a proxy turn a model or header the proxy
|
|
125
|
+
* can't price or doesn't expect. Dropped in proxy mode only.
|
|
126
|
+
*/
|
|
127
|
+
const PROXY_SCRUBBED_ENV_VARS = [
|
|
128
|
+
...[
|
|
129
|
+
...Object.keys(PROXY_MODEL_ENV),
|
|
130
|
+
...PROXY_AGENT_MODEL_ENV_VARS,
|
|
131
|
+
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC"
|
|
132
|
+
],
|
|
133
|
+
"ANTHROPIC_MODEL",
|
|
134
|
+
"ANTHROPIC_DEFAULT_FABLE_MODEL",
|
|
135
|
+
"ANTHROPIC_CUSTOM_HEADERS",
|
|
136
|
+
"CLAUDE_CODE_SUBAGENT_MODEL"
|
|
137
|
+
];
|
|
71
138
|
/** The single env var each credential kind is exported as. */
|
|
72
139
|
const CREDENTIAL_ENV_VAR = {
|
|
73
140
|
oauth_token: "CLAUDE_CODE_OAUTH_TOKEN",
|
|
@@ -78,21 +145,30 @@ const CREDENTIAL_ENV_VAR = {
|
|
|
78
145
|
* explicitly so it unit-tests without touching `process.env`.
|
|
79
146
|
*
|
|
80
147
|
* Order: merge `base` + `extra` → drop every scrubbed var (plus provider
|
|
81
|
-
* overrides
|
|
82
|
-
*
|
|
83
|
-
* smuggle an auth variable in or override the
|
|
148
|
+
* overrides for a credential or the proxy, plus model overrides for the
|
|
149
|
+
* proxy) → set the bridge marker → set the source's variables. Neither `base`
|
|
150
|
+
* nor `extra` can smuggle an auth variable in or override the source.
|
|
151
|
+
* `auth` omitted = local login.
|
|
84
152
|
*/
|
|
85
|
-
function buildChildEnv(base, extra,
|
|
153
|
+
function buildChildEnv(base, extra, auth = { mode: "local_login" }) {
|
|
86
154
|
const merged = {
|
|
87
155
|
...base,
|
|
88
156
|
...extra
|
|
89
157
|
};
|
|
90
158
|
const scrubbed = new Set(SCRUBBED_ANTHROPIC_ENV_VARS);
|
|
91
|
-
if (
|
|
159
|
+
if (auth.mode !== "local_login") for (const key of PROVIDER_OVERRIDE_ENV_VARS) scrubbed.add(key);
|
|
160
|
+
if (auth.mode === "ai_proxy") for (const key of PROXY_SCRUBBED_ENV_VARS) scrubbed.add(key);
|
|
92
161
|
const env = {};
|
|
93
162
|
for (const [key, value] of Object.entries(merged)) if (!scrubbed.has(key)) env[key] = value;
|
|
94
163
|
env[BRIDGE_ENV_VAR] = "1";
|
|
95
|
-
if (credential) env[CREDENTIAL_ENV_VAR[credential.kind]] = credential.value;
|
|
164
|
+
if (auth.mode === "credential") env[CREDENTIAL_ENV_VAR[auth.credential.kind]] = auth.credential.value;
|
|
165
|
+
else if (auth.mode === "ai_proxy") {
|
|
166
|
+
env.ANTHROPIC_BASE_URL = auth.baseUrl;
|
|
167
|
+
env.ANTHROPIC_AUTH_TOKEN = PROXY_API_KEY_PLACEHOLDER;
|
|
168
|
+
Object.assign(env, PROXY_MODEL_ENV);
|
|
169
|
+
for (const key of PROXY_AGENT_MODEL_ENV_VARS) env[key] = auth.model;
|
|
170
|
+
env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = "1";
|
|
171
|
+
}
|
|
96
172
|
return env;
|
|
97
173
|
}
|
|
98
174
|
//#endregion
|
|
@@ -173,14 +249,52 @@ function isAuthFailureText(text) {
|
|
|
173
249
|
const lower = text.toLowerCase();
|
|
174
250
|
return AUTH_FAILURE_MARKERS.some((marker) => lower.includes(marker));
|
|
175
251
|
}
|
|
252
|
+
/**
|
|
253
|
+
* Proxy texts for a model this workspace or agent cannot use: 409
|
|
254
|
+
* `model_route_denied` (unknown, disabled or mis-routed registry id) and 403
|
|
255
|
+
* `model_not_approved` (tenant model policy). The 403 must not read as a
|
|
256
|
+
* refused key.
|
|
257
|
+
*/
|
|
258
|
+
const MODEL_UNAVAILABLE_MARKERS = [
|
|
259
|
+
"model_route_denied",
|
|
260
|
+
"model_not_approved",
|
|
261
|
+
"model is not available",
|
|
262
|
+
"is restricted to:",
|
|
263
|
+
"not approved for this organization",
|
|
264
|
+
"requires an anthropic-routed model",
|
|
265
|
+
"no supported standard inference route",
|
|
266
|
+
"cannot use the standard data plane",
|
|
267
|
+
"not an enabled reviewed"
|
|
268
|
+
];
|
|
269
|
+
/**
|
|
270
|
+
* Classify an Alfe AI proxy refusal from the text `claude` reports. The proxy
|
|
271
|
+
* answers credit and usage-limit denials on `/v1/messages` with a 429
|
|
272
|
+
* `rate_limit_error` (never 402, see services/ai DEVELOPING.md "Credit denials
|
|
273
|
+
* on LLM routes are 429"); claude 2.1.287 surfaces it as
|
|
274
|
+
* `API Error: Request rejected (429) · <message>` (fixture
|
|
275
|
+
* `turn-proxy-credits.stream.jsonl`). Credit texts also say "usage limit
|
|
276
|
+
* reached", so they are checked first. An Anthropic subscription limit
|
|
277
|
+
* ("Claude AI usage limit reached") is not an Alfe denial.
|
|
278
|
+
*/
|
|
279
|
+
function classifyProxyDenial(text) {
|
|
280
|
+
const lower = text.toLowerCase();
|
|
281
|
+
if (lower.includes("no ai credits left") || lower.includes("partner account cannot run ai requests") || lower.includes("insufficient_credits") || lower.includes("credits_exhausted")) return "credits";
|
|
282
|
+
if (/\balfe (?:daily |weekly |monthly )?usage limit reached\b/.test(lower)) return "usage_limit";
|
|
283
|
+
if (MODEL_UNAVAILABLE_MARKERS.some((marker) => lower.includes(marker))) return "model_unavailable";
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
176
286
|
/** HTTP statuses on a `result` event that mean "credential refused". */
|
|
177
287
|
function isAuthFailureStatus(status) {
|
|
178
288
|
return status === 401 || status === 403;
|
|
179
289
|
}
|
|
180
290
|
const AUTH_PROBE_TIMEOUT_MS = 6e4;
|
|
181
291
|
const MAX_PROBE_OUTPUT_CHARS = 64e3;
|
|
182
|
-
/**
|
|
183
|
-
|
|
292
|
+
/**
|
|
293
|
+
* argv for the probe. No tools, no session file, no MCP, no settings files.
|
|
294
|
+
* `model` is `haiku` (the alias) on a credential or local login, and the
|
|
295
|
+
* registry Haiku id through the AI proxy.
|
|
296
|
+
*/
|
|
297
|
+
function buildProbeArgs(model = "haiku") {
|
|
184
298
|
return [
|
|
185
299
|
"-p",
|
|
186
300
|
"--input-format",
|
|
@@ -188,7 +302,7 @@ function buildProbeArgs() {
|
|
|
188
302
|
"--output-format",
|
|
189
303
|
"json",
|
|
190
304
|
"--model",
|
|
191
|
-
|
|
305
|
+
model,
|
|
192
306
|
"--tools",
|
|
193
307
|
"",
|
|
194
308
|
"--system-prompt",
|
|
@@ -215,7 +329,15 @@ function parseProbeOutput(stdout, stderr, exitCode) {
|
|
|
215
329
|
if (record.type !== "result") continue;
|
|
216
330
|
if (record.is_error === false) return { result: "ok" };
|
|
217
331
|
const text = typeof record.result === "string" ? record.result : "";
|
|
218
|
-
|
|
332
|
+
const denial = classifyProxyDenial(text);
|
|
333
|
+
if (denial) return {
|
|
334
|
+
result: "denied",
|
|
335
|
+
denial
|
|
336
|
+
};
|
|
337
|
+
if (isAuthFailureStatus(record.api_error_status) || isAuthFailureText(text)) return record.api_error_status === 403 ? {
|
|
338
|
+
result: "invalid",
|
|
339
|
+
forbidden: true
|
|
340
|
+
} : { result: "invalid" };
|
|
219
341
|
return {
|
|
220
342
|
result: "unknown",
|
|
221
343
|
reason: `probe failed (${typeof record.api_error_status === "number" ? `HTTP ${String(record.api_error_status)}` : "no auth error"})`
|
|
@@ -228,12 +350,14 @@ function parseProbeOutput(stdout, stderr, exitCode) {
|
|
|
228
350
|
};
|
|
229
351
|
}
|
|
230
352
|
/**
|
|
231
|
-
* One minimal real turn
|
|
232
|
-
*
|
|
353
|
+
* One minimal real turn on the given auth source: Anthropic with the agent's
|
|
354
|
+
* credential or the machine's own login, or the Alfe AI proxy (a Haiku ping
|
|
355
|
+
* billed from Alfe credits). Never throws.
|
|
233
356
|
*/
|
|
234
357
|
function runAuthProbe(opts) {
|
|
235
358
|
const spawnImpl = opts.spawnImpl ?? node_child_process.spawn;
|
|
236
|
-
const env = buildChildEnv(opts.baseEnv, { MAX_THINKING_TOKENS: "0" }, opts.
|
|
359
|
+
const env = buildChildEnv(opts.baseEnv, { MAX_THINKING_TOKENS: "0" }, opts.auth);
|
|
360
|
+
const model = opts.auth.mode === "ai_proxy" ? PROXY_MODEL_ENV.ANTHROPIC_DEFAULT_HAIKU_MODEL : void 0;
|
|
237
361
|
return new Promise((resolve) => {
|
|
238
362
|
if (opts.signal?.aborted) {
|
|
239
363
|
resolve({
|
|
@@ -266,7 +390,7 @@ function runAuthProbe(opts) {
|
|
|
266
390
|
}
|
|
267
391
|
let child;
|
|
268
392
|
try {
|
|
269
|
-
child = spawnImpl(opts.claudeBin ?? "claude", buildProbeArgs(), {
|
|
393
|
+
child = spawnImpl(opts.claudeBin ?? "claude", buildProbeArgs(model), {
|
|
270
394
|
cwd: opts.cwd,
|
|
271
395
|
env,
|
|
272
396
|
stdio: [
|
|
@@ -338,6 +462,7 @@ var CredentialStatusReporter = class {
|
|
|
338
462
|
async deliver(report) {
|
|
339
463
|
const key = JSON.stringify([
|
|
340
464
|
report.status,
|
|
465
|
+
report.source ?? null,
|
|
341
466
|
report.revision ?? null,
|
|
342
467
|
report.detail ?? null
|
|
343
468
|
]);
|
|
@@ -353,6 +478,64 @@ var CredentialStatusReporter = class {
|
|
|
353
478
|
}
|
|
354
479
|
};
|
|
355
480
|
//#endregion
|
|
481
|
+
//#region src/mcp-allow-rules.ts
|
|
482
|
+
/**
|
|
483
|
+
* Allow rules for the MCP servers a bridged turn loads.
|
|
484
|
+
*
|
|
485
|
+
* Turns run under `--permission-mode dontAsk`, which DENIES any tool not in
|
|
486
|
+
* `--allowedTools`. MCP tools are named `mcp__<server>__<tool>`, so without a
|
|
487
|
+
* rule per server every MCP call (alfe-platform memory/voice/send/secrets and
|
|
488
|
+
* every integration server) is refused (prod, 2026-10-17). Turns pass
|
|
489
|
+
* `--strict-mcp-config --mcp-config <file>`, so the servers in that file are
|
|
490
|
+
* exactly the Alfe-installed set; each gets a server-wide `mcp__<server>` rule.
|
|
491
|
+
* No blanket `mcp__*` wildcard: the allow-list stays scoped to what Alfe wrote.
|
|
492
|
+
*
|
|
493
|
+
* The file is read on every spawn, so a newly installed integration server is
|
|
494
|
+
* allowed on the next turn without restarting the host.
|
|
495
|
+
*/
|
|
496
|
+
const CLAUDE_AI_PREFIX = "claude.ai ";
|
|
497
|
+
/**
|
|
498
|
+
* Mirrors how claude (2.1.x) builds the `<server>` segment of an MCP tool id:
|
|
499
|
+
* every character outside `[a-zA-Z0-9_-]` becomes `_` (hyphens are kept), and
|
|
500
|
+
* `claude.ai ` connector names also collapse and trim underscores. Permission
|
|
501
|
+
* rules match on this normalised name, so a rule must use it too.
|
|
502
|
+
*/
|
|
503
|
+
function normalizeMcpServerName(name) {
|
|
504
|
+
let normalized = name.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
505
|
+
if (name.startsWith(CLAUDE_AI_PREFIX)) normalized = normalized.replace(/_+/g, "_").replace(/^_|_$/g, "");
|
|
506
|
+
return normalized;
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Server-wide allow rules (`mcp__<server>`) for every server in the
|
|
510
|
+
* `--mcp-config` file. A missing or malformed file yields no rules and one
|
|
511
|
+
* warning; the turn still runs, it just cannot call MCP tools.
|
|
512
|
+
*/
|
|
513
|
+
function mcpServerAllowRules(configPath, log) {
|
|
514
|
+
if (!configPath) return [];
|
|
515
|
+
let parsed;
|
|
516
|
+
try {
|
|
517
|
+
parsed = JSON.parse((0, node_fs.readFileSync)(configPath, "utf8"));
|
|
518
|
+
} catch (err) {
|
|
519
|
+
log.warn(`[claude-host] could not read MCP config ${configPath}; MCP tools are not allowed this turn: ${err instanceof Error ? err.message : String(err)}`);
|
|
520
|
+
return [];
|
|
521
|
+
}
|
|
522
|
+
const servers = parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed.mcpServers : void 0;
|
|
523
|
+
if (servers === null || typeof servers !== "object" || Array.isArray(servers)) {
|
|
524
|
+
log.warn(`[claude-host] MCP config ${configPath} has no mcpServers map; MCP tools are not allowed this turn`);
|
|
525
|
+
return [];
|
|
526
|
+
}
|
|
527
|
+
const rules = /* @__PURE__ */ new Set();
|
|
528
|
+
for (const name of Object.keys(servers)) {
|
|
529
|
+
const normalized = normalizeMcpServerName(name);
|
|
530
|
+
if (!normalized || normalized.includes("__")) {
|
|
531
|
+
log.warn(`[claude-host] MCP server "${name}" cannot be expressed as a server-wide allow rule; its tools stay denied`);
|
|
532
|
+
continue;
|
|
533
|
+
}
|
|
534
|
+
rules.add(`mcp__${normalized}`);
|
|
535
|
+
}
|
|
536
|
+
return [...rules];
|
|
537
|
+
}
|
|
538
|
+
//#endregion
|
|
356
539
|
//#region src/stream-mapper.ts
|
|
357
540
|
/**
|
|
358
541
|
* Claude Code `--output-format stream-json` → Alfe chat-activity mapper.
|
|
@@ -600,7 +783,8 @@ function mapStreamEvent(state, raw, nowMs) {
|
|
|
600
783
|
finalText: isError ? "" : finalText,
|
|
601
784
|
...durationMs !== void 0 ? { durationMs } : {},
|
|
602
785
|
...errorMessage ? { errorMessage } : {},
|
|
603
|
-
...authFailed ? { authFailed: true } : {}
|
|
786
|
+
...authFailed ? { authFailed: true } : {},
|
|
787
|
+
...authFailed && evt.api_error_status === 403 ? { authForbidden: true } : {}
|
|
604
788
|
}];
|
|
605
789
|
}
|
|
606
790
|
return [];
|
|
@@ -811,7 +995,7 @@ var SessionDriver = class {
|
|
|
811
995
|
abortCurrent = null;
|
|
812
996
|
bin;
|
|
813
997
|
baseEnv;
|
|
814
|
-
|
|
998
|
+
auth;
|
|
815
999
|
managed;
|
|
816
1000
|
log;
|
|
817
1001
|
/** Injected spawn — tests replace this with a fake child. */
|
|
@@ -819,7 +1003,7 @@ var SessionDriver = class {
|
|
|
819
1003
|
constructor(opts) {
|
|
820
1004
|
this.bin = opts?.claudeBin ?? DEFAULT_CLAUDE_BIN;
|
|
821
1005
|
this.baseEnv = opts?.baseEnv ?? process.env;
|
|
822
|
-
this.
|
|
1006
|
+
this.auth = opts?.auth ?? { mode: "local_login" };
|
|
823
1007
|
this.managed = opts?.managed ?? false;
|
|
824
1008
|
this.log = opts?.logger ?? console;
|
|
825
1009
|
this.spawnImpl = opts?.__spawn ?? node_child_process.spawn;
|
|
@@ -844,7 +1028,7 @@ var SessionDriver = class {
|
|
|
844
1028
|
return this.runPerTurn(params);
|
|
845
1029
|
}
|
|
846
1030
|
buildArgs(params) {
|
|
847
|
-
const allowed =
|
|
1031
|
+
const allowed = [...params.allowedTools ?? (this.managed ? MANAGED_ALLOWED_TOOLS : DEFAULT_ALLOWED_TOOLS), ...mcpServerAllowRules(params.mcpConfigPath, this.log)].join(",");
|
|
848
1032
|
const permissionMode = params.permissionMode ?? "dontAsk";
|
|
849
1033
|
if (!CLAUDE_PERMISSION_MODES.includes(permissionMode)) throw new Error(`Unsupported Claude permission mode: ${permissionMode}`);
|
|
850
1034
|
const args = [
|
|
@@ -879,7 +1063,7 @@ var SessionDriver = class {
|
|
|
879
1063
|
let stderr = "";
|
|
880
1064
|
let rateLimited = false;
|
|
881
1065
|
const args = this.buildArgs(params);
|
|
882
|
-
const env = buildChildEnv(this.baseEnv, void 0, this.
|
|
1066
|
+
const env = buildChildEnv(this.baseEnv, void 0, this.auth);
|
|
883
1067
|
return new Promise((resolve) => {
|
|
884
1068
|
let child;
|
|
885
1069
|
try {
|
|
@@ -931,7 +1115,8 @@ var SessionDriver = class {
|
|
|
931
1115
|
finalText: finalIsError ? "" : endText,
|
|
932
1116
|
...finalErrMsg ? { errorMessage: finalErrMsg } : {},
|
|
933
1117
|
aborted,
|
|
934
|
-
...authFailed ? { authFailed: true } : {}
|
|
1118
|
+
...authFailed ? { authFailed: true } : {},
|
|
1119
|
+
...authFailed && end?.type === "turn-end" && end.authForbidden ? { authForbidden: true } : {}
|
|
935
1120
|
});
|
|
936
1121
|
};
|
|
937
1122
|
const requestTermination = (reason) => {
|
|
@@ -1059,6 +1244,27 @@ async function writeTextAtomically(path, contents) {
|
|
|
1059
1244
|
*
|
|
1060
1245
|
* where `sessionId = conversationId ?? sessionKey`.
|
|
1061
1246
|
*/
|
|
1247
|
+
/**
|
|
1248
|
+
* The channel every chat session is stored on. Every chat client lists with
|
|
1249
|
+
* `sessions.list { channel: 'alfe' }` and openclaw-chat stores `'alfe'`, so any
|
|
1250
|
+
* other value hides the conversation from the rail.
|
|
1251
|
+
*/
|
|
1252
|
+
const CHAT_SESSION_CHANNEL = "alfe";
|
|
1253
|
+
/**
|
|
1254
|
+
* Values hosts up to 0.1.16 stored as `channel`: the chat surface label
|
|
1255
|
+
* (`params.clientType ?? 'chat'`). Read as `CHAT_SESSION_CHANNEL`; the file is
|
|
1256
|
+
* rewritten on its next save.
|
|
1257
|
+
*/
|
|
1258
|
+
const LEGACY_SURFACE_CHANNELS = new Set([
|
|
1259
|
+
"chat",
|
|
1260
|
+
"dashboard",
|
|
1261
|
+
"mobile",
|
|
1262
|
+
"desktop",
|
|
1263
|
+
"widget",
|
|
1264
|
+
"unknown"
|
|
1265
|
+
]);
|
|
1266
|
+
/** Legacy values that never named a real surface, so carry no `clientType`. */
|
|
1267
|
+
const LEGACY_PLACEHOLDER_CHANNELS = new Set(["chat", "unknown"]);
|
|
1062
1268
|
function sessionsDir() {
|
|
1063
1269
|
return process.env.ALFE_SESSIONS_DIR ?? (0, node_path.join)((0, node_os.homedir)(), ".alfe", "sessions", "chat");
|
|
1064
1270
|
}
|
|
@@ -1077,6 +1283,7 @@ function sessionPath(sessionId) {
|
|
|
1077
1283
|
function asSessionData(value, expectedSessionId) {
|
|
1078
1284
|
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
1079
1285
|
const record = value;
|
|
1286
|
+
normaliseLegacyChannel(record);
|
|
1080
1287
|
if (typeof record.sessionId !== "string" || expectedSessionId !== void 0 && record.sessionId !== expectedSessionId || typeof record.agentId !== "string" || typeof record.channel !== "string" || typeof record.createdAt !== "string" || typeof record.updatedAt !== "string" || !Array.isArray(record.messages)) return null;
|
|
1081
1288
|
if (!record.messages.every((message) => {
|
|
1082
1289
|
if (!message || typeof message !== "object" || Array.isArray(message)) return false;
|
|
@@ -1087,6 +1294,13 @@ function asSessionData(value, expectedSessionId) {
|
|
|
1087
1294
|
if (record.memory !== void 0 && !isMemoryCursor(record.memory)) delete record.memory;
|
|
1088
1295
|
return value;
|
|
1089
1296
|
}
|
|
1297
|
+
/** Map a pre-0.1.17 surface-label channel (or none) onto `CHAT_SESSION_CHANNEL`. */
|
|
1298
|
+
function normaliseLegacyChannel(record) {
|
|
1299
|
+
const channel = record.channel;
|
|
1300
|
+
if (channel !== void 0 && !(typeof channel === "string" && LEGACY_SURFACE_CHANNELS.has(channel))) return;
|
|
1301
|
+
if (typeof channel === "string" && !LEGACY_PLACEHOLDER_CHANNELS.has(channel) && record.clientType === void 0) record.clientType = channel;
|
|
1302
|
+
record.channel = CHAT_SESSION_CHANNEL;
|
|
1303
|
+
}
|
|
1090
1304
|
function isMemoryCursor(value) {
|
|
1091
1305
|
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
1092
1306
|
const cursor = value;
|
|
@@ -1141,12 +1355,13 @@ async function saveSession(session) {
|
|
|
1141
1355
|
session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1142
1356
|
await writeTextAtomically(sessionPath(session.sessionId), JSON.stringify(session, null, 2));
|
|
1143
1357
|
}
|
|
1144
|
-
async function createSession(sessionId, agentId, channel, tenantId, userId) {
|
|
1358
|
+
async function createSession(sessionId, agentId, channel, tenantId, userId, clientType) {
|
|
1145
1359
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1146
1360
|
const session = {
|
|
1147
1361
|
sessionId,
|
|
1148
1362
|
agentId,
|
|
1149
1363
|
channel,
|
|
1364
|
+
...clientType ? { clientType } : {},
|
|
1150
1365
|
tenantId,
|
|
1151
1366
|
userId,
|
|
1152
1367
|
createdAt: now,
|
|
@@ -1180,17 +1395,35 @@ async function withSessionLock(sessionId, fn) {
|
|
|
1180
1395
|
* memory-ingest index, or -1 when the session does not exist.
|
|
1181
1396
|
*/
|
|
1182
1397
|
async function addMessage(sessionId, role, content, senderId, senderName, components, attachments) {
|
|
1398
|
+
return appendMessage(sessionId, {
|
|
1399
|
+
role,
|
|
1400
|
+
content,
|
|
1401
|
+
...senderId ? { senderId } : {},
|
|
1402
|
+
...senderName ? { senderName } : {},
|
|
1403
|
+
...components?.length ? { components } : {},
|
|
1404
|
+
...attachments?.length ? { attachments } : {}
|
|
1405
|
+
});
|
|
1406
|
+
}
|
|
1407
|
+
/**
|
|
1408
|
+
* Store the failure message a turn settled with as the assistant's reply, so
|
|
1409
|
+
* the history shows what the user saw. Returns its index, or -1 when the
|
|
1410
|
+
* session does not exist.
|
|
1411
|
+
*/
|
|
1412
|
+
async function addErrorMessage(sessionId, content) {
|
|
1413
|
+
return appendMessage(sessionId, {
|
|
1414
|
+
role: "assistant",
|
|
1415
|
+
content,
|
|
1416
|
+
kind: "error"
|
|
1417
|
+
});
|
|
1418
|
+
}
|
|
1419
|
+
async function appendMessage(sessionId, message) {
|
|
1183
1420
|
return withSessionLock(sessionId, async () => {
|
|
1184
1421
|
const session = await getSession(sessionId);
|
|
1185
1422
|
if (!session) return -1;
|
|
1423
|
+
const last = session.messages.at(-1)?.timestamp ?? 0;
|
|
1186
1424
|
session.messages.push({
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
timestamp: Date.now(),
|
|
1190
|
-
...senderId ? { senderId } : {},
|
|
1191
|
-
...senderName ? { senderName } : {},
|
|
1192
|
-
...components?.length ? { components } : {},
|
|
1193
|
-
...attachments?.length ? { attachments } : {}
|
|
1425
|
+
...message,
|
|
1426
|
+
timestamp: Math.max(Date.now(), last + 1)
|
|
1194
1427
|
});
|
|
1195
1428
|
await saveSession(session);
|
|
1196
1429
|
return session.messages.length - 1;
|
|
@@ -1278,8 +1511,8 @@ async function ensureMemoryCursor(sessionId) {
|
|
|
1278
1511
|
/**
|
|
1279
1512
|
* The messages not yet accepted by the memory service, oldest first, bounded
|
|
1280
1513
|
* by count and serialised bytes (always at least one message when any is
|
|
1281
|
-
* pending).
|
|
1282
|
-
* content) but still covered: `throughIndex` is the last index this batch
|
|
1514
|
+
* pending). Error replies (history only) and whitespace-only messages (the
|
|
1515
|
+
* service rejects empty content) are skipped but still covered: `throughIndex` is the last index this batch
|
|
1283
1516
|
* accounts for, sent or skipped. Leftovers go out with the next turn's batch.
|
|
1284
1517
|
*/
|
|
1285
1518
|
async function pendingMemoryBatch(sessionId, opts = {}) {
|
|
@@ -1297,7 +1530,7 @@ async function pendingMemoryBatch(sessionId, opts = {}) {
|
|
|
1297
1530
|
let bytes = 0;
|
|
1298
1531
|
for (let index = session.memory.lastIngestedIndex + 1; index < session.messages.length && messages.length < maxMessages; index += 1) {
|
|
1299
1532
|
const message = session.messages[index];
|
|
1300
|
-
if (message.content.trim() === "") {
|
|
1533
|
+
if (message.kind === "error" || message.content.trim() === "") {
|
|
1301
1534
|
throughIndex = index;
|
|
1302
1535
|
continue;
|
|
1303
1536
|
}
|
|
@@ -1585,14 +1818,15 @@ const RECALL_TIMEOUT_MS = 5e3;
|
|
|
1585
1818
|
//#endregion
|
|
1586
1819
|
//#region src/host.ts
|
|
1587
1820
|
/**
|
|
1588
|
-
* ClaudeCodeHost — the persistent chat bridge for a
|
|
1589
|
-
*
|
|
1821
|
+
* ClaudeCodeHost — the persistent chat bridge for a Claude Code agent
|
|
1822
|
+
* (self-hosted or managed).
|
|
1590
1823
|
*
|
|
1591
1824
|
* It holds ONE `ChatServiceClient` (reused verbatim from `@alfe.ai/chat`, with
|
|
1592
1825
|
* NO custom reconnect — the client already backs off with jitter and guards
|
|
1593
1826
|
* against orphan sockets), registers the inbound RPC handlers the chat service
|
|
1594
|
-
* calls, and drives the `SessionDriver` to run `claude -p` turns on
|
|
1595
|
-
*
|
|
1827
|
+
* calls, and drives the `SessionDriver` to run `claude -p` turns on one auth
|
|
1828
|
+
* source: the agent's stored credential, a self-hosted machine's own login, or
|
|
1829
|
+
* the Alfe AI proxy (see DEVELOPING.md "Auth source resolution").
|
|
1596
1830
|
*
|
|
1597
1831
|
* Every wire shape here is byte-for-byte the shape the `@alfe.ai/openclaw-chat`
|
|
1598
1832
|
* plugin emits, so the chat service / adapter / UI treat a Claude Code turn
|
|
@@ -1630,9 +1864,28 @@ const PERSONA_POINTER_SYSTEM_PROMPT = [
|
|
|
1630
1864
|
"Fully embody that persona in how you speak and act; respond AS that agent, not as a generic coding assistant."
|
|
1631
1865
|
].join("\n");
|
|
1632
1866
|
const CREDENTIAL_INVALID_MESSAGE = "This agent's Claude credential is invalid or expired. Update it in agent settings.";
|
|
1633
|
-
const
|
|
1634
|
-
const
|
|
1867
|
+
const OUT_OF_CREDITS_MESSAGE = "This agent is out of Alfe credits. Add credits in Alfe Billing, or add a Claude credential in agent settings, then resend your message.";
|
|
1868
|
+
const USAGE_LIMIT_MESSAGE = "This agent hit an Alfe usage limit. Raise the limit in Alfe or wait for it to reset, then resend your message.";
|
|
1869
|
+
const LOCAL_LOGIN_SWITCHED_MESSAGE = "Your machine's Claude login stopped working, so this agent switched to Alfe credits. Please resend your message.";
|
|
1870
|
+
const LOCAL_LOGIN_REFUSED_MESSAGE = "Claude refused this machine's claude login (403). Check the account or its organisation policy, or add a Claude credential in agent settings.";
|
|
1871
|
+
const MODEL_UNAVAILABLE_MESSAGE = "This agent's model isn't available on this workspace. Choose another Claude model in agent settings, then resend your message.";
|
|
1872
|
+
const PROXY_AUTH_FAILED_MESSAGE = "Alfe AI rejected this agent's key. Reconnect the agent (run `alfe setup` on its machine), or add a Claude credential in agent settings.";
|
|
1635
1873
|
const CREDENTIAL_UNAVAILABLE_MESSAGE = "This agent's Claude credential couldn't be loaded yet. Try again in a moment.";
|
|
1874
|
+
/** Fixed status details (never `claude` output). */
|
|
1875
|
+
const SOURCE_OK_DETAIL = {
|
|
1876
|
+
local_login: "Using this machine's claude login",
|
|
1877
|
+
ai_proxy: "Using Alfe AI credits"
|
|
1878
|
+
};
|
|
1879
|
+
const DENIAL_DETAIL = {
|
|
1880
|
+
credits: "Using Alfe AI credits. This workspace is out of credits.",
|
|
1881
|
+
usage_limit: "Using Alfe AI credits. An Alfe usage limit has been reached.",
|
|
1882
|
+
model_unavailable: "Using Alfe AI credits. The agent's model is not available on this workspace."
|
|
1883
|
+
};
|
|
1884
|
+
const DENIAL_MESSAGE = {
|
|
1885
|
+
credits: OUT_OF_CREDITS_MESSAGE,
|
|
1886
|
+
usage_limit: USAGE_LIMIT_MESSAGE,
|
|
1887
|
+
model_unavailable: MODEL_UNAVAILABLE_MESSAGE
|
|
1888
|
+
};
|
|
1636
1889
|
const MAX_AGENT_MESSAGE_BYTES = 256 * 1024;
|
|
1637
1890
|
const MAX_SESSION_KEY_CHARS = 768;
|
|
1638
1891
|
const MAX_IDENTIFIER_CHARS = 512;
|
|
@@ -1856,8 +2109,16 @@ var ClaudeCodeHost = class {
|
|
|
1856
2109
|
/** Turns waiting for the credential, before queue admission (chat.abort). */
|
|
1857
2110
|
waitingTurns = /* @__PURE__ */ new Map();
|
|
1858
2111
|
loaderRestartMs;
|
|
2112
|
+
/** How often a fallen-back self-hosted agent re-checks its local login. */
|
|
2113
|
+
localLoginRecheckMs;
|
|
2114
|
+
localLoginRecheck = null;
|
|
1859
2115
|
/** In memory only. Never logged, never persisted. */
|
|
1860
2116
|
credential = null;
|
|
2117
|
+
/**
|
|
2118
|
+
* The auth every new turn runs on, resolved once the credential fetch
|
|
2119
|
+
* answers (see `resolveAuthSource`). Null before that.
|
|
2120
|
+
*/
|
|
2121
|
+
authSource = null;
|
|
1861
2122
|
credentialAbort = new AbortController();
|
|
1862
2123
|
reporter;
|
|
1863
2124
|
probe;
|
|
@@ -1867,18 +2128,18 @@ var ClaudeCodeHost = class {
|
|
|
1867
2128
|
this.cfg = cfg;
|
|
1868
2129
|
this.log = cfg.logger ?? console;
|
|
1869
2130
|
this.claudeSessionsPath = cfg.claudeSessionsPath ?? (0, node_path.join)((0, node_os.homedir)(), ".alfe", "claude-code", "sessions.json");
|
|
1870
|
-
this.makeDriver = deps?.makeDriver ?? ((
|
|
2131
|
+
this.makeDriver = deps?.makeDriver ?? ((auth) => new SessionDriver({
|
|
1871
2132
|
logger: this.log,
|
|
1872
2133
|
baseEnv: process.env,
|
|
1873
|
-
|
|
2134
|
+
auth,
|
|
1874
2135
|
managed: cfg.managed === true
|
|
1875
2136
|
}));
|
|
1876
2137
|
this.api = deps?.api ?? new _alfe_ai_agent_api_client.AgentApiClient({
|
|
1877
2138
|
apiKey: cfg.apiKey,
|
|
1878
2139
|
apiUrl: cfg.apiUrl
|
|
1879
2140
|
});
|
|
1880
|
-
this.probe = deps?.probe ?? ((
|
|
1881
|
-
|
|
2141
|
+
this.probe = deps?.probe ?? ((auth, signal) => runAuthProbe({
|
|
2142
|
+
auth,
|
|
1882
2143
|
baseEnv: process.env,
|
|
1883
2144
|
cwd: (0, node_os.tmpdir)(),
|
|
1884
2145
|
signal
|
|
@@ -1886,6 +2147,7 @@ var ClaudeCodeHost = class {
|
|
|
1886
2147
|
this.credentialWaitMs = deps?.credentialWaitMs ?? 15e3;
|
|
1887
2148
|
this.loaderRestartMs = deps?.loaderRestartMs ?? 5e3;
|
|
1888
2149
|
this.credentialRetry = deps?.credentialRetry ?? {};
|
|
2150
|
+
this.localLoginRecheckMs = deps?.localLoginRecheckMs ?? 9e5;
|
|
1889
2151
|
this.reporter = new CredentialStatusReporter((report) => this.api.reportClaudeCredentialStatus(report), { log: this.log });
|
|
1890
2152
|
this.chat = deps?.chat ?? new _alfe_ai_chat.ChatServiceClient({
|
|
1891
2153
|
wsUrl: cfg.wsUrl,
|
|
@@ -1939,49 +2201,117 @@ var ClaudeCodeHost = class {
|
|
|
1939
2201
|
});
|
|
1940
2202
|
if (!loaded) return;
|
|
1941
2203
|
this.credential = loaded.credential;
|
|
2204
|
+
this.authSource = loaded.credential ? "credential" : this.cfg.managed ? "ai_proxy" : "local_login";
|
|
1942
2205
|
this.credentialLoaded = true;
|
|
1943
2206
|
this.resolveCredentialReady();
|
|
1944
2207
|
await this.checkCredential();
|
|
1945
2208
|
}
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
2209
|
+
/**
|
|
2210
|
+
* A method, not a property read: `stopping` flips across awaits, which
|
|
2211
|
+
* control-flow narrowing cannot see.
|
|
2212
|
+
*/
|
|
2213
|
+
isStopping() {
|
|
2214
|
+
return this.stopping;
|
|
2215
|
+
}
|
|
2216
|
+
/** Same reason as `isStopping`: `authSource` can change across an await. */
|
|
2217
|
+
currentAuthSource() {
|
|
2218
|
+
return this.authSource;
|
|
2219
|
+
}
|
|
2220
|
+
/** The auth a new turn runs on. Local login until the fetch has answered. */
|
|
2221
|
+
childAuth() {
|
|
2222
|
+
if (this.authSource === "credential" && this.credential) return {
|
|
2223
|
+
mode: "credential",
|
|
2224
|
+
credential: {
|
|
2225
|
+
kind: this.credential.kind,
|
|
2226
|
+
value: this.credential.value
|
|
2227
|
+
}
|
|
2228
|
+
};
|
|
2229
|
+
if (this.authSource === "ai_proxy") return {
|
|
2230
|
+
mode: "ai_proxy",
|
|
2231
|
+
baseUrl: this.cfg.localAiProxyUrl ?? "http://127.0.0.1:18193",
|
|
2232
|
+
model: this.cfg.model ?? "claude-haiku-4-5"
|
|
2233
|
+
};
|
|
2234
|
+
return { mode: "local_login" };
|
|
1951
2235
|
}
|
|
1952
|
-
/**
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
2236
|
+
/**
|
|
2237
|
+
* A self-hosted agent with no credential whose local login was refused (401
|
|
2238
|
+
* or an explicit auth message, never a 403): run new turns through the Alfe
|
|
2239
|
+
* AI proxy, and re-check the login periodically so a re-login moves the agent
|
|
2240
|
+
* back off Alfe credits. Never for a stored credential (the user chose it; a
|
|
2241
|
+
* refusal there needs their fix) and never on managed.
|
|
2242
|
+
*/
|
|
2243
|
+
fallBackToProxy(why) {
|
|
2244
|
+
if (this.authSource !== "local_login") return;
|
|
2245
|
+
this.log.warn(`[claude-host] the claude login on this machine was refused (${why}); using the Alfe AI proxy`);
|
|
2246
|
+
this.authSource = "ai_proxy";
|
|
2247
|
+
this.scheduleLocalLoginRecheck();
|
|
2248
|
+
}
|
|
2249
|
+
scheduleLocalLoginRecheck() {
|
|
2250
|
+
if (this.localLoginRecheck || this.stopping) return;
|
|
2251
|
+
this.localLoginRecheck = setTimeout(() => {
|
|
2252
|
+
this.localLoginRecheck = null;
|
|
2253
|
+
this.recheckLocalLogin();
|
|
2254
|
+
}, this.localLoginRecheckMs);
|
|
2255
|
+
this.localLoginRecheck.unref();
|
|
2256
|
+
}
|
|
2257
|
+
/** While fallen back: one local-login probe; back to the login when it works. */
|
|
2258
|
+
async recheckLocalLogin() {
|
|
2259
|
+
if (this.isStopping() || this.authSource !== "ai_proxy" || this.credential || this.cfg.managed) return;
|
|
2260
|
+
let outcome;
|
|
2261
|
+
try {
|
|
2262
|
+
outcome = await this.probe({ mode: "local_login" }, this.credentialAbort.signal);
|
|
2263
|
+
} catch (err) {
|
|
2264
|
+
outcome = {
|
|
2265
|
+
result: "unknown",
|
|
2266
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
2267
|
+
};
|
|
2268
|
+
}
|
|
2269
|
+
if (this.isStopping() || this.currentAuthSource() !== "ai_proxy") return;
|
|
2270
|
+
if (outcome.result === "ok") {
|
|
2271
|
+
this.log.info("[claude-host] the claude login on this machine works again; leaving the Alfe AI proxy");
|
|
2272
|
+
this.authSource = "local_login";
|
|
2273
|
+
await this.reportStatus("ok");
|
|
1959
2274
|
return;
|
|
1960
2275
|
}
|
|
1961
|
-
|
|
2276
|
+
this.scheduleLocalLoginRecheck();
|
|
2277
|
+
}
|
|
2278
|
+
/**
|
|
2279
|
+
* Start-up check: one minimal real call on the resolved source, reported to
|
|
2280
|
+
* the cloud. A refused local login falls back to the proxy and checks that.
|
|
2281
|
+
*/
|
|
2282
|
+
async checkCredential() {
|
|
2283
|
+
let outcome = await this.probe(this.childAuth(), this.credentialAbort.signal);
|
|
1962
2284
|
if (this.stopping) return;
|
|
1963
|
-
if (outcome.result === "
|
|
1964
|
-
this.
|
|
1965
|
-
|
|
2285
|
+
if (outcome.result === "invalid" && !outcome.forbidden && this.authSource === "local_login") {
|
|
2286
|
+
this.fallBackToProxy("start-up check");
|
|
2287
|
+
outcome = await this.probe(this.childAuth(), this.credentialAbort.signal);
|
|
2288
|
+
if (this.isStopping()) return;
|
|
1966
2289
|
}
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
2290
|
+
switch (outcome.result) {
|
|
2291
|
+
case "unknown":
|
|
2292
|
+
this.log.warn(`[claude-host] Claude auth check inconclusive (${outcome.reason}); the next turn will tell`);
|
|
2293
|
+
return;
|
|
2294
|
+
case "denied":
|
|
2295
|
+
if (outcome.denial === "model_unavailable") {
|
|
2296
|
+
this.log.warn("[claude-host] the probe model is unavailable through the Alfe AI proxy; the next turn will tell");
|
|
2297
|
+
return;
|
|
2298
|
+
}
|
|
2299
|
+
await this.reportStatus("ok", DENIAL_DETAIL[outcome.denial]);
|
|
2300
|
+
return;
|
|
2301
|
+
default: await this.reportStatus(outcome.result);
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
/** Report the current auth source as working or refused. */
|
|
2305
|
+
async reportStatus(status, detail) {
|
|
2306
|
+
const source = this.authSource;
|
|
2307
|
+
if (!this.credentialLoaded || !source) return;
|
|
2308
|
+
const revision = source === "credential" ? this.credential?.revision : void 0;
|
|
2309
|
+
const fixedDetail = detail ?? (status === "ok" ? source === "credential" ? void 0 : SOURCE_OK_DETAIL[source] : source === "credential" ? "Anthropic rejected the stored credential" : source === "ai_proxy" ? "The Alfe AI proxy rejected this agent's key" : "The claude login on this machine is not signed in");
|
|
2310
|
+
await this.reporter.report({
|
|
2311
|
+
status,
|
|
2312
|
+
source,
|
|
2313
|
+
...fixedDetail ? { detail: fixedDetail } : {},
|
|
2314
|
+
...revision ? { revision } : {}
|
|
1985
2315
|
});
|
|
1986
2316
|
}
|
|
1987
2317
|
/**
|
|
@@ -2025,7 +2355,6 @@ var ClaudeCodeHost = class {
|
|
|
2025
2355
|
credentialGateMessage() {
|
|
2026
2356
|
if (!this.credentialReady) return null;
|
|
2027
2357
|
if (!this.credentialLoaded) return CREDENTIAL_UNAVAILABLE_MESSAGE;
|
|
2028
|
-
if (!this.credential && this.cfg.managed) return CREDENTIAL_MISSING_MESSAGE;
|
|
2029
2358
|
return null;
|
|
2030
2359
|
}
|
|
2031
2360
|
/** Stop admission, abort owned children, and give active turns time to settle. */
|
|
@@ -2033,6 +2362,10 @@ var ClaudeCodeHost = class {
|
|
|
2033
2362
|
if (this.stopPromise) return this.stopPromise;
|
|
2034
2363
|
this.stopping = true;
|
|
2035
2364
|
this.credentialAbort.abort();
|
|
2365
|
+
if (this.localLoginRecheck) {
|
|
2366
|
+
clearTimeout(this.localLoginRecheck);
|
|
2367
|
+
this.localLoginRecheck = null;
|
|
2368
|
+
}
|
|
2036
2369
|
for (const waiting of this.waitingTurns.values()) for (const turn of waiting) turn.cancellation.abort();
|
|
2037
2370
|
this.queues.stop();
|
|
2038
2371
|
for (const { driver, cancellation } of this.runningTurns.values()) {
|
|
@@ -2182,10 +2515,7 @@ var ClaudeCodeHost = class {
|
|
|
2182
2515
|
await addMessage(bareSessionId, "user", params.message, params.identityId ?? params.userId, params.displayName);
|
|
2183
2516
|
const credentialProblem = this.credentialGateMessage();
|
|
2184
2517
|
if (credentialProblem) {
|
|
2185
|
-
complete
|
|
2186
|
-
ok: false,
|
|
2187
|
-
payload: { message: credentialProblem }
|
|
2188
|
-
});
|
|
2518
|
+
await this.failTurn(bareSessionId, complete, credentialProblem);
|
|
2189
2519
|
return;
|
|
2190
2520
|
}
|
|
2191
2521
|
let memoryContext = "";
|
|
@@ -2200,26 +2530,36 @@ var ClaudeCodeHost = class {
|
|
|
2200
2530
|
const mcpConfigPath = this.cfg.mcpConfigPath && (0, node_fs.existsSync)(this.cfg.mcpConfigPath) ? this.cfg.mcpConfigPath : void 0;
|
|
2201
2531
|
const appendSystemPrompt = this.buildSystemPrompt(memoryContext, isVoice);
|
|
2202
2532
|
const claudeSessionId = this.claudeSessionIds.get(bareSessionId) ?? null;
|
|
2203
|
-
const driver = this.makeDriver(this.childCredential());
|
|
2204
2533
|
const cancellation = new AbortController();
|
|
2205
|
-
this.
|
|
2206
|
-
|
|
2207
|
-
driver,
|
|
2208
|
-
cancellation
|
|
2209
|
-
});
|
|
2534
|
+
let auth = this.childAuth();
|
|
2535
|
+
let switchedWithoutRerun = false;
|
|
2210
2536
|
let result;
|
|
2211
2537
|
try {
|
|
2212
|
-
result = await this.
|
|
2538
|
+
result = await this.runTurnOn(auth, sessionKey, runId, cancellation, bareSessionId, {
|
|
2213
2539
|
prompt: params.message,
|
|
2214
2540
|
claudeSessionId,
|
|
2215
2541
|
cwd: this.cfg.workspace,
|
|
2216
2542
|
...mcpConfigPath ? { mcpConfigPath } : {},
|
|
2217
2543
|
...appendSystemPrompt ? { appendSystemPrompt } : {},
|
|
2218
|
-
...this.cfg.model ? { model: this.cfg.model } : {},
|
|
2219
2544
|
onEmission: (emission) => {
|
|
2220
2545
|
this.forwardEmission(runId, sessionKey, emission);
|
|
2221
2546
|
}
|
|
2222
2547
|
}, isVoice);
|
|
2548
|
+
if (auth.mode === "local_login" && result.isError && result.authFailed && !result.authForbidden && !result.aborted && !cancellation.signal.aborted && !this.isStopping()) {
|
|
2549
|
+
this.fallBackToProxy("turn refused");
|
|
2550
|
+
if (producedVisibleOutput(result.emissions)) switchedWithoutRerun = true;
|
|
2551
|
+
else auth = this.childAuth();
|
|
2552
|
+
if (!switchedWithoutRerun && auth.mode === "ai_proxy") result = await this.runTurnOn(auth, sessionKey, runId, cancellation, bareSessionId, {
|
|
2553
|
+
prompt: params.message,
|
|
2554
|
+
claudeSessionId,
|
|
2555
|
+
cwd: this.cfg.workspace,
|
|
2556
|
+
...mcpConfigPath ? { mcpConfigPath } : {},
|
|
2557
|
+
...appendSystemPrompt ? { appendSystemPrompt } : {},
|
|
2558
|
+
onEmission: (emission) => {
|
|
2559
|
+
this.forwardEmission(runId, sessionKey, emission);
|
|
2560
|
+
}
|
|
2561
|
+
}, isVoice);
|
|
2562
|
+
}
|
|
2223
2563
|
} finally {
|
|
2224
2564
|
this.runningTurns.delete(sessionKey);
|
|
2225
2565
|
}
|
|
@@ -2237,21 +2577,30 @@ var ClaudeCodeHost = class {
|
|
|
2237
2577
|
return;
|
|
2238
2578
|
}
|
|
2239
2579
|
if (result.isError) {
|
|
2580
|
+
const denial = auth.mode === "ai_proxy" ? classifyProxyDenial(result.errorMessage ?? "") : null;
|
|
2581
|
+
if (denial) {
|
|
2582
|
+
await this.failTurn(bareSessionId, complete, DENIAL_MESSAGE[denial]);
|
|
2583
|
+
await this.reportStatus("ok", DENIAL_DETAIL[denial]);
|
|
2584
|
+
return;
|
|
2585
|
+
}
|
|
2586
|
+
if (switchedWithoutRerun) {
|
|
2587
|
+
await this.failTurn(bareSessionId, complete, LOCAL_LOGIN_SWITCHED_MESSAGE);
|
|
2588
|
+
return;
|
|
2589
|
+
}
|
|
2590
|
+
if (result.authFailed && auth.mode === "local_login") {
|
|
2591
|
+
await this.failTurn(bareSessionId, complete, result.authForbidden ? LOCAL_LOGIN_REFUSED_MESSAGE : CREDENTIAL_INVALID_MESSAGE);
|
|
2592
|
+
await this.reportStatus("invalid");
|
|
2593
|
+
return;
|
|
2594
|
+
}
|
|
2240
2595
|
if (result.authFailed) {
|
|
2241
|
-
complete
|
|
2242
|
-
|
|
2243
|
-
payload: { message: this.credential ? CREDENTIAL_INVALID_MESSAGE : LOCAL_LOGIN_FAILED_MESSAGE }
|
|
2244
|
-
});
|
|
2245
|
-
await this.reportAuth(false);
|
|
2596
|
+
await this.failTurn(bareSessionId, complete, auth.mode === "ai_proxy" ? PROXY_AUTH_FAILED_MESSAGE : CREDENTIAL_INVALID_MESSAGE);
|
|
2597
|
+
await this.reportStatus("invalid");
|
|
2246
2598
|
return;
|
|
2247
2599
|
}
|
|
2248
|
-
complete
|
|
2249
|
-
ok: false,
|
|
2250
|
-
payload: { message: result.errorMessage ?? "Claude Code turn failed" }
|
|
2251
|
-
});
|
|
2600
|
+
await this.failTurn(bareSessionId, complete, result.errorMessage ?? "Claude Code turn failed");
|
|
2252
2601
|
return;
|
|
2253
2602
|
}
|
|
2254
|
-
this.
|
|
2603
|
+
this.reportStatus("ok");
|
|
2255
2604
|
if (result.finalText) await addMessage(bareSessionId, "assistant", result.finalText);
|
|
2256
2605
|
const notification = result.finalText ? {
|
|
2257
2606
|
conversationId: bareSessionId,
|
|
@@ -2267,6 +2616,23 @@ var ClaudeCodeHost = class {
|
|
|
2267
2616
|
await this.ingestPendingMemory(bareSessionId, params);
|
|
2268
2617
|
}
|
|
2269
2618
|
/**
|
|
2619
|
+
* Settle a failed turn (C3) after storing its message as the assistant's
|
|
2620
|
+
* reply, so the history shows what the user saw. Stored first: a client that
|
|
2621
|
+
* reloads on the failure must find it. Best-effort: a store failure never
|
|
2622
|
+
* changes the outcome.
|
|
2623
|
+
*/
|
|
2624
|
+
async failTurn(bareSessionId, complete, message) {
|
|
2625
|
+
try {
|
|
2626
|
+
await addErrorMessage(bareSessionId, message);
|
|
2627
|
+
} catch (err) {
|
|
2628
|
+
this.log.warn(`[claude-host] failed to store a turn error in history: ${err instanceof Error ? err.message : String(err)}`);
|
|
2629
|
+
}
|
|
2630
|
+
complete({
|
|
2631
|
+
ok: false,
|
|
2632
|
+
payload: { message }
|
|
2633
|
+
});
|
|
2634
|
+
}
|
|
2635
|
+
/**
|
|
2270
2636
|
* Send every message the memory service has not accepted yet. Indices are
|
|
2271
2637
|
* the messages' positions in the session file, so they advance across turns
|
|
2272
2638
|
* and survive host restarts; the per-conversation `ingestEpoch` tells the
|
|
@@ -2324,6 +2690,24 @@ var ClaudeCodeHost = class {
|
|
|
2324
2690
|
* Run a turn; on a `--resume` failure (stale/missing claude session id) start
|
|
2325
2691
|
* fresh and prepend the last-N stored messages so the model keeps context.
|
|
2326
2692
|
*/
|
|
2693
|
+
/**
|
|
2694
|
+
* Run one turn on `auth` with its own driver, registered as the session's
|
|
2695
|
+
* running turn so `chat.abort` reaches it. Proxy turns always carry a
|
|
2696
|
+
* registry model.
|
|
2697
|
+
*/
|
|
2698
|
+
runTurnOn(auth, sessionKey, runId, cancellation, bareSessionId, baseParams, isVoice) {
|
|
2699
|
+
const driver = this.makeDriver(auth);
|
|
2700
|
+
this.runningTurns.set(sessionKey, {
|
|
2701
|
+
runId,
|
|
2702
|
+
driver,
|
|
2703
|
+
cancellation
|
|
2704
|
+
});
|
|
2705
|
+
const model = auth.mode === "ai_proxy" ? auth.model : this.cfg.model;
|
|
2706
|
+
return this.runWithResumeFallback(driver, bareSessionId, cancellation.signal, {
|
|
2707
|
+
...baseParams,
|
|
2708
|
+
...model ? { model } : {}
|
|
2709
|
+
}, isVoice);
|
|
2710
|
+
}
|
|
2327
2711
|
async runWithResumeFallback(driver, bareSessionId, cancellation, baseParams, isVoice) {
|
|
2328
2712
|
const cancelled = () => cancellation.aborted || this.stopping;
|
|
2329
2713
|
const aborted = (result) => ({
|
|
@@ -2449,7 +2833,7 @@ var ClaudeCodeHost = class {
|
|
|
2449
2833
|
async buildHistoryPreamble(bareSessionId) {
|
|
2450
2834
|
const session = await getSession(bareSessionId);
|
|
2451
2835
|
if (!session || session.messages.length === 0) return "";
|
|
2452
|
-
const prior = session.messages.slice(0, -1).slice(-10);
|
|
2836
|
+
const prior = session.messages.slice(0, -1).filter((m) => m.kind !== "error").slice(-10);
|
|
2453
2837
|
if (prior.length === 0) return "";
|
|
2454
2838
|
return `Earlier in this conversation:\n${prior.map((m) => `${m.role === "assistant" ? "Assistant" : "User"}: ${m.content}`).join("\n")}`;
|
|
2455
2839
|
}
|
|
@@ -2459,7 +2843,7 @@ var ClaudeCodeHost = class {
|
|
|
2459
2843
|
if (params.tenantId && existing.tenantId && params.tenantId !== existing.tenantId || params.userId && existing.userId && params.userId !== existing.userId) throw new Error("Session ownership metadata mismatch");
|
|
2460
2844
|
return;
|
|
2461
2845
|
}
|
|
2462
|
-
await createSession(bareSessionId, params.agentId, params.
|
|
2846
|
+
await createSession(bareSessionId, params.agentId, CHAT_SESSION_CHANNEL, params.tenantId, params.userId, params.clientType);
|
|
2463
2847
|
}
|
|
2464
2848
|
async loadClaudeSessionIds() {
|
|
2465
2849
|
try {
|
|
@@ -2535,6 +2919,13 @@ function stripAgentPrefix(sessionKey) {
|
|
|
2535
2919
|
if (parts.length >= 3 && parts[0] === "agent") return parts.slice(2).join(":");
|
|
2536
2920
|
return sessionKey;
|
|
2537
2921
|
}
|
|
2922
|
+
/**
|
|
2923
|
+
* True when a turn already showed the user something (text or any activity
|
|
2924
|
+
* card). A refused turn that did is never re-run: its tools may have run.
|
|
2925
|
+
*/
|
|
2926
|
+
function producedVisibleOutput(emissions) {
|
|
2927
|
+
return emissions.some((emission) => emission.type === "chat-activity" || emission.type === "chat-delta" && emission.text.trim().length > 0);
|
|
2928
|
+
}
|
|
2538
2929
|
//#endregion
|
|
2539
2930
|
//#region src/host-config.ts
|
|
2540
2931
|
/**
|
|
@@ -2556,6 +2947,7 @@ function buildHostConfig(resolved, home, chat, env) {
|
|
|
2556
2947
|
if (!chat.apiKey || !chat.chatWsUrl) throw new Error("Unable to resolve chat service URL / API key from ~/.alfe/config.toml. Run `alfe login`.");
|
|
2557
2948
|
const model = env.ALFE_DEFAULT_MODEL?.trim();
|
|
2558
2949
|
const managedFlag = env.ALFE_MANAGED?.trim().toLowerCase();
|
|
2950
|
+
const localAiProxyUrl = parseLoopbackUrl(env.ALFE_LOCAL_AI_PROXY_URL);
|
|
2559
2951
|
return {
|
|
2560
2952
|
wsUrl: chat.chatWsUrl,
|
|
2561
2953
|
apiKey: chat.apiKey,
|
|
@@ -2564,10 +2956,30 @@ function buildHostConfig(resolved, home, chat, env) {
|
|
|
2564
2956
|
mcpConfigPath: (0, node_path.join)(home, "mcp-config.json"),
|
|
2565
2957
|
claudeSessionsPath: (0, node_path.join)((0, node_os.homedir)(), ".alfe", "claude-code", "sessions.json"),
|
|
2566
2958
|
...model ? { model } : {},
|
|
2567
|
-
managed: managedFlag === "1" || managedFlag === "true"
|
|
2959
|
+
managed: managedFlag === "1" || managedFlag === "true",
|
|
2960
|
+
...localAiProxyUrl ? { localAiProxyUrl } : {}
|
|
2568
2961
|
};
|
|
2569
2962
|
}
|
|
2570
2963
|
/**
|
|
2964
|
+
* The daemon's loopback AI proxy URL (`ALFE_LOCAL_AI_PROXY_URL`), as a bare
|
|
2965
|
+
* origin. Only plain-http loopback is accepted: the proxy-mode `claude` child
|
|
2966
|
+
* sends every model request there, so a stray value must never point it off
|
|
2967
|
+
* the machine. Anything else → undefined (the host default applies).
|
|
2968
|
+
*/
|
|
2969
|
+
function parseLoopbackUrl(raw) {
|
|
2970
|
+
const value = raw?.trim();
|
|
2971
|
+
if (!value) return void 0;
|
|
2972
|
+
let url;
|
|
2973
|
+
try {
|
|
2974
|
+
url = new URL(value);
|
|
2975
|
+
} catch {
|
|
2976
|
+
return;
|
|
2977
|
+
}
|
|
2978
|
+
if (url.protocol !== "http:") return void 0;
|
|
2979
|
+
if (url.hostname !== "127.0.0.1" && url.hostname !== "localhost") return void 0;
|
|
2980
|
+
return url.origin;
|
|
2981
|
+
}
|
|
2982
|
+
/**
|
|
2571
2983
|
* Build the host config. Throws (→ non-zero exit from the CLI) when the chat
|
|
2572
2984
|
* URL / api key can't resolve.
|
|
2573
2985
|
*/
|
|
@@ -2606,12 +3018,6 @@ Object.defineProperty(exports, "CREDENTIAL_INVALID_MESSAGE", {
|
|
|
2606
3018
|
return CREDENTIAL_INVALID_MESSAGE;
|
|
2607
3019
|
}
|
|
2608
3020
|
});
|
|
2609
|
-
Object.defineProperty(exports, "CREDENTIAL_MISSING_MESSAGE", {
|
|
2610
|
-
enumerable: true,
|
|
2611
|
-
get: function() {
|
|
2612
|
-
return CREDENTIAL_MISSING_MESSAGE;
|
|
2613
|
-
}
|
|
2614
|
-
});
|
|
2615
3021
|
Object.defineProperty(exports, "CREDENTIAL_UNAVAILABLE_MESSAGE", {
|
|
2616
3022
|
enumerable: true,
|
|
2617
3023
|
get: function() {
|
|
@@ -2636,16 +3042,28 @@ Object.defineProperty(exports, "DEFAULT_ALLOWED_TOOLS", {
|
|
|
2636
3042
|
return DEFAULT_ALLOWED_TOOLS;
|
|
2637
3043
|
}
|
|
2638
3044
|
});
|
|
3045
|
+
Object.defineProperty(exports, "DEFAULT_LOCAL_AI_PROXY_URL", {
|
|
3046
|
+
enumerable: true,
|
|
3047
|
+
get: function() {
|
|
3048
|
+
return DEFAULT_LOCAL_AI_PROXY_URL;
|
|
3049
|
+
}
|
|
3050
|
+
});
|
|
2639
3051
|
Object.defineProperty(exports, "DEFAULT_PERMISSION_MODE", {
|
|
2640
3052
|
enumerable: true,
|
|
2641
3053
|
get: function() {
|
|
2642
3054
|
return DEFAULT_PERMISSION_MODE;
|
|
2643
3055
|
}
|
|
2644
3056
|
});
|
|
2645
|
-
Object.defineProperty(exports, "
|
|
3057
|
+
Object.defineProperty(exports, "LOCAL_LOGIN_REFUSED_MESSAGE", {
|
|
2646
3058
|
enumerable: true,
|
|
2647
3059
|
get: function() {
|
|
2648
|
-
return
|
|
3060
|
+
return LOCAL_LOGIN_REFUSED_MESSAGE;
|
|
3061
|
+
}
|
|
3062
|
+
});
|
|
3063
|
+
Object.defineProperty(exports, "LOCAL_LOGIN_SWITCHED_MESSAGE", {
|
|
3064
|
+
enumerable: true,
|
|
3065
|
+
get: function() {
|
|
3066
|
+
return LOCAL_LOGIN_SWITCHED_MESSAGE;
|
|
2649
3067
|
}
|
|
2650
3068
|
});
|
|
2651
3069
|
Object.defineProperty(exports, "MANAGED_ALLOWED_TOOLS", {
|
|
@@ -2660,6 +3078,48 @@ Object.defineProperty(exports, "MANAGED_DISALLOWED_TOOLS", {
|
|
|
2660
3078
|
return MANAGED_DISALLOWED_TOOLS;
|
|
2661
3079
|
}
|
|
2662
3080
|
});
|
|
3081
|
+
Object.defineProperty(exports, "MODEL_UNAVAILABLE_MESSAGE", {
|
|
3082
|
+
enumerable: true,
|
|
3083
|
+
get: function() {
|
|
3084
|
+
return MODEL_UNAVAILABLE_MESSAGE;
|
|
3085
|
+
}
|
|
3086
|
+
});
|
|
3087
|
+
Object.defineProperty(exports, "OUT_OF_CREDITS_MESSAGE", {
|
|
3088
|
+
enumerable: true,
|
|
3089
|
+
get: function() {
|
|
3090
|
+
return OUT_OF_CREDITS_MESSAGE;
|
|
3091
|
+
}
|
|
3092
|
+
});
|
|
3093
|
+
Object.defineProperty(exports, "PROXY_AGENT_MODEL_ENV_VARS", {
|
|
3094
|
+
enumerable: true,
|
|
3095
|
+
get: function() {
|
|
3096
|
+
return PROXY_AGENT_MODEL_ENV_VARS;
|
|
3097
|
+
}
|
|
3098
|
+
});
|
|
3099
|
+
Object.defineProperty(exports, "PROXY_API_KEY_PLACEHOLDER", {
|
|
3100
|
+
enumerable: true,
|
|
3101
|
+
get: function() {
|
|
3102
|
+
return PROXY_API_KEY_PLACEHOLDER;
|
|
3103
|
+
}
|
|
3104
|
+
});
|
|
3105
|
+
Object.defineProperty(exports, "PROXY_AUTH_FAILED_MESSAGE", {
|
|
3106
|
+
enumerable: true,
|
|
3107
|
+
get: function() {
|
|
3108
|
+
return PROXY_AUTH_FAILED_MESSAGE;
|
|
3109
|
+
}
|
|
3110
|
+
});
|
|
3111
|
+
Object.defineProperty(exports, "PROXY_DEFAULT_MODEL", {
|
|
3112
|
+
enumerable: true,
|
|
3113
|
+
get: function() {
|
|
3114
|
+
return PROXY_DEFAULT_MODEL;
|
|
3115
|
+
}
|
|
3116
|
+
});
|
|
3117
|
+
Object.defineProperty(exports, "PROXY_MODEL_ENV", {
|
|
3118
|
+
enumerable: true,
|
|
3119
|
+
get: function() {
|
|
3120
|
+
return PROXY_MODEL_ENV;
|
|
3121
|
+
}
|
|
3122
|
+
});
|
|
2663
3123
|
Object.defineProperty(exports, "SCRUBBED_ANTHROPIC_ENV_VARS", {
|
|
2664
3124
|
enumerable: true,
|
|
2665
3125
|
get: function() {
|
|
@@ -2690,6 +3150,12 @@ Object.defineProperty(exports, "TURN_TIMEOUT_MS", {
|
|
|
2690
3150
|
return TURN_TIMEOUT_MS;
|
|
2691
3151
|
}
|
|
2692
3152
|
});
|
|
3153
|
+
Object.defineProperty(exports, "USAGE_LIMIT_MESSAGE", {
|
|
3154
|
+
enumerable: true,
|
|
3155
|
+
get: function() {
|
|
3156
|
+
return USAGE_LIMIT_MESSAGE;
|
|
3157
|
+
}
|
|
3158
|
+
});
|
|
2693
3159
|
Object.defineProperty(exports, "WARM_STREAMING_SUPPORTED", {
|
|
2694
3160
|
enumerable: true,
|
|
2695
3161
|
get: function() {
|
|
@@ -2720,6 +3186,12 @@ Object.defineProperty(exports, "buildProbeArgs", {
|
|
|
2720
3186
|
return buildProbeArgs;
|
|
2721
3187
|
}
|
|
2722
3188
|
});
|
|
3189
|
+
Object.defineProperty(exports, "classifyProxyDenial", {
|
|
3190
|
+
enumerable: true,
|
|
3191
|
+
get: function() {
|
|
3192
|
+
return classifyProxyDenial;
|
|
3193
|
+
}
|
|
3194
|
+
});
|
|
2723
3195
|
Object.defineProperty(exports, "collectTurnActivityRecords", {
|
|
2724
3196
|
enumerable: true,
|
|
2725
3197
|
get: function() {
|