nexus-ai-pro 1.17.0 → 1.19.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 +29 -0
- package/CHANGELOG.md +117 -1
- package/README.md +3 -0
- package/dist/graph/checkpointer.d.ts +5 -0
- package/dist/graph/checkpointer.js +9 -0
- package/dist/mcp/client.d.ts +6 -1
- package/dist/mcp/client.js +7 -0
- package/dist/mcp/server.js +4 -4
- package/dist/operations/runner.js +11 -1
- package/dist/security/index.js +3 -3
- package/dist/security/input-guard.js +6 -1
- package/dist/security/pii-detector.d.ts +1 -1
- package/dist/security/pii-detector.js +9 -3
- package/dist/security/prompt-hardening.d.ts +3 -0
- package/dist/security/semantic-injection-classifier.d.ts +6 -0
- package/dist/security/semantic-injection-classifier.js +3 -1
- package/dist/server/assistant.d.ts +52 -0
- package/dist/server/assistant.js +45 -0
- package/dist/server/cron.d.ts +69 -0
- package/dist/server/cron.js +172 -0
- package/dist/server/errors.d.ts +56 -0
- package/dist/server/errors.js +56 -0
- package/dist/server/events.d.ts +91 -0
- package/dist/server/events.js +103 -0
- package/dist/server/http.d.ts +34 -0
- package/dist/server/http.js +73 -0
- package/dist/server/index.d.ts +18 -0
- package/dist/server/index.js +8 -0
- package/dist/server/node.d.ts +43 -0
- package/dist/server/node.js +86 -0
- package/dist/server/remote.d.ts +73 -0
- package/dist/server/remote.js +163 -0
- package/dist/server/runs.d.ts +116 -0
- package/dist/server/runs.js +310 -0
- package/dist/server/server.d.ts +54 -0
- package/dist/server/server.js +248 -0
- package/dist/server/state.d.ts +44 -0
- package/dist/server/state.js +40 -0
- package/dist/tracing/instrument.js +2 -0
- package/dist/tracing/tracer.d.ts +4 -0
- package/dist/tracing/tracer.js +2 -0
- package/dist/types/graph.d.ts +2 -0
- package/dist/types/security.d.ts +74 -5
- package/dist/types/server.d.ts +185 -0
- package/dist/types/server.js +1 -0
- package/dist/workflow/chains.d.ts +3 -3
- package/dist/workflow/chains.js +18 -7
- package/dist-cjs/graph/checkpointer.d.ts +5 -0
- package/dist-cjs/graph/checkpointer.js +9 -0
- package/dist-cjs/mcp/client.d.ts +6 -1
- package/dist-cjs/mcp/client.js +7 -0
- package/dist-cjs/mcp/server.js +3 -3
- package/dist-cjs/operations/runner.js +11 -1
- package/dist-cjs/security/index.js +3 -3
- package/dist-cjs/security/input-guard.js +6 -1
- package/dist-cjs/security/pii-detector.d.ts +1 -1
- package/dist-cjs/security/pii-detector.js +9 -3
- package/dist-cjs/security/prompt-hardening.d.ts +3 -0
- package/dist-cjs/security/semantic-injection-classifier.d.ts +6 -0
- package/dist-cjs/security/semantic-injection-classifier.js +3 -1
- package/dist-cjs/server/assistant.d.ts +52 -0
- package/dist-cjs/server/assistant.js +49 -0
- package/dist-cjs/server/cron.d.ts +69 -0
- package/dist-cjs/server/cron.js +177 -0
- package/dist-cjs/server/errors.d.ts +56 -0
- package/dist-cjs/server/errors.js +66 -0
- package/dist-cjs/server/events.d.ts +91 -0
- package/dist-cjs/server/events.js +108 -0
- package/dist-cjs/server/http.d.ts +34 -0
- package/dist-cjs/server/http.js +81 -0
- package/dist-cjs/server/index.d.ts +18 -0
- package/dist-cjs/server/index.js +29 -0
- package/dist-cjs/server/node.d.ts +43 -0
- package/dist-cjs/server/node.js +89 -0
- package/dist-cjs/server/remote.d.ts +73 -0
- package/dist-cjs/server/remote.js +166 -0
- package/dist-cjs/server/runs.d.ts +116 -0
- package/dist-cjs/server/runs.js +314 -0
- package/dist-cjs/server/server.d.ts +54 -0
- package/dist-cjs/server/server.js +251 -0
- package/dist-cjs/server/state.d.ts +44 -0
- package/dist-cjs/server/state.js +45 -0
- package/dist-cjs/tracing/instrument.js +2 -0
- package/dist-cjs/tracing/tracer.d.ts +4 -0
- package/dist-cjs/tracing/tracer.js +2 -0
- package/dist-cjs/types/graph.d.ts +2 -0
- package/dist-cjs/types/security.d.ts +74 -5
- package/dist-cjs/types/server.d.ts +185 -0
- package/dist-cjs/types/server.js +2 -0
- package/dist-cjs/workflow/chains.d.ts +3 -3
- package/dist-cjs/workflow/chains.js +18 -7
- package/package.json +36 -5
package/API_STABILITY.md
CHANGED
|
@@ -426,6 +426,35 @@ patch release. None is exported from the root.
|
|
|
426
426
|
- The wording of gate reasons, of the note a forced promotion records, and of `formatPromptDiff()`
|
|
427
427
|
output is for people and may improve in any release.
|
|
428
428
|
|
|
429
|
+
## Agent server stage (1.18.0)
|
|
430
|
+
|
|
431
|
+
The `nexus-ai-pro/server` subpaths are experimental in production readiness, as the image and prompt
|
|
432
|
+
families are: their exported types, functions, and classes follow the 1.x semantic versioning rules,
|
|
433
|
+
and the experimental label does not permit an incompatible minor or patch release. Neither is exported
|
|
434
|
+
from the root.
|
|
435
|
+
|
|
436
|
+
- The routes listed in the server guide, their status codes, and the shape of `RunRecord`,
|
|
437
|
+
`ThreadRecord`, and `RunEvent` follow the 1.x rules. A minor release may add routes and fields.
|
|
438
|
+
- Event ids are per run and strictly increasing, which is what `Last-Event-ID` depends on. A client
|
|
439
|
+
that reconnects with an id never receives it again.
|
|
440
|
+
- The server is not a security boundary on its own: authentication, authorization beyond tenancy and
|
|
441
|
+
scopes, and rate limiting are the application's, through the hooks.
|
|
442
|
+
- Recovery re-runs a whole run rather than the step it died on, so an assistant with side effects
|
|
443
|
+
should be idempotent or be left at a single attempt.
|
|
444
|
+
|
|
445
|
+
## Studio stage (1.19.0)
|
|
446
|
+
|
|
447
|
+
`nexus-ai-pro-studio` is a separate package, experimental in production readiness. It is versioned
|
|
448
|
+
with nexus-ai-pro and declares the core versions it works with as a peer dependency.
|
|
449
|
+
|
|
450
|
+
- The `nexus-studio` command, its flags, and the shape of the config module's default export follow
|
|
451
|
+
the 1.x rules. A minor release may add sources and flags.
|
|
452
|
+
- `createStudio()`, `startStudio()`, and the exported types follow the 1.x rules.
|
|
453
|
+
- The JSON routes behind the page are for the page and may change in any release; build on the
|
|
454
|
+
stores themselves rather than on the studio's routes.
|
|
455
|
+
- The access rules are a guarantee: loopback binding by default, a token on every request, the token
|
|
456
|
+
in a header for every change, and loopback hosts only unless others are allowed.
|
|
457
|
+
|
|
429
458
|
## Deprecation process
|
|
430
459
|
|
|
431
460
|
Deprecated APIs are marked with `@deprecated` in declarations and described in the changelog. Removals
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,120 @@ Notable changes to this project are recorded here. The format follows [Keep a Ch
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [1.19.0] - 2026-09-28
|
|
8
|
+
|
|
9
|
+
A studio to look at it all. `nexus-ai-pro-studio` is a separate package — so the core install never
|
|
10
|
+
carries a UI — that shows what an application records, from its own stores, on the developer's
|
|
11
|
+
machine, with no hosted service.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **`nexus-ai-pro-studio`**, experimental, with the `nexus-studio` command. Given a config module
|
|
16
|
+
whose default export lists the application's stores, it serves eight views:
|
|
17
|
+
- **Traces**: filtered run lists, run trees with inputs, outputs, cost, and latency, trace
|
|
18
|
+
comparison, and feedback on a run.
|
|
19
|
+
- **Threads**: each graph's diagram with the next nodes highlighted, state at any step, and fork,
|
|
20
|
+
edit, and resume.
|
|
21
|
+
- **Inbox**: every thread waiting for a human and every review item waiting for a grade.
|
|
22
|
+
- **Experiments**: experiment and dataset lists, and comparisons with per-metric intervals and a
|
|
23
|
+
verdict.
|
|
24
|
+
- **Prompts**: versions, labels, history, diffs, gated promotion, rollback, and a playground.
|
|
25
|
+
- **Costs**: cost per day and per model, the most expensive runs, and budgets.
|
|
26
|
+
- **Health**: provider health, local and shared circuit state, metrics, and cache statistics.
|
|
27
|
+
- **Operations**: the operation queue by status, and asset store totals.
|
|
28
|
+
- **Locked down for a local tool.** It binds `127.0.0.1`, requires a token that moves from the URL
|
|
29
|
+
into an HTTP-only same-site cookie, requires the token in a header for every change, refuses
|
|
30
|
+
non-loopback `Host` headers against DNS rebinding, and serves every page under a strict content
|
|
31
|
+
security policy with every value inserted as text.
|
|
32
|
+
- `startStudio()` and `createStudio()` start it from code or mount it in an existing server.
|
|
33
|
+
- `npm run studio:demo` starts it on a demo application with something in every view.
|
|
34
|
+
- The operation-store checkpointer — the one behind Redis and Postgres — lists its threads through
|
|
35
|
+
`threadIds()`, as the in-memory one already did, and `GraphCheckpointer` declares it as optional.
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
|
|
39
|
+
- **Secret detection on input now detects under every action.** It only scanned when the action
|
|
40
|
+
was `mask`. Under the default action, `block`, and under `flag`, a request carrying a private
|
|
41
|
+
key, a cloud or payment key, a token, or a secret assignment went out with no finding. Such a
|
|
42
|
+
request is now blocked by default, at every level but `off`. Set `input.secrets.action` to `flag`
|
|
43
|
+
to record secrets without blocking.
|
|
44
|
+
- The PII action `remove` deletes the matched data. It was accepted but behaved like `flag`, so the
|
|
45
|
+
data stayed in the request.
|
|
46
|
+
- The semantic injection check no longer re-embeds its example attacks on every request.
|
|
47
|
+
- `McpServer` answers an unknown method with the JSON-RPC method-not-found code, and an unknown
|
|
48
|
+
resource with invalid-params, instead of an internal error, so a client can tell an unsupported
|
|
49
|
+
feature from a failure.
|
|
50
|
+
- `McpClient` requests made together before the handshake finished each sent `initialize`; they now
|
|
51
|
+
share one handshake.
|
|
52
|
+
- `summarizeVerifyFormat()`'s verify step sent the summary to the model with no instruction, so the
|
|
53
|
+
model answered the summary instead of restating it, and that reply was what got checked against
|
|
54
|
+
the sources. It now asks for a restatement against the sources. The final format is applied only in
|
|
55
|
+
the last step, and a verify step that was skipped is no longer recorded in `steps`.
|
|
56
|
+
- `traceModelClient()` records the model and provider that answered on the run itself. It recorded
|
|
57
|
+
the model the request asked for — often `auto` — and left `provider` empty, so a `provider` query
|
|
58
|
+
never matched a model run and per-model views showed `auto`. `FinishRunOptions` gains `model` and
|
|
59
|
+
`provider` for the same purpose in your own instrumentation.
|
|
60
|
+
|
|
61
|
+
### Deprecated
|
|
62
|
+
|
|
63
|
+
- `InjectionDetectionConfig.sensitivity` and `ToolPolicyConfig.requiresApproval` have never been
|
|
64
|
+
read. Both will be removed in 2.0; approval before a tool runs belongs to the agent loop.
|
|
65
|
+
|
|
66
|
+
### Changed
|
|
67
|
+
|
|
68
|
+
- Nineteen guides under `docs/` — agents, batch, caching, core, graphs, grounding, MCP, memory,
|
|
69
|
+
Postgres, prompts, providers, resilience, security, server, telephony, testing, tracing, voice, and
|
|
70
|
+
workflows — now explain each export they list in their text, not only in the generated reference.
|
|
71
|
+
The security configuration's nested settings have doc comments.
|
|
72
|
+
- The documentation coverage check also covers the studio package's exports.
|
|
73
|
+
|
|
74
|
+
## [1.18.0] - 2026-09-25
|
|
75
|
+
|
|
76
|
+
A server you can host. Assistants get an HTTP surface — threads, background runs, resumable event
|
|
77
|
+
streams, and cron jobs — on top of the durability that already existed: a run is an operation, so it
|
|
78
|
+
outlives the request that started it and the worker that was running it.
|
|
79
|
+
|
|
80
|
+
### Added
|
|
81
|
+
|
|
82
|
+
- **The agent server** (`nexus-ai-pro/server`, experimental). `createAgentServer()` serves assistants,
|
|
83
|
+
threads, runs, and cron jobs as one `Request`-to-`Response` handler. `toNodeListener()` runs it on
|
|
84
|
+
Node's `http`, and as Express or Fastify middleware.
|
|
85
|
+
- **Assistants are structural.** `graphAssistant()` serves a compiled graph, mapping threads to graph
|
|
86
|
+
threads; `functionAssistant()` serves a plain function. The entry point imports neither runtime.
|
|
87
|
+
- **Runs are durable operations**, so leases, heartbeats, retries, idempotency, dead-lettering, and
|
|
88
|
+
webhooks come from the operations family. A run abandoned by a crashed worker is re-claimed by
|
|
89
|
+
another replica through `start()` or `runs.recover()`.
|
|
90
|
+
- **Resumable streaming.** `GET /runs/:id/events` is a server-sent event stream whose events carry
|
|
91
|
+
their log id, so a client reconnects with `Last-Event-ID` and gets exactly what it missed.
|
|
92
|
+
`MemoryRunEventLog` covers one replica; `RedisRunEventLog` lets a client reconnect to any replica.
|
|
93
|
+
- **A busy thread is configurable**: `reject`, `enqueue`, `interrupt`, or `rollback`, per server or
|
|
94
|
+
per run.
|
|
95
|
+
- **Cron jobs.** `CronScheduler` fires `{ everyMs }` or five-field cron schedules, parsed by
|
|
96
|
+
`parseCron()`. Every replica ticks and every firing carries the idempotency key `<job>:<slot>`, so a
|
|
97
|
+
job runs once however many replicas are up, with no lock and no leader election.
|
|
98
|
+
- **Authentication and tenancy hooks.** `authenticate` returns a `Principal`; threads, runs, and cron
|
|
99
|
+
jobs record their tenant, and another tenant's resource is a `404`.
|
|
100
|
+
- **`createRemoteGraph()`** (`nexus-ai-pro/server/remote`), the client: `invoke()`, `stream()` with
|
|
101
|
+
reconnection, `resume()`, and `asNode()`, which makes a deployed assistant a node in a local graph.
|
|
102
|
+
- **Deployment templates**: `deploy/Dockerfile`, `deploy/compose.yaml` for two replicas behind Redis,
|
|
103
|
+
and an nginx configuration that does not buffer event streams. `examples/agent-server.ts` is a
|
|
104
|
+
runnable server.
|
|
105
|
+
|
|
106
|
+
### Changed
|
|
107
|
+
|
|
108
|
+
- **Every feature's exports are documented in that feature's guide.** A guide can now claim exports by
|
|
109
|
+
source directory, so features reachable only from the root — guardrails, grounding, workflows,
|
|
110
|
+
metrics, conformance, routing — no longer pile into the client guide, which drops from 249 exports
|
|
111
|
+
to 141. Retrieval, citations, and verification get a guide of their own, and the agents, batch,
|
|
112
|
+
caching, and client guides now explain every export they cover.
|
|
113
|
+
|
|
114
|
+
### Fixed
|
|
115
|
+
|
|
116
|
+
- An operation whose record was settled by another worker — after its lease lapsed and the work was
|
|
117
|
+
taken over — crashed the original worker with an unhandled `OperationTransitionError` from its
|
|
118
|
+
heartbeat. The heartbeat now stops and fails its local run instead, and a heartbeat that throws for
|
|
119
|
+
any other reason no longer becomes an unhandled rejection.
|
|
120
|
+
|
|
7
121
|
## [1.17.0] - 2026-09-23
|
|
8
122
|
|
|
9
123
|
Prompt versioning, and documentation you can navigate. Prompts are defined with typed variables,
|
|
@@ -894,7 +1008,9 @@ shared policy vocabulary is in place for a future release that revisits this.
|
|
|
894
1008
|
- URL fetching rejects unsafe private-network targets and limits response reads.
|
|
895
1009
|
- CLI and audit findings no longer disclose detected secret values.
|
|
896
1010
|
|
|
897
|
-
[Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.
|
|
1011
|
+
[Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.19.0...HEAD
|
|
1012
|
+
[1.19.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.18.0...v1.19.0
|
|
1013
|
+
[1.18.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.17.0...v1.18.0
|
|
898
1014
|
[1.17.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.16.0...v1.17.0
|
|
899
1015
|
[1.16.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.15.0...v1.16.0
|
|
900
1016
|
[1.15.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.14.0...v1.15.0
|
package/README.md
CHANGED
|
@@ -118,12 +118,15 @@ from the doc comments. The guides live in the repository, so these links go to G
|
|
|
118
118
|
| [Resilience and observability](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/resilience.md) | Circuit breaking, distributed rate limits, metrics, logs, and audit |
|
|
119
119
|
| [Batch tiers](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/batch.md) | The providers' discounted batch APIs, resumable from any process |
|
|
120
120
|
| [Caching](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/caching.md) | Exact and semantic response caches, with memory, Redis, and SQLite adapters |
|
|
121
|
+
| [Grounding](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/grounding.md) | Retrieval, citations, verification, self-consistency, and knowledge graphs |
|
|
121
122
|
| [Embeddings and retrieval](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/embeddings.md) | Embeddings as a routed operation family, and RAG helpers |
|
|
122
123
|
| [Images (experimental)](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/images.md) | Generation and masked edits over three backends, input safety, moderation, asset stores |
|
|
123
124
|
| [Voice](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/voice.md) | Transcription, speech, voice turns, and voice sessions |
|
|
124
125
|
| [Realtime voice](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/realtime.md) | Browser and server realtime sessions with barge-in, tools, and exports |
|
|
125
126
|
| [Telephony](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/telephony.md) | Calls, webhooks, phone numbers, and phone agents on realtime sessions |
|
|
126
127
|
| [Testing](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/testing.md) | Recording and replaying provider traffic, and conformance suites |
|
|
128
|
+
| [Studio](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/studio.md) | A local UI for traces, threads, approvals, experiments, prompts, costs, and health, as a separate package |
|
|
129
|
+
| [Agent server](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/server.md) | Self-hosted HTTP server for assistants: threads, durable runs, resumable streams, cron |
|
|
127
130
|
| [Workflows](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/workflows.md) | Ready-made chains and domain workflows |
|
|
128
131
|
| [Command line](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/cli.md) | The `nexus` command |
|
|
129
132
|
| [Packaging and install weight](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/packaging.md) | Every entry point and what it costs |
|
|
@@ -66,6 +66,11 @@ export declare class OperationStoreCheckpointer implements GraphCheckpointer {
|
|
|
66
66
|
history(threadId: string, limit?: number): Promise<GraphCheckpoint[]>;
|
|
67
67
|
/** Deletes a thread's checkpoints and its head. */
|
|
68
68
|
delete(threadId: string): Promise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* Every thread with a checkpoint, read from the head records. Empty when the store cannot list,
|
|
71
|
+
* since finding heads any other way would mean scanning every key.
|
|
72
|
+
*/
|
|
73
|
+
threadIds(): Promise<string[]>;
|
|
69
74
|
private readHead;
|
|
70
75
|
/**
|
|
71
76
|
* Writes one record, creating it when absent and otherwise updating in place.
|
|
@@ -86,6 +86,15 @@ export class OperationStoreCheckpointer {
|
|
|
86
86
|
await this.store.delete?.(stepId(threadId, step));
|
|
87
87
|
await this.store.delete?.(threadId);
|
|
88
88
|
}
|
|
89
|
+
async threadIds() {
|
|
90
|
+
if (!this.store.list)
|
|
91
|
+
return [];
|
|
92
|
+
const records = await this.store.list();
|
|
93
|
+
return records
|
|
94
|
+
.filter((record) => record.result !== undefined && isHead(record.result))
|
|
95
|
+
.map((record) => record.id)
|
|
96
|
+
.sort();
|
|
97
|
+
}
|
|
89
98
|
async readHead(threadId) {
|
|
90
99
|
const record = await this.store.read(threadId);
|
|
91
100
|
return record?.result && isHead(record.result) ? record.result : undefined;
|
package/dist/mcp/client.d.ts
CHANGED
|
@@ -62,13 +62,18 @@ export declare class McpClient {
|
|
|
62
62
|
private readonly pending;
|
|
63
63
|
private nextId;
|
|
64
64
|
private initialized;
|
|
65
|
+
private connecting;
|
|
65
66
|
private serverInfo;
|
|
66
67
|
constructor(transport: McpTransport, options?: McpClientOptions);
|
|
67
|
-
/**
|
|
68
|
+
/**
|
|
69
|
+
* Performs the handshake and returns the server's name and version. Called automatically by the
|
|
70
|
+
* first request that needs it; requests made together share one handshake.
|
|
71
|
+
*/
|
|
68
72
|
connect(): Promise<{
|
|
69
73
|
name?: string;
|
|
70
74
|
version?: string;
|
|
71
75
|
}>;
|
|
76
|
+
private handshake;
|
|
72
77
|
/** Lists the server's tools. */
|
|
73
78
|
listTools(): Promise<McpToolDescriptor[]>;
|
|
74
79
|
/**
|
package/dist/mcp/client.js
CHANGED
|
@@ -5,6 +5,7 @@ export class McpClient {
|
|
|
5
5
|
pending = new Map();
|
|
6
6
|
nextId = 1;
|
|
7
7
|
initialized = false;
|
|
8
|
+
connecting;
|
|
8
9
|
serverInfo = {};
|
|
9
10
|
constructor(transport, options = {}) {
|
|
10
11
|
this.transport = transport;
|
|
@@ -15,6 +16,12 @@ export class McpClient {
|
|
|
15
16
|
async connect() {
|
|
16
17
|
if (this.initialized)
|
|
17
18
|
return this.serverInfo;
|
|
19
|
+
this.connecting ??= this.handshake().finally(() => {
|
|
20
|
+
this.connecting = undefined;
|
|
21
|
+
});
|
|
22
|
+
return this.connecting;
|
|
23
|
+
}
|
|
24
|
+
async handshake() {
|
|
18
25
|
await this.transport.start?.();
|
|
19
26
|
const result = (await this.request('initialize', {
|
|
20
27
|
protocolVersion: MCP_PROTOCOL_VERSION,
|
package/dist/mcp/server.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isNotification, isRequest, JSON_RPC_ERRORS, LineDecoder, MCP_PROTOCOL_VERSION, textContent, } from './protocol.js';
|
|
1
|
+
import { isNotification, isRequest, JSON_RPC_ERRORS, LineDecoder, MCP_PROTOCOL_VERSION, McpError, textContent, } from './protocol.js';
|
|
2
2
|
export class McpServer {
|
|
3
3
|
options;
|
|
4
4
|
tools = new Map();
|
|
@@ -35,7 +35,7 @@ export class McpServer {
|
|
|
35
35
|
jsonrpc: '2.0',
|
|
36
36
|
id: message.id,
|
|
37
37
|
error: {
|
|
38
|
-
code: JSON_RPC_ERRORS.internal,
|
|
38
|
+
code: error instanceof McpError ? error.code : JSON_RPC_ERRORS.internal,
|
|
39
39
|
message: error instanceof Error ? error.message : String(error),
|
|
40
40
|
},
|
|
41
41
|
});
|
|
@@ -89,7 +89,7 @@ export class McpServer {
|
|
|
89
89
|
const uri = String(params.uri ?? '');
|
|
90
90
|
const resource = this.options.resources?.find((item) => item.uri === uri);
|
|
91
91
|
if (!resource)
|
|
92
|
-
throw new
|
|
92
|
+
throw new McpError(`Unknown resource "${uri}"`, JSON_RPC_ERRORS.invalidParams);
|
|
93
93
|
const value = await resource.read();
|
|
94
94
|
return {
|
|
95
95
|
contents: [
|
|
@@ -102,7 +102,7 @@ export class McpServer {
|
|
|
102
102
|
};
|
|
103
103
|
}
|
|
104
104
|
default:
|
|
105
|
-
throw new
|
|
105
|
+
throw new McpError(`Unknown method "${request.method}"`, JSON_RPC_ERRORS.methodNotFound);
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
}
|
|
@@ -233,6 +233,10 @@ export class OperationRunner {
|
|
|
233
233
|
handle.settleFailure(new OperationLeaseLostError(latest.id, current.lease.owner));
|
|
234
234
|
return;
|
|
235
235
|
}
|
|
236
|
+
if (isTerminalOperationStatus(current.status)) {
|
|
237
|
+
handle.settleFailure(new OperationLeaseLostError(latest.id, current.lease?.owner ?? 'another worker'));
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
236
240
|
const renewed = this.advance(current, 'running');
|
|
237
241
|
renewed.lease = {
|
|
238
242
|
owner: this.owner,
|
|
@@ -243,7 +247,13 @@ export class OperationRunner {
|
|
|
243
247
|
if (await this.store.update(renewed, current.sequence))
|
|
244
248
|
latest = renewed;
|
|
245
249
|
};
|
|
246
|
-
const timer = setInterval(() =>
|
|
250
|
+
const timer = setInterval(() => {
|
|
251
|
+
void beat().catch((error) => {
|
|
252
|
+
stopped = true;
|
|
253
|
+
clearInterval(timer);
|
|
254
|
+
handle.settleFailure(error);
|
|
255
|
+
});
|
|
256
|
+
}, this.heartbeatMs);
|
|
247
257
|
timer.unref?.();
|
|
248
258
|
return {
|
|
249
259
|
stop: () => {
|
package/dist/security/index.js
CHANGED
|
@@ -63,9 +63,9 @@ export class SecurityPipeline {
|
|
|
63
63
|
findings.push(...piiFindings);
|
|
64
64
|
guardrailsApplied.push('pii-detection');
|
|
65
65
|
const action = piiConfig.action || (level === 'strict' || level === 'paranoid' ? 'mask' : 'flag');
|
|
66
|
-
if (piiFindings.length > 0 && action === 'mask') {
|
|
67
|
-
safeRequest = this.piiDetector.mask(safeRequest, piiConfig);
|
|
68
|
-
guardrailsApplied.push('pii-masking');
|
|
66
|
+
if (piiFindings.length > 0 && (action === 'mask' || action === 'remove')) {
|
|
67
|
+
safeRequest = this.piiDetector.mask(safeRequest, { ...piiConfig, action });
|
|
68
|
+
guardrailsApplied.push(action === 'remove' ? 'pii-removal' : 'pii-masking');
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
const blockingFindings = this.getBlockingFindings(findings, level, normalized);
|
|
@@ -103,8 +103,13 @@ export class InputGuard {
|
|
|
103
103
|
});
|
|
104
104
|
return output;
|
|
105
105
|
};
|
|
106
|
-
if (action !== 'mask')
|
|
106
|
+
if (action !== 'mask') {
|
|
107
|
+
request.messages.forEach((message, index) => {
|
|
108
|
+
for (const text of this.extractText(message))
|
|
109
|
+
maskText(text, `messages.${index}.content`);
|
|
110
|
+
});
|
|
107
111
|
return request;
|
|
112
|
+
}
|
|
108
113
|
return {
|
|
109
114
|
...request,
|
|
110
115
|
messages: request.messages.map((message, index) => ({
|
|
@@ -4,7 +4,7 @@ import type { PIIConfig, SecurityFinding } from '../types/security.js';
|
|
|
4
4
|
export declare class PIIDetector {
|
|
5
5
|
/** Finds personal data in a request's messages. */
|
|
6
6
|
detect(request: CompletionRequest, config?: PIIConfig): SecurityFinding[];
|
|
7
|
-
/** Returns the request with personal data masked
|
|
7
|
+
/** Returns the request with personal data masked, or removed when the action is `remove`. */
|
|
8
8
|
mask(request: CompletionRequest, config?: PIIConfig): CompletionRequest;
|
|
9
9
|
private maskText;
|
|
10
10
|
private extractText;
|
|
@@ -35,22 +35,28 @@ export class PIIDetector {
|
|
|
35
35
|
mask(request, config = {}) {
|
|
36
36
|
const enabledTypes = config.detect || ['email', 'phone', 'credit-card', 'ip-address', 'aws-key', 'private-key'];
|
|
37
37
|
const maskChar = config.maskChar || '█';
|
|
38
|
+
const remove = config.action === 'remove';
|
|
38
39
|
return {
|
|
39
40
|
...request,
|
|
40
41
|
messages: request.messages.map((message) => ({
|
|
41
42
|
...message,
|
|
42
43
|
content: typeof message.content === 'string'
|
|
43
|
-
? this.maskText(message.content, enabledTypes, maskChar, config.preserveFormat !== false)
|
|
44
|
+
? this.maskText(message.content, enabledTypes, maskChar, config.preserveFormat !== false, remove)
|
|
44
45
|
: message.content.map((part) => part.type === 'text'
|
|
45
|
-
? {
|
|
46
|
+
? {
|
|
47
|
+
...part,
|
|
48
|
+
text: this.maskText(part.text, enabledTypes, maskChar, config.preserveFormat !== false, remove),
|
|
49
|
+
}
|
|
46
50
|
: part),
|
|
47
51
|
})),
|
|
48
52
|
};
|
|
49
53
|
}
|
|
50
|
-
maskText(text, types, maskChar, preserveFormat) {
|
|
54
|
+
maskText(text, types, maskChar, preserveFormat, remove = false) {
|
|
51
55
|
let masked = text;
|
|
52
56
|
for (const type of types) {
|
|
53
57
|
masked = masked.replace(PII_PATTERNS[type], (value) => {
|
|
58
|
+
if (remove)
|
|
59
|
+
return '';
|
|
54
60
|
if (!preserveFormat)
|
|
55
61
|
return maskChar.repeat(Math.min(value.length, 12));
|
|
56
62
|
return value.replace(/[A-Za-z0-9]/g, maskChar);
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { CompletionRequest } from '../types/messages.js';
|
|
2
|
+
/** Options for `hardenPrompt()`. */
|
|
2
3
|
export interface PromptHardeningOptions {
|
|
4
|
+
/** What user text is wrapped in. Defaults to `"""`. */
|
|
3
5
|
delimiter?: string;
|
|
6
|
+
/** The system message placed first, telling the model to treat delimited text as data. */
|
|
4
7
|
systemInstruction?: string;
|
|
5
8
|
}
|
|
6
9
|
/**
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import type { CompletionRequest } from '../types/messages.js';
|
|
2
2
|
import type { SecurityFinding } from '../types/security.js';
|
|
3
3
|
import { type EmbeddingProvider } from '../hallucination/retrieval.js';
|
|
4
|
+
/** Options for `SemanticInjectionClassifier`. */
|
|
4
5
|
export interface SemanticInjectionClassifierOptions {
|
|
6
|
+
/** `false` turns `detect()` off. */
|
|
5
7
|
enabled?: boolean;
|
|
8
|
+
/** Similarity at which a prompt is flagged, from 0 to 1. Defaults to 0.78; above 0.88 is critical. */
|
|
6
9
|
threshold?: number;
|
|
10
|
+
/** Embeds text for `detect()`. Defaults to hashed term vectors; `detectSync()` always uses those. */
|
|
7
11
|
embed?: EmbeddingProvider;
|
|
12
|
+
/** Example attacks to compare against, instead of the built-in ones. */
|
|
8
13
|
examples?: string[];
|
|
9
14
|
}
|
|
10
15
|
/**
|
|
@@ -16,6 +21,7 @@ export declare class SemanticInjectionClassifier {
|
|
|
16
21
|
private examples;
|
|
17
22
|
private embed;
|
|
18
23
|
private exampleEmbeddings?;
|
|
24
|
+
private hashedExamples?;
|
|
19
25
|
constructor(options?: SemanticInjectionClassifierOptions);
|
|
20
26
|
/** Finds injection attempts, using the configured embedding function. */
|
|
21
27
|
detect(request: CompletionRequest): Promise<SecurityFinding[]>;
|
|
@@ -12,6 +12,7 @@ export class SemanticInjectionClassifier {
|
|
|
12
12
|
examples;
|
|
13
13
|
embed;
|
|
14
14
|
exampleEmbeddings;
|
|
15
|
+
hashedExamples;
|
|
15
16
|
constructor(options = {}) {
|
|
16
17
|
this.options = options;
|
|
17
18
|
this.examples = options.examples || DEFAULT_ATTACK_EXAMPLES;
|
|
@@ -43,7 +44,8 @@ export class SemanticInjectionClassifier {
|
|
|
43
44
|
detectSync(request) {
|
|
44
45
|
const findings = [];
|
|
45
46
|
const threshold = this.options.threshold ?? 0.78;
|
|
46
|
-
|
|
47
|
+
this.hashedExamples ??= createHashEmbeddings(this.examples);
|
|
48
|
+
const exampleEmbeddings = this.hashedExamples;
|
|
47
49
|
for (const [index, message] of request.messages.entries()) {
|
|
48
50
|
for (const text of extractText(message)) {
|
|
49
51
|
const [embedding] = createHashEmbeddings([text]);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { AssistantRunContext, ServerAssistant } from '../types/server.js';
|
|
2
|
+
/**
|
|
3
|
+
* The part of a compiled graph the adapter uses.
|
|
4
|
+
*
|
|
5
|
+
* Structural, so the server entry point never imports the graph runtime: an application that serves
|
|
6
|
+
* plain functions does not load it, and one that serves graphs already has it.
|
|
7
|
+
*/
|
|
8
|
+
export interface GraphLike {
|
|
9
|
+
/** Runs an input, yielding one event per superstep. */
|
|
10
|
+
stream(input: unknown, options: {
|
|
11
|
+
threadId?: string;
|
|
12
|
+
signal?: AbortSignal;
|
|
13
|
+
metadata?: Record<string, unknown>;
|
|
14
|
+
}): AsyncIterable<unknown>;
|
|
15
|
+
/** Answers an interrupt and continues the thread. */
|
|
16
|
+
resume(threadId: string, value: unknown, options: {
|
|
17
|
+
signal?: AbortSignal;
|
|
18
|
+
metadata?: Record<string, unknown>;
|
|
19
|
+
}): AsyncIterable<unknown>;
|
|
20
|
+
/** The thread's checkpoint, at its latest step or at one given. */
|
|
21
|
+
state(threadId: string, step?: number): Promise<{
|
|
22
|
+
step?: number;
|
|
23
|
+
state?: unknown;
|
|
24
|
+
} | undefined>;
|
|
25
|
+
/** Writes values into a thread's state, which is how a rollback is applied. */
|
|
26
|
+
updateState?(threadId: string, values: Record<string, unknown>, options?: {
|
|
27
|
+
asNode?: string;
|
|
28
|
+
}): Promise<unknown>;
|
|
29
|
+
}
|
|
30
|
+
/** Options for `graphAssistant()`. */
|
|
31
|
+
export interface GraphAssistantOptions {
|
|
32
|
+
/** What the assistant is, reported by the assistants endpoint. */
|
|
33
|
+
description?: string;
|
|
34
|
+
/** Metadata recorded on every checkpoint the server's runs create. */
|
|
35
|
+
metadata?: Record<string, unknown>;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Serves a compiled graph as an assistant.
|
|
39
|
+
*
|
|
40
|
+
* Threads map to graph threads, so state, interrupts, history, and checkpoints are the graph's own;
|
|
41
|
+
* the server adds only the record of which run owns a thread. A graph compiled without a
|
|
42
|
+
* checkpointer still works for stateless runs, but cannot resume or roll back.
|
|
43
|
+
*/
|
|
44
|
+
export declare function graphAssistant(graph: GraphLike, options?: GraphAssistantOptions): ServerAssistant;
|
|
45
|
+
/**
|
|
46
|
+
* Serves a plain function as an assistant, for work that is not a graph.
|
|
47
|
+
*
|
|
48
|
+
* The function may return a value or yield events; the last value it yields is the run's output.
|
|
49
|
+
*/
|
|
50
|
+
export declare function functionAssistant(run: (input: unknown, context: AssistantRunContext) => AsyncIterable<unknown> | Promise<unknown> | unknown, options?: {
|
|
51
|
+
description?: string;
|
|
52
|
+
}): ServerAssistant;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export function graphAssistant(graph, options = {}) {
|
|
2
|
+
return {
|
|
3
|
+
description: options.description,
|
|
4
|
+
stream(input, context) {
|
|
5
|
+
return graph.stream(input ?? {}, {
|
|
6
|
+
threadId: context.threadId,
|
|
7
|
+
signal: context.signal,
|
|
8
|
+
metadata: { ...options.metadata, runId: context.runId },
|
|
9
|
+
});
|
|
10
|
+
},
|
|
11
|
+
resume(threadId, value, context) {
|
|
12
|
+
return graph.resume(threadId, value, {
|
|
13
|
+
signal: context.signal,
|
|
14
|
+
metadata: { ...options.metadata, runId: context.runId },
|
|
15
|
+
});
|
|
16
|
+
},
|
|
17
|
+
async state(threadId) {
|
|
18
|
+
return (await graph.state(threadId))?.state;
|
|
19
|
+
},
|
|
20
|
+
async step(threadId) {
|
|
21
|
+
return (await graph.state(threadId))?.step;
|
|
22
|
+
},
|
|
23
|
+
async restore(threadId, step) {
|
|
24
|
+
if (!graph.updateState)
|
|
25
|
+
return;
|
|
26
|
+
const checkpoint = await graph.state(threadId, step);
|
|
27
|
+
if (!checkpoint?.state)
|
|
28
|
+
return;
|
|
29
|
+
await graph.updateState(threadId, checkpoint.state);
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export function functionAssistant(run, options = {}) {
|
|
34
|
+
return {
|
|
35
|
+
description: options.description,
|
|
36
|
+
async *stream(input, context) {
|
|
37
|
+
const result = run(input, context);
|
|
38
|
+
if (result !== null && typeof result === 'object' && Symbol.asyncIterator in result) {
|
|
39
|
+
yield* result;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
yield { type: 'done', status: 'succeeded', state: await result };
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { CronRecord, RunRecord, ServerStateStore } from '../types/server.js';
|
|
2
|
+
/** A cron job that is due, with the slot it is due for. */
|
|
3
|
+
export interface DueCronJob extends CronRecord {
|
|
4
|
+
/** The firing this is: the epoch millisecond of the slot, which makes the run idempotent. */
|
|
5
|
+
slot: number;
|
|
6
|
+
}
|
|
7
|
+
/** Options for the cron scheduler. */
|
|
8
|
+
export interface CronSchedulerOptions {
|
|
9
|
+
/** Starts a run for a due job. */
|
|
10
|
+
start?: (job: DueCronJob) => Promise<RunRecord>;
|
|
11
|
+
/** Where jobs are recorded. Defaults to memory; share it to schedule across replicas. */
|
|
12
|
+
state?: ServerStateStore;
|
|
13
|
+
/** How often due jobs are looked for, in milliseconds. Defaults to 30 seconds. */
|
|
14
|
+
tickMs?: number;
|
|
15
|
+
/** Receives errors from a firing, which never stop the scheduler. */
|
|
16
|
+
onError?: (error: unknown, job: CronRecord) => void;
|
|
17
|
+
/** Replaces the system clock, for tests. */
|
|
18
|
+
now?: () => Date;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Fires scheduled runs.
|
|
22
|
+
*
|
|
23
|
+
* Every replica ticks, and every firing is submitted with the idempotency key `<job>:<slot>`, so the
|
|
24
|
+
* operation store decides which replica's submission wins and the job runs once however many
|
|
25
|
+
* replicas are up. That is the whole coordination mechanism: no lock, no leader election, and a
|
|
26
|
+
* replica that misses a tick simply does not win that slot.
|
|
27
|
+
*/
|
|
28
|
+
export declare class CronScheduler {
|
|
29
|
+
private readonly options;
|
|
30
|
+
private readonly state;
|
|
31
|
+
private readonly now;
|
|
32
|
+
private timer;
|
|
33
|
+
private lastTick;
|
|
34
|
+
constructor(options?: CronSchedulerOptions);
|
|
35
|
+
/** Adds a job, validating its schedule. */
|
|
36
|
+
add(job: Omit<CronRecord, 'id' | 'createdAt'> & {
|
|
37
|
+
id?: string;
|
|
38
|
+
createdAt?: string;
|
|
39
|
+
}): Promise<CronRecord>;
|
|
40
|
+
/** Reads a job. */
|
|
41
|
+
get(id: string): Promise<CronRecord | undefined>;
|
|
42
|
+
/** Every job. */
|
|
43
|
+
list(): Promise<CronRecord[]>;
|
|
44
|
+
/** Removes a job. */
|
|
45
|
+
remove(id: string): Promise<void>;
|
|
46
|
+
/** Starts ticking. Ticks are skipped while one is still running. */
|
|
47
|
+
start(): void;
|
|
48
|
+
/** Stops ticking. */
|
|
49
|
+
stop(): void;
|
|
50
|
+
/**
|
|
51
|
+
* Fires every job due since the last tick. Call it directly to drive the scheduler from a test or
|
|
52
|
+
* from an external scheduler such as Kubernetes.
|
|
53
|
+
*/
|
|
54
|
+
tick(at?: Date): Promise<DueCronJob[]>;
|
|
55
|
+
}
|
|
56
|
+
interface CronFields {
|
|
57
|
+
minute: number[];
|
|
58
|
+
hour: number[];
|
|
59
|
+
day: number[];
|
|
60
|
+
month: number[];
|
|
61
|
+
weekday: number[];
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Parses the five-field cron syntax: `*`, numbers, `a-b` ranges, `a,b` lists, and `*/n` steps.
|
|
65
|
+
*
|
|
66
|
+
* Times are UTC, so a schedule means the same thing on every replica whatever its timezone is set to.
|
|
67
|
+
*/
|
|
68
|
+
export declare function parseCron(expression: string): CronFields;
|
|
69
|
+
export {};
|