nexus-ai-pro 1.12.0 → 1.14.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.
Files changed (80) hide show
  1. package/API_STABILITY.md +27 -0
  2. package/CHANGELOG.md +66 -1
  3. package/README.md +160 -0
  4. package/dist/agent/create-agent.d.ts +119 -0
  5. package/dist/agent/create-agent.js +176 -0
  6. package/dist/agent/index.d.ts +5 -0
  7. package/dist/agent/index.js +4 -0
  8. package/dist/agent/middleware.d.ts +46 -0
  9. package/dist/agent/middleware.js +77 -0
  10. package/dist/graph/graph.js +1 -0
  11. package/dist/mcp/client.d.ts +106 -0
  12. package/dist/mcp/client.js +243 -0
  13. package/dist/mcp/index.d.ts +3 -0
  14. package/dist/mcp/index.js +3 -0
  15. package/dist/mcp/protocol.d.ts +71 -0
  16. package/dist/mcp/protocol.js +68 -0
  17. package/dist/mcp/server.d.ts +46 -0
  18. package/dist/mcp/server.js +155 -0
  19. package/dist/store/index.d.ts +2 -0
  20. package/dist/store/index.js +1 -0
  21. package/dist/store/memory.d.ts +41 -0
  22. package/dist/store/memory.js +182 -0
  23. package/dist/store/redis.d.ts +49 -0
  24. package/dist/store/redis.js +128 -0
  25. package/dist/tracing/alerts.d.ts +60 -0
  26. package/dist/tracing/alerts.js +98 -0
  27. package/dist/tracing/compare.d.ts +31 -0
  28. package/dist/tracing/compare.js +69 -0
  29. package/dist/tracing/index.d.ts +6 -0
  30. package/dist/tracing/index.js +5 -0
  31. package/dist/tracing/instrument.d.ts +47 -0
  32. package/dist/tracing/instrument.js +138 -0
  33. package/dist/tracing/stores.d.ts +53 -0
  34. package/dist/tracing/stores.js +192 -0
  35. package/dist/tracing/tracer.d.ts +73 -0
  36. package/dist/tracing/tracer.js +182 -0
  37. package/dist/types/graph.d.ts +10 -0
  38. package/dist/types/store.d.ts +61 -0
  39. package/dist/types/store.js +9 -0
  40. package/dist/types/tracing.d.ts +102 -0
  41. package/dist/types/tracing.js +10 -0
  42. package/dist-cjs/agent/create-agent.d.ts +119 -0
  43. package/dist-cjs/agent/create-agent.js +181 -0
  44. package/dist-cjs/agent/index.d.ts +5 -0
  45. package/dist-cjs/agent/index.js +16 -0
  46. package/dist-cjs/agent/middleware.d.ts +46 -0
  47. package/dist-cjs/agent/middleware.js +82 -0
  48. package/dist-cjs/graph/graph.js +1 -0
  49. package/dist-cjs/mcp/client.d.ts +106 -0
  50. package/dist-cjs/mcp/client.js +282 -0
  51. package/dist-cjs/mcp/index.d.ts +3 -0
  52. package/dist-cjs/mcp/index.js +15 -0
  53. package/dist-cjs/mcp/protocol.d.ts +71 -0
  54. package/dist-cjs/mcp/protocol.js +77 -0
  55. package/dist-cjs/mcp/server.d.ts +46 -0
  56. package/dist-cjs/mcp/server.js +160 -0
  57. package/dist-cjs/store/index.d.ts +2 -0
  58. package/dist-cjs/store/index.js +6 -0
  59. package/dist-cjs/store/memory.d.ts +41 -0
  60. package/dist-cjs/store/memory.js +190 -0
  61. package/dist-cjs/store/redis.d.ts +49 -0
  62. package/dist-cjs/store/redis.js +132 -0
  63. package/dist-cjs/tracing/alerts.d.ts +60 -0
  64. package/dist-cjs/tracing/alerts.js +104 -0
  65. package/dist-cjs/tracing/compare.d.ts +31 -0
  66. package/dist-cjs/tracing/compare.js +73 -0
  67. package/dist-cjs/tracing/index.d.ts +6 -0
  68. package/dist-cjs/tracing/index.js +21 -0
  69. package/dist-cjs/tracing/instrument.d.ts +47 -0
  70. package/dist-cjs/tracing/instrument.js +142 -0
  71. package/dist-cjs/tracing/stores.d.ts +53 -0
  72. package/dist-cjs/tracing/stores.js +202 -0
  73. package/dist-cjs/tracing/tracer.d.ts +73 -0
  74. package/dist-cjs/tracing/tracer.js +187 -0
  75. package/dist-cjs/types/graph.d.ts +10 -0
  76. package/dist-cjs/types/store.d.ts +61 -0
  77. package/dist-cjs/types/store.js +10 -0
  78. package/dist-cjs/types/tracing.d.ts +102 -0
  79. package/dist-cjs/types/tracing.js +11 -0
  80. package/package.json +66 -1
package/API_STABILITY.md CHANGED
@@ -328,6 +328,33 @@ Also since 1.12.0, all additive:
328
328
  - A subgraph node's thread id is `<parent thread>:<task id>`, which equals the previous
329
329
  `<parent thread>:<node>` for any node not reached through `Send`.
330
330
 
331
+ ## Agent, store, MCP, and tracing stages (1.14.0)
332
+
333
+ The `nexus-ai-pro/agent`, `nexus-ai-pro/store`, `nexus-ai-pro/store/redis`, and `nexus-ai-pro/mcp`
334
+ subpaths are public and follow the 1.x rules. None is exported from the root import.
335
+
336
+ - `createAgent()` returns a compiled graph, so the graph guarantees above apply to an agent: a
337
+ superstep is atomic with respect to checkpointing, a finished task is never re-run by a resume, and
338
+ an approval survives a restart. The agent's state channels (`messages`, `iterations`, `answer`,
339
+ `stopReason`) are a normalized shape; what a model decides to put in them is not.
340
+ - `Store` is the contract; `MemoryStore` and `RedisStore` implement it. Ranking from a semantic search
341
+ depends on the embedding function a caller injects and is not a guarantee. `MemoryStore` is
342
+ process-local and bounded, and drops the least recently written item past `maxItems`.
343
+ - The MCP subpath implements a subset of the Model Context Protocol: initialize, tools, resources, and
344
+ prompts, over stdio and HTTP. Protocol versions and server behaviour are upstream contracts outside
345
+ this policy. Server-initiated streaming, sampling, and roots are not implemented; new methods may be
346
+ added in a minor release.
347
+ - `nexus-ai-pro/tracing` is public: `Tracer`, the `TraceStore` contract, `MemoryTraceStore`,
348
+ `JsonlTraceStore`, `traceGraph()`, `traceModelClient()`, `compareTraces()`, and `AlertEvaluator`.
349
+ The `Run`, `RunTree`, and `RunQuery` shapes are normalized; what a model puts in a run's inputs and
350
+ outputs is not. New `RunKind` values and query fields may be added in a minor release. Sampling is
351
+ probabilistic by definition, so which traces are kept is not a guarantee, but the tail rules are:
352
+ an error, a run past `keepSlowerThanMs`, or one past `keepCostlierThan` is always kept.
353
+ - `agentAsTool()` and the bundled middleware (`summarizeHistory`, `redactMessages`, `limitToolCalls`)
354
+ are public and additive.
355
+ - `AgentLoop`, `tool()`, and `ToolExecutor` keep their behaviour and are re-exported from
356
+ `nexus-ai-pro/agent` as well as the root.
357
+
331
358
  ## Deprecation process
332
359
 
333
360
  Deprecated APIs are marked with `@deprecated` in declarations and described in the changelog. Removals
package/CHANGELOG.md CHANGED
@@ -4,6 +4,70 @@ Notable changes to this project are recorded here. The format follows [Keep a Ch
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.14.0] - 2026-09-20
8
+
9
+ The memory, agent, and MCP work planned for 1.13.0 ships here together with tracing, so no 1.13.0 was
10
+ published.
11
+
12
+ Memory, agents, MCP, and traces. An agent is now a graph rather than a loop, so it inherits
13
+ approvals, parallel tool calls, and durability. A store gives it memory that outlives a thread, MCP
14
+ connects it to tools this package does not have to ship, and every run can be recorded as a tree you
15
+ can search, compare, and alert on.
16
+
17
+ ### Added
18
+
19
+ - **`nexus-ai-pro/agent` and `createAgent()`.** Returns a compiled graph, so an agent gets
20
+ checkpoints, resume, forks, events, and a diagram from the graph runtime rather than from code
21
+ written twice.
22
+ - Tool calls run as parallel tasks, bounded by `toolConcurrency`.
23
+ - `interruptOn` makes a tool wait for a human. The approval is a checkpoint, so it can arrive days
24
+ later from another process, and it can approve, refuse with a reason the model sees, or approve
25
+ with corrected arguments.
26
+ - Middleware wraps the model call (`beforeModel`, `afterModel`) and each tool call
27
+ (`wrapToolCall`).
28
+ - `stopReason` distinguishes a finished answer from one that ran out of iterations, and malformed
29
+ tool arguments never reach a tool.
30
+ - `AgentLoop` is unchanged, for the case that wants none of this.
31
+ - **`nexus-ai-pro/store`, long-term memory.** Namespaced tuples, `put`/`get`/`delete`/`search`/
32
+ `listNamespaces`, TTLs, and field filters.
33
+ - Semantic search ranks by any embedding function you inject, so the store never imports the
34
+ embeddings runtime; without an index, a query matches text.
35
+ - `MemoryStore` is bounded, like the graph's memory checkpointer. `RedisStore`
36
+ (`nexus-ai-pro/store/redis`) shares memory across processes through a client-like interface.
37
+ - `compile({ store })` hands it to every node as `context.store`.
38
+ - **`nexus-ai-pro/tracing`, run trees you can query.** Metrics say how it is going; a trace says what
39
+ happened.
40
+ - Every family has a run kind: model, tool, graph, node, agent, retriever, embedding, image, voice,
41
+ realtime, operation. A run carries inputs, outputs, timing, tokens, cost, tags, and errors.
42
+ - `traceGraph()` turns the graph's own event stream into a run tree, so nothing inside the graph
43
+ knows about tracing and an untraced run pays nothing. `traceModelClient()` nests each model call
44
+ inside the node that made it, with its tokens and cost.
45
+ - Tail sampling keeps every error, slow run, and expensive run whatever the head rate is, because
46
+ the decision is made when a trace finishes.
47
+ - Redaction runs before storage: hidden fields are never written, and a `redact` hook gets the last
48
+ word.
49
+ - `MemoryTraceStore` is bounded; `JsonlTraceStore` appends to a file that survives a restart and can
50
+ be read back, pruned, and rotated.
51
+ - Queries filter by kind, status, name, model, tags, metadata, latency, cost, time, and feedback.
52
+ `tree()` assembles a trace, `formatTree()` prints it, and `compareTraces()` reports what changed
53
+ between two runs of the same shape.
54
+ - `recordFeedback()` attaches scores from people or from evaluators.
55
+ - `AlertEvaluator` runs rules over stored runs — error rate, latency percentiles, cost, count — and
56
+ every alert carries the runs that tripped it. `createWebhookNotifier()` posts them.
57
+ - **Bundled agent middleware.** `summarizeHistory` keeps a long transcript from growing without
58
+ bound, `redactMessages` keeps secrets out of provider requests and responses, and `limitToolCalls`
59
+ caps how often a tool may run.
60
+ - **`agentAsTool()`.** Gives one agent to another as a tool, so a supervisor can delegate while
61
+ keeping control. Each agent keeps its own memory, approvals, and thread.
62
+
63
+ - **`nexus-ai-pro/mcp`, both directions of the Model Context Protocol.** Implemented directly, with
64
+ no protocol SDK dependency.
65
+ - `McpClient` lists and calls tools, reads resources and prompts, and `toNexusTools()` turns a
66
+ server's tools into tools an agent can call, with optional name prefixes.
67
+ - `McpServer` exposes this application's tools and resources to other assistants.
68
+ - Stdio and HTTP transports are included, and the transport is an interface, so a client and a
69
+ server can be wired together in memory for a test.
70
+
7
71
  ## [1.12.0] - 2026-09-20
8
72
 
9
73
  Graph control and introspection. A node can route itself, a run can pause at breakpoints and be
@@ -653,7 +717,8 @@ shared policy vocabulary is in place for a future release that revisits this.
653
717
  - URL fetching rejects unsafe private-network targets and limits response reads.
654
718
  - CLI and audit findings no longer disclose detected secret values.
655
719
 
656
- [Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.12.0...HEAD
720
+ [Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.14.0...HEAD
721
+ [1.14.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.12.0...v1.14.0
657
722
  [1.12.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.11.0...v1.12.0
658
723
  [1.11.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.10.0...v1.11.0
659
724
  [1.10.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.9.0...v1.10.0
package/README.md CHANGED
@@ -655,6 +655,161 @@ private to the subgraph.
655
655
 
656
656
  The graph is not in the root import. It costs nothing to a user who does not build graphs.
657
657
 
658
+ ## Agents
659
+
660
+ An agent is a graph, so everything above applies to it: checkpoints, human approval, parallel work,
661
+ forks, events, and a diagram.
662
+
663
+ ```ts
664
+ import { createAgent, agentInput, tool } from 'nexus-ai-pro/agent';
665
+
666
+ const agent = createAgent({
667
+ client: ai, // anything with complete(); NexusAI qualifies
668
+ systemPrompt: 'You are a support engineer.',
669
+ tools: [refundTool, emailTool],
670
+ interruptOn: { send_email: true }, // this one waits for a human
671
+ store, // long-term memory, below
672
+ checkpointer, // survives a restart
673
+ });
674
+
675
+ const run = await agent.invoke(agentInput('Refund order 1182 and tell the customer'), { threadId });
676
+ run.state.answer; // the final text
677
+ run.state.stopReason; // 'completed' | 'max_iterations'
678
+ ```
679
+
680
+ **Tool calls run in parallel.** Each call the model requests becomes its own task, bounded by
681
+ `toolConcurrency`, so three lookups take as long as the slowest one.
682
+
683
+ **Approval is an interrupt, not a callback.** A tool listed in `interruptOn` pauses the run and
684
+ checkpoints it. The answer can approve, refuse with a reason the model sees, or approve with
685
+ corrected arguments:
686
+
687
+ ```ts
688
+ if (run.status === 'awaiting_input') {
689
+ await agent.resumeWith(threadId, { approved: true, args: { to: 'billing@example.com' } });
690
+ }
691
+ ```
692
+
693
+ Because it is a checkpoint, the approval can arrive days later, from another process.
694
+
695
+ **Middleware** wraps the parts worth controlling: `beforeModel` adjusts the request, `afterModel`
696
+ inspects or replaces the response, and `wrapToolCall` surrounds each tool call for logging, timing,
697
+ or policy. `AgentLoop` remains for the simple case that needs none of this.
698
+
699
+ ## Long-term Memory
700
+
701
+ A checkpoint remembers one conversation. A store remembers across them:
702
+
703
+ ```ts
704
+ import { MemoryStore } from 'nexus-ai-pro/store';
705
+
706
+ const store = new MemoryStore({ index: { embed, fields: ['text'] } }); // embed is yours
707
+ await store.put(['tenant-7', 'users', 'alice'], 'tone', { text: 'prefers brief answers' });
708
+
709
+ // In any node or tool, in any thread, later:
710
+ const memories = await context.store.search(['tenant-7', 'users', 'alice'], { query: 'how do they like answers?' });
711
+ ```
712
+
713
+ Namespaces are tuples, so `['tenant-7', 'users', 'alice']` is both a place to put something and a
714
+ prefix to search. Items can expire with `ttlMs`, be filtered by field, and be ranked semantically by
715
+ any embedding function you inject — the store never imports the embeddings runtime, and without an
716
+ index a query falls back to matching text. `RedisStore` from `nexus-ai-pro/store/redis` carries the
717
+ same contract across processes through a client-like interface, so no Redis package is a dependency
718
+ here.
719
+
720
+ ## MCP
721
+
722
+ The Model Context Protocol, both directions, implemented directly rather than through an SDK:
723
+
724
+ ```ts
725
+ import { McpClient, createStdioTransport } from 'nexus-ai-pro/mcp';
726
+ import { Tracer, MemoryTraceStore } from 'nexus-ai-pro/tracing';
727
+
728
+ const client = new McpClient(createStdioTransport({ command: 'npx', args: ['-y', 'some-mcp-server'] }));
729
+ const agent = createAgent({ client: ai, tools: await client.toNexusTools({ prefix: 'files' }) });
730
+ ```
731
+
732
+ That is how this package reaches a broad tool ecosystem without maintaining a catalogue of
733
+ integrations: anything exposed over MCP becomes tools an agent can call. The server direction lends
734
+ your tools out the same way, so a tool written once with `tool()` serves an agent here and an editor
735
+ elsewhere:
736
+
737
+ ```ts
738
+ import { McpServer, createStdioServerTransport } from 'nexus-ai-pro/mcp';
739
+
740
+ await new McpServer({ name: 'my-app', tools: [refundTool] }).connect(createStdioServerTransport());
741
+ ```
742
+
743
+ Stdio and HTTP transports are included, and the transport is an interface, so a test can wire a
744
+ client to a server in memory.
745
+
746
+ ## Traces
747
+
748
+ Metrics say how it is going. A trace says what happened: the run tree of one request, with its
749
+ inputs, outputs, tokens, cost, and errors, stored where you can search it.
750
+
751
+ ```ts
752
+ import { Tracer, MemoryTraceStore, traceGraph, traceModelClient } from 'nexus-ai-pro/tracing';
753
+
754
+ const tracer = new Tracer({
755
+ store: new MemoryTraceStore(), // or JsonlTraceStore({ file })
756
+ sampling: { rate: 0.05, keepErrors: true }, // 5% of traces, plus every failure
757
+ redaction: { hideFields: ['apiKey', 'user.email'] },
758
+ });
759
+
760
+ const tracing = traceGraph(tracer, { name: 'support-agent', kind: 'agent' });
761
+ const agent = createAgent({ client: traceModelClient(ai, tracer, { parent: () => tracing.runFor('model') }) });
762
+
763
+ const run = await agent.invoke(agentInput(question), { threadId, ...tracing.runOptions });
764
+ await tracing.finish(run);
765
+ ```
766
+
767
+ **Nothing inside the graph knows about tracing.** The graph already reports every task starting,
768
+ retrying, and finishing; a trace is that stream written down as a tree. An application that does not
769
+ trace creates no context, allocates nothing, and does no per-call work.
770
+
771
+ **Tail sampling keeps what matters.** A 5% rate still records every error, every run slower than a
772
+ threshold, and every run more expensive than one, because the decision is made when the trace
773
+ finishes rather than when it starts.
774
+
775
+ **Redaction happens before storage**, so a field you hide is never written, and a `redact` hook gets
776
+ the last word on every run.
777
+
778
+ **Then ask questions of it:**
779
+
780
+ ```ts
781
+ const failures = await store.query({ status: 'error', kind: 'model', since: yesterday, minLatencyMs: 2000 });
782
+ const tree = await store.tree(failures[0].traceId);
783
+ console.log(formatTree(tree));
784
+
785
+ await tracer.recordFeedback(runId, { key: 'thumbs', score: 0, source: 'user' });
786
+ ```
787
+
788
+ `compareTraces(a, b)` lays two runs of the same shape side by side and reports what changed — the
789
+ step that got slower, the tool that stopped being called, the output that differs — which is how
790
+ "it worked yesterday" becomes an answerable question.
791
+
792
+ **Alerts** run the same queries on a timer, and carry the runs that tripped them:
793
+
794
+ ```ts
795
+ import { AlertEvaluator, createWebhookNotifier } from 'nexus-ai-pro/tracing';
796
+
797
+ const alerts = new AlertEvaluator(
798
+ store,
799
+ [
800
+ { name: 'error rate', metric: 'errorRate', threshold: 0.05, minRuns: 20 },
801
+ { name: 'p95 latency', metric: 'latencyP95', threshold: 8000 },
802
+ { name: 'hourly spend', metric: 'cost', threshold: 25, windowMs: 3_600_000 },
803
+ ],
804
+ { notifier: createWebhookNotifier({ url: process.env.ALERT_WEBHOOK! }) },
805
+ );
806
+
807
+ setInterval(() => void alerts.evaluate(), 60_000);
808
+ ```
809
+
810
+ An alert names the rule, the measured value, and three runs that contributed, so the next step is
811
+ reading them rather than starting an investigation.
812
+
658
813
  ## Provider Batch Tiers
659
814
 
660
815
  Both OpenAI and Anthropic sell an asynchronous tier at roughly half price, in exchange for a
@@ -1925,6 +2080,7 @@ only, so it never appears in an import graph.
1925
2080
  | `nexus-ai-pro/providers/llamacpp` | 77 KB | 13% | none |
1926
2081
  | `nexus-ai-pro/providers/lmstudio` | 77 KB | 13% | none |
1927
2082
  | `nexus-ai-pro/providers/openai` | 77 KB | 13% | none |
2083
+ | `nexus-ai-pro/agent` | 76 KB | 12% | none |
1928
2084
  | `nexus-ai-pro/providers/anthropic` | 70 KB | 11% | none |
1929
2085
  | `nexus-ai-pro/providers/google` | 66 KB | 11% | none |
1930
2086
  | `nexus-ai-pro/images` | 65 KB | 11% | none |
@@ -1940,6 +2096,7 @@ only, so it never appears in an import graph.
1940
2096
  | `nexus-ai-pro/models` | 35 KB | 6% | none |
1941
2097
  | `nexus-ai-pro/images/inputs` | 31 KB | 5% | none |
1942
2098
  | `nexus-ai-pro/realtime/openai-websocket` | 29 KB | 5% | none |
2099
+ | `nexus-ai-pro/tracing` | 26 KB | 4% | none |
1943
2100
  | `nexus-ai-pro/evals` | 23 KB | 4% | none |
1944
2101
  | `nexus-ai-pro/images/transform` | 22 KB | 4% | none |
1945
2102
  | `nexus-ai-pro/images/stores` | 22 KB | 4% | none |
@@ -1948,6 +2105,7 @@ only, so it never appears in an import graph.
1948
2105
  | `nexus-ai-pro/images/openai` | 19 KB | 3% | none |
1949
2106
  | `nexus-ai-pro/realtime/mock` | 19 KB | 3% | none |
1950
2107
  | `nexus-ai-pro/voice` | 18 KB | 3% | none |
2108
+ | `nexus-ai-pro/mcp` | 18 KB | 3% | none |
1951
2109
  | `nexus-ai-pro/images/comfyui` | 17 KB | 3% | none |
1952
2110
  | `nexus-ai-pro/embeddings/adapters` | 17 KB | 3% | none |
1953
2111
  | `nexus-ai-pro/realtime/conversation` | 16 KB | 3% | none |
@@ -1961,6 +2119,7 @@ only, so it never appears in an import graph.
1961
2119
  | `nexus-ai-pro/images/mock` | 12 KB | 2% | none |
1962
2120
  | `nexus-ai-pro/optimizer` | 11 KB | 2% | none |
1963
2121
  | `nexus-ai-pro/voice/session` | 11 KB | 2% | none |
2122
+ | `nexus-ai-pro/store/redis` | 11 KB | 2% | none |
1964
2123
  | `nexus-ai-pro/providers` | 10 KB | 2% | none |
1965
2124
  | `nexus-ai-pro/providers/base` | 10 KB | 2% | none |
1966
2125
  | `nexus-ai-pro/embeddings/models` | 10 KB | 2% | none |
@@ -1971,6 +2130,7 @@ only, so it never appears in an import graph.
1971
2130
  | `nexus-ai-pro/capabilities` | 8 KB | 1% | none |
1972
2131
  | `nexus-ai-pro/telephony/realtime-bridge` | 7 KB | 1% | none |
1973
2132
  | `nexus-ai-pro/graph/visualize` | 6 KB | 1.0% | none |
2133
+ | `nexus-ai-pro/store` | 6 KB | 1.0% | none |
1974
2134
  | `nexus-ai-pro/providers/errors` | 5 KB | 0.8% | none |
1975
2135
  | `nexus-ai-pro/embeddings/mock` | 5 KB | 0.8% | none |
1976
2136
  | `nexus-ai-pro/cache/semantic-cache` | 5 KB | 0.8% | none |
@@ -0,0 +1,119 @@
1
+ import { type CompiledGraph } from '../graph/graph.js';
2
+ import type { GraphCheckpointer, NodeContext } from '../types/graph.js';
3
+ import type { CompletionRequest, Message, ToolDefinition } from '../types/messages.js';
4
+ import type { NexusResponse } from '../types/response.js';
5
+ import type { Store } from '../types/store.js';
6
+ /**
7
+ * An agent as a graph.
8
+ *
9
+ * `AgentLoop` is a `while` loop: it cannot be paused, resumed after a restart, inspected halfway, or
10
+ * made to run two tool calls at once, because a loop has nowhere to keep that state. The same agent
11
+ * expressed as a graph inherits all of it — checkpoints, human approval through `interrupt()`,
12
+ * parallel tool tasks through `Send`, forks, events, and a Mermaid diagram — without this module
13
+ * implementing any of them. `AgentLoop` stays for the simple case that wants none of that.
14
+ */
15
+ /** The model call an agent makes. Injected, so this module needs no provider runtime. */
16
+ export interface AgentModelClient {
17
+ complete(request: CompletionRequest): Promise<NexusResponse>;
18
+ }
19
+ export interface AgentToolCall {
20
+ id: string;
21
+ name: string;
22
+ args: Record<string, unknown>;
23
+ }
24
+ /** What an approver may answer: allow, refuse with a reason, or allow with corrected arguments. */
25
+ export type AgentApproval = boolean | {
26
+ approved: true;
27
+ args?: Record<string, unknown>;
28
+ } | {
29
+ approved: false;
30
+ reason?: string;
31
+ };
32
+ export interface AgentApprovalPolicy {
33
+ /** The question an operator sees. Defaults to naming the tool and its arguments. */
34
+ reason?: (call: AgentToolCall) => string;
35
+ }
36
+ export interface AgentMiddleware {
37
+ name?: string;
38
+ /** Adjusts the request before it is sent: trim history, add context, swap the model. */
39
+ beforeModel?(context: {
40
+ request: CompletionRequest;
41
+ state: AgentState;
42
+ store?: Store;
43
+ }): CompletionRequest | void | Promise<CompletionRequest | void>;
44
+ /** Inspects or replaces the response: redact, validate, count. */
45
+ afterModel?(context: {
46
+ response: NexusResponse;
47
+ state: AgentState;
48
+ store?: Store;
49
+ }): NexusResponse | void | Promise<NexusResponse | void>;
50
+ /** Wraps a tool call, so a policy can log it, time it, or refuse it. */
51
+ wrapToolCall?(call: AgentToolCall, next: () => Promise<AgentToolResult>): Promise<AgentToolResult> | AgentToolResult;
52
+ }
53
+ export interface AgentToolResult {
54
+ ok: boolean;
55
+ result?: unknown;
56
+ error?: string;
57
+ }
58
+ export interface CreateAgentOptions {
59
+ client: AgentModelClient;
60
+ model?: string;
61
+ tools?: ToolDefinition[];
62
+ systemPrompt?: string;
63
+ /** Model calls before the agent stops with `stopReason: 'max_iterations'`. Defaults to 8. */
64
+ maxIterations?: number;
65
+ /** Tool calls run at once. Defaults to 8; `1` runs them one at a time. */
66
+ toolConcurrency?: number;
67
+ /** Tools that need human approval before they run, keyed by tool name. */
68
+ interruptOn?: Record<string, AgentApprovalPolicy | true>;
69
+ middleware?: AgentMiddleware[];
70
+ /** Long-term memory, available to tools and middleware as `store`. */
71
+ store?: Store;
72
+ checkpointer?: GraphCheckpointer | false;
73
+ name?: string;
74
+ temperature?: number;
75
+ maxTokens?: number;
76
+ responseFormat?: CompletionRequest['responseFormat'];
77
+ metadata?: Record<string, unknown>;
78
+ }
79
+ declare const agentChannels: () => {
80
+ messages: import("../types/graph.js").Channel<Message[]>;
81
+ iterations: import("../types/graph.js").Channel<number>;
82
+ answer: import("../types/graph.js").Channel<string>;
83
+ stopReason: import("../types/graph.js").Channel<AgentStopReason | undefined>;
84
+ };
85
+ export type AgentChannels = ReturnType<typeof agentChannels>;
86
+ export type AgentState = {
87
+ messages: Message[];
88
+ iterations: number;
89
+ answer: string;
90
+ stopReason?: AgentStopReason;
91
+ };
92
+ export type AgentStopReason = 'completed' | 'max_iterations';
93
+ export type AgentGraph = CompiledGraph<AgentChannels>;
94
+ /** Wraps a question into the state an agent starts from. */
95
+ export declare function agentInput(goal: string): {
96
+ messages: Message[];
97
+ };
98
+ /**
99
+ * Builds an agent and returns it as a compiled graph.
100
+ *
101
+ * Run it like any graph: `agent.invoke(agentInput('...'), { threadId })`. The result's `answer` is
102
+ * the final text, and `stopReason` says whether the agent finished or ran out of iterations.
103
+ */
104
+ export declare function createAgent(options: CreateAgentOptions): AgentGraph;
105
+ /**
106
+ * Turns an agent into a tool another agent can call.
107
+ *
108
+ * The simplest multi-agent shape: a supervisor keeps control and delegates, rather than handing over
109
+ * the conversation. The specialist runs as its own graph, with its own memory and approvals, and
110
+ * returns its answer as the tool result.
111
+ */
112
+ export declare function agentAsTool(options: {
113
+ agent: AgentGraph;
114
+ name: string;
115
+ description: string;
116
+ /** Threads the specialist's runs, so its work is resumable too. Defaults to a fresh thread. */
117
+ threadId?: (goal: string) => string;
118
+ }): ToolDefinition;
119
+ export type { NodeContext };
@@ -0,0 +1,176 @@
1
+ import { appendList, counter, lastValue } from '../graph/channels.js';
2
+ import { createGraph } from '../graph/graph.js';
3
+ import { Command, END, Send } from '../types/graph.js';
4
+ import { ToolExecutor } from './tool.js';
5
+ const agentChannels = () => ({
6
+ messages: appendList(),
7
+ iterations: counter(),
8
+ answer: lastValue(''),
9
+ stopReason: lastValue(undefined),
10
+ });
11
+ /** Wraps a question into the state an agent starts from. */
12
+ export function agentInput(goal) {
13
+ return { messages: [{ role: 'user', content: goal }] };
14
+ }
15
+ /**
16
+ * Builds an agent and returns it as a compiled graph.
17
+ *
18
+ * Run it like any graph: `agent.invoke(agentInput('...'), { threadId })`. The result's `answer` is
19
+ * the final text, and `stopReason` says whether the agent finished or ran out of iterations.
20
+ */
21
+ export function createAgent(options) {
22
+ if (typeof options.client?.complete !== 'function') {
23
+ throw new TypeError('createAgent needs a client with a complete() method');
24
+ }
25
+ const maxIterations = Math.max(1, options.maxIterations ?? 8);
26
+ const executor = new ToolExecutor(options.tools ?? []);
27
+ const middleware = options.middleware ?? [];
28
+ const approvals = options.interruptOn ?? {};
29
+ const graph = createGraph({ channels: agentChannels() })
30
+ .addNode('model', async (context) => {
31
+ const state = context.state;
32
+ let request = {
33
+ model: options.model ?? 'auto',
34
+ messages: [
35
+ ...(options.systemPrompt ? [{ role: 'system', content: options.systemPrompt }] : []),
36
+ ...state.messages,
37
+ ],
38
+ ...(options.tools?.length ? { tools: executor.list() } : {}),
39
+ ...(options.temperature === undefined ? {} : { temperature: options.temperature }),
40
+ ...(options.maxTokens === undefined ? {} : { maxTokens: options.maxTokens }),
41
+ ...(options.responseFormat ? { responseFormat: options.responseFormat } : {}),
42
+ ...(options.metadata ? { metadata: options.metadata } : {}),
43
+ };
44
+ for (const item of middleware) {
45
+ request = (await item.beforeModel?.({ request, state, store: options.store })) ?? request;
46
+ }
47
+ let response = await options.client.complete(request);
48
+ for (const item of middleware) {
49
+ response = (await item.afterModel?.({ response, state, store: options.store })) ?? response;
50
+ }
51
+ const assistant = {
52
+ role: 'assistant',
53
+ content: response.content,
54
+ ...(response.toolCalls?.length ? { toolCalls: response.toolCalls } : {}),
55
+ };
56
+ const calls = response.toolCalls ?? [];
57
+ if (calls.length === 0) {
58
+ return new Command({
59
+ update: { messages: [assistant], iterations: 1, answer: response.content, stopReason: 'completed' },
60
+ goto: END,
61
+ });
62
+ }
63
+ if (state.iterations + 1 >= maxIterations) {
64
+ // The model still wants tools but has used its budget; say so rather than pretending the
65
+ // last message is an answer.
66
+ return new Command({
67
+ update: { messages: [assistant], iterations: 1, answer: response.content, stopReason: 'max_iterations' },
68
+ goto: END,
69
+ });
70
+ }
71
+ return new Command({
72
+ update: { messages: [assistant], iterations: 1 },
73
+ goto: calls.map((call) => new Send('tools', call)),
74
+ });
75
+ }, { ends: ['tools', END] })
76
+ .addNode('tools', async (context) => {
77
+ const call = context.input;
78
+ const parsed = parseArguments(call.function.arguments);
79
+ if (!parsed.ok) {
80
+ return { messages: [toolMessage(call, { ok: false, error: parsed.error })] };
81
+ }
82
+ let args = parsed.args;
83
+ const description = { id: call.id, name: call.function.name, args };
84
+ const policy = approvals[description.name];
85
+ if (policy) {
86
+ const decision = context.interrupt({
87
+ reason: (policy === true ? undefined : policy.reason?.(description)) ?? defaultReason(description),
88
+ payload: description,
89
+ });
90
+ const verdict = normalizeApproval(decision);
91
+ if (!verdict.approved) {
92
+ return {
93
+ messages: [toolMessage(call, { ok: false, error: verdict.reason ?? 'A human refused this tool call' })],
94
+ };
95
+ }
96
+ if (verdict.args)
97
+ args = verdict.args;
98
+ }
99
+ const run = async () => executor.execute(description.name, args);
100
+ const result = await middleware.reduceRight((next, item) => item.wrapToolCall
101
+ ? () => Promise.resolve(item.wrapToolCall?.({ ...description, args }, next))
102
+ : next, run)();
103
+ return { messages: [toolMessage(call, result)] };
104
+ }, { ends: ['model'] })
105
+ .setEntry('model')
106
+ .addEdge('tools', 'model');
107
+ return graph.compile({
108
+ // Each iteration is two supersteps, plus the entry and a margin for a paused approval.
109
+ maxSteps: maxIterations * 2 + 4,
110
+ maxConcurrency: options.toolConcurrency ?? 8,
111
+ ...(options.checkpointer === undefined ? {} : { checkpointer: options.checkpointer }),
112
+ ...(options.store ? { store: options.store } : {}),
113
+ ...(options.name ? { name: options.name } : {}),
114
+ });
115
+ }
116
+ /**
117
+ * Turns an agent into a tool another agent can call.
118
+ *
119
+ * The simplest multi-agent shape: a supervisor keeps control and delegates, rather than handing over
120
+ * the conversation. The specialist runs as its own graph, with its own memory and approvals, and
121
+ * returns its answer as the tool result.
122
+ */
123
+ export function agentAsTool(options) {
124
+ return {
125
+ name: options.name,
126
+ description: options.description,
127
+ parameters: {
128
+ type: 'object',
129
+ properties: { goal: { type: 'string', description: 'What this agent should do' } },
130
+ required: ['goal'],
131
+ },
132
+ execute: async (args) => {
133
+ const goal = String(args.goal ?? '');
134
+ const result = await options.agent.invoke(agentInput(goal), {
135
+ ...(options.threadId ? { threadId: options.threadId(goal) } : {}),
136
+ });
137
+ if (result.status === 'awaiting_input') {
138
+ return `The ${options.name} agent is waiting for a human: ${result.interrupt?.reason ?? 'approval needed'}`;
139
+ }
140
+ return result.state.answer;
141
+ },
142
+ };
143
+ }
144
+ /** Turns a tool result into the message the model reads next. */
145
+ function toolMessage(call, result) {
146
+ return {
147
+ role: 'tool',
148
+ toolCallId: call.id,
149
+ content: JSON.stringify(result.ok ? (result.result ?? null) : { error: result.error }),
150
+ };
151
+ }
152
+ function defaultReason(call) {
153
+ return `Run tool "${call.name}" with ${JSON.stringify(call.args)}?`;
154
+ }
155
+ function normalizeApproval(decision) {
156
+ if (typeof decision === 'boolean')
157
+ return { approved: decision };
158
+ return decision.approved
159
+ ? { approved: true, ...(decision.args ? { args: decision.args } : {}) }
160
+ : { approved: false, ...(decision.reason ? { reason: decision.reason } : {}) };
161
+ }
162
+ /** Arguments the model did not actually send must never reach a tool. */
163
+ function parseArguments(raw) {
164
+ if (!raw?.trim())
165
+ return { ok: true, args: {} };
166
+ try {
167
+ const parsed = JSON.parse(raw);
168
+ if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed))
169
+ return { ok: true, args: parsed };
170
+ return { ok: false, error: 'Tool arguments must be a JSON object; the tool was not run' };
171
+ }
172
+ catch (error) {
173
+ const reason = error instanceof Error ? error.message : String(error);
174
+ return { ok: false, error: `Tool arguments are not valid JSON (${reason}); the tool was not run` };
175
+ }
176
+ }
@@ -0,0 +1,5 @@
1
+ export { AgentLoop, type AgentModelClient as AgentLoopModelClient } from './loop.js';
2
+ export { tool, ToolExecutor } from './tool.js';
3
+ export { limitToolCalls, redactMessages, type RedactOptions, summarizeHistory, type SummarizeOptions, } from './middleware.js';
4
+ export { agentAsTool, agentInput, type AgentApproval, type AgentApprovalPolicy, type AgentChannels, type AgentGraph, type AgentMiddleware, type AgentModelClient, type AgentState, type AgentStopReason, type AgentToolCall, type AgentToolResult, createAgent, type CreateAgentOptions, } from './create-agent.js';
5
+ export type { AgentConfig, AgentResult, AgentStep, ToolExecutionResult } from '../types/agent.js';
@@ -0,0 +1,4 @@
1
+ export { AgentLoop } from './loop.js';
2
+ export { tool, ToolExecutor } from './tool.js';
3
+ export { limitToolCalls, redactMessages, summarizeHistory, } from './middleware.js';
4
+ export { agentAsTool, agentInput, createAgent, } from './create-agent.js';
@@ -0,0 +1,46 @@
1
+ import type { Message } from '../types/messages.js';
2
+ import type { AgentMiddleware } from './create-agent.js';
3
+ /**
4
+ * Middleware that ships with the agent.
5
+ *
6
+ * Each is small on purpose: the seam matters more than the set, and an application's real policy is
7
+ * usually its own. These cover the three every long-running agent needs — a transcript that does not
8
+ * grow forever, secrets that never reach a provider, and a cap on tool use.
9
+ */
10
+ export interface SummarizeOptions {
11
+ /** Messages kept verbatim at the end of the transcript. Defaults to 6. */
12
+ keepLast?: number;
13
+ /** Summarizing starts once the transcript passes this many messages. Defaults to 20. */
14
+ triggerAfter?: number;
15
+ /** Turns the older messages into one summary. Usually a cheap model. */
16
+ summarize: (messages: Message[]) => Promise<string> | string;
17
+ }
18
+ /**
19
+ * Replaces the older half of a long transcript with a summary.
20
+ *
21
+ * An agent that runs for hours otherwise re-sends its whole history on every call, which costs more
22
+ * each turn and eventually exceeds the context window.
23
+ */
24
+ export declare function summarizeHistory(options: SummarizeOptions): AgentMiddleware;
25
+ export interface RedactOptions {
26
+ /** Patterns replaced before a message reaches the provider. */
27
+ patterns: readonly RegExp[];
28
+ replacement?: string;
29
+ /** Also redact what the model sends back, for anything written to logs or state. */
30
+ redactOutput?: boolean;
31
+ }
32
+ /**
33
+ * Redacts matching text from requests, and optionally from responses.
34
+ *
35
+ * For an agent whose transcript may contain a key, an account number, or anything else that should
36
+ * not leave the process. The full security pipeline is in `nexus-ai-pro/security`; this is the small
37
+ * version that needs no configuration.
38
+ */
39
+ export declare function redactMessages(options: RedactOptions): AgentMiddleware;
40
+ /**
41
+ * Caps how often a tool may be called in one run.
42
+ *
43
+ * A model that loops on the same call otherwise burns the iteration budget, and with an expensive
44
+ * tool it burns money too.
45
+ */
46
+ export declare function limitToolCalls(limits: Record<string, number>): AgentMiddleware;