@tangle-network/agent-runtime 0.15.0 → 0.15.1

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.
package/dist/index.d.ts CHANGED
@@ -250,6 +250,42 @@ interface EventRecord {
250
250
  payload: unknown;
251
251
  emittedAt: string;
252
252
  }
253
+ /**
254
+ * A pointer to a substrate run that outlives the worker isolate — the sandbox
255
+ * container is orchestrator-managed and survives a Worker death or a Durable
256
+ * Object migration. Persisted on the run row so a fresh supervisor re-attaches
257
+ * to the in-flight run instead of re-prompting.
258
+ */
259
+ interface RunHandle {
260
+ /** Which substrate owns the run. `sandbox` runs are reconnectable;
261
+ * `tcloud` runs have no cross-process replay endpoint. */
262
+ kind: 'sandbox' | 'tcloud';
263
+ /** Orchestrator-managed sandbox id — stable across worker isolates. */
264
+ sandboxId?: string;
265
+ /** Sandbox conversation/session id. */
266
+ sessionId?: string;
267
+ /** The substrate run id (the sandbox SDK's `executionId`). The replay
268
+ * endpoint keys on it. */
269
+ runId?: string;
270
+ /** Lifecycle of the substrate run as last observed. */
271
+ status: 'running' | 'completed' | 'failed';
272
+ /** Last substrate event id seen — the adapter's reconnect cursor. */
273
+ cursor?: string;
274
+ }
275
+ /**
276
+ * One event in a run's ordered, replayable stream log. The supervisor drains
277
+ * a run's event stream into this log as it flows, so replay is guaranteed by
278
+ * the substrate rather than by the sandbox runtime's own buffering.
279
+ */
280
+ interface StreamEventRecord {
281
+ runId: string;
282
+ /** Monotonic 0-based sequence — the store's ordering + cursor. */
283
+ seq: number;
284
+ /** Producer-supplied stable id — the dedup key and the substrate cursor. */
285
+ eventId: string;
286
+ payload: unknown;
287
+ appendedAt: string;
288
+ }
253
289
  type RunStatus = 'pending' | 'running' | 'completed' | 'failed' | 'suspended';
254
290
  interface RunOutcome {
255
291
  pass?: boolean;
@@ -284,6 +320,9 @@ interface RunRecord {
284
320
  leaseExpiresAt?: string;
285
321
  outcome?: RunOutcome;
286
322
  stepCount: number;
323
+ /** Pointer to the in-flight substrate run, when one has been registered.
324
+ * A fresh supervisor re-attaches by it. */
325
+ handle?: RunHandle;
287
326
  }
288
327
  /**
289
328
  * The durable-run substrate. Implementations: in-memory (dev), file-system
@@ -367,6 +406,31 @@ interface DurableRunStore {
367
406
  }>;
368
407
  /** Load the cached event payload if it has been emitted. */
369
408
  loadEvent(runId: string, key: string): Promise<EventRecord | undefined>;
409
+ /**
410
+ * Append an event to the run's ordered stream log. The store assigns the
411
+ * monotonic `seq`. Idempotent on `eventId`: re-appending a known id is a
412
+ * no-op that returns the existing record under `accepted: false` — so an
413
+ * adapter that re-yields a boundary event on reconnect cannot double-log.
414
+ */
415
+ appendStreamEvent(input: {
416
+ runId: string;
417
+ eventId: string;
418
+ payload: unknown;
419
+ }): Promise<{
420
+ accepted: boolean;
421
+ record: StreamEventRecord;
422
+ }>;
423
+ /**
424
+ * Read the stream log in `seq` order. `afterSeq` (exclusive) resumes a
425
+ * reader from a cursor; omit for the whole log.
426
+ */
427
+ readStreamEvents(runId: string, afterSeq?: number): Promise<ReadonlyArray<StreamEventRecord>>;
428
+ /** Persist the run handle — the pointer a fresh supervisor re-attaches by.
429
+ * One per run; overwrites. */
430
+ setRunHandle(input: {
431
+ runId: string;
432
+ handle: RunHandle;
433
+ }): Promise<void>;
370
434
  /** Cleanup hook for in-memory / fs stores; no-op for D1. Idempotent. */
371
435
  close(): Promise<void>;
372
436
  }
@@ -696,6 +760,16 @@ declare class D1DurableRunStore implements DurableRunStore {
696
760
  payload: unknown;
697
761
  }): ReturnType<DurableRunStore['emitEvent']>;
698
762
  loadEvent(runId: string, key: string): Promise<EventRecord | undefined>;
763
+ appendStreamEvent(input: {
764
+ runId: string;
765
+ eventId: string;
766
+ payload: unknown;
767
+ }): ReturnType<DurableRunStore['appendStreamEvent']>;
768
+ readStreamEvents(runId: string, afterSeq?: number): Promise<ReadonlyArray<StreamEventRecord>>;
769
+ setRunHandle(input: {
770
+ runId: string;
771
+ handle: RunHandle;
772
+ }): Promise<void>;
699
773
  close(): Promise<void>;
700
774
  /** Inspect the currently-applied schema version. */
701
775
  getSchemaVersion(): Promise<number | undefined>;
@@ -774,7 +848,18 @@ declare class FileSystemDurableRunStore implements DurableRunStore {
774
848
  payload: unknown;
775
849
  }): ReturnType<DurableRunStore['emitEvent']>;
776
850
  loadEvent(runId: string, key: string): Promise<EventRecord | undefined>;
851
+ appendStreamEvent(input: {
852
+ runId: string;
853
+ eventId: string;
854
+ payload: unknown;
855
+ }): ReturnType<DurableRunStore['appendStreamEvent']>;
856
+ readStreamEvents(runId: string, afterSeq?: number): Promise<ReadonlyArray<StreamEventRecord>>;
857
+ setRunHandle(input: {
858
+ runId: string;
859
+ handle: RunHandle;
860
+ }): Promise<void>;
777
861
  close(): Promise<void>;
862
+ private readStreamEventsRaw;
778
863
  /** @internal — used by tests to list runs in the store. */
779
864
  _listRunIds(): Promise<string[]>;
780
865
  private runDir;
@@ -875,6 +960,16 @@ declare class InMemoryDurableRunStore implements DurableRunStore {
875
960
  payload: unknown;
876
961
  }): ReturnType<DurableRunStore['emitEvent']>;
877
962
  loadEvent(runId: string, key: string): Promise<EventRecord | undefined>;
963
+ appendStreamEvent(input: {
964
+ runId: string;
965
+ eventId: string;
966
+ payload: unknown;
967
+ }): ReturnType<DurableRunStore['appendStreamEvent']>;
968
+ readStreamEvents(runId: string, afterSeq?: number): Promise<ReadonlyArray<StreamEventRecord>>;
969
+ setRunHandle(input: {
970
+ runId: string;
971
+ handle: RunHandle;
972
+ }): Promise<void>;
878
973
  close(): Promise<void>;
879
974
  /** @internal — used by tests to inspect lease metadata. */
880
975
  _inspect(runId: string): RunRecord | undefined;
@@ -983,8 +1078,178 @@ declare function runDurable<TResult>(input: RunDurableInput<TResult>): Promise<R
983
1078
  * this string. Bump it on every backwards-incompatible change AND add a new
984
1079
  * migration entry to durable_schema_info instead of mutating prior rows.
985
1080
  */
986
- declare const DURABLE_SCHEMA_VERSION = 1;
987
- declare const DURABLE_SCHEMA_SQL = "-- Durable-run substrate \u2014 versioned schema for D1 / SQLite.\n--\n-- Apply once per database. Subsequent migrations append; never rewrite a\n-- prior version. See `durable_schema_info` for the migration trail.\n--\n-- Concurrency notes for D1:\n-- - SQLite supports UNIQUE constraints for first-emit-wins (`durable_events`\n-- PK is (run_id, key) \u2014 duplicate insert raises, caller treats as \"already\n-- emitted\").\n-- - Lease takeover happens via a conditional UPDATE: we only claim the lease\n-- if (lease_holder_id IS NULL OR lease_expires_at < :now) \u2014 atomic under\n-- SQLite's row-level locking.\n-- - All timestamps stored as ISO-8601 TEXT for cross-platform consistency.\n-- - `result_json` / `error_json` / `outcome_json` / `payload_json` are\n-- JSON-encoded TEXT; the application enforces canonical-JSON discipline at\n-- the boundary so the store stays type-agnostic.\n\nCREATE TABLE IF NOT EXISTS durable_schema_info (\n version INTEGER PRIMARY KEY,\n applied_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS durable_runs (\n run_id TEXT PRIMARY KEY,\n manifest_hash TEXT NOT NULL,\n project_id TEXT NOT NULL,\n scenario_id TEXT,\n status TEXT NOT NULL CHECK (status IN ('pending','running','completed','failed','suspended')),\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n completed_at TEXT,\n lease_holder_id TEXT,\n lease_expires_at TEXT,\n outcome_json TEXT,\n step_count INTEGER NOT NULL DEFAULT 0\n);\n\nCREATE INDEX IF NOT EXISTS idx_durable_runs_project_status ON durable_runs(project_id, status);\nCREATE INDEX IF NOT EXISTS idx_durable_runs_lease_expires ON durable_runs(lease_expires_at);\n\nCREATE TABLE IF NOT EXISTS durable_steps (\n run_id TEXT NOT NULL,\n step_index INTEGER NOT NULL,\n intent TEXT NOT NULL,\n kind TEXT NOT NULL,\n input_hash TEXT NOT NULL DEFAULT '',\n status TEXT NOT NULL CHECK (status IN ('pending','running','completed','failed')),\n attempts INTEGER NOT NULL DEFAULT 0,\n result_json TEXT,\n error_json TEXT,\n started_at TEXT,\n completed_at TEXT,\n PRIMARY KEY (run_id, step_index)\n);\n\nCREATE INDEX IF NOT EXISTS idx_durable_steps_status ON durable_steps(run_id, status);\n\nCREATE TABLE IF NOT EXISTS durable_events (\n run_id TEXT NOT NULL,\n key TEXT NOT NULL,\n payload_json TEXT,\n emitted_at TEXT NOT NULL,\n PRIMARY KEY (run_id, key)\n);\n\nINSERT OR IGNORE INTO durable_schema_info (version, applied_at)\nVALUES (1, strftime('%Y-%m-%dT%H:%M:%fZ', 'now'));\n";
1081
+ declare const DURABLE_SCHEMA_VERSION = 2;
1082
+ declare const DURABLE_SCHEMA_SQL = "-- Durable-run substrate \u2014 versioned schema for D1 / SQLite.\n--\n-- Apply once per database. Subsequent migrations append; never rewrite a\n-- prior version. See `durable_schema_info` for the migration trail.\n--\n-- Concurrency notes for D1:\n-- - SQLite supports UNIQUE constraints for first-emit-wins (`durable_events`\n-- PK is (run_id, key) \u2014 duplicate insert raises, caller treats as \"already\n-- emitted\").\n-- - Lease takeover happens via a conditional UPDATE: we only claim the lease\n-- if (lease_holder_id IS NULL OR lease_expires_at < :now) \u2014 atomic under\n-- SQLite's row-level locking.\n-- - All timestamps stored as ISO-8601 TEXT for cross-platform consistency.\n-- - `result_json` / `error_json` / `outcome_json` / `payload_json` are\n-- JSON-encoded TEXT; the application enforces canonical-JSON discipline at\n-- the boundary so the store stays type-agnostic.\n\nCREATE TABLE IF NOT EXISTS durable_schema_info (\n version INTEGER PRIMARY KEY,\n applied_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS durable_runs (\n run_id TEXT PRIMARY KEY,\n manifest_hash TEXT NOT NULL,\n project_id TEXT NOT NULL,\n scenario_id TEXT,\n status TEXT NOT NULL CHECK (status IN ('pending','running','completed','failed','suspended')),\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n completed_at TEXT,\n lease_holder_id TEXT,\n lease_expires_at TEXT,\n outcome_json TEXT,\n step_count INTEGER NOT NULL DEFAULT 0\n);\n\nCREATE INDEX IF NOT EXISTS idx_durable_runs_project_status ON durable_runs(project_id, status);\nCREATE INDEX IF NOT EXISTS idx_durable_runs_lease_expires ON durable_runs(lease_expires_at);\n\nCREATE TABLE IF NOT EXISTS durable_steps (\n run_id TEXT NOT NULL,\n step_index INTEGER NOT NULL,\n intent TEXT NOT NULL,\n kind TEXT NOT NULL,\n input_hash TEXT NOT NULL DEFAULT '',\n status TEXT NOT NULL CHECK (status IN ('pending','running','completed','failed')),\n attempts INTEGER NOT NULL DEFAULT 0,\n result_json TEXT,\n error_json TEXT,\n started_at TEXT,\n completed_at TEXT,\n PRIMARY KEY (run_id, step_index)\n);\n\nCREATE INDEX IF NOT EXISTS idx_durable_steps_status ON durable_steps(run_id, status);\n\nCREATE TABLE IF NOT EXISTS durable_events (\n run_id TEXT NOT NULL,\n key TEXT NOT NULL,\n payload_json TEXT,\n emitted_at TEXT NOT NULL,\n PRIMARY KEY (run_id, key)\n);\n\nINSERT OR IGNORE INTO durable_schema_info (version, applied_at)\nVALUES (1, strftime('%Y-%m-%dT%H:%M:%fZ', 'now'));\n\n-- \u2500\u2500 Migration v2 \u2014 durable event-stream log + run handle \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n-- Run once on a database created at v1. `ALTER TABLE` is not idempotent; the\n-- version trail in `durable_schema_info` is how migrations are sequenced \u2014\n-- never by blind re-execution of this block.\n--\n-- - `durable_stream_events` is the ordered, replayable per-run event log.\n-- `seq` is the store-assigned monotonic cursor; the UNIQUE index on\n-- (run_id, event_id) makes appends idempotent \u2014 a reconnecting adapter\n-- that re-yields a boundary event cannot double-log it.\n-- - `durable_runs.handle_json` is the pointer (sandbox + substrate run id +\n-- cursor) a fresh supervisor re-attaches by.\n\nALTER TABLE durable_runs ADD COLUMN handle_json TEXT;\n\nCREATE TABLE IF NOT EXISTS durable_stream_events (\n run_id TEXT NOT NULL,\n seq INTEGER NOT NULL,\n event_id TEXT NOT NULL,\n payload_json TEXT,\n appended_at TEXT NOT NULL,\n PRIMARY KEY (run_id, seq)\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS idx_durable_stream_events_event_id\n ON durable_stream_events(run_id, event_id);\n\nINSERT OR IGNORE INTO durable_schema_info (version, applied_at)\nVALUES (2, strftime('%Y-%m-%dT%H:%M:%fZ', 'now'));\n";
1083
+
1084
+ /**
1085
+ * `runSupervisedTurn` — relocates the durability boundary off the ephemeral
1086
+ * worker isolate.
1087
+ *
1088
+ * `runDurableTurn` replays a *completed* turn; an interrupted turn re-runs.
1089
+ * `runSupervisedTurn` closes that gap for sandbox-backed runs: the sandbox
1090
+ * container is orchestrator-managed and outlives the worker, so instead of
1091
+ * re-prompting, a fresh supervisor re-attaches to the in-flight substrate run
1092
+ * and resumes draining its event stream.
1093
+ *
1094
+ * Durability is owned by the substrate, not hoped-for from the sandbox. The
1095
+ * supervisor drains every event into the store's stream log as it flows
1096
+ * (`appendStreamEvent`), persists the reconnect pointer the instant the
1097
+ * substrate yields it (`setRunHandle`), and heartbeats the lease. A fresh
1098
+ * supervisor reads the log for its cursor and calls `adapter.attach` to
1099
+ * resume strictly after it — the append's idempotency on `eventId` dedups
1100
+ * the reconnect seam, so no event is lost and none is delivered twice.
1101
+ *
1102
+ * The platform-agnostic core is here; `SessionSupervisorDO` hosts it on a
1103
+ * Cloudflare Durable Object. The reconnect glue is one typed contract —
1104
+ * `SandboxReconnectAdapter` — implemented once per substrate, never per
1105
+ * product.
1106
+ */
1107
+
1108
+ /** One event drained from a supervised run. */
1109
+ interface SupervisedEvent<TEvent> {
1110
+ /** Stable substrate id — the dedup key and the reconnect cursor. */
1111
+ eventId: string;
1112
+ payload: TEvent;
1113
+ /**
1114
+ * The substrate run handle, carried on the first frame(s) once the run id
1115
+ * is known. The supervisor persists it so a fresh supervisor can re-attach.
1116
+ * Omit on later frames; the last non-undefined handle wins.
1117
+ */
1118
+ handle?: RunHandle;
1119
+ }
1120
+ /**
1121
+ * Product-supplied glue to a reconnectable substrate run. The dangerous
1122
+ * reconnect logic — re-attaching to a live distributed run — lives behind
1123
+ * this one typed contract: implement it once per substrate (the sandbox SDK,
1124
+ * etc.), never per product.
1125
+ *
1126
+ * Conformance (asserted by `supervisor.test.ts`):
1127
+ * - `start()` yields the run's events; at least one early event carries a
1128
+ * `handle` with `status: 'running'` and a defined `runId`.
1129
+ * - `attach(handle, afterEventId)` yields only events strictly after
1130
+ * `afterEventId`, and terminates cleanly when the run has no more.
1131
+ * - `eventId`s are unique within a run.
1132
+ */
1133
+ interface SandboxReconnectAdapter<TEvent> {
1134
+ /** Begin a fresh substrate run. */
1135
+ start(): AsyncIterable<SupervisedEvent<TEvent>>;
1136
+ /**
1137
+ * Re-attach to an in-flight run, resuming strictly after `afterEventId`
1138
+ * (`undefined` → from the first event).
1139
+ */
1140
+ attach(handle: RunHandle, afterEventId: string | undefined): AsyncIterable<SupervisedEvent<TEvent>>;
1141
+ }
1142
+ /** How the supervised turn resolved. */
1143
+ type SupervisedRunMode = 'fresh' | 'resumed' | 'replayed';
1144
+ interface RunSupervisorOptions<TEvent> {
1145
+ store: DurableRunStore;
1146
+ /** Stable per-turn run id — the same id on a retry is what enables both
1147
+ * replay (completed turn) and resume (in-flight turn). */
1148
+ runId: string;
1149
+ manifest: DurableRunManifest;
1150
+ /** Stable per-isolate worker id. */
1151
+ workerId: string;
1152
+ adapter: SandboxReconnectAdapter<TEvent>;
1153
+ /** Lease window in ms. Default 60_000 — deliberately short: the heartbeat
1154
+ * keeps an actively-draining supervisor's lease alive, so an abandoned
1155
+ * supervisor's lease lapses fast and a fresh supervisor can take over. */
1156
+ leaseMs?: number;
1157
+ /** Renew the lease at most this often while draining. Default 30_000 —
1158
+ * must be below `leaseMs` or an active drain loses its own lease. */
1159
+ heartbeatMs?: number;
1160
+ /** Human-readable step label. Default `turn`. */
1161
+ intent?: string;
1162
+ /** Time source override — tests pin this for deterministic heartbeats. */
1163
+ now?: () => number;
1164
+ }
1165
+ interface SupervisedRunHandle<TEvent> {
1166
+ /** Drop-in stream. Fresh forwards live events; resumed re-yields the logged
1167
+ * prefix then forwards live events; replayed re-yields the full log. */
1168
+ stream: AsyncGenerator<TEvent, void, unknown>;
1169
+ /** Which path ran. Valid after `stream` drains. */
1170
+ mode(): SupervisedRunMode;
1171
+ /** The durable RunRecord for the turn. Valid after `stream` drains. */
1172
+ record(): RunRecord | undefined;
1173
+ }
1174
+ declare function runSupervisedTurn<TEvent>(options: RunSupervisorOptions<TEvent>): SupervisedRunHandle<TEvent>;
1175
+
1176
+ /**
1177
+ * `SessionSupervisorDO` — the Cloudflare Durable Object host for
1178
+ * `runSupervisedTurn`.
1179
+ *
1180
+ * A stateless Worker isolate is the wrong place to own a 15-minute run: it
1181
+ * dies on a deploy roll or CPU limit. A Durable Object is addressable by
1182
+ * session id and survives across requests — it is the right home for the
1183
+ * supervisor. This host is deliberately thin: all the durability logic lives
1184
+ * in the platform-agnostic `runSupervisedTurn`; the DO only hosts it and
1185
+ * uses an `alarm()` to re-attach a run the response stream abandoned.
1186
+ *
1187
+ * - `fetch` resolves the run, records it, arms the orphan-check alarm, and
1188
+ * streams the supervised events back. If the client disconnects, the
1189
+ * supervisor stops being pulled and its short lease lapses.
1190
+ * - `alarm()` is the recovery mechanism: it finds a recorded-but-unfinished
1191
+ * run and re-drives `runSupervisedTurn` headlessly to completion (events
1192
+ * land in the durable log; a later `fetch` replays them). A run still held
1193
+ * by a live `fetch` raises `DurableRunLeaseHeldError` — not orphaned, so
1194
+ * the alarm just re-arms.
1195
+ *
1196
+ * Structural CF types (`DurableObjectStateLike`) are defined locally so
1197
+ * agent-runtime keeps no dependency on `@cloudflare/workers-types` — the same
1198
+ * discipline as `D1DatabaseLike` in `d1-store.ts`.
1199
+ */
1200
+
1201
+ /** Minimal Durable Object storage surface this host uses. Compatible with
1202
+ * Cloudflare's `DurableObjectStorage`. */
1203
+ interface DurableObjectStorageLike {
1204
+ get<T = unknown>(key: string): Promise<T | undefined>;
1205
+ put<T = unknown>(key: string, value: T): Promise<void>;
1206
+ delete(key: string): Promise<boolean>;
1207
+ /** Schedule the next `alarm()` invocation at an epoch-ms time. */
1208
+ setAlarm(scheduledTime: number): Promise<void>;
1209
+ }
1210
+ /** Minimal Durable Object state surface — the `state` ctor argument. */
1211
+ interface DurableObjectStateLike {
1212
+ storage: DurableObjectStorageLike;
1213
+ }
1214
+ /**
1215
+ * Product-supplied wiring for the host. `resolveRun` / `resolveOrphan` build
1216
+ * the supervisor inputs (store, adapter, manifest) — the host owns no
1217
+ * product policy.
1218
+ */
1219
+ interface SupervisorHostConfig<TEvent, TEnv> {
1220
+ /** Build supervisor inputs for an incoming request. `undefined` → 404. */
1221
+ resolveRun(request: Request, env: TEnv, state: DurableObjectStateLike): Promise<RunSupervisorOptions<TEvent> | undefined>;
1222
+ /** Rebuild supervisor inputs for an orphan re-attach, from the recorded
1223
+ * runId. `undefined` → the run is untrackable; the host stops tracking it. */
1224
+ resolveOrphan(runId: string, env: TEnv, state: DurableObjectStateLike): Promise<RunSupervisorOptions<TEvent> | undefined>;
1225
+ /** Serialize one event into a response-stream chunk (an SSE or NDJSON
1226
+ * line — the product owns the framing). */
1227
+ encodeEvent(event: TEvent): string;
1228
+ /** Delay before the orphan-check alarm fires. Default 60_000. */
1229
+ orphanCheckMs?: number;
1230
+ /** Time source — tests pin this. */
1231
+ now?: () => number;
1232
+ }
1233
+ /** The host instance surface — what a Cloudflare DO runtime invokes. */
1234
+ interface SessionSupervisorDO {
1235
+ fetch(request: Request): Promise<Response>;
1236
+ alarm(): Promise<void>;
1237
+ }
1238
+ /**
1239
+ * Build the `SessionSupervisorDO` class for a product. Export the result from
1240
+ * the Worker entrypoint and bind it in `wrangler.toml`:
1241
+ *
1242
+ * export const SessionSupervisor = createSessionSupervisorDO(config)
1243
+ *
1244
+ * # wrangler.toml
1245
+ * [[durable_objects.bindings]]
1246
+ * name = "SESSION_SUPERVISOR"
1247
+ * class_name = "SessionSupervisor"
1248
+ * [[migrations]]
1249
+ * tag = "v1"
1250
+ * new_classes = ["SessionSupervisor"]
1251
+ */
1252
+ declare function createSessionSupervisorDO<TEvent, TEnv>(config: SupervisorHostConfig<TEvent, TEnv>): new (state: DurableObjectStateLike, env: TEnv) => SessionSupervisorDO;
988
1253
 
989
1254
  /**
990
1255
  * Cloudflare Workflows integration for the durable-run substrate.
@@ -1206,6 +1471,99 @@ interface ClassifyIntentOptions {
1206
1471
  */
1207
1472
  declare function classifyIntent(profile: AgentProfile, message: string, opts?: ClassifyIntentOptions): ClassifyIntentResult;
1208
1473
 
1474
+ /**
1475
+ * @stable
1476
+ *
1477
+ * Chat-model resolution + catalog validation — the shared primitive every
1478
+ * product chat handler needs and was, until now, hand-rolling. Lifts the
1479
+ * router `/v1/models` fetch, the fail-closed id validation, and the
1480
+ * precedence resolver out of four near-identical per-repo copies.
1481
+ *
1482
+ * Policy-free by design: callers pass their own precedence order
1483
+ * (`resolveChatModel`) and their own known-good `allowlist`
1484
+ * (`validateChatModelId`), so each product keeps its resolution policy while
1485
+ * sharing the catalog fetch, the malformed-id guard, and the fail-closed
1486
+ * admission rule. No React, no `process.env` assumption — `env` is an
1487
+ * explicit narrow record so this runs unchanged in Node and in Workers.
1488
+ */
1489
+ /**
1490
+ * A model entry as returned by the Tangle Router `/v1/models` endpoint.
1491
+ * Intentionally minimal — only the fields resolution + validation read.
1492
+ */
1493
+ interface ModelInfo {
1494
+ id: string;
1495
+ name?: string;
1496
+ description?: string;
1497
+ /** Provider slug, when the router exposes it (`provider` or `_provider`). */
1498
+ provider?: string;
1499
+ _provider?: string;
1500
+ architecture?: {
1501
+ modality?: string;
1502
+ input_modalities?: string[];
1503
+ output_modalities?: string[];
1504
+ };
1505
+ }
1506
+ /** Env keys the router base URL is resolved from. */
1507
+ interface RouterEnv {
1508
+ TANGLE_ROUTER_URL?: string;
1509
+ TANGLE_ROUTER_BASE_URL?: string;
1510
+ }
1511
+ declare const DEFAULT_ROUTER_BASE_URL = "https://router.tangle.tools";
1512
+ /** Resolve the router base URL from env, normalised — no trailing `/v1` or `/`. */
1513
+ declare function resolveRouterBaseUrl(env?: RouterEnv): string;
1514
+ /**
1515
+ * Fetch the model catalog from the router's `/v1/models`. Throws on a non-2xx
1516
+ * response — callers decide whether to fail open (empty catalog) or closed.
1517
+ */
1518
+ declare function getModels(routerBaseUrl?: string): Promise<ModelInfo[]>;
1519
+ /**
1520
+ * Prepend synthetic catalog entries for ids the environment pins but the
1521
+ * router may not list (e.g. a private or self-hosted chat model). Ids already
1522
+ * present in `models` are not duplicated.
1523
+ */
1524
+ declare function withConfiguredModels(models: ModelInfo[], extraIds: string[]): ModelInfo[];
1525
+ /** Trim a candidate model id; `undefined` for non-strings and blanks. */
1526
+ declare function cleanModelId(value: unknown): string | undefined;
1527
+ interface ChatModelCandidate {
1528
+ /** Stable label for telemetry — e.g. `request`, `workspace`, `env`. */
1529
+ source: string;
1530
+ model: string | undefined;
1531
+ }
1532
+ interface ResolvedChatModel {
1533
+ source: string;
1534
+ model: string;
1535
+ }
1536
+ /**
1537
+ * Resolve a chat model by precedence: the first candidate carrying a
1538
+ * non-blank model wins, else `fallback`. The caller owns the precedence
1539
+ * order, so each product keeps its own policy (request → workspace → env,
1540
+ * etc.) while the first-non-blank logic and the telemetry shape stay shared.
1541
+ */
1542
+ declare function resolveChatModel(candidates: ChatModelCandidate[], fallback: ResolvedChatModel): ResolvedChatModel;
1543
+ type ChatModelValidation = {
1544
+ succeeded: true;
1545
+ value: string;
1546
+ } | {
1547
+ succeeded: false;
1548
+ error: string;
1549
+ };
1550
+ /**
1551
+ * Validate a caller-supplied chat-model id. Rejects non-strings, malformed
1552
+ * ids, and ids absent from both the caller's `allowlist` and the live router
1553
+ * catalog. Fails closed: when the catalog cannot be fetched, an unverifiable
1554
+ * id is rejected rather than admitted — a bad model never reaches the agent.
1555
+ */
1556
+ declare function validateChatModelId(modelId: unknown, options?: {
1557
+ /**
1558
+ * Known-good ids that skip the catalog round trip — e.g. the product's
1559
+ * default model plus any env-configured ids.
1560
+ */
1561
+ allowlist?: string[];
1562
+ routerBaseUrl?: string;
1563
+ /** Injectable catalog loader — overridden in tests. */
1564
+ loadModels?: (routerBaseUrl: string) => Promise<ModelInfo[]>;
1565
+ }): Promise<ChatModelValidation>;
1566
+
1209
1567
  /**
1210
1568
  * Validate an AgentProfile against canonical conformance rules: tool keys
1211
1569
  * must map to an MCP server entry (not be shell capabilities masquerading
@@ -1651,4 +2009,4 @@ declare function createTraceBridge(options: TraceBridgeOptions): TraceBridge;
1651
2009
  */
1652
2010
  declare function toAgentEvalTrace(event: RuntimeStreamEvent, options: TraceBridgeOptions): TraceEvent | undefined;
1653
2011
 
1654
- export { AgentBackendContext, AgentBackendInput, AgentExecutionBackend, AgentRuntimeEvent, AgentTaskRunResult, AgentTaskRunSummary, AgentTaskSpec, AgentTaskStatus, type BackendRetryPolicy, BackendTransportError, type ChatStreamEvent, ChatTurnError, type ChatTurnHooks, type ChatTurnIdentity, type ChatTurnMessage, type ChatTurnOverlay, type ChatTurnResult, type ChatTurnSandbox, type ClassifyIntentOptions, type ClassifyIntentResult, type ConformanceIssue, type ConformanceOptions, type ConformanceResult, type D1DatabaseLike, D1DurableRunStore, type D1PreparedStatementLike, DURABLE_SCHEMA_SQL, DURABLE_SCHEMA_VERSION, DurableAwaitEventTimeoutError, DurableChatTurnEngine, type DurableContext, DurableRunDivergenceError, DurableRunError, DurableRunInputMismatchError, DurableRunLeaseHeldError, type DurableRunManifest, type DurableRunStore, type DurableTurnHandle, type DurableTurnProducer, type EventRecord, FileSystemDurableRunStore, InMemoryDurableRunStore, InMemoryRuntimeSessionStore, KnowledgeReadinessDecision, RunAgentTaskOptions, RunAgentTaskStreamOptions, type RunChatTurnInput, type RunChatTurnOptions, type RunDurableInput, type RunDurableResult, type RunDurableTurnOptions, type RunOnWorkflowStepInput, type RunOutcome, type RunStatus, type RuntimeEventCollector, type RuntimeRunCompleteInput, type RuntimeRunCost, type RuntimeRunHandle, type RuntimeRunOptions, type RuntimeRunPersistenceAdapter, type RuntimeRunRow, RuntimeRunStateError, type RuntimeRunStatus, RuntimeSession, RuntimeSessionStore, RuntimeStreamEvent, type RuntimeStreamEventCollector, type RuntimeStreamEventSink, type RuntimeStreamEventSummary, type RuntimeTelemetryOptions, type SanitizedKnowledgeReadinessReport, type SanitizedKnowledgeRequirement, type ServerSentEventOptions, SessionMismatchError, type StepError, type StepKind, type StepRecord, type StepStatus, type SubagentMatcher, type TraceBridge, type TraceBridgeOptions, type WorkflowStepConfig, type WorkflowStepLike, assertProfileConformance, canonicalHash, canonicalJson, classifyIntent, composeTurnProfile, createIterableBackend, createOpenAICompatibleBackend, createRuntimeEventCollector, createRuntimeStreamEventCollector, createSandboxPromptBackend, createTraceBridge, decideKnowledgeReadiness, deriveWorkerId, durableChatTurnEngine, encodeServerSentEvent, manifestHash, readinessServerSentEvent, runAgentTask, runAgentTaskStream, runChatTurn, runDurable, runDurableTurn, runOnWorkflowStep, runtimeStreamServerSentEvent, sandboxAsChatTurnTarget, sanitizeAgentRuntimeEvent, sanitizeKnowledgeReadinessReport, sanitizeRuntimeStreamEvent, startRuntimeRun, stepId, summarizeAgentTaskRun, toAgentEvalTrace };
2012
+ export { AgentBackendContext, AgentBackendInput, AgentExecutionBackend, AgentRuntimeEvent, AgentTaskRunResult, AgentTaskRunSummary, AgentTaskSpec, AgentTaskStatus, type BackendRetryPolicy, BackendTransportError, type ChatModelCandidate, type ChatModelValidation, type ChatStreamEvent, ChatTurnError, type ChatTurnHooks, type ChatTurnIdentity, type ChatTurnMessage, type ChatTurnOverlay, type ChatTurnResult, type ChatTurnSandbox, type ClassifyIntentOptions, type ClassifyIntentResult, type ConformanceIssue, type ConformanceOptions, type ConformanceResult, type D1DatabaseLike, D1DurableRunStore, type D1PreparedStatementLike, DEFAULT_ROUTER_BASE_URL, DURABLE_SCHEMA_SQL, DURABLE_SCHEMA_VERSION, DurableAwaitEventTimeoutError, DurableChatTurnEngine, type DurableContext, type DurableObjectStateLike, type DurableObjectStorageLike, DurableRunDivergenceError, DurableRunError, DurableRunInputMismatchError, DurableRunLeaseHeldError, type DurableRunManifest, type DurableRunStore, type DurableTurnHandle, type DurableTurnProducer, type EventRecord, FileSystemDurableRunStore, InMemoryDurableRunStore, InMemoryRuntimeSessionStore, KnowledgeReadinessDecision, type ModelInfo, type ResolvedChatModel, type RouterEnv, RunAgentTaskOptions, RunAgentTaskStreamOptions, type RunChatTurnInput, type RunChatTurnOptions, type RunDurableInput, type RunDurableResult, type RunDurableTurnOptions, type RunHandle, type RunOnWorkflowStepInput, type RunOutcome, type RunStatus, type RunSupervisorOptions, type RuntimeEventCollector, type RuntimeRunCompleteInput, type RuntimeRunCost, type RuntimeRunHandle, type RuntimeRunOptions, type RuntimeRunPersistenceAdapter, type RuntimeRunRow, RuntimeRunStateError, type RuntimeRunStatus, RuntimeSession, RuntimeSessionStore, RuntimeStreamEvent, type RuntimeStreamEventCollector, type RuntimeStreamEventSink, type RuntimeStreamEventSummary, type RuntimeTelemetryOptions, type SandboxReconnectAdapter, type SanitizedKnowledgeReadinessReport, type SanitizedKnowledgeRequirement, type ServerSentEventOptions, SessionMismatchError, type SessionSupervisorDO, type StepError, type StepKind, type StepRecord, type StepStatus, type StreamEventRecord, type SubagentMatcher, type SupervisedEvent, type SupervisedRunHandle, type SupervisedRunMode, type SupervisorHostConfig, type TraceBridge, type TraceBridgeOptions, type WorkflowStepConfig, type WorkflowStepLike, assertProfileConformance, canonicalHash, canonicalJson, classifyIntent, cleanModelId, composeTurnProfile, createIterableBackend, createOpenAICompatibleBackend, createRuntimeEventCollector, createRuntimeStreamEventCollector, createSandboxPromptBackend, createSessionSupervisorDO, createTraceBridge, decideKnowledgeReadiness, deriveWorkerId, durableChatTurnEngine, encodeServerSentEvent, getModels, manifestHash, readinessServerSentEvent, resolveChatModel, resolveRouterBaseUrl, runAgentTask, runAgentTaskStream, runChatTurn, runDurable, runDurableTurn, runOnWorkflowStep, runSupervisedTurn, runtimeStreamServerSentEvent, sandboxAsChatTurnTarget, sanitizeAgentRuntimeEvent, sanitizeKnowledgeReadinessReport, sanitizeRuntimeStreamEvent, startRuntimeRun, stepId, summarizeAgentTaskRun, toAgentEvalTrace, validateChatModelId, withConfiguredModels };