nexus-ai-pro 2.5.0 → 2.7.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 (131) hide show
  1. package/API_STABILITY.md +88 -0
  2. package/CHANGELOG.md +220 -1
  3. package/README.md +13 -4
  4. package/dist/agent/create-agent.d.ts +37 -2
  5. package/dist/agent/create-agent.js +77 -6
  6. package/dist/agent/glob.d.ts +24 -0
  7. package/dist/agent/glob.js +72 -0
  8. package/dist/agent/index.d.ts +3 -1
  9. package/dist/agent/index.js +1 -0
  10. package/dist/agent/interpreter.d.ts +125 -0
  11. package/dist/agent/interpreter.js +270 -0
  12. package/dist/agent/sandbox-container.d.ts +48 -0
  13. package/dist/agent/sandbox-container.js +291 -0
  14. package/dist/agent/sandbox.d.ts +50 -1
  15. package/dist/agent/sandbox.js +155 -2
  16. package/dist/agent/tool-search.d.ts +39 -0
  17. package/dist/agent/tool-search.js +113 -0
  18. package/dist/agent/tool.d.ts +21 -4
  19. package/dist/agent/tool.js +28 -4
  20. package/dist/context/window.js +4 -0
  21. package/dist/deep-agent/helpers.d.ts +57 -0
  22. package/dist/deep-agent/helpers.js +235 -0
  23. package/dist/deep-agent/index.d.ts +20 -1
  24. package/dist/deep-agent/index.js +69 -51
  25. package/dist/evaluate/generate.d.ts +171 -0
  26. package/dist/evaluate/generate.js +266 -0
  27. package/dist/evaluate/index.d.ts +1 -1
  28. package/dist/evaluate/index.js +1 -1
  29. package/dist/evaluate/online.d.ts +114 -0
  30. package/dist/evaluate/online.js +261 -0
  31. package/dist/evaluate/run.d.ts +28 -0
  32. package/dist/evaluate/run.js +56 -5
  33. package/dist/graph/event-stream.js +2 -0
  34. package/dist/graph/graph.d.ts +7 -1
  35. package/dist/graph/graph.js +16 -2
  36. package/dist/mcp/client.d.ts +9 -1
  37. package/dist/mcp/client.js +38 -8
  38. package/dist/mcp/server.js +42 -2
  39. package/dist/operations/runner.js +1 -1
  40. package/dist/protocols/a2a.d.ts +166 -1
  41. package/dist/protocols/a2a.js +499 -132
  42. package/dist/protocols/acp-editor.d.ts +43 -0
  43. package/dist/protocols/acp-editor.js +164 -0
  44. package/dist/protocols/acp.d.ts +76 -3
  45. package/dist/protocols/acp.js +180 -7
  46. package/dist/protocols/ag-ui.d.ts +23 -0
  47. package/dist/protocols/ag-ui.js +128 -8
  48. package/dist/providers/anthropic.d.ts +6 -0
  49. package/dist/providers/anthropic.js +36 -9
  50. package/dist/providers/content.d.ts +33 -1
  51. package/dist/providers/content.js +62 -0
  52. package/dist/providers/google.js +14 -5
  53. package/dist/providers/openai.d.ts +8 -4
  54. package/dist/providers/openai.js +75 -34
  55. package/dist/runtime/index.d.ts +1 -1
  56. package/dist/security/index.d.ts +1 -1
  57. package/dist/security/index.js +1 -1
  58. package/dist/security/injection-detector.d.ts +18 -0
  59. package/dist/security/injection-detector.js +39 -4
  60. package/dist/security/schema-validator.js +11 -2
  61. package/dist/tracing/instrument.js +14 -3
  62. package/dist/types/graph.d.ts +7 -0
  63. package/dist/types/messages.d.ts +69 -3
  64. package/dist/utils/tokenizer.js +4 -0
  65. package/dist/version.d.ts +1 -1
  66. package/dist/version.js +1 -1
  67. package/dist-cjs/agent/create-agent.d.ts +37 -2
  68. package/dist-cjs/agent/create-agent.js +76 -5
  69. package/dist-cjs/agent/glob.d.ts +24 -0
  70. package/dist-cjs/agent/glob.js +77 -0
  71. package/dist-cjs/agent/index.d.ts +3 -1
  72. package/dist-cjs/agent/index.js +5 -1
  73. package/dist-cjs/agent/interpreter.d.ts +125 -0
  74. package/dist-cjs/agent/interpreter.js +308 -0
  75. package/dist-cjs/agent/sandbox-container.d.ts +48 -0
  76. package/dist-cjs/agent/sandbox-container.js +327 -0
  77. package/dist-cjs/agent/sandbox.d.ts +50 -1
  78. package/dist-cjs/agent/sandbox.js +155 -2
  79. package/dist-cjs/agent/tool-search.d.ts +39 -0
  80. package/dist-cjs/agent/tool-search.js +118 -0
  81. package/dist-cjs/agent/tool.d.ts +21 -4
  82. package/dist-cjs/agent/tool.js +28 -4
  83. package/dist-cjs/context/window.js +4 -0
  84. package/dist-cjs/deep-agent/helpers.d.ts +57 -0
  85. package/dist-cjs/deep-agent/helpers.js +238 -0
  86. package/dist-cjs/deep-agent/index.d.ts +20 -1
  87. package/dist-cjs/deep-agent/index.js +68 -50
  88. package/dist-cjs/evaluate/generate.d.ts +171 -0
  89. package/dist-cjs/evaluate/generate.js +272 -0
  90. package/dist-cjs/evaluate/index.d.ts +1 -1
  91. package/dist-cjs/evaluate/index.js +2 -1
  92. package/dist-cjs/evaluate/online.d.ts +114 -0
  93. package/dist-cjs/evaluate/online.js +264 -0
  94. package/dist-cjs/evaluate/run.d.ts +28 -0
  95. package/dist-cjs/evaluate/run.js +58 -5
  96. package/dist-cjs/graph/event-stream.js +2 -0
  97. package/dist-cjs/graph/graph.d.ts +7 -1
  98. package/dist-cjs/graph/graph.js +17 -2
  99. package/dist-cjs/mcp/client.d.ts +9 -1
  100. package/dist-cjs/mcp/client.js +37 -7
  101. package/dist-cjs/mcp/server.js +42 -2
  102. package/dist-cjs/operations/runner.js +1 -1
  103. package/dist-cjs/protocols/a2a.d.ts +166 -1
  104. package/dist-cjs/protocols/a2a.js +501 -132
  105. package/dist-cjs/protocols/acp-editor.d.ts +43 -0
  106. package/dist-cjs/protocols/acp-editor.js +167 -0
  107. package/dist-cjs/protocols/acp.d.ts +76 -3
  108. package/dist-cjs/protocols/acp.js +183 -8
  109. package/dist-cjs/protocols/ag-ui.d.ts +23 -0
  110. package/dist-cjs/protocols/ag-ui.js +127 -7
  111. package/dist-cjs/providers/anthropic.d.ts +6 -0
  112. package/dist-cjs/providers/anthropic.js +35 -8
  113. package/dist-cjs/providers/content.d.ts +33 -1
  114. package/dist-cjs/providers/content.js +69 -0
  115. package/dist-cjs/providers/google.js +13 -4
  116. package/dist-cjs/providers/openai.d.ts +8 -4
  117. package/dist-cjs/providers/openai.js +74 -33
  118. package/dist-cjs/runtime/index.d.ts +1 -1
  119. package/dist-cjs/security/index.d.ts +1 -1
  120. package/dist-cjs/security/index.js +2 -1
  121. package/dist-cjs/security/injection-detector.d.ts +18 -0
  122. package/dist-cjs/security/injection-detector.js +40 -4
  123. package/dist-cjs/security/schema-validator.js +11 -2
  124. package/dist-cjs/tracing/instrument.js +14 -3
  125. package/dist-cjs/types/graph.d.ts +7 -0
  126. package/dist-cjs/types/messages.d.ts +69 -3
  127. package/dist-cjs/utils/tokenizer.js +4 -0
  128. package/dist-cjs/version.d.ts +1 -1
  129. package/dist-cjs/version.js +1 -1
  130. package/llms.txt +1 -1
  131. package/package.json +69 -2
package/API_STABILITY.md CHANGED
@@ -41,6 +41,94 @@ Both are covered by this policy:
41
41
  Deep imports into `dist`, `dist-cjs`, or `src` are not supported. Import only from `nexus-ai-pro` or
42
42
  one of its explicit subpaths.
43
43
 
44
+ ## 2.7 stage (2.7.0)
45
+
46
+ Everything 2.7 adds is stable from the start and additive.
47
+
48
+ New and stable:
49
+ - on `nexus-ai-pro/agent/sandbox`: `Sandbox.glob()`, `grep()`, and `readBytes()`, all optional;
50
+ `SandboxGlobOptions`, `SandboxGrepOptions`, and `SandboxMatch`; `SandboxToolsOptions.maxMediaBytes`;
51
+ and the tool names `glob_files`, `grep_files`, and `read_media`;
52
+ - `DocumentContent` and the `document` member of `ContentPart`, exported from
53
+ `nexus-ai-pro/runtime`; the `base64` source of `AudioContent`;
54
+ - `nexus-ai-pro/agent/sandbox/container`: `containerSandbox()` and `ContainerSandboxOptions`, with
55
+ their defaults: no network, every capability dropped, no new privileges, 512 MB of memory and swap,
56
+ one CPU, and 256 processes;
57
+ - `nexus-ai-pro/agent/interpreter`: `Interpreter`, `InterpreterFunction`, `InterpreterRunOptions`,
58
+ `InterpreterResult`, `InterpreterCall`, `workerInterpreter()` and `WorkerInterpreterOptions`,
59
+ `codeTool()` and `CodeToolOptions` with the tool name `run_code`, and `runInterpreterConformance()`
60
+ with `InterpreterConformanceReport`; `DeepAgentOptions.interpreter`;
61
+ - `DeepAgentOptions.createHelpers` and `maxBackgroundHelpers`, `DeepHelperLimits`, the `background`
62
+ and `instructions` arguments of `delegate`, the tool name `collect_helper`, the helper thread id
63
+ `<thread>/helper/<name>@<call id>`, and the helper namespace `<name>@<call id>`;
64
+ - `ToolContext.forward`;
65
+ - `ToolDefinition.deferred` and `tool({ deferred })`; `ToolReferenceContent` and the `tool_reference`
66
+ member of `ContentPart`, exported from `nexus-ai-pro/runtime`; on `nexus-ai-pro/agent`,
67
+ `toolSearchTool()`, `ToolSearchOptions`, `searchTools()`, `ToolSearchHit`, and `TOOL_SEARCH_NAME`
68
+ with the tool name `search_tools`.
69
+
70
+ - `nexus-ai-pro/evaluate/generate`: `syntheticDataset()` with `SyntheticDatasetOptions`,
71
+ `SyntheticDocument`, `SyntheticPassage`, `SyntheticDraft`, `SyntheticCitation`, `SyntheticModelClient`,
72
+ and `SyntheticDatasetResult`; `adversarialDataset()` with `AdversarialDatasetOptions`,
73
+ `AdversarialInputs`, `AdversarialExpected`, `AttackCarrier`, and `AttackDisguise`; `resistsAttack()`;
74
+ `promoteIssue()` with `PromoteIssueOptions`;
75
+ - on `nexus-ai-pro/security`: `attackSeeds()`, `AttackSeed`, and `AttackCategory`;
76
+ - `nexus-ai-pro/evaluate/online`: `onlineEvaluationWorker()`, `OnlineEvaluationWorker`,
77
+ `OnlineEvaluationWorkerOptions`, `OnlineEvaluationWorkerReport`, and `OnlineSchedulePass`; the
78
+ operation kind `online-evaluation:<source>` and the score mark `value.evaluation`;
79
+ - `EvaluateOptions.budget` and `EvaluationBudgetError` on `nexus-ai-pro/evaluate`.
80
+
81
+ An agent with no deferred tool offers exactly the tools it did before. Adversarial example ids and
82
+ canaries are stable across runs, so experiments over the same options compare.
83
+
84
+ A graph run's first checkpoint carries the run's `metadata`, as every later checkpoint already did.
85
+
86
+ `sandboxTools()` offers the new tools only for a sandbox that implements the methods behind them, so
87
+ a sandbox of the application's own offers the same tools as before. A deep agent over
88
+ `processSandbox()` gets the three new tools and a line of its system prompt naming them.
89
+
90
+ On OpenAI, a tool message whose media is not only images is followed by a user message that starts
91
+ `Files the tools returned:`; one with only images keeps `Images the tools returned:`.
92
+
93
+ `ContentPart` and `AudioContent['source']` gain members, as the compatibility rules allow in a minor
94
+ release. Code that switches over either exhaustively should handle the new ones.
95
+
96
+ ## 2.6 stage (2.6.0)
97
+
98
+ Everything 2.6 adds is stable from the start and additive.
99
+
100
+ New and stable:
101
+ - `ToolDefinition.stream`, `ToolContext.progress`, and `ToolBody`; `tool()` with `stream`. The
102
+ AG-UI event name `tool_progress`, and the `progress` and `callId` fields of a tool run's metadata;
103
+ - `McpClient.callTool()`'s `progress` option, and `notifications/progress` from `McpServer`;
104
+ - `AgUiOptions.stateDeltas`, `messagesSnapshot`, and `frontendTools`, and `AgUiPatchOperation`;
105
+ - `CreateAgentOptions.toolSource` and `ToolSourceContext`; `ClientToolDefinition`, the `clientTools`
106
+ channel, and the `AgentStopReason` member `client_tools`;
107
+ - the `step` member of `GraphEvent`;
108
+ - on `nexus-ai-pro/protocols/a2a`: `A2aTaskStore`, `A2aTaskRecord`, `A2aTaskQuery`,
109
+ `MemoryA2aTaskStore`, `storeA2aTasks()`; `A2aServerOptions.store`, `pushNotifications`,
110
+ `extendedCard`, `pollMs`, and `onError`; `A2aPushOptions` and `A2aPushNotificationConfig`; the
111
+ methods `ListTasks`, `SubscribeToTask`, the four push notification config methods, and
112
+ `GetExtendedAgentCard`; the client's new methods, `A2aListTasksOptions`, and `A2aTaskPage`; and
113
+ the error codes `pushNotificationNotSupported` and `extendedCardNotConfigured`.
114
+
115
+ - on `nexus-ai-pro/protocols/acp`: `AcpSessionStore`, `AcpSessionRecord`, `MemoryAcpSessionStore`,
116
+ `storeAcpSessions()`, `AcpOptions.sessions` and `editor`, `AcpEditorBridge`, `AcpConnection`,
117
+ `AcpClientCapabilities`, `session/load`, image and audio blocks, and the fourth argument of
118
+ `AcpOptions.input`;
119
+ - `nexus-ai-pro/protocols/acp/editor`: `acpEditor()`, `AcpEditor`, `AcpEditorOptions`, and the tool
120
+ names `editor_read_file`, `editor_write_file`, and `editor_run_command`.
121
+
122
+ An AG-UI run reports a node's tasks running side by side as one step, and finishes every step it
123
+ started before `RUN_FINISHED` or `RUN_ERROR`. A2A `ListTasks` refuses a status that is not a task
124
+ state, and reads `TASK_STATE_UNSPECIFIED` and `UNRECOGNIZED` as no filter.
125
+
126
+ A streamed A2A task now reports its move to `TASK_STATE_WORKING` as a status update, before its
127
+ other events.
128
+
129
+ `GraphEvent` and `AgentStopReason` gain members, as the compatibility rules allow in a minor
130
+ release. Code that switches over either exhaustively should handle the new ones.
131
+
44
132
  ## 2.5 stage (2.5.0)
45
133
 
46
134
  Everything 2.5 adds is stable from the start and additive.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,223 @@ Notable changes to this project are recorded here. The format follows [Keep a Ch
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [2.7.0] - 2026-10-12
8
+
9
+ Agents that do more, and evaluation that runs on its own.
10
+
11
+ ### Added
12
+
13
+ - **Workspace search.** A `Sandbox` may implement `glob()`, `grep()`, and `readBytes()`, with
14
+ `SandboxGlobOptions`, `SandboxGrepOptions`, and `SandboxMatch`. `sandboxTools()` then offers
15
+ `glob_files` and `grep_files`, bounded at 500 paths and 200 lines, each declaring the directory it
16
+ reads. `processSandbox()` implements all three: its search follows no links, skips `.git` and
17
+ `node_modules`, walks at most 20,000 entries, and skips files over 1 MB and binary files.
18
+ - **A multimodal workspace.** `read_media` reads an image, audio, or a PDF in the sandbox as content
19
+ the model sees, as base64 so it survives a checkpoint, up to `SandboxToolsOptions.maxMediaBytes`
20
+ (10 MB by default). A deep agent gets all three tools when its sandbox supports them.
21
+ - **Documents and audio reach the model.** The new `DocumentContent` part carries a document, such
22
+ as a PDF, and `AudioContent` takes base64 text. Anthropic receives a document as a document block,
23
+ inside a tool result too; OpenAI chat as a file, OpenAI Responses as an input file, and Google as
24
+ inline data. Audio reaches Google, and OpenAI chat's audio models as `input_audio`. A tool's media
25
+ follows its results on OpenAI and Google, as its images already did. Any other provider, or a
26
+ source it cannot read, receives one line naming the part.
27
+ - **A container sandbox**, on the new `nexus-ai-pro/agent/sandbox/container`: `containerSandbox()`
28
+ drives the `docker` or `podman` binary, with no dependency, one container per sandbox. By default
29
+ the container has no network, drops every capability, cannot gain privileges, and is held to
30
+ 512 MB, one CPU, and 256 processes; nothing of the host is mounted. A command past its timeout is
31
+ killed inside the container with everything it started. Files, glob, grep, and media work inside
32
+ it. `ContainerSandboxOptions` sets the image, binary, working directory, network, and limits.
33
+ - **An interpreter contract**, on the new `nexus-ai-pro/agent/interpreter`. An `Interpreter` runs a
34
+ snippet that calls the agent's tools as `await tools.<name>(args)`, so many calls take one step.
35
+ `codeTool()` offers it to a model as `run_code`: it declares `code`, and the policy decides each
36
+ call the code makes as it decides the model's own, refusing one it would ask a person about.
37
+ `workerInterpreter()` is the JavaScript reference: a fresh worker per run, a 64 MB heap, a
38
+ 10-second timeout, and caps on calls and logs. It is not a security boundary.
39
+ `runInterpreterConformance()` checks any interpreter. `DeepAgentOptions.interpreter` adds
40
+ `run_code` over the deep agent's own tools, and its default policy then grants code.
41
+ - **Helpers beside the deep agent.**
42
+ - `delegate` takes `background`: it returns a task id at once, and the new `collect_helper` tool
43
+ returns the answer later, waiting up to `wait_seconds`. `maxBackgroundHelpers` (4) bounds how
44
+ many work at once.
45
+ - A background helper keeps checkpoints in the deep agent's checkpointer, on a thread named after
46
+ the call that started it. After a crash, collecting the task, or repeating the call, carries the
47
+ same helper on from its last checkpoint.
48
+ - `createHelpers` lets the agent create a helper from instructions it writes, within
49
+ `DeepHelperLimits`: the tools, model, model calls, and number per thread.
50
+ - Streamed with `subgraphs: true`, a helper's events reach the agent's stream under its namespace,
51
+ such as `tools/researcher@call-7`. `ToolContext.forward` lets any tool do the same for a run it
52
+ starts.
53
+ - **Tool search.** `ToolDefinition.deferred` (and `tool({ deferred })`) loads a tool only once the
54
+ model finds it. An agent with a deferred tool offers `search_tools`, which ranks the deferred tools
55
+ by BM25 over their names, descriptions, and arguments, and answers with `ToolReferenceContent`
56
+ parts. On Claude 4.5 and later every tool is sent, a deferred one with `defer_loading`, and a
57
+ found tool loads through a `tool_reference` block, so the tool list never changes; every other
58
+ model, OpenAI's and Google's included, is sent a deferred tool once it has been found.
59
+ `toolSearchTool()`, `searchTools()`, and `TOOL_SEARCH_NAME` are on `nexus-ai-pro/agent`.
60
+ - **Datasets that grow**, on the new `nexus-ai-pro/evaluate/generate`:
61
+ - `syntheticDataset()` writes examples from documents, from your function or a model. Each keeps
62
+ a citation of the quote that supports it, checked against its passage; unsupported drafts and
63
+ near-duplicate questions are dropped and counted.
64
+ - `adversarialDataset()` grows attacks from the guardrails' own patterns, in the user's message, a
65
+ document, or a tool's result, plain or disguised, each after a canary in the system prompt.
66
+ `resistsAttack()` scores whether the canary stayed in.
67
+ - `promoteIssue()` turns an insights issue's runs into examples that keep their run and the issue.
68
+ - `attackSeeds()` on `nexus-ai-pro/security` returns each injection pattern's example attack.
69
+ - **Online evaluation on workers**, on the new `nexus-ai-pro/evaluate/online`.
70
+ `onlineEvaluationWorker()` samples runs by a stable hash, queues one operation per run and source
71
+ on a shared operation store, stops queuing at `maxQueued`, and scores what it claims within a
72
+ budget ledger. Each score carries a mark naming its operation, so a retry after a crash writes
73
+ only what is missing, and each run is scored exactly once per source.
74
+ - **A spend cap for experiments.** `evaluate()` takes `budget`. Each example run is reserved and
75
+ reconciled, and once the budget runs out no further example starts; `evaluate()` rejects with the
76
+ new `EvaluationBudgetError`, which carries the results it paid for.
77
+
78
+ ### Fixed
79
+
80
+ - An operation whose store failed under it, such as a database going away while it recorded its
81
+ result, left its handle pending forever, with an unhandled rejection; the handle now fails with
82
+ the store's error, and the record's lease lapses for recovery.
83
+
84
+ ### Changed
85
+
86
+ - A graph run's first checkpoint now carries the run's `metadata`, as every later checkpoint did.
87
+ - A deep agent with helpers offers `collect_helper` beside `delegate`, and its prompt says how to use
88
+ both; its default policy allows `collect_helper`.
89
+
90
+ ### Proof
91
+
92
+ - Glob and grep on the reference sandbox: alternatives, `**/` matching no directory, results relative
93
+ to the root, a limit, a path outside the root refused, a link out of the root not followed, and
94
+ binary, oversized, vendored, and past-the-line-limit matches skipped.
95
+ - An image, a PDF, and audio from a tool, after a JSON round trip, on the wire of Anthropic, OpenAI
96
+ chat (with and without an audio model), OpenAI Responses, and Google.
97
+ - A deep agent that greps its workspace, finds a PDF, and reads it as a document.
98
+ - The container sandbox passes the sandbox conformance suite against a real Docker in CI, which now
99
+ requires one. A command cannot find a host file anywhere in the container, cannot reach the host or
100
+ the internet, while the same probe from a container with a network can; a command over a 64 MB
101
+ limit is stopped; and a timeout leaves nothing it started running.
102
+ - The worker interpreter passes the interpreter conformance suite. An agent totals three orders and
103
+ describes one from code in one model round trip, while the refund in the same code, which the
104
+ policy asks about, never runs; a deep agent searches and reads its workspace from code, and a
105
+ command its policy denies is denied from code too.
106
+ - A background helper answers after the agent has moved on, with its events in the agent's stream. A
107
+ crash while the agent waits for it, and a crash before the agent saved the call that started it,
108
+ each end with one lookup, one note, and one start of the helper.
109
+ - An agent with 500 deferred tools finishes all 40 tasks on Anthropic, OpenAI, and Google, with a
110
+ model that calls a tool only once that provider's request has loaded it. Anthropic is sent all 501
111
+ tools on every call with 500 deferred; OpenAI and Google never more than 11. The search ranks each
112
+ of the 500 tools first for the step that describes it.
113
+ - A synthetic dataset written from all 39 guides cites, for every example, words that stand in its
114
+ guide where the citation says; an invented quote and a repeated question are dropped every time.
115
+ An adversarial dataset of 75 examples gets its canary out of an unguarded agent every time and
116
+ never out of a guarded one, which still answers the benign examples. Each attack the guardrails
117
+ block at the input is critical or high, and each medium one is redacted on the way out.
118
+ - Ten online-evaluation workers score 10,000 runs, each score exactly once, while one dies right
119
+ after writing a run's scores and before recording them; that run is taken back and found already
120
+ scored, and the queue stays near its limit. Workers on a budget stop at it, leave the rest queued,
121
+ and carry on the next period without scoring any run twice. Two sources sharing one store never
122
+ cross, and sampling picks the same runs on every worker. An experiment stops at its budget.
123
+
124
+ ## [2.6.0] - 2026-10-11
125
+
126
+ Protocols complete, and progress on every stream.
127
+
128
+ ### Added
129
+
130
+ - **Tool progress, end to end.**
131
+ - A `ToolDefinition` can have `stream`: an async generator that yields progress and returns the
132
+ result. `tool()` takes one instead of `execute`, and gives it an `execute` that runs the stream,
133
+ so it works wherever a tool is executed. `ToolExecutor` runs a stream itself.
134
+ - `ToolContext.progress` reports progress from any tool. An agent sets it, and turns each report
135
+ into a `progress` tool event while the tool runs.
136
+ - Progress reaches the event stream and the server's run events; AG-UI, as a `CUSTOM`
137
+ `tool_progress` event; A2A, as a working status update with the progress as data; ACP, as an
138
+ update of the tool call in progress; traces, in the tool run's `metadata.progress`; and the
139
+ studio's run view.
140
+ - MCP: `McpServer` runs `stream` tools and sends `notifications/progress` for a call that names a
141
+ `progressToken`. `McpClient.callTool()` takes a `progress` listener, and the tools from
142
+ `toNexusTools()` pass a remote tool's progress to the agent.
143
+
144
+ - **AG-UI, complete**, each part opt-in on `AgUiOptions`:
145
+ - `stateDeltas` reports state as it changes: a `STATE_SNAPSHOT` after the first step, then a
146
+ `STATE_DELTA` with an RFC 6902 JSON Patch after every step that changed it, appending to arrays
147
+ item by item. `AgUiPatchOperation` is its type.
148
+ - `messagesSnapshot` sends the whole transcript as `MESSAGES_SNAPSHOT` before the run finishes.
149
+ - `frontendTools` offers the tools the frontend sends to the model. The frontend runs a call to one,
150
+ and the next run's messages carry its result.
151
+ - **Tools the client runs.** An agent's input may carry `clientTools`, each a `ClientToolDefinition`.
152
+ The model is offered them; a call to one ends the turn with the new `stopReason: 'client_tools'`
153
+ once the agent's own calls of that step have run, and the client's result continues it. A client
154
+ tool never replaces a tool of the agent's own.
155
+ - **Tools that depend on the run.** `CreateAgentOptions.toolSource` supplies tools per run, from a
156
+ `ToolSourceContext`, offered and run like `tools`, under the same permission policy.
157
+ - **A2A, durable and complete.**
158
+ - Tasks and conversations live in an `A2aTaskStore`: `MemoryA2aTaskStore` by default, or
159
+ `storeA2aTasks()` over any long-term `Store`, so a task paused for an answer survives a restart
160
+ and another replica finishes it.
161
+ - `ListTasks` by context and state, a page at a time; `SubscribeToTask` on any replica; and a
162
+ cancel that stands against the replica running the task.
163
+ - Push notifications, with `pushNotifications`: each state change POSTed to a task's webhook,
164
+ with the caller's token and signed as the server's operation webhooks are. A webhook must be
165
+ `https` and not a private address, unless `allowPrivateNetworks` is set.
166
+ - `GetExtendedAgentCard`, with `extendedCard`, for authenticated callers.
167
+ - The client gains `extendedCard()`, `listTasks()`, `subscribe()`, `setPushNotification()`,
168
+ `listPushNotifications()`, `deletePushNotification()`, and `push` and `returnImmediately` when
169
+ sending.
170
+ - **ACP, durable and complete.**
171
+ - Sessions live in an `AcpSessionStore`: `MemoryAcpSessionStore` by default, or
172
+ `storeAcpSessions()` over any long-term `Store`. The agent offers `session/load`, replays the
173
+ conversation, and asks again a question it was waiting on.
174
+ - A prompt can carry images and audio, which reach the model as content parts.
175
+ - `acpEditor()`, on the new `nexus-ai-pro/protocols/acp/editor`, gives an agent the editor's files,
176
+ terminal, and MCP servers, as tools from its `toolSource`, each declaring what it touches.
177
+ `serveAcp()` takes it as `editor`, and detaches it when the editor closes its end.
178
+ - **Interoperability, proven.**
179
+ - A browser in CI: `npm run test:browser` serves the build to headless Chromium, which imports
180
+ every kernel entry point and runs a graph, an agent, and AG-UI. It runs in `npm test`.
181
+ - The previous minor on the other side of the wire: a 2.5 client against a 2.6 server, and the
182
+ other way round, on the server's HTTP API and A2A, and one harness across both releases for
183
+ AG-UI and ACP.
184
+ - A nightly `Interoperability` workflow drives each protocol with its reference client:
185
+ `@ag-ui/client`, `@a2a-js/sdk`, and `@agentclientprotocol/sdk`.
186
+ - **A `step` graph event.** `GraphRunOptions.onEvent` receives `step` after each superstep, with the
187
+ state after it.
188
+
189
+ ### Fixed
190
+
191
+ - AG-UI: a run that paused inside a node left that node's step open, so a strict client refused its
192
+ `RUN_FINISHED`; and a node's tasks running side by side each started the same step. A node's tasks
193
+ are now one step, and every step still open is finished before the run finishes or fails. Found by
194
+ the reference client.
195
+ - A2A: `ListTasks` without a status filter, as the reference SDK sends it (`UNRECOGNIZED`), matched
196
+ no task. An unset filter now filters nothing, and an unknown state is refused as invalid.
197
+ - MCP: the stdio transport's `close()` killed the server without waiting for it, so what the process
198
+ held, such as its working directory, could outlive the close. It now waits up to two seconds for
199
+ the process to exit.
200
+
201
+ ### Proof
202
+
203
+ - Headless Chromium imports every kernel entry point and runs a graph, an agent with a streaming
204
+ tool, and AG-UI. A 2.5 client and a 2.6 server pass the protocol suites in both directions, and the
205
+ reference clients of AG-UI, A2A, and ACP each drive this release's servers through a whole run.
206
+ - An ACP session paused on a question survives the agent's restart, on memory and SQLite; the
207
+ editor reopens it, sees the conversation replayed, and answers. An image and audio reach the model.
208
+ Through the bridge, an agent reads, writes (after the person allows it), and runs the tests in the
209
+ editor, and searches docs through an MCP server the editor offered, started as a real process.
210
+ - An A2A task paused for an approval survives a restart and is finished by another replica, sending
211
+ its email once, on the memory, SQLite, Postgres, and Redis stores. A running task is followed where
212
+ it runs and from another replica; a cancel on one replica stands against the other. Every state
213
+ change reaches the webhook, signed and with the caller's token, and nine webhook URLs that point
214
+ into the agent's own network or are not https are refused.
215
+ - An AG-UI client applying a run's snapshot and its deltas rebuilds the final state exactly, with
216
+ each new message one `add`. A frontend tool is offered to the model beside the agent's own; the
217
+ agent runs its own call, waits without calling the model again, and continues from the page's
218
+ result in the next run. A page cannot shadow the agent's tool, and without the options the events
219
+ are what they were.
220
+ - A tool that will not go on until a reader has seen its first report finishes on every stream: the
221
+ event stream, the server's events, AG-UI, A2A, ACP, and MCP. Its reports arrive in order, after the
222
+ call and before the result, and its traced run keeps them.
223
+
7
224
  ## [2.5.0] - 2026-10-09
8
225
 
9
226
  Graduation finished, and conversations evaluated.
@@ -2104,7 +2321,9 @@ shared policy vocabulary is in place for a future release that revisits this.
2104
2321
  - URL fetching rejects unsafe private-network targets and limits response reads.
2105
2322
  - CLI and audit findings no longer disclose detected secret values.
2106
2323
 
2107
- [Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.5.0...HEAD
2324
+ [Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.7.0...HEAD
2325
+ [2.7.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.6.0...v2.7.0
2326
+ [2.6.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.5.0...v2.6.0
2108
2327
  [2.5.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.4.0...v2.5.0
2109
2328
  [2.4.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.3.0...v2.4.0
2110
2329
  [2.3.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.2.0...v2.3.0
package/README.md CHANGED
@@ -270,13 +270,22 @@ Shipped so far:
270
270
  - 2.5 graduation finished, and conversations evaluated: deployments that change atomically across
271
271
  replicas, revision worker pools, fair canaries judged on every run, and the studio out of
272
272
  experimental after a threat review; Postgres tenant usage; signed context named on every trace;
273
- simulated users and thread evaluators; and a doctor that knows the opt-in settings.
273
+ simulated users and thread evaluators; and a doctor that knows the opt-in settings;
274
+ - 2.6 protocols complete, and progress on every stream: a tool's progress reaching the event stream,
275
+ the protocols, traces, and the studio; AG-UI state deltas and frontend tools; A2A tasks and ACP
276
+ sessions that survive a restart, with push notifications, `session/load`, and the editor's files,
277
+ terminal, and MCP servers; and every protocol proven in a browser, against the previous release,
278
+ and against its reference client;
279
+ - 2.7 agents that do more, and evaluation that runs on its own: workspace search and a multimodal
280
+ workspace, a container sandbox, an interpreter, helpers that run in the background and survive a
281
+ crash, and tool search on every provider; datasets generated from documents and from the
282
+ guardrails' attacks; and online evaluation on workers, within a budget.
274
283
 
275
284
  Upgrading from 1.x: `npx nexus migrate src --write` moves your imports, and
276
285
  [MIGRATING.md](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/MIGRATING.md) covers the rest.
277
- Next, 2.6 completes the agent protocols: tool progress on every stream, AG-UI deltas and frontend
278
- tools, A2A tasks and ACP sessions that survive a restart, and a browser in CI. The [roadmap](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/ROADMAP.md) lists the
279
- releases after it.
286
+ Next, the remote vector stores and images graduate once the live workflow passes against each. The
287
+ [roadmap](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/ROADMAP.md) lists what comes after, in the order
288
+ applications ask for it.
280
289
 
281
290
  ## Before Production
282
291
 
@@ -163,12 +163,44 @@ export interface CreateAgentOptions {
163
163
  * event once the model returns. Middleware sees the assembled response either way.
164
164
  */
165
165
  streamTokens?: boolean;
166
+ /**
167
+ * Tools that depend on the run, added to `tools` on every model call and run like them: an editor's
168
+ * file system for one session, or the MCP servers one user connected. A tool in `tools` wins over a
169
+ * source's tool of the same name.
170
+ */
171
+ toolSource?: (context: ToolSourceContext) => ToolDefinition[] | Promise<ToolDefinition[]>;
172
+ }
173
+ /** What a tool source is told about the run it supplies tools for. */
174
+ export interface ToolSourceContext {
175
+ /** The run's thread. */
176
+ threadId: string;
177
+ /** The tenant the run is for. */
178
+ tenantId?: string;
179
+ /** Who the run is for. */
180
+ principal?: Readonly<Principal>;
181
+ /** Aborted when the run is cancelled. */
182
+ signal: AbortSignal;
183
+ }
184
+ /**
185
+ * A tool the client runs, such as one an AG-UI frontend offers: a name, a description, and a schema,
186
+ * with no implementation on the agent's side. Given in the input as `clientTools`, it is offered to
187
+ * the model like any tool. When the model calls one, the agent ends its turn with `stopReason:
188
+ * 'client_tools'`, and the client sends the result back in the next run's messages.
189
+ */
190
+ export interface ClientToolDefinition {
191
+ /** The name the model calls it by. */
192
+ name: string;
193
+ /** What it does, written for the model. */
194
+ description: string;
195
+ /** JSON Schema for the arguments. */
196
+ parameters: Record<string, unknown>;
166
197
  }
167
198
  declare const agentChannels: () => {
168
199
  messages: import("../types/graph.js").Channel<Message[]>;
169
200
  iterations: import("../types/graph.js").Channel<number>;
170
201
  answer: import("../types/graph.js").Channel<string>;
171
202
  stopReason: import("../types/graph.js").Channel<AgentStopReason | undefined>;
203
+ clientTools: import("../types/graph.js").Channel<ClientToolDefinition[] | undefined>;
172
204
  };
173
205
  /** The agent's state channels, as a graph schema. */
174
206
  export type AgentChannels = ReturnType<typeof agentChannels>;
@@ -182,12 +214,15 @@ export type AgentState = {
182
214
  answer: string;
183
215
  /** Why the agent stopped. */
184
216
  stopReason?: AgentStopReason;
217
+ /** Tools the client runs, given in the input; see `ClientToolDefinition`. */
218
+ clientTools?: ClientToolDefinition[];
185
219
  };
186
220
  /**
187
221
  * `completed` when the model answered, `max_iterations` when it ran out of model calls, `stopped`
188
- * when a middleware ended the run through `context.stop()`.
222
+ * when a middleware ended the run through `context.stop()`, and `client_tools` when the model called
223
+ * a tool the client runs, which waits for the client's result.
189
224
  */
190
- export type AgentStopReason = 'completed' | 'max_iterations' | 'stopped';
225
+ export type AgentStopReason = 'completed' | 'max_iterations' | 'stopped' | 'client_tools';
191
226
  /** An agent: a compiled graph over the agent's channels. */
192
227
  export type AgentGraph = CompiledGraph<AgentChannels>;
193
228
  /** Wraps a question into the state an agent starts from. */
@@ -1,13 +1,15 @@
1
1
  import { appendList, counter, lastValue } from '../graph/channels.js';
2
- import { createGraph } from '../graph/graph.js';
2
+ import { createGraph, forwardedEvents } from '../graph/graph.js';
3
3
  import { Command, END, Send } from '../types/graph.js';
4
4
  import { capabilitiesOf } from './capabilities.js';
5
5
  import { ToolExecutor, toolMessageContent } from './tool.js';
6
+ import { TOOL_SEARCH_NAME, toolSearchTool } from './tool-search.js';
6
7
  const agentChannels = () => ({
7
8
  messages: appendList(),
8
9
  iterations: counter(),
9
10
  answer: lastValue(''),
10
11
  stopReason: lastValue(undefined),
12
+ clientTools: lastValue(undefined),
11
13
  });
12
14
  export function agentInput(goal) {
13
15
  return { messages: [{ role: 'user', content: goal }] };
@@ -17,19 +19,42 @@ export function createAgent(options) {
17
19
  throw new TypeError('createAgent needs a client with a complete() method');
18
20
  }
19
21
  const maxIterations = Math.max(1, options.maxIterations ?? 8);
20
- const executor = new ToolExecutor(options.tools ?? []);
22
+ const searchable = (tools) => {
23
+ const deferred = tools.list().filter((definition) => definition.deferred);
24
+ if (deferred.length === 0 || tools.has(TOOL_SEARCH_NAME))
25
+ return tools;
26
+ return new ToolExecutor([...tools.list(), toolSearchTool(deferred)]);
27
+ };
28
+ const executor = searchable(new ToolExecutor(options.tools ?? []));
21
29
  const middleware = options.middleware ?? [];
22
30
  const approvals = options.interruptOn ?? {};
31
+ const toolsFor = async (context) => {
32
+ if (!options.toolSource)
33
+ return executor;
34
+ const supplied = await options.toolSource({
35
+ threadId: context.threadId,
36
+ ...(context.tenantId !== undefined ? { tenantId: context.tenantId } : {}),
37
+ ...(context.principal ? { principal: context.principal } : {}),
38
+ signal: context.signal,
39
+ });
40
+ return supplied.length ? searchable(new ToolExecutor([...supplied, ...executor.list()])) : executor;
41
+ };
23
42
  const graph = createGraph({ channels: agentChannels() })
24
43
  .addNode('model', async (context) => {
25
44
  const state = context.state;
45
+ const runTools = await toolsFor(context);
46
+ const clientTools = clientToolsOf(state, runTools);
47
+ if (clientTools.length > 0 && waitingOnClient(state.messages, clientTools)) {
48
+ return new Command({ update: { stopReason: 'client_tools' }, goto: END });
49
+ }
50
+ const offered = [...runTools.list(), ...clientTools];
26
51
  let request = {
27
52
  model: options.model ?? 'auto',
28
53
  messages: [
29
54
  ...(options.systemPrompt ? [{ role: 'system', content: options.systemPrompt }] : []),
30
55
  ...state.messages,
31
56
  ],
32
- ...(options.tools?.length ? { tools: executor.list() } : {}),
57
+ ...(offered.length ? { tools: offered } : {}),
33
58
  ...(options.temperature === undefined ? {} : { temperature: options.temperature }),
34
59
  ...(options.maxTokens === undefined ? {} : { maxTokens: options.maxTokens }),
35
60
  ...(options.responseFormat ? { responseFormat: options.responseFormat } : {}),
@@ -88,9 +113,28 @@ export function createAgent(options) {
88
113
  goto: END,
89
114
  });
90
115
  }
116
+ const forClient = new Set(clientTools.map((item) => item.name));
117
+ const ours = calls.filter((call) => !forClient.has(call.function.name));
118
+ for (const call of calls) {
119
+ if (!forClient.has(call.function.name))
120
+ continue;
121
+ const parsed = parseArguments(call.function.arguments);
122
+ context.tool({
123
+ phase: 'start',
124
+ id: call.id,
125
+ name: call.function.name,
126
+ args: parsed.ok ? parsed.args : call.function.arguments,
127
+ });
128
+ }
129
+ if (ours.length === 0) {
130
+ return new Command({
131
+ update: { messages: [assistant], iterations: 1, stopReason: 'client_tools' },
132
+ goto: END,
133
+ });
134
+ }
91
135
  return new Command({
92
136
  update: { messages: [assistant], iterations: 1 },
93
- goto: calls.map((call) => new Send('tools', call)),
137
+ goto: ours.map((call) => new Send('tools', call)),
94
138
  });
95
139
  }, { ends: ['tools', END] })
96
140
  .addNode('tools', async (context) => {
@@ -100,7 +144,8 @@ export function createAgent(options) {
100
144
  return { messages: [toolMessage(call, { ok: false, error: parsed.error })] };
101
145
  }
102
146
  let args = parsed.args;
103
- const definition = executor.get(call.function.name);
147
+ const runTools = await toolsFor(context);
148
+ const definition = runTools.get(call.function.name);
104
149
  const capabilities = capabilitiesOf(definition, args);
105
150
  const description = {
106
151
  id: call.id,
@@ -149,6 +194,7 @@ export function createAgent(options) {
149
194
  }
150
195
  }
151
196
  }
197
+ const forward = forwardedEvents(context);
152
198
  const run = async (current) => {
153
199
  if (options.permissions && current.args !== args) {
154
200
  const recheck = await options.permissions.decide({
@@ -159,13 +205,19 @@ export function createAgent(options) {
159
205
  if (recheck.decision === 'deny')
160
206
  return { ok: false, error: `Permission denied: ${recheck.reason}` };
161
207
  }
162
- return executor.execute(description.name, current.args, {
208
+ return runTools.execute(description.name, current.args, {
163
209
  toolCallId: call.id,
164
210
  threadId: context.threadId,
165
211
  ...(context.tenantId !== undefined ? { tenantId: context.tenantId } : {}),
166
212
  ...(context.principal ? { principal: context.principal } : {}),
167
213
  signal: context.signal,
168
214
  ...(context.store ? { store: context.store } : {}),
215
+ progress: (progress) => context.tool({ phase: 'progress', id: call.id, name: description.name, progress }),
216
+ ...(forward
217
+ ? {
218
+ forward: (event, namespace) => forward({ ...event, namespace: [...namespace, ...(event.namespace ?? [])] }),
219
+ }
220
+ : {}),
169
221
  });
170
222
  };
171
223
  context.tool({ phase: 'start', id: call.id, name: description.name, args });
@@ -250,6 +302,25 @@ function parseArguments(raw) {
250
302
  return { ok: false, error: `Tool arguments are not valid JSON (${reason}); the tool was not run` };
251
303
  }
252
304
  }
305
+ function clientToolsOf(state, runTools) {
306
+ return (state.clientTools ?? []).filter((item) => typeof item?.name === 'string' && item.name !== '' && !runTools.has(item.name));
307
+ }
308
+ function waitingOnClient(messages, clientTools) {
309
+ const names = new Set(clientTools.map((item) => item.name));
310
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
311
+ const message = messages[index];
312
+ if (message.role === 'user')
313
+ return false;
314
+ if (message.role !== 'assistant' || !message.toolCalls?.length)
315
+ continue;
316
+ const answered = new Set(messages
317
+ .slice(index + 1)
318
+ .filter((item) => item.role === 'tool')
319
+ .map((item) => item.toolCallId));
320
+ return message.toolCalls.some((call) => names.has(call.function.name) && !answered.has(call.id));
321
+ }
322
+ return false;
323
+ }
253
324
  function hookContext(context, state, store) {
254
325
  return {
255
326
  state,
@@ -0,0 +1,24 @@
1
+ /**
2
+ * File globs for workspace search, shared by the sandboxes: `*` matches within a path segment, `**`
3
+ * across segments (`**\/` also matches none), `?` one character, and `{a,b}` either alternative.
4
+ * Paths use `/`, are relative to the search root, and match case-sensitively.
5
+ */
6
+ export declare function globMatcher(pattern: string): (path: string) => boolean;
7
+ /** Directories workspace search never enters. */
8
+ export declare const SKIPPED_DIRECTORIES: ReadonlySet<string>;
9
+ /** The most directory entries, or files, one search walks. */
10
+ export declare const WALK_LIMIT = 20000;
11
+ /** Files larger than this are not searched. */
12
+ export declare const GREP_FILE_BYTES = 1048576;
13
+ /** One search reads no more than this, in all. */
14
+ export declare const GREP_TOTAL_BYTES: number;
15
+ /**
16
+ * The lines of a file that match, numbered from 1 and cut to 300 characters, at most `limit`; none
17
+ * for a binary file, one with a NUL byte in its first 8,000 bytes.
18
+ */
19
+ export declare function matchingLines(bytes: Uint8Array, expression: RegExp, limit: number): Array<{
20
+ line: number;
21
+ text: string;
22
+ }>;
23
+ /** MIME types for the media a workspace tool reads as content the model sees, by file extension. */
24
+ export declare const MEDIA_TYPES: Readonly<Record<string, string>>;