agent-shim 8.17.0 → 8.18.0

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 CHANGED
@@ -73,7 +73,7 @@ Sharing composes through a cascade (shipped defaults, then a user-global overrid
73
73
 
74
74
  `claude` refuses to launch while `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, `CLAUDE_CODE_OAUTH_TOKEN` or a `CLAUDE_CODE_USE_BEDROCK`/`VERTEX`/`FOUNDRY` variable is set in the environment, because those outrank every identity's stored credential and would make all identities authenticate as the same account. Opt in deliberately with `AGENT_SHIM_ALLOW_AMBIENT_CREDENTIAL=1` for one launch or `agent-shim identity set <name> --allow-ambient-credential`.
75
75
 
76
- Providers and identities take their token from one `credential` block: an ordered list of sources (`env`, `file`, `command`, and the `op` and `keychain` presets, plus a `literal` placeholder for a local proxy), tried in turn, and a target (`bearer`, `apiKey`, or `oauthToken` for an identity). The token reaches the child's environment only; `check`, `doctor` and `--json` report the source kind and target, never the value.
76
+ Providers and identities take their token from one `credential` block: an ordered list of sources (`env`, `file`, `command`, and the `op` and `keychain` presets, plus a `literal` placeholder for a local proxy), tried in turn, and a target (`bearer`, `apiKey`, or `oauthToken` for an identity). An identity's token reaches the child's environment only; a provider's never reaches the child at all (the front door attaches it at the provider's route, which is also what lets provider inference and Remote Control share one session). `check`, `doctor` and `--json` report the source kind and target, never the value.
77
77
 
78
78
  The full mechanics, including the credential block, the merge algorithm, conditional sharing rules (age, size, git branch, environment), the ambient-credential launch guard, and how `~/.claude/projects/` history is pattern-matched, are in [docs/configuration-model.md](docs/configuration-model.md). Worked examples are in [docs/examples.md](docs/examples.md).
79
79
 
package/dist/cli.cjs CHANGED
@@ -55589,7 +55589,7 @@ function ensureHeadroom(params) {
55589
55589
 
55590
55590
  // src/frontdoor/state.ts
55591
55591
  var import_node_path6 = __toESM(require("node:path"), 1);
55592
- var FRONT_DOOR_PROTOCOL = 2;
55592
+ var FRONT_DOOR_PROTOCOL = 3;
55593
55593
  var FrontDoorSessionSchema = external_exports.strictObject({ pid: external_exports.number().int().positive(), startedAt: external_exports.number(), token: external_exports.string().min(1) });
55594
55594
  var FrontDoorStateSchema = external_exports.strictObject({
55595
55595
  /**
@@ -61749,7 +61749,7 @@ function registerIdentityCommand(program2, deps) {
61749
61749
  // package.json
61750
61750
  var package_default = {
61751
61751
  name: "agent-shim",
61752
- version: "8.17.0",
61752
+ version: "8.18.0",
61753
61753
  description: "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
61754
61754
  license: "MIT",
61755
61755
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -62124,6 +62124,7 @@ var INTERNAL_HEADER_NAMES = [
62124
62124
  "x-agent-shim-session",
62125
62125
  "x-agent-shim-headroom",
62126
62126
  "x-agent-shim-auth",
62127
+ "x-agent-shim-provider",
62127
62128
  "x-agent-shim-hop",
62128
62129
  "x-agent-shim-hop-id",
62129
62130
  "x-headroom-project-id",
@@ -62131,6 +62132,7 @@ var INTERNAL_HEADER_NAMES = [
62131
62132
  ];
62132
62133
  var IDENTITY_HEADER = "x-agent-shim-identity";
62133
62134
  var SESSION_HEADER = "x-agent-shim-session";
62135
+ var PROVIDER_HEADER = "x-agent-shim-provider";
62134
62136
  var HEADROOM_FLAG_HEADER = "x-agent-shim-headroom";
62135
62137
  var PROJECT_ID_HEADER = "x-headroom-project-id";
62136
62138
  var HEADROOM_BASE_URL_HEADER = "x-headroom-base-url";
@@ -62166,6 +62168,7 @@ function identifyRequest(headers) {
62166
62168
  session: {
62167
62169
  identity: internalHeaderValue(headers, IDENTITY_HEADER),
62168
62170
  sessionId: internalHeaderValue(headers, SESSION_HEADER),
62171
+ provider: internalHeaderValue(headers, PROVIDER_HEADER),
62169
62172
  headroom: internalHeaderValue(headers, HEADROOM_FLAG_HEADER) === "1",
62170
62173
  projectId: internalHeaderValue(headers, PROJECT_ID_HEADER)
62171
62174
  },
@@ -62173,9 +62176,6 @@ function identifyRequest(headers) {
62173
62176
  };
62174
62177
  }
62175
62178
  var PROVIDER_PATH_PREFIX = "/providers/";
62176
- function providerBaseUrl(port, provider) {
62177
- return `https://127.0.0.1:${String(port)}${PROVIDER_PATH_PREFIX}${encodeURIComponent(provider)}`;
62178
- }
62179
62179
  function directOrigin(port) {
62180
62180
  return `http://127.0.0.1:${String(port)}`;
62181
62181
  }
@@ -65050,7 +65050,7 @@ function admitHop(hopSecret, custody) {
65050
65050
  return { ok: false, message: "agent-shim front door: the direct listener serves only this door's own headroom hop" };
65051
65051
  }
65052
65052
  const hopId = singleValue(request7.headers[HOP_ID_HEADER]);
65053
- const provider = parseProviderPath(new URL(request7.url, "http://127.0.0.1").pathname)?.provider;
65053
+ const provider = parseProviderPath(new URL(request7.url, "http://127.0.0.1").pathname)?.provider ?? internalHeaderValue(request7.headers, PROVIDER_HEADER);
65054
65054
  const credentials = hopId === void 0 || provider === void 0 ? void 0 : custody.redeem(hopId, provider);
65055
65055
  if (credentials === void 0) {
65056
65056
  return { ok: false, message: "agent-shim front door: this request names no live headroom hop for its provider" };
@@ -67820,7 +67820,7 @@ function pushProvider(push, entry, cacheEnv, signIn) {
67820
67820
  const cache = cacheClause(cacheEnv, `provider ${entry.name}`, validated2.data.credential);
67821
67821
  push("provider", cache.unreadable ? "fail" : "pass", `${entry.name} is valid (${describeProviderEndpoint(validated2.data)}, credential ${describeCredential(validated2.data.credential)})${cache.text}.`, entry.name);
67822
67822
  if (validated2.data.env?.ENABLE_TOOL_SEARCH === void 0) {
67823
- push("provider", "warn", `${entry.name} does not set ENABLE_TOOL_SEARCH: a session on this provider loads MCP tools upfront rather than deferring them, because its base URL is the front door, which Claude Code treats as a non-first-party host. Set ENABLE_TOOL_SEARCH in the provider's env block to restore deferred loading, when the upstream backend supports the tool_reference payloads it sends.`, entry.name);
67823
+ push("provider", "warn", `${entry.name} does not set ENABLE_TOOL_SEARCH: a session on this provider keeps Claude Code's own API as its base URL, so it defers MCP tool loading and sends tool_reference payloads the upstream backend must understand. Set ENABLE_TOOL_SEARCH=false in the provider's env block to load tools upfront instead, when the backend does not support them.`, entry.name);
67824
67824
  }
67825
67825
  if (signIn !== void 0 && isCodexProvider(validated2.data) && validated2.data.codex?.login === "chatgpt-sign-in") {
67826
67826
  const problem = signInProblem(signIn);
@@ -72125,7 +72125,7 @@ async function applyHeadroomHop(request7, response, upstream, deps) {
72125
72125
  let headers = forwardableHeaders(request7.headers);
72126
72126
  let hopId;
72127
72127
  if (upstream !== void 0) {
72128
- const provider = parseProviderPath(new URL(request7.url, "http://127.0.0.1").pathname)?.provider;
72128
+ const provider = parseProviderPath(new URL(request7.url, "http://127.0.0.1").pathname)?.provider ?? request7.session.provider;
72129
72129
  if (provider === void 0) {
72130
72130
  throw new Error(`headroom hop for ${request7.url} names an upstream but no provider`);
72131
72131
  }
@@ -72135,6 +72135,9 @@ async function applyHeadroomHop(request7, response, upstream, deps) {
72135
72135
  headers[HOP_ID_HEADER] = hopId;
72136
72136
  headers[HEADROOM_BASE_URL_HEADER] = upstream;
72137
72137
  }
72138
+ if (request7.session.provider !== void 0) {
72139
+ headers[PROVIDER_HEADER] = request7.session.provider;
72140
+ }
72138
72141
  if (request7.session.projectId !== void 0) {
72139
72142
  headers[PROJECT_ID_HEADER] = request7.session.projectId;
72140
72143
  }
@@ -73289,6 +73292,9 @@ function createProviderRouteResolver(deps) {
73289
73292
  if (deps.rcSelfHostRoute !== void 0 && isRcSelfHostPath(path44)) {
73290
73293
  return { ok: true, route: deps.rcSelfHostRoute };
73291
73294
  }
73295
+ if (request7.session.provider !== void 0 && path44.startsWith("/v1/") && !isRcSelfHostPath(path44)) {
73296
+ return resolve({ ...request7, url: `${PROVIDER_PATH_PREFIX}${encodeURIComponent(request7.session.provider)}${request7.url}` });
73297
+ }
73292
73298
  return path44.startsWith("/v1/") ? { ok: true, route: oauthRoute } : { ok: false, status: HTTP_STATUS.notFound, message: `no such endpoint: ${path44}` };
73293
73299
  }
73294
73300
  const { provider } = scoped;
@@ -73318,15 +73324,40 @@ function createProviderRouteResolver(deps) {
73318
73324
  const httpProvider = definition;
73319
73325
  return {
73320
73326
  ok: true,
73321
- route: passthroughFor(`http:${provider}`, {
73322
- baseUrl: httpProvider.baseUrl,
73323
- stripPrefix: `${PROVIDER_PATH_PREFIX}${encodeURIComponent(provider)}`,
73324
- headroomUpstream: directOrigin(deps.directPort())
73325
- })
73327
+ route: attachingProviderCredential(
73328
+ passthroughFor(`http:${provider}`, {
73329
+ baseUrl: httpProvider.baseUrl,
73330
+ stripPrefix: `${PROVIDER_PATH_PREFIX}${encodeURIComponent(provider)}`,
73331
+ headroomUpstream: directOrigin(deps.directPort())
73332
+ }),
73333
+ { provider, credential: httpProvider.credential, env: deps.env, port: deps.credentials }
73334
+ )
73326
73335
  };
73327
73336
  };
73328
73337
  return async (request7) => await Promise.resolve(resolve(request7));
73329
73338
  }
73339
+ function providerCredentialHeaders(credential) {
73340
+ return credential.target === "apiKey" ? { "x-api-key": credential.token } : { authorization: `Bearer ${credential.token}` };
73341
+ }
73342
+ function attachingProviderCredential(route, deps) {
73343
+ return {
73344
+ name: route.name,
73345
+ headroomEligible: route.headroomEligible,
73346
+ headroomUpstream: route.headroomUpstream,
73347
+ serve: async (request7, response) => {
73348
+ const resolution = resolveCredential({ credential: deps.credential, env: deps.env, port: deps.port, subject: `provider ${deps.provider}` });
73349
+ if (!resolution.ok) {
73350
+ response.start(HTTP_STATUS.badGateway, { "Content-Type": "application/json" });
73351
+ await response.write(
73352
+ JSON.stringify({ type: "error", error: { type: "api_error", message: `provider ${deps.provider} has no usable credential for the front door to attach: ${resolution.message}` } })
73353
+ );
73354
+ response.end();
73355
+ return;
73356
+ }
73357
+ await route.serve({ ...request7, headers: restoreCredentials(request7.headers, providerCredentialHeaders(resolution.credential)) }, response);
73358
+ }
73359
+ };
73360
+ }
73330
73361
 
73331
73362
  // src/frontdoor/rcCredentialStore.ts
73332
73363
  var import_node_path34 = __toESM(require("node:path"), 1);
@@ -73716,6 +73747,7 @@ function startPage(heldToken) {
73716
73747
  refreshPending();
73717
73748
  window.setInterval(function () { refreshSessions(); refreshPending(); }, 4000);
73718
73749
  runFeed();
73750
+ document.getElementById('door-state').textContent = 'connected';
73719
73751
  }
73720
73752
 
73721
73753
  function boot() {
@@ -73790,7 +73822,7 @@ var RC_CLIENT_PAGE_HTML = `<!doctype html>
73790
73822
  <body data-agent-shim-rc-client>
73791
73823
  <header>
73792
73824
  <h1>agent-shim Remote Control</h1>
73793
- <span class="state" id="door-state">not connected</span>
73825
+ <span class="state" id="door-state">connecting</span>
73794
73826
  <span class="state" id="stream-state"></span>
73795
73827
  </header>
73796
73828
  <div id="token-form">
@@ -75238,7 +75270,16 @@ function realFrontDoorSupervisorPorts(paths) {
75238
75270
  });
75239
75271
  const rcControlToken = (0, import_node_crypto14.randomUUID)();
75240
75272
  const resolveRoute = rcObservingResolver(
75241
- createProviderRouteResolver({ fs: realFsPort, providersDir: paths.providersDir, codexPorts, directPort: () => directPort, ...rcSelfHostSurface === void 0 ? {} : { rcSelfHostRoute: rcSelfHostSurface.route } }),
75273
+ createProviderRouteResolver({
75274
+ fs: realFsPort,
75275
+ providersDir: paths.providersDir,
75276
+ codexPorts,
75277
+ directPort: () => directPort,
75278
+ // The same port (cache included) the launcher resolves a launch's provider with, so the credential the door attaches at the route and the one the launcher handed the child are the same resolution of the same block.
75279
+ env: process.env,
75280
+ credentials: { ...realCredentialPort, cache: realCredentialCacheEnv(paths) },
75281
+ ...rcSelfHostSurface === void 0 ? {} : { rcSelfHostRoute: rcSelfHostSurface.route }
75282
+ }),
75242
75283
  rcTracker,
75243
75284
  rcHub
75244
75285
  );
@@ -77467,15 +77508,6 @@ function buildFlagArgs(flags) {
77467
77508
  function buildArgv(params) {
77468
77509
  return [...params.toolFlags, ...params.extraFlags, ...params.passthrough];
77469
77510
  }
77470
- var LOOPBACK_HOSTS = ["127.0.0.1", "localhost", "::1"];
77471
- function isLoopbackUrl(baseUrl) {
77472
- const host = new URL(baseUrl).hostname.replace(/^\[|\]$/g, "");
77473
- return LOOPBACK_HOSTS.some((loopback) => loopback === host);
77474
- }
77475
- function withLoopbackHosts(existing) {
77476
- const listed = (existing ?? "").split(",").map((entry) => entry.trim()).filter((entry) => entry !== "");
77477
- return [...listed, ...LOOPBACK_HOSTS.filter((host) => !listed.includes(host))].join(",");
77478
- }
77479
77511
  function buildEnv(params) {
77480
77512
  const env = { ...params.baseEnv };
77481
77513
  const parentCustomHeaders = params.baseEnv.ANTHROPIC_CUSTOM_HEADERS;
@@ -77486,19 +77518,18 @@ function buildEnv(params) {
77486
77518
  if (params.provider !== void 0) {
77487
77519
  const providerEnv = params.provider.definition.env ?? {};
77488
77520
  providerCustomHeaders = providerEnv.ANTHROPIC_CUSTOM_HEADERS;
77489
- env.ANTHROPIC_BASE_URL = params.provider.baseUrl;
77490
- if (isLoopbackUrl(params.provider.baseUrl)) {
77491
- env.NO_PROXY = withLoopbackHosts(params.baseEnv.NO_PROXY);
77492
- env.no_proxy = withLoopbackHosts(params.baseEnv.no_proxy);
77493
- }
77521
+ env.ANTHROPIC_BASE_URL = void 0;
77494
77522
  env.AGENT_SHIM_PROVIDER = params.provider.definition.displayName;
77495
77523
  for (const [key, value2] of Object.entries(providerEnv)) {
77496
77524
  env[key] = value2;
77497
77525
  }
77498
77526
  }
77499
- const credential = params.provider?.credential ?? params.identityCredential;
77500
- if (credential !== void 0) {
77501
- for (const [variable, value2] of Object.entries(credentialVariables(credential.target, credential.token))) {
77527
+ if (params.provider !== void 0) {
77528
+ env.ANTHROPIC_AUTH_TOKEN = void 0;
77529
+ env.ANTHROPIC_API_KEY = void 0;
77530
+ env.CLAUDE_CODE_OAUTH_TOKEN = void 0;
77531
+ } else if (params.identityCredential !== void 0) {
77532
+ for (const [variable, value2] of Object.entries(credentialVariables(params.identityCredential.target, params.identityCredential.token))) {
77502
77533
  env[variable] = value2;
77503
77534
  }
77504
77535
  }
@@ -77512,6 +77543,7 @@ function buildEnv(params) {
77512
77543
  ...params.resolvedIdentityName === void 0 ? [] : [{ name: IDENTITY_HEADER, value: params.resolvedIdentityName }],
77513
77544
  { name: SESSION_HEADER, value: params.sessionId },
77514
77545
  { name: AUTH_HEADER, value: params.frontdoor.sessionToken },
77546
+ ...params.provider === void 0 ? [] : [{ name: PROVIDER_HEADER, value: params.provider.name }],
77515
77547
  ...params.headroom === void 0 ? [] : [
77516
77548
  { name: HEADROOM_FLAG_HEADER, value: "1" },
77517
77549
  { name: PROJECT_ID_HEADER, value: params.headroom.projectId }
@@ -77519,9 +77551,7 @@ function buildEnv(params) {
77519
77551
  ]
77520
77552
  );
77521
77553
  env.NODE_EXTRA_CA_CERTS = params.frontdoor.trustBundlePath;
77522
- if (params.provider === void 0) {
77523
- env.HTTPS_PROXY = connectProxyUrl(params.frontdoor.connectPort, params.frontdoor.sessionToken);
77524
- }
77554
+ env.HTTPS_PROXY = connectProxyUrl(params.frontdoor.connectPort, params.frontdoor.sessionToken);
77525
77555
  }
77526
77556
  return env;
77527
77557
  }
@@ -77796,10 +77826,6 @@ function prepareLaunch(params) {
77796
77826
  if (frontDoor?.trustWarning !== void 0) {
77797
77827
  log.warn(frontDoor.trustWarning);
77798
77828
  }
77799
- let routedProvider;
77800
- if (resolvedProvider !== void 0 && frontDoor !== void 0) {
77801
- routedProvider = { ...resolvedProvider, baseUrl: providerBaseUrl(frontDoor.port, resolvedProvider.name) };
77802
- }
77803
77829
  let headroom;
77804
77830
  const headroomPort = params.headroom;
77805
77831
  if (resolvedFlags.headroom && headroomPort !== void 0) {
@@ -77823,7 +77849,7 @@ function prepareLaunch(params) {
77823
77849
  configDirEscapeHatch: configDirEscapeHatchApplies,
77824
77850
  resolvedIdentityName: identityDecision.name,
77825
77851
  identitiesDir: paths.identitiesDir,
77826
- ...routedProvider === void 0 ? {} : { provider: routedProvider },
77852
+ ...resolvedProvider === void 0 ? {} : { provider: resolvedProvider },
77827
77853
  ...identityCredential === void 0 ? {} : { identityCredential },
77828
77854
  ...frontDoor === void 0 ? {} : { frontdoor: frontDoor },
77829
77855
  ...headroom === void 0 ? {} : { headroom },
package/dist/index.cjs CHANGED
@@ -2852,18 +2852,15 @@ function isLiveCapability(presented, live) {
2852
2852
  // src/frontdoor/route.ts
2853
2853
  var IDENTITY_HEADER = "x-agent-shim-identity";
2854
2854
  var SESSION_HEADER = "x-agent-shim-session";
2855
+ var PROVIDER_HEADER = "x-agent-shim-provider";
2855
2856
  var HEADROOM_FLAG_HEADER = "x-agent-shim-headroom";
2856
2857
  var PROJECT_ID_HEADER = "x-headroom-project-id";
2857
2858
  var AUTH_HEADER = "x-agent-shim-auth";
2858
- var PROVIDER_PATH_PREFIX = "/providers/";
2859
- function providerBaseUrl(port, provider) {
2860
- return `https://127.0.0.1:${String(port)}${PROVIDER_PATH_PREFIX}${encodeURIComponent(provider)}`;
2861
- }
2862
2859
 
2863
2860
  // src/frontdoor/state.ts
2864
2861
  var import_node_path8 = __toESM(require("node:path"), 1);
2865
2862
  var import_zod3 = require("zod");
2866
- var FRONT_DOOR_PROTOCOL = 2;
2863
+ var FRONT_DOOR_PROTOCOL = 3;
2867
2864
  var FrontDoorSessionSchema = import_zod3.z.strictObject({ pid: import_zod3.z.number().int().positive(), startedAt: import_zod3.z.number(), token: import_zod3.z.string().min(1) });
2868
2865
  var FrontDoorStateSchema = import_zod3.z.strictObject({
2869
2866
  /**
@@ -9118,7 +9115,7 @@ function pushProvider(push, entry, cacheEnv, signIn) {
9118
9115
  const cache = cacheClause(cacheEnv, `provider ${entry.name}`, validated2.data.credential);
9119
9116
  push("provider", cache.unreadable ? "fail" : "pass", `${entry.name} is valid (${describeProviderEndpoint(validated2.data)}, credential ${describeCredential(validated2.data.credential)})${cache.text}.`, entry.name);
9120
9117
  if (validated2.data.env?.ENABLE_TOOL_SEARCH === void 0) {
9121
- push("provider", "warn", `${entry.name} does not set ENABLE_TOOL_SEARCH: a session on this provider loads MCP tools upfront rather than deferring them, because its base URL is the front door, which Claude Code treats as a non-first-party host. Set ENABLE_TOOL_SEARCH in the provider's env block to restore deferred loading, when the upstream backend supports the tool_reference payloads it sends.`, entry.name);
9118
+ push("provider", "warn", `${entry.name} does not set ENABLE_TOOL_SEARCH: a session on this provider keeps Claude Code's own API as its base URL, so it defers MCP tool loading and sends tool_reference payloads the upstream backend must understand. Set ENABLE_TOOL_SEARCH=false in the provider's env block to load tools upfront instead, when the backend does not support them.`, entry.name);
9122
9119
  }
9123
9120
  if (signIn !== void 0 && isCodexProvider(validated2.data) && validated2.data.codex?.login === "chatgpt-sign-in") {
9124
9121
  const problem = signInProblem(signIn);
@@ -11339,15 +11336,6 @@ function buildFlagArgs(flags) {
11339
11336
  function buildArgv(params) {
11340
11337
  return [...params.toolFlags, ...params.extraFlags, ...params.passthrough];
11341
11338
  }
11342
- var LOOPBACK_HOSTS = ["127.0.0.1", "localhost", "::1"];
11343
- function isLoopbackUrl(baseUrl) {
11344
- const host = new URL(baseUrl).hostname.replace(/^\[|\]$/g, "");
11345
- return LOOPBACK_HOSTS.some((loopback) => loopback === host);
11346
- }
11347
- function withLoopbackHosts(existing) {
11348
- const listed = (existing ?? "").split(",").map((entry) => entry.trim()).filter((entry) => entry !== "");
11349
- return [...listed, ...LOOPBACK_HOSTS.filter((host) => !listed.includes(host))].join(",");
11350
- }
11351
11339
  function buildEnv(params) {
11352
11340
  const env = { ...params.baseEnv };
11353
11341
  const parentCustomHeaders = params.baseEnv.ANTHROPIC_CUSTOM_HEADERS;
@@ -11358,19 +11346,18 @@ function buildEnv(params) {
11358
11346
  if (params.provider !== void 0) {
11359
11347
  const providerEnv = params.provider.definition.env ?? {};
11360
11348
  providerCustomHeaders = providerEnv.ANTHROPIC_CUSTOM_HEADERS;
11361
- env.ANTHROPIC_BASE_URL = params.provider.baseUrl;
11362
- if (isLoopbackUrl(params.provider.baseUrl)) {
11363
- env.NO_PROXY = withLoopbackHosts(params.baseEnv.NO_PROXY);
11364
- env.no_proxy = withLoopbackHosts(params.baseEnv.no_proxy);
11365
- }
11349
+ env.ANTHROPIC_BASE_URL = void 0;
11366
11350
  env.AGENT_SHIM_PROVIDER = params.provider.definition.displayName;
11367
11351
  for (const [key, value] of Object.entries(providerEnv)) {
11368
11352
  env[key] = value;
11369
11353
  }
11370
11354
  }
11371
- const credential = params.provider?.credential ?? params.identityCredential;
11372
- if (credential !== void 0) {
11373
- for (const [variable, value] of Object.entries(credentialVariables(credential.target, credential.token))) {
11355
+ if (params.provider !== void 0) {
11356
+ env.ANTHROPIC_AUTH_TOKEN = void 0;
11357
+ env.ANTHROPIC_API_KEY = void 0;
11358
+ env.CLAUDE_CODE_OAUTH_TOKEN = void 0;
11359
+ } else if (params.identityCredential !== void 0) {
11360
+ for (const [variable, value] of Object.entries(credentialVariables(params.identityCredential.target, params.identityCredential.token))) {
11374
11361
  env[variable] = value;
11375
11362
  }
11376
11363
  }
@@ -11384,6 +11371,7 @@ function buildEnv(params) {
11384
11371
  ...params.resolvedIdentityName === void 0 ? [] : [{ name: IDENTITY_HEADER, value: params.resolvedIdentityName }],
11385
11372
  { name: SESSION_HEADER, value: params.sessionId },
11386
11373
  { name: AUTH_HEADER, value: params.frontdoor.sessionToken },
11374
+ ...params.provider === void 0 ? [] : [{ name: PROVIDER_HEADER, value: params.provider.name }],
11387
11375
  ...params.headroom === void 0 ? [] : [
11388
11376
  { name: HEADROOM_FLAG_HEADER, value: "1" },
11389
11377
  { name: PROJECT_ID_HEADER, value: params.headroom.projectId }
@@ -11391,9 +11379,7 @@ function buildEnv(params) {
11391
11379
  ]
11392
11380
  );
11393
11381
  env.NODE_EXTRA_CA_CERTS = params.frontdoor.trustBundlePath;
11394
- if (params.provider === void 0) {
11395
- env.HTTPS_PROXY = connectProxyUrl(params.frontdoor.connectPort, params.frontdoor.sessionToken);
11396
- }
11382
+ env.HTTPS_PROXY = connectProxyUrl(params.frontdoor.connectPort, params.frontdoor.sessionToken);
11397
11383
  }
11398
11384
  return env;
11399
11385
  }
@@ -11668,10 +11654,6 @@ function prepareLaunch(params) {
11668
11654
  if (frontDoor?.trustWarning !== void 0) {
11669
11655
  log.warn(frontDoor.trustWarning);
11670
11656
  }
11671
- let routedProvider;
11672
- if (resolvedProvider !== void 0 && frontDoor !== void 0) {
11673
- routedProvider = { ...resolvedProvider, baseUrl: providerBaseUrl(frontDoor.port, resolvedProvider.name) };
11674
- }
11675
11657
  let headroom;
11676
11658
  const headroomPort = params.headroom;
11677
11659
  if (resolvedFlags.headroom && headroomPort !== void 0) {
@@ -11695,7 +11677,7 @@ function prepareLaunch(params) {
11695
11677
  configDirEscapeHatch: configDirEscapeHatchApplies,
11696
11678
  resolvedIdentityName: identityDecision.name,
11697
11679
  identitiesDir: paths.identitiesDir,
11698
- ...routedProvider === void 0 ? {} : { provider: routedProvider },
11680
+ ...resolvedProvider === void 0 ? {} : { provider: resolvedProvider },
11699
11681
  ...identityCredential === void 0 ? {} : { identityCredential },
11700
11682
  ...frontDoor === void 0 ? {} : { frontdoor: frontDoor },
11701
11683
  ...headroom === void 0 ? {} : { headroom },
package/dist/index.mjs CHANGED
@@ -2588,18 +2588,15 @@ function isLiveCapability(presented, live) {
2588
2588
  // src/frontdoor/route.ts
2589
2589
  var IDENTITY_HEADER = "x-agent-shim-identity";
2590
2590
  var SESSION_HEADER = "x-agent-shim-session";
2591
+ var PROVIDER_HEADER = "x-agent-shim-provider";
2591
2592
  var HEADROOM_FLAG_HEADER = "x-agent-shim-headroom";
2592
2593
  var PROJECT_ID_HEADER = "x-headroom-project-id";
2593
2594
  var AUTH_HEADER = "x-agent-shim-auth";
2594
- var PROVIDER_PATH_PREFIX = "/providers/";
2595
- function providerBaseUrl(port, provider) {
2596
- return `https://127.0.0.1:${String(port)}${PROVIDER_PATH_PREFIX}${encodeURIComponent(provider)}`;
2597
- }
2598
2595
 
2599
2596
  // src/frontdoor/state.ts
2600
2597
  import path8 from "node:path";
2601
2598
  import { z as z3 } from "zod";
2602
- var FRONT_DOOR_PROTOCOL = 2;
2599
+ var FRONT_DOOR_PROTOCOL = 3;
2603
2600
  var FrontDoorSessionSchema = z3.strictObject({ pid: z3.number().int().positive(), startedAt: z3.number(), token: z3.string().min(1) });
2604
2601
  var FrontDoorStateSchema = z3.strictObject({
2605
2602
  /**
@@ -8854,7 +8851,7 @@ function pushProvider(push, entry, cacheEnv, signIn) {
8854
8851
  const cache = cacheClause(cacheEnv, `provider ${entry.name}`, validated2.data.credential);
8855
8852
  push("provider", cache.unreadable ? "fail" : "pass", `${entry.name} is valid (${describeProviderEndpoint(validated2.data)}, credential ${describeCredential(validated2.data.credential)})${cache.text}.`, entry.name);
8856
8853
  if (validated2.data.env?.ENABLE_TOOL_SEARCH === void 0) {
8857
- push("provider", "warn", `${entry.name} does not set ENABLE_TOOL_SEARCH: a session on this provider loads MCP tools upfront rather than deferring them, because its base URL is the front door, which Claude Code treats as a non-first-party host. Set ENABLE_TOOL_SEARCH in the provider's env block to restore deferred loading, when the upstream backend supports the tool_reference payloads it sends.`, entry.name);
8854
+ push("provider", "warn", `${entry.name} does not set ENABLE_TOOL_SEARCH: a session on this provider keeps Claude Code's own API as its base URL, so it defers MCP tool loading and sends tool_reference payloads the upstream backend must understand. Set ENABLE_TOOL_SEARCH=false in the provider's env block to load tools upfront instead, when the backend does not support them.`, entry.name);
8858
8855
  }
8859
8856
  if (signIn !== void 0 && isCodexProvider(validated2.data) && validated2.data.codex?.login === "chatgpt-sign-in") {
8860
8857
  const problem = signInProblem(signIn);
@@ -11075,15 +11072,6 @@ function buildFlagArgs(flags) {
11075
11072
  function buildArgv(params) {
11076
11073
  return [...params.toolFlags, ...params.extraFlags, ...params.passthrough];
11077
11074
  }
11078
- var LOOPBACK_HOSTS = ["127.0.0.1", "localhost", "::1"];
11079
- function isLoopbackUrl(baseUrl) {
11080
- const host = new URL(baseUrl).hostname.replace(/^\[|\]$/g, "");
11081
- return LOOPBACK_HOSTS.some((loopback) => loopback === host);
11082
- }
11083
- function withLoopbackHosts(existing) {
11084
- const listed = (existing ?? "").split(",").map((entry) => entry.trim()).filter((entry) => entry !== "");
11085
- return [...listed, ...LOOPBACK_HOSTS.filter((host) => !listed.includes(host))].join(",");
11086
- }
11087
11075
  function buildEnv(params) {
11088
11076
  const env = { ...params.baseEnv };
11089
11077
  const parentCustomHeaders = params.baseEnv.ANTHROPIC_CUSTOM_HEADERS;
@@ -11094,19 +11082,18 @@ function buildEnv(params) {
11094
11082
  if (params.provider !== void 0) {
11095
11083
  const providerEnv = params.provider.definition.env ?? {};
11096
11084
  providerCustomHeaders = providerEnv.ANTHROPIC_CUSTOM_HEADERS;
11097
- env.ANTHROPIC_BASE_URL = params.provider.baseUrl;
11098
- if (isLoopbackUrl(params.provider.baseUrl)) {
11099
- env.NO_PROXY = withLoopbackHosts(params.baseEnv.NO_PROXY);
11100
- env.no_proxy = withLoopbackHosts(params.baseEnv.no_proxy);
11101
- }
11085
+ env.ANTHROPIC_BASE_URL = void 0;
11102
11086
  env.AGENT_SHIM_PROVIDER = params.provider.definition.displayName;
11103
11087
  for (const [key, value] of Object.entries(providerEnv)) {
11104
11088
  env[key] = value;
11105
11089
  }
11106
11090
  }
11107
- const credential = params.provider?.credential ?? params.identityCredential;
11108
- if (credential !== void 0) {
11109
- for (const [variable, value] of Object.entries(credentialVariables(credential.target, credential.token))) {
11091
+ if (params.provider !== void 0) {
11092
+ env.ANTHROPIC_AUTH_TOKEN = void 0;
11093
+ env.ANTHROPIC_API_KEY = void 0;
11094
+ env.CLAUDE_CODE_OAUTH_TOKEN = void 0;
11095
+ } else if (params.identityCredential !== void 0) {
11096
+ for (const [variable, value] of Object.entries(credentialVariables(params.identityCredential.target, params.identityCredential.token))) {
11110
11097
  env[variable] = value;
11111
11098
  }
11112
11099
  }
@@ -11120,6 +11107,7 @@ function buildEnv(params) {
11120
11107
  ...params.resolvedIdentityName === void 0 ? [] : [{ name: IDENTITY_HEADER, value: params.resolvedIdentityName }],
11121
11108
  { name: SESSION_HEADER, value: params.sessionId },
11122
11109
  { name: AUTH_HEADER, value: params.frontdoor.sessionToken },
11110
+ ...params.provider === void 0 ? [] : [{ name: PROVIDER_HEADER, value: params.provider.name }],
11123
11111
  ...params.headroom === void 0 ? [] : [
11124
11112
  { name: HEADROOM_FLAG_HEADER, value: "1" },
11125
11113
  { name: PROJECT_ID_HEADER, value: params.headroom.projectId }
@@ -11127,9 +11115,7 @@ function buildEnv(params) {
11127
11115
  ]
11128
11116
  );
11129
11117
  env.NODE_EXTRA_CA_CERTS = params.frontdoor.trustBundlePath;
11130
- if (params.provider === void 0) {
11131
- env.HTTPS_PROXY = connectProxyUrl(params.frontdoor.connectPort, params.frontdoor.sessionToken);
11132
- }
11118
+ env.HTTPS_PROXY = connectProxyUrl(params.frontdoor.connectPort, params.frontdoor.sessionToken);
11133
11119
  }
11134
11120
  return env;
11135
11121
  }
@@ -11404,10 +11390,6 @@ function prepareLaunch(params) {
11404
11390
  if (frontDoor?.trustWarning !== void 0) {
11405
11391
  log.warn(frontDoor.trustWarning);
11406
11392
  }
11407
- let routedProvider;
11408
- if (resolvedProvider !== void 0 && frontDoor !== void 0) {
11409
- routedProvider = { ...resolvedProvider, baseUrl: providerBaseUrl(frontDoor.port, resolvedProvider.name) };
11410
- }
11411
11393
  let headroom;
11412
11394
  const headroomPort = params.headroom;
11413
11395
  if (resolvedFlags.headroom && headroomPort !== void 0) {
@@ -11431,7 +11413,7 @@ function prepareLaunch(params) {
11431
11413
  configDirEscapeHatch: configDirEscapeHatchApplies,
11432
11414
  resolvedIdentityName: identityDecision.name,
11433
11415
  identitiesDir: paths.identitiesDir,
11434
- ...routedProvider === void 0 ? {} : { provider: routedProvider },
11416
+ ...resolvedProvider === void 0 ? {} : { provider: resolvedProvider },
11435
11417
  ...identityCredential === void 0 ? {} : { identityCredential },
11436
11418
  ...frontDoor === void 0 ? {} : { frontdoor: frontDoor },
11437
11419
  ...headroom === void 0 ? {} : { headroom },
@@ -1687,6 +1687,8 @@ interface SessionIdentity {
1687
1687
  readonly identity: string | undefined;
1688
1688
  /** The per-launch session id, when the launcher injected one. */
1689
1689
  readonly sessionId: string | undefined;
1690
+ /** The provider this launch selected, when it selected one: the door routes the session's API traffic to that provider whatever host the child believes it is dialling. */
1691
+ readonly provider: string | undefined;
1690
1692
  /** Whether this launch resolved headroom on, so its requests must pass through the headroom hop before their route. */
1691
1693
  readonly headroom: boolean;
1692
1694
  /** The project identity headroom scopes memory to, re-set on the headroom hop and sent nowhere else. */
@@ -2832,7 +2834,7 @@ interface EvaluateAmbientCredentialGuardParams {
2832
2834
  /** The active identity's name, for the message's persistent-opt-in command. Undefined when no identity is known. */
2833
2835
  readonly identityName?: string;
2834
2836
  /**
2835
- * True when this launch routes through an API provider. The guard inspects the PARENT environment, before `buildEnv` runs; a provider launch has its `ANTHROPIC_AUTH_TOKEN` injected and its `ANTHROPIC_API_KEY` cleared by `buildEnv` itself, so an ambient credential in the parent environment never reaches the child and there is nothing left for this guard to protect against. Everything else about the guard (including `IdentitySchema.allowAmbientCredential`) is unchanged by this flag.
2837
+ * True when this launch routes through an API provider. The guard inspects the PARENT environment, before `buildEnv` runs; a provider launch has every credential variable removed by `buildEnv` itself (the door attaches the provider's credential at its route, so the child presents nothing), so an ambient credential in the parent environment never reaches the child and there is nothing left for this guard to protect against. Everything else about the guard (including `IdentitySchema.allowAmbientCredential`) is unchanged by this flag.
2836
2838
  */
2837
2839
  readonly providerSelected?: boolean;
2838
2840
  /** The launching identity's own credential, when its credential block resolved: see `detectAmbientCredential`. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-shim",
3
- "version": "8.17.0",
3
+ "version": "8.18.0",
4
4
  "description": "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
5
5
  "license": "MIT",
6
6
  "author": "Joseph Mearman <joseph@mearman.co.uk>",