nexus-ai-pro 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/API_STABILITY.md +52 -0
  2. package/CHANGELOG.md +123 -1
  3. package/README.md +191 -0
  4. package/ROADMAP.md +98 -41
  5. package/dist/core/nexus.d.ts +26 -0
  6. package/dist/core/nexus.js +46 -0
  7. package/dist/embeddings/adapters.d.ts +66 -0
  8. package/dist/embeddings/adapters.js +246 -0
  9. package/dist/embeddings/errors.d.ts +36 -0
  10. package/dist/embeddings/errors.js +76 -0
  11. package/dist/embeddings/index.d.ts +8 -0
  12. package/dist/embeddings/index.js +7 -0
  13. package/dist/embeddings/manager.d.ts +62 -0
  14. package/dist/embeddings/manager.js +618 -0
  15. package/dist/embeddings/mock.d.ts +36 -0
  16. package/dist/embeddings/mock.js +64 -0
  17. package/dist/embeddings/models.d.ts +74 -0
  18. package/dist/embeddings/models.js +252 -0
  19. package/dist/embeddings/providers.d.ts +15 -0
  20. package/dist/embeddings/providers.js +10 -0
  21. package/dist/embeddings/register.d.ts +10 -0
  22. package/dist/embeddings/register.js +39 -0
  23. package/dist/images/manager.js +7 -121
  24. package/dist/index.d.ts +16 -2
  25. package/dist/index.js +13 -1
  26. package/dist/operations/adapters.d.ts +69 -0
  27. package/dist/operations/adapters.js +111 -0
  28. package/dist/operations/errors.d.ts +59 -0
  29. package/dist/operations/errors.js +99 -0
  30. package/dist/operations/handle.d.ts +75 -0
  31. package/dist/operations/handle.js +229 -0
  32. package/dist/operations/index.d.ts +9 -0
  33. package/dist/operations/index.js +8 -0
  34. package/dist/operations/runner.d.ts +72 -0
  35. package/dist/operations/runner.js +489 -0
  36. package/dist/operations/state-machine.d.ts +11 -0
  37. package/dist/operations/state-machine.js +47 -0
  38. package/dist/operations/store.d.ts +36 -0
  39. package/dist/operations/store.js +140 -0
  40. package/dist/operations/webhooks.d.ts +33 -0
  41. package/dist/operations/webhooks.js +84 -0
  42. package/dist/ops/rate-limiter.d.ts +11 -2
  43. package/dist/testing/embedding-provider-conformance.d.ts +33 -0
  44. package/dist/testing/embedding-provider-conformance.js +117 -0
  45. package/dist/types/config.d.ts +3 -0
  46. package/dist/types/embeddings.d.ts +230 -0
  47. package/dist/types/embeddings.js +1 -0
  48. package/dist/types/images.d.ts +5 -45
  49. package/dist/types/operations.d.ts +227 -0
  50. package/dist/types/operations.js +12 -0
  51. package/dist-cjs/core/nexus.d.ts +26 -0
  52. package/dist-cjs/core/nexus.js +46 -0
  53. package/dist-cjs/embeddings/adapters.d.ts +66 -0
  54. package/dist-cjs/embeddings/adapters.js +254 -0
  55. package/dist-cjs/embeddings/errors.d.ts +36 -0
  56. package/dist-cjs/embeddings/errors.js +86 -0
  57. package/dist-cjs/embeddings/index.d.ts +8 -0
  58. package/dist-cjs/embeddings/index.js +42 -0
  59. package/dist-cjs/embeddings/manager.d.ts +62 -0
  60. package/dist-cjs/embeddings/manager.js +622 -0
  61. package/dist-cjs/embeddings/mock.d.ts +36 -0
  62. package/dist-cjs/embeddings/mock.js +68 -0
  63. package/dist-cjs/embeddings/models.d.ts +74 -0
  64. package/dist-cjs/embeddings/models.js +265 -0
  65. package/dist-cjs/embeddings/providers.d.ts +15 -0
  66. package/dist-cjs/embeddings/providers.js +11 -0
  67. package/dist-cjs/embeddings/register.d.ts +10 -0
  68. package/dist-cjs/embeddings/register.js +42 -0
  69. package/dist-cjs/images/manager.js +7 -121
  70. package/dist-cjs/index.d.ts +16 -2
  71. package/dist-cjs/index.js +76 -15
  72. package/dist-cjs/operations/adapters.d.ts +69 -0
  73. package/dist-cjs/operations/adapters.js +116 -0
  74. package/dist-cjs/operations/errors.d.ts +59 -0
  75. package/dist-cjs/operations/errors.js +110 -0
  76. package/dist-cjs/operations/handle.d.ts +75 -0
  77. package/dist-cjs/operations/handle.js +234 -0
  78. package/dist-cjs/operations/index.d.ts +9 -0
  79. package/dist-cjs/operations/index.js +37 -0
  80. package/dist-cjs/operations/runner.d.ts +72 -0
  81. package/dist-cjs/operations/runner.js +493 -0
  82. package/dist-cjs/operations/state-machine.d.ts +11 -0
  83. package/dist-cjs/operations/state-machine.js +55 -0
  84. package/dist-cjs/operations/store.d.ts +36 -0
  85. package/dist-cjs/operations/store.js +145 -0
  86. package/dist-cjs/operations/webhooks.d.ts +33 -0
  87. package/dist-cjs/operations/webhooks.js +90 -0
  88. package/dist-cjs/ops/rate-limiter.d.ts +11 -2
  89. package/dist-cjs/security/schema-validator.d.ts +24 -24
  90. package/dist-cjs/testing/embedding-provider-conformance.d.ts +33 -0
  91. package/dist-cjs/testing/embedding-provider-conformance.js +121 -0
  92. package/dist-cjs/types/config.d.ts +3 -0
  93. package/dist-cjs/types/embeddings.d.ts +230 -0
  94. package/dist-cjs/types/embeddings.js +2 -0
  95. package/dist-cjs/types/images.d.ts +5 -45
  96. package/dist-cjs/types/operations.d.ts +227 -0
  97. package/dist-cjs/types/operations.js +16 -0
  98. package/package.json +92 -1
package/API_STABILITY.md CHANGED
@@ -106,6 +106,58 @@ exactly-once provider execution. Those capabilities will use additive adapters a
106
106
  `MemoryAssetStore` is likewise process-local and does not promise cross-process durability or shared
107
107
  tenant state.
108
108
 
109
+ ## Embeddings API stage
110
+
111
+ The `nexus-ai-pro/embeddings` family is public and follows the 1.x rules, including the explicit
112
+ `embeddings/adapters`, `embeddings/mock`, and `embeddings/models` subpaths. This covers
113
+ `EmbeddingManager`, `ai.embed()` and `ai.embedOne()`, the `EmbeddingsProvider` contract, the
114
+ normalized request, embedding, and meta shapes, the error subclasses, and `toEmbeddingFunction()`.
115
+
116
+ `EmbeddingsProvider` is a distinct contract from the pre-existing `EmbeddingProvider` function type
117
+ used by `MemoryVectorStore`, the semantic cache, and RAG ingestion. That function type is unchanged
118
+ and remains supported; `toEmbeddingFunction()` converts one into the other.
119
+
120
+ Two behaviors are guaranteed and differ deliberately from completions. Vectors are returned in input
121
+ order regardless of how the request was split, deduplicated, or served from cache. And an option the
122
+ target model or adapter declares it cannot honor is **refused**, not dropped, because a vector built
123
+ with different dimensions or a different input type is silently incompatible with vectors already in
124
+ a store. An option nothing in the registry describes is still passed through, on the same
125
+ absence-means-unknown rule the completion path uses.
126
+
127
+ Embedding model entries are registry data, not compatibility guarantees. Bundled dimensions, input
128
+ limits, batch limits, and prices can change in a minor release when a provider changes its lineup,
129
+ and are overridable through `embeddings.models.registry`. Which concrete model an alias such as
130
+ `embed-quality` resolves to can also change in a minor release: the vectors two targets produce are
131
+ not interchangeable, so pin a concrete model whenever a stored index must stay valid across
132
+ upgrades. Provider payloads under `raw` and reported token counts are controlled by the provider.
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
+
109
161
  ## Deprecation process
110
162
 
111
163
  Deprecated APIs are marked with `@deprecated` in declarations and described in the changelog. Removals
package/CHANGELOG.md CHANGED
@@ -4,6 +4,126 @@ Notable changes to this project are recorded here. The format follows [Keep a Ch
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.6.0] - 2026-09-03
8
+
9
+ Durable operations, the first half of the theme *work that outlives a process*. Long-running work now
10
+ runs through one lifecycle that survives a worker crash. Everything here is additive.
11
+
12
+ The rest of the theme — provider batch APIs, distributed rate limiting and circuit breaking,
13
+ filesystem and S3 asset stores, a generated model registry, and cross-family observability — moves to
14
+ 1.7.0. The operation handle they all sit behind ships here, which is what unblocks them.
15
+
16
+ ### Added
17
+
18
+ - **`OperationRunner` and a shared operation lifecycle.** `queued → running → succeeded | failed`,
19
+ with `retrying`, `cancelling`, `cancelled`, and `expired` covering the rest, exposed through the
20
+ new `nexus-ai-pro/operations` subpath. The runner persists a record before doing any work, claims
21
+ a lease, heartbeats while the executor runs, retries with backoff and jitter, dead-letters what
22
+ never succeeds, and emits signed webhooks.
23
+ - **Restart survival through leases rather than locks.** A worker that dies leaves a record whose
24
+ lease lapses; another worker's `recover()` sweep resumes it. Every store write is a compare-and-set
25
+ on the record's `sequence`, so two workers racing on one operation cannot both win, and the loser
26
+ is told it lost instead of silently overwriting.
27
+ - **`RedisOperationStore` and `BullMQOperationDispatcher`.** Redis persists records, with the
28
+ compare-and-set done in one Lua call when the client exposes `eval`. BullMQ dispatch queues the
29
+ operation id and its routing metadata only, never a payload.
30
+ - **Idempotency keys.** A key matching an existing record replays that operation instead of starting
31
+ a second one, so an ambiguous timeout cannot produce a duplicate charge.
32
+ - **Progress and cancellation.** An executor reports progress through `context.report()`, which
33
+ reaches both `handle.events()` and the persisted record. `runner.cancel()` settles an operation
34
+ owned by another worker, observed there through its heartbeat.
35
+ - **Signed webhooks, with the verifying half included.** Deliveries are HMAC-SHA256 over
36
+ `${timestamp}.${body}`, so a captured delivery cannot be replayed indefinitely.
37
+ `verifyOperationWebhook()` ships alongside `signOperationWebhook()` rather than leaving every
38
+ receiver to hand-roll a constant-time comparison. A failed delivery is reported and never fails
39
+ the operation it describes.
40
+ - **A binary-payload guard.** Persisting a result carrying a `Uint8Array`, `Buffer`, `Blob`, or
41
+ stream throws `OperationSerializationError` naming the exact path and pointing at `AssetStore`.
42
+ Base64 in a job payload inflates it by a third and most queue backends cap job size well below one
43
+ image, so this fails loudly at the boundary instead of appearing later as a truncated job.
44
+ - New subpaths `nexus-ai-pro/operations`, `operations/adapters`, and `operations/webhooks`.
45
+
46
+ ### Changed
47
+
48
+ - The operation lifecycle types moved from `types/images.ts` to a family-neutral `types/operations.ts`
49
+ and are re-exported from their old home, so existing image imports resolve unchanged. The image
50
+ family's private handle was replaced by the shared `LocalOperationHandle`, which is told to reject
51
+ with `ImageOperationCancelledError` so callers catching that error see no difference.
52
+ - `OperationStatus` gained `retrying`; `OperationEvent` gained `progress` and `retrying` variants and
53
+ an `attempt` field on `running`. Additive, but code switching exhaustively on either union should
54
+ add the new members.
55
+
56
+ ### Fixed
57
+
58
+ - A handle driven by the runner stayed in `queued` for the whole run, so no `running` event was
59
+ emitted and every `context.report()` call was silently dropped. Found while testing the runner's
60
+ event sequence; the handle now exposes `markRunning()` and the runner calls it before each attempt.
61
+
62
+ ## [1.5.0] - 2026-08-31
63
+
64
+ Embeddings become a first-class operation. `ai.embed()` gets the routing, caching, batching, budget,
65
+ retry, audit, and metrics that until now only completions had, so embedding spend is visible next to
66
+ completion spend instead of leaving the platform entirely. Everything here is additive.
67
+
68
+ ### Added
69
+
70
+ - **`ai.embed()` and `ai.embedOne()`.** A provider-neutral embeddings operation family behind
71
+ `EmbeddingManager`, reached through `ai.embeddings` or the new `nexus-ai-pro/embeddings` subpath.
72
+ The manager is built on first access, so a runtime that never embeds pays nothing for it.
73
+ - **Adapters for OpenAI, Google, Cohere, Mistral, and Ollama**, plus any OpenAI-compatible
74
+ `/embeddings` server through `OpenAIEmbeddingProvider`'s `baseUrl` and `providerName`. They are
75
+ registered automatically from the provider credentials already in `providers`, so a configured
76
+ chat provider makes `ai.embedOne('text')` work with no embedding-specific configuration. Explicit
77
+ registration through `registerEmbeddingProvider()` always wins, and auto-registration can be
78
+ turned off with `embeddings.autoRegisterProviders: false`.
79
+ - **Batching, deduplication, and per-input caching.** A batch larger than the model's limit is split
80
+ and run with bounded concurrency, and the vectors still come back in input order. Repeated texts
81
+ within one request are answered by a single provider call, and caching is keyed per input, so a
82
+ partially repeated batch only sends the texts it has not seen. `meta.batches`, `meta.cachedInputs`,
83
+ and `meta.deduplicatedInputs` report what actually happened.
84
+ - **Structured usage and numeric cost for embeddings.** `meta.usage` and `meta.cost` reuse the same
85
+ `TokenUsage` and `ResponseCost` shapes as completions, priced from a new embedding model registry.
86
+ When a provider reports no token counts — Google's `batchEmbedContents` does not — tokens are
87
+ estimated locally rather than reported as zero, so a call is never priced at nothing.
88
+ - **An embedding model registry** with dimensions, supported truncation sizes, input limits, batch
89
+ limits, and prices for eleven models across five providers, with aliases (`auto`, `embed-fast`,
90
+ `embed-quality`, `embed-multilingual`, `embed-local`) and full override through
91
+ `embeddings.models.registry`. It is deliberately separate from `KNOWN_MODELS`, because completion
92
+ routing scores models on context window, output price, and tool support, none of which an
93
+ embedding model has.
94
+ - **Capability refusal.** An unsupported `dimensions`, `inputType`, `encodingFormat`, or `truncate`
95
+ value throws `EmbeddingCapabilityError` before the provider call. Embeddings refuse rather than
96
+ drop: a vector built with different dimensions is silently incompatible with the vectors already
97
+ in a store, and the mismatch only surfaces later as unexplained retrieval quality loss. An option
98
+ the registry says nothing about is still passed through, and `providerOptions` reaches the
99
+ provider body untouched.
100
+ - **`toEmbeddingFunction()`**, which adapts the family to the plain `EmbeddingProvider` function that
101
+ `MemoryVectorStore`, the semantic cache, and RAG ingestion already accept. An existing vector store
102
+ gains routing, caching, retry, and metrics without changing its own contract.
103
+ - **`normalize`** for unit-length vectors, applied locally when the provider does not already return
104
+ them, and reapplied after a locally truncated vector loses its length.
105
+ - **A deterministic `MockEmbeddingProvider`** and `runEmbeddingProviderConformance()`, which checks
106
+ one vector per input, input order, consistent width, optional determinism, and an honored abort.
107
+ - New subpaths `nexus-ai-pro/embeddings`, `embeddings/adapters`, `embeddings/mock`, and
108
+ `embeddings/models`.
109
+
110
+ ### Changed
111
+
112
+ - `RateLimiter.check()` accepts the new structural `RateLimitedRequest` instead of a
113
+ `CompletionRequest`, so one limiter instance and one budget cover every operation family.
114
+ `CompletionRequest` still satisfies it, so no call site changes.
115
+ - Coverage rose from 82.6% lines / 68.9% branches / 78.2% functions to 85.7% / 70.9% / 80.8%, with
116
+ 65 new tests. `embeddings/providers.ts`, previously the weakest file in the package at 37% lines,
117
+ is now at 99%.
118
+
119
+ ### Notes
120
+
121
+ - Bundled embedding dimensions and prices are defaults, not financial truth; override them through
122
+ `embeddings.models.registry` when exact numbers matter.
123
+ - Which concrete model an embedding alias resolves to can change in a minor release. Two targets do
124
+ not produce interchangeable vectors, so pin a concrete model when a stored index must stay valid
125
+ across upgrades.
126
+
7
127
  ## [1.4.0] - 2026-08-25
8
128
 
9
129
  Request parity and cost truth: the completion request can now express what current models actually
@@ -201,7 +321,9 @@ shared policy vocabulary is in place for a future release that revisits this.
201
321
  - URL fetching rejects unsafe private-network targets and limits response reads.
202
322
  - CLI and audit findings no longer disclose detected secret values.
203
323
 
204
- [Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.4.0...HEAD
324
+ [Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.6.0...HEAD
325
+ [1.6.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.5.0...v1.6.0
326
+ [1.5.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.4.0...v1.5.0
205
327
  [1.4.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.3.0...v1.4.0
206
328
  [1.3.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.2.1...v1.3.0
207
329
  [1.2.1]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.2.0...v1.2.1
package/README.md CHANGED
@@ -83,6 +83,8 @@ You can still pass a plain `NexusAIConfig` to `new NexusAI(...)` when you want f
83
83
  - estimate tokens and cost before provider calls
84
84
  - cache exact or semantically similar prompts
85
85
  - add tools, agents, RAG context, evals, batch jobs, and queues
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
86
88
  - build persistent realtime voice agents with interruption, live tools, and normalized conversation state
87
89
  - keep TypeScript types around every request and response
88
90
 
@@ -416,6 +418,188 @@ const response = await ai.completeVerified(
416
418
  );
417
419
  ```
418
420
 
421
+ ## Durable Operations
422
+
423
+ Long-running work — an image render, a batch, anything asynchronous — runs through one lifecycle:
424
+ `queued → running → succeeded | failed`, with `retrying`, `cancelling`, `cancelled`, and `expired`
425
+ covering the rest. `OperationRunner` owns it end to end, so a crashed worker does not lose work.
426
+
427
+ The default store is in-process, so the small case needs no infrastructure:
428
+
429
+ ```ts
430
+ import { OperationRunner } from 'nexus-ai-pro/operations';
431
+
432
+ const runner = new OperationRunner<string>({ retry: { maxAttempts: 3, baseDelayMs: 500 } });
433
+
434
+ const handle = await runner.submit(async (context) => {
435
+ context.report({ completed: 1, total: 3 });
436
+ return doTheWork(context.signal);
437
+ });
438
+
439
+ for await (const event of handle.events()) {
440
+ console.log(event.type, event.sequence);
441
+ }
442
+
443
+ const value = await handle.result();
444
+ ```
445
+
446
+ Swapping the store makes the same code survive a restart. Nothing else changes:
447
+
448
+ ```ts
449
+ import { OperationRunner } from 'nexus-ai-pro/operations';
450
+ import { BullMQOperationDispatcher, RedisOperationStore } from 'nexus-ai-pro/operations/adapters';
451
+
452
+ const runner = new OperationRunner({
453
+ store: new RedisOperationStore(redis),
454
+ dispatcher: new BullMQOperationDispatcher(queue),
455
+ owner: process.env.HOSTNAME,
456
+ leaseMs: 30_000,
457
+ webhook: { url: 'https://app.example/hooks/operations', secret: process.env.HOOK_SECRET! },
458
+ });
459
+ ```
460
+
461
+ **How restart survival actually works.** A worker claims a lease before running and heartbeats
462
+ while it works. If the process dies, the lease simply lapses; another worker's `recover()` sweep
463
+ finds the record and resumes it. There is no distributed lock — every store write is a
464
+ compare-and-set on the record's `sequence`, so two workers racing on the same operation cannot both
465
+ win.
466
+
467
+ ```ts
468
+ // On startup, in each worker.
469
+ const resumed = await runner.recover(executor);
470
+ ```
471
+
472
+ Recovery is deliberately conservative: a record past its `expiresAt` is **expired** rather than
473
+ re-run, and one that has used every attempt is **dead-lettered** and parked for inspection, so a
474
+ permanently failing operation cannot be recovered forever.
475
+
476
+ **Idempotency.** An `idempotencyKey` that matches an existing record replays that operation instead
477
+ of starting a second one, which is what stops an ambiguous timeout from double-charging:
478
+
479
+ ```ts
480
+ const handle = await runner.submit(chargeAndRender, { idempotencyKey: `render:${orderId}` });
481
+ ```
482
+
483
+ **Webhooks** are signed with HMAC-SHA256 over `${timestamp}.${body}`, so a captured delivery cannot
484
+ be replayed indefinitely. The verifying half ships too — a receiver should never have to hand-roll a
485
+ constant-time comparison:
486
+
487
+ ```ts
488
+ import { OPERATION_WEBHOOK_SIGNATURE_HEADER, verifyOperationWebhook } from 'nexus-ai-pro/operations/webhooks';
489
+
490
+ app.post('/hooks/operations', (request, response) => {
491
+ const ok = verifyOperationWebhook(
492
+ request.rawBody,
493
+ request.header(OPERATION_WEBHOOK_SIGNATURE_HEADER),
494
+ process.env.HOOK_SECRET!,
495
+ );
496
+ response.sendStatus(ok ? 204 : 400);
497
+ });
498
+ ```
499
+
500
+ A failed delivery is reported through `onWebhookError` and never turns a completed operation into a
501
+ failed one.
502
+
503
+ **Binary payloads are refused, not truncated.** Persisting a result that carries a `Uint8Array`,
504
+ `Buffer`, or `Blob` throws `OperationSerializationError` naming the exact path. Base64 in a job
505
+ payload inflates it by a third and most queue backends cap job size well below one image, so the
506
+ bytes belong in an `AssetStore` with only a reference on the record. The BullMQ dispatcher likewise
507
+ queues the operation id and nothing else.
508
+
509
+ The image family already runs on this lifecycle, so `ai.images.submit()` reports the same events.
510
+
511
+ ## Embeddings
512
+
513
+ `ai.embed()` is a first-class operation, not a helper: it gets the same routing, caching, batching,
514
+ budget, retry, audit, and metrics as a completion, so embedding spend shows up next to completion
515
+ spend instead of being invisible.
516
+
517
+ The smallest call is one line, and needs no embedding-specific configuration — adapters are derived
518
+ from the provider credentials already in `providers`:
519
+
520
+ ```ts
521
+ import { NexusAI } from 'nexus-ai-pro';
522
+
523
+ const ai = new NexusAI({ providers: { openai: { apiKey: process.env.OPENAI_API_KEY! } } });
524
+
525
+ const vector = await ai.embedOne('Provider-neutral embeddings.');
526
+ ```
527
+
528
+ A batch returns vectors in input order regardless of how many provider calls the model's batch
529
+ limit required, and reports what the call cost:
530
+
531
+ ```ts
532
+ const response = await ai.embed({
533
+ input: documents.map((document) => document.text),
534
+ model: 'embed-quality',
535
+ inputType: 'document',
536
+ normalize: true,
537
+ });
538
+
539
+ response.vectors; // number[][], in input order
540
+ response.meta.batches; // provider calls the split required
541
+ response.meta.usage.inputTokens; // reported by the provider, or estimated when it reports none
542
+ response.meta.cost.amount; // numeric, priced from the embedding registry
543
+ response.meta.cachedInputs; // inputs answered from cache
544
+ response.meta.deduplicatedInputs; // repeated inputs answered from one call
545
+ ```
546
+
547
+ Caching is per input rather than per request, so a partially repeated batch only sends the texts it
548
+ has not seen. Repeated texts inside one batch are collapsed into a single provider call:
549
+
550
+ ```ts
551
+ const ai = new NexusAI({
552
+ providers: { openai: { apiKey: process.env.OPENAI_API_KEY! } },
553
+ embeddings: {
554
+ cache: { enabled: true, ttlSeconds: 86_400 },
555
+ costBudget: { enabled: true, maxEstimatedCost: 5 },
556
+ retry: { enabled: true, maxRetries: 2 },
557
+ fallback: [{ provider: 'cohere', model: 'embed-v4.0' }],
558
+ },
559
+ });
560
+ ```
561
+
562
+ Unlike a completion, an unsupported embedding option is **refused rather than dropped**. A vector
563
+ built with different dimensions or a different input type is silently incompatible with the vectors
564
+ already in a store, and the mismatch only surfaces later as unexplained retrieval quality loss:
565
+
566
+ ```ts
567
+ await ai.embed({ input: 'x', model: 'embed-english-v3.0', dimensions: 512 });
568
+ // EmbeddingCapabilityError: model has a fixed size of 1024 dimensions
569
+ ```
570
+
571
+ An option the registry says nothing about is still passed through, because an undeclared capability
572
+ means unknown rather than unsupported, and `providerOptions` reaches the provider body untouched for
573
+ anything the neutral contract does not model yet.
574
+
575
+ Existing vector stores keep their contract. `toEmbeddingFunction()` adapts the family to the plain
576
+ function `MemoryVectorStore`, the semantic cache, and RAG ingestion already accept:
577
+
578
+ ```ts
579
+ import { MemoryVectorStore, toEmbeddingFunction } from 'nexus-ai-pro';
580
+
581
+ const store = new MemoryVectorStore(toEmbeddingFunction(ai, { inputType: 'document' }));
582
+ ```
583
+
584
+ Bundled adapters cover OpenAI (and any OpenAI-compatible `/embeddings` server), Google, Cohere,
585
+ Mistral, and Ollama. A custom adapter implements `EmbeddingsProvider` and is checked against the
586
+ neutral contract by `runEmbeddingProviderConformance()`:
587
+
588
+ ```ts
589
+ import { OpenAIEmbeddingProvider } from 'nexus-ai-pro/embeddings/adapters';
590
+ import { MockEmbeddingProvider } from 'nexus-ai-pro/embeddings/mock';
591
+
592
+ ai.registerEmbeddingProvider('gateway', new OpenAIEmbeddingProvider({
593
+ apiKey: process.env.GATEWAY_KEY!,
594
+ baseUrl: 'https://gateway.internal/v1',
595
+ providerName: 'gateway',
596
+ }));
597
+ ai.registerEmbeddingProvider('mock', new MockEmbeddingProvider());
598
+ ```
599
+
600
+ Bundled embedding dimensions and prices are defaults, not financial truth. Override them through
601
+ `embeddings.models.registry` when exact numbers matter.
602
+
419
603
  ## Image Generation and Editing (Experimental)
420
604
 
421
605
  Image operations use a separate provider-neutral manager because generated assets have different
@@ -1133,6 +1317,13 @@ import { negotiateCompletionRequest } from 'nexus-ai-pro/capabilities';
1133
1317
  import { guardrailPolicy } from 'nexus-ai-pro/security';
1134
1318
  import { RedisCacheAdapter } from 'nexus-ai-pro/cache';
1135
1319
  import { DeepSeekProvider } from 'nexus-ai-pro/providers/deepseek';
1320
+ import { OperationRunner } from 'nexus-ai-pro/operations';
1321
+ import { RedisOperationStore } from 'nexus-ai-pro/operations/adapters';
1322
+ import { verifyOperationWebhook } from 'nexus-ai-pro/operations/webhooks';
1323
+ import { EmbeddingManager } from 'nexus-ai-pro/embeddings';
1324
+ import { OpenAIEmbeddingProvider } from 'nexus-ai-pro/embeddings/adapters';
1325
+ import { MockEmbeddingProvider } from 'nexus-ai-pro/embeddings/mock';
1326
+ import { KNOWN_EMBEDDING_MODELS } from 'nexus-ai-pro/embeddings/models';
1136
1327
  import { ImageManager } from 'nexus-ai-pro/images';
1137
1328
  import { MemoryAssetStore } from 'nexus-ai-pro/images/assets';
1138
1329
  import { MockImageProvider } from 'nexus-ai-pro/images/mock';
package/ROADMAP.md CHANGED
@@ -3,10 +3,11 @@
3
3
  This roadmap is a design proposal, not a compatibility promise. Stable and experimental
4
4
  surfaces are defined in [API_STABILITY.md](./API_STABILITY.md).
5
5
 
6
- Status baseline: **1.4.0**. 55 export subpaths, 12 completion providers, 99 registry models plus 63
7
- aliases. 163 unit tests pass; coverage sits at **82.6% lines / 68.9% branches / 78.2% functions**
8
- against gates of 80/68/75. CI verifies lint, format, build, tests, coverage, mock conformance,
9
- packed-package smoke, API contract, consumer type resolution, and clean install on Node 22 and 24.
6
+ Status baseline: **1.6.0**. 62 export subpaths, 12 completion providers, 5 embedding providers, 99
7
+ completion registry models plus 63 aliases, and 11 embedding models plus 5 aliases. 292 unit tests
8
+ pass; coverage sits at **86.3% lines / 71.6% branches / 81.4% functions** against gates of
9
+ 82/67/73. CI verifies lint, format, build, tests, coverage, mock conformance, packed-package smoke,
10
+ API contract, consumer type resolution, and clean install on Node 22 and 24.
10
11
 
11
12
  ---
12
13
 
@@ -91,7 +92,26 @@ parallel-tool-call, seed, top-k, and penalty controls; structured `usage` with c
91
92
  cached-write, and reasoning token counts; numeric `cost` priced per token class; and capability
92
93
  negotiation under a `strict`, `warn`, or `off` policy with registry provenance and a freshness check.
93
94
 
94
- ### 1.8 Operations, evaluation, and packaging
95
+ ### 1.8 Embeddings
96
+
97
+ Delivered in 1.5.0. `ai.embed()` and `ai.embedOne()` over a provider-neutral `EmbeddingsProvider`
98
+ contract with adapters for OpenAI (and any OpenAI-compatible server), Google, Cohere, Mistral, and
99
+ Ollama, auto-registered from existing provider credentials. Around them: model-registry routing with
100
+ aliases and per-provider fallback, batch splitting with bounded concurrency, within-request
101
+ deduplication, per-input caching, cost budgets, retry, structured usage and numeric cost, capability
102
+ refusal rather than silent option dropping, `toEmbeddingFunction()` for existing vector stores, a
103
+ deterministic mock, and a conformance harness.
104
+
105
+ ### 1.9 Durable operations
106
+
107
+ Delivered in 1.6.0. `OperationRunner` over an `OperationStore` contract, with a checked state
108
+ machine, leases and heartbeats, delayed retry with backoff and jitter, dead-lettering, progress
109
+ events, idempotency replay, crash recovery, and HMAC-signed webhooks with a matching verifier.
110
+ `MemoryOperationStore` keeps the single-process case dependency-free; `RedisOperationStore` and
111
+ `BullMQOperationDispatcher` make the same code survive a restart. Persisting raw bytes is refused
112
+ so binary media cannot end up in a queue payload.
113
+
114
+ ### 1.10 Operations, evaluation, and packaging
95
115
 
96
116
  Rate limiting, audit logging, in-memory and OpenTelemetry metrics sinks, Prometheus export, an
97
117
  OpenTelemetry trace exporter, provider health monitoring, `EvalRunner` with LLM-as-judge and a
@@ -107,21 +127,27 @@ trusted publishing with provenance.
107
127
 
108
128
  Ordered by how much each one costs a consumer today.
109
129
 
110
- ### 2.1 Only completions get the platform
130
+ ### 2.1 Media families still do not get the platform
111
131
 
112
- The pipeline, rate limiter, audit log, metrics collector, and cost budget are wired into
113
- `complete()` and `stream()` only. `ImageManager`, `VoiceManager`, `TelephonyManager`, and
114
- `RealtimeSession` emit nothing to `MetricsCollector` and pass through no rate limit or audit stage.
115
- An application running phone agents and image generation has observability for the smallest part of
116
- its spend. `PipelineContext` is also typed strictly around `CompletionRequest`/`NexusResponse`, so
117
- other families cannot reuse it without a refactor, and `PipelineStepName` ends in `| string`, which
118
- erases the union it defines.
132
+ Narrowed in 1.5.0: `EmbeddingManager` shares the runtime's rate limiter, audit log, and metrics
133
+ collector, and `RateLimiter` now buckets on a structural request rather than a `CompletionRequest`,
134
+ so a family no longer has to be a completion to be governed. `ImageManager`, `VoiceManager`,
135
+ `TelephonyManager`, and `RealtimeSession` still emit nothing to `MetricsCollector` and pass through
136
+ no rate limit or audit stage, so an application running phone agents and image generation has
137
+ observability for only part of its spend.
119
138
 
120
- ### 2.2 No first-class embeddings operation
139
+ The traced pipeline itself remains completion-only. `PipelineContext` is typed strictly around
140
+ `CompletionRequest`/`NexusResponse`, so other families cannot reuse it without a refactor, and
141
+ `PipelineStepName` ends in `| string`, which erases the union it defines. Embeddings therefore share
142
+ the observability primitives but not the step trace.
121
143
 
122
- `src/embeddings/providers.ts` exports OpenAI/Gemini/Cohere factory functions used only by the
123
- semantic cache and vector store. There is no `ai.embed()`, so embeddings get no routing, caching,
124
- budget, retry, audit, or metrics — and no provider-neutral batch contract.
144
+ ### 2.2 Embeddings beyond text retrieval
145
+
146
+ Closed for text in 1.5.0: `ai.embed()` is an operation family with routing, per-input caching,
147
+ batching, deduplication, budget, retry, audit, and metrics, over five adapters and its own model
148
+ registry. What remains is everything the family does not yet cover: a provider batch-API tier for
149
+ large asynchronous ingestion jobs, image and multimodal embeddings, a distributed cache adapter for
150
+ vectors shared across processes, and reranking as a sibling operation.
125
151
 
126
152
  ### 2.3 The model registry is still hand-maintained
127
153
 
@@ -130,13 +156,17 @@ visible. The data itself is still a hand-written TypeScript literal of 99 models
130
156
  Generation from versioned provider data remains outstanding, and the Claude 5 reasoning bug 1.4.0
131
157
  fixed is the kind of error generation would have prevented.
132
158
 
133
- ### 2.4 Durable-execution primitives are process-local
159
+ ### 2.4 Durable execution is only half distributed
160
+
161
+ Closed in 1.6.0 for operations: `OperationHandle` is no longer process-bound, cross-process recovery
162
+ works through lapsed leases, and idempotency keys deduplicate across workers.
134
163
 
135
- `RateLimiter` is an in-memory token bucket with no distributed adapter. There is no circuit breaker
136
- (health monitoring exists, but nothing opens a circuit). `OperationHandle` is process-bound;
137
- `MemoryAssetStore` is process-local. Realtime conversation snapshots and exports live in memory.
138
- Cross-process recovery, distributed deduplication, and provider-state replay remain application
139
- responsibilities.
164
+ Still process-local: `RateLimiter` is an in-memory token bucket with no distributed adapter, there is
165
+ no circuit breaker (health monitoring exists, but nothing opens a circuit), `MemoryAssetStore` holds
166
+ assets in one process, and realtime conversation snapshots and exports live in memory. Provider-state
167
+ replay remains an application responsibility. One caveat on what did land: `RedisOperationStore` is
168
+ atomic only when the client exposes `eval`; without it the compare-and-set degrades to a
169
+ read-compare-write that narrows but does not close the race.
140
170
 
141
171
  ### 2.5 Image family blockers
142
172
 
@@ -160,14 +190,14 @@ local estimate, and a published latency benchmark methodology remain follow-on w
160
190
  ### 2.7 Test coverage weak spots
161
191
 
162
192
  1.4.0 closed the worst of these: the agent loop went from 40% to 95% line coverage, the rules router
163
- from 34% to 91%, and the evaluation metric library from 40% (5% of functions) to 99%. What is still
164
- thin:
193
+ from 34% to 91%, and the evaluation metric library from 40% (5% of functions) to 99%. 1.5.0 took
194
+ `embeddings/providers.ts` from 37% to 99% and landed the new embeddings family at 95–100% across its
195
+ seven files. What is still thin:
165
196
 
166
197
  | Area | Lines | Note |
167
198
  | --- | --- | --- |
168
199
  | `hallucination/rag.ts`, `verification.ts` | 38% / 47% | Grounding claims deserve tests. |
169
200
  | `workflow/chains.ts`, `domain.ts` | 51% / 50% | |
170
- | `embeddings/providers.ts` | 37% | Should be covered as part of the embeddings operation family. |
171
201
  | `security/pii-detector.ts`, `semantic-injection-classifier.ts` | 49% / 49% | Security-relevant. |
172
202
  | `jobs/queue.ts`, `batch.ts` | 47% / 53% | |
173
203
  | `rag/file-ingestion.ts` | 43% | |
@@ -219,15 +249,44 @@ policy vocabulary is in place for a later release that revisits this.
219
249
 
220
250
  ---
221
251
 
222
- ## 4. Next release: 1.5.0 — durable operations and batch economics
252
+ ## 4. Shipped in 1.5.0 — embeddings as an operation family
253
+
254
+ `ai.embed()` with routing, per-input caching, batching, deduplication, budget, retry, audit, and
255
+ metrics; the existing factory functions became one adapter shape behind it, and
256
+ `toEmbeddingFunction()` keeps `MemoryVectorStore`, the semantic cache, and RAG ingestion working
257
+ unchanged. Embeddings refuse an unsupported option rather than dropping it, because a mis-sized
258
+ vector is silently incompatible with a populated store rather than merely different.
259
+
260
+ Deliberately deferred: the provider batch-API tier, which belongs with the durable operation handle
261
+ below rather than with the synchronous path.
262
+
263
+ ## 5. Shipped in 1.6.0 — durable operations
264
+
265
+ The first half of *work that outlives a process*. One lifecycle now covers every long-running
266
+ family, and it survives a worker crash.
267
+
268
+ - **Operation state machine.** `queued → running → retrying → succeeded | failed | cancelling |
269
+ cancelled | expired`, with leases, heartbeats, progress events, delayed retry, dead-letter
270
+ handling, timestamps, signed webhooks, and trace propagation. The image family runs on it, and the
271
+ lifecycle types moved to a family-neutral `types/operations.ts` re-exported from their old home.
272
+ - **Durable operation adapters.** `RedisOperationStore` persists records with a compare-and-set on
273
+ `sequence`, done in one Lua call when the client exposes `eval`. `BullMQOperationDispatcher`
274
+ queues the operation id only. A record carrying raw bytes is refused rather than serialized.
275
+ - **Idempotency and recovery.** A matching key replays an accepted operation instead of starting a
276
+ second; `recover()` resumes records whose lease lapsed, expiring those past their deadline and
277
+ dead-lettering those that used every attempt.
278
+
279
+ Restart survival comes from leases rather than locks. There is no distributed lock anywhere: every
280
+ store write is a compare-and-set, so a losing writer is told it lost instead of overwriting.
281
+
282
+ Deliberately deferred: everything below, which needed the operation handle to exist first.
283
+
284
+ ---
285
+
286
+ ## 6. Next release: 1.7.0 — batch economics and distributed limits
223
287
 
224
- The theme is work that outlives a process.
288
+ The rest of the theme, now that the handle they sit behind exists.
225
289
 
226
- - **Operation state machine.** `queued → running → succeeded | failed | cancelling | cancelled | expired`,
227
- with leases, heartbeats, progress events, delayed retry, dead-letter handling, timestamps, signed
228
- webhooks, and trace propagation. Applies to images first, then to any long-running family.
229
- - **Durable operation adapters.** Redis and BullMQ-backed handles so `submit()` survives a restart.
230
- Queue asset references only; never JSON-serialize binary media into job payloads.
231
290
  - **Provider batch APIs.** OpenAI Batch and Anthropic Message Batches behind the same operation
232
291
  handle, exposing the discounted asynchronous tier that local `runBatch()` concurrency cannot reach.
233
292
  Idempotency keys replay an accepted operation; ambiguous timeouts must not produce duplicate
@@ -235,19 +294,17 @@ The theme is work that outlives a process.
235
294
  - **Distributed rate limiting and circuit breaking.** Redis-backed limiter adapter, plus a breaker
236
295
  that consumes existing `ProviderHealthMonitor` signals and trips routing away from a failing
237
296
  provider.
238
- - **Embeddings as an operation family.** `ai.embed()` with routing, caching, batching, budget, retry,
239
- audit, and metrics; existing factories become adapters behind it.
240
297
  - **Filesystem and S3-compatible asset stores** implementing the existing `AssetStore` interface,
241
298
  with retention, tenant ownership, checksums, streaming, and signing. Provider URLs remain temporary
242
299
  delivery locations, never durable storage.
243
300
  - **Generated model registry** from versioned provider data, consuming the 1.4.0 provenance fields.
244
301
  - **Cross-family observability.** Route image, voice, telephony, and realtime operations through the
245
- rate limiter, audit log, and metrics collector, closing gap 2.3 without waiting for the full
302
+ rate limiter, audit log, and metrics collector, closing gap 2.1 without waiting for the full
246
303
  lifecycle refactor.
247
304
 
248
305
  ---
249
306
 
250
- ## 5. 1.6.0 — image portability, then promotion
307
+ ## 7. 1.8.0 — image portability, then promotion
251
308
 
252
309
  Images cannot leave experimental until the neutral contract survives a second wire protocol.
253
310
 
@@ -275,7 +332,7 @@ label come off.
275
332
 
276
333
  ---
277
334
 
278
- ## 6. 2.0 candidate — one lifecycle for every operation
335
+ ## 8. 2.0 candidate — one lifecycle for every operation
279
336
 
280
337
  Create an internal typed lifecycle and adapt every family to it:
281
338
 
@@ -298,7 +355,7 @@ Also in the 2.0 window, because each requires a breaking change:
298
355
 
299
356
  ---
300
357
 
301
- ## 7. Longer-term backlog
358
+ ## 9. Longer-term backlog
302
359
 
303
360
  1. MCP client and server adapters with asset and tool interoperability.
304
361
  2. Video generation through the same asynchronous operation, job, and asset contracts.
@@ -323,7 +380,7 @@ Also in the 2.0 window, because each requires a breaking change:
323
380
 
324
381
  ---
325
382
 
326
- ## 8. Design notes carried forward
383
+ ## 10. Design notes carried forward
327
384
 
328
385
  These decisions predate this revision and still hold.
329
386
 
@@ -375,7 +432,7 @@ await ai.images.edit({
375
432
 
376
433
  ---
377
434
 
378
- ## 9. How an item graduates
435
+ ## 11. How an item graduates
379
436
 
380
437
  1. Provider-neutral types and a deterministic mock land first.
381
438
  2. One real adapter proves the contract; conformance fixtures cover it.