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. * * `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 { const userId: Record = { 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?.oauth.identity("anthropic", sessionId)?.accountId; 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) }; }