nexus-ai-pro 1.22.0 → 1.24.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 -2
- package/CHANGELOG.md +96 -1
- package/README.md +33 -11
- package/dist/cli/deploy.d.ts +15 -0
- package/dist/cli/deploy.js +116 -0
- package/dist/cli.js +12 -1
- package/dist/context-hub/diff.d.ts +32 -0
- package/dist/context-hub/diff.js +64 -0
- package/dist/context-hub/hub.d.ts +268 -0
- package/dist/context-hub/hub.js +313 -0
- package/dist/context-hub/index.d.ts +46 -0
- package/dist/context-hub/index.js +55 -0
- package/dist/evaluate/cache.d.ts +70 -0
- package/dist/evaluate/cache.js +73 -0
- package/dist/evaluate/index.d.ts +1 -0
- package/dist/evaluate/index.js +1 -0
- package/dist/evaluate/run.d.ts +13 -0
- package/dist/evaluate/run.js +40 -11
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/insights/clusters.d.ts +98 -0
- package/dist/insights/clusters.js +153 -0
- package/dist/insights/index.d.ts +8 -0
- package/dist/insights/index.js +3 -0
- package/dist/insights/proposals.d.ts +229 -0
- package/dist/insights/proposals.js +241 -0
- package/dist/insights/regressions.d.ts +117 -0
- package/dist/insights/regressions.js +137 -0
- package/dist/operations/adapters.d.ts +5 -1
- package/dist/operations/adapters.js +11 -0
- package/dist/operations/errors.d.ts +18 -0
- package/dist/operations/errors.js +10 -0
- package/dist/operations/handle.d.ts +7 -0
- package/dist/operations/handle.js +6 -0
- package/dist/operations/index.d.ts +3 -3
- package/dist/operations/index.js +2 -2
- package/dist/operations/runner.d.ts +43 -1
- package/dist/operations/runner.js +102 -8
- package/dist/operations/stats.d.ts +19 -0
- package/dist/operations/stats.js +32 -0
- package/dist/operations/store.d.ts +6 -1
- package/dist/operations/store.js +17 -0
- package/dist/postgres/operations.d.ts +5 -1
- package/dist/postgres/operations.js +40 -0
- package/dist/prompts/evaluate.d.ts +3 -1
- package/dist/prompts/evaluate.js +4 -0
- package/dist/prompts/gates.d.ts +10 -1
- package/dist/prompts/gates.js +38 -37
- package/dist/security/index.d.ts +1 -0
- package/dist/security/index.js +1 -0
- package/dist/server/deployments.d.ts +257 -0
- package/dist/server/deployments.js +449 -0
- package/dist/server/errors.d.ts +20 -0
- package/dist/server/errors.js +18 -0
- package/dist/server/http.js +2 -1
- package/dist/server/index.d.ts +3 -3
- package/dist/server/index.js +2 -2
- package/dist/server/metrics.d.ts +24 -0
- package/dist/server/metrics.js +85 -0
- package/dist/server/runs.d.ts +107 -4
- package/dist/server/runs.js +410 -63
- package/dist/server/server.d.ts +37 -5
- package/dist/server/server.js +68 -5
- package/dist/server/state.d.ts +2 -0
- package/dist/server/state.js +1 -0
- package/dist/server/tenancy.d.ts +148 -0
- package/dist/server/tenancy.js +194 -0
- package/dist/sqlite/operations.d.ts +5 -1
- package/dist/sqlite/operations.js +38 -1
- package/dist/store/redis.d.ts +8 -4
- package/dist/types/evaluate.d.ts +10 -0
- package/dist/types/operations.d.ts +30 -0
- package/dist/types/server.d.ts +215 -0
- package/dist-cjs/cli/deploy.d.ts +15 -0
- package/dist-cjs/cli/deploy.js +119 -0
- package/dist-cjs/context-hub/diff.d.ts +32 -0
- package/dist-cjs/context-hub/diff.js +68 -0
- package/dist-cjs/context-hub/hub.d.ts +268 -0
- package/dist-cjs/context-hub/hub.js +323 -0
- package/dist-cjs/context-hub/index.d.ts +46 -0
- package/dist-cjs/context-hub/index.js +105 -0
- package/dist-cjs/evaluate/cache.d.ts +70 -0
- package/dist-cjs/evaluate/cache.js +113 -0
- package/dist-cjs/evaluate/index.d.ts +1 -0
- package/dist-cjs/evaluate/index.js +6 -1
- package/dist-cjs/evaluate/run.d.ts +13 -0
- package/dist-cjs/evaluate/run.js +40 -11
- package/dist-cjs/index.d.ts +2 -2
- package/dist-cjs/index.js +5 -4
- package/dist-cjs/insights/clusters.d.ts +98 -0
- package/dist-cjs/insights/clusters.js +159 -0
- package/dist-cjs/insights/index.d.ts +8 -0
- package/dist-cjs/insights/index.js +17 -0
- package/dist-cjs/insights/proposals.d.ts +229 -0
- package/dist-cjs/insights/proposals.js +282 -0
- package/dist-cjs/insights/regressions.d.ts +117 -0
- package/dist-cjs/insights/regressions.js +141 -0
- package/dist-cjs/operations/adapters.d.ts +5 -1
- package/dist-cjs/operations/adapters.js +11 -0
- package/dist-cjs/operations/errors.d.ts +18 -0
- package/dist-cjs/operations/errors.js +12 -1
- package/dist-cjs/operations/handle.d.ts +7 -0
- package/dist-cjs/operations/handle.js +6 -0
- package/dist-cjs/operations/index.d.ts +3 -3
- package/dist-cjs/operations/index.js +3 -1
- package/dist-cjs/operations/runner.d.ts +43 -1
- package/dist-cjs/operations/runner.js +101 -7
- package/dist-cjs/operations/stats.d.ts +19 -0
- package/dist-cjs/operations/stats.js +38 -0
- package/dist-cjs/operations/store.d.ts +6 -1
- package/dist-cjs/operations/store.js +19 -1
- package/dist-cjs/postgres/operations.d.ts +5 -1
- package/dist-cjs/postgres/operations.js +40 -0
- package/dist-cjs/prompts/evaluate.d.ts +3 -1
- package/dist-cjs/prompts/evaluate.js +4 -0
- package/dist-cjs/prompts/gates.d.ts +10 -1
- package/dist-cjs/prompts/gates.js +39 -37
- package/dist-cjs/security/index.d.ts +1 -0
- package/dist-cjs/security/index.js +4 -1
- package/dist-cjs/server/deployments.d.ts +257 -0
- package/dist-cjs/server/deployments.js +455 -0
- package/dist-cjs/server/errors.d.ts +20 -0
- package/dist-cjs/server/errors.js +20 -1
- package/dist-cjs/server/http.js +2 -1
- package/dist-cjs/server/index.d.ts +3 -3
- package/dist-cjs/server/index.js +2 -1
- package/dist-cjs/server/metrics.d.ts +24 -0
- package/dist-cjs/server/metrics.js +90 -0
- package/dist-cjs/server/runs.d.ts +107 -4
- package/dist-cjs/server/runs.js +416 -69
- package/dist-cjs/server/server.d.ts +37 -5
- package/dist-cjs/server/server.js +68 -5
- package/dist-cjs/server/state.d.ts +2 -0
- package/dist-cjs/server/state.js +2 -1
- package/dist-cjs/server/tenancy.d.ts +148 -0
- package/dist-cjs/server/tenancy.js +201 -0
- package/dist-cjs/sqlite/operations.d.ts +5 -1
- package/dist-cjs/sqlite/operations.js +37 -0
- package/dist-cjs/store/redis.d.ts +8 -4
- package/dist-cjs/types/evaluate.d.ts +10 -0
- package/dist-cjs/types/operations.d.ts +30 -0
- package/dist-cjs/types/server.d.ts +215 -0
- package/package.json +78 -5
package/API_STABILITY.md
CHANGED
|
@@ -437,8 +437,9 @@ from the root.
|
|
|
437
437
|
`ThreadRecord`, and `RunEvent` follow the 1.x rules. A minor release may add routes and fields.
|
|
438
438
|
- Event ids are per run and strictly increasing, which is what `Last-Event-ID` depends on. A client
|
|
439
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
|
|
441
|
-
scopes,
|
|
440
|
+
- The server is not a security boundary on its own: authentication, and authorization beyond tenancy
|
|
441
|
+
and scopes, are the application's, through the hooks. Per-tenant limits are opt-in, through
|
|
442
|
+
`tenants`.
|
|
442
443
|
- Recovery repeats the step a run died in for graphs and workflows served by `graphAssistant()`, and
|
|
443
444
|
the whole run for any other assistant, so the repeated part should be idempotent or the run left at a
|
|
444
445
|
single attempt.
|
|
@@ -456,6 +457,32 @@ with nexus-ai-pro and declares the core versions it works with as a peer depende
|
|
|
456
457
|
- The access rules are a guarantee: loopback binding by default, a token on every request, the token
|
|
457
458
|
in a header for every change, and loopback hosts only unless others are allowed.
|
|
458
459
|
|
|
460
|
+
## Deployment stage (1.24.0)
|
|
461
|
+
|
|
462
|
+
Deployments (`nexus-ai-pro/server/deployments`), tenant limits (`nexus-ai-pro/server/tenancy`), and the
|
|
463
|
+
server's worker queue are experimental in production readiness. Their contracts follow the 1.x rules.
|
|
464
|
+
|
|
465
|
+
- `bucket()` is stable across 1.x, so a thread keeps its place in a traffic split through an upgrade.
|
|
466
|
+
- The stored `DeploymentRecord` and `ReplicaReport` shapes may gain fields but not lose them, so
|
|
467
|
+
replicas on different minor releases share one store.
|
|
468
|
+
- The metric names on `/metrics` and the fields of `/scaling` follow the 1.x rules; a minor release may
|
|
469
|
+
add metrics and fields. Autoscaler configurations that read them keep working.
|
|
470
|
+
- `drain()` hands off only runs it could not finish; a hand-off never counts against the retry budget.
|
|
471
|
+
- The files under `deploy/` are templates in the repository, not part of the package, and may change in
|
|
472
|
+
any release.
|
|
473
|
+
|
|
474
|
+
## Team platform stage (1.23.0)
|
|
475
|
+
|
|
476
|
+
The context hub (`nexus-ai-pro/context-hub`), insights (`nexus-ai-pro/insights`), and the studio's
|
|
477
|
+
accounts are experimental in production readiness. Their contracts follow the 1.x rules.
|
|
478
|
+
|
|
479
|
+
- A bundle's content version covers its name, description, prompts, instructions, tools, skills, and
|
|
480
|
+
config, and is stable across 1.x, so the same content is the same version in every release.
|
|
481
|
+
- The export format is `nexus-context-bundle`, format version 1; a later release reads it.
|
|
482
|
+
- A cached evaluation output is keyed by the fingerprint, the example's id and inputs, and the
|
|
483
|
+
repetition. The key's derivation is stable across 1.x, so a cache survives an upgrade.
|
|
484
|
+
- The studio's roles keep their order and meaning; new actions are assigned to an existing role.
|
|
485
|
+
|
|
459
486
|
## Retrieval breadth stage (1.22.0)
|
|
460
487
|
|
|
461
488
|
The loaders (`nexus-ai-pro/loaders/*`), the Redis, Pinecone, Weaviate, Chroma, and SQLite vector
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,99 @@ Notable changes to this project are recorded here. The format follows [Keep a Ch
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [1.24.0] - 2026-09-30
|
|
8
|
+
|
|
9
|
+
Deployment at scale, self-managed. Running assistants as a service without a hosted platform:
|
|
10
|
+
revisions and canaries, a worker pool that scales on its queue, and limits per tenant.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Revisions and canaries** on `nexus-ai-pro/server/deployments`. `Deployments.assistant()` serves
|
|
15
|
+
several revisions of an assistant from one image; the traffic split is recorded in the server's
|
|
16
|
+
state store and changed with `canary()`, `split()`, `promote()`, and `rollback()`, each kept in a
|
|
17
|
+
history with who and why, and guarded by an expected version. New threads and stateless runs are
|
|
18
|
+
split by a stable hash, a thread keeps its revision while that revision takes traffic, and every
|
|
19
|
+
run records its revision, share, reason, and the deployment version.
|
|
20
|
+
- **A canary guard**: `watchCanaries()` compares each canary with the live revision over the same
|
|
21
|
+
window, rolls it back when its error rate, latency, or cost regressed, and moves one that holds up
|
|
22
|
+
through its `steps` to promotion. A latency rise must be at least `minLatencyChangeMs`, 50 ms by
|
|
23
|
+
default, so noise on fast runs never rolls a canary back. `compareRuns()` on `nexus-ai-pro/insights`
|
|
24
|
+
is the comparison, lifted out of `detectRegressions()`.
|
|
25
|
+
- **A worker queue** in the agent server: with `queue`, a replica runs at most `concurrency` runs and
|
|
26
|
+
leaves the rest queued for the next free worker; `claim: false` makes an API replica. Replicas take
|
|
27
|
+
over lapsed runs every `recoverEveryMs`. `drain()` answers `/health` with `503`, stops claiming, and
|
|
28
|
+
hands runs still going after its timeout to another worker without using a retry.
|
|
29
|
+
- **Scaling signals**: `GET /scaling` as JSON and `GET /metrics` in Prometheus text — queued and running
|
|
30
|
+
runs across the deployment, their load, the oldest wait, lapsed leases, each replica's capacity, and
|
|
31
|
+
run counts and latency per revision. `metrics: { public: true }` serves them to in-cluster scrapers.
|
|
32
|
+
- **Kubernetes and Helm**: `deploy/app` is one image run as an API, a worker, or both, configured from
|
|
33
|
+
the environment; `deploy/kubernetes` has the manifests with a KEDA `ScaledObject` and an HPA
|
|
34
|
+
alternative; `deploy/helm/agent-server` is the chart. The clean-install test runs the template against
|
|
35
|
+
the packed package.
|
|
36
|
+
- **Tenant limits** on `nexus-ai-pro/server/tenancy`: `tenantLimits()` holds each tenant to its active
|
|
37
|
+
runs, its runs per window, and its spending per period, answered with `429` and `Retry-After`.
|
|
38
|
+
`MemoryTenantUsage`, and `RedisTenantUsage` with atomic scripts so replicas share one set of limits.
|
|
39
|
+
Assistants record spending with `context.recordCost()`; `GET /usage` reports it.
|
|
40
|
+
- **Replica health**: with `deployments`, each replica heartbeats its runs, capacity, drain state, and
|
|
41
|
+
revisions, readable from `GET /replicas`.
|
|
42
|
+
- **A deployments view** in the studio: traffic splits and history, each revision's runs, error rate,
|
|
43
|
+
and latency since the last change, replicas, the queue, and tenant usage, with canary, promote, and
|
|
44
|
+
rollback for admins.
|
|
45
|
+
- **`nexus deploy`**: `status`, `canary`, `promote`, and `rollback` against a running server, for the
|
|
46
|
+
pipeline that rolls out an image.
|
|
47
|
+
- Operation queues: `OperationRunner.enqueue()`, `claimQueued()`, and `release()`; `listQueued()` and
|
|
48
|
+
`stats()` on every operation store, and `operationStats()`.
|
|
49
|
+
- Run records gain `revision`, `startedAt`, `finishedAt`, `durationMs`, `cost`, `worker`, and `attempt`.
|
|
50
|
+
|
|
51
|
+
### Changed
|
|
52
|
+
|
|
53
|
+
- `GET /health` is answered without authentication, so a load balancer's or Kubernetes' probe reaches
|
|
54
|
+
it.
|
|
55
|
+
- A worker never takes over an operation whose lease another worker still holds.
|
|
56
|
+
- `RedisStoreLikeClient` declares `set()` with its expiry and `del()` with one key, the shapes an
|
|
57
|
+
`ioredis` client satisfies as it is.
|
|
58
|
+
- The last nine guides are rewritten for readability.
|
|
59
|
+
- `nexus-ai-pro-studio` requires nexus-ai-pro 1.24 or newer.
|
|
60
|
+
|
|
61
|
+
### Fixed
|
|
62
|
+
|
|
63
|
+
- The deployment templates run. The Dockerfile started a file the build never produced, and the
|
|
64
|
+
example server ignored `REDIS_URL`, so its replicas shared nothing.
|
|
65
|
+
|
|
66
|
+
## [1.23.0] - 2026-09-30
|
|
67
|
+
|
|
68
|
+
The team engineering platform. The studio becomes something a team shares, and it starts finding
|
|
69
|
+
problems on its own.
|
|
70
|
+
|
|
71
|
+
### Added
|
|
72
|
+
|
|
73
|
+
- **A shared studio.** Accounts through an authenticator — personal links, identity headers from a
|
|
74
|
+
signing-in proxy, or bearer tokens verified by your function, such as OIDC — with four roles
|
|
75
|
+
(viewer, reviewer, editor, admin), an audit log of every change and every refused attempt, comments
|
|
76
|
+
on runs, review items, proposals, threads, and bundles, per-person page tokens for changes, and
|
|
77
|
+
`Secure` cookies behind HTTPS. Local, token-only use stays the default. The CLI gains `--users` and
|
|
78
|
+
`--journal`, and a config module may export `options`.
|
|
79
|
+
- **The context hub** on `nexus-ai-pro/context-hub`: prompts, instructions, tool sets, skills, and
|
|
80
|
+
settings versioned together as bundles, committed by content, labelled, promoted through evaluation
|
|
81
|
+
gates, diffed, rolled back, and exported with their prompts to move between projects. Bundles live in
|
|
82
|
+
any prompt store. The studio shows them.
|
|
83
|
+
- **Insights** on `nexus-ai-pro/insights`: failing and slow runs clustered by error, trajectory, or
|
|
84
|
+
meaning; regressions between two time windows; and, opt-in, a proposed fix — a candidate prompt or
|
|
85
|
+
bundle version — evaluated against the dataset before it waits in the studio's inbox for a person
|
|
86
|
+
to promote, with an optional pull request through an injected client.
|
|
87
|
+
- **Evaluation caching**: `evaluate()` reuses a target's outputs when the example and the target's
|
|
88
|
+
fingerprint are unchanged, so a comparison re-runs only what changed; evaluators always run again.
|
|
89
|
+
`MemoryEvaluationCache`, `FileEvaluationCache`, and `fingerprintOf()` on `nexus-ai-pro/evaluate`;
|
|
90
|
+
`evaluatePrompt()` fingerprints the prompt version itself.
|
|
91
|
+
|
|
92
|
+
### Changed
|
|
93
|
+
|
|
94
|
+
- The guides are rewritten for readability: each says what its feature is for before its options,
|
|
95
|
+
options and fields are in tables, and long sentences are split. Every export is still explained.
|
|
96
|
+
- `nexus-ai-pro-studio` requires nexus-ai-pro 1.23 or newer.
|
|
97
|
+
- The studio's development type paths map each entry point to the file it publishes, so studio tests
|
|
98
|
+
exercise what the package ships.
|
|
99
|
+
|
|
7
100
|
## [1.22.0] - 2026-09-29
|
|
8
101
|
|
|
9
102
|
Retrieval and integration breadth. Getting documents in, finding the right ones, and reaching the
|
|
@@ -1118,7 +1211,9 @@ shared policy vocabulary is in place for a future release that revisits this.
|
|
|
1118
1211
|
- URL fetching rejects unsafe private-network targets and limits response reads.
|
|
1119
1212
|
- CLI and audit findings no longer disclose detected secret values.
|
|
1120
1213
|
|
|
1121
|
-
[Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.
|
|
1214
|
+
[Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.24.0...HEAD
|
|
1215
|
+
[1.24.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.23.0...v1.24.0
|
|
1216
|
+
[1.23.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.22.0...v1.23.0
|
|
1122
1217
|
[1.22.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.21.0...v1.22.0
|
|
1123
1218
|
[1.21.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.20.0...v1.21.0
|
|
1124
1219
|
[1.20.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.19.0...v1.20.0
|
package/README.md
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# nexus-ai-pro
|
|
2
2
|
|
|
3
|
-
The typed AI framework for TypeScript. One API for provider routing and failover, agent graphs
|
|
4
|
-
run branches in parallel and survive a restart,
|
|
5
|
-
control, images, voice, and evals
|
|
3
|
+
The typed AI framework for TypeScript. One API for provider routing and failover, agent graphs and
|
|
4
|
+
workflows that run branches in parallel and survive a restart, retrieval over any vector store, MCP,
|
|
5
|
+
durable background operations, guardrails, cost control, images, voice, and evals — with a studio a
|
|
6
|
+
team can share.
|
|
6
7
|
|
|
7
8
|
Import the whole runtime, or one piece: every capability has its own entry point with a size budget
|
|
8
9
|
CI enforces, and [the packaging guide](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/packaging.md) publishes what each one costs. A graph-only application loads 49 KB
|
|
@@ -90,6 +91,16 @@ You can still pass a plain `NexusAIConfig` to `new NexusAI(...)` when you want f
|
|
|
90
91
|
- embed text through the same routing, caching, batching, budget, retry, and metrics as a completion
|
|
91
92
|
- run long operations that survive a restart, with leases, retries, dead-lettering, and signed webhooks
|
|
92
93
|
- build stateful graphs with cycles, fan-out, subgraphs, human approval, and resumable checkpoints
|
|
94
|
+
- make ordinary async functions durable step by step, and recover a crashed run at the step it died on
|
|
95
|
+
- load files, web pages, sitemaps, and Git repositories into retrieval, over memory, Postgres, SQLite,
|
|
96
|
+
Redis, Qdrant, Pinecone, Weaviate, or Chroma
|
|
97
|
+
- retrieve with hybrid keyword and vector search, reranking, and diversity, and measure it with evals
|
|
98
|
+
- reach tools from many MCP servers under one configuration, with allowlists and credentials from the
|
|
99
|
+
environment
|
|
100
|
+
- version prompts, instructions, tools, and skills together, and promote them through evaluation gates
|
|
101
|
+
- find failing and slow runs, detect regressions, and review evaluated fix proposals in a shared studio
|
|
102
|
+
- serve assistants as revisions, with canaries that roll back on a regression, workers that scale on
|
|
103
|
+
the queue under Kubernetes, and per-tenant quotas, rate limits, and budgets
|
|
93
104
|
- trip routing away from a failing provider, and share one rate-limit budget across workers
|
|
94
105
|
- reach the providers' half-price asynchronous batch tier behind one operation handle
|
|
95
106
|
- persist generated media to disk or S3 with tenant isolation, retention, and checksums
|
|
@@ -112,6 +123,8 @@ from the doc comments. The guides live in the repository, so these links go to G
|
|
|
112
123
|
| [MCP](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/mcp.md) | Borrowing tools from MCP servers, a registry of many, and serving your own |
|
|
113
124
|
| [Traces](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/tracing.md) | Run trees, queries, feedback, comparison, and alerts |
|
|
114
125
|
| [Evaluation](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/evaluation.md) | Datasets, evaluators, experiments, comparisons with a verdict, review queues, LLM judges |
|
|
126
|
+
| [Context hub](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/context-hub.md) | Prompts, instructions, tools, and skills versioned together, promoted through gates, and moved between projects |
|
|
127
|
+
| [Insights](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/insights.md) | Failing and slow runs clustered, regressions between time windows, and evaluated fix proposals |
|
|
115
128
|
| [Prompts](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/prompts.md) | Typed templates, content versions, gated promotion, rollback, A/B splits, serving through outages |
|
|
116
129
|
| [Postgres](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/postgres.md) | One adapter family for operations, memory, traces, evaluation, circuits, and prompts |
|
|
117
130
|
| [SQLite](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/sqlite.md) | Durable operations, checkpoints, and memory on one machine, over any SQLite driver |
|
|
@@ -128,8 +141,9 @@ from the doc comments. The guides live in the repository, so these links go to G
|
|
|
128
141
|
| [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 |
|
|
129
142
|
| [Telephony](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/telephony.md) | Calls, webhooks, phone numbers, and phone agents on realtime sessions |
|
|
130
143
|
| [Testing](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/testing.md) | Recording and replaying provider traffic, and conformance suites |
|
|
131
|
-
| [Studio](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/studio.md) | A
|
|
132
|
-
| [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 |
|
|
144
|
+
| [Studio](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/studio.md) | A shared UI for traces, threads, approvals, experiments, prompts, deployments, costs, and health, as a separate package |
|
|
145
|
+
| [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, a worker queue, and metrics |
|
|
146
|
+
| [Deployments](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/deployments.md) | Revisions and canaries with automatic rollback, autoscaling on Kubernetes and Helm, and per-tenant limits |
|
|
133
147
|
| [Workflows](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/workflows.md) | Ready-made chains and domain workflows |
|
|
134
148
|
| [Command line](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/cli.md) | The `nexus` command |
|
|
135
149
|
| [Packaging and install weight](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/packaging.md) | Every entry point and what it costs |
|
|
@@ -179,10 +193,11 @@ interface, or enum, has no doc comment, and `npm run docs:guides` fails when a g
|
|
|
179
193
|
every export of the entry points it covers, or its generated reference is stale. Both hold at 100%;
|
|
180
194
|
`--list` names what is missing, and `npm run docs:update` regenerates the references.
|
|
181
195
|
|
|
182
|
-
Real provider conformance
|
|
196
|
+
Real provider conformance, and the vector store contract against real servers, are opt-in:
|
|
183
197
|
|
|
184
198
|
```bash
|
|
185
199
|
npm run test:conformance:real
|
|
200
|
+
npm run test:vectors:live
|
|
186
201
|
```
|
|
187
202
|
|
|
188
203
|
## Roadmap
|
|
@@ -201,11 +216,18 @@ Shipped so far:
|
|
|
201
216
|
- record and replay of provider traffic;
|
|
202
217
|
- prompt versioning: typed templates, content versions, gated promotion, and serving through outages;
|
|
203
218
|
- a self-hosted agent server, and a local studio as a separate package;
|
|
204
|
-
- per-entry-point size budgets
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
219
|
+
- per-entry-point size budgets;
|
|
220
|
+
- vector stores behind one contract, and deprecation warnings at run time;
|
|
221
|
+
- durable functional workflows, step-level recovery in the server, SQLite persistence, and diagrams
|
|
222
|
+
as images;
|
|
223
|
+
- document loaders, five more vector stores, hybrid retrieval with reranking, and an MCP registry;
|
|
224
|
+
- a shared studio with accounts, roles, an audit log, and comments; a context hub; insights with
|
|
225
|
+
evaluated fix proposals; and evaluation caching;
|
|
226
|
+
- self-managed deployment at scale: revisions and canaries with automatic rollback, a worker queue
|
|
227
|
+
that autoscales on Kubernetes, and per-tenant limits.
|
|
228
|
+
|
|
229
|
+
Next: 2.0.0 consolidates. The image family leaves experimental once recorded live
|
|
230
|
+
conformance passes on all three backends.
|
|
209
231
|
|
|
210
232
|
## Before Production
|
|
211
233
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type ParsedArgs } from './args.js';
|
|
2
|
+
/**
|
|
3
|
+
* `nexus deploy` reads and changes an agent server's deployments over HTTP, for a pipeline that rolls
|
|
4
|
+
* out a new image and then moves traffic onto it:
|
|
5
|
+
*
|
|
6
|
+
* nexus deploy status [assistant]
|
|
7
|
+
* nexus deploy canary <assistant> <revision> <percent>
|
|
8
|
+
* nexus deploy promote <assistant> <revision>
|
|
9
|
+
* nexus deploy rollback <assistant> [--to <revision>]
|
|
10
|
+
*
|
|
11
|
+
* `--url` (or `NEXUS_SERVER_URL`) names the server, and `--token` (or `NEXUS_SERVER_TOKEN`) is sent
|
|
12
|
+
* as a bearer token. Changes take `--reason`, recorded in the deployment's history, and `--version`,
|
|
13
|
+
* which refuses the change if someone else moved the deployment first.
|
|
14
|
+
*/
|
|
15
|
+
export declare function runDeployCommand(subcommand: string | undefined, { positionals, flags }: ParsedArgs): Promise<void>;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { CliUsageError, numberFlag, printTable, stringFlag, writeJson } from './args.js';
|
|
2
|
+
export async function runDeployCommand(subcommand, { positionals, flags }) {
|
|
3
|
+
const server = serverOf(flags);
|
|
4
|
+
const reason = stringFlag(flags, 'reason');
|
|
5
|
+
const expectedVersion = numberFlag(flags, 'version');
|
|
6
|
+
const change = async (assistant, body) => {
|
|
7
|
+
if (!assistant)
|
|
8
|
+
throw new CliUsageError(`nexus deploy ${subcommand} needs an assistant.`);
|
|
9
|
+
const deployment = await server.request('POST', `/deployments/${encodeURIComponent(assistant)}`, {
|
|
10
|
+
...body,
|
|
11
|
+
...(reason ? { reason } : {}),
|
|
12
|
+
...(expectedVersion === undefined ? {} : { expectedVersion }),
|
|
13
|
+
});
|
|
14
|
+
if (flags.json === true)
|
|
15
|
+
writeJson(deployment);
|
|
16
|
+
else
|
|
17
|
+
console.log(`${assistant}: version ${deployment.version}, live ${deployment.live}, ${splitText(deployment.traffic)}`);
|
|
18
|
+
};
|
|
19
|
+
switch (subcommand) {
|
|
20
|
+
case 'status': {
|
|
21
|
+
const [assistant] = positionals;
|
|
22
|
+
if (assistant) {
|
|
23
|
+
const deployment = await server.request('GET', `/deployments/${encodeURIComponent(assistant)}`);
|
|
24
|
+
if (flags.json === true)
|
|
25
|
+
return writeJson(deployment);
|
|
26
|
+
console.log(`${deployment.assistant}: version ${deployment.version}, live ${deployment.live}`);
|
|
27
|
+
console.log(`split: ${splitText(deployment.traffic)}`);
|
|
28
|
+
if (deployment.stats?.length) {
|
|
29
|
+
console.log('');
|
|
30
|
+
printTable(deployment.stats.map((item) => ({
|
|
31
|
+
revision: item.revision || '(none)',
|
|
32
|
+
runs: String(item.runs),
|
|
33
|
+
failed: String(item.failed),
|
|
34
|
+
'error rate': `${(item.errorRate * 100).toFixed(1)}%`,
|
|
35
|
+
p95: item.p95Ms === undefined ? '' : `${Math.round(item.p95Ms)}ms`,
|
|
36
|
+
})), ['revision', 'runs', 'failed', 'error rate', 'p95']);
|
|
37
|
+
}
|
|
38
|
+
if (deployment.history.length) {
|
|
39
|
+
console.log('');
|
|
40
|
+
printTable(deployment.history.slice(0, 10).map((entry) => ({
|
|
41
|
+
version: String(entry.version),
|
|
42
|
+
at: entry.at,
|
|
43
|
+
action: entry.action,
|
|
44
|
+
split: splitText(entry.traffic),
|
|
45
|
+
by: entry.by ?? '',
|
|
46
|
+
reason: entry.reason ?? '',
|
|
47
|
+
})), ['version', 'at', 'action', 'split', 'by', 'reason']);
|
|
48
|
+
}
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const { deployments } = await server.request('GET', '/deployments');
|
|
52
|
+
if (flags.json === true)
|
|
53
|
+
return writeJson(deployments);
|
|
54
|
+
printTable(deployments.map((deployment) => ({
|
|
55
|
+
assistant: deployment.assistant,
|
|
56
|
+
live: deployment.live,
|
|
57
|
+
split: splitText(deployment.traffic),
|
|
58
|
+
version: String(deployment.version),
|
|
59
|
+
updated: deployment.version ? deployment.updatedAt : 'never',
|
|
60
|
+
})), ['assistant', 'live', 'split', 'version', 'updated']);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
case 'canary': {
|
|
64
|
+
const [assistant, revision, percent] = positionals;
|
|
65
|
+
const share = Number(percent);
|
|
66
|
+
if (!revision || !Number.isFinite(share) || share < 0 || share > 100) {
|
|
67
|
+
throw new CliUsageError('Usage: nexus deploy canary <assistant> <revision> <percent from 0 to 100>');
|
|
68
|
+
}
|
|
69
|
+
return change(assistant, { action: 'canary', revision, weight: share / 100 });
|
|
70
|
+
}
|
|
71
|
+
case 'promote': {
|
|
72
|
+
const [assistant, revision] = positionals;
|
|
73
|
+
if (!revision)
|
|
74
|
+
throw new CliUsageError('Usage: nexus deploy promote <assistant> <revision>');
|
|
75
|
+
return change(assistant, { action: 'promote', revision });
|
|
76
|
+
}
|
|
77
|
+
case 'rollback': {
|
|
78
|
+
const [assistant] = positionals;
|
|
79
|
+
const to = stringFlag(flags, 'to');
|
|
80
|
+
return change(assistant, { action: 'rollback', ...(to ? { to } : {}) });
|
|
81
|
+
}
|
|
82
|
+
default:
|
|
83
|
+
throw new CliUsageError(`Unknown deploy command "${subcommand ?? ''}". Use status, canary, promote, or rollback.`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function serverOf(flags) {
|
|
87
|
+
const url = stringFlag(flags, 'url') ?? process.env.NEXUS_SERVER_URL;
|
|
88
|
+
if (!url)
|
|
89
|
+
throw new CliUsageError('nexus deploy needs the server: pass --url or set NEXUS_SERVER_URL.');
|
|
90
|
+
const token = stringFlag(flags, 'token') ?? process.env.NEXUS_SERVER_TOKEN;
|
|
91
|
+
const base = url.replace(/\/+$/, '');
|
|
92
|
+
return {
|
|
93
|
+
async request(method, route, body) {
|
|
94
|
+
const response = await fetch(`${base}${route}`, {
|
|
95
|
+
method,
|
|
96
|
+
headers: {
|
|
97
|
+
...(token ? { authorization: `Bearer ${token}` } : {}),
|
|
98
|
+
...(body === undefined ? {} : { 'content-type': 'application/json' }),
|
|
99
|
+
},
|
|
100
|
+
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
|
101
|
+
});
|
|
102
|
+
const text = await response.text();
|
|
103
|
+
const parsed = text ? JSON.parse(text) : {};
|
|
104
|
+
if (!response.ok) {
|
|
105
|
+
const error = parsed.error;
|
|
106
|
+
throw new Error(`${response.status} ${error?.code ?? response.statusText}: ${error?.message ?? text}`);
|
|
107
|
+
}
|
|
108
|
+
return parsed;
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function splitText(traffic) {
|
|
113
|
+
return Object.entries(traffic)
|
|
114
|
+
.map(([revision, weight]) => `${revision} ${Math.round(weight * 1000) / 10}%`)
|
|
115
|
+
.join(', ');
|
|
116
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -49,6 +49,12 @@ async function main() {
|
|
|
49
49
|
await runDbCommand(subcommand, { positionals: args, flags: parsed.flags });
|
|
50
50
|
return;
|
|
51
51
|
}
|
|
52
|
+
case 'deploy': {
|
|
53
|
+
const [subcommand, ...args] = parsed.positionals;
|
|
54
|
+
const { runDeployCommand } = await import('./cli/deploy.js');
|
|
55
|
+
await runDeployCommand(subcommand, { positionals: args, flags: parsed.flags });
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
52
58
|
case 'optimize':
|
|
53
59
|
await runOptimize(parsed);
|
|
54
60
|
return;
|
|
@@ -390,12 +396,17 @@ Usage:
|
|
|
390
396
|
nexus traces show <traceId> --store <runs.jsonl|store.mjs> [--json]
|
|
391
397
|
nexus traces export --store <runs.jsonl|store.mjs> [--out runs.jsonl] [same filters as list]
|
|
392
398
|
nexus db sql [--adapters operations,store,traces,evaluation,circuits,prompts] [--vector-dimensions 1536]
|
|
399
|
+
nexus deploy status [assistant] --url <server> [--token t] [--json]
|
|
400
|
+
nexus deploy canary <assistant> <revision> <percent> --url <server> [--reason r] [--version n]
|
|
401
|
+
nexus deploy promote <assistant> <revision> --url <server> [--reason r] [--version n]
|
|
402
|
+
nexus deploy rollback <assistant> [--to revision] --url <server> [--reason r] [--version n]
|
|
393
403
|
nexus optimize [request.json|prompt.txt] [--model gpt-5-mini] [--max-input-tokens 4000] [--densify] [--json]
|
|
394
404
|
|
|
395
405
|
Eval files can export { cases, client } or { cases, config }. JSON cases may use "expected", "contains", and "match": "includes" | "exact" | "regex".
|
|
396
406
|
Evaluation modules for "eval run" export { target, dataset, evaluators, summary?, options? }.
|
|
397
407
|
"eval gate" exits 1 when a metric got worse beyond noise, a new failure appeared, or the datasets differ.
|
|
398
|
-
Trace store modules export any TraceStore as { store }, such as a PostgresTraceStore over the application's own pool
|
|
408
|
+
Trace store modules export any TraceStore as { store }, such as a PostgresTraceStore over the application's own pool.
|
|
409
|
+
"deploy" reads NEXUS_SERVER_URL and NEXUS_SERVER_TOKEN when --url and --token are not given.`);
|
|
399
410
|
}
|
|
400
411
|
main().catch((error) => {
|
|
401
412
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type PromptLineChange } from '../prompts/diff.js';
|
|
2
|
+
import type { ContextBundle } from './hub.js';
|
|
3
|
+
/** One entry that differs between two bundle versions. */
|
|
4
|
+
export interface ContextChange {
|
|
5
|
+
/** The part of the bundle. */
|
|
6
|
+
section: 'description' | 'prompts' | 'instructions' | 'tools' | 'skills' | 'config';
|
|
7
|
+
/** The entry within it: a prompt role, an instruction or skill name, a tool name, or a config key. */
|
|
8
|
+
key: string;
|
|
9
|
+
/** Whether the entry was added, removed, or changed. */
|
|
10
|
+
change: 'added' | 'removed' | 'changed';
|
|
11
|
+
/** Line by line, for text: an instruction, a skill's instructions, or the description. */
|
|
12
|
+
lines?: PromptLineChange[];
|
|
13
|
+
/** The entry before, for anything that is not text. */
|
|
14
|
+
before?: unknown;
|
|
15
|
+
/** The entry after, for anything that is not text. */
|
|
16
|
+
after?: unknown;
|
|
17
|
+
}
|
|
18
|
+
/** What changed between two versions of a bundle. */
|
|
19
|
+
export interface ContextDiff {
|
|
20
|
+
/** The bundle. */
|
|
21
|
+
name: string;
|
|
22
|
+
/** The earlier version. */
|
|
23
|
+
from: string;
|
|
24
|
+
/** The later version. */
|
|
25
|
+
to: string;
|
|
26
|
+
/** Every entry that differs, section by section. */
|
|
27
|
+
changes: ContextChange[];
|
|
28
|
+
}
|
|
29
|
+
/** Compares two versions of a bundle, entry by entry, with line diffs for text. */
|
|
30
|
+
export declare function diffContexts(from: ContextBundle, to: ContextBundle): ContextDiff;
|
|
31
|
+
/** A diff as text, one line per change and `+`/`-` lines for text, for a terminal or a pull request. */
|
|
32
|
+
export declare function formatContextDiff(diff: ContextDiff): string;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { diffLines } from '../prompts/diff.js';
|
|
2
|
+
import { canonicalJson } from '../prompts/version.js';
|
|
3
|
+
export function diffContexts(from, to) {
|
|
4
|
+
const changes = [];
|
|
5
|
+
if ((from.description ?? '') !== (to.description ?? '')) {
|
|
6
|
+
changes.push({
|
|
7
|
+
section: 'description',
|
|
8
|
+
key: 'description',
|
|
9
|
+
change: from.description === undefined ? 'added' : to.description === undefined ? 'removed' : 'changed',
|
|
10
|
+
lines: diffLines(from.description ?? '', to.description ?? ''),
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
compare(changes, 'prompts', from.prompts, to.prompts);
|
|
14
|
+
compare(changes, 'instructions', from.instructions, to.instructions, (value) => value);
|
|
15
|
+
compare(changes, 'tools', Object.fromEntries((from.tools ?? []).map((tool) => [tool.name, tool])), Object.fromEntries((to.tools ?? []).map((tool) => [tool.name, tool])));
|
|
16
|
+
compare(changes, 'skills', from.skills, to.skills, (value) => {
|
|
17
|
+
const skill = value;
|
|
18
|
+
return `${skill.description}\n\n${skill.instructions}${Object.entries(skill.resources ?? {})
|
|
19
|
+
.map(([name, text]) => `\n\n[${name}]\n${text}`)
|
|
20
|
+
.join('')}`;
|
|
21
|
+
});
|
|
22
|
+
compare(changes, 'config', from.config, to.config);
|
|
23
|
+
return { name: to.name, from: from.version, to: to.version, changes };
|
|
24
|
+
}
|
|
25
|
+
export function formatContextDiff(diff) {
|
|
26
|
+
const lines = [`${diff.name}: ${diff.from} -> ${diff.to}`];
|
|
27
|
+
if (diff.changes.length === 0)
|
|
28
|
+
lines.push(' no changes');
|
|
29
|
+
for (const change of diff.changes) {
|
|
30
|
+
lines.push(` ${change.change} ${change.section}.${change.key}`);
|
|
31
|
+
for (const line of change.lines ?? []) {
|
|
32
|
+
if (line.op !== '=')
|
|
33
|
+
lines.push(` ${line.op} ${line.text}`);
|
|
34
|
+
}
|
|
35
|
+
if (!change.lines && change.change === 'changed') {
|
|
36
|
+
lines.push(` - ${canonicalJson(change.before)}`, ` + ${canonicalJson(change.after)}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return lines.join('\n');
|
|
40
|
+
}
|
|
41
|
+
function compare(changes, section, before, after, asText) {
|
|
42
|
+
const keys = [...new Set([...Object.keys(before ?? {}), ...Object.keys(after ?? {})])].sort();
|
|
43
|
+
for (const key of keys) {
|
|
44
|
+
const was = before?.[key];
|
|
45
|
+
const is = after?.[key];
|
|
46
|
+
if (was !== undefined && is !== undefined && canonicalJson(was) === canonicalJson(is))
|
|
47
|
+
continue;
|
|
48
|
+
const change = was === undefined ? 'added' : is === undefined ? 'removed' : 'changed';
|
|
49
|
+
changes.push(asText
|
|
50
|
+
? {
|
|
51
|
+
section,
|
|
52
|
+
key,
|
|
53
|
+
change,
|
|
54
|
+
lines: diffLines(was === undefined ? '' : asText(was), is === undefined ? '' : asText(is)),
|
|
55
|
+
}
|
|
56
|
+
: {
|
|
57
|
+
section,
|
|
58
|
+
key,
|
|
59
|
+
change,
|
|
60
|
+
...(was === undefined ? {} : { before: was }),
|
|
61
|
+
...(is === undefined ? {} : { after: is }),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|