c5af78d403
The advisor Agent is constructed separately in session-advisors.ts with its own advisorProviderSessionId, but unlike AgentSession it never had a metadata resolver installed. Its outbound requests therefore omitted the metadata.user_id session identity that main and subagent requests carry, so custom Anthropic-compatible proxies saw advisor traffic with no stable session id to route or attribute on. Extract buildSessionMetadata into session/session-metadata.ts and install it as the advisor agent's metadata resolver, scoped to the advisor's own provider session id and resolved live so token refreshes surface the current account_uuid. Fixes #6625
54 lines
2.8 KiB
TypeScript
54 lines
2.8 KiB
TypeScript
import { deriveClaudeDeviceId } from "@oh-my-pi/pi-ai";
|
|
import { getInstallId } from "@oh-my-pi/pi-utils";
|
|
import type { AuthStorage } from "./auth-storage";
|
|
|
|
/**
|
|
* Build the per-request `metadata` payload for the Anthropic provider, shaped
|
|
* like real Claude Code's `getAPIMetadata` output (`{ session_id, account_uuid,
|
|
* device_id }`) so the backend buckets requests under one session and attributes
|
|
* them to the authenticated OAuth account when available. Resolved at request
|
|
* time so token refreshes and login/logout transitions don't strand a stale
|
|
* account UUID in memory. `account_uuid` and `device_id` are omitted for
|
|
* non-Anthropic providers to avoid leaking the user's Claude identity to
|
|
* third-party APIs (including Anthropic-format-compatible proxies such as
|
|
* cloudflare-ai-gateway or gitlab-duo).
|
|
*
|
|
* Installed via `Agent#setMetadataResolver` on the main `AgentSession`, each
|
|
* subagent session, and the separately constructed advisor `Agent` — each with
|
|
* its own provider session id — so Main, subagent, and Advisor requests each
|
|
* expose a distinct, stable provider-facing session identity.
|
|
*
|
|
* `provider` is the target provider string (e.g. `"anthropic"`) and gates the
|
|
* `account_uuid` and `device_id` lookups — only `"anthropic"` requests carry them.
|
|
*
|
|
* `sessionId` is forwarded to the auth-storage session-sticky lookup so that
|
|
* multi-credential setups attribute to the same OAuth account used for the
|
|
* actual API request rather than always picking the first credential.
|
|
*
|
|
* `authStorage` is treated as optional so test fixtures that stub `modelRegistry`
|
|
* without a real storage layer still work; the resolver simply skips the lookup
|
|
* and emits `{ session_id }` alone, matching the no-OAuth-credential path.
|
|
*/
|
|
export function buildSessionMetadata(
|
|
sessionId: string,
|
|
provider: string,
|
|
authStorage: AuthStorage | undefined,
|
|
): Record<string, unknown> {
|
|
const userId: Record<string, string> = { session_id: sessionId };
|
|
// Only look up account_uuid when the request is going to Anthropic. Injecting
|
|
// a Claude OAuth account_uuid into requests bound for other providers (including
|
|
// Anthropic-format-compatible proxies like cloudflare-ai-gateway or gitlab-duo)
|
|
// would leak the user's Anthropic identity to unrelated third-party APIs.
|
|
if (provider === "anthropic") {
|
|
const accountUuid = authStorage?.getOAuthAccountId("anthropic", sessionId);
|
|
if (typeof accountUuid === "string" && accountUuid.length > 0) {
|
|
userId.account_uuid = accountUuid;
|
|
// Claude Code's `device_id` is a stable 64-hex account-scoped install
|
|
// identifier. Include both omp's persistent install id and the Claude
|
|
// account UUID so two accounts on the same install do not share a device.
|
|
userId.device_id = deriveClaudeDeviceId(getInstallId(), accountUuid);
|
|
}
|
|
}
|
|
return { user_id: JSON.stringify(userId) };
|
|
}
|