nexus-ai-pro 1.5.0 → 1.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.
- package/API_STABILITY.md +94 -0
- package/CHANGELOG.md +134 -1
- package/README.md +319 -3
- package/SECURITY.md +1 -1
- package/dist/batch/anthropic.d.ts +26 -0
- package/dist/batch/anthropic.js +179 -0
- package/dist/batch/errors.d.ts +24 -0
- package/dist/batch/errors.js +46 -0
- package/dist/batch/index.d.ts +7 -0
- package/dist/batch/index.js +6 -0
- package/dist/batch/manager.d.ts +59 -0
- package/dist/batch/manager.js +275 -0
- package/dist/batch/mock.d.ts +41 -0
- package/dist/batch/mock.js +93 -0
- package/dist/batch/openai.d.ts +31 -0
- package/dist/batch/openai.js +169 -0
- package/dist/core/nexus.d.ts +5 -0
- package/dist/core/nexus.js +56 -14
- package/dist/embeddings/manager.js +6 -1
- package/dist/images/asset-stores.d.ts +127 -0
- package/dist/images/asset-stores.js +377 -0
- package/dist/images/asset-support.d.ts +107 -0
- package/dist/images/asset-support.js +166 -0
- package/dist/images/assets.d.ts +5 -85
- package/dist/images/assets.js +7 -166
- package/dist/images/index.d.ts +1 -0
- package/dist/images/index.js +1 -0
- package/dist/images/manager.d.ts +4 -1
- package/dist/images/manager.js +21 -123
- package/dist/index.d.ts +16 -0
- package/dist/index.js +14 -0
- package/dist/operations/adapters.d.ts +69 -0
- package/dist/operations/adapters.js +111 -0
- package/dist/operations/errors.d.ts +59 -0
- package/dist/operations/errors.js +99 -0
- package/dist/operations/handle.d.ts +75 -0
- package/dist/operations/handle.js +229 -0
- package/dist/operations/index.d.ts +9 -0
- package/dist/operations/index.js +8 -0
- package/dist/operations/runner.d.ts +72 -0
- package/dist/operations/runner.js +489 -0
- package/dist/operations/state-machine.d.ts +11 -0
- package/dist/operations/state-machine.js +47 -0
- package/dist/operations/store.d.ts +36 -0
- package/dist/operations/store.js +140 -0
- package/dist/operations/webhooks.d.ts +33 -0
- package/dist/operations/webhooks.js +84 -0
- package/dist/ops/circuit-breaker.d.ts +89 -0
- package/dist/ops/circuit-breaker.js +207 -0
- package/dist/ops/family-telemetry.d.ts +52 -0
- package/dist/ops/family-telemetry.js +74 -0
- package/dist/ops/rate-limit-adapters.d.ts +62 -0
- package/dist/ops/rate-limit-adapters.js +81 -0
- package/dist/ops/rate-limiter.d.ts +15 -1
- package/dist/ops/rate-limiter.js +33 -2
- package/dist/router/auto-router.d.ts +8 -0
- package/dist/router/auto-router.js +15 -1
- package/dist/router/index.d.ts +1 -1
- package/dist/router/index.js +2 -2
- package/dist/router/types.d.ts +7 -0
- package/dist/telephony/manager.d.ts +5 -1
- package/dist/telephony/manager.js +18 -7
- package/dist/types/batch.d.ts +134 -0
- package/dist/types/batch.js +4 -0
- package/dist/types/config.d.ts +9 -0
- package/dist/types/images.d.ts +5 -45
- package/dist/types/operations.d.ts +227 -0
- package/dist/types/operations.js +12 -0
- package/dist/voice/manager.d.ts +5 -1
- package/dist/voice/manager.js +18 -7
- package/dist-cjs/batch/anthropic.d.ts +26 -0
- package/dist-cjs/batch/anthropic.js +183 -0
- package/dist-cjs/batch/errors.d.ts +24 -0
- package/dist-cjs/batch/errors.js +55 -0
- package/dist-cjs/batch/index.d.ts +7 -0
- package/dist-cjs/batch/index.js +21 -0
- package/dist-cjs/batch/manager.d.ts +59 -0
- package/dist-cjs/batch/manager.js +279 -0
- package/dist-cjs/batch/mock.d.ts +41 -0
- package/dist-cjs/batch/mock.js +97 -0
- package/dist-cjs/batch/openai.d.ts +31 -0
- package/dist-cjs/batch/openai.js +173 -0
- package/dist-cjs/core/nexus.d.ts +5 -0
- package/dist-cjs/core/nexus.js +56 -14
- package/dist-cjs/embeddings/manager.js +6 -1
- package/dist-cjs/images/asset-stores.d.ts +127 -0
- package/dist-cjs/images/asset-stores.js +385 -0
- package/dist-cjs/images/asset-support.d.ts +107 -0
- package/dist-cjs/images/asset-support.js +185 -0
- package/dist-cjs/images/assets.d.ts +5 -85
- package/dist-cjs/images/assets.js +40 -199
- package/dist-cjs/images/index.d.ts +1 -0
- package/dist-cjs/images/index.js +4 -1
- package/dist-cjs/images/manager.d.ts +4 -1
- package/dist-cjs/images/manager.js +21 -123
- package/dist-cjs/index.d.ts +16 -0
- package/dist-cjs/index.js +83 -29
- package/dist-cjs/operations/adapters.d.ts +69 -0
- package/dist-cjs/operations/adapters.js +116 -0
- package/dist-cjs/operations/errors.d.ts +59 -0
- package/dist-cjs/operations/errors.js +110 -0
- package/dist-cjs/operations/handle.d.ts +75 -0
- package/dist-cjs/operations/handle.js +234 -0
- package/dist-cjs/operations/index.d.ts +9 -0
- package/dist-cjs/operations/index.js +37 -0
- package/dist-cjs/operations/runner.d.ts +72 -0
- package/dist-cjs/operations/runner.js +493 -0
- package/dist-cjs/operations/state-machine.d.ts +11 -0
- package/dist-cjs/operations/state-machine.js +55 -0
- package/dist-cjs/operations/store.d.ts +36 -0
- package/dist-cjs/operations/store.js +145 -0
- package/dist-cjs/operations/webhooks.d.ts +33 -0
- package/dist-cjs/operations/webhooks.js +90 -0
- package/dist-cjs/ops/circuit-breaker.d.ts +89 -0
- package/dist-cjs/ops/circuit-breaker.js +211 -0
- package/dist-cjs/ops/family-telemetry.d.ts +52 -0
- package/dist-cjs/ops/family-telemetry.js +78 -0
- package/dist-cjs/ops/rate-limit-adapters.d.ts +62 -0
- package/dist-cjs/ops/rate-limit-adapters.js +86 -0
- package/dist-cjs/ops/rate-limiter.d.ts +15 -1
- package/dist-cjs/ops/rate-limiter.js +33 -2
- package/dist-cjs/router/auto-router.d.ts +8 -0
- package/dist-cjs/router/auto-router.js +15 -1
- package/dist-cjs/router/index.d.ts +1 -1
- package/dist-cjs/router/index.js +2 -2
- package/dist-cjs/router/types.d.ts +7 -0
- package/dist-cjs/security/schema-validator.d.ts +24 -24
- package/dist-cjs/telephony/manager.d.ts +5 -1
- package/dist-cjs/telephony/manager.js +18 -7
- package/dist-cjs/types/batch.d.ts +134 -0
- package/dist-cjs/types/batch.js +8 -0
- package/dist-cjs/types/config.d.ts +9 -0
- package/dist-cjs/types/images.d.ts +5 -45
- package/dist-cjs/types/operations.d.ts +227 -0
- package/dist-cjs/types/operations.js +16 -0
- package/dist-cjs/voice/manager.d.ts +5 -1
- package/dist-cjs/voice/manager.js +18 -7
- package/package.json +135 -5
- package/NEXUS.md +0 -387
- package/ROADMAP.md +0 -413
package/API_STABILITY.md
CHANGED
|
@@ -131,6 +131,100 @@ and are overridable through `embeddings.models.registry`. Which concrete model a
|
|
|
131
131
|
not interchangeable, so pin a concrete model whenever a stored index must stay valid across
|
|
132
132
|
upgrades. Provider payloads under `raw` and reported token counts are controlled by the provider.
|
|
133
133
|
|
|
134
|
+
## Operations API stage
|
|
135
|
+
|
|
136
|
+
The `nexus-ai-pro/operations` family is public and follows the 1.x rules, including the
|
|
137
|
+
`operations/adapters` and `operations/webhooks` subpaths. This covers `OperationRunner`,
|
|
138
|
+
`LocalOperationHandle`, `MemoryOperationStore`, the `OperationStore` and `OperationDispatcher`
|
|
139
|
+
contracts, the Redis and BullMQ adapters, the webhook signing and verification helpers, the state
|
|
140
|
+
machine predicates, and the normalized record, event, and error shapes.
|
|
141
|
+
|
|
142
|
+
The lifecycle types moved out of `types/images.ts` and are re-exported from it, so existing image
|
|
143
|
+
imports of `OperationStatus`, `OperationEvent`, `OperationHandle`, `OperationEventBase`, and
|
|
144
|
+
`OperationErrorDescriptor` resolve unchanged. `OperationStatus` gained `retrying`, and
|
|
145
|
+
`OperationEvent` gained `progress` and `retrying` variants and an `attempt` field on `running` —
|
|
146
|
+
additive changes this policy permits in a minor release. Code that switches exhaustively on either
|
|
147
|
+
union should add the new members.
|
|
148
|
+
|
|
149
|
+
Three behaviors are guaranteed. Terminal statuses are final, so a late provider callback cannot
|
|
150
|
+
resurrect a settled operation. Store writes are compare-and-set on `sequence`, so a losing writer
|
|
151
|
+
is told it lost rather than silently overwriting. And a record carrying raw bytes is refused with
|
|
152
|
+
`OperationSerializationError` rather than persisted, because a base64 round trip through a queue
|
|
153
|
+
payload is a failure mode that otherwise appears only as a truncated job.
|
|
154
|
+
|
|
155
|
+
Durability is a property of the configured store, not of the runner. `MemoryOperationStore` does
|
|
156
|
+
not survive a restart and does not promise cross-process recovery. `RedisOperationStore` is atomic
|
|
157
|
+
only when the client exposes `eval`; without it the compare-and-set degrades to a read-compare-write
|
|
158
|
+
that narrows but does not close the race, and that limitation is documented rather than hidden.
|
|
159
|
+
Webhook delivery is best-effort and never fails the operation it describes.
|
|
160
|
+
|
|
161
|
+
## Resilience API stage
|
|
162
|
+
|
|
163
|
+
`CircuitBreaker`, the `RateLimitStore` contract, `MemoryRateLimitStore`, `RedisRateLimitStore`, and
|
|
164
|
+
the `ops/circuit-breaker` and `ops/rate-limit-adapters` subpaths are public and follow the 1.x rules,
|
|
165
|
+
together with `circuitBreaker` and `rateLimit.store` in `NexusAIConfig`.
|
|
166
|
+
|
|
167
|
+
`RateLimiter.check()` keeps its synchronous signature and in-memory behavior; `checkAsync()` is the
|
|
168
|
+
additive store-aware path. `NexusRateLimitError` gained an optional `resetAt` and a
|
|
169
|
+
`retryAfterSeconds` accessor. `RouterContext` gained an optional `openCircuits`, and `Router.route()`
|
|
170
|
+
an optional trailing parameter, both additive.
|
|
171
|
+
|
|
172
|
+
Which provider a breaker excludes at a given moment is a runtime decision, not a compatibility
|
|
173
|
+
guarantee: thresholds, scoring, and the exact ordering of candidates can change in a minor release.
|
|
174
|
+
What is guaranteed is the state machine — closed, open after a threshold is crossed, half-open after
|
|
175
|
+
the cooldown, closed again only after the configured probe successes — and that routing still selects
|
|
176
|
+
a provider when every circuit is open rather than failing the request untried.
|
|
177
|
+
|
|
178
|
+
Breaker state is per process by design. Two workers can disagree about a provider, and neither the
|
|
179
|
+
breaker nor its snapshot promises cluster-wide consensus. Rate-limit counters are the opposite: they
|
|
180
|
+
are only shared when a store is configured, and the default in-memory counter gives each worker its
|
|
181
|
+
own budget.
|
|
182
|
+
|
|
183
|
+
## Batch API stage
|
|
184
|
+
|
|
185
|
+
`BatchManager`, the `BatchProvider` contract, the OpenAI and Anthropic adapters, the deterministic
|
|
186
|
+
mock, and the `nexus-ai-pro/batch`, `batch/openai`, `batch/anthropic`, and `batch/mock` subpaths are
|
|
187
|
+
public and follow the 1.x rules, as are the normalized request, ref, state, and result shapes.
|
|
188
|
+
|
|
189
|
+
`BatchJobRef` is guaranteed to stay JSON-serializable and sufficient on its own to poll, collect, or
|
|
190
|
+
cancel a batch. That is what makes a persisted ref survive a restart, and narrowing it would break
|
|
191
|
+
every stored ref, so it requires a major release.
|
|
192
|
+
|
|
193
|
+
Two behaviors are guaranteed. Results are matched by `customId`, never by position, and a duplicate
|
|
194
|
+
`customId` is refused before submission. And a failed batch returns no items rather than fabricated
|
|
195
|
+
ones.
|
|
196
|
+
|
|
197
|
+
Provider vocabulary is not normalized beyond `BatchJobStatus`. Anthropic reports only `in_progress`
|
|
198
|
+
and `ended`, with the real outcome on the per-item results; that adapter maps `ended` to `completed`
|
|
199
|
+
and lets item errors carry the detail rather than inventing a batch-level failure. Payloads under
|
|
200
|
+
`raw`, provider quotas, completion windows, and the discount rate itself are provider-controlled and
|
|
201
|
+
may change without a major release.
|
|
202
|
+
|
|
203
|
+
## Asset store stage
|
|
204
|
+
|
|
205
|
+
`FilesystemAssetStore`, `S3AssetStore`, the `S3LikeClient` contract, and the
|
|
206
|
+
`nexus-ai-pro/images/stores` subpath are public under the 1.x rules and implement the existing
|
|
207
|
+
`AssetStore` contract unchanged. The shared contract, errors, and validation moved to an internal
|
|
208
|
+
`asset-support` module and are re-exported from `images/assets`, so every existing import resolves
|
|
209
|
+
as before.
|
|
210
|
+
|
|
211
|
+
All three stores guarantee that a missing asset and one owned by another tenant are
|
|
212
|
+
indistinguishable, that stored bytes carry a SHA-256 checksum, and that a lapsed asset is unreadable
|
|
213
|
+
before it is purged. `S3AssetStore.purgeExpired()` lists the record prefix on every call; on a large
|
|
214
|
+
bucket prefer the provider lifecycle rules. Neither durable store coordinates concurrent deletes
|
|
215
|
+
across processes.
|
|
216
|
+
|
|
217
|
+
## Model registry generation stage
|
|
218
|
+
|
|
219
|
+
`data/models/*.json` is the versioned source of truth in the repository, and
|
|
220
|
+
`scripts/generate-model-registry.mjs` emits `src/models/generated.ts` from it.
|
|
221
|
+
|
|
222
|
+
Neither is published, and neither is public API. They duplicate `KNOWN_MODELS` exactly, so shipping
|
|
223
|
+
them would add roughly 310KB to every install for data nothing reads. Generation is a build-time
|
|
224
|
+
guarantee about how the registry is maintained, not a runtime surface: the resolver continues to read
|
|
225
|
+
`KNOWN_MODELS`, and a test asserts the two match. Switching the resolver over is a later change, so
|
|
226
|
+
that introducing generation cannot alter pricing behavior in the same release.
|
|
227
|
+
|
|
134
228
|
## Deprecation process
|
|
135
229
|
|
|
136
230
|
Deprecated APIs are marked with `@deprecated` in declarations and described in the changelog. Removals
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,137 @@ Notable changes to this project are recorded here. The format follows [Keep a Ch
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [1.7.0] - 2026-09-08
|
|
8
|
+
|
|
9
|
+
Batch economics, distributed limits, and durable assets: the rest of the theme *work that
|
|
10
|
+
outlives a process*. The half-price asynchronous tier is reachable, one rate-limit budget covers
|
|
11
|
+
every worker, a failing provider leaves routing until it recovers, generated media persists
|
|
12
|
+
outside the process, and every operation family reports into the same metrics and audit log.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **Circuit breaking.** `CircuitBreaker` consumes the same attempt signals as `ProviderHealthMonitor`
|
|
17
|
+
and trips routing away from a failing provider entirely, rather than merely ranking it lower. Two
|
|
18
|
+
independent triggers: consecutive failures, and a failure rate over a rolling window that catches a
|
|
19
|
+
provider failing half its calls without ever failing several in a row. After a cooldown the circuit
|
|
20
|
+
admits a limited number of probes; a success closes it, a failure reopens it and restarts the
|
|
21
|
+
cooldown. Exposed through `circuitBreaker` config, `ai.getCircuitBreakerStatus()`,
|
|
22
|
+
`ai.resetCircuitBreaker()`, and the `nexus-ai-pro/ops/circuit-breaker` subpath.
|
|
23
|
+
- **Distributed rate limiting.** A `RateLimitStore` contract with `MemoryRateLimitStore` and
|
|
24
|
+
`RedisRateLimitStore`, set through `rateLimit.store`, so one budget covers every worker instead of
|
|
25
|
+
each process getting the full limit. The Redis store does the increment and the expiry in one
|
|
26
|
+
atomic Lua call when the client exposes `eval`, and re-arms a missing TTL on the fallback path so a
|
|
27
|
+
crash between `INCR` and `PEXPIRE` cannot block a key forever. Completions and embeddings share the
|
|
28
|
+
limiter, so they share the budget.
|
|
29
|
+
- `NexusRateLimitError` now carries `resetAt` and a `retryAfterSeconds` accessor, so a gateway can
|
|
30
|
+
answer with a real `Retry-After` header.
|
|
31
|
+
- New subpaths `nexus-ai-pro/ops/circuit-breaker` and `nexus-ai-pro/ops/rate-limit-adapters`.
|
|
32
|
+
- **Provider batch tiers.** `BatchManager` puts OpenAI Batch and Anthropic Message Batches behind one
|
|
33
|
+
operation handle, reaching the roughly half-price asynchronous tier that local `runBatch()`
|
|
34
|
+
concurrency cannot. Results are matched by a required `customId` rather than by position, since a
|
|
35
|
+
batch provider does not guarantee output order; a duplicate id is refused before submission.
|
|
36
|
+
Polling backs off up to a configurable ceiling, an idempotency key replays instead of submitting
|
|
37
|
+
twice, and `resume()` collects a batch from its `BatchJobRef` alone so a restarted worker can
|
|
38
|
+
finish what another submitted. Ships with a deterministic `MockBatchProvider`.
|
|
39
|
+
- **Filesystem and S3 asset stores.** `FilesystemAssetStore` and `S3AssetStore` implement the
|
|
40
|
+
existing `AssetStore` contract, with tenant isolation, retention, SHA-256 checksums, and signing.
|
|
41
|
+
A missing asset and one owned by another tenant are indistinguishable, because a distinguishable
|
|
42
|
+
error leaks the existence of another tenant asset. Both write bytes and a JSON sidecar per asset
|
|
43
|
+
rather than a shared index, so concurrent writers do not contend and a torn write loses at most
|
|
44
|
+
one record. `S3LikeClient` is structural, so S3, R2, and MinIO all work without an SDK dependency.
|
|
45
|
+
- **Generated model registry.** `data/models/*.json` is now the versioned source of truth, and
|
|
46
|
+
`scripts/generate-model-registry.mjs` emits `src/models/generated.ts` from it. The generator
|
|
47
|
+
validates required fields, price signs, context bounds, status values, and dangling aliases;
|
|
48
|
+
`npm run registry:check` runs inside `npm run check` so committed data and output cannot drift.
|
|
49
|
+
The runtime still reads `KNOWN_MODELS`, and a test asserts the two match exactly. The generated
|
|
50
|
+
module and its data are build-time artifacts and are not published: they duplicate `KNOWN_MODELS`
|
|
51
|
+
exactly, so shipping them would add roughly 310KB to every install for data nothing reads.
|
|
52
|
+
- New subpaths `nexus-ai-pro/batch`, `batch/openai`, `batch/anthropic`, `batch/mock`, and
|
|
53
|
+
`nexus-ai-pro/images/stores`.
|
|
54
|
+
- **Cross-family observability.** Image, voice, and telephony operations now report into the same
|
|
55
|
+
metrics collector, audit log, and rate limiter as completions and embeddings, closing the gap where
|
|
56
|
+
an application running phone agents and image generation had observability for only part of its
|
|
57
|
+
spend. Metrics carry `family` and `operation` labels, and one rate-limit budget now covers every
|
|
58
|
+
family. `FamilyTelemetry` is exported for a family an application adds itself.
|
|
59
|
+
|
|
60
|
+
### Changed
|
|
61
|
+
|
|
62
|
+
- `RateLimiter` gained `checkAsync()` for the store-aware path. `check()` keeps its synchronous
|
|
63
|
+
signature and behavior, and the runtime only awaits when a store is configured, so a request
|
|
64
|
+
without one pays no extra microtask.
|
|
65
|
+
- `ImageManager`, `VoiceManager`, and `TelephonyManager` accept an optional second constructor
|
|
66
|
+
argument carrying the shared observability objects. It defaults to empty, so constructing one
|
|
67
|
+
standalone is unchanged and simply records nothing.
|
|
68
|
+
- The shared asset contract, errors, and validation moved from `images/assets` to an internal
|
|
69
|
+
`asset-support` module so the filesystem, S3, and memory stores cannot drift on what a valid asset
|
|
70
|
+
is. `images/assets` re-exports all of it, so existing imports are unchanged.
|
|
71
|
+
- Documentation consolidated from nine files to seven. `NEXUS.md` and `EXPLANATION.md` were four
|
|
72
|
+
releases stale and largely restated the README; their unique content moved to `CONTRIBUTING.md`
|
|
73
|
+
(real-provider test setup, the public-feature checklist, the manual smoke checklist) and to a new
|
|
74
|
+
README "Known Limitations" section. The published package now carries only `README.md`,
|
|
75
|
+
`API_STABILITY.md`, `CHANGELOG.md`, `SECURITY.md`, and `LICENSE`; `ROADMAP.md` is a design proposal
|
|
76
|
+
and stays on GitHub.
|
|
77
|
+
- `SECURITY.md` no longer names a specific supported version line. It claimed `0.9.x` while 1.6.0 was
|
|
78
|
+
the published release, so the wording is now version-independent and cannot go stale again.
|
|
79
|
+
- `RouterContext` gained an optional `openCircuits`, and `Router.route()` an optional trailing
|
|
80
|
+
parameter. When every candidate's circuit is open the router routes anyway: that usually means a
|
|
81
|
+
shared dependency is down, and one attempt beats a certain failure with no attempt at all.
|
|
82
|
+
|
|
83
|
+
## [1.6.0] - 2026-09-03
|
|
84
|
+
|
|
85
|
+
Durable operations, the first half of the theme *work that outlives a process*. Long-running work now
|
|
86
|
+
runs through one lifecycle that survives a worker crash. Everything here is additive.
|
|
87
|
+
|
|
88
|
+
The rest of the theme — provider batch APIs, distributed rate limiting and circuit breaking,
|
|
89
|
+
filesystem and S3 asset stores, a generated model registry, and cross-family observability — moves to
|
|
90
|
+
1.7.0. The operation handle they all sit behind ships here, which is what unblocks them.
|
|
91
|
+
|
|
92
|
+
### Added
|
|
93
|
+
|
|
94
|
+
- **`OperationRunner` and a shared operation lifecycle.** `queued → running → succeeded | failed`,
|
|
95
|
+
with `retrying`, `cancelling`, `cancelled`, and `expired` covering the rest, exposed through the
|
|
96
|
+
new `nexus-ai-pro/operations` subpath. The runner persists a record before doing any work, claims
|
|
97
|
+
a lease, heartbeats while the executor runs, retries with backoff and jitter, dead-letters what
|
|
98
|
+
never succeeds, and emits signed webhooks.
|
|
99
|
+
- **Restart survival through leases rather than locks.** A worker that dies leaves a record whose
|
|
100
|
+
lease lapses; another worker's `recover()` sweep resumes it. Every store write is a compare-and-set
|
|
101
|
+
on the record's `sequence`, so two workers racing on one operation cannot both win, and the loser
|
|
102
|
+
is told it lost instead of silently overwriting.
|
|
103
|
+
- **`RedisOperationStore` and `BullMQOperationDispatcher`.** Redis persists records, with the
|
|
104
|
+
compare-and-set done in one Lua call when the client exposes `eval`. BullMQ dispatch queues the
|
|
105
|
+
operation id and its routing metadata only, never a payload.
|
|
106
|
+
- **Idempotency keys.** A key matching an existing record replays that operation instead of starting
|
|
107
|
+
a second one, so an ambiguous timeout cannot produce a duplicate charge.
|
|
108
|
+
- **Progress and cancellation.** An executor reports progress through `context.report()`, which
|
|
109
|
+
reaches both `handle.events()` and the persisted record. `runner.cancel()` settles an operation
|
|
110
|
+
owned by another worker, observed there through its heartbeat.
|
|
111
|
+
- **Signed webhooks, with the verifying half included.** Deliveries are HMAC-SHA256 over
|
|
112
|
+
`${timestamp}.${body}`, so a captured delivery cannot be replayed indefinitely.
|
|
113
|
+
`verifyOperationWebhook()` ships alongside `signOperationWebhook()` rather than leaving every
|
|
114
|
+
receiver to hand-roll a constant-time comparison. A failed delivery is reported and never fails
|
|
115
|
+
the operation it describes.
|
|
116
|
+
- **A binary-payload guard.** Persisting a result carrying a `Uint8Array`, `Buffer`, `Blob`, or
|
|
117
|
+
stream throws `OperationSerializationError` naming the exact path and pointing at `AssetStore`.
|
|
118
|
+
Base64 in a job payload inflates it by a third and most queue backends cap job size well below one
|
|
119
|
+
image, so this fails loudly at the boundary instead of appearing later as a truncated job.
|
|
120
|
+
- New subpaths `nexus-ai-pro/operations`, `operations/adapters`, and `operations/webhooks`.
|
|
121
|
+
|
|
122
|
+
### Changed
|
|
123
|
+
|
|
124
|
+
- The operation lifecycle types moved from `types/images.ts` to a family-neutral `types/operations.ts`
|
|
125
|
+
and are re-exported from their old home, so existing image imports resolve unchanged. The image
|
|
126
|
+
family's private handle was replaced by the shared `LocalOperationHandle`, which is told to reject
|
|
127
|
+
with `ImageOperationCancelledError` so callers catching that error see no difference.
|
|
128
|
+
- `OperationStatus` gained `retrying`; `OperationEvent` gained `progress` and `retrying` variants and
|
|
129
|
+
an `attempt` field on `running`. Additive, but code switching exhaustively on either union should
|
|
130
|
+
add the new members.
|
|
131
|
+
|
|
132
|
+
### Fixed
|
|
133
|
+
|
|
134
|
+
- A handle driven by the runner stayed in `queued` for the whole run, so no `running` event was
|
|
135
|
+
emitted and every `context.report()` call was silently dropped. Found while testing the runner's
|
|
136
|
+
event sequence; the handle now exposes `markRunning()` and the runner calls it before each attempt.
|
|
137
|
+
|
|
7
138
|
## [1.5.0] - 2026-08-31
|
|
8
139
|
|
|
9
140
|
Embeddings become a first-class operation. `ai.embed()` gets the routing, caching, batching, budget,
|
|
@@ -266,7 +397,9 @@ shared policy vocabulary is in place for a future release that revisits this.
|
|
|
266
397
|
- URL fetching rejects unsafe private-network targets and limits response reads.
|
|
267
398
|
- CLI and audit findings no longer disclose detected secret values.
|
|
268
399
|
|
|
269
|
-
[Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.
|
|
400
|
+
[Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.7.0...HEAD
|
|
401
|
+
[1.7.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.6.0...v1.7.0
|
|
402
|
+
[1.6.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.5.0...v1.6.0
|
|
270
403
|
[1.5.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.4.0...v1.5.0
|
|
271
404
|
[1.4.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.3.0...v1.4.0
|
|
272
405
|
[1.3.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.2.1...v1.3.0
|
package/README.md
CHANGED
|
@@ -6,8 +6,8 @@ Use the whole pipeline for production AI features, or turn pieces off when you o
|
|
|
6
6
|
|
|
7
7
|
- NPM: https://www.npmjs.com/package/nexus-ai-pro
|
|
8
8
|
- GitHub: https://github.com/mkhitar-abrahamyan/nexus-ai
|
|
9
|
-
-
|
|
10
|
-
-
|
|
9
|
+
- Contributing, testing, and release procedure: [CONTRIBUTING.md](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/CONTRIBUTING.md)
|
|
10
|
+
- Delivered work and what is planned next: [ROADMAP.md](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/ROADMAP.md)
|
|
11
11
|
|
|
12
12
|
## Install
|
|
13
13
|
|
|
@@ -84,6 +84,10 @@ You can still pass a plain `NexusAIConfig` to `new NexusAI(...)` when you want f
|
|
|
84
84
|
- cache exact or semantically similar prompts
|
|
85
85
|
- add tools, agents, RAG context, evals, batch jobs, and queues
|
|
86
86
|
- embed text through the same routing, caching, batching, budget, retry, and metrics as a completion
|
|
87
|
+
- run long operations that survive a restart, with leases, retries, dead-lettering, and signed webhooks
|
|
88
|
+
- trip routing away from a failing provider, and share one rate-limit budget across workers
|
|
89
|
+
- reach the providers' half-price asynchronous batch tier behind one operation handle
|
|
90
|
+
- persist generated media to disk or S3 with tenant isolation, retention, and checksums
|
|
87
91
|
- build persistent realtime voice agents with interruption, live tools, and normalized conversation state
|
|
88
92
|
- keep TypeScript types around every request and response
|
|
89
93
|
|
|
@@ -417,6 +421,294 @@ const response = await ai.completeVerified(
|
|
|
417
421
|
);
|
|
418
422
|
```
|
|
419
423
|
|
|
424
|
+
## Provider Batch Tiers
|
|
425
|
+
|
|
426
|
+
Both OpenAI and Anthropic sell an asynchronous tier at roughly half price, in exchange for a
|
|
427
|
+
completion window measured in hours. Local `runBatch()` concurrency cannot reach it — it is a
|
|
428
|
+
different API. `BatchManager` puts both behind one operation handle.
|
|
429
|
+
|
|
430
|
+
```ts
|
|
431
|
+
import { BatchManager } from 'nexus-ai-pro/batch';
|
|
432
|
+
import { OpenAIBatchProvider } from 'nexus-ai-pro/batch/openai';
|
|
433
|
+
|
|
434
|
+
const batch = new BatchManager({
|
|
435
|
+
providers: { openai: new OpenAIBatchProvider({ apiKey: process.env.OPENAI_API_KEY! }) },
|
|
436
|
+
defaultProvider: 'openai',
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
const handle = await batch.submit({
|
|
440
|
+
model: 'gpt-5.4-mini',
|
|
441
|
+
idempotencyKey: `nightly-${date}`,
|
|
442
|
+
items: documents.map((document) => ({
|
|
443
|
+
customId: document.id,
|
|
444
|
+
request: { model: 'gpt-5.4-mini', messages: [{ role: 'user', content: document.text }] },
|
|
445
|
+
})),
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
console.log(handle.id); // persist this, do not block a request on the result
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
`customId` is required, not optional. A batch provider does not guarantee output order, and matching
|
|
452
|
+
results by position is exactly the bug that silently mislabels every row. Duplicates are refused
|
|
453
|
+
before submission for the same reason.
|
|
454
|
+
|
|
455
|
+
The handle settles when the provider finishes, which can be hours later, so treat it as a background
|
|
456
|
+
operation:
|
|
457
|
+
|
|
458
|
+
```ts
|
|
459
|
+
const result = await handle.result();
|
|
460
|
+
|
|
461
|
+
result.status; // completed | failed | expired | cancelled
|
|
462
|
+
result.counts; // { total, completed, failed }
|
|
463
|
+
result.items; // one entry per customId, with response or error
|
|
464
|
+
result.cost.amount; // priced per item, then discounted at the provider's batch rate
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
A mixed batch is normal: individual items carry their own `error` while the batch still reports
|
|
468
|
+
`completed`. Nothing is invented for a failed batch — `items` comes back empty rather than padded.
|
|
469
|
+
|
|
470
|
+
**Surviving a restart.** Everything after `submit` takes only a `BatchJobRef`, which is
|
|
471
|
+
JSON-serializable. A worker that never submitted the batch can collect it:
|
|
472
|
+
|
|
473
|
+
```ts
|
|
474
|
+
const ref = { id: savedBatchId, provider: 'openai' };
|
|
475
|
+
|
|
476
|
+
await batch.status(ref); // provider-side state, without waiting
|
|
477
|
+
const result = await batch.resume(ref);
|
|
478
|
+
await batch.cancel(ref);
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
Polling backs off from the configured interval up to `maxPollIntervalMs`, so a 24-hour batch does
|
|
482
|
+
not generate 2,880 polls while a fast one is still caught by the first few short intervals.
|
|
483
|
+
|
|
484
|
+
## Asset Stores
|
|
485
|
+
|
|
486
|
+
`AssetStore` has three implementations. They share one contract, so retention, tenant isolation, and
|
|
487
|
+
checksums behave identically:
|
|
488
|
+
|
|
489
|
+
```ts
|
|
490
|
+
import { MemoryAssetStore } from 'nexus-ai-pro/images/assets';
|
|
491
|
+
import { FilesystemAssetStore, S3AssetStore } from 'nexus-ai-pro/images/stores';
|
|
492
|
+
|
|
493
|
+
const store = new FilesystemAssetStore({ directory: '/var/lib/app/assets', defaultTtlSeconds: 86_400 });
|
|
494
|
+
|
|
495
|
+
const s3 = new S3AssetStore({
|
|
496
|
+
client, // structural: S3, R2, MinIO, or a test double
|
|
497
|
+
bucket: 'generated-media',
|
|
498
|
+
prefix: 'nexus-assets/',
|
|
499
|
+
});
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
A missing asset and one owned by another tenant are **indistinguishable** — both return `undefined`
|
|
503
|
+
rather than a permission error, because a distinguishable error leaks the existence of another
|
|
504
|
+
tenant's asset.
|
|
505
|
+
|
|
506
|
+
Both durable stores write two objects per asset: the bytes, and a JSON sidecar holding the
|
|
507
|
+
descriptor, tenant, and expiry. A single shared index would be a write-contention point and a
|
|
508
|
+
corruption blast radius; per-asset sidecars let concurrent writers proceed and lose at most one
|
|
509
|
+
record. `purgeExpired()` is exact but costs a directory listing, so on a large S3 bucket prefer the
|
|
510
|
+
provider's own lifecycle rules and keep this as the fallback.
|
|
511
|
+
|
|
512
|
+
## Model Registry Generation
|
|
513
|
+
|
|
514
|
+
The registry is generated from versioned provider data in `data/models/`, not hand-edited. The
|
|
515
|
+
Claude 5 reasoning bug fixed in 1.4.0 was exactly the kind of error a hand-written literal of 100+
|
|
516
|
+
models invites.
|
|
517
|
+
|
|
518
|
+
```bash
|
|
519
|
+
npm run registry:generate # data/models/*.json -> src/models/generated.ts
|
|
520
|
+
npm run registry:check # fails if the committed output is stale
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
`registry:check` runs as part of `npm run check`, so committed data and committed output cannot
|
|
524
|
+
drift apart. The generator validates required fields, price signs, context bounds, status values,
|
|
525
|
+
and that every alias points at a model that exists — a dangling alias otherwise fails only when a
|
|
526
|
+
request happens to use it.
|
|
527
|
+
|
|
528
|
+
The runtime still reads the hand-written `KNOWN_MODELS`; a test asserts the generated registry
|
|
529
|
+
matches it exactly. Swapping the runtime over is deliberately a separate change, so introducing the
|
|
530
|
+
generator cannot quietly alter pricing data in the same release.
|
|
531
|
+
|
|
532
|
+
`src/models/generated.ts` and `data/` are build-time artifacts and are **not** published. They
|
|
533
|
+
duplicate `KNOWN_MODELS` exactly, and shipping them in both builds would add roughly 310KB to every
|
|
534
|
+
install for data nothing reads. Both live in the repository, where a diff is what you actually want.
|
|
535
|
+
|
|
536
|
+
## Resilience: Circuit Breaking and Distributed Limits
|
|
537
|
+
|
|
538
|
+
Health monitoring ranks a struggling provider lower. A circuit breaker is the stronger step: while a
|
|
539
|
+
circuit is open the provider is removed from routing entirely, so a hard-down provider stops
|
|
540
|
+
absorbing one failed attempt per request.
|
|
541
|
+
|
|
542
|
+
```ts
|
|
543
|
+
const ai = new NexusAI({
|
|
544
|
+
providers: { openai: { apiKey: process.env.OPENAI_API_KEY! }, anthropic: { apiKey: process.env.ANTHROPIC_API_KEY! } },
|
|
545
|
+
health: { enabled: true },
|
|
546
|
+
circuitBreaker: {
|
|
547
|
+
enabled: true,
|
|
548
|
+
failureThreshold: 5, // consecutive failures that open the circuit
|
|
549
|
+
failureRateThreshold: 0.5, // or half the calls failing in the window
|
|
550
|
+
minimumThroughput: 10,
|
|
551
|
+
resetTimeoutMs: 30_000, // cooldown before a probe
|
|
552
|
+
halfOpenMaxCalls: 1,
|
|
553
|
+
onStateChange: (event) => logger.warn('circuit', event),
|
|
554
|
+
},
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
ai.getCircuitBreakerStatus(); // state, failure rate, retryAt per provider
|
|
558
|
+
ai.resetCircuitBreaker('openai');
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
Two independent triggers, because they catch different failures. A consecutive count catches a
|
|
562
|
+
provider that is hard down. A failure *rate* catches one that fails half its calls without ever
|
|
563
|
+
failing several in a row — invisible to a consecutive counter. The rate is only considered once
|
|
564
|
+
`minimumThroughput` calls have been seen, since one failure out of two is not evidence of anything.
|
|
565
|
+
|
|
566
|
+
After the cooldown the circuit goes half-open and admits a limited number of probes. A successful
|
|
567
|
+
probe closes it; a failed probe reopens it and restarts the cooldown. If *every* circuit is open the
|
|
568
|
+
router routes anyway — that usually means a shared dependency is down, and one attempt beats a
|
|
569
|
+
certain failure with no attempt at all.
|
|
570
|
+
|
|
571
|
+
`isFailure` keeps errors that are not the provider's fault out of the calculation:
|
|
572
|
+
|
|
573
|
+
```ts
|
|
574
|
+
circuitBreaker: {
|
|
575
|
+
enabled: true,
|
|
576
|
+
isFailure: (error) => !(error instanceof Error && error.name === 'AbortError'),
|
|
577
|
+
}
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
**Distributed rate limiting.** The built-in limiter is process-local, which multiplies the real
|
|
581
|
+
limit by the number of workers. Pointing it at a shared store fixes that, and the same budget then
|
|
582
|
+
covers completions and embeddings alike:
|
|
583
|
+
|
|
584
|
+
```ts
|
|
585
|
+
import { RedisRateLimitStore } from 'nexus-ai-pro/ops/rate-limit-adapters';
|
|
586
|
+
|
|
587
|
+
const ai = new NexusAI({
|
|
588
|
+
providers: { openai: { apiKey: process.env.OPENAI_API_KEY! } },
|
|
589
|
+
rateLimit: {
|
|
590
|
+
enabled: true,
|
|
591
|
+
maxRequests: 100,
|
|
592
|
+
windowMs: 60_000,
|
|
593
|
+
key: 'userId',
|
|
594
|
+
store: new RedisRateLimitStore(redis),
|
|
595
|
+
},
|
|
596
|
+
});
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
Prefer a client exposing `eval`: the increment and the expiry then happen in one atomic round trip.
|
|
600
|
+
Without it the store falls back to `INCR` plus `PEXPIRE` and re-arms any missing TTL it sees, so a
|
|
601
|
+
crash between the two calls cannot block a key forever.
|
|
602
|
+
|
|
603
|
+
`NexusRateLimitError` carries `resetAt` and `retryAfterSeconds`, so a gateway can answer with a
|
|
604
|
+
real `Retry-After` header:
|
|
605
|
+
|
|
606
|
+
```ts
|
|
607
|
+
catch (error) {
|
|
608
|
+
if (error instanceof NexusRateLimitError) {
|
|
609
|
+
response.setHeader('Retry-After', String(error.retryAfterSeconds ?? 60));
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
Omitting `store` keeps the original synchronous in-memory path, which costs no extra microtask per
|
|
615
|
+
request.
|
|
616
|
+
|
|
617
|
+
## Durable Operations
|
|
618
|
+
|
|
619
|
+
Long-running work — an image render, a batch, anything asynchronous — runs through one lifecycle:
|
|
620
|
+
`queued → running → succeeded | failed`, with `retrying`, `cancelling`, `cancelled`, and `expired`
|
|
621
|
+
covering the rest. `OperationRunner` owns it end to end, so a crashed worker does not lose work.
|
|
622
|
+
|
|
623
|
+
The default store is in-process, so the small case needs no infrastructure:
|
|
624
|
+
|
|
625
|
+
```ts
|
|
626
|
+
import { BatchManager } from 'nexus-ai-pro/batch';
|
|
627
|
+
import { OpenAIBatchProvider } from 'nexus-ai-pro/batch/openai';
|
|
628
|
+
import { FilesystemAssetStore, S3AssetStore } from 'nexus-ai-pro/images/stores';
|
|
629
|
+
import { CircuitBreaker } from 'nexus-ai-pro/ops/circuit-breaker';
|
|
630
|
+
import { RedisRateLimitStore } from 'nexus-ai-pro/ops/rate-limit-adapters';
|
|
631
|
+
import { OperationRunner } from 'nexus-ai-pro/operations';
|
|
632
|
+
|
|
633
|
+
const runner = new OperationRunner<string>({ retry: { maxAttempts: 3, baseDelayMs: 500 } });
|
|
634
|
+
|
|
635
|
+
const handle = await runner.submit(async (context) => {
|
|
636
|
+
context.report({ completed: 1, total: 3 });
|
|
637
|
+
return doTheWork(context.signal);
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
for await (const event of handle.events()) {
|
|
641
|
+
console.log(event.type, event.sequence);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
const value = await handle.result();
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
Swapping the store makes the same code survive a restart. Nothing else changes:
|
|
648
|
+
|
|
649
|
+
```ts
|
|
650
|
+
import { OperationRunner } from 'nexus-ai-pro/operations';
|
|
651
|
+
import { BullMQOperationDispatcher, RedisOperationStore } from 'nexus-ai-pro/operations/adapters';
|
|
652
|
+
|
|
653
|
+
const runner = new OperationRunner({
|
|
654
|
+
store: new RedisOperationStore(redis),
|
|
655
|
+
dispatcher: new BullMQOperationDispatcher(queue),
|
|
656
|
+
owner: process.env.HOSTNAME,
|
|
657
|
+
leaseMs: 30_000,
|
|
658
|
+
webhook: { url: 'https://app.example/hooks/operations', secret: process.env.HOOK_SECRET! },
|
|
659
|
+
});
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
**How restart survival actually works.** A worker claims a lease before running and heartbeats
|
|
663
|
+
while it works. If the process dies, the lease simply lapses; another worker's `recover()` sweep
|
|
664
|
+
finds the record and resumes it. There is no distributed lock — every store write is a
|
|
665
|
+
compare-and-set on the record's `sequence`, so two workers racing on the same operation cannot both
|
|
666
|
+
win.
|
|
667
|
+
|
|
668
|
+
```ts
|
|
669
|
+
// On startup, in each worker.
|
|
670
|
+
const resumed = await runner.recover(executor);
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
Recovery is deliberately conservative: a record past its `expiresAt` is **expired** rather than
|
|
674
|
+
re-run, and one that has used every attempt is **dead-lettered** and parked for inspection, so a
|
|
675
|
+
permanently failing operation cannot be recovered forever.
|
|
676
|
+
|
|
677
|
+
**Idempotency.** An `idempotencyKey` that matches an existing record replays that operation instead
|
|
678
|
+
of starting a second one, which is what stops an ambiguous timeout from double-charging:
|
|
679
|
+
|
|
680
|
+
```ts
|
|
681
|
+
const handle = await runner.submit(chargeAndRender, { idempotencyKey: `render:${orderId}` });
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
**Webhooks** are signed with HMAC-SHA256 over `${timestamp}.${body}`, so a captured delivery cannot
|
|
685
|
+
be replayed indefinitely. The verifying half ships too — a receiver should never have to hand-roll a
|
|
686
|
+
constant-time comparison:
|
|
687
|
+
|
|
688
|
+
```ts
|
|
689
|
+
import { OPERATION_WEBHOOK_SIGNATURE_HEADER, verifyOperationWebhook } from 'nexus-ai-pro/operations/webhooks';
|
|
690
|
+
|
|
691
|
+
app.post('/hooks/operations', (request, response) => {
|
|
692
|
+
const ok = verifyOperationWebhook(
|
|
693
|
+
request.rawBody,
|
|
694
|
+
request.header(OPERATION_WEBHOOK_SIGNATURE_HEADER),
|
|
695
|
+
process.env.HOOK_SECRET!,
|
|
696
|
+
);
|
|
697
|
+
response.sendStatus(ok ? 204 : 400);
|
|
698
|
+
});
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
A failed delivery is reported through `onWebhookError` and never turns a completed operation into a
|
|
702
|
+
failed one.
|
|
703
|
+
|
|
704
|
+
**Binary payloads are refused, not truncated.** Persisting a result that carries a `Uint8Array`,
|
|
705
|
+
`Buffer`, or `Blob` throws `OperationSerializationError` naming the exact path. Base64 in a job
|
|
706
|
+
payload inflates it by a third and most queue backends cap job size well below one image, so the
|
|
707
|
+
bytes belong in an `AssetStore` with only a reference on the record. The BullMQ dispatcher likewise
|
|
708
|
+
queues the operation id and nothing else.
|
|
709
|
+
|
|
710
|
+
The image family already runs on this lifecycle, so `ai.images.submit()` reports the same events.
|
|
711
|
+
|
|
420
712
|
## Embeddings
|
|
421
713
|
|
|
422
714
|
`ai.embed()` is a first-class operation, not a helper: it gets the same routing, caching, batching,
|
|
@@ -1226,6 +1518,9 @@ import { negotiateCompletionRequest } from 'nexus-ai-pro/capabilities';
|
|
|
1226
1518
|
import { guardrailPolicy } from 'nexus-ai-pro/security';
|
|
1227
1519
|
import { RedisCacheAdapter } from 'nexus-ai-pro/cache';
|
|
1228
1520
|
import { DeepSeekProvider } from 'nexus-ai-pro/providers/deepseek';
|
|
1521
|
+
import { OperationRunner } from 'nexus-ai-pro/operations';
|
|
1522
|
+
import { RedisOperationStore } from 'nexus-ai-pro/operations/adapters';
|
|
1523
|
+
import { verifyOperationWebhook } from 'nexus-ai-pro/operations/webhooks';
|
|
1229
1524
|
import { EmbeddingManager } from 'nexus-ai-pro/embeddings';
|
|
1230
1525
|
import { OpenAIEmbeddingProvider } from 'nexus-ai-pro/embeddings/adapters';
|
|
1231
1526
|
import { MockEmbeddingProvider } from 'nexus-ai-pro/embeddings/mock';
|
|
@@ -1291,12 +1586,33 @@ npm run test:conformance:real
|
|
|
1291
1586
|
|
|
1292
1587
|
## Roadmap
|
|
1293
1588
|
|
|
1589
|
+
See [ROADMAP.md](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/ROADMAP.md) for what has
|
|
1590
|
+
shipped and what is planned. It is a design proposal, not a compatibility promise; the guarantees
|
|
1591
|
+
live in [API_STABILITY.md](./API_STABILITY.md).
|
|
1592
|
+
|
|
1294
1593
|
1.4.0 closed the completion-request gap: prompt caching, reasoning controls, tool and sampling
|
|
1295
1594
|
controls, structured usage with numeric cost, and capability negotiation with registry provenance.
|
|
1296
1595
|
|
|
1297
1596
|
Next is durable execution — an operation state machine, provider batch APIs, distributed rate
|
|
1298
1597
|
limiting and circuit breaking, first-class embeddings, and filesystem/S3 asset stores — followed by
|
|
1299
|
-
image portability and promotion out of experimental. See
|
|
1598
|
+
image portability and promotion out of experimental. See
|
|
1599
|
+
[ROADMAP.md](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/ROADMAP.md).
|
|
1600
|
+
|
|
1601
|
+
## Known Limitations
|
|
1602
|
+
|
|
1603
|
+
- Voice, telephony, images, local models, and custom providers are optional layers; enable only what
|
|
1604
|
+
you use.
|
|
1605
|
+
- Audio and video preprocessing is limited, and provider modality support differs.
|
|
1606
|
+
- Bundled model metadata is a routing and estimation convenience, not a pricing contract.
|
|
1607
|
+
- Process-memory cache, queues, and asset storage are not enough for a distributed deployment; use
|
|
1608
|
+
the Redis, BullMQ, filesystem, or S3 adapters instead.
|
|
1609
|
+
- Default hash embeddings suit tests and demos, not strong semantic search. Register a real
|
|
1610
|
+
embedding provider for production retrieval.
|
|
1611
|
+
- NLI verification is an interface; bring a specialized verifier for high-confidence entailment.
|
|
1612
|
+
- Guardrails reduce risk but do not replace application authorization, provider-side moderation, or
|
|
1613
|
+
human review of high-impact actions.
|
|
1614
|
+
- Circuit-breaker state is per process, and realtime sessions are not yet routed through the metrics,
|
|
1615
|
+
audit, and rate-limit path that every other family uses.
|
|
1300
1616
|
|
|
1301
1617
|
## Production Notes
|
|
1302
1618
|
|
package/SECURITY.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Supported versions
|
|
4
4
|
|
|
5
|
-
Security fixes are provided for the latest published minor release.
|
|
5
|
+
Security fixes are provided for the latest published minor release. Upgrade to the newest patch of that line as fixes become available; older minor releases do not receive backports.
|
|
6
6
|
|
|
7
7
|
## Reporting a vulnerability
|
|
8
8
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { BatchJobRef, BatchJobState, BatchOutputItem, BatchProvider, BatchProviderCallContext, BatchProviderInfo, BatchSubmitRequest } from '../types/batch.js';
|
|
2
|
+
export interface AnthropicBatchProviderOptions {
|
|
3
|
+
apiKey: string;
|
|
4
|
+
baseUrl?: string;
|
|
5
|
+
version?: string;
|
|
6
|
+
headers?: Record<string, string>;
|
|
7
|
+
fetch?: typeof fetch;
|
|
8
|
+
/** Applied to any item that names no model. */
|
|
9
|
+
defaultModel?: string;
|
|
10
|
+
maxTokens?: number;
|
|
11
|
+
}
|
|
12
|
+
/** Anthropic Message Batches: half price, 24-hour window. */
|
|
13
|
+
export declare class AnthropicBatchProvider implements BatchProvider {
|
|
14
|
+
private readonly options;
|
|
15
|
+
readonly info: BatchProviderInfo;
|
|
16
|
+
constructor(options: AnthropicBatchProviderOptions);
|
|
17
|
+
submit(request: BatchSubmitRequest, context: BatchProviderCallContext): Promise<BatchJobRef>;
|
|
18
|
+
poll(ref: BatchJobRef, context: BatchProviderCallContext): Promise<BatchJobState>;
|
|
19
|
+
results(ref: BatchJobRef, context: BatchProviderCallContext): Promise<BatchOutputItem[]>;
|
|
20
|
+
cancel(ref: BatchJobRef, context: BatchProviderCallContext): Promise<BatchJobState>;
|
|
21
|
+
private toState;
|
|
22
|
+
private toOutputItem;
|
|
23
|
+
private request;
|
|
24
|
+
private requestText;
|
|
25
|
+
private send;
|
|
26
|
+
}
|