@sentropic/h2a-runtime 0.91.1 → 0.93.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.
Files changed (108) hide show
  1. package/dist/config.d.ts +2 -0
  2. package/dist/config.d.ts.map +1 -1
  3. package/dist/config.js +16 -3
  4. package/dist/config.js.map +1 -1
  5. package/dist/conv-guard.d.ts +6 -1
  6. package/dist/conv-guard.d.ts.map +1 -1
  7. package/dist/conv-guard.js +33 -8
  8. package/dist/conv-guard.js.map +1 -1
  9. package/dist/enroll.d.ts.map +1 -1
  10. package/dist/enroll.js +12 -2
  11. package/dist/enroll.js.map +1 -1
  12. package/dist/index.d.ts +3 -1
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +819 -155
  15. package/dist/index.js.map +1 -1
  16. package/dist/launch-layout.d.ts +74 -0
  17. package/dist/launch-layout.d.ts.map +1 -0
  18. package/dist/launch-layout.js +324 -0
  19. package/dist/launch-layout.js.map +1 -0
  20. package/dist/llm-gateway-runtime/index.d.ts +6 -0
  21. package/dist/llm-gateway-runtime/index.d.ts.map +1 -1
  22. package/dist/llm-gateway-runtime/index.js +134 -60
  23. package/dist/llm-gateway-runtime/index.js.map +1 -1
  24. package/dist/llm-gateway-runtime/session-ledger.d.ts +38 -55
  25. package/dist/llm-gateway-runtime/session-ledger.d.ts.map +1 -1
  26. package/dist/llm-gateway-runtime/session-ledger.js +102 -250
  27. package/dist/llm-gateway-runtime/session-ledger.js.map +1 -1
  28. package/dist/llm-gateway-runtime/sticky.d.ts +25 -52
  29. package/dist/llm-gateway-runtime/sticky.d.ts.map +1 -1
  30. package/dist/llm-gateway-runtime/sticky.js +36 -605
  31. package/dist/llm-gateway-runtime/sticky.js.map +1 -1
  32. package/dist/llm-mesh.d.ts +19 -72
  33. package/dist/llm-mesh.d.ts.map +1 -1
  34. package/dist/llm-mesh.js +42 -399
  35. package/dist/llm-mesh.js.map +1 -1
  36. package/dist/llm-routing-config.d.ts +28 -0
  37. package/dist/llm-routing-config.d.ts.map +1 -0
  38. package/dist/llm-routing-config.js +100 -0
  39. package/dist/llm-routing-config.js.map +1 -0
  40. package/dist/native-host.d.ts +146 -0
  41. package/dist/native-host.d.ts.map +1 -0
  42. package/dist/native-host.js +456 -0
  43. package/dist/native-host.js.map +1 -0
  44. package/dist/native-terminal/host.d.ts +317 -1
  45. package/dist/native-terminal/host.d.ts.map +1 -1
  46. package/dist/native-terminal/host.js +696 -3
  47. package/dist/native-terminal/host.js.map +1 -1
  48. package/dist/native-terminal/op.d.ts +5 -0
  49. package/dist/native-terminal/op.d.ts.map +1 -0
  50. package/dist/native-terminal/op.js +408 -0
  51. package/dist/native-terminal/op.js.map +1 -0
  52. package/dist/native-terminal/process.d.ts +2 -0
  53. package/dist/native-terminal/process.d.ts.map +1 -1
  54. package/dist/native-terminal/process.js +16 -2
  55. package/dist/native-terminal/process.js.map +1 -1
  56. package/dist/native-terminal/server.d.ts.map +1 -1
  57. package/dist/native-terminal/server.js +6 -2
  58. package/dist/native-terminal/server.js.map +1 -1
  59. package/dist/native-terminal/socket-path.d.ts +8 -0
  60. package/dist/native-terminal/socket-path.d.ts.map +1 -1
  61. package/dist/native-terminal/socket-path.js +31 -1
  62. package/dist/native-terminal/socket-path.js.map +1 -1
  63. package/dist/native-terminal/supervisor.d.ts +13 -0
  64. package/dist/native-terminal/supervisor.d.ts.map +1 -1
  65. package/dist/native-terminal/supervisor.js +45 -0
  66. package/dist/native-terminal/supervisor.js.map +1 -1
  67. package/dist/pty.d.ts +23 -0
  68. package/dist/pty.d.ts.map +1 -1
  69. package/dist/pty.js +35 -0
  70. package/dist/pty.js.map +1 -1
  71. package/dist/readiness.d.ts +7 -0
  72. package/dist/readiness.d.ts.map +1 -1
  73. package/dist/readiness.js +12 -9
  74. package/dist/readiness.js.map +1 -1
  75. package/dist/registry.d.ts +324 -2
  76. package/dist/registry.d.ts.map +1 -1
  77. package/dist/registry.js +659 -20
  78. package/dist/registry.js.map +1 -1
  79. package/dist/restore.d.ts +91 -32
  80. package/dist/restore.d.ts.map +1 -1
  81. package/dist/restore.js +226 -186
  82. package/dist/restore.js.map +1 -1
  83. package/dist/status-projection.d.ts.map +1 -1
  84. package/dist/tmux.d.ts +9 -6
  85. package/dist/tmux.d.ts.map +1 -1
  86. package/dist/tmux.js +34 -8
  87. package/dist/tmux.js.map +1 -1
  88. package/package.json +3 -3
  89. package/dist/llm-gateway-runtime/accounts.d.ts +0 -62
  90. package/dist/llm-gateway-runtime/accounts.d.ts.map +0 -1
  91. package/dist/llm-gateway-runtime/accounts.js +0 -323
  92. package/dist/llm-gateway-runtime/accounts.js.map +0 -1
  93. package/dist/llm-gateway-runtime/model-catalog.d.ts +0 -44
  94. package/dist/llm-gateway-runtime/model-catalog.d.ts.map +0 -1
  95. package/dist/llm-gateway-runtime/model-catalog.js +0 -191
  96. package/dist/llm-gateway-runtime/model-catalog.js.map +0 -1
  97. package/dist/llm-gateway-runtime/proxy-anthropic.d.ts +0 -3
  98. package/dist/llm-gateway-runtime/proxy-anthropic.d.ts.map +0 -1
  99. package/dist/llm-gateway-runtime/proxy-anthropic.js +0 -318
  100. package/dist/llm-gateway-runtime/proxy-anthropic.js.map +0 -1
  101. package/dist/llm-gateway-runtime/proxy-cloud-code.d.ts +0 -11
  102. package/dist/llm-gateway-runtime/proxy-cloud-code.d.ts.map +0 -1
  103. package/dist/llm-gateway-runtime/proxy-cloud-code.js +0 -215
  104. package/dist/llm-gateway-runtime/proxy-cloud-code.js.map +0 -1
  105. package/dist/llm-gateway-runtime/proxy-openai.d.ts +0 -118
  106. package/dist/llm-gateway-runtime/proxy-openai.d.ts.map +0 -1
  107. package/dist/llm-gateway-runtime/proxy-openai.js +0 -1305
  108. package/dist/llm-gateway-runtime/proxy-openai.js.map +0 -1
@@ -1,620 +1,51 @@
1
- import { closeSync, chmodSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from "node:fs";
2
- import { dirname } from "node:path";
3
- import { createHmac, timingSafeEqual } from "node:crypto";
4
- import { accountSupportsRoute, effectiveAccountAuthType, findAccount, selectAccount, selectAccountForRoute, upstreamTransportForAccount, } from "./accounts.js";
5
- import { resolveModelRoute, routeForProvider, } from "./model-catalog.js";
6
- import { upsertSessionLedger } from "./session-ledger.js";
7
- // ─── k8s ConfigMap client ────────────────────────────────────────────────────
8
- const SA_TOKEN_PATH = "/var/run/secrets/kubernetes.io/serviceaccount/token";
9
- const K8S_HOST = process.env.KUBERNETES_SERVICE_HOST ?? "kubernetes.default.svc.cluster.local";
10
- const K8S_PORT = process.env.KUBERNETES_SERVICE_PORT ?? "443";
11
- const NAMESPACE = process.env.POD_NAMESPACE ?? "sentropic-remote";
12
- const CONFIGMAP_NAME = process.env.STICKY_CONFIGMAP ?? "llm-gateway-sticky";
13
- const LOCAL_STICKY_FILE = process.env.LLM_GATEWAY_STICKY_FILE;
14
- function saToken() {
15
- try {
16
- return readFileSync(SA_TOKEN_PATH, "utf-8").trim();
17
- }
18
- catch {
19
- // Fallback for local dev/testing
20
- return process.env.K8S_TOKEN ?? "";
21
- }
22
- }
23
- function cmUrl() {
24
- return `https://${K8S_HOST}:${K8S_PORT}/api/v1/namespaces/${NAMESPACE}/configmaps/${CONFIGMAP_NAME}`;
25
- }
26
- function readLocalSticky() {
27
- if (!LOCAL_STICKY_FILE)
28
- return {};
29
- try {
30
- return JSON.parse(readFileSync(LOCAL_STICKY_FILE, "utf8"));
31
- }
32
- catch {
33
- return {};
34
- }
35
- }
36
- function writeLocalSticky(data) {
37
- if (!LOCAL_STICKY_FILE)
38
- return;
39
- mkdirSync(dirname(LOCAL_STICKY_FILE), { recursive: true });
40
- const tmp = `${LOCAL_STICKY_FILE}.tmp.${process.pid}`;
41
- writeFileSync(tmp, JSON.stringify(data, null, 2) + "\n", { mode: 0o600 });
42
- chmodSync(tmp, 0o600);
43
- renameSync(tmp, LOCAL_STICKY_FILE);
44
- }
45
- async function withLocalStickyLock(operation) {
46
- if (!LOCAL_STICKY_FILE)
47
- return operation();
48
- const lockPath = `${LOCAL_STICKY_FILE}.lock`;
49
- let fd;
50
- for (let attempt = 0; attempt < 100; attempt++) {
51
- try {
52
- fd = openSync(lockPath, "wx", 0o600);
53
- break;
54
- }
55
- catch (err) {
56
- if (err.code !== "EEXIST")
57
- throw err;
58
- await new Promise((resolveDelay) => setTimeout(resolveDelay, 10));
59
- }
60
- }
61
- if (fd === undefined)
62
- throw new Error("timed out acquiring sticky-file lock");
63
- try {
64
- return operation();
65
- }
66
- finally {
67
- closeSync(fd);
68
- try {
69
- unlinkSync(lockPath);
70
- }
71
- catch {
72
- // The lock was already removed; the guarded operation has still ended.
73
- }
74
- }
75
- }
76
- function parsePersistedTransport(value) {
77
- return value === "anthropic-messages" ||
78
- value === "codex-responses" ||
79
- value === "cloud-code" ||
80
- value === "openai-chat-completions"
81
- ? value
82
- : undefined;
83
- }
84
- function isCloudCodeTransportConstraints(value) {
85
- if (!value || typeof value !== "object")
86
- return false;
87
- const candidate = value;
88
- return candidate.transportProviderId === "cloud-code" &&
89
- typeof candidate.accountConstraints?.targetProviderId === "string" &&
90
- (candidate.accountConstraints.modelId === undefined ||
91
- typeof candidate.accountConstraints.modelId === "string") &&
92
- (candidate.accountConstraints.workspaceId === undefined ||
93
- typeof candidate.accountConstraints.workspaceId === "string") &&
94
- (candidate.accountConstraints.affinityKey === undefined ||
95
- typeof candidate.accountConstraints.affinityKey === "string");
96
- }
97
- function decodeStickyBinding(value) {
98
- if (!value.startsWith("{"))
99
- return { accountId: value };
100
- try {
101
- const parsed = JSON.parse(value);
102
- if (isCloudCodeTransportConstraints(parsed.transportConstraints)) {
1
+ import { randomBytes } from "node:crypto";
2
+ const sessionsByToken = new Map();
3
+ const tokensBySession = new Map();
4
+ const newGatewayToken = () => `gw-v2-${randomBytes(32).toString("base64url")}`;
5
+ export async function acquireSession(sessionId, options = {}) {
6
+ const cleanSessionId = sessionId.trim();
7
+ if (!cleanSessionId)
8
+ throw new Error("sessionId (string) required");
9
+ const existingToken = tokensBySession.get(cleanSessionId);
10
+ if (existingToken) {
11
+ const existing = sessionsByToken.get(existingToken);
12
+ if (existing) {
103
13
  return {
104
- transportConstraints: parsed.transportConstraints,
105
- requiredTransport: "cloud-code",
14
+ gatewayToken: existing.gatewayToken,
15
+ sessionId: existing.sessionId,
16
+ clientSessionId: existing.clientSessionId,
106
17
  };
107
18
  }
108
- const requiredTransport = parsePersistedTransport(parsed.requiredTransport);
109
- if (typeof parsed.accountId !== "string" || !requiredTransport) {
110
- return undefined;
111
- }
112
- return { accountId: parsed.accountId, requiredTransport };
113
- }
114
- catch {
115
- return undefined;
116
- }
117
- }
118
- function encodeStickyBinding(accountId, requiredTransport, transportConstraints) {
119
- if (transportConstraints) {
120
- return JSON.stringify({ transportConstraints });
121
- }
122
- if (!accountId)
123
- throw new Error("session binding requires an account id");
124
- return requiredTransport
125
- ? JSON.stringify({ accountId, requiredTransport })
126
- : accountId;
127
- }
128
- async function readStickySnapshot() {
129
- if (LOCAL_STICKY_FILE)
130
- return { data: readLocalSticky(), hasData: true };
131
- const token = saToken();
132
- if (!token)
133
- return { data: readLocalSticky(), hasData: true };
134
- const resp = await fetch(cmUrl(), {
135
- headers: { Authorization: `Bearer ${token}` },
136
- });
137
- if (resp.status === 404)
138
- return { data: {}, hasData: false };
139
- if (!resp.ok)
140
- throw new Error(`sticky ConfigMap read failed: ${resp.status}`);
141
- const cm = (await resp.json());
142
- return {
143
- data: cm.data ?? {},
144
- hasData: cm.data !== undefined,
145
- ...(cm.metadata?.resourceVersion
146
- ? { resourceVersion: cm.metadata.resourceVersion }
147
- : {}),
148
- };
149
- }
150
- export async function readSticky() {
151
- return (await readStickySnapshot()).data;
152
- }
153
- export async function writeSticky(sessionId, accountId, requiredTransport, transportConstraints) {
154
- const binding = encodeStickyBinding(accountId, requiredTransport, transportConstraints);
155
- if (LOCAL_STICKY_FILE) {
156
- await withLocalStickyLock(() => {
157
- writeLocalSticky({ ...readLocalSticky(), [sessionId]: binding });
158
- });
159
- return;
160
- }
161
- const token = saToken();
162
- if (!token) {
163
- await withLocalStickyLock(() => {
164
- writeLocalSticky({ ...readLocalSticky(), [sessionId]: binding });
165
- });
166
- return;
167
- }
168
- const patch = {
169
- apiVersion: "v1",
170
- kind: "ConfigMap",
171
- metadata: { name: CONFIGMAP_NAME, namespace: NAMESPACE },
172
- data: { [sessionId]: binding },
173
- };
174
- const resp = await fetch(cmUrl(), {
175
- method: "PATCH",
176
- headers: {
177
- Authorization: `Bearer ${token}`,
178
- "Content-Type": "application/strategic-merge-patch+json",
179
- },
180
- body: JSON.stringify(patch),
181
- });
182
- if (!resp.ok)
183
- throw new Error(`sticky ConfigMap patch failed: ${resp.status}`);
184
- }
185
- async function compareAndSetStickyBinding(sessionId, accountId, requiredTransport, expected, transportConstraints) {
186
- const binding = encodeStickyBinding(accountId, requiredTransport, transportConstraints);
187
- if (LOCAL_STICKY_FILE) {
188
- await withLocalStickyLock(() => {
189
- const current = readLocalSticky();
190
- if (current[sessionId] !== expected.data[sessionId]) {
191
- throw new Error("session binding changed concurrently");
192
- }
193
- writeLocalSticky({ ...current, [sessionId]: binding });
194
- if (readLocalSticky()[sessionId] !== binding) {
195
- throw new Error("session transport claim was not persisted");
196
- }
197
- });
198
- return;
199
- }
200
- const token = saToken();
201
- if (!token) {
202
- if (requiredTransport) {
203
- throw new Error("a durable sticky backend is required for constrained sessions");
204
- }
205
- await writeSticky(sessionId, accountId, requiredTransport, transportConstraints);
206
- return;
207
- }
208
- const escapedSessionId = sessionId.replaceAll("~", "~0").replaceAll("/", "~1");
209
- let snapshot = expected;
210
- for (let attempt = 0; attempt < 4; attempt++) {
211
- if (!snapshot.resourceVersion) {
212
- throw new Error("sticky ConfigMap resourceVersion is required for claim CAS");
213
- }
214
- const resp = await fetch(cmUrl(), {
215
- method: "PATCH",
216
- headers: {
217
- Authorization: `Bearer ${token}`,
218
- "Content-Type": "application/json-patch+json",
219
- },
220
- body: JSON.stringify([
221
- {
222
- op: "test",
223
- path: "/metadata/resourceVersion",
224
- value: snapshot.resourceVersion,
225
- },
226
- snapshot.hasData
227
- ? { op: "add", path: `/data/${escapedSessionId}`, value: binding }
228
- : { op: "add", path: "/data", value: { [sessionId]: binding } },
229
- ]),
230
- });
231
- const current = await readStickySnapshot();
232
- if (current.data[sessionId] === binding)
233
- return;
234
- if (resp.ok) {
235
- throw new Error("session transport claim was not persisted");
236
- }
237
- if (current.data[sessionId] !== snapshot.data[sessionId]) {
238
- throw new Error(`session binding CAS failed (${resp.status})`);
239
- }
240
- snapshot = current;
241
- }
242
- throw new Error("session binding CAS retry limit exceeded");
243
- }
244
- const _sessions = new Map();
245
- const _acquisitionLocks = new Map();
246
- const TOKEN_PREFIX = "gw-v1-";
247
- async function withSessionAcquisitionLock(sessionId, operation) {
248
- const previous = _acquisitionLocks.get(sessionId) ?? Promise.resolve();
249
- let release;
250
- const current = new Promise((resolveLock) => {
251
- release = resolveLock;
252
- });
253
- _acquisitionLocks.set(sessionId, current);
254
- await previous;
255
- try {
256
- return await operation();
257
- }
258
- finally {
259
- release();
260
- if (_acquisitionLocks.get(sessionId) === current) {
261
- _acquisitionLocks.delete(sessionId);
262
- }
263
- }
264
- }
265
- function tokenSeed() {
266
- const seed = process.env.LLM_GATEWAY_TOKEN_SEED;
267
- if (!seed)
268
- throw new Error("LLM_GATEWAY_TOKEN_SEED env var is required");
269
- return seed;
270
- }
271
- function b64url(value) {
272
- return Buffer.from(value, "utf8").toString("base64url");
273
- }
274
- function unb64url(value) {
275
- try {
276
- return Buffer.from(value, "base64url").toString("utf8");
277
- }
278
- catch {
279
- return null;
280
- }
281
- }
282
- function macFor(sessionId) {
283
- return createHmac("sha256", tokenSeed())
284
- .update("gw-token-v1\0")
285
- .update(sessionId)
286
- .digest("base64url");
287
- }
288
- export function gatewayTokenForSession(sessionId) {
289
- return `${TOKEN_PREFIX}${b64url(sessionId)}.${macFor(sessionId)}`;
290
- }
291
- function sessionIdFromGatewayToken(gatewayToken) {
292
- if (!gatewayToken.startsWith(TOKEN_PREFIX))
293
- return null;
294
- const rest = gatewayToken.slice(TOKEN_PREFIX.length);
295
- const dot = rest.indexOf(".");
296
- if (dot <= 0)
297
- return null;
298
- const encodedSessionId = rest.slice(0, dot);
299
- const suppliedMac = rest.slice(dot + 1);
300
- const sessionId = unb64url(encodedSessionId);
301
- if (!sessionId)
302
- return null;
303
- const expectedMac = macFor(sessionId);
304
- const a = Buffer.from(suppliedMac);
305
- const b = Buffer.from(expectedMac);
306
- if (a.length !== b.length || !timingSafeEqual(a, b))
307
- return null;
308
- return sessionId;
309
- }
310
- export function sessionCount() {
311
- return _sessions.size;
312
- }
313
- export async function lookupToken(gatewayToken) {
314
- const cached = _sessions.get(gatewayToken);
315
- const sessionId = sessionIdFromGatewayToken(gatewayToken);
316
- if (!sessionId)
317
- return undefined;
318
- const existing = await readSticky();
319
- const rawBinding = existing[sessionId];
320
- const binding = rawBinding === undefined ? undefined : decodeStickyBinding(rawBinding);
321
- if (rawBinding !== undefined && !binding)
322
- return undefined;
323
- if (!binding) {
324
- return cached &&
325
- (cached.transportConstraints ||
326
- (!cached.requiredTransport && !LOCAL_STICKY_FILE && !saToken()))
327
- ? cached
328
- : undefined;
329
- }
330
- if (binding.transportConstraints) {
331
- const entry = {
332
- sessionId,
333
- gatewayToken,
334
- provider: "cloud-code",
335
- transport: "cloud-code",
336
- requiredTransport: "cloud-code",
337
- transportConstraints: binding.transportConstraints,
338
- };
339
- _sessions.set(gatewayToken, entry);
340
- return entry;
341
- }
342
- if (!binding.accountId)
343
- return undefined;
344
- const account = findAccount(binding.accountId);
345
- if (!account)
346
- return undefined;
347
- if (binding?.requiredTransport &&
348
- upstreamTransportForAccount(account) !== binding.requiredTransport) {
349
- return undefined;
350
19
  }
20
+ const gatewayToken = newGatewayToken();
351
21
  const entry = {
352
- sessionId,
22
+ sessionId: cleanSessionId,
353
23
  gatewayToken,
354
- accountId: account.id,
355
- provider: account.provider,
356
- authType: effectiveAccountAuthType(account),
357
- transport: upstreamTransportForAccount(account),
358
- ...(binding?.requiredTransport
359
- ? { requiredTransport: binding.requiredTransport }
360
- : {}),
24
+ clientSessionId: options.clientSessionId?.trim() || cleanSessionId,
25
+ ...(options.workspaceId?.trim() ? { workspaceId: options.workspaceId.trim() } : {}),
26
+ ...(options.profile?.trim() ? { profile: options.profile.trim() } : {}),
27
+ createdAt: new Date().toISOString(),
361
28
  };
362
- _sessions.set(gatewayToken, entry);
363
- upsertSessionLedger({
364
- gatewaySessionId: sessionId,
365
- account,
366
- });
367
- return entry;
368
- }
369
- export async function rebindGatewaySession(gatewayToken, account, route) {
370
- const sessionId = sessionIdFromGatewayToken(gatewayToken);
371
- if (!sessionId)
372
- return undefined;
373
- const cached = _sessions.get(gatewayToken);
374
- const snapshot = await readStickySnapshot();
375
- const rawBinding = snapshot.data[sessionId];
376
- const persistedBinding = rawBinding === undefined ? undefined : decodeStickyBinding(rawBinding);
377
- if (rawBinding !== undefined && !persistedBinding) {
378
- throw new Error("invalid persisted session binding");
379
- }
380
- if (persistedBinding &&
381
- cached &&
382
- persistedBinding.accountId !== cached.accountId) {
383
- throw new Error("cannot rebind a stale gateway session");
384
- }
385
- if (persistedBinding?.requiredTransport &&
386
- cached?.requiredTransport &&
387
- persistedBinding.requiredTransport !== cached.requiredTransport) {
388
- throw new Error("cached and persisted session transport claims conflict");
389
- }
390
- const targetTransport = upstreamTransportForAccount(account);
391
- const requiredTransport = targetTransport !== "unknown"
392
- ? targetTransport
393
- : (persistedBinding?.requiredTransport ?? cached?.requiredTransport);
394
- await compareAndSetStickyBinding(sessionId, account.id, requiredTransport, snapshot);
395
- const entry = {
396
- sessionId,
29
+ sessionsByToken.set(gatewayToken, entry);
30
+ tokensBySession.set(cleanSessionId, gatewayToken);
31
+ return {
397
32
  gatewayToken,
398
- accountId: account.id,
399
- provider: account.provider,
400
- authType: effectiveAccountAuthType(account),
401
- transport: upstreamTransportForAccount(account),
402
- ...(requiredTransport ? { requiredTransport } : {}),
33
+ sessionId: entry.sessionId,
34
+ clientSessionId: entry.clientSessionId,
403
35
  };
404
- _sessions.set(gatewayToken, entry);
405
- upsertSessionLedger({
406
- gatewaySessionId: sessionId,
407
- account,
408
- ...(route ? { route } : {}),
409
- });
410
- return entry;
411
36
  }
412
- const REASONING_EFFORTS = new Set(["low", "medium", "high", "xhigh"]);
413
- const REQUIRED_TRANSPORTS = new Set([
414
- "anthropic-messages",
415
- "codex-responses",
416
- "cloud-code",
417
- "openai-chat-completions",
418
- ]);
419
- function requiredTransportFromOptions(options) {
420
- if (!options.requiredTransport)
421
- return undefined;
422
- if (!REQUIRED_TRANSPORTS.has(options.requiredTransport)) {
423
- throw new Error(`unsupported required transport: ${options.requiredTransport}`);
424
- }
425
- return options.requiredTransport;
37
+ export async function lookupToken(gatewayToken) {
38
+ return sessionsByToken.get(gatewayToken);
426
39
  }
427
- function routeFromOptions(options) {
428
- if (options.model)
429
- return resolveModelRoute(options.model);
430
- if (options.provider)
431
- return routeForProvider(options.provider);
432
- return undefined;
40
+ export function lookupSessionById(sessionId) {
41
+ const token = tokensBySession.get(sessionId);
42
+ return token ? sessionsByToken.get(token) : undefined;
433
43
  }
434
- /**
435
- * Acquire or re-acquire a session for a given sessionId.
436
- * Idempotent: the same sessionId always maps to the same accountId and token.
437
- * The token is derived from LLM_GATEWAY_TOKEN_SEED + sessionId, never persisted.
438
- */
439
- export async function acquireSession(sessionId, options = {}) {
440
- return withSessionAcquisitionLock(sessionId, () => acquireSessionUnlocked(sessionId, options));
44
+ export function sessionCount() {
45
+ return sessionsByToken.size;
441
46
  }
442
- async function acquireSessionUnlocked(sessionId, options = {}) {
443
- const route = routeFromOptions(options);
444
- if (options.model && !route)
445
- throw new Error(`unsupported model: ${options.model}`);
446
- if (options.reasoningEffort && !REASONING_EFFORTS.has(options.reasoningEffort)) {
447
- throw new Error(`unsupported reasoning effort: ${options.reasoningEffort}`);
448
- }
449
- const requestedTransport = requiredTransportFromOptions(options);
450
- if (requestedTransport && !route) {
451
- throw new Error("requiredTransport requires a model or provider route");
452
- }
453
- const cloudCodeRoute = requestedTransport === "cloud-code" ||
454
- route?.transportProviderId === "cloud-code" ||
455
- options.provider === "cloud-code";
456
- if (cloudCodeRoute) {
457
- if (requestedTransport && requestedTransport !== "cloud-code") {
458
- throw new Error("Cloud Code routes require the cloud-code transport");
459
- }
460
- const snapshot = await readStickySnapshot();
461
- const rawBinding = snapshot.data[sessionId];
462
- const binding = rawBinding === undefined ? undefined : decodeStickyBinding(rawBinding);
463
- if (rawBinding !== undefined && !binding) {
464
- throw new Error("invalid persisted session binding");
465
- }
466
- if (binding?.accountId) {
467
- throw new Error("session is already bound to a non-Cloud-Code account");
468
- }
469
- const transportConstraints = binding?.transportConstraints ?? {
470
- transportProviderId: "cloud-code",
471
- accountConstraints: {
472
- targetProviderId: route?.providerId ?? "google",
473
- ...(route?.upstreamModel ? { modelId: route.upstreamModel } : {}),
474
- ...(options.workspaceId ? { workspaceId: options.workspaceId } : {}),
475
- affinityKey: options.clientSessionId ?? sessionId,
476
- },
477
- };
478
- if (!binding?.transportConstraints) {
479
- await compareAndSetStickyBinding(sessionId, undefined, "cloud-code", snapshot, transportConstraints);
480
- }
481
- const gatewayToken = gatewayTokenForSession(sessionId);
482
- _sessions.set(gatewayToken, {
483
- sessionId,
484
- gatewayToken,
485
- provider: "cloud-code",
486
- transport: "cloud-code",
487
- requiredTransport: "cloud-code",
488
- transportConstraints,
489
- });
490
- return {
491
- gatewayToken,
492
- accountId: "cloud-code",
493
- ...(route?.requestedModel ? { requestedModel: route.requestedModel } : {}),
494
- ...(route?.catalogModelId ? { modelId: route.catalogModelId } : {}),
495
- ...(route?.upstreamModel ? { upstreamModel: route.upstreamModel } : {}),
496
- ...(options.reasoningEffort
497
- ? { reasoningEffort: options.reasoningEffort }
498
- : {}),
499
- provider: "cloud-code",
500
- authType: "bearer",
501
- transport: "cloud-code",
502
- ...(route?.routingPolicy ? { routePolicy: route.routingPolicy } : {}),
503
- ...(route?.routeReason ? { routeReason: route.routeReason } : {}),
504
- };
505
- }
506
- // Check ConfigMap for existing sticky binding
507
- const snapshot = await readStickySnapshot();
508
- const existing = snapshot.data;
509
- const rawBinding = existing[sessionId];
510
- let persistedBinding = rawBinding === undefined ? undefined : decodeStickyBinding(rawBinding);
511
- if (rawBinding !== undefined && !persistedBinding) {
512
- throw new Error("invalid persisted session binding");
513
- }
514
- const cached = _sessions.get(gatewayTokenForSession(sessionId));
515
- const cachedBinding = cached?.accountId
516
- ? {
517
- accountId: cached.accountId,
518
- ...(cached.requiredTransport
519
- ? { requiredTransport: cached.requiredTransport }
520
- : {}),
521
- }
522
- : undefined;
523
- if (persistedBinding?.accountId && route) {
524
- const boundAcc = findAccount(persistedBinding.accountId);
525
- if (!boundAcc || !accountSupportsRoute(boundAcc, route)) {
526
- persistedBinding = undefined;
527
- }
528
- }
529
- if (persistedBinding &&
530
- cachedBinding?.requiredTransport &&
531
- persistedBinding.requiredTransport &&
532
- cachedBinding.requiredTransport !== persistedBinding.requiredTransport) {
533
- throw new Error("cached and persisted session transport claims conflict");
534
- }
535
- const binding = persistedBinding?.accountId
536
- ? {
537
- accountId: persistedBinding.accountId,
538
- ...(persistedBinding.requiredTransport ?? cachedBinding?.requiredTransport
539
- ? {
540
- requiredTransport: persistedBinding.requiredTransport ??
541
- cachedBinding.requiredTransport,
542
- }
543
- : {}),
544
- }
545
- : cachedBinding;
546
- if (requestedTransport &&
547
- binding?.requiredTransport &&
548
- requestedTransport !== binding.requiredTransport) {
549
- throw new Error(`session transport constraint is already ${binding.requiredTransport}`);
550
- }
551
- const requiredTransport = binding?.requiredTransport ?? requestedTransport;
552
- if (requiredTransport && !route) {
553
- throw new Error("a constrained session requires a model or provider route");
554
- }
555
- let account;
556
- let bindingChanged = false;
557
- const boundId = binding?.accountId;
558
- if (boundId !== undefined) {
559
- const found = findAccount(boundId);
560
- if (found &&
561
- (!route || accountSupportsRoute(found, route)) &&
562
- (!requiredTransport ||
563
- upstreamTransportForAccount(found) === requiredTransport)) {
564
- account = found;
565
- }
566
- else {
567
- account = route
568
- ? selectAccountForRoute(route, requiredTransport ? { requiredTransport } : {})
569
- : selectAccount();
570
- bindingChanged = true;
571
- }
572
- }
573
- else {
574
- account = route
575
- ? selectAccountForRoute(route, requiredTransport ? { requiredTransport } : {})
576
- : selectAccount();
577
- bindingChanged = true;
578
- }
579
- if (requiredTransport &&
580
- persistedBinding?.requiredTransport !== requiredTransport) {
581
- await compareAndSetStickyBinding(sessionId, account.id, requiredTransport, snapshot);
582
- }
583
- else if (bindingChanged) {
584
- await compareAndSetStickyBinding(sessionId, account.id, requiredTransport, snapshot);
585
- }
586
- const gatewayToken = gatewayTokenForSession(sessionId);
587
- _sessions.set(gatewayToken, {
588
- sessionId,
589
- gatewayToken,
590
- accountId: account.id,
591
- provider: account.provider,
592
- authType: effectiveAccountAuthType(account),
593
- transport: upstreamTransportForAccount(account),
594
- ...(requiredTransport ? { requiredTransport } : {}),
595
- });
596
- upsertSessionLedger({
597
- gatewaySessionId: sessionId,
598
- clientSessionId: options.clientSessionId ?? sessionId,
599
- ...(options.workspaceId ? { workspaceId: options.workspaceId } : {}),
600
- ...(options.profile ? { profile: options.profile } : {}),
601
- account,
602
- ...(route ? { route } : {}),
603
- });
604
- return {
605
- gatewayToken,
606
- accountId: account.id,
607
- ...(route?.requestedModel ? { requestedModel: route.requestedModel } : {}),
608
- ...(route?.catalogModelId ? { modelId: route.catalogModelId } : {}),
609
- ...(route?.upstreamModel ? { upstreamModel: route.upstreamModel } : {}),
610
- ...(options.reasoningEffort
611
- ? { reasoningEffort: options.reasoningEffort }
612
- : {}),
613
- provider: account.provider,
614
- authType: effectiveAccountAuthType(account),
615
- transport: upstreamTransportForAccount(account),
616
- ...(route?.routingPolicy ? { routePolicy: route.routingPolicy } : {}),
617
- ...(route?.routeReason ? { routeReason: route.routeReason } : {}),
618
- };
47
+ export function resetSessions() {
48
+ sessionsByToken.clear();
49
+ tokensBySession.clear();
619
50
  }
620
51
  //# sourceMappingURL=sticky.js.map