nexus-ai-pro 1.10.0 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/API_STABILITY.md CHANGED
@@ -106,7 +106,7 @@ exactly-once provider execution. Those capabilities will use additive adapters a
106
106
  `MemoryAssetStore` is likewise process-local and does not promise cross-process durability or shared
107
107
  tenant state.
108
108
 
109
- ### Image portability (Unreleased)
109
+ ### Image portability (1.10.0)
110
110
 
111
111
  The following are public under the same 1.x rules. None is exported from the root or from
112
112
  `nexus-ai-pro/images`:
@@ -278,10 +278,10 @@ and threading the schema through it would force an annotation on every construct
278
278
  The graph casts at that boundary and hands typed state back through `state()` and `history()`.
279
279
 
280
280
  `compile()` without a checkpointer uses an in-process `MemoryGraphCheckpointer` capped at 1,000
281
- threads (Unreleased; earlier releases created none). `checkpointer: false` disables checkpointing. A
281
+ threads (since 1.10.0; earlier releases created none). `checkpointer: false` disables checkpointing. A
282
282
  run started without a `threadId` reports its generated id on the result.
283
283
 
284
- Also Unreleased:
284
+ Also since 1.10.0:
285
285
 
286
286
  - A paused or failed superstep records its already finished nodes in the optional `completed` field.
287
287
  Those nodes are not run again, and their edges still count when the step completes.
@@ -290,10 +290,28 @@ Also Unreleased:
290
290
  - A subgraph interrupt surfaces as an interrupt of the parent node.
291
291
  - `onProgress` receives `context.report()` calls.
292
292
 
293
- Scheduling within a superstep is not a guarantee. Nodes in a fan-out currently run in edge order and
294
- sequentially; running them concurrently is a valid future change, so a node must not depend on
295
- observing another node's write within the same superstep — that is what channels are for. The default
296
- `maxSteps` may change; pass it explicitly when a run depends on the bound.
293
+ Scheduling within a superstep is not a guarantee, and 1.11.0 makes use of that: tasks in a
294
+ superstep now run concurrently, bounded by `maxConcurrency` (default 16, settable per compile and per
295
+ run; `1` is strictly sequential). A node still must not depend on observing another node's write
296
+ within the same superstep — that is what channels are for. What is guaranteed is that writes are
297
+ reduced in task order rather than completion order, so a replay of the same decisions produces the
298
+ same state. The default `maxSteps` and `maxConcurrency` may change; pass them explicitly when a run
299
+ depends on the bound.
300
+
301
+ Also since 1.11.0, all additive:
302
+
303
+ - `Send` creates one task per value, with the task's input on `context.input`. Task ids are derived
304
+ from the step and the order produced, and appear in the checkpoint's optional `tasks` field, which
305
+ is written only when it says more than `next` does.
306
+ - `NodeOptions` on `addNode` carries `retry`, `timeoutMs`, and `ends`; `compile({ retry })` sets the
307
+ default policy. Retrying is off unless asked for. A timeout raises `GraphNodeTimeoutError`.
308
+ - `onNodeError` chooses `fail-fast` (default) or `settle` for the siblings of a failed task.
309
+ - A paused checkpoint and result carry `interrupts`, every question the step asked;
310
+ `interrupt` remains the first of them. `resumeInterrupts()` and `resumeInterruptsWith()` answer any
311
+ subset by interrupt id. Interrupt ids are keyed by task, so ids written by earlier releases for
312
+ ordinary nodes still resolve.
313
+ - `context.taskId` and `context.attempt` are new. Step events carry `tasks` and `attempts` when there
314
+ is something to report.
297
315
 
298
316
  ## Deprecation process
299
317
 
package/CHANGELOG.md CHANGED
@@ -4,6 +4,55 @@ Notable changes to this project are recorded here. The format follows [Keep a Ch
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.11.0] - 2026-09-18
8
+
9
+ Parallel graphs. A fan-out that looks parallel now runs in parallel, a graph can fan out over data it
10
+ discovers at run time, and a node can retry or time out on its own.
11
+
12
+ ### Added
13
+
14
+ - **Concurrent supersteps.** Every task in a superstep starts together: four branches of 300 ms
15
+ finish in about 310 ms, where they used to take 1,253 ms. `npm run bench:graph` measures it and
16
+ fails if the gain disappears.
17
+ - `maxConcurrency` bounds how many run at once. It defaults to 16, is settable per compile and per
18
+ run, and `1` restores strictly sequential execution.
19
+ - Writes are reduced in task order, never completion order, so timing cannot change the state a
20
+ replay produces.
21
+ - A superstep with a single task skips the scheduler, so a linear graph pays nothing for this.
22
+ - **`Send`, for fan-out over run-time data.** A router can return `new Send(node, input)` values,
23
+ creating one task per value, each reading its own `context.input`. Fifty-seven URLs become
24
+ fifty-seven tasks of one node in one superstep.
25
+ - Each copy is checkpointed separately, so a resume re-runs only the copies that did not finish.
26
+ - Task ids come from the step and the order produced, so a replay rebuilds the same tasks.
27
+ - `addNode(name, fn, { ends })` declares `Send` targets, keeping reachability checks exact.
28
+ - **Per-node retries and timeouts.** `addNode(name, fn, { retry, timeoutMs })`, with
29
+ `compile({ retry })` as the default for every node.
30
+ - Retrying is off unless asked for, because only the application knows whether a node is
31
+ idempotent. Interrupts, aborts, and validation errors are never retried.
32
+ - `context.attempt` tells a node which try it is on, and step events report the attempts a task
33
+ needed.
34
+ - A timeout aborts the node's signal and fails that attempt with `GraphNodeTimeoutError`, even if
35
+ the node ignores its signal.
36
+ - **`onNodeError`.** When a task fails, its siblings are aborted (`fail-fast`, the default) or left to
37
+ finish (`settle`). Either way, what they already wrote is kept.
38
+ - **Several questions at once.** Parallel tasks can each interrupt. Paused checkpoints and results
39
+ carry `interrupts`, and `resumeInterrupts()` answers any subset by id. A single question still works
40
+ exactly as before through `resume()`.
41
+ - **Install weight is now measured honestly.** The size table only ever counted this package's own
42
+ files, so a small entry point could quietly force a large dependency on the consumer.
43
+ - `npm run size:check` follows bare imports too, and the README table gains a column for what each
44
+ entry point makes you install. Most entry points force nothing: `/graph`, `/operations`,
45
+ `/batch`, `/embeddings`, and every image subpath import no third-party package at all. The root
46
+ import, `/core`, `/config`, and `/security` pull in 3.4 to 4.7 MB, because JSON-schema and Zod
47
+ validation live there.
48
+ - The clean-install test now measures and bounds the whole production install: 10.0 MB of
49
+ `node_modules`, of which 3.0 MB is this package.
50
+
51
+ ### Fixed
52
+
53
+ - **A retry no longer lets the process exit while it waits.** The backoff timer was unref'd, so a run
54
+ waiting to retry could be abandoned by an otherwise idle process.
55
+
7
56
  ## [1.10.0] - 2026-09-16
8
57
 
9
58
  Image portability. Three image backends now sit behind one contract: OpenAI, Google Imagen, and a
@@ -565,7 +614,8 @@ shared policy vocabulary is in place for a future release that revisits this.
565
614
  - URL fetching rejects unsafe private-network targets and limits response reads.
566
615
  - CLI and audit findings no longer disclose detected secret values.
567
616
 
568
- [Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.10.0...HEAD
617
+ [Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.11.0...HEAD
618
+ [1.11.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.10.0...v1.11.0
569
619
  [1.10.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.9.0...v1.10.0
570
620
  [1.9.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.8.0...v1.9.0
571
621
  [1.8.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.7.0...v1.8.0
package/README.md CHANGED
@@ -452,6 +452,58 @@ whether that means overwrite, append, or sum. Assignment would silently drop one
452
452
  **Cycles are first-class**, so `maxSteps` is what stands between a mistaken router and an infinite
453
453
  loop. Exceeding it names the nodes still pending rather than hanging.
454
454
 
455
+ **Branches run in parallel.** Every task in a superstep starts together, so four branches of three
456
+ seconds each finish in about three seconds rather than twelve:
457
+
458
+ ```ts
459
+ .addConditionalEdges('plan', () => ['research-a', 'research-b', 'research-c', 'research-d'])
460
+ ```
461
+
462
+ `maxConcurrency` caps how many run at once — 16 by default, settable per compile and per run, and
463
+ `1` restores strictly sequential execution. Whatever the timing, writes are reduced in task order, so
464
+ a replay produces the same state. A superstep with one task skips the scheduler entirely.
465
+
466
+ **Fan out over data decided at run time.** Edges can only name nodes that already exist. `Send`
467
+ creates one task per value instead, each reading its own `context.input`:
468
+
469
+ ```ts
470
+ import { Send } from 'nexus-ai-pro/graph';
471
+
472
+ .addNode('research', async (ctx) => ({ findings: [await fetch(ctx.input as string)] }), { ends: [END] })
473
+ .addConditionalEdges('plan', (state) => state.urls.map((url) => new Send('research', url)))
474
+ ```
475
+
476
+ Fifty-seven URLs become fifty-seven tasks of one node in a single superstep, bounded by
477
+ `maxConcurrency` and checkpointed individually, so a resume re-runs only the copies that did not
478
+ finish. Declaring `ends` keeps compile-time reachability checks exact.
479
+
480
+ **Retries and timeouts per node.** A node that calls a flaky service can retry on its own, without
481
+ wrapping every node body in the same try/catch:
482
+
483
+ ```ts
484
+ .addNode('fetch', fetchNode, {
485
+ retry: { maxAttempts: 3, initialIntervalMs: 500, backoffFactor: 2, jitter: true },
486
+ timeoutMs: 10_000,
487
+ })
488
+ ```
489
+
490
+ Retrying defaults to off, because only you know whether a node is idempotent; `compile({ retry })`
491
+ sets a default for every node. Interrupts, aborts, and validation errors are never retried.
492
+ `context.attempt` tells a node which try it is on, and a timeout aborts the node's signal and fails
493
+ that attempt. When one task fails, its siblings are aborted by default; `onNodeError: 'settle'` lets
494
+ them finish first, and either way what they already wrote is kept.
495
+
496
+ **Several questions at once.** Parallel tasks can each interrupt. The paused result carries every
497
+ pending question, and they can be answered together or a few at a time:
498
+
499
+ ```ts
500
+ const run = await graph.invoke(input, { threadId });
501
+ await graph.resumeInterruptsWith(threadId, {
502
+ [run.interrupts[0].id]: true,
503
+ [run.interrupts[1].id]: 'use the second draft',
504
+ });
505
+ ```
506
+
455
507
  **Human in the loop.** A node calls `interrupt()`; the graph checkpoints and stops:
456
508
 
457
509
  ```ts
@@ -481,7 +533,8 @@ await graph.history(threadId); // newest first
481
533
  graph.resumeFrom(threadId, 3); // rewind and run forward
482
534
  ```
483
535
 
484
- **Durable by construction.** The default checkpointer is in-process. Point it at the operation store
536
+ **Durable by construction.** The default checkpointer is in-process, holding up to 1,000 threads;
537
+ pass `checkpointer: false` to turn checkpointing off. Point it at the operation store
485
538
  that already backs durable operations and a thread survives a restart — a different worker resumes
486
539
  what another suspended:
487
540
 
@@ -1750,85 +1803,93 @@ Importing one piece loads one piece. The figures below are the transitive import
1750
1803
  point, generated from the build by `npm run size:check`, which fails CI when an entry point grows
1751
1804
  past its budget — so the numbers stay true rather than aspirational.
1752
1805
 
1806
+ The last column is the part a file-size table usually hides: what an entry point forces you to
1807
+ install. Most of them force nothing at all — `/graph`, `/operations`, `/images/*`, `/batch`,
1808
+ `/embeddings` and the rest import no third-party package. The root import, `/core`, `/config`, and
1809
+ `/security` do, because JSON-schema and Zod validation live there. A production install of this
1810
+ package is about 10 MB of `node_modules`, of which 3 MB is this package; the clean-install test holds
1811
+ that total to a ceiling too. `@types/node` accounts for a further 2.4 MB at install time but is types
1812
+ only, so it never appears in an import graph.
1813
+
1753
1814
  <!-- size-table:start -->
1754
- | Import | Size | Share of root |
1755
- | --- | --- | --- |
1756
- | `nexus-ai-pro` | 612 KB | 100% |
1757
- | `nexus-ai-pro/config` | 468 KB | 76% |
1758
- | `nexus-ai-pro/core` | 462 KB | 75% |
1759
- | `nexus-ai-pro/realtime` | 157 KB | 26% |
1760
- | `nexus-ai-pro/batch` | 118 KB | 19% |
1761
- | `nexus-ai-pro/realtime/session` | 94 KB | 15% |
1762
- | `nexus-ai-pro/embeddings` | 91 KB | 15% |
1763
- | `nexus-ai-pro/providers/groq` | 79 KB | 13% |
1764
- | `nexus-ai-pro/providers/mistral` | 79 KB | 13% |
1765
- | `nexus-ai-pro/providers/azure-openai` | 78 KB | 13% |
1766
- | `nexus-ai-pro/providers/openrouter` | 78 KB | 13% |
1767
- | `nexus-ai-pro/providers/deepseek` | 77 KB | 13% |
1768
- | `nexus-ai-pro/providers/llamacpp` | 77 KB | 13% |
1769
- | `nexus-ai-pro/providers/lmstudio` | 77 KB | 13% |
1770
- | `nexus-ai-pro/providers/openai` | 77 KB | 13% |
1771
- | `nexus-ai-pro/providers/anthropic` | 70 KB | 11% |
1772
- | `nexus-ai-pro/providers/google` | 66 KB | 11% |
1773
- | `nexus-ai-pro/images` | 65 KB | 11% |
1774
- | `nexus-ai-pro/providers/ollama` | 58 KB | 9% |
1775
- | `nexus-ai-pro/batch/openai` | 52 KB | 8% |
1776
- | `nexus-ai-pro/batch/anthropic` | 52 KB | 8% |
1777
- | `nexus-ai-pro/operations` | 49 KB | 8% |
1778
- | `nexus-ai-pro/batch/mock` | 47 KB | 8% |
1779
- | `nexus-ai-pro/providers/cohere` | 46 KB | 8% |
1780
- | `nexus-ai-pro/realtime/openai-webrtc` | 46 KB | 8% |
1781
- | `nexus-ai-pro/security` | 40 KB | 7% |
1782
- | `nexus-ai-pro/models` | 35 KB | 6% |
1783
- | `nexus-ai-pro/graph` | 33 KB | 5% |
1784
- | `nexus-ai-pro/images/inputs` | 31 KB | 5% |
1785
- | `nexus-ai-pro/realtime/openai-websocket` | 29 KB | 5% |
1786
- | `nexus-ai-pro/evals` | 23 KB | 4% |
1787
- | `nexus-ai-pro/images/transform` | 22 KB | 4% |
1788
- | `nexus-ai-pro/images/stores` | 22 KB | 4% |
1789
- | `nexus-ai-pro/telephony/twilio` | 21 KB | 3% |
1790
- | `nexus-ai-pro/telephony` | 20 KB | 3% |
1791
- | `nexus-ai-pro/images/openai` | 19 KB | 3% |
1792
- | `nexus-ai-pro/realtime/mock` | 19 KB | 3% |
1793
- | `nexus-ai-pro/voice` | 18 KB | 3% |
1794
- | `nexus-ai-pro/images/comfyui` | 17 KB | 3% |
1795
- | `nexus-ai-pro/embeddings/adapters` | 17 KB | 3% |
1796
- | `nexus-ai-pro/realtime/conversation` | 16 KB | 3% |
1797
- | `nexus-ai-pro/images/google` | 15 KB | 2% |
1798
- | `nexus-ai-pro/images/evals` | 15 KB | 2% |
1799
- | `nexus-ai-pro/images/assets` | 14 KB | 2% |
1800
- | `nexus-ai-pro/context` | 13 KB | 2% |
1801
- | `nexus-ai-pro/operations/adapters` | 13 KB | 2% |
1802
- | `nexus-ai-pro/realtime/tools` | 13 KB | 2% |
1803
- | `nexus-ai-pro/workflows` | 13 KB | 2% |
1804
- | `nexus-ai-pro/images/mock` | 12 KB | 2% |
1805
- | `nexus-ai-pro/optimizer` | 11 KB | 2% |
1806
- | `nexus-ai-pro/voice/session` | 11 KB | 2% |
1807
- | `nexus-ai-pro/providers` | 10 KB | 2% |
1808
- | `nexus-ai-pro/providers/base` | 10 KB | 2% |
1809
- | `nexus-ai-pro/embeddings/models` | 10 KB | 2% |
1810
- | `nexus-ai-pro/voice/openai` | 9 KB | 1% |
1811
- | `nexus-ai-pro/images/moderation` | 9 KB | 1% |
1812
- | `nexus-ai-pro/ops/circuit-breaker` | 8 KB | 1% |
1813
- | `nexus-ai-pro/realtime/openai-server` | 8 KB | 1% |
1814
- | `nexus-ai-pro/capabilities` | 8 KB | 1% |
1815
- | `nexus-ai-pro/telephony/realtime-bridge` | 7 KB | 1% |
1816
- | `nexus-ai-pro/providers/errors` | 5 KB | 0.8% |
1817
- | `nexus-ai-pro/embeddings/mock` | 5 KB | 0.8% |
1818
- | `nexus-ai-pro/cache/semantic-cache` | 5 KB | 0.8% |
1819
- | `nexus-ai-pro/evals/judge` | 5 KB | 0.8% |
1820
- | `nexus-ai-pro/operations/webhooks` | 4 KB | 0.7% |
1821
- | `nexus-ai-pro/ops/rate-limit-adapters` | 3 KB | 0.5% |
1822
- | `nexus-ai-pro/cache/memory-cache` | 3 KB | 0.5% |
1823
- | `nexus-ai-pro/cache` | 2 KB | 0.3% |
1824
- | `nexus-ai-pro/cache/adapters` | 2 KB | 0.3% |
1825
- | `nexus-ai-pro/rag` | 2 KB | 0.3% |
1826
- | `nexus-ai-pro/jobs` | 2 KB | 0.3% |
1827
- | `nexus-ai-pro/jobs/durable-adapters` | 2 KB | 0.3% |
1828
- | `nexus-ai-pro/jobs/queue` | 2 KB | 0.3% |
1829
- | `nexus-ai-pro/streaming` | 1 KB | 0.2% |
1830
- | `nexus-ai-pro/providers/type-guards` | 1 KB | 0.2% |
1831
- | `nexus-ai-pro/jobs/batch` | 1 KB | 0.2% |
1815
+ | Import | Size | Share of root | Third-party install |
1816
+ | --- | --- | --- | --- |
1817
+ | `nexus-ai-pro` | 612 KB | 100% | +4.7 MB |
1818
+ | `nexus-ai-pro/config` | 468 KB | 76% | +4.7 MB |
1819
+ | `nexus-ai-pro/core` | 462 KB | 75% | +4.7 MB |
1820
+ | `nexus-ai-pro/realtime` | 157 KB | 26% | none |
1821
+ | `nexus-ai-pro/batch` | 118 KB | 19% | none |
1822
+ | `nexus-ai-pro/realtime/session` | 94 KB | 15% | none |
1823
+ | `nexus-ai-pro/embeddings` | 91 KB | 15% | none |
1824
+ | `nexus-ai-pro/providers/groq` | 79 KB | 13% | none |
1825
+ | `nexus-ai-pro/providers/mistral` | 79 KB | 13% | none |
1826
+ | `nexus-ai-pro/providers/azure-openai` | 78 KB | 13% | none |
1827
+ | `nexus-ai-pro/providers/openrouter` | 78 KB | 13% | none |
1828
+ | `nexus-ai-pro/providers/deepseek` | 77 KB | 13% | none |
1829
+ | `nexus-ai-pro/providers/llamacpp` | 77 KB | 13% | none |
1830
+ | `nexus-ai-pro/providers/lmstudio` | 77 KB | 13% | none |
1831
+ | `nexus-ai-pro/providers/openai` | 77 KB | 13% | none |
1832
+ | `nexus-ai-pro/providers/anthropic` | 70 KB | 11% | none |
1833
+ | `nexus-ai-pro/providers/google` | 66 KB | 11% | none |
1834
+ | `nexus-ai-pro/images` | 65 KB | 11% | none |
1835
+ | `nexus-ai-pro/providers/ollama` | 58 KB | 9% | none |
1836
+ | `nexus-ai-pro/batch/openai` | 52 KB | 8% | none |
1837
+ | `nexus-ai-pro/batch/anthropic` | 52 KB | 8% | none |
1838
+ | `nexus-ai-pro/operations` | 49 KB | 8% | none |
1839
+ | `nexus-ai-pro/batch/mock` | 47 KB | 8% | none |
1840
+ | `nexus-ai-pro/providers/cohere` | 46 KB | 8% | none |
1841
+ | `nexus-ai-pro/graph` | 46 KB | 8% | none |
1842
+ | `nexus-ai-pro/realtime/openai-webrtc` | 46 KB | 8% | none |
1843
+ | `nexus-ai-pro/security` | 40 KB | 7% | +3.4 MB |
1844
+ | `nexus-ai-pro/models` | 35 KB | 6% | none |
1845
+ | `nexus-ai-pro/images/inputs` | 31 KB | 5% | none |
1846
+ | `nexus-ai-pro/realtime/openai-websocket` | 29 KB | 5% | none |
1847
+ | `nexus-ai-pro/evals` | 23 KB | 4% | none |
1848
+ | `nexus-ai-pro/images/transform` | 22 KB | 4% | none |
1849
+ | `nexus-ai-pro/images/stores` | 22 KB | 4% | none |
1850
+ | `nexus-ai-pro/telephony/twilio` | 21 KB | 3% | none |
1851
+ | `nexus-ai-pro/telephony` | 20 KB | 3% | none |
1852
+ | `nexus-ai-pro/images/openai` | 19 KB | 3% | none |
1853
+ | `nexus-ai-pro/realtime/mock` | 19 KB | 3% | none |
1854
+ | `nexus-ai-pro/voice` | 18 KB | 3% | none |
1855
+ | `nexus-ai-pro/images/comfyui` | 17 KB | 3% | none |
1856
+ | `nexus-ai-pro/embeddings/adapters` | 17 KB | 3% | none |
1857
+ | `nexus-ai-pro/realtime/conversation` | 16 KB | 3% | none |
1858
+ | `nexus-ai-pro/images/google` | 15 KB | 2% | none |
1859
+ | `nexus-ai-pro/images/evals` | 15 KB | 2% | none |
1860
+ | `nexus-ai-pro/images/assets` | 14 KB | 2% | none |
1861
+ | `nexus-ai-pro/context` | 13 KB | 2% | none |
1862
+ | `nexus-ai-pro/operations/adapters` | 13 KB | 2% | none |
1863
+ | `nexus-ai-pro/realtime/tools` | 13 KB | 2% | none |
1864
+ | `nexus-ai-pro/workflows` | 13 KB | 2% | none |
1865
+ | `nexus-ai-pro/images/mock` | 12 KB | 2% | none |
1866
+ | `nexus-ai-pro/optimizer` | 11 KB | 2% | none |
1867
+ | `nexus-ai-pro/voice/session` | 11 KB | 2% | none |
1868
+ | `nexus-ai-pro/providers` | 10 KB | 2% | none |
1869
+ | `nexus-ai-pro/providers/base` | 10 KB | 2% | none |
1870
+ | `nexus-ai-pro/embeddings/models` | 10 KB | 2% | none |
1871
+ | `nexus-ai-pro/voice/openai` | 9 KB | 1% | none |
1872
+ | `nexus-ai-pro/images/moderation` | 9 KB | 1% | none |
1873
+ | `nexus-ai-pro/ops/circuit-breaker` | 8 KB | 1% | none |
1874
+ | `nexus-ai-pro/realtime/openai-server` | 8 KB | 1% | none |
1875
+ | `nexus-ai-pro/capabilities` | 8 KB | 1% | none |
1876
+ | `nexus-ai-pro/telephony/realtime-bridge` | 7 KB | 1% | none |
1877
+ | `nexus-ai-pro/providers/errors` | 5 KB | 0.8% | none |
1878
+ | `nexus-ai-pro/embeddings/mock` | 5 KB | 0.8% | none |
1879
+ | `nexus-ai-pro/cache/semantic-cache` | 5 KB | 0.8% | none |
1880
+ | `nexus-ai-pro/evals/judge` | 5 KB | 0.8% | none |
1881
+ | `nexus-ai-pro/operations/webhooks` | 4 KB | 0.7% | none |
1882
+ | `nexus-ai-pro/ops/rate-limit-adapters` | 3 KB | 0.5% | none |
1883
+ | `nexus-ai-pro/cache/memory-cache` | 3 KB | 0.5% | none |
1884
+ | `nexus-ai-pro/cache` | 2 KB | 0.3% | none |
1885
+ | `nexus-ai-pro/cache/adapters` | 2 KB | 0.3% | none |
1886
+ | `nexus-ai-pro/rag` | 2 KB | 0.3% | none |
1887
+ | `nexus-ai-pro/jobs` | 2 KB | 0.3% | none |
1888
+ | `nexus-ai-pro/jobs/durable-adapters` | 2 KB | 0.3% | none |
1889
+ | `nexus-ai-pro/jobs/queue` | 2 KB | 0.3% | none |
1890
+ | `nexus-ai-pro/streaming` | 1 KB | 0.2% | none |
1891
+ | `nexus-ai-pro/providers/type-guards` | 1 KB | 0.2% | none |
1892
+ | `nexus-ai-pro/jobs/batch` | 1 KB | 0.2% | none |
1832
1893
  <!-- size-table:end -->
1833
1894
 
1834
1895
  A capability that only works by importing the whole runtime is treated as a design problem, not an
@@ -19,7 +19,11 @@ export declare class GraphInterrupt extends GraphError {
19
19
  readonly node: string;
20
20
  readonly step: number;
21
21
  readonly index: number;
22
- constructor(request: InterruptRequest, node: string, step: number, index: number);
22
+ /** Task that asked. Equal to `node` unless the task came from a `Send`. */
23
+ readonly taskId: string;
24
+ constructor(request: InterruptRequest, node: string, step: number, index: number,
25
+ /** Task that asked. Equal to `node` unless the task came from a `Send`. */
26
+ taskId?: string);
23
27
  }
24
28
  /**
25
29
  * Raised when a run exceeds its superstep budget.
@@ -46,5 +50,24 @@ export declare class GraphNotInterruptedError extends GraphError {
46
50
  readonly status: string;
47
51
  constructor(threadId: string, status: string);
48
52
  }
49
- /** Stable key for one interrupt, so a replayed node picks up the answer it was given. */
50
- export declare function interruptKey(pending: Pick<PendingInterrupt, 'node' | 'step' | 'index'>): string;
53
+ /**
54
+ * Raised when a node outlives its `timeoutMs`.
55
+ *
56
+ * Separate from an ordinary failure because it is usually worth retrying, and because the node's
57
+ * signal was aborted underneath it rather than the node choosing to stop.
58
+ */
59
+ export declare class GraphNodeTimeoutError extends GraphError {
60
+ readonly node: string;
61
+ readonly timeoutMs: number;
62
+ constructor(node: string, timeoutMs: number);
63
+ }
64
+ /**
65
+ * Stable key for one interrupt, so a replayed node picks up the answer it was given.
66
+ *
67
+ * Keyed by task rather than node, so several `Send` tasks of one node each keep their own answer.
68
+ * For a node reached through an ordinary edge the task id is the node name, so keys written by
69
+ * earlier releases still resolve.
70
+ */
71
+ export declare function interruptKey(pending: Pick<PendingInterrupt, 'node' | 'step' | 'index'> & {
72
+ taskId?: string;
73
+ }): string;
@@ -26,12 +26,16 @@ export class GraphInterrupt extends GraphError {
26
26
  node;
27
27
  step;
28
28
  index;
29
- constructor(request, node, step, index) {
29
+ taskId;
30
+ constructor(request, node, step, index,
31
+ /** Task that asked. Equal to `node` unless the task came from a `Send`. */
32
+ taskId = node) {
30
33
  super(`Graph interrupted at "${node}": ${request.reason}`, 'GRAPH_INTERRUPT');
31
34
  this.request = request;
32
35
  this.node = node;
33
36
  this.step = step;
34
37
  this.index = index;
38
+ this.taskId = taskId;
35
39
  this.name = 'GraphInterrupt';
36
40
  }
37
41
  }
@@ -79,7 +83,29 @@ export class GraphNotInterruptedError extends GraphError {
79
83
  this.name = 'GraphNotInterruptedError';
80
84
  }
81
85
  }
82
- /** Stable key for one interrupt, so a replayed node picks up the answer it was given. */
86
+ /**
87
+ * Raised when a node outlives its `timeoutMs`.
88
+ *
89
+ * Separate from an ordinary failure because it is usually worth retrying, and because the node's
90
+ * signal was aborted underneath it rather than the node choosing to stop.
91
+ */
92
+ export class GraphNodeTimeoutError extends GraphError {
93
+ node;
94
+ timeoutMs;
95
+ constructor(node, timeoutMs) {
96
+ super(`Graph node "${node}" exceeded its ${timeoutMs}ms timeout`, 'GRAPH_NODE_TIMEOUT');
97
+ this.node = node;
98
+ this.timeoutMs = timeoutMs;
99
+ this.name = 'GraphNodeTimeoutError';
100
+ }
101
+ }
102
+ /**
103
+ * Stable key for one interrupt, so a replayed node picks up the answer it was given.
104
+ *
105
+ * Keyed by task rather than node, so several `Send` tasks of one node each keep their own answer.
106
+ * For a node reached through an ordinary edge the task id is the node name, so keys written by
107
+ * earlier releases still resolve.
108
+ */
83
109
  export function interruptKey(pending) {
84
- return `${pending.node}:${pending.step}:${pending.index}`;
110
+ return `${pending.taskId ?? pending.node}:${pending.step}:${pending.index}`;
85
111
  }
@@ -1,4 +1,8 @@
1
- import type { ChannelSchema, CompileOptions, EdgeRouter, GraphCheckpoint, GraphProgress, GraphResult, GraphRunOptions, GraphStatus, GraphStepEvent, NodeFn, StateUpdate } from '../types/graph.js';
1
+ import type { ChannelSchema, CompileOptions, EdgeRouter, GraphCheckpoint, GraphProgress, GraphResult, GraphRunOptions, GraphStatus, GraphStepEvent, NodeFn, NodeOptions, StateUpdate } from '../types/graph.js';
2
+ interface GraphNodeDefinition<S extends ChannelSchema> {
3
+ fn: NodeFn<S>;
4
+ options: NodeOptions;
5
+ }
2
6
  interface Edge<S extends ChannelSchema> {
3
7
  from: string;
4
8
  to?: string;
@@ -18,7 +22,7 @@ export declare class StateGraph<S extends ChannelSchema> {
18
22
  private readonly nodes;
19
23
  private readonly edges;
20
24
  constructor(channels: S);
21
- addNode(name: string, fn: NodeFn<S>): this;
25
+ addNode(name: string, fn: NodeFn<S>, options?: NodeOptions): this;
22
26
  addEdge(from: string, to: string): this;
23
27
  /**
24
28
  * Routes on state after `from` runs.
@@ -40,7 +44,7 @@ export declare class CompiledGraph<S extends ChannelSchema> {
40
44
  private readonly options;
41
45
  private readonly now;
42
46
  private readonly store;
43
- constructor(channels: S, nodes: Map<string, NodeFn<S>>, edges: Array<Edge<S>>, options: CompileOptions);
47
+ constructor(channels: S, nodes: Map<string, GraphNodeDefinition<S>>, edges: Array<Edge<S>>, options: CompileOptions);
44
48
  /** Runs to completion, to an interrupt, or to the step limit. */
45
49
  invoke(input?: StateUpdate<S>, runOptions?: GraphRunOptions): Promise<GraphResult<S>>;
46
50
  /** Yields one event per superstep, so a caller can render progress as it happens. */
@@ -55,6 +59,15 @@ export declare class CompiledGraph<S extends ChannelSchema> {
55
59
  resume(threadId: string, value: unknown, runOptions?: GraphRunOptions): AsyncIterable<GraphStepEvent<S>>;
56
60
  /** Like `resume`, but returns the final result rather than the stream. */
57
61
  resumeWith(threadId: string, value: unknown, runOptions?: GraphRunOptions): Promise<GraphResult<S>>;
62
+ /**
63
+ * Answers several of a paused step's questions at once, keyed by interrupt id.
64
+ *
65
+ * Parallel tasks can each ask something, so one answer is not always enough. Questions left
66
+ * unanswered are asked again when the step runs.
67
+ */
68
+ resumeInterrupts(threadId: string, answers: Record<string, unknown>, runOptions?: GraphRunOptions): AsyncIterable<GraphStepEvent<S>>;
69
+ /** Like `resumeInterrupts`, but returns the final result rather than the stream. */
70
+ resumeInterruptsWith(threadId: string, answers: Record<string, unknown>, runOptions?: GraphRunOptions): Promise<GraphResult<S>>;
58
71
  /** Continues a thread that stopped for any other reason, such as the step limit. */
59
72
  continue(threadId: string, runOptions?: GraphRunOptions): AsyncIterable<GraphStepEvent<S>>;
60
73
  /**
@@ -79,10 +92,13 @@ export declare class CompiledGraph<S extends ChannelSchema> {
79
92
  */
80
93
  asNode<P extends ChannelSchema>(): NodeFn<P>;
81
94
  private run;
95
+ /** Runs one task to a verdict, applying its retry policy and timeout. */
96
+ private runTask;
82
97
  private runNode;
83
98
  /** Combines this superstep's writes into state through each channel's reducer. */
84
99
  private reduce;
85
- private nextNodes;
100
+ /** Follows every edge out of the nodes that ran, producing the next step's tasks. */
101
+ private nextTasks;
86
102
  private entryNodes;
87
103
  private seedState;
88
104
  private requireCheckpoint;