nanocodex 0.6.4 → 0.6.6

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 (78) hide show
  1. package/README.md +37 -11
  2. package/browser/Agent.d.mts +1 -1
  3. package/browser/ElevenLabsPlayback.mjs +119 -0
  4. package/browser/InlineAgent.mjs +22 -17
  5. package/browser/Transport.d.mts +2 -0
  6. package/browser/Transport.mjs +3 -2
  7. package/browser/Voice.d.mts +4 -0
  8. package/browser/Voice.mjs +38 -6
  9. package/browser/VoiceSession.mjs +37 -7
  10. package/browser/host.d.mts +20 -4
  11. package/browser/host.mjs +156 -18
  12. package/cloud/types.d.mts +1 -0
  13. package/cloudflare/Agent.d.mts +14 -9
  14. package/cloudflare/Agent.mjs +354 -96
  15. package/cloudflare/agent-settings.d.mts +15 -0
  16. package/cloudflare/agent-settings.mjs +135 -0
  17. package/cloudflare/chat-reasoning.mjs +20 -0
  18. package/cloudflare/durable-placement.d.mts +12 -0
  19. package/cloudflare/durable-placement.mjs +82 -0
  20. package/cloudflare/egress.d.mts +31 -0
  21. package/cloudflare/gateway-binding-responses.mjs +84 -0
  22. package/cloudflare/gateway-responses.d.mts +48 -0
  23. package/cloudflare/gateway-responses.mjs +191 -0
  24. package/cloudflare/index.d.mts +6 -0
  25. package/cloudflare/index.mjs +3 -0
  26. package/cloudflare/managed-auth.d.mts +15 -0
  27. package/cloudflare/managed-auth.mjs +119 -0
  28. package/cloudflare/managed-live.d.mts +7 -0
  29. package/cloudflare/managed-live.mjs +56 -0
  30. package/cloudflare/prepared-connection.mjs +49 -0
  31. package/cloudflare/provider-stream.mjs +353 -0
  32. package/cloudflare/rpc.d.mts +9 -0
  33. package/cloudflare/rpc.mjs +14 -0
  34. package/cloudflare/tool-alias-diagnostic.mjs +21 -0
  35. package/cloudflare/tracing.d.mts +16 -0
  36. package/cloudflare/tracing.mjs +47 -0
  37. package/cloudflare/workers-ai-responses.d.mts +28 -0
  38. package/cloudflare/workers-ai-responses.mjs +399 -0
  39. package/host/Agent.d.mts +1 -1
  40. package/index.d.mts +2 -0
  41. package/internal.mjs +53 -14
  42. package/managed/Agent.d.mts +25 -69
  43. package/managed/Agent.mjs +82 -196
  44. package/managed/README.md +9 -38
  45. package/managed/Voice.mjs +5 -2
  46. package/managed/index.d.mts +0 -3
  47. package/managed/internal.mjs +2 -1
  48. package/node/Agent.d.mts +1 -1
  49. package/node/Agent.mjs +9 -3
  50. package/node/host.mjs +5 -0
  51. package/package.json +34 -2
  52. package/pkg-node/nanocodex.d.ts +22 -18
  53. package/pkg-node/nanocodex.js +118 -92
  54. package/pkg-node/nanocodex_bg.wasm.d.ts +6 -5
  55. package/pkg-web/.nanocodex-bindgen-stamp +7 -1
  56. package/pkg-web/nanocodex-build.json +1 -1
  57. package/pkg-web/nanocodex.d.ts +28 -23
  58. package/pkg-web/nanocodex.js +117 -91
  59. package/pkg-web/nanocodex_bg.js +117 -91
  60. package/pkg-web/nanocodex_bg.wasm +0 -0
  61. package/pkg-web/nanocodex_bg.wasm.d.ts +6 -5
  62. package/pkg-web/nanocodex_worker.js +1 -1
  63. package/runtime/before-compaction.mjs +49 -0
  64. package/runtime/code-evaluator.worker.mjs +2 -9
  65. package/runtime/managed-transport.mjs +10 -2
  66. package/runtime/mcp-runtime.mjs +20 -4
  67. package/runtime/quickjs-evaluator.mjs +11 -5
  68. package/runtime/response-controls.mjs +67 -4
  69. package/runtime/steer-receipt.mjs +13 -0
  70. package/runtime/subagent-routing.d.mts +38 -0
  71. package/runtime/subagent-routing.mjs +81 -0
  72. package/runtime/subagents.d.mts +1 -1
  73. package/runtime/tempo-provider.d.mts +1 -1
  74. package/runtime/tempo-provider.mjs +1 -1
  75. package/tools/environment.d.mts +17 -2
  76. package/tools/environment.mjs +30 -3
  77. package/tools/index.d.mts +2 -0
  78. package/types.d.mts +30 -2
@@ -1,7 +1,12 @@
1
+ import { steerInputKey } from "../runtime/steer-receipt.mjs";
2
+ export { steerInputKey };
3
+ import { createGatewayResponses } from "./gateway-responses.mjs";
4
+ import { createWorkersAiResponses } from "./workers-ai-responses.mjs";
1
5
  import { responseControlsBody, responseControlsSocket } from "../runtime/response-controls.mjs";
2
6
  import * as HostAgent from "../host/Agent.mjs";
3
7
  import {
4
8
  CLOUDFLARE_SESSION_RESERVATION,
9
+ checkpoint as checkpointAgent,
5
10
  commitCloudflareAgentSession,
6
11
  installHostBridge,
7
12
  loadDurabilityRuntime,
@@ -12,7 +17,7 @@ import {
12
17
  releaseAgentSession,
13
18
  routePrompt,
14
19
  } from "../internal.mjs";
15
- import { pruneDurableReceipts as pruneWasmDurableReceipts, managedBootstrapPlan } from "../pkg-web/nanocodex.js";
20
+ import { pruneDurableReceipts as pruneWasmDurableReceipts } from "../pkg-web/nanocodex.js";
16
21
  import * as Transport from "../browser/Transport.mjs";
17
22
  import { initializeBrowserEngine } from "../browser/engine.mjs";
18
23
  import { createCloudflareDurabilityStore } from "../runtime/cloudflare-durability-store.mjs";
@@ -24,6 +29,7 @@ import {
24
29
  importDurabilityState as importPortableState,
25
30
  } from "../runtime/durability-store.mjs";
26
31
  import { cloudflareEgress } from "./egress.mjs";
32
+ import { prepareConnection } from "./prepared-connection.mjs";
27
33
  import { scopeCloudflareEgress } from "./egress-subject.mjs";
28
34
  import {
29
35
  clearCloudflareEventSocket,
@@ -33,7 +39,9 @@ import {
33
39
  const STARTUP_TIMEOUT_MS = 10_000;
34
40
  const INTERNAL_RUNTIME = Symbol.for("nanocodex.cloudflare.internalRuntime");
35
41
  const INTERNAL_CONFIGURATION = Symbol.for("nanocodex.cloudflare.internalConfiguration");
42
+ const INTERNAL_FORK_RESUME = Symbol.for("nanocodex.cloudflare.internalForkResume");
36
43
  const EPHEMERAL_APPLICATION_OPTIONS = new Set([
44
+ "beforeCompaction",
37
45
  "additionalInstructions",
38
46
  "fastMode",
39
47
  "instructions",
@@ -46,6 +54,7 @@ const EPHEMERAL_APPLICATION_OPTIONS = new Set([
46
54
  "workspace",
47
55
  ]);
48
56
  const APPLICATION_OPTIONS = new Set([
57
+ "beforeCompaction",
49
58
  "additionalInstructions",
50
59
  "durabilityId",
51
60
  "eventPersistence",
@@ -58,13 +67,12 @@ const lifecycles = new WeakMap();
58
67
  /** @internal Binds the package-owned module to the public Cloudflare namespace. */
59
68
  export function bindAgent(module, hostAgent = HostAgent) {
60
69
  return Object.freeze({
61
- bootstrapPlan: async (input) => {
62
- await initializeBrowserEngine({ module });
63
- return JSON.parse(managedBootstrapPlan(input));
64
- },
70
+ steerReceipt,
71
+ steerInputKey,
65
72
  pruneDurableReceipts: (owner, options) => pruneDurableReceipts(module, owner, options),
66
73
  create: (owner, options) => create(module, owner, options, hostAgent),
67
74
  createEphemeral: (owner, options) => createEphemeral(module, owner, options),
75
+ checkpoint,
68
76
  destroy,
69
77
  exportDurabilityState,
70
78
  exportDurabilityHead,
@@ -73,6 +81,11 @@ export function bindAgent(module, hostAgent = HostAgent) {
73
81
  });
74
82
  }
75
83
 
84
+ /** Copies the latest safe committed boundary as a resumable SessionSnapshot. */
85
+ export function checkpoint(agent) {
86
+ return checkpointAgent(agent);
87
+ }
88
+
76
89
  /** Atomically steers an active Cloudflare Agent turn or starts a new turn. */
77
90
  export function route(agent, options) {
78
91
  return routePrompt(agent, options);
@@ -117,7 +130,7 @@ export function destroy(owner) {
117
130
  stateId,
118
131
  );
119
132
  }
120
- storage.sql.exec("DELETE FROM nanocodex_cloudflare_subagents");
133
+ storage.sql.exec("DROP TABLE IF EXISTS nanocodex_cloudflare_fork_resume");
121
134
  clearCloudflareEventSocket(context);
122
135
  });
123
136
  }
@@ -274,13 +287,111 @@ export async function create(module, owner, options = {}, hostAgent = HostAgent)
274
287
  }
275
288
  lifecycle.creating = true;
276
289
  try {
277
- return await createOwned(module, resolved, options, hostAgent, lifecycle);
290
+ const prepare = options?.[INTERNAL_RUNTIME]?.prepare;
291
+ if (prepare === undefined) return await createOwned(module, resolved, options, hostAgent, lifecycle);
292
+ return await createPrepared(module, resolved, options, hostAgent, lifecycle, prepare);
278
293
  } finally {
279
294
  lifecycle.creating = false;
280
295
  }
281
296
  }
282
297
 
283
- async function createOwned(module, resolved, options, hostAgent, lifecycle) {
298
+ // Private managed construction barrier. The adapter retains lifecycle, identity,
299
+ // endpoint and socket ownership throughout tool discovery; callers receive only
300
+ // a single-use continuation, never a transport or a session identity.
301
+ async function createPrepared(module, resolved, options, hostAgent, lifecycle, prepare) {
302
+ applicationOptions(options);
303
+ if (typeof prepare !== "function") throw new TypeError("Cloudflare Agent preparation must be a function");
304
+ const configuration = options[INTERNAL_CONFIGURATION];
305
+ validateInternalConfiguration(configuration);
306
+ if (!configuration || !["gpt-6-astra", "gpt-6.1-sol", "gpt-6-luna"].includes(configuration.model)) {
307
+ throw new Error("Cloudflare Agent preparation requires a native root configuration");
308
+ }
309
+ const runtime = options[INTERNAL_RUNTIME];
310
+ if (Object.keys(runtime).some(key => !["prepare", "preparationSignal"].includes(key))) {
311
+ throw new TypeError("Cloudflare Agent preparation owns its native transport policy");
312
+ }
313
+ const signal = runtime.preparationSignal;
314
+ signal?.throwIfAborted();
315
+ createCloudflareDurabilityStore(resolved.context.storage);
316
+ initializeAgentStorage(resolved.context.storage);
317
+ const pristineAtStart = storedSessionId(resolved.context.storage) === undefined
318
+ && storedStateId(resolved.context.storage) === undefined;
319
+ const initialForkResume = options?.[INTERNAL_FORK_RESUME];
320
+ let initialForkDigest;
321
+ if (initialForkResume !== undefined) {
322
+ if (!initialForkResume || typeof initialForkResume !== "object" || Array.isArray(initialForkResume))
323
+ throw new TypeError("Cloudflare Agent fork resume must be a SessionSnapshot");
324
+ const bytes = new Uint8Array(await crypto.subtle.digest("SHA-256",
325
+ new TextEncoder().encode(JSON.stringify(initialForkResume))));
326
+ initialForkDigest = [...bytes].map(byte => byte.toString(16).padStart(2, "0")).join("");
327
+ }
328
+ const { sessionId, stateId } = durableIdentity(resolved.context.storage, options.durabilityId);
329
+ if (initialForkDigest !== undefined) {
330
+ // Pin the seed before optional tool discovery/transport preparation. A
331
+ // failed preparation can reopen the exact fork with the same snapshot.
332
+ resolved.context.storage.sql.exec(`CREATE TABLE IF NOT EXISTS nanocodex_cloudflare_fork_resume (
333
+ singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
334
+ state_id TEXT NOT NULL, digest TEXT NOT NULL
335
+ )`);
336
+ const previous = resolved.context.storage.sql.exec(
337
+ "SELECT state_id, digest FROM nanocodex_cloudflare_fork_resume WHERE singleton = 1",
338
+ ).toArray()[0];
339
+ if ((!pristineAtStart && !previous) || (previous
340
+ && (previous.state_id !== stateId || previous.digest !== initialForkDigest))) {
341
+ throw new Error("Cloudflare Agent fork resume conflicts with its retained seed");
342
+ }
343
+ resolved.context.storage.sql.exec(
344
+ "INSERT OR IGNORE INTO nanocodex_cloudflare_fork_resume(singleton,state_id,digest) VALUES (1,?,?)",
345
+ stateId, initialForkDigest);
346
+ }
347
+ const endpoint = cloudflareEgress({ binding: scopeCloudflareEgress(resolved.egress, resolved.subject) });
348
+ const connection = prepareConnection(endpoint, sessionId, signal);
349
+ let completing;
350
+ let accepting = true;
351
+ let attempted = false;
352
+ try {
353
+ // Share the realm's engine initialization while the socket and tool metadata
354
+ // load. No Agent or durable runtime owner exists until preparation finishes.
355
+ installHostBridge();
356
+ const initializing = initializeBrowserEngine({ module });
357
+ void initializing.catch(() => {});
358
+ const result = await prepare((prepared) => {
359
+ if (!accepting || attempted) throw new Error("Cloudflare Agent preparation was already completed");
360
+ attempted = true;
361
+ signal?.throwIfAborted();
362
+ completing = initializing.then(() => {
363
+ signal?.throwIfAborted();
364
+ const runtime = prepared?.[INTERNAL_RUNTIME];
365
+ const pinned = prepared?.[INTERNAL_CONFIGURATION];
366
+ validateInternalConfiguration(pinned);
367
+ if (prepared?.durabilityId !== stateId
368
+ || ["model", "reasoning_mode"].some(key => pinned?.[key] !== configuration[key])
369
+ || runtime?.prepare !== undefined || runtime?.workersAi !== undefined || runtime?.gateway !== undefined
370
+ || (runtime?.inferenceForSession !== undefined && runtime?.preserveRootTransport !== true)
371
+ || (initialForkDigest !== undefined && JSON.stringify(prepared?.[INTERNAL_FORK_RESUME]) !== JSON.stringify(initialForkResume))) {
372
+ throw new Error("Cloudflare Agent preparation changed its pinned transport or configuration");
373
+ }
374
+ return createOwned(module, resolved, prepared, hostAgent, lifecycle, connection,
375
+ pristineAtStart ? { sessionId, stateId } : undefined);
376
+ });
377
+ void completing.catch(() => {});
378
+ return completing;
379
+ });
380
+ if (completing === undefined || result !== await completing) {
381
+ throw new Error("Cloudflare Agent preparation must return its completed Agent");
382
+ }
383
+ return result;
384
+ } catch (error) {
385
+ const agent = await completing?.catch(() => undefined);
386
+ if (agent !== undefined) await agent.session.shutdown();
387
+ throw error;
388
+ } finally {
389
+ accepting = false;
390
+ await connection.dispose();
391
+ }
392
+ }
393
+
394
+ async function createOwned(module, resolved, options, hostAgent, lifecycle, preparedConnection, pristinePreparedIdentity) {
284
395
  const { context, egress, subject } = resolved;
285
396
  const configured = applicationOptions(options);
286
397
  const {
@@ -288,12 +399,25 @@ async function createOwned(module, resolved, options, hostAgent, lifecycle) {
288
399
  eventPersistence = "durable",
289
400
  [INTERNAL_RUNTIME]: internalRuntime,
290
401
  [INTERNAL_CONFIGURATION]: internalConfiguration,
402
+ [INTERNAL_FORK_RESUME]: forkResume,
291
403
  ...agentOptions
292
404
  } = configured;
293
405
  if (internalRuntime !== undefined
294
406
  && (!internalRuntime || typeof internalRuntime !== "object" || Array.isArray(internalRuntime))) {
295
407
  throw new TypeError("Cloudflare Agent internal runtime options must be an object");
296
408
  }
409
+ if (internalRuntime?.onSocketTiming !== undefined
410
+ && typeof internalRuntime.onSocketTiming !== "function") {
411
+ throw new TypeError("Cloudflare Agent socket timing hook must be a function");
412
+ }
413
+ if (internalRuntime?.onResponseCreateSent !== undefined
414
+ && typeof internalRuntime.onResponseCreateSent !== "function") {
415
+ throw new TypeError("Cloudflare Agent response.create sent hook must be a function");
416
+ }
417
+ if (internalRuntime?.onRequestShape !== undefined
418
+ && typeof internalRuntime.onRequestShape !== "function") {
419
+ throw new TypeError("Cloudflare Agent request shape hook must be a function");
420
+ }
297
421
  if (internalRuntime?.subagentLifecycle !== undefined
298
422
  && typeof internalRuntime.subagentLifecycle !== "function") {
299
423
  throw new TypeError("Cloudflare Agent subagent lifecycle hook must be a function");
@@ -302,31 +426,174 @@ async function createOwned(module, resolved, options, hostAgent, lifecycle) {
302
426
  && typeof internalRuntime.waitForPreconnect !== "boolean") {
303
427
  throw new TypeError("Cloudflare Agent internal waitForPreconnect must be a boolean");
304
428
  }
429
+ if (internalRuntime?.preserveRootTransport !== undefined
430
+ && typeof internalRuntime.preserveRootTransport !== "boolean") {
431
+ throw new TypeError("Cloudflare Agent root transport policy must be a boolean");
432
+ }
433
+ if (internalRuntime?.inferenceForSession !== undefined
434
+ && typeof internalRuntime.inferenceForSession !== "function") {
435
+ throw new TypeError("Cloudflare Agent inference routing must be a function");
436
+ }
437
+ if (internalRuntime?.subagentRouting !== undefined && internalRuntime?.inferenceForSession === undefined) {
438
+ throw new TypeError("Subagent routing requires session-specific inference routing");
439
+ }
305
440
  validateInternalConfiguration(internalConfiguration);
306
441
  const eventSocket = eventPersistence === "durable"
307
442
  ? createCloudflareEventSocket(context)
308
443
  : undefined;
309
444
  if (eventPersistence === "caller") clearCloudflareEventSocket(context);
310
445
  const durability = createCloudflareDurabilityStore(context.storage);
446
+ let resumeDigest;
447
+ if (forkResume !== undefined) {
448
+ if (!forkResume || typeof forkResume !== "object" || Array.isArray(forkResume)) {
449
+ throw new TypeError("Cloudflare Agent fork resume must be a SessionSnapshot");
450
+ }
451
+ initializeAgentStorage(context.storage);
452
+ context.storage.sql.exec(`CREATE TABLE IF NOT EXISTS nanocodex_cloudflare_fork_resume (
453
+ singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
454
+ state_id TEXT NOT NULL, digest TEXT NOT NULL
455
+ )`);
456
+ const digestBytes = new Uint8Array(await crypto.subtle.digest("SHA-256",
457
+ new TextEncoder().encode(JSON.stringify(forkResume))));
458
+ resumeDigest = [...digestBytes].map(byte => byte.toString(16).padStart(2, "0")).join("");
459
+ const recordedSeed = context.storage.sql.exec(
460
+ "SELECT state_id, digest FROM nanocodex_cloudflare_fork_resume WHERE singleton = 1",
461
+ ).toArray()[0];
462
+ const existingSessionId = storedSessionId(context.storage);
463
+ const existingStateId = storedStateId(context.storage);
464
+ // The preparation barrier may have installed the new identity already. A
465
+ // failed/restarted child construction may retain that identity and a
466
+ // revision-zero owner fence, but still has no executed checkpoint. The
467
+ // durable seed is unchanged; allow that cold retry and reject any real head.
468
+ if ((existingSessionId !== undefined || existingStateId !== undefined)
469
+ && !((pristinePreparedIdentity?.sessionId === existingSessionId
470
+ && pristinePreparedIdentity?.stateId === existingStateId)
471
+ || (existingSessionId !== undefined && existingStateId !== undefined
472
+ && (durabilityId === undefined || existingStateId === durabilityId)
473
+ && recordedSeed?.state_id === existingStateId
474
+ && recordedSeed?.digest === resumeDigest))) {
475
+ throw new Error("Cloudflare Agent fork resume requires a pristine Durable Object");
476
+ }
477
+ if (recordedSeed && (recordedSeed.digest !== resumeDigest
478
+ || (existingStateId !== undefined && recordedSeed.state_id !== existingStateId))) {
479
+ throw new Error("Cloudflare Agent fork resume conflicts with its retained seed");
480
+ }
481
+ const checkStateId = existingStateId ?? durabilityId;
482
+ if (checkStateId !== undefined && context.storage.sql.exec(
483
+ "SELECT revision, payload FROM nanocodex_durable_states WHERE state_id = ?", checkStateId,
484
+ ).toArray().some(row => row.revision !== "0" || row.payload !== null)) {
485
+ throw new Error("Cloudflare Agent fork resume requires pristine durability state");
486
+ }
487
+ }
311
488
  const { sessionId, stateId } = durableIdentity(context.storage, durabilityId);
312
- const endpoint = cloudflareEgress({
489
+ if (resumeDigest !== undefined) {
490
+ context.storage.sql.exec(
491
+ "INSERT OR IGNORE INTO nanocodex_cloudflare_fork_resume(singleton,state_id,digest) VALUES (1,?,?)",
492
+ stateId, resumeDigest);
493
+ }
494
+ const workersAi = internalRuntime?.workersAi;
495
+ const gateway = internalRuntime?.gateway;
496
+ if (gateway !== undefined && (workersAi !== undefined
497
+ || gateway.model !== internalConfiguration?.model
498
+ || gateway.reasoningEffort !== internalConfiguration?.thinking)) {
499
+ throw new TypeError("Gateway profile must match the pinned model and thinking, with one transport only");
500
+ }
501
+ const routedInference = internalRuntime?.inferenceForSession !== undefined;
502
+ const directInference = workersAi !== undefined || gateway !== undefined
503
+ || (routedInference && internalRuntime?.preserveRootTransport !== true);
504
+ if (workersAi !== undefined && (internalConfiguration?.model !== "@cf/zai-org/glm-5.3"
505
+ || workersAi.model !== internalConfiguration.model || workersAi.thinking !== internalConfiguration.thinking)) {
506
+ throw new TypeError("Workers AI profile must match the pinned model and thinking");
507
+ }
508
+ if (["@cf/zai-org/glm-5.3", "kimi-k3", "mimo-v2.6-pro"].includes(internalConfiguration?.model) && !directInference) {
509
+ throw new TypeError("GLM-5.3 requires a Workers AI or gateway transport binding");
510
+ }
511
+ const endpoint = gateway !== undefined ? createGatewayResponses(gateway) : workersAi === undefined ? cloudflareEgress({
313
512
  binding: scopeCloudflareEgress(egress, subject),
314
- });
513
+ }) : createWorkersAiResponses(workersAi.ai);
514
+ const frontierEndpoint = routedInference ? cloudflareEgress({ binding: scopeCloudflareEgress(egress, subject) }) : undefined;
315
515
  const startup = deferred();
316
516
  const transport = Transport.hostManaged({
317
517
  ...endpoint,
318
- websocketPreconnect: true,
319
- createResponse(url, id, request) {
320
- return endpoint.createResponse(url, id, {
321
- ...request,
322
- body: responseControlsBody(request.body, internalRuntime?.responseControls),
323
- });
518
+ stateless: directInference,
519
+ websocketPreconnect: !directInference,
520
+ async createResponse(url, id, request) {
521
+ let selected = endpoint;
522
+ const body = responseControlsBody(request.body, internalRuntime?.responseControls);
523
+ if (routedInference) {
524
+ // This callback rechecks retained authority on EVERY request, including
525
+ // the root. Never inherit the root provider when a child pin is missing.
526
+ // The transport session is the shared lineage; threadId identifies the
527
+ // actual root/child branch registered by the Rust host bridge.
528
+ const routedSessionId = request.threadId ?? id;
529
+ const profile = await internalRuntime.inferenceForSession(routedSessionId);
530
+ if (profile?.native === true) {
531
+ if (internalRuntime?.preserveRootTransport !== true || profile.gateway !== undefined || profile.workersAi !== undefined) {
532
+ throw new Error("Native inference cannot replace a routed transport");
533
+ }
534
+ const parsed = JSON.parse(body);
535
+ if (!["gpt-6-astra", "gpt-6.1-sol", "gpt-6-luna"].includes(parsed.model)
536
+ || !["none", "low", "medium", "high", "xhigh", "max"].includes(parsed.reasoning?.effort)) {
537
+ throw new Error("Native inference requires a supported GPT model and thinking");
538
+ }
539
+ // Native roots and children retain Rust-owned live defaults/overrides.
540
+ // Only routed sessions have immutable model/thinking pins.
541
+ selected = frontierEndpoint;
542
+ } else {
543
+ if (!profile || typeof profile.model !== "string" || !["low", "medium", "high"].includes(profile.thinking)) {
544
+ throw new Error("Session inference route is missing or invalid");
545
+ }
546
+ if (routedSessionId === sessionId && (profile.model !== internalConfiguration?.model
547
+ || profile.thinking !== internalConfiguration?.thinking)) {
548
+ throw new Error("Root inference route conflicts with its pinned configuration");
549
+ }
550
+ const parsed = JSON.parse(body);
551
+ if (parsed.model !== profile.model || parsed.reasoning?.effort !== profile.thinking) {
552
+ throw new Error("Inference request conflicts with the session model or thinking pin");
553
+ }
554
+ if (profile.workersAi !== undefined && profile.gateway !== undefined) {
555
+ throw new Error("Session inference route has multiple transports");
556
+ }
557
+ if (profile.gateway !== undefined) {
558
+ if (profile.gateway.model !== profile.model || profile.gateway.reasoningEffort !== profile.thinking) {
559
+ throw new Error("Gateway profile conflicts with the session pin");
560
+ }
561
+ selected = createGatewayResponses(profile.gateway);
562
+ } else if (profile.workersAi !== undefined) {
563
+ if (profile.model !== "@cf/zai-org/glm-5.3" || profile.workersAi.model !== profile.model
564
+ || profile.workersAi.thinking !== profile.thinking) {
565
+ throw new Error("Workers AI profile conflicts with the session pin");
566
+ }
567
+ selected = createWorkersAiResponses(profile.workersAi.ai);
568
+ } else {
569
+ if (!["gpt-6-astra", "gpt-6.1-sol", "gpt-6-luna"].includes(profile.model)) {
570
+ throw new Error("Session model requires an explicit inference binding");
571
+ }
572
+ selected = frontierEndpoint;
573
+ }
574
+ if (url !== `${endpoint.apiBaseUrl}/responses`) {
575
+ throw new Error("Routed inference supports only full-history Responses requests");
576
+ }
577
+ url = `${selected.apiBaseUrl}/responses`;
578
+ }
579
+ }
580
+ return selected.createResponse(url, id, { ...request, body });
324
581
  },
325
582
  async createWebSocket(url, id, request) {
583
+ if (directInference) throw new Error("Direct inference threads require HTTP Responses transport");
584
+ if (routedInference && (request.threadId ?? id) !== sessionId) {
585
+ const profile = await internalRuntime.inferenceForSession(request.threadId ?? id);
586
+ if (profile?.native !== true || profile.gateway !== undefined || profile.workersAi !== undefined) {
587
+ throw new Error("Routed children require full-history HTTP Responses transport");
588
+ }
589
+ }
326
590
  try {
327
- const opened = await endpoint.createWebSocket(url, id, request);
591
+ const preparation = request.authorization === "preconnect" ? preparedConnection : undefined;
592
+ if (preparation !== undefined) preparedConnection = undefined;
593
+ const opened = await (preparation === undefined
594
+ ? endpoint.createWebSocket(url, id, request) : preparation.take(url, id, request));
328
595
  if (request.authorization === "preconnect") startup.resolve();
329
- return { ...opened, socket: responseControlsSocket(opened.socket, internalRuntime?.responseControls) };
596
+ return { ...opened, socket: responseControlsSocket(opened.socket, internalRuntime?.responseControls, internalRuntime?.onRequestShape, internalRuntime?.onResponseCreateSent) };
330
597
  } catch (error) {
331
598
  if (request.authorization === "preconnect") startup.reject(error);
332
599
  throw error;
@@ -336,7 +603,6 @@ async function createOwned(module, resolved, options, hostAgent, lifecycle) {
336
603
 
337
604
  const sessionReservation = prepareCloudflareAgentSession(sessionId, subject);
338
605
  const subagentSessions = cloudflareSubagentSessions(
339
- context.storage,
340
606
  sessionReservation,
341
607
  internalRuntime?.subagentLifecycle,
342
608
  );
@@ -353,6 +619,7 @@ async function createOwned(module, resolved, options, hostAgent, lifecycle) {
353
619
  fastMode: internalConfiguration.fast_mode,
354
620
  }),
355
621
  module,
622
+ rawApiEvents: internalRuntime?.rawApiEvents,
356
623
  toolMode: internalRuntime?.toolMode ?? "direct",
357
624
  codeEvaluator: internalRuntime?.codeEvaluator,
358
625
  [Symbol.for("nanocodex.browser.internalRuntime")]: {
@@ -360,17 +627,20 @@ async function createOwned(module, resolved, options, hostAgent, lifecycle) {
360
627
  subagentsEnabled: internalRuntime?.subagentsEnabled,
361
628
  subagentMaxConcurrency: internalRuntime?.subagentMaxConcurrency,
362
629
  subagentSessions,
630
+ subagentRouting: internalRuntime?.subagentRouting,
631
+ onSocketTiming: internalRuntime?.onSocketTiming,
363
632
  [CLOUDFLARE_SESSION_RESERVATION]: sessionReservation,
364
633
  },
365
634
  transport,
366
635
  sessionId,
367
636
  durability,
368
637
  durabilityId: stateId,
638
+ ...(forkResume === undefined ? {} : { resume: forkResume }),
369
639
  });
370
640
  // Managed voice needs the durable session before the separate Responses
371
641
  // relay is ready. Its preconnection remains owned by the host and a later
372
642
  // text turn consumes it through the same credential-checked transport.
373
- if (internalRuntime?.waitForPreconnect !== false) {
643
+ if (!directInference && internalRuntime?.waitForPreconnect !== false) {
374
644
  await withTimeout(
375
645
  startup.promise,
376
646
  STARTUP_TIMEOUT_MS,
@@ -554,12 +824,14 @@ function validateInternalConfiguration(configuration) {
554
824
  "reasoning_mode",
555
825
  "fast_mode",
556
826
  ].includes(key))
557
- || !["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "gpt-6-astra"]
827
+ || !["gpt-6.1-sol", "gpt-6-luna", "gpt-6-astra", "@cf/zai-org/glm-5.3", "kimi-k3", "mimo-v2.6-pro"]
558
828
  .includes(configuration.model)
559
829
  || !["none", "low", "medium", "high", "xhigh", "max"].includes(configuration.thinking)
560
830
  || !["standard", "pro"].includes(configuration.reasoning_mode)
561
831
  || typeof configuration.fast_mode !== "boolean"
562
- || (configuration.model === "gpt-6-astra" && configuration.thinking === "none")) {
832
+ || (["@cf/zai-org/glm-5.3", "kimi-k3", "mimo-v2.6-pro"].includes(configuration.model)
833
+ && (!(configuration.model === "kimi-k3" ? ["low", "high"] : ["low", "medium", "high"]).includes(configuration.thinking) || configuration.reasoning_mode !== "standard"))
834
+ || (["gpt-6-astra", "gpt-6.1-sol"].includes(configuration.model) && configuration.thinking === "none")) {
563
835
  throw new TypeError("Cloudflare Agent internal configuration is invalid");
564
836
  }
565
837
  }
@@ -629,42 +901,28 @@ function initializeAgentStorage(storage) {
629
901
  state_id TEXT NOT NULL UNIQUE
630
902
  )
631
903
  `);
632
- storage.sql.exec(`
633
- CREATE TABLE IF NOT EXISTS nanocodex_cloudflare_subagents (
634
- session_id TEXT PRIMARY KEY,
635
- agent_id TEXT NOT NULL UNIQUE,
636
- descriptor_json TEXT NOT NULL,
637
- host_context_ref TEXT
638
- )
639
- `);
640
- const subagentColumns = storage.sql.exec(
641
- "PRAGMA table_info('nanocodex_cloudflare_subagents')",
642
- ).toArray();
643
- if (!subagentColumns.some(({ name }) => name === "host_context_ref")) {
644
- storage.sql.exec(
645
- "ALTER TABLE nanocodex_cloudflare_subagents ADD COLUMN host_context_ref TEXT",
646
- );
647
- }
904
+ // These tables belonged to durable children. Root records and identity remain
905
+ // untouched; obsolete child data must never restore a task tree on startup.
906
+ storage.sql.exec("DROP TABLE IF EXISTS nanocodex_cloudflare_subagents");
907
+ storage.sql.exec("DROP TABLE IF EXISTS nanocodex_cloudflare_subagent_checkpoints");
648
908
  }
649
909
 
650
- function cloudflareSubagentSessions(storage, reservation, lifecycle) {
651
- const restoredHostContextRefs = new Map();
910
+ function cloudflareSubagentSessions(reservation, lifecycle) {
911
+ const bindings = new Map();
652
912
  return Object.freeze({
653
- restore() {
654
- const restored = storage.sql.exec(
655
- "SELECT descriptor_json, host_context_ref FROM nanocodex_cloudflare_subagents",
656
- ).toArray().map(({ descriptor_json, host_context_ref }) => {
657
- const descriptor = Object.freeze(JSON.parse(descriptor_json));
658
- restoredHostContextRefs.set(
659
- descriptor.sessionId,
660
- host_context_ref === null ? undefined : host_context_ref,
661
- );
662
- return descriptor;
663
- });
664
- return Object.freeze(restored);
665
- },
666
- hostContextRef(sessionId) {
667
- return restoredHostContextRefs.get(sessionId);
913
+ bindingDescriptor(sessionId, descriptor, hostContextRef) {
914
+ const retained = bindings.get(sessionId);
915
+ if (retained === undefined) return descriptor;
916
+ const original = retained.descriptor;
917
+ const attachesInitialProvenance = retained.hostContextRef === undefined
918
+ && typeof hostContextRef === "string" && descriptor.task === original.task;
919
+ if (original.sessionId !== descriptor.sessionId || original.agentId !== descriptor.agentId
920
+ || original.parentAgentId !== descriptor.parentAgentId || original.role !== descriptor.role
921
+ || (retained.hostContextRef !== hostContextRef && !attachesInitialProvenance)) {
922
+ throw new Error("Subagent binding identity or host context changed");
923
+ }
924
+ // Delegation replaces the Rust task, not its spawning-turn authority.
925
+ return original;
668
926
  },
669
927
  bind(sessionId, descriptor, hostContextRef) {
670
928
  if (!mayBindCloudflareSubagentSession(reservation)) return;
@@ -672,53 +930,36 @@ function cloudflareSubagentSessions(storage, reservation, lifecycle) {
672
930
  && (typeof hostContextRef !== "string" || hostContextRef.length === 0)) {
673
931
  throw new TypeError("subagent host context ref must be a non-empty string when supplied");
674
932
  }
675
- const type = restoredHostContextRefs.has(sessionId) ? "reconstruct" : "bind";
676
- storage.transactionSync(() => {
677
- storage.sql.exec(
678
- `INSERT INTO nanocodex_cloudflare_subagents
679
- (session_id, agent_id, descriptor_json, host_context_ref) VALUES (?, ?, ?, ?)
680
- ON CONFLICT (session_id) DO UPDATE SET
681
- agent_id = excluded.agent_id,
682
- descriptor_json = excluded.descriptor_json,
683
- host_context_ref = excluded.host_context_ref`,
684
- sessionId,
685
- descriptor.agentId,
686
- JSON.stringify(descriptor),
687
- hostContextRef ?? null,
688
- );
689
- notifySubagentLifecycle(lifecycle, {
690
- type,
691
- rootSessionId: reservation.sessionId,
692
- sessionId,
693
- descriptor,
694
- hostContextRef,
695
- });
933
+ notifySubagentLifecycle(lifecycle, {
934
+ type: "bind", rootSessionId: reservation.sessionId,
935
+ sessionId, descriptor, hostContextRef,
696
936
  });
697
- restoredHostContextRefs.delete(sessionId);
937
+ bindings.set(sessionId, { descriptor, hostContextRef });
698
938
  },
699
939
  release(sessionId, hostContextRef) {
700
940
  if (!mayReleaseCloudflareSubagentSession(reservation)) return;
701
- storage.transactionSync(() => {
702
- const retained = storage.sql.exec(
703
- `SELECT 1 AS retained FROM nanocodex_cloudflare_subagents
704
- WHERE session_id = ? AND host_context_ref IS ?`,
705
- sessionId,
706
- hostContextRef ?? null,
707
- ).toArray();
708
- if (retained.length === 0) return;
941
+ const retained = bindings.get(sessionId);
942
+ if (retained === undefined || retained.hostContextRef !== hostContextRef) return;
943
+ // Closing a parent releases its complete live subtree.
944
+ const removed = new Set([retained.descriptor.agentId]);
945
+ let changed;
946
+ do {
947
+ changed = false;
948
+ for (const { descriptor } of bindings.values()) {
949
+ if (removed.has(descriptor.parentAgentId) && !removed.has(descriptor.agentId)) {
950
+ removed.add(descriptor.agentId);
951
+ changed = true;
952
+ }
953
+ }
954
+ } while (changed);
955
+ for (const { descriptor, hostContextRef: context } of bindings.values()) {
956
+ if (!removed.has(descriptor.agentId)) continue;
709
957
  notifySubagentLifecycle(lifecycle, {
710
- type: "release",
711
- rootSessionId: reservation.sessionId,
712
- sessionId,
713
- hostContextRef,
958
+ type: "release", rootSessionId: reservation.sessionId,
959
+ sessionId: descriptor.sessionId, hostContextRef: context,
714
960
  });
715
- storage.sql.exec(
716
- `DELETE FROM nanocodex_cloudflare_subagents
717
- WHERE session_id = ? AND host_context_ref IS ?`,
718
- sessionId,
719
- hostContextRef ?? null,
720
- );
721
- });
961
+ bindings.delete(descriptor.sessionId);
962
+ }
722
963
  },
723
964
  });
724
965
  }
@@ -788,3 +1029,20 @@ async function withTimeout(promise, timeoutMs, message) {
788
1029
  clearTimeout(timer);
789
1030
  }
790
1031
  }
1032
+
1033
+ /** Read a single Rust-owned receipt without loading/fencing the execution. */
1034
+ export function steerReceipt(owner, operationId, messageId) {
1035
+ const { storage } = resolveContext(owner);
1036
+ const tables = storage.sql.exec("SELECT name FROM sqlite_master WHERE type = 'table' AND name IN ('nanocodex_cloudflare_durability', 'nanocodex_durable_states')").toArray();
1037
+ if (tables.length !== 2) return null;
1038
+ const stateId = storedStateId(storage);
1039
+ if (stateId === undefined) return null;
1040
+ const path = `$.nanocodex_durable_state.operations.${JSON.stringify(operationId)}.steer_receipts.${JSON.stringify(messageId)}`;
1041
+ const row = storage.sql.exec("SELECT json_extract(payload, ?) AS receipt FROM nanocodex_durable_states WHERE state_id = ?", path, stateId).toArray()[0];
1042
+ if (row?.receipt == null) return null;
1043
+ const receipt = JSON.parse(row.receipt);
1044
+ if (!/^[a-f0-9]{64}$/.test(receipt.input_key) || !Number.isSafeInteger(receipt.index) || receipt.index < 1 || typeof receipt.withdrawn !== "boolean") {
1045
+ throw new Error("invalid durable steer receipt");
1046
+ }
1047
+ return Object.freeze(receipt);
1048
+ }
@@ -0,0 +1,15 @@
1
+ export const AGENT_MODELS: readonly ["gpt-6.1-sol", "gpt-6-luna", "gpt-6-astra", "@cf/zai-org/glm-5.3", "kimi-k3", "mimo-v2.6-pro"];
2
+ export const AGENT_THINKING: readonly ["none", "low", "medium", "high", "xhigh", "max"];
3
+ export const AGENT_REASONING_MODES: readonly ["standard", "pro"];
4
+ export type ManagedAgentSettings = Readonly<{ model: typeof AGENT_MODELS[number]; thinking: typeof AGENT_THINKING[number]; reasoning_mode: typeof AGENT_REASONING_MODES[number]; fast_mode: boolean }>;
5
+ export type ManagedAgentSettingsPatch = Partial<ManagedAgentSettings>;
6
+ export const DEFAULT_AGENT_SETTINGS: ManagedAgentSettings;
7
+ export function parseAgentSettingsQuery(search: URLSearchParams): ManagedAgentSettings;
8
+ export function agentSettingsQuery(settings: ManagedAgentSettings): URLSearchParams;
9
+ export function parseAgentSettingsPatch(value: unknown): ManagedAgentSettingsPatch;
10
+ export function parseCompleteAgentSettings(value: unknown): ManagedAgentSettings;
11
+ export function validateAgentSettings(settings: ManagedAgentSettings): ManagedAgentSettings;
12
+ export function validateAgentAdmissionSettings(settings: ManagedAgentSettings): ManagedAgentSettings;
13
+ export function isAgentModel(value: unknown): value is ManagedAgentSettings["model"];
14
+ export function isAgentThinking(value: unknown): value is ManagedAgentSettings["thinking"];
15
+ export function isAgentReasoningMode(value: unknown): value is ManagedAgentSettings["reasoning_mode"];