/** * DeepSeek `user_id` — ONE ID PER SESSION, never one shared value. * * DeepSeek's Chat Completions API accepts a custom `user_id` body parameter * (https://api-docs.deepseek.com/quick_start/rate_limit) used for: * - content-safety review isolation * - KV-cache isolation * - scheduling/concurrency isolation * * WHY PER SESSION. Those are ISOLATION keys: a single fixed value puts every session that uses it * into the SAME namespace, which is the opposite of what the parameter is for — three concurrent * sessions then share whatever is keyed by it. The id is `-` (e.g. * `santiago-BUILDING-ATOMIQUE-101`), so the value both ISOLATES the session and NAMES it: the * provider's own usage and rate-limit views show which session spent what, which a constant can * never do. * * THE NAME COMES FROM pi.getSessionName() — the same accessor pi-intercom resolves a session's * alias through — and the runtime session id is used when a session has no name yet, so the id is * unique from the first request rather than only after someone renames the session. A rename * changes the id and therefore starts a fresh cache namespace: correct, and the price of the id * following the name. * * Pi's models.json config surface only supports HTTP `headers`, not body parameters, so this hooks * the `before_provider_request` event, which can replace the outgoing payload. Scoped to * `deepseek*` model ids so other providers are never touched. */ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; const USER_PREFIX = "santiago"; /** The id is a provider-facing token: DeepSeek's Chat Completions docs allow exactly * [a-zA-Z0-9\-_], up to 512 characters (stated in the `user_id` row of the API reference), and ask * that no user privacy information ride it. A session label is a workspace name, not personal data; * the cap is far below their limit so a long label cannot be rejected on length. */ const MAX_ID = 96; /** * Does a session name look like an ALIAS (the thing this id wants) or like a PROMPT? * * An unaliased session's name is its opening prompt — this session's own first `session_info` record * was the whole handoff text — and DeepSeek's API reference says plainly: "Do not include user * privacy information in the user_id." So a prompt-shaped name is refused deliberately and the * runtime session id is used instead; only a short, single-token label becomes the readable half. */ function looksLikeAlias(name: string): boolean { return name !== "" && name.length <= 48 && !/\s/.test(name); } /** * Build the per-session `user_id`. Exported so it can be asserted directly. * * NO FALLBACK TO A SHARED VALUE: if neither an alias nor a session id can be read, this returns * undefined and the caller leaves the payload alone, because the one thing this id must never be * is the SAME for two sessions. */ export function deepseekUserId(pi: Pick): string | undefined { const name = pi.getSessionName()?.trim(); const sessionId = process.env.PI_SESSION_ID?.trim(); const alias = name !== undefined && looksLikeAlias(name) ? name : undefined; const basis = alias ?? (sessionId !== undefined && sessionId !== "" ? sessionId : undefined); if (basis === undefined) { return undefined; } const clean = basis.replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, MAX_ID); return clean === "" ? undefined : `${USER_PREFIX}-${clean}`; } export default function (pi: ExtensionAPI) { pi.on("before_provider_request", (event) => { const payload = event.payload as Record | null | undefined; if (!payload || typeof payload !== "object") { return; } const model = typeof payload.model === "string" ? payload.model : ""; if (!model.startsWith("deepseek")) { return; } const userId = deepseekUserId(pi); if (userId === undefined) { return; } return { ...payload, user_id: userId }; }); }