Последняя активность 1791843245

plugin to make pi.dev / pi.gui send the session as user.id to avoid sharing KV_cache

santiago's Avatar santiago ревизий этого фрагмента 1791843245. К ревизии

Без изменений

santiago's Avatar santiago ревизий этого фрагмента 1791843192. К ревизии

1 file changed, 88 insertions

deepseek_user_id_with_session_name(файл создан)

@@ -0,0 +1,88 @@
1 + /**
2 + * DeepSeek `user_id` — ONE ID PER SESSION, never one shared value.
3 + *
4 + * DeepSeek's Chat Completions API accepts a custom `user_id` body parameter
5 + * (https://api-docs.deepseek.com/quick_start/rate_limit) used for:
6 + * - content-safety review isolation
7 + * - KV-cache isolation
8 + * - scheduling/concurrency isolation
9 + *
10 + * WHY PER SESSION. Those are ISOLATION keys: a single fixed value puts every session that uses it
11 + * into the SAME namespace, which is the opposite of what the parameter is for — three concurrent
12 + * sessions then share whatever is keyed by it. The id is `<prefix>-<session name>` (e.g.
13 + * `santiago-BUILDING-ATOMIQUE-101`), so the value both ISOLATES the session and NAMES it: the
14 + * provider's own usage and rate-limit views show which session spent what, which a constant can
15 + * never do.
16 + *
17 + * THE NAME COMES FROM pi.getSessionName() — the same accessor pi-intercom resolves a session's
18 + * alias through — and the runtime session id is used when a session has no name yet, so the id is
19 + * unique from the first request rather than only after someone renames the session. A rename
20 + * changes the id and therefore starts a fresh cache namespace: correct, and the price of the id
21 + * following the name.
22 + *
23 + * Pi's models.json config surface only supports HTTP `headers`, not body parameters, so this hooks
24 + * the `before_provider_request` event, which can replace the outgoing payload. Scoped to
25 + * `deepseek*` model ids so other providers are never touched.
26 + */
27 +
28 + import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
29 +
30 + const USER_PREFIX = "santiago";
31 +
32 + /** The id is a provider-facing token: DeepSeek's Chat Completions docs allow exactly
33 + * [a-zA-Z0-9\-_], up to 512 characters (stated in the `user_id` row of the API reference), and ask
34 + * that no user privacy information ride it. A session label is a workspace name, not personal data;
35 + * the cap is far below their limit so a long label cannot be rejected on length. */
36 + const MAX_ID = 96;
37 +
38 + /**
39 + * Does a session name look like an ALIAS (the thing this id wants) or like a PROMPT?
40 + *
41 + * An unaliased session's name is its opening prompt — this session's own first `session_info` record
42 + * was the whole handoff text — and DeepSeek's API reference says plainly: "Do not include user
43 + * privacy information in the user_id." So a prompt-shaped name is refused deliberately and the
44 + * runtime session id is used instead; only a short, single-token label becomes the readable half.
45 + */
46 + function looksLikeAlias(name: string): boolean {
47 + return name !== "" && name.length <= 48 && !/\s/.test(name);
48 + }
49 +
50 + /**
51 + * Build the per-session `user_id`. Exported so it can be asserted directly.
52 + *
53 + * NO FALLBACK TO A SHARED VALUE: if neither an alias nor a session id can be read, this returns
54 + * undefined and the caller leaves the payload alone, because the one thing this id must never be
55 + * is the SAME for two sessions.
56 + */
57 + export function deepseekUserId(pi: Pick<ExtensionAPI, "getSessionName">): string | undefined {
58 + const name = pi.getSessionName()?.trim();
59 + const sessionId = process.env.PI_SESSION_ID?.trim();
60 + const alias = name !== undefined && looksLikeAlias(name) ? name : undefined;
61 + const basis = alias ?? (sessionId !== undefined && sessionId !== "" ? sessionId : undefined);
62 + if (basis === undefined) {
63 + return undefined;
64 + }
65 + const clean = basis.replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, MAX_ID);
66 + return clean === "" ? undefined : `${USER_PREFIX}-${clean}`;
67 + }
68 +
69 + export default function (pi: ExtensionAPI) {
70 + pi.on("before_provider_request", (event) => {
71 + const payload = event.payload as Record<string, unknown> | null | undefined;
72 + if (!payload || typeof payload !== "object") {
73 + return;
74 + }
75 +
76 + const model = typeof payload.model === "string" ? payload.model : "";
77 + if (!model.startsWith("deepseek")) {
78 + return;
79 + }
80 +
81 + const userId = deepseekUserId(pi);
82 + if (userId === undefined) {
83 + return;
84 + }
85 +
86 + return { ...payload, user_id: userId };
87 + });
88 + }
Новее Позже