super-dsh 0.1.6 → 0.1.10-e
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -3
- package/agent-agy/cordis.patch.yml +7 -4
- package/agent-agy/dist/agent.d.ts +12 -11
- package/agent-agy/dist/agy-auth-gateway.d.ts +124 -0
- package/agent-agy/dist/agy-auth-gateway.js +233 -0
- package/agent-agy/dist/agy-auth.d.ts +153 -0
- package/agent-agy/dist/agy-auth.js +179 -0
- package/agent-agy/dist/agy-cli-client.d.ts +39 -9
- package/agent-agy/dist/agy-cli-client.js +82 -18
- package/agent-agy/dist/agy-store.d.ts +8 -0
- package/agent-agy/dist/index.d.ts +46 -16
- package/agent-agy/dist/index.js +82 -56
- package/agent-agy/dist/models.d.ts +70 -0
- package/agent-agy/dist/models.js +102 -3
- package/agent-agy/package.json +7 -5
- package/agent-agy-sdk/cordis.patch.yml +58 -0
- package/agent-agy-sdk/dist/adapter.d.ts +15 -0
- package/agent-agy-sdk/dist/adapter.js +42 -0
- package/agent-agy-sdk/dist/agent-preset-agy.d.ts +27 -0
- package/agent-agy-sdk/dist/agent-preset-agy.js +32 -0
- package/agent-agy-sdk/dist/agent.d.ts +138 -0
- package/agent-agy-sdk/dist/agent.js +494 -0
- package/{agent-agy → agent-agy-sdk}/dist/agy-client.d.ts +26 -6
- package/{agent-agy → agent-agy-sdk}/dist/agy-client.js +43 -25
- package/agent-agy-sdk/dist/agy-sessions.d.ts +25 -0
- package/agent-agy-sdk/dist/agy-sessions.js +107 -0
- package/agent-agy-sdk/dist/agy-store.d.ts +39 -0
- package/agent-agy-sdk/dist/agy-store.js +83 -0
- package/agent-agy-sdk/dist/inbox.d.ts +26 -0
- package/agent-agy-sdk/dist/inbox.js +53 -0
- package/agent-agy-sdk/dist/index.d.ts +93 -0
- package/agent-agy-sdk/dist/index.js +476 -0
- package/agent-agy-sdk/dist/models.d.ts +41 -0
- package/agent-agy-sdk/dist/models.js +74 -0
- package/agent-agy-sdk/dist/sdk-venv.d.ts +76 -0
- package/agent-agy-sdk/dist/sdk-venv.js +173 -0
- package/agent-agy-sdk/dist/world-plugin.d.ts +10 -0
- package/agent-agy-sdk/dist/world-plugin.js +82 -0
- package/agent-agy-sdk/package.json +64 -0
- package/agent-claude/dist/adapter.js +106 -31
- package/agent-claude/dist/agent.js +154 -17
- package/agent-claude/dist/claude-client.js +84 -43
- package/agent-claude/dist/claude-launch.js +101 -0
- package/agent-claude/dist/claude-native-config.js +169 -0
- package/agent-claude/dist/claude-provider-gateway.js +340 -0
- package/agent-claude/dist/claude-providers.js +391 -0
- package/agent-claude/dist/claude-route.js +152 -0
- package/agent-claude/dist/index.js +214 -37
- package/agent-claude/dist/models.js +14 -0
- package/agent-claude/dist/sdk-runtime.js +320 -0
- package/agent-claude/package.json +6 -4
- package/agent-codex/README.md +141 -36
- package/agent-codex/cordis.patch.yml +14 -10
- package/agent-codex/dist/adapter.js +64 -28
- package/agent-codex/dist/agent.js +255 -81
- package/agent-codex/dist/app-server-approvals.js +165 -0
- package/agent-codex/dist/app-server-client.js +723 -0
- package/agent-codex/dist/app-server-events.js +432 -0
- package/agent-codex/dist/app-server-protocol.js +95 -0
- package/agent-codex/dist/app-server-wire.js +299 -0
- package/agent-codex/dist/codex-launch.js +78 -0
- package/agent-codex/dist/codex-native-config.js +392 -0
- package/agent-codex/dist/codex-provider-gateway.js +402 -0
- package/agent-codex/dist/codex-providers.js +376 -0
- package/agent-codex/dist/codex-route.js +86 -0
- package/agent-codex/dist/index.js +256 -115
- package/agent-codex/dist/permission.js +75 -1
- package/agent-codex/package.json +1 -2
- package/agent-codex-sdk/README.md +79 -0
- package/agent-codex-sdk/cordis.patch.yml +81 -0
- package/agent-codex-sdk/dist/adapter.js +91 -0
- package/agent-codex-sdk/dist/agent-preset-codex.js +32 -0
- package/agent-codex-sdk/dist/agent.js +1085 -0
- package/agent-codex-sdk/dist/codex-native-config.js +392 -0
- package/agent-codex-sdk/dist/codex-provider-gateway.js +402 -0
- package/agent-codex-sdk/dist/codex-providers.js +376 -0
- package/agent-codex-sdk/dist/codex-route.js +86 -0
- package/{agent-codex/dist/codex-client.js → agent-codex-sdk/dist/codex-sdk-client.js} +253 -32
- package/agent-codex-sdk/dist/codex-sdk-launch.js +92 -0
- package/agent-codex-sdk/dist/codex-store.js +180 -0
- package/agent-codex-sdk/dist/inbox.js +53 -0
- package/agent-codex-sdk/dist/index.js +739 -0
- package/agent-codex-sdk/dist/permission.js +71 -0
- package/agent-codex-sdk/dist/sdk-runtime.js +307 -0
- package/agent-codex-sdk/dist/session-map.js +167 -0
- package/agent-codex-sdk/dist/world-plugin.js +93 -0
- package/agent-codex-sdk/package.json +79 -0
- package/agent-codex-sdk/scripts/fetch-sdk-runtime.mjs +57 -0
- package/agent-omp/README.md +72 -55
- package/agent-omp/dist/agent.js +36 -11
- package/agent-omp/dist/index.js +92 -52
- package/agent-omp/dist/lazy-rpc.js +11 -4
- package/agent-omp/dist/omp-cli.js +114 -97
- package/agent-omp/dist/omp-store.js +12 -4
- package/agent-omp/dist/permission.js +70 -5
- package/agent-omp/dist/rpc-client.js +409 -0
- package/agent-omp/dist/world-plugin.js +36 -3
- package/agent-omp/package.json +2 -6
- package/agent-omp-sdk/README.md +103 -0
- package/agent-omp-sdk/cordis.patch.yml +108 -0
- package/agent-omp-sdk/dist/adapter.d.ts +21 -0
- package/agent-omp-sdk/dist/adapter.js +36 -0
- package/agent-omp-sdk/dist/agent-preset-omp.d.ts +31 -0
- package/agent-omp-sdk/dist/agent-preset-omp.js +32 -0
- package/agent-omp-sdk/dist/agent.d.ts +64 -0
- package/agent-omp-sdk/dist/agent.js +1178 -0
- package/agent-omp-sdk/dist/discovery.d.ts +57 -0
- package/agent-omp-sdk/dist/discovery.js +116 -0
- package/agent-omp-sdk/dist/inbox.d.ts +28 -0
- package/agent-omp-sdk/dist/inbox.js +53 -0
- package/agent-omp-sdk/dist/index.d.ts +60 -0
- package/agent-omp-sdk/dist/index.js +632 -0
- package/agent-omp-sdk/dist/knobs.d.ts +34 -0
- package/agent-omp-sdk/dist/knobs.js +50 -0
- package/agent-omp-sdk/dist/lazy-rpc.d.ts +98 -0
- package/agent-omp-sdk/dist/lazy-rpc.js +178 -0
- package/agent-omp-sdk/dist/mobile/zoom-guard.d.ts +187 -0
- package/agent-omp-sdk/dist/mobile/zoom-guard.js +294 -0
- package/agent-omp-sdk/dist/mobile-boot.d.ts +49 -0
- package/agent-omp-sdk/dist/mobile-boot.js +38 -0
- package/agent-omp-sdk/dist/models.d.ts +39 -0
- package/agent-omp-sdk/dist/models.js +299 -0
- package/agent-omp-sdk/dist/omp-cli.d.ts +67 -0
- package/agent-omp-sdk/dist/omp-cli.js +164 -0
- package/agent-omp-sdk/dist/omp-disk-discovery.d.ts +27 -0
- package/agent-omp-sdk/dist/omp-disk-discovery.js +178 -0
- package/agent-omp-sdk/dist/omp-executable.d.ts +2 -0
- package/agent-omp-sdk/dist/omp-executable.js +30 -0
- package/agent-omp-sdk/dist/omp-store.d.ts +134 -0
- package/agent-omp-sdk/dist/omp-store.js +526 -0
- package/agent-omp-sdk/dist/pairing.d.ts +7 -0
- package/agent-omp-sdk/dist/pairing.js +45 -0
- package/agent-omp-sdk/dist/permission.d.ts +138 -0
- package/agent-omp-sdk/dist/permission.js +161 -0
- package/agent-omp-sdk/dist/protocol.d.ts +189 -0
- package/agent-omp-sdk/dist/rpc-types.d.ts +77 -0
- package/agent-omp-sdk/dist/rpc-types.js +1 -0
- package/agent-omp-sdk/dist/sdk-client.d.ts +160 -0
- package/{agent-omp → agent-omp-sdk}/dist/sdk-client.js +108 -56
- package/agent-omp-sdk/dist/sdk-runtime.d.ts +211 -0
- package/agent-omp-sdk/dist/sdk-runtime.js +418 -0
- package/agent-omp-sdk/dist/sidecar-client.d.ts +87 -0
- package/agent-omp-sdk/dist/sidecar-client.js +197 -0
- package/agent-omp-sdk/dist/store/adapter.d.ts +17 -0
- package/agent-omp-sdk/dist/store/adapter.js +47 -0
- package/agent-omp-sdk/dist/store/db.d.ts +70 -0
- package/agent-omp-sdk/dist/store/db.js +210 -0
- package/agent-omp-sdk/dist/store/index.d.ts +23 -0
- package/agent-omp-sdk/dist/store/index.js +59 -0
- package/agent-omp-sdk/dist/store/reconcile.d.ts +15 -0
- package/agent-omp-sdk/dist/store/reconcile.js +51 -0
- package/agent-omp-sdk/dist/store/schema.d.ts +8 -0
- package/agent-omp-sdk/dist/store/schema.js +40 -0
- package/agent-omp-sdk/dist/transport.d.ts +49 -0
- package/agent-omp-sdk/dist/transport.js +35 -0
- package/agent-omp-sdk/dist/world-home.d.ts +4 -0
- package/agent-omp-sdk/dist/world-home.js +23 -0
- package/agent-omp-sdk/dist/world-plugin.d.ts +10 -0
- package/agent-omp-sdk/dist/world-plugin.js +137 -0
- package/agent-omp-sdk/package.json +87 -0
- package/client/index.js +1883 -287
- package/cordis.patch.yml +80 -0
- package/package.json +23 -9
- package/superd-hub/dist/a2a-delivery.d.ts +189 -0
- package/superd-hub/dist/a2a-delivery.js +240 -0
- package/superd-hub/dist/a2a-host.d.ts +76 -0
- package/superd-hub/dist/a2a-host.js +145 -0
- package/superd-hub/dist/a2a-ingest.d.ts +31 -0
- package/superd-hub/dist/a2a-ingest.js +636 -0
- package/superd-hub/dist/a2a-log.d.ts +17 -0
- package/superd-hub/dist/a2a-log.js +42 -0
- package/superd-hub/dist/a2a-row.d.ts +65 -0
- package/superd-hub/dist/a2a-row.js +162 -0
- package/superd-hub/dist/a2a-service.d.ts +357 -0
- package/superd-hub/dist/a2a-service.js +980 -0
- package/superd-hub/dist/a2a-wire.d.ts +220 -0
- package/superd-hub/dist/a2a-wire.js +305 -0
- package/superd-hub/dist/agent-ids.d.ts +10 -0
- package/superd-hub/dist/agent-ids.js +68 -0
- package/superd-hub/dist/agent-roster.d.ts +29 -18
- package/superd-hub/dist/agent-roster.js +54 -15
- package/superd-hub/dist/agent-status.d.ts +119 -0
- package/superd-hub/dist/agent-status.js +386 -0
- package/superd-hub/dist/client/agent-settings.d.ts +210 -0
- package/superd-hub/dist/client/agent-settings.js +300 -0
- package/superd-hub/dist/client/mesh-api.d.ts +134 -0
- package/superd-hub/dist/client/mesh-api.js +219 -0
- package/superd-hub/dist/client/mesh-groups.d.ts +63 -0
- package/superd-hub/dist/client/mesh-groups.js +66 -0
- package/superd-hub/dist/client/roster.d.ts +48 -0
- package/superd-hub/dist/client/roster.js +102 -0
- package/superd-hub/dist/client/runtime-settings-api.d.ts +76 -0
- package/superd-hub/dist/client/runtime-settings-api.js +137 -0
- package/superd-hub/dist/client/switch.d.ts +165 -0
- package/superd-hub/dist/client/switch.js +295 -0
- package/superd-hub/dist/exec-pty-channel.d.ts +58 -0
- package/superd-hub/dist/exec-pty-channel.js +162 -0
- package/superd-hub/dist/exec-pty-framing.d.ts +32 -0
- package/superd-hub/dist/exec-pty-framing.js +81 -0
- package/superd-hub/dist/exec-pty-rpc.d.ts +21 -0
- package/superd-hub/dist/exec-pty-rpc.js +128 -0
- package/superd-hub/dist/exec-pty-session.d.ts +76 -0
- package/superd-hub/dist/exec-pty-session.js +234 -0
- package/superd-hub/dist/exec.d.ts +56 -0
- package/superd-hub/dist/exec.js +225 -0
- package/superd-hub/dist/gateway.d.ts +39 -2
- package/superd-hub/dist/gateway.js +62 -3
- package/superd-hub/dist/hub-tool.d.ts +174 -0
- package/superd-hub/dist/hub-tool.js +464 -0
- package/superd-hub/dist/index.d.ts +15 -1
- package/superd-hub/dist/index.js +16 -0
- package/superd-hub/dist/machines.d.ts +45 -0
- package/superd-hub/dist/machines.js +125 -0
- package/superd-hub/dist/mesh-federate.d.ts +138 -0
- package/superd-hub/dist/mesh-federate.js +223 -0
- package/superd-hub/dist/mesh-gateway.d.ts +67 -0
- package/superd-hub/dist/mesh-gateway.js +408 -0
- package/superd-hub/dist/mesh-link.d.ts +274 -0
- package/superd-hub/dist/mesh-link.js +441 -0
- package/superd-hub/dist/mesh-rpc.d.ts +123 -0
- package/superd-hub/dist/mesh-rpc.js +250 -0
- package/superd-hub/dist/mux-client.d.ts +71 -0
- package/superd-hub/dist/mux-client.js +281 -0
- package/superd-hub/dist/pack-version.d.ts +2 -0
- package/superd-hub/dist/pack-version.js +30 -0
- package/superd-hub/dist/roster.d.ts +6 -0
- package/superd-hub/dist/rpc.d.ts +18 -2
- package/superd-hub/dist/rpc.js +34 -4
- package/superd-hub/dist/spawn-world.js +3 -1
- package/superd-hub/dist/switch-core.d.ts +62 -25
- package/superd-hub/dist/switch-core.js +53 -30
- package/superd-hub/dist/system-head.d.ts +22 -12
- package/superd-hub/dist/system-head.js +100 -84
- package/superd-hub/dist/wire-endpoints.d.ts +264 -0
- package/superd-hub/dist/wire-endpoints.js +180 -0
- package/superd-hub/lib/client/index.js +1883 -287
- package/superd-hub/package.json +11 -2
- package/agent-agy/dist/agent-preset-projection.d.ts +0 -31
- package/agent-agy/dist/agent-preset-projection.js +0 -33
- package/agent-claude/dist/agent-preset-projection.js +0 -30
- package/agent-codex/dist/agent-preset-projection.js +0 -37
- package/agent-codex/dist/app-home.js +0 -65
- package/agent-codex/dist/models.js +0 -152
- package/agent-codex/dist/spike.js +0 -46
- package/agent-hermes/dist/agent-preset-projection.js +0 -34
- package/agent-omp/dist/agent-preset-projection.js +0 -36
- package/agent-omp/dist/app-home.js +0 -86
- package/agent-omp/dist/sidecar-client.js +0 -195
- package/agent-pi/dist/agent-preset-projection.js +0 -29
- package/superd-hub/dist/client-shim.d.ts +0 -28
- package/superd-hub/dist/client-shim.js +0 -251
- package/superd-hub/dist/index-pass.d.ts +0 -22
- package/superd-hub/dist/index-pass.js +0 -47
- package/superd-hub/dist/labels.d.ts +0 -45
- package/superd-hub/dist/labels.js +0 -83
- /package/{agent-agy → agent-agy-sdk}/bridge/agy_bridge.py +0 -0
- /package/{agent-agy → agent-agy-sdk}/bridge/gemini_openai_server.mjs +0 -0
- /package/{agent-codex/dist/codex-events.js → agent-codex-sdk/dist/codex-sdk-events.js} +0 -0
- /package/{agent-omp → agent-omp-sdk}/dist/protocol.js +0 -0
- /package/{agent-omp → agent-omp-sdk}/sidecar/main.ts +0 -0
|
@@ -39,11 +39,14 @@
|
|
|
39
39
|
*/
|
|
40
40
|
import { randomUUID } from "node:crypto";
|
|
41
41
|
import { agentEvents } from "@deepseek-ai/dsh-agent";
|
|
42
|
+
import { canonicalHeader, headerEquals } from "@deepseek-ai/dsh-session";
|
|
42
43
|
import { LlmAttemptId, ToolCallId, createAssistantMessage, createSystemMessage, createToolResultMessage, createUserMessage, } from "@deepseek-ai/dsh-llm";
|
|
43
44
|
import { createScope } from "@deepseek-ai/dsh-scope";
|
|
44
45
|
import { toClaudeContent } from "./content.js";
|
|
45
46
|
import { Inbox } from "./inbox.js";
|
|
46
47
|
import { resetProjectionState } from "./claude-events.js";
|
|
48
|
+
import { NATIVE_LAUNCH } from "./claude-launch.js";
|
|
49
|
+
import { resolveRoute, routeSeed } from "./claude-route.js";
|
|
47
50
|
import { CLAUDE_PROVIDER_ID } from "./adapter.js";
|
|
48
51
|
import { claudePermissionMode, presetFromEvents } from "./permission.js";
|
|
49
52
|
import { catalogFromInit, modelEntryFromSdk, readModelCatalog, setModelCatalog } from "./models.js";
|
|
@@ -235,6 +238,9 @@ export class ClaudeAgent {
|
|
|
235
238
|
inbox;
|
|
236
239
|
ctx;
|
|
237
240
|
client;
|
|
241
|
+
/** Unsubscriber for the LIVE client's wire listener — released before a swap closes the old child. */
|
|
242
|
+
clientUnsubscribe = undefined;
|
|
243
|
+
clientFactory;
|
|
238
244
|
loopCtx;
|
|
239
245
|
scope;
|
|
240
246
|
dispatch;
|
|
@@ -262,7 +268,12 @@ export class ClaudeAgent {
|
|
|
262
268
|
systemMessageDone = false;
|
|
263
269
|
/** Last `request/context` route key (`provider/model`) — dedupes per-turn emits. */
|
|
264
270
|
lastRouteKey = null;
|
|
265
|
-
/**
|
|
271
|
+
/** The route the OPEN turn runs, captured when the turn opened. */
|
|
272
|
+
turnRoute = undefined;
|
|
273
|
+
/** The route the LIVE client was last told to run (`provider/model`) — a work-saver. */
|
|
274
|
+
routeKey = null;
|
|
275
|
+
/** Whether THIS agent instance has already logged a `request/header` snapshot. */
|
|
276
|
+
requestHeaderLogged = false;
|
|
266
277
|
streamBridge;
|
|
267
278
|
assistantAttemptCounter = 0;
|
|
268
279
|
assistantStreamRevision = 0;
|
|
@@ -295,12 +306,13 @@ export class ClaudeAgent {
|
|
|
295
306
|
commandsSignature = "";
|
|
296
307
|
/** Releases the current mirror before it is rebuilt or the agent is disposed. */
|
|
297
308
|
commandsDisposer = undefined;
|
|
298
|
-
constructor(loopCtx, id, options, session, client, onIdleExit, runtimeInfo) {
|
|
309
|
+
constructor(loopCtx, id, options, session, client, onIdleExit, runtimeInfo, clientFactory) {
|
|
299
310
|
this.loopCtx = loopCtx;
|
|
300
311
|
this.id = id;
|
|
301
312
|
this.options = options;
|
|
302
313
|
this.session = withSystemHead(session, () => createSystemMessage(""));
|
|
303
314
|
this.client = client;
|
|
315
|
+
this.clientFactory = clientFactory;
|
|
304
316
|
this.onIdleExit = onIdleExit;
|
|
305
317
|
this.runtimeInfo = runtimeInfo;
|
|
306
318
|
this.dispatch = agentEvents(loopCtx, this);
|
|
@@ -318,7 +330,7 @@ export class ClaudeAgent {
|
|
|
318
330
|
this.attachCommandRuntime(commandCtx.commands);
|
|
319
331
|
});
|
|
320
332
|
this.lastTurn = session.snapshotEvents().findLast((event) => event.type === "turn/start")?.data.turn ?? 0;
|
|
321
|
-
client.on((event) => this.handleEvent(event));
|
|
333
|
+
this.clientUnsubscribe = client.on((event) => this.handleEvent(event));
|
|
322
334
|
// Session-start projection reset: the module-level tool-name map must never
|
|
323
335
|
// leak another session's tool into this one (Task 4 review finding).
|
|
324
336
|
resetProjectionState();
|
|
@@ -420,6 +432,12 @@ export class ClaudeAgent {
|
|
|
420
432
|
}
|
|
421
433
|
this.markIdle();
|
|
422
434
|
this.endActivity();
|
|
435
|
+
// Release the live client's wire listener BEFORE its child closes (Task 9
|
|
436
|
+
// ledger): without this the dispose path can still receive the superseded
|
|
437
|
+
// client's stale `agent_end` — the same bug class `reattach()` guards
|
|
438
|
+
// against on the swap path.
|
|
439
|
+
this.clientUnsubscribe?.();
|
|
440
|
+
this.clientUnsubscribe = undefined;
|
|
423
441
|
this.client.close();
|
|
424
442
|
resetProjectionState();
|
|
425
443
|
// Release the command mirror explicitly (the agent scope would also dispose
|
|
@@ -618,8 +636,14 @@ export class ClaudeAgent {
|
|
|
618
636
|
* than hanging, then unwinds the reservation.
|
|
619
637
|
*/
|
|
620
638
|
async startTurn(message) {
|
|
639
|
+
let route;
|
|
621
640
|
try {
|
|
622
|
-
|
|
641
|
+
// Resolve the route and put it in force BEFORE the client is touched: a
|
|
642
|
+
// provider move is a different child, and a mid-turn selection change must
|
|
643
|
+
// land on the NEXT turn's applyRoute — never kill the running turn.
|
|
644
|
+
route = this.resolveRoute();
|
|
645
|
+
await this.applyRoute(route);
|
|
646
|
+
this.turnRoute = route;
|
|
623
647
|
await this.bootstrapSessionIdentity();
|
|
624
648
|
}
|
|
625
649
|
catch (error) {
|
|
@@ -637,9 +661,11 @@ export class ClaudeAgent {
|
|
|
637
661
|
if (this.disposed)
|
|
638
662
|
return;
|
|
639
663
|
this.session.append("turn/start", { turn: this.dashTurn });
|
|
640
|
-
// Route metadata for the next request
|
|
641
|
-
//
|
|
642
|
-
|
|
664
|
+
// Route metadata for the next request lands before the turn's messages,
|
|
665
|
+
// exactly as the reference loop stamps it.
|
|
666
|
+
if (route !== undefined)
|
|
667
|
+
this.commitRequestHeader(route);
|
|
668
|
+
this.emitRequestContext(route);
|
|
643
669
|
this.appendUserMessage(message);
|
|
644
670
|
// Flush any idle-injection that was parked before this turn.
|
|
645
671
|
for (const entry of this.remoteQueue.splice(0)) {
|
|
@@ -743,25 +769,132 @@ export class ClaudeAgent {
|
|
|
743
769
|
* effective provider/model plus the model's context window when genuinely
|
|
744
770
|
* known. Turn-enclosed (no step required) and deduped by route key.
|
|
745
771
|
*/
|
|
746
|
-
emitRequestContext() {
|
|
772
|
+
emitRequestContext(route) {
|
|
747
773
|
if (!this.turnOpen)
|
|
748
774
|
return;
|
|
749
|
-
|
|
775
|
+
// A resolved route names the raw provider/model pair (the route the selector
|
|
776
|
+
// picked); a missing route falls back to the observed model (the degraded
|
|
777
|
+
// single-provider shape), preserving the pre-multi-provider metadata.
|
|
778
|
+
const model = route !== undefined
|
|
779
|
+
? route.model
|
|
780
|
+
: (this.observedMessageModel || this.observedDefaultModel || this.options.model || "");
|
|
750
781
|
if (model === "")
|
|
751
782
|
return;
|
|
752
|
-
const
|
|
783
|
+
const provider = route?.provider ?? CLAUDE_PROVIDER_ID;
|
|
784
|
+
const key = `${provider}/${model}`;
|
|
753
785
|
if (key === this.lastRouteKey)
|
|
754
786
|
return;
|
|
755
787
|
this.lastRouteKey = key;
|
|
756
|
-
const
|
|
757
|
-
|
|
788
|
+
const contextWindow = route !== undefined
|
|
789
|
+
? (this.runtimeInfo?.contextWindowFor?.(route.provider, route.model)
|
|
790
|
+
?? readModelCatalog().models.find((candidate) => candidate.id === route.model)?.contextWindow)
|
|
791
|
+
: undefined;
|
|
758
792
|
this.session.append("request/context", {
|
|
759
|
-
provider
|
|
793
|
+
provider,
|
|
760
794
|
model,
|
|
761
795
|
...(contextWindow !== undefined && contextWindow > 0 ? { contextWindow } : {}),
|
|
762
796
|
});
|
|
763
797
|
trace(`request/context ${key}${contextWindow === undefined ? "" : ` ctx=${contextWindow}`}`);
|
|
764
798
|
}
|
|
799
|
+
/**
|
|
800
|
+
* Resolve the route this session must run, mirroring the harness's own
|
|
801
|
+
* precedence (`pending ?? logged ?? preferred ?? fallback`). Each candidate is
|
|
802
|
+
* already a raw provider/model pair — the provider IS the route id and the
|
|
803
|
+
* model IS the raw wire id (the codex twin's shape). A provider id with no
|
|
804
|
+
* store entry reads as "launch native, inject nothing".
|
|
805
|
+
*/
|
|
806
|
+
resolveRoute() {
|
|
807
|
+
const projections = this.loopCtx.get("sessionProjections");
|
|
808
|
+
const service = this.ctx.get("agentDefaultModel");
|
|
809
|
+
return resolveRoute({
|
|
810
|
+
pending: projections?.stateOf?.(this.session, "modelSelection")?.pending,
|
|
811
|
+
logged: this.session.requestHeader()?.config,
|
|
812
|
+
preferred: service?.currentSelection?.(),
|
|
813
|
+
fallback: routeSeed(this.options),
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Put the resolved route in force on the client, swapping the child when the
|
|
818
|
+
* provider moved. Three cases, in increasing cost:
|
|
819
|
+
* 1. nothing spawned yet — pin the launch and let the first prompt spawn
|
|
820
|
+
* (the lazy-spawn gate: choosing a model must not cost a process);
|
|
821
|
+
* 2. same provider — `setModel` rides the live query with the RAW id;
|
|
822
|
+
* 3. different provider — the child is replaced (`reattach`).
|
|
823
|
+
*/
|
|
824
|
+
async applyRoute(route) {
|
|
825
|
+
const launch = route === undefined ? undefined : this.runtimeInfo?.launchFor?.(route.provider, route.model);
|
|
826
|
+
// An unknown provider id (a legacy session pinned before the multi-provider
|
|
827
|
+
// rework) maps to "no injection": the user's own settings.json decides.
|
|
828
|
+
const launchSpec = launch ?? NATIVE_LAUNCH;
|
|
829
|
+
const providerId = launch === undefined ? null : route?.provider ?? null;
|
|
830
|
+
const client = this.client;
|
|
831
|
+
const key = `${providerId ?? "native"}/${route?.model ?? ""}`;
|
|
832
|
+
if (!client.spawned) {
|
|
833
|
+
client.setLaunch(launchSpec);
|
|
834
|
+
await client.ensureStarted();
|
|
835
|
+
}
|
|
836
|
+
else if ((client.appliedProvider ?? null) !== providerId) {
|
|
837
|
+
await this.reattach(launchSpec, providerId, route);
|
|
838
|
+
}
|
|
839
|
+
else if (route !== undefined) {
|
|
840
|
+
await client.setModel(route.model).catch((error) => {
|
|
841
|
+
trace(`set_model ${key} failed: ${String(error)}`);
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
this.routeKey = key;
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Replace the client at a turn boundary: close the old one and build a fresh
|
|
848
|
+
* child through the injected factory, resuming the SAME SDK session id and
|
|
849
|
+
* re-wiring the event listener and the live permission mode.
|
|
850
|
+
*/
|
|
851
|
+
async reattach(launch, _providerId, _route) {
|
|
852
|
+
const old = this.client;
|
|
853
|
+
const threadId = old.threadId;
|
|
854
|
+
this.clientUnsubscribe?.();
|
|
855
|
+
old.close();
|
|
856
|
+
if (this.clientFactory === undefined) {
|
|
857
|
+
throw new Error("agent-claude: cannot reattach — no clientFactory was injected");
|
|
858
|
+
}
|
|
859
|
+
const replacement = this.clientFactory({ ...(threadId !== null ? { resumeSessionId: threadId } : {}), launch });
|
|
860
|
+
this.clientUnsubscribe = replacement.on((event) => this.handleEvent(event));
|
|
861
|
+
this.client = replacement;
|
|
862
|
+
// Re-apply the live permission mode onto the fresh child (the old client's
|
|
863
|
+
// buffered control calls died with it).
|
|
864
|
+
const mode = this.runtimeInfo?.claudeMode ?? claudePermissionMode(presetFromEvents(this.session.snapshotEvents()));
|
|
865
|
+
if (mode !== undefined)
|
|
866
|
+
void replacement.setPermissionMode(mode).catch(() => { });
|
|
867
|
+
await replacement.ensureStarted();
|
|
868
|
+
}
|
|
869
|
+
/**
|
|
870
|
+
* Log the request header the NEXT request runs under, so the harness's
|
|
871
|
+
* `modelSelection` projection retires a pending switch when it matches.
|
|
872
|
+
*/
|
|
873
|
+
commitRequestHeader(route) {
|
|
874
|
+
const header = canonicalHeader({
|
|
875
|
+
config: {
|
|
876
|
+
provider: route.provider,
|
|
877
|
+
model: route.model,
|
|
878
|
+
},
|
|
879
|
+
});
|
|
880
|
+
const baseline = this.session.requestHeader();
|
|
881
|
+
if (!this.requestHeaderLogged) {
|
|
882
|
+
this.session.append("request/header", { header, reason: baseline === undefined ? "initial" : "resume" });
|
|
883
|
+
this.requestHeaderLogged = true;
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
if (baseline === undefined || !headerEquals(baseline, header)) {
|
|
887
|
+
this.session.append("request/header", { header, reason: "change" });
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
/** Test-only thin shell over {@link applyRoute}. */
|
|
891
|
+
async applyRouteForTest(route) {
|
|
892
|
+
await this.applyRoute(route);
|
|
893
|
+
}
|
|
894
|
+
/** Test-only thin shell over {@link resolveRoute}. */
|
|
895
|
+
resolveRouteForTest() {
|
|
896
|
+
return this.resolveRoute();
|
|
897
|
+
}
|
|
765
898
|
/** The open stream bridge for this message, starting one on first use. */
|
|
766
899
|
ensureStreamBridge() {
|
|
767
900
|
if (this.streamBridge === undefined || this.streamBridge.ended) {
|
|
@@ -856,7 +989,7 @@ export class ClaudeAgent {
|
|
|
856
989
|
const assistant = createAssistantMessage({
|
|
857
990
|
content: blocks,
|
|
858
991
|
// The OBSERVED model (what the CLI actually ran), not the requested one.
|
|
859
|
-
source: { provider: CLAUDE_PROVIDER_ID, model: this.observedMessageModel || this.observedDefaultModel || this.options.model || "" },
|
|
992
|
+
source: { provider: this.turnRoute?.provider ?? CLAUDE_PROVIDER_ID, model: this.observedMessageModel || this.observedDefaultModel || this.options.model || "" },
|
|
860
993
|
});
|
|
861
994
|
const event = this.session.append("assistant/message", {
|
|
862
995
|
turn: this.dashTurn,
|
|
@@ -905,13 +1038,17 @@ export class ClaudeAgent {
|
|
|
905
1038
|
observeModelCatalog(init) {
|
|
906
1039
|
if (typeof init.model === "string" && init.model !== "")
|
|
907
1040
|
this.observedDefaultModel = init.model;
|
|
1041
|
+
// The observed catalog stays RAW: the fixed `claude` route serves exactly
|
|
1042
|
+
// what the CLI reported, and curated provider models live under their own
|
|
1043
|
+
// routes (2026-10-04 F-A) — no composite minting here.
|
|
908
1044
|
const entries = catalogFromInit(init);
|
|
909
1045
|
if (entries.length > 0)
|
|
910
1046
|
setModelCatalog(entries, this.observedDefaultModel);
|
|
911
1047
|
void this.client.supportedModels().then((models) => {
|
|
912
1048
|
const full = Array.isArray(models) ? models.flatMap((m) => modelEntryFromSdk(m)) : [];
|
|
913
|
-
if (full.length
|
|
914
|
-
|
|
1049
|
+
if (full.length === 0)
|
|
1050
|
+
return;
|
|
1051
|
+
setModelCatalog(full, this.observedDefaultModel);
|
|
915
1052
|
}).catch((error) => {
|
|
916
1053
|
trace(`supportedModels() failed: ${String(error)}`);
|
|
917
1054
|
});
|
|
@@ -1133,7 +1270,7 @@ export class ClaudeAgent {
|
|
|
1133
1270
|
this.commitStepStart();
|
|
1134
1271
|
const assistant = createAssistantMessage({
|
|
1135
1272
|
content: blocks,
|
|
1136
|
-
source: { provider: CLAUDE_PROVIDER_ID, model: String(event.fallbackModel ?? this.options.model ?? "") },
|
|
1273
|
+
source: { provider: this.turnRoute?.provider ?? CLAUDE_PROVIDER_ID, model: String(event.fallbackModel ?? this.options.model ?? "") },
|
|
1137
1274
|
});
|
|
1138
1275
|
this.session.append("assistant/message", {
|
|
1139
1276
|
turn: this.dashTurn,
|
|
@@ -8,6 +8,15 @@
|
|
|
8
8
|
* methods exist at all), drives the bridge lazily per session, and fans every
|
|
9
9
|
* message out through `projectClaudeEvent` (claude-events.ts).
|
|
10
10
|
*
|
|
11
|
+
* ## The SDK is runtime-self-fetched, not imported
|
|
12
|
+
*
|
|
13
|
+
* The SDK is not a static dependency of this package: the default factory
|
|
14
|
+
* resolves it at the first real prompt via `loadClaudeQuery(worldHome)`
|
|
15
|
+
* (sdk-runtime.ts), which fetches the pinned tree into the agent world's
|
|
16
|
+
* home. `ClaudeFactory` stays SYNCHRONOUS — the await lives in factory
|
|
17
|
+
* RESOLUTION, never between query construction and the `this.query`
|
|
18
|
+
* assignment (see `ensureStarted`).
|
|
19
|
+
*
|
|
11
20
|
* ## The two hard deviations from the codex precedent
|
|
12
21
|
*
|
|
13
22
|
* 1. **No shared singleton.** `agent-codex` multiplexes all clients through one
|
|
@@ -54,38 +63,31 @@
|
|
|
54
63
|
* every push is queued in order and there is no separate follow-up channel.
|
|
55
64
|
* `steer` differs by pushing with `priority: "now"`.
|
|
56
65
|
*/
|
|
57
|
-
import {
|
|
58
|
-
import { homedir } from "node:os";
|
|
59
|
-
import { join } from "node:path";
|
|
60
|
-
import { query as officialQuery } from "@anthropic-ai/claude-agent-sdk";
|
|
66
|
+
import { loadClaudeQuery, resolveClaudeBinary } from "./sdk-runtime.js";
|
|
61
67
|
import { InputQueue } from "./input-queue.js";
|
|
62
68
|
import { projectClaudeEvent } from "./claude-events.js";
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
*
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const defaultExecutableResolver = () => process.env.CLAUDE_EXECUTABLE ?? resolveClaudeExecutable();
|
|
72
|
-
/** PATH first, then the common native-installer locations (a service unit
|
|
73
|
-
* PATH may not include ~/.local/bin — dev3 2026-09-22: "native binary not
|
|
74
|
-
* found at claude" mid-session). */
|
|
75
|
-
function resolveClaudeExecutable() {
|
|
76
|
-
for (const dir of (process.env.PATH ?? "").split(":").filter(Boolean)) {
|
|
77
|
-
if (existsSync(join(dir, "claude")))
|
|
78
|
-
return DEFAULT_CLAUDE_EXECUTABLE;
|
|
79
|
-
}
|
|
80
|
-
for (const candidate of [join(homedir(), ".local/bin/claude"), "/usr/local/bin/claude"]) {
|
|
81
|
-
if (existsSync(candidate))
|
|
82
|
-
return candidate;
|
|
83
|
-
}
|
|
84
|
-
return DEFAULT_CLAUDE_EXECUTABLE;
|
|
85
|
-
}
|
|
86
|
-
let factory = defaultFactory;
|
|
69
|
+
import { NATIVE_LAUNCH } from "./claude-launch.js";
|
|
70
|
+
/** The default executable resolution: Task 1's `resolveClaudeBinary` —
|
|
71
|
+
* `$CLAUDE_EXECUTABLE` (only while it exists) > `claude` on PATH >
|
|
72
|
+
* `~/.local/bin/claude` > `/usr/local/bin/claude`. `undefined` means "no
|
|
73
|
+
* local binary": `pathToClaudeCodeExecutable` is left unset and the SDK's
|
|
74
|
+
* bundled platform payload is the CLI (bundled-payload mode). */
|
|
75
|
+
const defaultExecutableResolver = () => resolveClaudeBinary();
|
|
76
|
+
let factory; // test injection only
|
|
87
77
|
let executableResolver = defaultExecutableResolver;
|
|
88
78
|
let refcount = 0;
|
|
79
|
+
/** The factory to construct queries with: a test-injected one wins (the sync
|
|
80
|
+
* seam); otherwise the SDK is self-fetched into `worldHome`'s durable runtime
|
|
81
|
+
* on first use (sdk-runtime.ts). */
|
|
82
|
+
async function resolveFactory(worldHome) {
|
|
83
|
+
if (factory)
|
|
84
|
+
return factory;
|
|
85
|
+
if (worldHome === undefined) {
|
|
86
|
+
throw new Error("agent-claude: client needs worldHome to fetch its SDK runtime (the provider passes dshHomePath)");
|
|
87
|
+
}
|
|
88
|
+
const sdkQuery = await loadClaudeQuery(worldHome);
|
|
89
|
+
return (o) => sdkQuery({ prompt: o.prompt, options: o.options });
|
|
90
|
+
}
|
|
89
91
|
/** Replace the SDK entry point (tests inject a fake query). */
|
|
90
92
|
export function setClaudeFactory(next) {
|
|
91
93
|
factory = next;
|
|
@@ -101,7 +103,7 @@ export function claudeClientRefCount() {
|
|
|
101
103
|
/** Restore module-level seams and refcount (test isolation only). */
|
|
102
104
|
export function resetClaudeClientState() {
|
|
103
105
|
refcount = 0;
|
|
104
|
-
factory =
|
|
106
|
+
factory = undefined;
|
|
105
107
|
executableResolver = defaultExecutableResolver;
|
|
106
108
|
}
|
|
107
109
|
/**
|
|
@@ -114,13 +116,15 @@ export function resetClaudeClientState() {
|
|
|
114
116
|
* is the caller's to trace; the boot catalog stays as-is.
|
|
115
117
|
*/
|
|
116
118
|
export async function probeClaudeModels(config = {}) {
|
|
119
|
+
const queryFactory = await resolveFactory(config.worldHome);
|
|
120
|
+
const executable = executableResolver();
|
|
117
121
|
const queue = new InputQueue();
|
|
118
|
-
const query =
|
|
122
|
+
const query = queryFactory({
|
|
119
123
|
prompt: queue,
|
|
120
124
|
options: {
|
|
121
125
|
cwd: config.cwd ?? process.cwd(),
|
|
122
126
|
env: { ...process.env },
|
|
123
|
-
pathToClaudeCodeExecutable:
|
|
127
|
+
...(executable !== undefined ? { pathToClaudeCodeExecutable: executable } : {}),
|
|
124
128
|
persistSession: false,
|
|
125
129
|
},
|
|
126
130
|
});
|
|
@@ -145,10 +149,13 @@ export async function probeClaudeModels(config = {}) {
|
|
|
145
149
|
}
|
|
146
150
|
export class ClaudeSdkClient {
|
|
147
151
|
cwdPath;
|
|
152
|
+
worldHome;
|
|
148
153
|
claudeSessionId;
|
|
149
154
|
resumeSessionId;
|
|
150
155
|
canUseTool;
|
|
151
156
|
allowDangerouslySkipPermissions;
|
|
157
|
+
/** The provider-pinning launch this client will spawn under (NATIVE_LAUNCH until `setLaunch`). */
|
|
158
|
+
launch;
|
|
152
159
|
/** True while a user turn is in flight (set on push, cleared on `turn_end`/`agent_end`). */
|
|
153
160
|
turnActive = false;
|
|
154
161
|
queue;
|
|
@@ -164,10 +171,12 @@ export class ClaudeSdkClient {
|
|
|
164
171
|
throw new Error("agent-claude: claudeSessionId and resumeSessionId are mutually exclusive");
|
|
165
172
|
}
|
|
166
173
|
this.cwdPath = config.cwd ?? process.cwd();
|
|
174
|
+
this.worldHome = config.worldHome;
|
|
167
175
|
this.claudeSessionId = config.claudeSessionId;
|
|
168
176
|
this.resumeSessionId = config.resumeSessionId;
|
|
169
177
|
this.canUseTool = config.canUseTool;
|
|
170
178
|
this.allowDangerouslySkipPermissions = config.allowDangerouslySkipPermissions;
|
|
179
|
+
this.launch = config.launch ?? NATIVE_LAUNCH;
|
|
171
180
|
refcount += 1;
|
|
172
181
|
}
|
|
173
182
|
/**
|
|
@@ -186,6 +195,21 @@ export class ClaudeSdkClient {
|
|
|
186
195
|
get threadId() {
|
|
187
196
|
return this.threadIdValue;
|
|
188
197
|
}
|
|
198
|
+
/** The provider id this client was last told to spawn under (constructor fact; `null` = native). */
|
|
199
|
+
get appliedProvider() {
|
|
200
|
+
return this.launch.providerId;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Pin the launch this client will spawn under. Only legal before the query
|
|
204
|
+
* exists: once the CLI subprocess is up, a provider move is a different child
|
|
205
|
+
* (the agent swaps the whole client at a turn boundary — see `#reattach`).
|
|
206
|
+
*/
|
|
207
|
+
setLaunch(launch) {
|
|
208
|
+
if (this.query !== undefined) {
|
|
209
|
+
throw new Error("agent-claude: setLaunch after spawn is not allowed (a provider move swaps the client)");
|
|
210
|
+
}
|
|
211
|
+
this.launch = launch;
|
|
212
|
+
}
|
|
189
213
|
/** The client's own running/streaming attestation (idle-TTL revalidation gate). */
|
|
190
214
|
async getState() {
|
|
191
215
|
return { isStreaming: this.turnActive };
|
|
@@ -221,22 +245,27 @@ export class ClaudeSdkClient {
|
|
|
221
245
|
};
|
|
222
246
|
}
|
|
223
247
|
/**
|
|
224
|
-
* Lazily construct the query (zero-IO until this runs).
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
* the
|
|
248
|
+
* Lazily construct the query (zero-IO until this runs). The factory is
|
|
249
|
+
* resolved first — an await (a test-injected seam, or the world-home SDK
|
|
250
|
+
* self-fetch) — and a caller that loses the race during that await reuses
|
|
251
|
+
* the winner's query at the re-check. From the construct line through the
|
|
252
|
+
* `this.query = query` assignment everything is synchronous (zero awaits),
|
|
253
|
+
* so concurrent first prompts can never build two queries. The single-query
|
|
254
|
+
* invariant therefore still depends on the `ClaudeFactory` contract staying
|
|
255
|
+
* synchronous (it must return the `Query` without awaiting). The reader loop
|
|
256
|
+
* is started fire-and-forget before any pending control call is replayed, so
|
|
257
|
+
* a throwing control can never leave the CLI undrained.
|
|
232
258
|
*/
|
|
233
259
|
async ensureStarted() {
|
|
234
260
|
if (this.query !== undefined)
|
|
235
261
|
return;
|
|
262
|
+
const f = await resolveFactory(this.worldHome); // await BEFORE the sync section
|
|
263
|
+
if (this.query !== undefined)
|
|
264
|
+
return; // lost the race to a concurrent first prompt — reuse its query
|
|
236
265
|
const queue = new InputQueue();
|
|
237
|
-
const query =
|
|
266
|
+
const query = f({ prompt: queue, options: this.buildOptions() });
|
|
238
267
|
this.queue = queue;
|
|
239
|
-
this.query = query;
|
|
268
|
+
this.query = query; // sync section: zero awaits before this assignment (invariant)
|
|
240
269
|
void this.runReader(query);
|
|
241
270
|
const controls = this.pendingControls;
|
|
242
271
|
this.pendingControls = [];
|
|
@@ -303,11 +332,23 @@ export class ClaudeSdkClient {
|
|
|
303
332
|
}
|
|
304
333
|
// --- internals ------------------------------------------------------------
|
|
305
334
|
buildOptions() {
|
|
335
|
+
const launchExecutable = this.launch.pathToClaudeCodeExecutable;
|
|
336
|
+
const executable = launchExecutable !== undefined && launchExecutable !== "" ? launchExecutable : executableResolver();
|
|
306
337
|
return {
|
|
307
338
|
cwd: this.cwdPath,
|
|
308
|
-
env
|
|
309
|
-
|
|
339
|
+
// The launch env holds ADDITIONS only; spreading process.env first keeps
|
|
340
|
+
// PATH/HOME and the user's own key alive (NATIVE_LAUNCH carries an empty
|
|
341
|
+
// env, so the native shape is exactly `{ ...process.env }`).
|
|
342
|
+
env: { ...process.env, ...this.launch.env },
|
|
343
|
+
// The `--settings` command-line tier (a JSON string) outranks the user's
|
|
344
|
+
// native settings.json env, so a provider-pinned launch rides it here
|
|
345
|
+
// (2026-10-04 F-B: without it the native env pins the endpoint).
|
|
346
|
+
...(this.launch.settings !== undefined ? { settings: this.launch.settings } : {}),
|
|
347
|
+
// undefined (no local binary) leaves the key unset — bundled-payload
|
|
348
|
+
// mode lets the SDK spawn its own platform payload as the CLI.
|
|
349
|
+
...(executable !== undefined ? { pathToClaudeCodeExecutable: executable } : {}),
|
|
310
350
|
persistSession: true,
|
|
351
|
+
...(this.launch.model !== undefined ? { model: this.launch.model } : {}),
|
|
311
352
|
...(this.claudeSessionId !== undefined ? { sessionId: this.claudeSessionId } : {}),
|
|
312
353
|
...(this.resumeSessionId !== undefined ? { resume: this.resumeSessionId } : {}),
|
|
313
354
|
...(this.canUseTool !== undefined ? { canUseTool: this.canUseTool } : {}),
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* claude-launch.ts — how a provider reaches the Claude Code CLI
|
|
3
|
+
* (2026-10-04 multi-provider rework).
|
|
4
|
+
*
|
|
5
|
+
* Claude Code is a single-provider harness: its own `settings.json` env block
|
|
6
|
+
* names exactly one `ANTHROPIC_BASE_URL` with one key, so the LIST of
|
|
7
|
+
* configured providers lives in `claude-providers.ts` instead. This module is
|
|
8
|
+
* the other end of that store: given the route the next turn will run (DSH's
|
|
9
|
+
* own per-session model selection, read by `claude-route.ts`), the resolved
|
|
10
|
+
* provider entry, and the resolved host binary, it produces the slice Task 9
|
|
11
|
+
* merges into the SDK's `Options`.
|
|
12
|
+
*
|
|
13
|
+
* The child's environment is the ONE channel a provider's variables enter the
|
|
14
|
+
* process through (spec §2.2) — AND, for a provider-pinned launch, the same env
|
|
15
|
+
* is re-carried as a `--settings`-tier JSON string so the command-line tier
|
|
16
|
+
* outranks the user's native `~/.claude/settings.json` env (2026-10-04 F-B).
|
|
17
|
+
* Concretely, the env keys are:
|
|
18
|
+
*
|
|
19
|
+
* - `ANTHROPIC_BASE_URL` — always, from the entry (validation guarantees a
|
|
20
|
+
* non-empty endpoint; an entry without one is not an entry).
|
|
21
|
+
* - `ANTHROPIC_AUTH_TOKEN` for `authToken` (Bearer), `ANTHROPIC_API_KEY` for
|
|
22
|
+
* `apiKey` (x-api-key) — exactly one, chosen by the entry's auth KIND, with
|
|
23
|
+
* the stored value verbatim. The kind names the channel, so the variable is
|
|
24
|
+
* present even when the value is empty ("keyed outside Super D"): the CLI
|
|
25
|
+
* reads both names with `.trim() || undefined` (measured in
|
|
26
|
+
* `@anthropic-ai/claude-agent-sdk` 2.1.263), so an empty value is unset
|
|
27
|
+
* rather than a fabricated secret.
|
|
28
|
+
* - `ANTHROPIC_MODEL` — the RAW route model id (`glm-5.2`), never the
|
|
29
|
+
* selector's composite key (`glm/glm-5.2`); the composite is a DSH catalog
|
|
30
|
+
* id and has no meaning on the wire.
|
|
31
|
+
* - `ANTHROPIC_DEFAULT_{SONNET,OPUS,HAIKU}_MODEL` — ONLY for aliases the
|
|
32
|
+
* entry actually declares, and never an empty-string one: an alias the user
|
|
33
|
+
* did not write must not be invented, or the child silently answers on a
|
|
34
|
+
* model they never chose.
|
|
35
|
+
*
|
|
36
|
+
* The returned env holds ADDITIONS only. The caller composes the child
|
|
37
|
+
* environment as `{ ...process.env, ...launch.env }` — the spread AFTER
|
|
38
|
+
* `process.env` is the contract, and it is why this function never reads or
|
|
39
|
+
* mutates `process.env` itself: it is a pure declaration, and the caller's
|
|
40
|
+
* precedence is what makes it effective. `NATIVE_LAUNCH` is the same
|
|
41
|
+
* declaration with nothing in it: `providerId: null` and an empty env, for a
|
|
42
|
+
* route no provider entry answers (a legacy bare-id selection, the native
|
|
43
|
+
* settings-only shape).
|
|
44
|
+
*
|
|
45
|
+
* The same reasoning makes `pathToClaudeCodeExecutable` optional and
|
|
46
|
+
* conditional: it is set only when a host binary was actually resolved, so an
|
|
47
|
+
* unresolved binary omits the key entirely and lets the SDK fall back to its
|
|
48
|
+
* bundled payload (spec §2.1) instead of spawning an empty path.
|
|
49
|
+
*
|
|
50
|
+
* @module agent-claude/claude-launch
|
|
51
|
+
*/
|
|
52
|
+
/** The launch for "no provider injection at all" (the native settings apply). */
|
|
53
|
+
export const NATIVE_LAUNCH = { providerId: null, env: {} };
|
|
54
|
+
/** The variable each auth kind authenticates through. */
|
|
55
|
+
const AUTH_ENV = {
|
|
56
|
+
authToken: "ANTHROPIC_AUTH_TOKEN",
|
|
57
|
+
apiKey: "ANTHROPIC_API_KEY",
|
|
58
|
+
};
|
|
59
|
+
/** The alias slots an entry may declare, in catalog order. */
|
|
60
|
+
const ALIAS_ENV = [
|
|
61
|
+
["sonnet", "ANTHROPIC_DEFAULT_SONNET_MODEL"],
|
|
62
|
+
["opus", "ANTHROPIC_DEFAULT_OPUS_MODEL"],
|
|
63
|
+
["haiku", "ANTHROPIC_DEFAULT_HAIKU_MODEL"],
|
|
64
|
+
];
|
|
65
|
+
/**
|
|
66
|
+
* Turn one resolved provider into the SDK `Options` slice for one route.
|
|
67
|
+
*
|
|
68
|
+
* Pure: reads no file, no env, no clock, and spawns nothing. The provider id
|
|
69
|
+
* comes from the ENTRY (the caller resolves the entry BY `route.provider`, so
|
|
70
|
+
* the label and the injected data stay one fact); the model comes from the
|
|
71
|
+
* ROUTE, because that is what the session selected.
|
|
72
|
+
*
|
|
73
|
+
* @param route - the route the next turn runs (raw model half).
|
|
74
|
+
* @param entry - the provider entry that route resolved to.
|
|
75
|
+
* @param hostBinary - the resolved Claude Code binary, or `undefined` to let
|
|
76
|
+
* the SDK use its bundled payload.
|
|
77
|
+
* @returns the env additions, the wire model, and the optional binary path.
|
|
78
|
+
*/
|
|
79
|
+
export function claudeLaunchFor(route, entry, hostBinary) {
|
|
80
|
+
const env = {
|
|
81
|
+
ANTHROPIC_BASE_URL: entry.baseUrl,
|
|
82
|
+
[AUTH_ENV[entry.auth.kind]]: entry.auth.value,
|
|
83
|
+
ANTHROPIC_MODEL: route.model,
|
|
84
|
+
};
|
|
85
|
+
// Declared aliases only: `undefined` means the entry never named that slot,
|
|
86
|
+
// and "" is not a model id — emitting either would invent a choice.
|
|
87
|
+
for (const [slot, name] of ALIAS_ENV) {
|
|
88
|
+
const value = entry.aliases?.[slot];
|
|
89
|
+
if (value !== undefined && value !== "")
|
|
90
|
+
env[name] = value;
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
providerId: entry.id,
|
|
94
|
+
env,
|
|
95
|
+
settings: JSON.stringify({ env }),
|
|
96
|
+
model: route.model,
|
|
97
|
+
// Spread, not `pathToClaudeCodeExecutable: hostBinary`: an absent key must
|
|
98
|
+
// not exist at all, or it would ride a spread into Options as `undefined`.
|
|
99
|
+
...(hostBinary !== undefined && hostBinary !== "" ? { pathToClaudeCodeExecutable: hostBinary } : {}),
|
|
100
|
+
};
|
|
101
|
+
}
|