nexus-ai-pro 1.10.0 → 1.12.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 +40 -7
- package/CHANGELOG.md +91 -1
- package/README.md +239 -81
- package/dist/graph/errors.d.ts +26 -3
- package/dist/graph/errors.js +29 -3
- package/dist/graph/graph.d.ts +50 -4
- package/dist/graph/graph.js +594 -82
- package/dist/graph/index.d.ts +3 -3
- package/dist/graph/index.js +2 -2
- package/dist/graph/visualize.d.ts +30 -0
- package/dist/graph/visualize.js +74 -0
- package/dist/types/graph.d.ts +240 -5
- package/dist/types/graph.js +39 -0
- package/dist-cjs/graph/errors.d.ts +26 -3
- package/dist-cjs/graph/errors.js +31 -4
- package/dist-cjs/graph/graph.d.ts +50 -4
- package/dist-cjs/graph/graph.js +592 -80
- package/dist-cjs/graph/index.d.ts +3 -3
- package/dist-cjs/graph/index.js +4 -1
- package/dist-cjs/graph/visualize.d.ts +30 -0
- package/dist-cjs/graph/visualize.js +78 -0
- package/dist-cjs/types/graph.d.ts +240 -5
- package/dist-cjs/types/graph.js +42 -1
- package/package.json +49 -12
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 (
|
|
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 (
|
|
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
|
|
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,43 @@ 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.
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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.
|
|
315
|
+
|
|
316
|
+
Also since 1.12.0, all additive:
|
|
317
|
+
|
|
318
|
+
- `Command` (with `Command.PARENT`) may be returned from a node; `NodeResult` names what a node may
|
|
319
|
+
return. `goto` routes are added to the node's outgoing edges, and a paused checkpoint records them in
|
|
320
|
+
the optional `gotos` field.
|
|
321
|
+
- `interruptBefore` and `interruptAfter` on compile and run options; a paused checkpoint and result
|
|
322
|
+
carry `breakpoint`, and step events may have type `breakpoint`.
|
|
323
|
+
- `NodeOptions.defer`, `GraphRunOptions.onEvent` with the `GraphEvent` union, and `context.emit()`.
|
|
324
|
+
New `GraphEvent` variants may be added in a minor release.
|
|
325
|
+
- `updateState()`, `fork()`, and `describe()` on a compiled graph, and the `nexus-ai-pro/graph/visualize`
|
|
326
|
+
subpath. The exact Mermaid text `toMermaid()` produces is not a guarantee — diagram layout may
|
|
327
|
+
improve in a minor release — but `GraphDescription` is a normalized shape.
|
|
328
|
+
- A subgraph node's thread id is `<parent thread>:<task id>`, which equals the previous
|
|
329
|
+
`<parent thread>:<node>` for any node not reached through `Send`.
|
|
297
330
|
|
|
298
331
|
## Deprecation process
|
|
299
332
|
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,94 @@ Notable changes to this project are recorded here. The format follows [Keep a Ch
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [1.12.0] - 2026-09-20
|
|
8
|
+
|
|
9
|
+
Graph control and introspection. A node can route itself, a run can pause at breakpoints and be
|
|
10
|
+
edited or forked, every task and custom event can be watched as it happens, and a graph can draw
|
|
11
|
+
itself.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **`Command`.** A node returns `new Command({ update, goto })` to write state and choose its
|
|
16
|
+
successors in one step, instead of splitting the decision into a separate router. `goto` takes node
|
|
17
|
+
names or `Send`s. A route chosen by a task that finished before its step paused is kept on the
|
|
18
|
+
checkpoint, so it is still followed when the step resumes.
|
|
19
|
+
- **`Command.PARENT`.** A node inside a subgraph hands control back to the graph that contains it. The
|
|
20
|
+
subgraph's own writes and the update it addressed to the parent are reduced separately, in order,
|
|
21
|
+
so neither overwrites the other.
|
|
22
|
+
- **Breakpoints.** `interruptBefore` and `interruptAfter`, per compile or per run, pause a run at
|
|
23
|
+
chosen nodes with status `interrupted` and a `breakpoint` describing where. `continue()` carries on,
|
|
24
|
+
and does not pause again at the breakpoint it resumes from.
|
|
25
|
+
- **Deferred nodes.** `addNode(name, fn, { defer: true })` holds a node until every other pending task
|
|
26
|
+
has finished, so an aggregator after branches of different lengths runs once.
|
|
27
|
+
- **`updateState()`.** Merges a correction into a thread's latest checkpoint in place, keeping any
|
|
28
|
+
pending question; with `asNode`, applies it as that node's output and continues along its edges.
|
|
29
|
+
- **`fork()`.** Copies a thread's history up to a step into a new thread, leaving the original
|
|
30
|
+
untouched and runnable, and records `metadata.forkedFrom`.
|
|
31
|
+
- **`onEvent` and `context.emit()`.** A run can observe each task starting, retrying, and finishing
|
|
32
|
+
with its update, every checkpoint written, and custom events a node emits, such as model tokens. A
|
|
33
|
+
listener that throws never fails the run.
|
|
34
|
+
- **`describe()` and `nexus-ai-pro/graph/visualize`.** `describe()` returns a graph's nodes, edges, and
|
|
35
|
+
run-time routes as plain JSON, including subgraphs. `toMermaid()` draws it, with optional
|
|
36
|
+
highlighting of where a thread is; `toGraphJSON()` returns the data. The visualizer imports no
|
|
37
|
+
runtime code.
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- **Parallel copies of a subgraph node no longer share a thread.** `asNode()` keyed the subgraph's
|
|
42
|
+
thread by node name, so two `Send` copies of one subgraph node in the same step wrote to the same
|
|
43
|
+
thread. It is now keyed by task; a plain node's task id is its name, so existing threads are
|
|
44
|
+
unaffected.
|
|
45
|
+
|
|
46
|
+
## [1.11.0] - 2026-09-18
|
|
47
|
+
|
|
48
|
+
Parallel graphs. A fan-out that looks parallel now runs in parallel, a graph can fan out over data it
|
|
49
|
+
discovers at run time, and a node can retry or time out on its own.
|
|
50
|
+
|
|
51
|
+
### Added
|
|
52
|
+
|
|
53
|
+
- **Concurrent supersteps.** Every task in a superstep starts together: four branches of 300 ms
|
|
54
|
+
finish in about 310 ms, where they used to take 1,253 ms. `npm run bench:graph` measures it and
|
|
55
|
+
fails if the gain disappears.
|
|
56
|
+
- `maxConcurrency` bounds how many run at once. It defaults to 16, is settable per compile and per
|
|
57
|
+
run, and `1` restores strictly sequential execution.
|
|
58
|
+
- Writes are reduced in task order, never completion order, so timing cannot change the state a
|
|
59
|
+
replay produces.
|
|
60
|
+
- A superstep with a single task skips the scheduler, so a linear graph pays nothing for this.
|
|
61
|
+
- **`Send`, for fan-out over run-time data.** A router can return `new Send(node, input)` values,
|
|
62
|
+
creating one task per value, each reading its own `context.input`. Fifty-seven URLs become
|
|
63
|
+
fifty-seven tasks of one node in one superstep.
|
|
64
|
+
- Each copy is checkpointed separately, so a resume re-runs only the copies that did not finish.
|
|
65
|
+
- Task ids come from the step and the order produced, so a replay rebuilds the same tasks.
|
|
66
|
+
- `addNode(name, fn, { ends })` declares `Send` targets, keeping reachability checks exact.
|
|
67
|
+
- **Per-node retries and timeouts.** `addNode(name, fn, { retry, timeoutMs })`, with
|
|
68
|
+
`compile({ retry })` as the default for every node.
|
|
69
|
+
- Retrying is off unless asked for, because only the application knows whether a node is
|
|
70
|
+
idempotent. Interrupts, aborts, and validation errors are never retried.
|
|
71
|
+
- `context.attempt` tells a node which try it is on, and step events report the attempts a task
|
|
72
|
+
needed.
|
|
73
|
+
- A timeout aborts the node's signal and fails that attempt with `GraphNodeTimeoutError`, even if
|
|
74
|
+
the node ignores its signal.
|
|
75
|
+
- **`onNodeError`.** When a task fails, its siblings are aborted (`fail-fast`, the default) or left to
|
|
76
|
+
finish (`settle`). Either way, what they already wrote is kept.
|
|
77
|
+
- **Several questions at once.** Parallel tasks can each interrupt. Paused checkpoints and results
|
|
78
|
+
carry `interrupts`, and `resumeInterrupts()` answers any subset by id. A single question still works
|
|
79
|
+
exactly as before through `resume()`.
|
|
80
|
+
- **Install weight is now measured honestly.** The size table only ever counted this package's own
|
|
81
|
+
files, so a small entry point could quietly force a large dependency on the consumer.
|
|
82
|
+
- `npm run size:check` follows bare imports too, and the README table gains a column for what each
|
|
83
|
+
entry point makes you install. Most entry points force nothing: `/graph`, `/operations`,
|
|
84
|
+
`/batch`, `/embeddings`, and every image subpath import no third-party package at all. The root
|
|
85
|
+
import, `/core`, `/config`, and `/security` pull in 3.4 to 4.7 MB, because JSON-schema and Zod
|
|
86
|
+
validation live there.
|
|
87
|
+
- The clean-install test now measures and bounds the whole production install: 10.0 MB of
|
|
88
|
+
`node_modules`, of which 3.0 MB is this package.
|
|
89
|
+
|
|
90
|
+
### Fixed
|
|
91
|
+
|
|
92
|
+
- **A retry no longer lets the process exit while it waits.** The backoff timer was unref'd, so a run
|
|
93
|
+
waiting to retry could be abandoned by an otherwise idle process.
|
|
94
|
+
|
|
7
95
|
## [1.10.0] - 2026-09-16
|
|
8
96
|
|
|
9
97
|
Image portability. Three image backends now sit behind one contract: OpenAI, Google Imagen, and a
|
|
@@ -565,7 +653,9 @@ shared policy vocabulary is in place for a future release that revisits this.
|
|
|
565
653
|
- URL fetching rejects unsafe private-network targets and limits response reads.
|
|
566
654
|
- CLI and audit findings no longer disclose detected secret values.
|
|
567
655
|
|
|
568
|
-
[Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.
|
|
656
|
+
[Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.12.0...HEAD
|
|
657
|
+
[1.12.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.11.0...v1.12.0
|
|
658
|
+
[1.11.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.10.0...v1.11.0
|
|
569
659
|
[1.10.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.9.0...v1.10.0
|
|
570
660
|
[1.9.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.8.0...v1.9.0
|
|
571
661
|
[1.8.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.7.0...v1.8.0
|
package/README.md
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
# nexus-ai-pro
|
|
2
2
|
|
|
3
|
-
The
|
|
3
|
+
The typed AI framework for TypeScript. One API for provider routing and failover, agent graphs that
|
|
4
|
+
run branches in parallel and survive a restart, durable background operations, guardrails, cost
|
|
5
|
+
control, images, voice, and evals.
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
Import the whole runtime, or one piece: every capability has its own entry point with a size budget
|
|
8
|
+
CI enforces, and the table below publishes what each one costs. A graph-only application loads 60 KB
|
|
9
|
+
and installs no third-party package at all.
|
|
6
10
|
|
|
7
11
|
- NPM: https://www.npmjs.com/package/nexus-ai-pro
|
|
8
12
|
- GitHub: https://github.com/mkhitar-abrahamyan/nexus-ai
|
|
@@ -452,6 +456,58 @@ whether that means overwrite, append, or sum. Assignment would silently drop one
|
|
|
452
456
|
**Cycles are first-class**, so `maxSteps` is what stands between a mistaken router and an infinite
|
|
453
457
|
loop. Exceeding it names the nodes still pending rather than hanging.
|
|
454
458
|
|
|
459
|
+
**Branches run in parallel.** Every task in a superstep starts together, so four branches of three
|
|
460
|
+
seconds each finish in about three seconds rather than twelve:
|
|
461
|
+
|
|
462
|
+
```ts
|
|
463
|
+
.addConditionalEdges('plan', () => ['research-a', 'research-b', 'research-c', 'research-d'])
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
`maxConcurrency` caps how many run at once — 16 by default, settable per compile and per run, and
|
|
467
|
+
`1` restores strictly sequential execution. Whatever the timing, writes are reduced in task order, so
|
|
468
|
+
a replay produces the same state. A superstep with one task skips the scheduler entirely.
|
|
469
|
+
|
|
470
|
+
**Fan out over data decided at run time.** Edges can only name nodes that already exist. `Send`
|
|
471
|
+
creates one task per value instead, each reading its own `context.input`:
|
|
472
|
+
|
|
473
|
+
```ts
|
|
474
|
+
import { Send } from 'nexus-ai-pro/graph';
|
|
475
|
+
|
|
476
|
+
.addNode('research', async (ctx) => ({ findings: [await fetch(ctx.input as string)] }), { ends: [END] })
|
|
477
|
+
.addConditionalEdges('plan', (state) => state.urls.map((url) => new Send('research', url)))
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
Fifty-seven URLs become fifty-seven tasks of one node in a single superstep, bounded by
|
|
481
|
+
`maxConcurrency` and checkpointed individually, so a resume re-runs only the copies that did not
|
|
482
|
+
finish. Declaring `ends` keeps compile-time reachability checks exact.
|
|
483
|
+
|
|
484
|
+
**Retries and timeouts per node.** A node that calls a flaky service can retry on its own, without
|
|
485
|
+
wrapping every node body in the same try/catch:
|
|
486
|
+
|
|
487
|
+
```ts
|
|
488
|
+
.addNode('fetch', fetchNode, {
|
|
489
|
+
retry: { maxAttempts: 3, initialIntervalMs: 500, backoffFactor: 2, jitter: true },
|
|
490
|
+
timeoutMs: 10_000,
|
|
491
|
+
})
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
Retrying defaults to off, because only you know whether a node is idempotent; `compile({ retry })`
|
|
495
|
+
sets a default for every node. Interrupts, aborts, and validation errors are never retried.
|
|
496
|
+
`context.attempt` tells a node which try it is on, and a timeout aborts the node's signal and fails
|
|
497
|
+
that attempt. When one task fails, its siblings are aborted by default; `onNodeError: 'settle'` lets
|
|
498
|
+
them finish first, and either way what they already wrote is kept.
|
|
499
|
+
|
|
500
|
+
**Several questions at once.** Parallel tasks can each interrupt. The paused result carries every
|
|
501
|
+
pending question, and they can be answered together or a few at a time:
|
|
502
|
+
|
|
503
|
+
```ts
|
|
504
|
+
const run = await graph.invoke(input, { threadId });
|
|
505
|
+
await graph.resumeInterruptsWith(threadId, {
|
|
506
|
+
[run.interrupts[0].id]: true,
|
|
507
|
+
[run.interrupts[1].id]: 'use the second draft',
|
|
508
|
+
});
|
|
509
|
+
```
|
|
510
|
+
|
|
455
511
|
**Human in the loop.** A node calls `interrupt()`; the graph checkpoints and stops:
|
|
456
512
|
|
|
457
513
|
```ts
|
|
@@ -481,7 +537,100 @@ await graph.history(threadId); // newest first
|
|
|
481
537
|
graph.resumeFrom(threadId, 3); // rewind and run forward
|
|
482
538
|
```
|
|
483
539
|
|
|
484
|
-
**
|
|
540
|
+
**Route from inside a node.** When a node already knows where to go — an agent that just picked a
|
|
541
|
+
tool, a triage step that classified a ticket — return a `Command` instead of splitting the decision
|
|
542
|
+
into a separate router:
|
|
543
|
+
|
|
544
|
+
```ts
|
|
545
|
+
import { Command } from 'nexus-ai-pro/graph';
|
|
546
|
+
|
|
547
|
+
.addNode('triage', (ctx) =>
|
|
548
|
+
new Command({ update: { label: classify(ctx.state) }, goto: isUrgent(ctx.state) ? 'page-oncall' : 'queue' }),
|
|
549
|
+
{ ends: ['page-oncall', 'queue'] },
|
|
550
|
+
)
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
`goto` takes node names or `Send`s, and `ends` keeps compile-time checks and diagrams exact. A node
|
|
554
|
+
inside a subgraph can return `new Command({ graph: Command.PARENT, goto: 'human' })` to hand control
|
|
555
|
+
back to the graph that contains it — how a nested agent escalates.
|
|
556
|
+
|
|
557
|
+
**Aggregate after branches of different lengths.** `{ defer: true }` holds a node until every other
|
|
558
|
+
pending task has finished, so an aggregator after a two-step branch and a one-step branch runs once,
|
|
559
|
+
after both, rather than once per arrival.
|
|
560
|
+
|
|
561
|
+
**Pause for inspection.** Breakpoints stop a run before or after chosen nodes, checkpointed, without
|
|
562
|
+
changing the nodes themselves:
|
|
563
|
+
|
|
564
|
+
```ts
|
|
565
|
+
const graph = builder.compile({ interruptBefore: ['publish'] });
|
|
566
|
+
const paused = await graph.invoke(input, { threadId }); // status: 'interrupted', breakpoint: { when: 'before', ... }
|
|
567
|
+
await graph.updateState(threadId, { draft: fixedDraft }); // correct something first, if needed
|
|
568
|
+
for await (const _ of graph.continue(threadId)) {} // then carry on
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
Set them per compile or per run; a run's list replaces the compiled one.
|
|
572
|
+
|
|
573
|
+
**Edit state, or fork a thread.** `updateState(threadId, update)` merges a correction into the latest
|
|
574
|
+
checkpoint through the channel reducers, leaving any pending question in place.
|
|
575
|
+
`updateState(threadId, update, { asNode: 'research' })` applies it as if that node had produced it,
|
|
576
|
+
so the next step follows that node's edges. `fork(threadId, { step })` copies the history up to a step
|
|
577
|
+
into a new thread and leaves the original untouched, so two answers to the same question can be run
|
|
578
|
+
side by side.
|
|
579
|
+
|
|
580
|
+
**Watch it run.** `onEvent` receives every task starting, retrying, and finishing with its update,
|
|
581
|
+
every checkpoint written, and whatever a node passes to `context.emit()` — model tokens as they
|
|
582
|
+
stream, a status line, an intermediate result:
|
|
583
|
+
|
|
584
|
+
```ts
|
|
585
|
+
.addNode('write', async (ctx) => {
|
|
586
|
+
for await (const token of streamAnswer(ctx.state)) ctx.emit({ token });
|
|
587
|
+
return { answer };
|
|
588
|
+
})
|
|
589
|
+
|
|
590
|
+
await graph.invoke(input, {
|
|
591
|
+
onEvent: (event) => event.type === 'custom' && process.stdout.write((event.data as { token: string }).token),
|
|
592
|
+
});
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
A listener that throws never fails the run it is watching.
|
|
596
|
+
|
|
597
|
+
**See the shape.** `graph.describe()` returns nodes, edges, and which routes are decided at run
|
|
598
|
+
time, as plain JSON. `toMermaid()` from `nexus-ai-pro/graph/visualize` draws it — solid arrows are
|
|
599
|
+
always taken, dotted ones are chosen at run time, and subgraphs are drawn inside the node that runs
|
|
600
|
+
them:
|
|
601
|
+
|
|
602
|
+
```ts
|
|
603
|
+
import { toMermaid } from 'nexus-ai-pro/graph/visualize';
|
|
604
|
+
|
|
605
|
+
console.log(toMermaid(graph, { highlight: checkpoint.next }));
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
This diagram is that function's actual output for a research-and-review graph:
|
|
609
|
+
|
|
610
|
+
```mermaid
|
|
611
|
+
flowchart TD
|
|
612
|
+
__start__([start])
|
|
613
|
+
__end__([end])
|
|
614
|
+
n_plan["plan"]
|
|
615
|
+
n_research["research<br/><small>retries</small>"]
|
|
616
|
+
n_summarize["summarize<br/><small>deferred</small>"]
|
|
617
|
+
n_review["review"]
|
|
618
|
+
n_publish["publish"]
|
|
619
|
+
__start__ --> n_plan
|
|
620
|
+
n_summarize --> n_review
|
|
621
|
+
n_publish --> __end__
|
|
622
|
+
n_plan -.-> n_research
|
|
623
|
+
n_research -.-> n_summarize
|
|
624
|
+
n_review -.-> n_publish
|
|
625
|
+
n_review -.-> n_plan
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
The visualizer is its own subpath and imports no runtime code, so drawing a graph costs nothing to an
|
|
629
|
+
application that only runs one. No image renderer is bundled; any Mermaid renderer turns the output
|
|
630
|
+
into PNG or SVG.
|
|
631
|
+
|
|
632
|
+
**Durable by construction.** The default checkpointer is in-process, holding up to 1,000 threads;
|
|
633
|
+
pass `checkpointer: false` to turn checkpointing off. Point it at the operation store
|
|
485
634
|
that already backs durable operations and a thread survives a restart — a different worker resumes
|
|
486
635
|
what another suspended:
|
|
487
636
|
|
|
@@ -1750,85 +1899,94 @@ Importing one piece loads one piece. The figures below are the transitive import
|
|
|
1750
1899
|
point, generated from the build by `npm run size:check`, which fails CI when an entry point grows
|
|
1751
1900
|
past its budget — so the numbers stay true rather than aspirational.
|
|
1752
1901
|
|
|
1902
|
+
The last column is the part a file-size table usually hides: what an entry point forces you to
|
|
1903
|
+
install. Most of them force nothing at all — `/graph`, `/operations`, `/images/*`, `/batch`,
|
|
1904
|
+
`/embeddings` and the rest import no third-party package. The root import, `/core`, `/config`, and
|
|
1905
|
+
`/security` do, because JSON-schema and Zod validation live there. A production install of this
|
|
1906
|
+
package is about 10 MB of `node_modules`, of which 3 MB is this package; the clean-install test holds
|
|
1907
|
+
that total to a ceiling too. `@types/node` accounts for a further 2.4 MB at install time but is types
|
|
1908
|
+
only, so it never appears in an import graph.
|
|
1909
|
+
|
|
1753
1910
|
<!-- 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/
|
|
1775
|
-
| `nexus-ai-pro/
|
|
1776
|
-
| `nexus-ai-pro/batch/
|
|
1777
|
-
| `nexus-ai-pro/
|
|
1778
|
-
| `nexus-ai-pro/
|
|
1779
|
-
| `nexus-ai-pro/
|
|
1780
|
-
| `nexus-ai-pro/
|
|
1781
|
-
| `nexus-ai-pro/
|
|
1782
|
-
| `nexus-ai-pro/
|
|
1783
|
-
| `nexus-ai-pro/
|
|
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/
|
|
1817
|
-
| `nexus-ai-pro/
|
|
1818
|
-
| `nexus-ai-pro/
|
|
1819
|
-
| `nexus-ai-pro/
|
|
1820
|
-
| `nexus-ai-pro/
|
|
1821
|
-
| `nexus-ai-pro/
|
|
1822
|
-
| `nexus-ai-pro/
|
|
1823
|
-
| `nexus-ai-pro/cache` |
|
|
1824
|
-
| `nexus-ai-pro/cache
|
|
1825
|
-
| `nexus-ai-pro/
|
|
1826
|
-
| `nexus-ai-pro/
|
|
1827
|
-
| `nexus-ai-pro/jobs
|
|
1828
|
-
| `nexus-ai-pro/jobs/
|
|
1829
|
-
| `nexus-ai-pro/
|
|
1830
|
-
| `nexus-ai-pro/
|
|
1831
|
-
| `nexus-ai-pro/
|
|
1911
|
+
| Import | Size | Share of root | Third-party install |
|
|
1912
|
+
| --- | --- | --- | --- |
|
|
1913
|
+
| `nexus-ai-pro` | 612 KB | 100% | +4.7 MB |
|
|
1914
|
+
| `nexus-ai-pro/config` | 468 KB | 76% | +4.7 MB |
|
|
1915
|
+
| `nexus-ai-pro/core` | 462 KB | 75% | +4.7 MB |
|
|
1916
|
+
| `nexus-ai-pro/realtime` | 157 KB | 26% | none |
|
|
1917
|
+
| `nexus-ai-pro/batch` | 118 KB | 19% | none |
|
|
1918
|
+
| `nexus-ai-pro/realtime/session` | 94 KB | 15% | none |
|
|
1919
|
+
| `nexus-ai-pro/embeddings` | 91 KB | 15% | none |
|
|
1920
|
+
| `nexus-ai-pro/providers/groq` | 79 KB | 13% | none |
|
|
1921
|
+
| `nexus-ai-pro/providers/mistral` | 79 KB | 13% | none |
|
|
1922
|
+
| `nexus-ai-pro/providers/azure-openai` | 78 KB | 13% | none |
|
|
1923
|
+
| `nexus-ai-pro/providers/openrouter` | 78 KB | 13% | none |
|
|
1924
|
+
| `nexus-ai-pro/providers/deepseek` | 77 KB | 13% | none |
|
|
1925
|
+
| `nexus-ai-pro/providers/llamacpp` | 77 KB | 13% | none |
|
|
1926
|
+
| `nexus-ai-pro/providers/lmstudio` | 77 KB | 13% | none |
|
|
1927
|
+
| `nexus-ai-pro/providers/openai` | 77 KB | 13% | none |
|
|
1928
|
+
| `nexus-ai-pro/providers/anthropic` | 70 KB | 11% | none |
|
|
1929
|
+
| `nexus-ai-pro/providers/google` | 66 KB | 11% | none |
|
|
1930
|
+
| `nexus-ai-pro/images` | 65 KB | 11% | none |
|
|
1931
|
+
| `nexus-ai-pro/graph` | 60 KB | 10% | none |
|
|
1932
|
+
| `nexus-ai-pro/providers/ollama` | 58 KB | 9% | none |
|
|
1933
|
+
| `nexus-ai-pro/batch/openai` | 52 KB | 8% | none |
|
|
1934
|
+
| `nexus-ai-pro/batch/anthropic` | 52 KB | 8% | none |
|
|
1935
|
+
| `nexus-ai-pro/operations` | 49 KB | 8% | none |
|
|
1936
|
+
| `nexus-ai-pro/batch/mock` | 47 KB | 8% | none |
|
|
1937
|
+
| `nexus-ai-pro/providers/cohere` | 46 KB | 8% | none |
|
|
1938
|
+
| `nexus-ai-pro/realtime/openai-webrtc` | 46 KB | 8% | none |
|
|
1939
|
+
| `nexus-ai-pro/security` | 40 KB | 7% | +3.4 MB |
|
|
1940
|
+
| `nexus-ai-pro/models` | 35 KB | 6% | none |
|
|
1941
|
+
| `nexus-ai-pro/images/inputs` | 31 KB | 5% | none |
|
|
1942
|
+
| `nexus-ai-pro/realtime/openai-websocket` | 29 KB | 5% | none |
|
|
1943
|
+
| `nexus-ai-pro/evals` | 23 KB | 4% | none |
|
|
1944
|
+
| `nexus-ai-pro/images/transform` | 22 KB | 4% | none |
|
|
1945
|
+
| `nexus-ai-pro/images/stores` | 22 KB | 4% | none |
|
|
1946
|
+
| `nexus-ai-pro/telephony/twilio` | 21 KB | 3% | none |
|
|
1947
|
+
| `nexus-ai-pro/telephony` | 20 KB | 3% | none |
|
|
1948
|
+
| `nexus-ai-pro/images/openai` | 19 KB | 3% | none |
|
|
1949
|
+
| `nexus-ai-pro/realtime/mock` | 19 KB | 3% | none |
|
|
1950
|
+
| `nexus-ai-pro/voice` | 18 KB | 3% | none |
|
|
1951
|
+
| `nexus-ai-pro/images/comfyui` | 17 KB | 3% | none |
|
|
1952
|
+
| `nexus-ai-pro/embeddings/adapters` | 17 KB | 3% | none |
|
|
1953
|
+
| `nexus-ai-pro/realtime/conversation` | 16 KB | 3% | none |
|
|
1954
|
+
| `nexus-ai-pro/images/google` | 15 KB | 2% | none |
|
|
1955
|
+
| `nexus-ai-pro/images/evals` | 15 KB | 2% | none |
|
|
1956
|
+
| `nexus-ai-pro/images/assets` | 14 KB | 2% | none |
|
|
1957
|
+
| `nexus-ai-pro/context` | 13 KB | 2% | none |
|
|
1958
|
+
| `nexus-ai-pro/operations/adapters` | 13 KB | 2% | none |
|
|
1959
|
+
| `nexus-ai-pro/realtime/tools` | 13 KB | 2% | none |
|
|
1960
|
+
| `nexus-ai-pro/workflows` | 13 KB | 2% | none |
|
|
1961
|
+
| `nexus-ai-pro/images/mock` | 12 KB | 2% | none |
|
|
1962
|
+
| `nexus-ai-pro/optimizer` | 11 KB | 2% | none |
|
|
1963
|
+
| `nexus-ai-pro/voice/session` | 11 KB | 2% | none |
|
|
1964
|
+
| `nexus-ai-pro/providers` | 10 KB | 2% | none |
|
|
1965
|
+
| `nexus-ai-pro/providers/base` | 10 KB | 2% | none |
|
|
1966
|
+
| `nexus-ai-pro/embeddings/models` | 10 KB | 2% | none |
|
|
1967
|
+
| `nexus-ai-pro/voice/openai` | 9 KB | 1% | none |
|
|
1968
|
+
| `nexus-ai-pro/images/moderation` | 9 KB | 1% | none |
|
|
1969
|
+
| `nexus-ai-pro/ops/circuit-breaker` | 8 KB | 1% | none |
|
|
1970
|
+
| `nexus-ai-pro/realtime/openai-server` | 8 KB | 1% | none |
|
|
1971
|
+
| `nexus-ai-pro/capabilities` | 8 KB | 1% | none |
|
|
1972
|
+
| `nexus-ai-pro/telephony/realtime-bridge` | 7 KB | 1% | none |
|
|
1973
|
+
| `nexus-ai-pro/graph/visualize` | 6 KB | 1.0% | none |
|
|
1974
|
+
| `nexus-ai-pro/providers/errors` | 5 KB | 0.8% | none |
|
|
1975
|
+
| `nexus-ai-pro/embeddings/mock` | 5 KB | 0.8% | none |
|
|
1976
|
+
| `nexus-ai-pro/cache/semantic-cache` | 5 KB | 0.8% | none |
|
|
1977
|
+
| `nexus-ai-pro/evals/judge` | 5 KB | 0.8% | none |
|
|
1978
|
+
| `nexus-ai-pro/operations/webhooks` | 4 KB | 0.7% | none |
|
|
1979
|
+
| `nexus-ai-pro/ops/rate-limit-adapters` | 3 KB | 0.5% | none |
|
|
1980
|
+
| `nexus-ai-pro/cache/memory-cache` | 3 KB | 0.5% | none |
|
|
1981
|
+
| `nexus-ai-pro/cache` | 2 KB | 0.3% | none |
|
|
1982
|
+
| `nexus-ai-pro/cache/adapters` | 2 KB | 0.3% | none |
|
|
1983
|
+
| `nexus-ai-pro/rag` | 2 KB | 0.3% | none |
|
|
1984
|
+
| `nexus-ai-pro/jobs` | 2 KB | 0.3% | none |
|
|
1985
|
+
| `nexus-ai-pro/jobs/durable-adapters` | 2 KB | 0.3% | none |
|
|
1986
|
+
| `nexus-ai-pro/jobs/queue` | 2 KB | 0.3% | none |
|
|
1987
|
+
| `nexus-ai-pro/streaming` | 1 KB | 0.2% | none |
|
|
1988
|
+
| `nexus-ai-pro/providers/type-guards` | 1 KB | 0.2% | none |
|
|
1989
|
+
| `nexus-ai-pro/jobs/batch` | 1 KB | 0.2% | none |
|
|
1832
1990
|
<!-- size-table:end -->
|
|
1833
1991
|
|
|
1834
1992
|
A capability that only works by importing the whole runtime is treated as a design problem, not an
|
package/dist/graph/errors.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
/**
|
|
50
|
-
|
|
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;
|
package/dist/graph/errors.js
CHANGED
|
@@ -26,12 +26,16 @@ export class GraphInterrupt extends GraphError {
|
|
|
26
26
|
node;
|
|
27
27
|
step;
|
|
28
28
|
index;
|
|
29
|
-
|
|
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
|
-
/**
|
|
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
|
}
|