threadwire 0.1.8 → 0.1.9

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.
@@ -12,10 +12,12 @@ const OWNER = "threadwire-isolated-runtime"
12
12
  * Authenticated registry of both public session aliases and private volume
13
13
  * ownership. Lineage ownership deliberately outlives an expired alias until
14
14
  * Docker confirms that the volume was deleted.
15
- * @param {{file: string, key: string, namespace: string, now?: () => number, random?: (size: number) => Buffer}} options
15
+ * @param {{file: string, key: string, namespace: string, provider?: "codex" | "kimi", now?: () => number, random?: (size: number) => Buffer}} options
16
16
  */
17
17
  export async function openStateRegistry(options) {
18
- if (!/^\/.+/u.test(options.file) || options.key.length < 32 || !/^[A-Za-z0-9_.-]{1,64}$/u.test(options.namespace)) {
18
+ const provider = options.provider ?? "codex"
19
+ if (!/^\/.+/u.test(options.file) || options.key.length < 32 || !/^[A-Za-z0-9_.-]{1,64}$/u.test(options.namespace)
20
+ || (provider !== "codex" && provider !== "kimi")) {
19
21
  throw new Error("Invalid isolated state configuration")
20
22
  }
21
23
  const now = options.now ?? Date.now
@@ -31,28 +33,35 @@ export async function openStateRegistry(options) {
31
33
  try {
32
34
  const document = JSON.parse(await readFile(options.file, "utf8"))
33
35
  const payload = JSON.stringify(document.payload)
34
- if (![1, 2, 3].includes(document.version) || typeof document.mac !== "string" || !equalMac(document.mac, mac(options.key, payload))) throw new Error()
36
+ if (![1, 2, 3, 4].includes(document.version) || typeof document.mac !== "string" || !equalMac(document.mac, mac(options.key, payload))) throw new Error()
37
+ if (document.version < 4 && provider !== "codex") throw new Error()
35
38
  if (!Array.isArray(document.payload?.sessions)) throw new Error()
36
- for (const entry of document.payload.sessions) {
37
- if (!validSession(entry)) throw new Error()
39
+ for (const rawEntry of document.payload.sessions) {
40
+ if (!validSession(rawEntry, document.version === 4)) throw new Error()
41
+ const entry = document.version === 4 ? rawEntry : {...rawEntry, provider: "codex"}
42
+ if (entry.provider !== provider) throw new Error()
38
43
  sessions.set(entry.sessionId, without(entry, "sessionId"))
39
44
  lineages.set(entry.lineage, lineageFromSession(options.namespace, entry))
40
45
  }
41
46
  if (document.version === 2) {
42
47
  if (!Array.isArray(document.payload.lineages)) throw new Error()
43
48
  for (const entry of document.payload.lineages) {
44
- if (!validLineage(entry, options.namespace)) throw new Error()
45
- lineages.set(entry.lineage, entry)
49
+ if (!validLineage(entry, options.namespace, false)) throw new Error()
50
+ lineages.set(entry.lineage, {...entry, provider: "codex"})
46
51
  }
47
52
  }
48
- if (document.version === 3) {
53
+ if (document.version === 3 || document.version === 4) {
49
54
  if (!Array.isArray(document.payload.lineages) || !Array.isArray(document.payload.runs)) throw new Error()
50
- for (const entry of document.payload.lineages) {
51
- if (!validLineage(entry, options.namespace)) throw new Error()
55
+ for (const rawEntry of document.payload.lineages) {
56
+ if (!validLineage(rawEntry, options.namespace, document.version === 4)) throw new Error()
57
+ const entry = document.version === 4 ? rawEntry : {...rawEntry, provider: "codex"}
58
+ if (entry.provider !== provider) throw new Error()
52
59
  lineages.set(entry.lineage, entry)
53
60
  }
54
- for (const entry of document.payload.runs) {
55
- canonicalRunIdentity(entry)
61
+ for (const rawEntry of document.payload.runs) {
62
+ canonicalRunIdentity(rawEntry)
63
+ const entry = document.version === 4 ? rawEntry : {...rawEntry, provider: "codex", sealVersion: 3}
64
+ if (entry.provider !== provider) throw new Error()
56
65
  runs.set(entry.run, entry)
57
66
  }
58
67
  }
@@ -69,7 +78,7 @@ export async function openStateRegistry(options) {
69
78
  runs: [...runState.values()]
70
79
  }
71
80
  const serializedPayload = JSON.stringify(payload)
72
- const document = JSON.stringify({version: 3, payload, mac: mac(options.key, serializedPayload)})
81
+ const document = JSON.stringify({version: 4, payload, mac: mac(options.key, serializedPayload)})
73
82
  const temporary = join(dirname(options.file), `.sessions-${random(12).toString("hex")}.tmp`)
74
83
  let handle
75
84
  try {
@@ -104,17 +113,33 @@ export async function openStateRegistry(options) {
104
113
  return result
105
114
  }
106
115
  const allocationRecord = (allocation) => ({
116
+ provider: allocation.provider,
107
117
  task: allocation.task,
108
118
  lineage: allocation.lineage,
109
119
  volume: allocation.volume,
110
120
  labels: allocation.labels
111
121
  })
122
+ const ownsRunSeal = (identity, seal, legacyLabels = false) => {
123
+ const persisted = runs.get(identity.run)
124
+ if (!persisted || typeof seal !== "string") return false
125
+ try {
126
+ if (canonicalRunIdentity(persisted) !== canonicalRunIdentity(identity)) return false
127
+ const legacySeal = persisted.sealVersion === 3
128
+ if (legacyLabels !== legacySeal) return false
129
+ const canonical = legacySeal ? legacyCanonicalRunIdentity(persisted) : canonicalRunIdentity(persisted)
130
+ return equalMac(seal, mac(options.key, canonical))
131
+ } catch {
132
+ return false
133
+ }
134
+ }
112
135
 
113
136
  return {
114
137
  namespace: options.namespace,
138
+ provider,
115
139
  allocate(task) {
116
140
  const lineage = random(24).toString("hex")
117
141
  return {
142
+ provider,
118
143
  task,
119
144
  lineage,
120
145
  volume: `threadwire-state-${options.namespace}-${lineage}`,
@@ -122,22 +147,29 @@ export async function openStateRegistry(options) {
122
147
  }
123
148
  },
124
149
  sealRun(identity) {
150
+ if (identity.sealVersion !== undefined) throw new Error("Invalid isolated run identity")
125
151
  return mac(options.key, canonicalRunIdentity(identity))
126
152
  },
127
- ownsRun(identity, seal) {
153
+ ownsRunSeal(identity, seal, legacyLabels = false) {
154
+ return ownsRunSeal(identity, seal, legacyLabels)
155
+ },
156
+ ownsRun(identity, seal, legacyLabels = false) {
128
157
  const lineage = lineages.get(identity.lineage)
129
- if (!lineage || lineage.volume !== identity.volume || lineage.labels["org.threadwire.task"] !== identity.task) return false
130
- return typeof seal === "string" && equalMac(seal, mac(options.key, canonicalRunIdentity(identity)))
158
+ if (!lineage || lineage.provider !== (identity.provider ?? "codex")
159
+ || lineage.volume !== identity.volume || lineage.labels["org.threadwire.task"] !== identity.task) return false
160
+ return ownsRunSeal(identity, seal, legacyLabels)
131
161
  },
132
162
  async trackRun(identity, signal) {
133
163
  await transact(async () => {
134
164
  throwIfAborted(signal)
135
- canonicalRunIdentity(identity)
136
- if (!lineages.has(identity.lineage)) throw new Error("Codex run lineage is unavailable")
137
- const existing = runs.get(identity.run)
138
- if (existing && canonicalRunIdentity(existing) !== canonicalRunIdentity(identity)) throw new Error("Codex run ownership collision")
165
+ const normalized = {...identity, provider: identity.provider ?? provider}
166
+ if (normalized.sealVersion !== undefined) throw new Error("Invalid isolated run identity")
167
+ canonicalRunIdentity(normalized)
168
+ if (normalized.provider !== provider || !lineages.has(normalized.lineage)) throw new Error(`${provider === "kimi" ? "Kimi" : "Codex"} run lineage is unavailable`)
169
+ const existing = runs.get(normalized.run)
170
+ if (existing && canonicalRunIdentity(existing) !== canonicalRunIdentity(normalized)) throw new Error(`${provider === "kimi" ? "Kimi" : "Codex"} run ownership collision`)
139
171
  const nextRuns = new Map(runs)
140
- nextRuns.set(identity.run, identity)
172
+ nextRuns.set(normalized.run, existing ?? normalized)
141
173
  await persist(sessions, lineages, nextRuns, signal)
142
174
  replace(runs, nextRuns)
143
175
  throwIfAborted(signal)
@@ -157,19 +189,20 @@ export async function openStateRegistry(options) {
157
189
  runRecords() {
158
190
  return [...runs.values()].map((entry) => ({...entry}))
159
191
  },
160
- lookup(sessionId) {
192
+ lookup(sessionId, expectedProvider = provider) {
161
193
  const entry = sessions.get(sessionId)
162
- if (!entry || entry.expiresAt <= now()) return undefined
194
+ if (!entry || entry.provider !== expectedProvider || entry.expiresAt <= now()) return undefined
163
195
  return {...entry, labels: stateLabels(options.namespace, entry.task, entry.lineage)}
164
196
  },
165
197
  async track(allocation, signal) {
166
198
  await transact(async () => {
167
199
  throwIfAborted(signal)
168
- const existing = lineages.get(allocation.lineage)
169
- const candidate = allocationRecord(allocation)
200
+ const normalized = providerAllocation(allocation, provider)
201
+ const existing = lineages.get(normalized.lineage)
202
+ const candidate = allocationRecord(normalized)
170
203
  if (existing && JSON.stringify(existing) !== JSON.stringify(candidate)) throw new Error("Codex lineage ownership collision")
171
204
  const nextLineages = new Map(lineages)
172
- nextLineages.set(allocation.lineage, candidate)
205
+ nextLineages.set(normalized.lineage, candidate)
173
206
  await persist(sessions, nextLineages, runs, signal)
174
207
  replace(lineages, nextLineages)
175
208
  throwIfAborted(signal)
@@ -178,21 +211,23 @@ export async function openStateRegistry(options) {
178
211
  async register(sessionIds, allocation, ttlMs, signal) {
179
212
  await transact(async () => {
180
213
  throwIfAborted(signal)
181
- const candidate = allocationRecord(allocation)
182
- const owned = lineages.get(allocation.lineage)
214
+ const normalized = providerAllocation(allocation, provider)
215
+ const candidate = allocationRecord(normalized)
216
+ const owned = lineages.get(normalized.lineage)
183
217
  if (owned && JSON.stringify(owned) !== JSON.stringify(candidate)) throw new Error("Codex lineage ownership collision")
184
218
  const nextSessions = new Map(sessions)
185
219
  const nextLineages = new Map(lineages)
186
220
  for (const sessionId of sessionIds) {
187
221
  const existing = nextSessions.get(sessionId)
188
- if (existing && (existing.task !== allocation.task || existing.lineage !== allocation.lineage || existing.volume !== allocation.volume)) {
222
+ if (existing && (existing.provider !== normalized.provider || existing.task !== normalized.task || existing.lineage !== normalized.lineage || existing.volume !== normalized.volume)) {
189
223
  throw new Error("Codex session lineage collision")
190
224
  }
191
225
  nextSessions.set(sessionId, {
192
- task: allocation.task, lineage: allocation.lineage, volume: allocation.volume, expiresAt: now() + ttlMs
226
+ provider: normalized.provider, task: normalized.task, lineage: normalized.lineage,
227
+ volume: normalized.volume, expiresAt: now() + ttlMs
193
228
  })
194
229
  }
195
- nextLineages.set(allocation.lineage, candidate)
230
+ nextLineages.set(normalized.lineage, candidate)
196
231
  await persist(nextSessions, nextLineages, runs, signal)
197
232
  replace(sessions, nextSessions)
198
233
  replace(lineages, nextLineages)
@@ -215,7 +250,7 @@ export async function openStateRegistry(options) {
215
250
  const nextLineages = new Map(lineages)
216
251
  for (const [sessionId, entry] of nextSessions) if (entry.expiresAt <= now()) nextSessions.delete(sessionId)
217
252
  for (const volume of Array.isArray(listed?.Volumes) ? listed.Volumes : []) {
218
- const orphan = ownedVolumeRecord(volume, options.namespace)
253
+ const orphan = ownedVolumeRecord(volume, options.namespace, provider)
219
254
  if (orphan && !nextLineages.has(orphan.lineage)) nextLineages.set(orphan.lineage, orphan)
220
255
  }
221
256
  // Persist removals and adopted cleanup evidence before touching Docker.
@@ -282,7 +317,7 @@ export function validateStateVolume(volume, name, labels) {
282
317
  for (const [key, value] of Object.entries(labels)) if (actual[key] !== value) throw new Error("Isolated state volume collision")
283
318
  }
284
319
 
285
- function ownedVolumeRecord(volume, namespace) {
320
+ function ownedVolumeRecord(volume, namespace, provider) {
286
321
  if (!record(volume) || !record(volume.Labels)) return undefined
287
322
  const labels = volume.Labels
288
323
  const lineage = labels["org.threadwire.lineage"]
@@ -293,25 +328,33 @@ function ownedVolumeRecord(volume, namespace) {
293
328
  || !/^[0-9a-f]{64}$/u.test(labels["org.threadwire.task"])
294
329
  || !/^[0-9a-f]{48}$/u.test(lineage)
295
330
  || name !== `threadwire-state-${namespace}-${lineage}`) return undefined
296
- return {task: "", lineage, volume: name, labels}
331
+ return {provider, task: "", lineage, volume: name, labels}
332
+ }
333
+
334
+ function providerAllocation(allocation, expectedProvider) {
335
+ const actualProvider = allocation.provider ?? "codex"
336
+ if (actualProvider !== expectedProvider) throw new Error("Isolated state provider mismatch")
337
+ return {...allocation, provider: actualProvider}
297
338
  }
298
339
 
299
340
  function lineageFromSession(namespace, entry) {
300
341
  return {
301
- task: entry.task, lineage: entry.lineage, volume: entry.volume,
342
+ provider: entry.provider, task: entry.task, lineage: entry.lineage, volume: entry.volume,
302
343
  labels: stateLabels(namespace, entry.task, entry.lineage)
303
344
  }
304
345
  }
305
346
 
306
- function validSession(value) {
307
- return record(value) && typeof value.sessionId === "string" && typeof value.task === "string"
347
+ function validSession(value, requireProvider = true) {
348
+ return record(value) && (!requireProvider || value.provider === "codex" || value.provider === "kimi")
349
+ && typeof value.sessionId === "string" && typeof value.task === "string"
308
350
  && /^[0-9a-f]{48}$/u.test(value.lineage)
309
351
  && /^threadwire-state-[A-Za-z0-9_.-]+-[0-9a-f]{48}$/u.test(value.volume)
310
352
  && Number.isSafeInteger(value.expiresAt)
311
353
  }
312
354
 
313
- function validLineage(value, namespace) {
314
- return record(value) && typeof value.task === "string" && /^[0-9a-f]{48}$/u.test(value.lineage)
355
+ function validLineage(value, namespace, requireProvider = true) {
356
+ return record(value) && (!requireProvider || value.provider === "codex" || value.provider === "kimi")
357
+ && typeof value.task === "string" && /^[0-9a-f]{48}$/u.test(value.lineage)
315
358
  && value.volume === `threadwire-state-${namespace}-${value.lineage}` && record(value.labels)
316
359
  && Object.keys(value.labels).length === 4
317
360
  && value.labels["org.threadwire.owner"] === OWNER
@@ -366,14 +409,15 @@ async function removeAuthenticatedTemps(directory, key, namespace) {
366
409
  }
367
410
 
368
411
  function authenticatedDocument(document, key, namespace) {
369
- if (!record(document) || ![1, 2, 3].includes(document.version) || typeof document.mac !== "string"
412
+ if (!record(document) || ![1, 2, 3, 4].includes(document.version) || typeof document.mac !== "string"
370
413
  || !record(document.payload) || !Array.isArray(document.payload.sessions)) return false
371
414
  const payload = JSON.stringify(document.payload)
372
415
  if (!equalMac(document.mac, mac(key, payload))) return false
373
- if (!document.payload.sessions.every(validSession)) return false
416
+ const current = document.version === 4
417
+ if (!document.payload.sessions.every((entry) => validSession(entry, current))) return false
374
418
  if (document.version === 1) return true
375
419
  if (!Array.isArray(document.payload.lineages)
376
- || !document.payload.lineages.every((entry) => validLineage(entry, namespace))) return false
420
+ || !document.payload.lineages.every((entry) => validLineage(entry, namespace, current))) return false
377
421
  return document.version === 2 || (Array.isArray(document.payload.runs)
378
422
  && document.payload.runs.every((entry) => {
379
423
  try { canonicalRunIdentity(entry); return true } catch { return false }
@@ -381,12 +425,29 @@ function authenticatedDocument(document, key, namespace) {
381
425
  }
382
426
 
383
427
  function canonicalRunIdentity(identity) {
428
+ const keys = [
429
+ "provider", "namespace", "run", "lineage", "task", "volume", "network", "container",
430
+ "image", "worktreeType", "worktreeSource", "worktreeSubpath"
431
+ ]
432
+ if (!record(identity)) throw new Error("Invalid isolated run identity")
433
+ const normalized = {...identity, provider: identity.provider ?? "codex"}
434
+ const legacySeal = normalized.sealVersion === 3 && normalized.provider === "codex"
435
+ const expectedKeys = legacySeal ? [...keys, "sealVersion"] : keys
436
+ if (Object.keys(normalized).length !== expectedKeys.length
437
+ || expectedKeys.some((key) => !Object.prototype.hasOwnProperty.call(normalized, key))
438
+ || (normalized.provider !== "codex" && normalized.provider !== "kimi")
439
+ || keys.some((key) => typeof normalized[key] !== "string")) throw new Error("Invalid isolated run identity")
440
+ return JSON.stringify(Object.fromEntries(keys.map((key) => [key, normalized[key]])))
441
+ }
442
+
443
+ function legacyCanonicalRunIdentity(identity) {
384
444
  const keys = [
385
445
  "namespace", "run", "lineage", "task", "volume", "network", "container",
386
446
  "image", "worktreeType", "worktreeSource", "worktreeSubpath"
387
447
  ]
388
- if (!record(identity) || Object.keys(identity).length !== keys.length
389
- || keys.some((key) => typeof identity[key] !== "string")) throw new Error("Invalid isolated run identity")
448
+ if (!record(identity) || identity.provider !== "codex" || identity.sealVersion !== 3
449
+ || Object.keys(identity).length !== keys.length + 2
450
+ || keys.some((key) => typeof identity[key] !== "string")) throw new Error("Invalid legacy Codex run identity")
390
451
  return JSON.stringify(Object.fromEntries(keys.map((key) => [key, identity[key]])))
391
452
  }
392
453
 
@@ -7,6 +7,7 @@ const SAFE_VALUE = /^[^\0\r\n]+$/u
7
7
 
8
8
  /**
9
9
  * @typedef {{
10
+ * provider?: "codex" | "kimi",
10
11
  * image: string,
11
12
  * worktree: string,
12
13
  * networkName: string,
@@ -17,6 +18,7 @@ const SAFE_VALUE = /^[^\0\r\n]+$/u
17
18
  * worktreeVolume?: {name: string, subpath: string}
18
19
  * workingDirectory: string,
19
20
  * providerArguments: string[],
21
+ * model?: string,
20
22
  * stateVolume: string,
21
23
  * worktreeInode: string,
22
24
  * cwdInode: string
@@ -30,6 +32,8 @@ const SAFE_VALUE = /^[^\0\r\n]+$/u
30
32
 
31
33
  /** @param {WorkerSpecOptions} options */
32
34
  export function buildWorkerContainerSpec(options) {
35
+ const provider = options.provider ?? "codex"
36
+ if (provider !== "codex" && provider !== "kimi") throw new Error("Worker provider is invalid")
33
37
  if (!DIGEST_IMAGE.test(options.image)) throw new Error("Worker image must use an immutable digest")
34
38
  if (!isAbsolute(options.worktree) || normalize(options.worktree) !== options.worktree || options.worktree === "/") {
35
39
  throw new Error("Worker worktree must be a normalized non-root absolute path")
@@ -39,19 +43,33 @@ export function buildWorkerContainerSpec(options) {
39
43
  }
40
44
  if (options.resumeSession !== undefined && !SAFE_VALUE.test(options.resumeSession)) throw new Error("Worker resume session is invalid")
41
45
  if (!isAbsolute(options.workingDirectory) || !withinWorktree(options.workingDirectory)) throw new Error("Worker cwd is invalid")
42
- const environment = [
46
+ if (provider === "kimi" && (typeof options.model !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$/u.test(options.model))) {
47
+ throw new Error("Worker Kimi model is invalid")
48
+ }
49
+ const commonEnvironment = [
43
50
  "HOME=/home/worker",
44
- "CODEX_HOME=/home/worker/.codex",
45
51
  "TMPDIR=/tmp",
46
- `OPENAI_BASE_URL=${options.brokerUrl ?? "http://model-broker:8789/v1"}`,
47
- `OPENAI_API_KEY=${options.brokerToken}`,
48
- `CODEX_API_KEY=${options.brokerToken}`,
49
52
  `THREADWIRE_PROMPT=${options.prompt}`,
50
- `THREADWIRE_CODEX_ARGUMENTS=${JSON.stringify(options.providerArguments)}`,
51
53
  `THREADWIRE_WORKTREE_INODE=${options.worktreeInode}`,
52
54
  `THREADWIRE_CWD_INODE=${options.cwdInode}`,
53
55
  ...(options.resumeSession === undefined ? [] : [`THREADWIRE_RESUME_SESSION=${options.resumeSession}`])
54
56
  ]
57
+ const environment = provider === "kimi"
58
+ ? [
59
+ ...commonEnvironment,
60
+ "KIMI_CODE_HOME=/home/worker/.kimi-code",
61
+ `THREADWIRE_KIMI_MODEL=${options.model}`,
62
+ `THREADWIRE_KIMI_BROKER_URL=${options.brokerUrl}`,
63
+ `THREADWIRE_KIMI_BROKER_TOKEN=${options.brokerToken}`
64
+ ]
65
+ : [
66
+ ...commonEnvironment,
67
+ "CODEX_HOME=/home/worker/.codex",
68
+ `OPENAI_BASE_URL=${options.brokerUrl ?? "http://model-broker:8789/v1"}`,
69
+ `OPENAI_API_KEY=${options.brokerToken}`,
70
+ `CODEX_API_KEY=${options.brokerToken}`,
71
+ `THREADWIRE_CODEX_ARGUMENTS=${JSON.stringify(options.providerArguments)}`
72
+ ]
55
73
  const worktreeMount = options.worktreeVolume === undefined
56
74
  ? {
57
75
  Type: "bind",
@@ -73,6 +91,7 @@ export function buildWorkerContainerSpec(options) {
73
91
  "org.threadwire.owner": "isolated-runtime",
74
92
  ...(options.lineage === undefined ? {} : {
75
93
  "org.threadwire.namespace": options.namespace,
94
+ "org.threadwire.provider": provider,
76
95
  "org.threadwire.run": options.runId,
77
96
  "org.threadwire.lineage": options.lineage,
78
97
  "org.threadwire.task": options.taskHash,
@@ -81,7 +100,9 @@ export function buildWorkerContainerSpec(options) {
81
100
  "org.threadwire.run-seal": options.runSeal
82
101
  })
83
102
  },
84
- Entrypoint: ["/usr/local/libexec/threadwire/worker-entrypoint"],
103
+ Entrypoint: provider === "kimi"
104
+ ? ["node", "/opt/threadwire/docker/kimi-worker-entrypoint.mjs"]
105
+ : ["/usr/local/libexec/threadwire/worker-entrypoint"],
85
106
  Env: environment,
86
107
  WorkingDir: options.workingDirectory,
87
108
  User: "10002:10002",
@@ -0,0 +1,209 @@
1
+ // @ts-check
2
+
3
+ import {randomBytes} from "node:crypto"
4
+
5
+ const TOKEN_PATTERN = /^[A-Za-z0-9_-]{32,128}$/u
6
+ const ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$/u
7
+ const MODEL_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$/u
8
+ const ALIAS_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u
9
+ const TASK_PATTERN = /^[0-9a-f]{64}$/u
10
+ const SESSION_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,511}$/u
11
+
12
+ /** @typedef {{provider: "kimi", runId: string, networkId: string, taskId: string, sessionId: string, modelAlias: string, model: string, expiresAt: number, active: boolean}} KimiGrant */
13
+ /** @typedef {{alias: string, model: string, protocol: "kimi"}} ApprovedKimiModel */
14
+ const ALLOWED_HEADERS = new Set([
15
+ "authorization", "content-length", "content-type", "accept", "accept-encoding",
16
+ "accept-language", "sec-fetch-mode", "user-agent",
17
+ "x-stainless-arch", "x-stainless-lang", "x-stainless-os",
18
+ "x-stainless-package-version", "x-stainless-runtime", "x-stainless-runtime-version",
19
+ "x-stainless-retry-count", "x-stainless-timeout", "x-stainless-async"
20
+ ])
21
+ const BODY_KEYS = new Set([
22
+ "model", "messages", "stream", "stream_options", "tools", "thinking",
23
+ "max_completion_tokens", "temperature", "top_p", "n", "presence_penalty",
24
+ "frequency_penalty", "stop", "prompt_cache_key", "response_format"
25
+ ])
26
+ const NUMERIC_KEYS = new Set([
27
+ "max_completion_tokens", "temperature", "top_p", "n", "presence_penalty", "frequency_penalty"
28
+ ])
29
+
30
+ export const KIMI_CHAT_COMPLETIONS_PATH = "/coding/v1/chat/completions"
31
+
32
+ /** @param {{now?: () => number, random?: (bytes: number) => Buffer}} [options] */
33
+ export function createKimiGrantStore(options = {}) {
34
+ const now = options.now ?? Date.now
35
+ const random = options.random ?? randomBytes
36
+ /** @type {Map<string, KimiGrant>} */
37
+ const grants = new Map()
38
+ return {
39
+ /** @param {{provider: "kimi", runId: string, networkId: string, taskId: string, sessionId: string, modelAlias: string, model: string, ttlMs: number}} grant */
40
+ issue(grant) {
41
+ if (grant.provider !== "kimi" || !ID_PATTERN.test(grant.runId) || !ID_PATTERN.test(grant.networkId)
42
+ || !TASK_PATTERN.test(grant.taskId) || !SESSION_PATTERN.test(grant.sessionId)
43
+ || !ALIAS_PATTERN.test(grant.modelAlias) || !MODEL_PATTERN.test(grant.model)
44
+ || !Number.isSafeInteger(grant.ttlMs) || grant.ttlMs < 1 || grant.ttlMs > 3_600_000) {
45
+ throw new Error("Invalid Kimi broker grant")
46
+ }
47
+ const token = random(32).toString("base64url")
48
+ if (!TOKEN_PATTERN.test(token) || grants.has(token)) throw new Error("Invalid Kimi broker grant")
49
+ grants.set(token, {...grant, expiresAt: now() + grant.ttlMs, active: false})
50
+ return token
51
+ },
52
+ /** @param {string} token */
53
+ activate(token) {
54
+ const grant = currentGrant(grants, token, now)
55
+ if (grant.active) throw new Error("Kimi broker grant already active")
56
+ grant.active = true
57
+ return {...grant}
58
+ },
59
+ /** @param {string} token @param {{provider: string, runId: string, networkId: string, taskId: string, sessionId: string, modelAlias: string}} context */
60
+ authorize(token, context) {
61
+ const grant = currentGrant(grants, token, now)
62
+ if (grant.provider !== context.provider || grant.runId !== context.runId
63
+ || grant.networkId !== context.networkId || grant.taskId !== context.taskId
64
+ || grant.sessionId !== context.sessionId || grant.modelAlias !== context.modelAlias) {
65
+ throw new Error("Kimi broker grant lineage denied")
66
+ }
67
+ if (!grant.active) throw new Error("Kimi broker grant pending")
68
+ return {...grant}
69
+ },
70
+ /** @param {string} token */
71
+ revoke(token) { grants.delete(token) },
72
+ clear() { grants.clear() }
73
+ }
74
+ }
75
+
76
+ /**
77
+ * @param {Map<string, KimiGrant>} grants
78
+ * @param {string} token
79
+ * @param {() => number} now
80
+ */
81
+ function currentGrant(grants, token, now) {
82
+ const grant = grants.get(token)
83
+ if (!grant) throw new Error("Kimi broker grant unknown token")
84
+ if (grant.expiresAt < now()) {
85
+ grants.delete(token)
86
+ throw new Error("Kimi broker grant expired")
87
+ }
88
+ return grant
89
+ }
90
+
91
+ /** @param {{method?: string, path?: string, headers?: Record<string, string | string[] | undefined>}} request */
92
+ export function validateKimiBrokerRequest(request) {
93
+ if (request.method !== "POST" || request.path !== "/v1/chat/completions") {
94
+ throw new Error("Kimi broker request denied")
95
+ }
96
+ for (const header of Object.keys(request.headers ?? {})) {
97
+ if (!ALLOWED_HEADERS.has(header.toLowerCase())) throw new Error(`Kimi broker request denied header: ${header.toLowerCase()}`)
98
+ }
99
+ return {method: "POST", path: "/v1/chat/completions", upstreamPath: KIMI_CHAT_COMPLETIONS_PATH}
100
+ }
101
+
102
+ /** @param {Buffer} body @param {string} allowedModel */
103
+ export function validateKimiChatBody(body, allowedModel) {
104
+ const text = body.toString("utf8")
105
+ if (topLevelKeyCount(text, "model") !== 1) throw denied()
106
+ let value
107
+ try { value = JSON.parse(text) } catch { throw denied() }
108
+ if (!record(value) || Object.keys(value).some((key) => !BODY_KEYS.has(key))) throw denied()
109
+ if (value.model !== allowedModel || !Array.isArray(value.messages) || value.stream !== true) throw denied()
110
+ if (value.messages.length === 0 || value.messages.length > 4096 || !value.messages.every(validMessage)) throw denied()
111
+ if (value.tools !== undefined && (!Array.isArray(value.tools) || value.tools.length > 512 || !value.tools.every(record))) throw denied()
112
+ if (value.stream_options !== undefined && (!record(value.stream_options)
113
+ || !exactKeys(value.stream_options, ["include_usage"]) || value.stream_options.include_usage !== true)) throw denied()
114
+ if (value.thinking !== undefined && !validThinking(value.thinking)) throw denied()
115
+ if (value.response_format !== undefined && !record(value.response_format)) throw denied()
116
+ if (value.prompt_cache_key !== undefined && (typeof value.prompt_cache_key !== "string" || value.prompt_cache_key.length > 512)) throw denied()
117
+ if (value.stop !== undefined && !validStop(value.stop)) throw denied()
118
+ for (const key of NUMERIC_KEYS) {
119
+ const item = value[key]
120
+ if (item !== undefined && (typeof item !== "number" || !Number.isFinite(item))) throw denied()
121
+ }
122
+ if (value.max_completion_tokens !== undefined && (typeof value.max_completion_tokens !== "number"
123
+ || !Number.isSafeInteger(value.max_completion_tokens)
124
+ || value.max_completion_tokens < 1 || value.max_completion_tokens > 1_000_000)) throw denied()
125
+ if (value.n !== undefined && value.n !== 1) throw denied()
126
+ return value
127
+ }
128
+
129
+ /** @param {unknown} value */
130
+ function validMessage(value) {
131
+ if (!record(value) || typeof value.role !== "string" || !["system", "user", "assistant", "tool"].includes(value.role)) return false
132
+ const allowed = new Set(["role", "content", "tool_calls", "tool_call_id", "name", "reasoning_content", "reasoning_details", "reasoning", "tools"])
133
+ return Object.keys(value).every((key) => allowed.has(key))
134
+ }
135
+
136
+ /** @param {unknown} value */
137
+ function validThinking(value) {
138
+ if (!record(value) || Object.keys(value).some((key) => !["type", "effort", "keep"].includes(key))) return false
139
+ if (value.type !== undefined && value.type !== "enabled" && value.type !== "disabled") return false
140
+ if (value.effort !== undefined && (typeof value.effort !== "string" || !/^[A-Za-z0-9_-]{1,32}$/u.test(value.effort))) return false
141
+ return value.keep === undefined || value.keep === "all"
142
+ }
143
+
144
+ /** @param {unknown} value */
145
+ function validStop(value) {
146
+ return typeof value === "string" ? value.length <= 512
147
+ : Array.isArray(value) && value.length <= 16 && value.every((item) => typeof item === "string" && item.length <= 512)
148
+ }
149
+
150
+ /** @param {string | undefined} authorization */
151
+ export function kimiBearerToken(authorization) {
152
+ const match = /^Bearer ([A-Za-z0-9_-]{32,128})$/u.exec(authorization ?? "")
153
+ if (!match || !TOKEN_PATTERN.test(match[1] ?? "")) throw new Error("Kimi broker grant format denied")
154
+ return /** @type {string} */ (match[1])
155
+ }
156
+
157
+ /** @param {string | undefined} value */
158
+ export function parseApprovedKimiModels(value) {
159
+ let parsed
160
+ try { parsed = JSON.parse(value ?? "") } catch { throw new Error("THREADWIRE_ALLOWED_KIMI_MODELS must contain allowed Kimi models") }
161
+ if (!record(parsed) || Object.keys(parsed).length < 1 || Object.keys(parsed).length > 32) {
162
+ throw new Error("THREADWIRE_ALLOWED_KIMI_MODELS must contain allowed Kimi models")
163
+ }
164
+ /** @type {Map<string, ApprovedKimiModel>} */
165
+ const models = new Map()
166
+ for (const [alias, item] of Object.entries(parsed)) {
167
+ if (!ALIAS_PATTERN.test(alias) || !record(item) || !exactKeys(item, ["model", "protocol"])
168
+ || typeof item.model !== "string" || !MODEL_PATTERN.test(item.model) || item.protocol !== "kimi") {
169
+ throw new Error("THREADWIRE_ALLOWED_KIMI_MODELS must contain allowed Kimi models")
170
+ }
171
+ models.set(alias, {alias, model: item.model, protocol: "kimi"})
172
+ }
173
+ return models
174
+ }
175
+
176
+ /** @returns {Error} */
177
+ function denied() { return new Error("Kimi broker request denied") }
178
+ /** @param {unknown} value @returns {value is Record<string, unknown>} */
179
+ function record(value) { return typeof value === "object" && value !== null && !Array.isArray(value) }
180
+ /** @param {Record<string, unknown>} value @param {readonly string[]} keys */
181
+ function exactKeys(value, keys) {
182
+ const actual = Object.keys(value).sort()
183
+ return actual.length === keys.length && actual.every((key, index) => key === [...keys].sort()[index])
184
+ }
185
+
186
+ /** @param {string} text @param {string} wanted @returns {number} */
187
+ function topLevelKeyCount(text, wanted) {
188
+ let depth = 0
189
+ let count = 0
190
+ let index = 0
191
+ while (index < text.length) {
192
+ if (text[index] === "\"") {
193
+ let string = ""
194
+ index++
195
+ while (index < text.length && text[index] !== "\"") {
196
+ if (text[index] === "\\") { index += 2; continue }
197
+ string += text[index++]
198
+ }
199
+ if (depth === 1) {
200
+ let next = index + 1
201
+ while (/\s/u.test(text[next] ?? "")) next++
202
+ if (text[next] === ":" && string === wanted) count++
203
+ }
204
+ } else if (text[index] === "{") depth++
205
+ else if (text[index] === "}") depth--
206
+ index++
207
+ }
208
+ return count
209
+ }