nexus-ai-pro 1.0.0 → 1.2.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 (164) hide show
  1. package/API_STABILITY.md +24 -6
  2. package/CHANGELOG.md +46 -2
  3. package/README.md +147 -7
  4. package/ROADMAP.md +17 -1
  5. package/dist/core/config-builder.d.ts +3 -0
  6. package/dist/core/config-builder.js +5 -0
  7. package/dist/core/nexus.d.ts +32 -1
  8. package/dist/core/nexus.js +48 -0
  9. package/dist/images/assets.d.ts +115 -0
  10. package/dist/images/assets.js +343 -0
  11. package/dist/images/errors.d.ts +35 -0
  12. package/dist/images/errors.js +79 -0
  13. package/dist/images/index.d.ts +4 -0
  14. package/dist/images/index.js +2 -0
  15. package/dist/images/manager.d.ts +23 -0
  16. package/dist/images/manager.js +574 -0
  17. package/dist/images/mock.d.ts +17 -0
  18. package/dist/images/mock.js +237 -0
  19. package/dist/images/openai.d.ts +46 -0
  20. package/dist/images/openai.js +355 -0
  21. package/dist/index.d.ts +5 -0
  22. package/dist/index.js +3 -0
  23. package/dist/telephony/index.d.ts +3 -1
  24. package/dist/telephony/index.js +1 -0
  25. package/dist/telephony/manager.d.ts +6 -1
  26. package/dist/telephony/manager.js +59 -0
  27. package/dist/telephony/providers/twilio.d.ts +9 -1
  28. package/dist/telephony/providers/twilio.js +165 -12
  29. package/dist/telephony/realtime-bridge.d.ts +58 -0
  30. package/dist/telephony/realtime-bridge.js +180 -0
  31. package/dist/testing/image-provider-conformance.d.ts +33 -0
  32. package/dist/testing/image-provider-conformance.js +324 -0
  33. package/dist/types/config.d.ts +2 -0
  34. package/dist/types/images.d.ts +245 -0
  35. package/dist/types/images.js +1 -0
  36. package/dist/types/telephony.d.ts +87 -0
  37. package/dist-cjs/agent/loop.js +93 -0
  38. package/dist-cjs/agent/tool.js +50 -0
  39. package/dist-cjs/cache/adapters.js +84 -0
  40. package/dist-cjs/cache/memory-cache.js +106 -0
  41. package/dist-cjs/cache/semantic-cache.js +75 -0
  42. package/dist-cjs/connectors/web.js +401 -0
  43. package/dist-cjs/context/index.js +5 -0
  44. package/dist-cjs/context/window.js +284 -0
  45. package/dist-cjs/core/config-builder.js +203 -0
  46. package/dist-cjs/core/create-nexus.js +184 -0
  47. package/dist-cjs/core/nexus.js +897 -0
  48. package/dist-cjs/core/response-format.js +109 -0
  49. package/dist-cjs/core/secure-stream.js +153 -0
  50. package/dist-cjs/core/streaming.js +35 -0
  51. package/dist-cjs/embeddings/providers.js +65 -0
  52. package/dist-cjs/evals/index.js +24 -0
  53. package/dist-cjs/evals/judge.js +158 -0
  54. package/dist-cjs/evals/metrics.js +171 -0
  55. package/dist-cjs/evals/runner.js +64 -0
  56. package/dist-cjs/hallucination/consistency.js +92 -0
  57. package/dist-cjs/hallucination/factual.js +37 -0
  58. package/dist-cjs/hallucination/knowledge-graph.js +74 -0
  59. package/dist-cjs/hallucination/rag.js +53 -0
  60. package/dist-cjs/hallucination/retrieval.js +78 -0
  61. package/dist-cjs/hallucination/verification.js +139 -0
  62. package/dist-cjs/images/assets.js +351 -0
  63. package/dist-cjs/images/errors.js +90 -0
  64. package/dist-cjs/images/index.js +14 -0
  65. package/dist-cjs/images/manager.js +578 -0
  66. package/dist-cjs/images/mock.js +241 -0
  67. package/dist-cjs/images/openai.js +360 -0
  68. package/dist-cjs/index.js +230 -0
  69. package/dist-cjs/jobs/batch.js +35 -0
  70. package/dist-cjs/jobs/durable-adapters.js +55 -0
  71. package/dist-cjs/jobs/queue.js +61 -0
  72. package/dist-cjs/models/registry.js +52 -0
  73. package/dist-cjs/next/route-handler.js +38 -0
  74. package/dist-cjs/ops/audit-logger.js +51 -0
  75. package/dist-cjs/ops/health.js +71 -0
  76. package/dist-cjs/ops/metrics.js +131 -0
  77. package/dist-cjs/ops/otel-tracing.js +54 -0
  78. package/dist-cjs/ops/rate-limiter.js +38 -0
  79. package/dist-cjs/optimizer/budget.js +93 -0
  80. package/dist-cjs/optimizer/cost.js +37 -0
  81. package/dist-cjs/optimizer/densifier.js +84 -0
  82. package/dist-cjs/optimizer/index.js +65 -0
  83. package/dist-cjs/package.json +3 -0
  84. package/dist-cjs/pipeline/pipeline.js +111 -0
  85. package/dist-cjs/pipeline/types.js +2 -0
  86. package/dist-cjs/providers/anthropic.js +253 -0
  87. package/dist-cjs/providers/azure-openai.js +30 -0
  88. package/dist-cjs/providers/base.js +172 -0
  89. package/dist-cjs/providers/cohere.js +123 -0
  90. package/dist-cjs/providers/deepseek.js +18 -0
  91. package/dist-cjs/providers/errors.js +141 -0
  92. package/dist-cjs/providers/google.js +251 -0
  93. package/dist-cjs/providers/groq.js +54 -0
  94. package/dist-cjs/providers/llamacpp.js +19 -0
  95. package/dist-cjs/providers/lmstudio.js +19 -0
  96. package/dist-cjs/providers/mistral.js +54 -0
  97. package/dist-cjs/providers/ollama.js +163 -0
  98. package/dist-cjs/providers/openai.js +474 -0
  99. package/dist-cjs/providers/openrouter.js +23 -0
  100. package/dist-cjs/providers/type-guards.js +43 -0
  101. package/dist-cjs/rag/file-ingestion.js +60 -0
  102. package/dist-cjs/rag/ingestion.js +52 -0
  103. package/dist-cjs/realtime/conversation.js +414 -0
  104. package/dist-cjs/realtime/errors.js +79 -0
  105. package/dist-cjs/realtime/events.js +43 -0
  106. package/dist-cjs/realtime/id.js +12 -0
  107. package/dist-cjs/realtime/index.js +41 -0
  108. package/dist-cjs/realtime/mock.transport.js +173 -0
  109. package/dist-cjs/realtime/openai-events.js +409 -0
  110. package/dist-cjs/realtime/openai-server.js +143 -0
  111. package/dist-cjs/realtime/openai-webrtc.transport.js +875 -0
  112. package/dist-cjs/realtime/openai-websocket.transport.js +424 -0
  113. package/dist-cjs/realtime/provider.js +64 -0
  114. package/dist-cjs/realtime/session.js +967 -0
  115. package/dist-cjs/realtime/tools.js +364 -0
  116. package/dist-cjs/realtime/transport-utils.js +259 -0
  117. package/dist-cjs/realtime/types.js +2 -0
  118. package/dist-cjs/router/auto-router.js +255 -0
  119. package/dist-cjs/router/failover.js +382 -0
  120. package/dist-cjs/router/index.js +36 -0
  121. package/dist-cjs/router/rules-router.js +72 -0
  122. package/dist-cjs/router/types.js +2 -0
  123. package/dist-cjs/security/index.js +223 -0
  124. package/dist-cjs/security/injection-calibration.js +56 -0
  125. package/dist-cjs/security/injection-detector.js +84 -0
  126. package/dist-cjs/security/input-guard.js +127 -0
  127. package/dist-cjs/security/output-guard.js +238 -0
  128. package/dist-cjs/security/pii-detector.js +70 -0
  129. package/dist-cjs/security/policies.js +65 -0
  130. package/dist-cjs/security/prompt-hardening.js +22 -0
  131. package/dist-cjs/security/schema-validator.js +69 -0
  132. package/dist-cjs/security/semantic-injection-classifier.js +79 -0
  133. package/dist-cjs/security/upload-scanner.js +79 -0
  134. package/dist-cjs/telephony/errors.js +21 -0
  135. package/dist-cjs/telephony/index.js +13 -0
  136. package/dist-cjs/telephony/manager.js +141 -0
  137. package/dist-cjs/telephony/providers/twilio.js +435 -0
  138. package/dist-cjs/telephony/realtime-bridge.js +184 -0
  139. package/dist-cjs/telephony/twiml.js +90 -0
  140. package/dist-cjs/testing/image-provider-conformance.js +328 -0
  141. package/dist-cjs/testing/provider-conformance.js +158 -0
  142. package/dist-cjs/types/agent.js +2 -0
  143. package/dist-cjs/types/config.js +2 -0
  144. package/dist-cjs/types/context-window.js +2 -0
  145. package/dist-cjs/types/images.js +2 -0
  146. package/dist-cjs/types/messages.js +2 -0
  147. package/dist-cjs/types/optimizer.js +2 -0
  148. package/dist-cjs/types/planning.js +2 -0
  149. package/dist-cjs/types/providers.js +604 -0
  150. package/dist-cjs/types/response.js +2 -0
  151. package/dist-cjs/types/security.js +2 -0
  152. package/dist-cjs/types/telephony.js +2 -0
  153. package/dist-cjs/types/voice.js +2 -0
  154. package/dist-cjs/utils/ids.js +7 -0
  155. package/dist-cjs/utils/logger.js +71 -0
  156. package/dist-cjs/utils/tokenizer.js +45 -0
  157. package/dist-cjs/voice/errors.js +21 -0
  158. package/dist-cjs/voice/index.js +10 -0
  159. package/dist-cjs/voice/manager.js +110 -0
  160. package/dist-cjs/voice/providers/openai.js +287 -0
  161. package/dist-cjs/voice/session.js +230 -0
  162. package/dist-cjs/workflow/chains.js +135 -0
  163. package/dist-cjs/workflow/domain.js +118 -0
  164. package/package.json +135 -55
package/API_STABILITY.md CHANGED
@@ -1,10 +1,13 @@
1
1
  # API Stability Policy
2
2
 
3
- This policy describes compatibility guarantees for `nexus-ai-pro` beginning with version 0.9.0.
3
+ This policy describes compatibility guarantees for `nexus-ai-pro`. The 1.x compatibility guarantees
4
+ apply beginning with version 1.0.0.
4
5
 
5
6
  ## Release stages
6
7
 
7
- The package is pre-1.0. Minor releases may contain breaking changes when they are necessary, but those changes will be called out in the changelog with migration guidance. Patch releases are intended to remain backward compatible.
8
+ The package follows semantic versioning. Within the 1.x line, incompatible changes to stable public
9
+ APIs require a major release. Minor releases may add backward-compatible features, and patch releases
10
+ are intended to remain backward compatible.
8
11
 
9
12
  The following surfaces are public and covered by this policy:
10
13
 
@@ -33,11 +36,12 @@ explicit `realtime/session`, `realtime/tools`, `realtime/conversation`, `realtim
33
36
  normalized event names, configuration fields, and conversation/export shapes follow the same patch
34
37
  compatibility rules as the rest of the package.
35
38
 
36
- The realtime surface is still pre-1.0. Provider-specific wire events returned by `openai-events`, values
39
+ The realtime surface follows the 1.x compatibility rules for its normalized public API. Provider-specific
40
+ wire events returned by `openai-events`, values
37
41
  under `raw`, SDP/ICE behavior, and upstream model or voice availability are controlled by the provider
38
42
  and are not normalized compatibility guarantees. New normalized event variants or optional metrics may
39
- be added in a minor release. A necessary incompatible change during 0.x will be documented in a minor
40
- release with migration guidance.
43
+ be added in a minor release. Incompatible normalized API changes require a major release unless an urgent
44
+ security fix makes preserving the old behavior unsafe.
41
45
 
42
46
  Platform interfaces for WebRTC and WebSocket are structural so applications can inject browser, server,
43
47
  mobile, or test adapters without a framework dependency. A documented structural member is public;
@@ -48,8 +52,22 @@ turn-oriented transcription/completion/speech workflow. `RealtimeSession` owns a
48
52
  live events, interruption, and normalized realtime conversation state. Neither is a compatibility alias
49
53
  for the other.
50
54
 
55
+ ## Image API stage
56
+
57
+ The `nexus-ai-pro/images` family is experimental in production-readiness, but published normalized
58
+ types, manager methods, operation events, and explicit subpath exports still follow the 1.x semantic
59
+ versioning rules. The experimental label does not permit an incompatible minor or patch release.
60
+
61
+ Provider payloads under `raw`, provider-specific error metadata, temporary delivery URLs, upstream model
62
+ availability, and generative output are not normalized compatibility guarantees. The current local
63
+ operation handle is process-bound and does not promise durable recovery, distributed cancellation, or
64
+ exactly-once provider execution. Those capabilities will use additive adapters and contracts when added.
65
+ `MemoryAssetStore` is likewise process-local and does not promise cross-process durability or shared
66
+ tenant state.
67
+
51
68
  ## Deprecation process
52
69
 
53
- Deprecated APIs are marked with `@deprecated` in declarations and described in the changelog. During the 0.x series, removal will not normally occur earlier than the next minor release. After 1.0, removals will be reserved for major releases, except when an urgent security issue requires otherwise.
70
+ Deprecated APIs are marked with `@deprecated` in declarations and described in the changelog. Removals
71
+ are reserved for major releases, except when an urgent security issue requires otherwise.
54
72
 
55
73
  Report accidental compatibility regressions through the project issue tracker. Security issues should follow [SECURITY.md](./SECURITY.md).
package/CHANGELOG.md CHANGED
@@ -1,9 +1,52 @@
1
1
  # Changelog
2
2
 
3
- Notable changes to this project are recorded here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and releases use semantic versioning while accounting for the pre-1.0 stability policy.
3
+ Notable changes to this project are recorded here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and releases use semantic versioning.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.2.0] - 2026-08-10
8
+
9
+ ### Added
10
+
11
+ - A `telephony/realtime-bridge` subpath joining a provider media stream to a realtime session:
12
+ inbound-only caller audio forwarding, outbound audio framing in emission order, barge-in that clears
13
+ provider-queued audio before cancelling the response, playback marks that resolve
14
+ `markAudioPlayed()`, stream lifecycle, and custom stream parameters for tenant routing.
15
+ - `twilioRealtimeAudioOptions()` pinning both directions to 8 kHz G.711 mu-law so telephony audio
16
+ reaches the model without transcoding.
17
+ - Call control and usage metering on `TelephonyProvider` and the Twilio adapter: `getCall`, `endCall`,
18
+ and `parseStatusCallback`, surfaced on `NexusAI` as `getCall`, `endCall`, and
19
+ `parseTelephonyStatusCallback`. Status callbacks carry the authoritative billable call duration.
20
+ - Phone-number management on `TelephonyProvider` and the Twilio adapter: `listPhoneNumbers` and
21
+ `updatePhoneNumber`, so an app can point a number at its own voice webhook and status callback.
22
+
23
+ ### Changed
24
+
25
+ - The package now ships parallel ESM and CommonJS builds. Every export subpath gained a `require`
26
+ condition, so NestJS and other `"module": "commonjs"` consumers can load it without a dynamic-import
27
+ shim. Types stay shared between both builds, and the packed-size guards were raised to account for the
28
+ duplicated JS payload.
29
+
30
+ ## [1.1.0] - 2026-07-28
31
+
32
+ ### Added
33
+
34
+ - An experimental provider-neutral image operation family with portable asset locations,
35
+ `ImageManager`, generation/editing, strict capability negotiation, runtime provider registration,
36
+ visual-safety hooks, and cancellable local operation handles.
37
+ - Opt-in `images`, `images/assets`, `images/mock`, and `images/openai` subpaths with a deterministic
38
+ mock provider and an OpenAI Image API adapter for one-shot generation and reference-based editing.
39
+ - An image-provider conformance harness covering normalized assets, metadata, generation, editing, and
40
+ pre-aborted calls.
41
+ - A bounded tenant-isolated `MemoryAssetStore` with retention, defensive byte copies, computed SHA-256
42
+ checksums, capacity enforcement, and optional HTTP(S) signing.
43
+
44
+ ### Changed
45
+
46
+ - Reconciled the changelog and API stability policy with the tagged 1.0.0 release.
47
+
48
+ ## [1.0.0] - 2026-07-17
49
+
7
50
  ### Added
8
51
 
9
52
  - An opt-in realtime package family with `realtime`, `realtime/session`, `realtime/tools`,
@@ -55,5 +98,6 @@ Notable changes to this project are recorded here. The format follows [Keep a Ch
55
98
  - URL fetching rejects unsafe private-network targets and limits response reads.
56
99
  - CLI and audit findings no longer disclose detected secret values.
57
100
 
58
- [Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v0.9.0...HEAD
101
+ [Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.0.0...HEAD
102
+ [1.0.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/releases/tag/v1.0.0
59
103
  [0.9.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/releases/tag/v0.9.0
package/README.md CHANGED
@@ -7,7 +7,7 @@ Use the whole pipeline for production AI features, or turn pieces off when you o
7
7
  - NPM: https://www.npmjs.com/package/nexus-ai-pro
8
8
  - GitHub: https://github.com/mkhitar-abrahamyan/nexus-ai
9
9
  - Full technical notes and manual test cases: [NEXUS.md](./NEXUS.md)
10
- - Planned image generation and infrastructure work: [ROADMAP.md](./ROADMAP.md)
10
+ - Image generation status and remaining infrastructure work: [ROADMAP.md](./ROADMAP.md)
11
11
 
12
12
  ## Install
13
13
 
@@ -15,7 +15,9 @@ Use the whole pipeline for production AI features, or turn pieces off when you o
15
15
  npm install nexus-ai-pro
16
16
  ```
17
17
 
18
- Requires Node.js 22 or newer. `nexus-ai-pro` is ESM-only: use `import` from an ES module rather than CommonJS `require()`.
18
+ Requires Node.js 22 or newer. The package is authored as ESM and ships both an ESM and a CommonJS build,
19
+ so `import` and `require()` both work — including from NestJS and other apps compiled with
20
+ `"module": "commonjs"`. Types are shared between both builds.
19
21
  The main Nexus pipeline is Node-oriented; the isolated realtime subpaths use portable structural
20
22
  interfaces and can be bundled for modern browsers.
21
23
 
@@ -307,6 +309,63 @@ const response = await ai.completeVerified(
307
309
  );
308
310
  ```
309
311
 
312
+ ## Image Generation and Editing (Experimental)
313
+
314
+ Image operations use a separate provider-neutral manager because generated assets have different
315
+ capabilities, delivery modes, safety checks, and lifecycles from text completions.
316
+
317
+ ```ts
318
+ import { NexusAI } from 'nexus-ai-pro';
319
+ import { OpenAIImageProvider } from 'nexus-ai-pro/images/openai';
320
+
321
+ const openaiImages = new OpenAIImageProvider({
322
+ apiKey: process.env.OPENAI_API_KEY!,
323
+ });
324
+
325
+ const mediaAi = new NexusAI({
326
+ providers: {},
327
+ images: {
328
+ defaultProvider: 'openai',
329
+ providers: { openai: openaiImages },
330
+ },
331
+ });
332
+
333
+ const image = await mediaAi.images.generate({
334
+ model: 'auto',
335
+ prompt: 'A clean product photograph of a red mechanical keyboard',
336
+ dimensions: { width: 1536, height: 1024 },
337
+ quality: 'high',
338
+ delivery: { kind: 'bytes', format: 'png' },
339
+ });
340
+
341
+ console.log(image.assets[0]?.location);
342
+ ```
343
+
344
+ `ImageProvider` is independent from completion providers. Explicit options are negotiated against the
345
+ selected provider's declared capabilities, so unsupported formats, delivery kinds, masks, seeds, or
346
+ dimensions fail before a provider call. The OpenAI adapter supports one-shot generation and
347
+ reference-based editing through byte assets. Masked OpenAI edits remain disabled until a transformer can
348
+ verify dimensions and convert the neutral mask contract to OpenAI's alpha-channel semantics.
349
+
350
+ Use `submit()` for a cancellable local operation handle with replayable lifecycle events:
351
+
352
+ ```ts
353
+ const operation = mediaAi.images.submit('generate', {
354
+ prompt: 'A minimal blue geometric poster',
355
+ delivery: { kind: 'bytes', format: 'png' },
356
+ });
357
+
358
+ for await (const event of operation.events()) {
359
+ console.log(event.type);
360
+ }
361
+ ```
362
+
363
+ For deterministic tests, register `MockImageProvider` from `nexus-ai-pro/images/mock`. The current
364
+ `submit()` handle is in-process; durable leases, recovery, distributed deduplication, and webhooks remain
365
+ future infrastructure work. `nexus-ai-pro/images/assets` includes a bounded, tenant-isolated
366
+ `MemoryAssetStore` for local development and single-process workloads; it computes SHA-256 checksums,
367
+ copies bytes at its boundaries, enforces retention and capacity, and never silently evicts live assets.
368
+
310
369
  ## Voice Integrations
311
370
 
312
371
  Voice support is optional and split into small imports.
@@ -734,6 +793,80 @@ const call = await ai.createCall({
734
793
 
735
794
  For smaller apps, skip Twilio entirely and register your own `TelephonyProvider`.
736
795
 
796
+ ### Phone Agents: Bridging a Call to a Realtime Session
797
+
798
+ A phone agent needs the telephony media stream and a realtime session joined together. The bridge owns
799
+ that audio path — caller audio in, assistant audio out, barge-in, playback marks, and stream lifecycle —
800
+ while session configuration, tools, and authorization stay in application code.
801
+
802
+ ```ts
803
+ import { createTelephonyRealtimeBridge, twilioRealtimeAudioOptions } from 'nexus-ai-pro/telephony/realtime-bridge';
804
+ import { OpenAIWebSocketTransport } from 'nexus-ai-pro/realtime/openai-websocket';
805
+ import { createRealtimeSession } from 'nexus-ai-pro/realtime/session';
806
+
807
+ // One socket per call, from your WebSocket server.
808
+ function handleCallSocket(socket: AppWebSocket) {
809
+ const session = createRealtimeSession({
810
+ provider: 'openai',
811
+ model: 'gpt-realtime',
812
+ transport: new OpenAIWebSocketTransport({ apiKey: process.env.OPENAI_API_KEY!, webSocketFactory }),
813
+ modalities: ['audio'],
814
+ instructions: 'You are a scheduling assistant.',
815
+ tools: [checkAvailability, createBooking],
816
+ // Telephony audio is 8 kHz G.711 mu-law; this passes it through untranscoded.
817
+ audio: twilioRealtimeAudioOptions({ output: { voice: 'alloy' } }),
818
+ });
819
+
820
+ const bridge = createTelephonyRealtimeBridge({
821
+ session,
822
+ telephony,
823
+ send: (message) => socket.send(message.body),
824
+ onStart: (event) => logger.info(`call ${event.callId} started for ${event.parameters?.tenant}`),
825
+ onStop: () => persistTranscript(session.export('json')),
826
+ onError: (error) => logger.error(error),
827
+ });
828
+
829
+ socket.on('message', (raw) => bridge.handleMessage(String(raw)));
830
+ socket.on('close', () => bridge.close());
831
+ }
832
+ ```
833
+
834
+ The bridge feeds only inbound caller audio to the model, so an echoed outbound track never loops back.
835
+ On caller speech it clears audio already queued at the provider and cancels the in-flight response, which
836
+ is what stops the assistant talking over an interruption. Custom `<Parameter>` values from the stream
837
+ instruction are exposed as `bridge.parameters` for tenant and worker routing.
838
+
839
+ Pass `bargeIn: false` to keep queued audio playing, `autoConnect: false` to connect the session yourself,
840
+ and `await bridge.flush()` when you need outstanding sends to settle.
841
+
842
+ ### Call Control and Usage Metering
843
+
844
+ Meter billable time from the provider's own record rather than from stream lifecycle events, which do not
845
+ account for ring time or provider-side teardown:
846
+
847
+ ```ts
848
+ // From your status webhook.
849
+ const status = ai.parseTelephonyStatusCallback('twilio', requestBody);
850
+ if (status?.status === 'completed') {
851
+ await usage.recordCallMinutes({ callId: status.callId, seconds: status.durationSeconds ?? 0 });
852
+ }
853
+
854
+ // Or read it directly, and hang up from a tool.
855
+ const details = await ai.getCall({ callId });
856
+ await ai.endCall({ callId });
857
+ ```
858
+
859
+ ### Pointing a Number at Your Webhook
860
+
861
+ ```ts
862
+ const [number] = await ai.listPhoneNumbers({ phoneNumber: '+15557650000' });
863
+ await ai.updatePhoneNumber({
864
+ id: number.id,
865
+ voiceUrl: 'https://api.example.com/voice/incoming',
866
+ statusCallbackUrl: 'https://api.example.com/voice/status',
867
+ });
868
+ ```
869
+
737
870
  ## Security
738
871
 
739
872
  Security is enabled by default with `standard` mode.
@@ -892,11 +1025,18 @@ import { ContextWindowManager } from 'nexus-ai-pro/context';
892
1025
  import { guardrailPolicy } from 'nexus-ai-pro/security';
893
1026
  import { RedisCacheAdapter } from 'nexus-ai-pro/cache';
894
1027
  import { DeepSeekProvider } from 'nexus-ai-pro/providers/deepseek';
1028
+ import { ImageManager } from 'nexus-ai-pro/images';
1029
+ import { MemoryAssetStore } from 'nexus-ai-pro/images/assets';
1030
+ import { MockImageProvider } from 'nexus-ai-pro/images/mock';
1031
+ import { OpenAIImageProvider } from 'nexus-ai-pro/images/openai';
895
1032
  import { createRealtimeSession } from 'nexus-ai-pro/realtime/session';
896
1033
  import { OpenAIWebRTCTransport } from 'nexus-ai-pro/realtime/openai-webrtc';
1034
+ import { TelephonyManager } from 'nexus-ai-pro/telephony';
1035
+ import { TwilioTelephonyProvider } from 'nexus-ai-pro/telephony/twilio';
1036
+ import { createTelephonyRealtimeBridge } from 'nexus-ai-pro/telephony/realtime-bridge';
897
1037
  ```
898
1038
 
899
- Provider SDKs are optional peer dependencies. The package is ESM-only, supports Node.js 22+, and is marked with `sideEffects: false`. Only the entry points listed in the package export map are public; deep imports into `dist` or `src` are unsupported.
1039
+ Provider SDKs are optional peer dependencies. The package ships ESM and CommonJS builds, supports Node.js 22+, and is marked with `sideEffects: false`. Only the entry points listed in the package export map are public; deep imports into `dist`, `dist-cjs`, or `src` are unsupported.
900
1040
 
901
1041
  ## Examples
902
1042
 
@@ -946,10 +1086,10 @@ npm run test:conformance:real
946
1086
 
947
1087
  ## Roadmap
948
1088
 
949
- The next recommended feature family is first-class image generation and editing through a
950
- provider-neutral `ImageManager`, portable asset types, durable operation handles, visual safety,
951
- asset storage, and media-specific evaluations. The design and prioritized infrastructure backlog
952
- are in [ROADMAP.md](./ROADMAP.md).
1089
+ The experimental image foundation now includes portable asset types, `ImageManager`, provider
1090
+ capability negotiation, local operation handles, visual-safety hooks, deterministic conformance, and an
1091
+ opt-in OpenAI adapter. Durable operations, production asset storage, additional providers, and
1092
+ media-specific evaluations remain planned in [ROADMAP.md](./ROADMAP.md).
953
1093
 
954
1094
  ## Production Notes
955
1095
 
package/ROADMAP.md CHANGED
@@ -36,7 +36,23 @@ Current limitations are intentional and should remain visible:
36
36
  - Realtime video tracks, SIP transports, durable cost accounting beyond the configurable local estimate,
37
37
  and a published latency benchmark methodology remain follow-on work.
38
38
 
39
- ## Recommended next release: first-class image generation
39
+ ## Current next-release work: first-class image generation
40
+
41
+ The experimental foundation is now implemented in the current development line:
42
+
43
+ - portable byte, URL, and stored asset locations plus image request/result, usage, safety, provenance,
44
+ provider-call context, and operation-handle types;
45
+ - a provider-neutral `ImageManager` with generation, editing, provider registration, strict capability
46
+ negotiation, visual-safety hooks, and cancellable in-process submissions;
47
+ - a deterministic network-free mock and a parallel image-provider conformance harness;
48
+ - an opt-in OpenAI Image API adapter for one-shot generation and reference-based editing;
49
+ - a bounded tenant-aware in-memory asset store with retention, defensive byte copies, computed SHA-256
50
+ checksums, capacity rejection, and optional signed-URL integration.
51
+
52
+ This surface remains experimental. OpenAI masks are intentionally disabled until a transformer can
53
+ verify dimensions and convert neutral mask polarity into provider alpha semantics. Durable operations,
54
+ filesystem/S3-compatible asset stores, a second cloud provider, ComfyUI, and media evaluation are still
55
+ required before promotion.
40
56
 
41
57
  Image generation should be a separate operation family rather than another variation of
42
58
  `complete()`. Text completions and generated assets have different request shapes, response
@@ -1,6 +1,7 @@
1
1
  import { NexusAI } from './nexus.js';
2
2
  import type { AnthropicProviderConfig, AuditLogConfig, AzureOpenAIProviderConfig, CacheConfig, CohereProviderConfig, CostBudgetConfig, CustomProviderConfig, DeepSeekProviderConfig, GoogleProviderConfig, GroqProviderConfig, LMStudioProviderConfig, LlamaCppProviderConfig, LoggerConfig, MistralProviderConfig, NexusAIConfig, OllamaProviderConfig, OpenAIProviderConfig, OpenRouterProviderConfig, ProvidersConfig, RateLimitConfig, ResponseFormatConfig, RetryConfig, RoutingConfig, RoutingStrategy } from '../types/config.js';
3
3
  import type { ContextWindowConfig } from '../types/context-window.js';
4
+ import type { ImageConfig } from '../types/images.js';
4
5
  import type { SecurityConfig, SecurityLevel } from '../types/security.js';
5
6
  import type { TokenOptimizerConfig } from '../types/optimizer.js';
6
7
  import type { PipelineConfig } from '../pipeline/types.js';
@@ -53,6 +54,8 @@ export declare class NexusConfigBuilder {
53
54
  security(config: SecurityLevel | SecurityConfig): this;
54
55
  /** Sets context-window compaction behavior. */
55
56
  contextWindow(config: ContextWindowConfig): this;
57
+ /** Sets image-provider defaults and registrations. */
58
+ images(config: ImageConfig): this;
56
59
  /** Sets token optimization behavior. */
57
60
  tokenOptimizer(config: TokenOptimizerConfig): this;
58
61
  /** Sets exact, semantic, or hybrid cache behavior. */
@@ -98,6 +98,11 @@ export class NexusConfigBuilder {
98
98
  this.config.contextWindow = config;
99
99
  return this;
100
100
  }
101
+ /** Sets image-provider defaults and registrations. */
102
+ images(config) {
103
+ this.config.images = config;
104
+ return this;
105
+ }
101
106
  /** Sets token optimization behavior. */
102
107
  tokenOptimizer(config) {
103
108
  this.config.tokenOptimizer = config;
@@ -4,10 +4,12 @@ import type { NexusPlan } from '../types/planning.js';
4
4
  import type { NexusResponse, NexusStream } from '../types/response.js';
5
5
  import type { AgentConfig, AgentResult } from '../types/agent.js';
6
6
  import type { SpeechRequest, SpeechResponse, TranscriptionRequest, TranscriptionResponse, VoiceProvider, VoiceSessionConfig, VoiceTurnRequest, VoiceTurnResponse } from '../types/voice.js';
7
- import type { CreateCallRequest, CreateCallResponse, TelephonyMediaStreamEvent, TelephonyOutboundAudioMessage, TelephonyProvider, TelephonyResponseRequest, TelephonyWebhookResponse, TelephonyWebhookValidationRequest } from '../types/telephony.js';
7
+ import type { ImageProvider } from '../types/images.js';
8
+ import type { CreateCallRequest, CreateCallResponse, EndCallRequest, GetCallRequest, ListPhoneNumbersRequest, TelephonyCallDetails, TelephonyMediaStreamEvent, TelephonyOutboundAudioMessage, TelephonyPhoneNumber, TelephonyProvider, TelephonyResponseRequest, TelephonyStatusCallback, TelephonyWebhookResponse, TelephonyWebhookValidationRequest, UpdatePhoneNumberRequest } from '../types/telephony.js';
8
9
  import type { PipelineStep } from '../pipeline/types.js';
9
10
  import type { BaseProvider } from '../providers/base.js';
10
11
  import { type VoiceSession } from '../voice/index.js';
12
+ import { ImageManager } from '../images/manager.js';
11
13
  import { type VerificationOptions } from '../hallucination/verification.js';
12
14
  import { type SelfConsistencyOptions } from '../hallucination/consistency.js';
13
15
  import { type BatchOptions, type BatchItemResult } from '../jobs/batch.js';
@@ -20,6 +22,8 @@ import { type SummarizeVerifyFormatOptions, type WorkflowResult } from '../workf
20
22
  * Use `new NexusAI(config)` when you want full control, or `createNexus()` for the beginner shorthand.
21
23
  */
22
24
  export declare class NexusAI {
25
+ /** Provider-neutral image generation and editing operations. */
26
+ readonly images: ImageManager;
23
27
  private config;
24
28
  private providers;
25
29
  private router;
@@ -104,6 +108,27 @@ export declare class NexusAI {
104
108
  event?: 'media' | 'mark' | 'clear';
105
109
  markName?: string;
106
110
  }): TelephonyOutboundAudioMessage;
111
+ /**
112
+ * Reads a call's current provider-side state, including the duration usage metering bills on.
113
+ */
114
+ getCall(request: GetCallRequest): Promise<TelephonyCallDetails>;
115
+ /**
116
+ * Hangs up a live call through the provider's call-control API.
117
+ */
118
+ endCall(request: EndCallRequest): Promise<TelephonyCallDetails>;
119
+ /**
120
+ * Parses a provider status webhook into a normalized record. Prefer this over media-stream
121
+ * lifecycle events when metering usage.
122
+ */
123
+ parseTelephonyStatusCallback(providerName: string, body: string | URLSearchParams | Record<string, string | number | boolean | undefined>): TelephonyStatusCallback | undefined;
124
+ /**
125
+ * Lists phone numbers owned by the provider account.
126
+ */
127
+ listPhoneNumbers(request?: ListPhoneNumbersRequest): Promise<TelephonyPhoneNumber[]>;
128
+ /**
129
+ * Points a phone number at a voice webhook and/or status callback.
130
+ */
131
+ updatePhoneNumber(request: UpdatePhoneNumberRequest): Promise<TelephonyPhoneNumber>;
107
132
  /**
108
133
  * Registers a custom text provider at runtime.
109
134
  */
@@ -112,18 +137,24 @@ export declare class NexusAI {
112
137
  * Registers a custom voice provider at runtime.
113
138
  */
114
139
  registerVoiceProvider(name: string, provider: VoiceProvider): this;
140
+ /**
141
+ * Registers a custom image provider at runtime.
142
+ */
143
+ registerImageProvider(name: string, provider: ImageProvider): this;
115
144
  /**
116
145
  * Registers a custom telephony provider at runtime.
117
146
  */
118
147
  registerTelephonyProvider(name: string, provider: TelephonyProvider): this;
119
148
  hasProvider(name: string): boolean;
120
149
  hasVoiceProvider(name: string): boolean;
150
+ hasImageProvider(name: string): boolean;
121
151
  hasTelephonyProvider(name: string): boolean;
122
152
  /**
123
153
  * Lists configured text providers.
124
154
  */
125
155
  listProviders(): string[];
126
156
  listVoiceProviders(): string[];
157
+ listImageProviders(): string[];
127
158
  listTelephonyProviders(): string[];
128
159
  /**
129
160
  * Adds a custom pipeline step.
@@ -15,6 +15,7 @@ import { Logger } from '../utils/logger.js';
15
15
  import { SecurityPipeline } from '../security/index.js';
16
16
  import { ContextWindowManager } from '../context/index.js';
17
17
  import { VoiceManager } from '../voice/index.js';
18
+ import { ImageManager } from '../images/manager.js';
18
19
  import { TelephonyManager } from '../telephony/index.js';
19
20
  import { TokenOptimizer } from '../optimizer/index.js';
20
21
  import { AgentLoop } from '../agent/loop.js';
@@ -41,6 +42,8 @@ import { summarizeVerifyFormat } from '../workflow/chains.js';
41
42
  * Use `new NexusAI(config)` when you want full control, or `createNexus()` for the beginner shorthand.
42
43
  */
43
44
  export class NexusAI {
45
+ /** Provider-neutral image generation and editing operations. */
46
+ images;
44
47
  config;
45
48
  providers = new Map();
46
49
  router = new Router();
@@ -73,6 +76,7 @@ export class NexusAI {
73
76
  this.security = new SecurityPipeline(this.config.security || 'standard');
74
77
  this.contextWindow = new ContextWindowManager(this.config.contextWindow || {});
75
78
  this.voiceManager = new VoiceManager(this.config.voice || {});
79
+ this.images = new ImageManager(this.config.images || {});
76
80
  this.telephonyManager = new TelephonyManager(this.config.telephony || {});
77
81
  this.optimizer = new TokenOptimizer(this.config.tokenOptimizer || {});
78
82
  this.cache = new MemoryCache(this.config.cache?.maxEntries || 500);
@@ -439,6 +443,37 @@ export class NexusAI {
439
443
  formatTelephonyAudioMessage(providerName, streamId, payload, options) {
440
444
  return this.telephonyManager.formatAudioMessage(providerName, streamId, payload, options);
441
445
  }
446
+ /**
447
+ * Reads a call's current provider-side state, including the duration usage metering bills on.
448
+ */
449
+ async getCall(request) {
450
+ return this.telephonyManager.getCall(request);
451
+ }
452
+ /**
453
+ * Hangs up a live call through the provider's call-control API.
454
+ */
455
+ async endCall(request) {
456
+ return this.telephonyManager.endCall(request);
457
+ }
458
+ /**
459
+ * Parses a provider status webhook into a normalized record. Prefer this over media-stream
460
+ * lifecycle events when metering usage.
461
+ */
462
+ parseTelephonyStatusCallback(providerName, body) {
463
+ return this.telephonyManager.parseStatusCallback(providerName, body);
464
+ }
465
+ /**
466
+ * Lists phone numbers owned by the provider account.
467
+ */
468
+ async listPhoneNumbers(request) {
469
+ return this.telephonyManager.listPhoneNumbers(request);
470
+ }
471
+ /**
472
+ * Points a phone number at a voice webhook and/or status callback.
473
+ */
474
+ async updatePhoneNumber(request) {
475
+ return this.telephonyManager.updatePhoneNumber(request);
476
+ }
442
477
  /**
443
478
  * Registers a custom text provider at runtime.
444
479
  */
@@ -453,6 +488,13 @@ export class NexusAI {
453
488
  this.voiceManager.registerProvider(name, provider);
454
489
  return this;
455
490
  }
491
+ /**
492
+ * Registers a custom image provider at runtime.
493
+ */
494
+ registerImageProvider(name, provider) {
495
+ this.images.registerImageProvider(name, provider);
496
+ return this;
497
+ }
456
498
  /**
457
499
  * Registers a custom telephony provider at runtime.
458
500
  */
@@ -466,6 +508,9 @@ export class NexusAI {
466
508
  hasVoiceProvider(name) {
467
509
  return this.voiceManager.hasProvider(name);
468
510
  }
511
+ hasImageProvider(name) {
512
+ return this.images.hasImageProvider(name);
513
+ }
469
514
  hasTelephonyProvider(name) {
470
515
  return this.telephonyManager.hasProvider(name);
471
516
  }
@@ -478,6 +523,9 @@ export class NexusAI {
478
523
  listVoiceProviders() {
479
524
  return this.voiceManager.listProviders();
480
525
  }
526
+ listImageProviders() {
527
+ return this.images.listImageProviders();
528
+ }
481
529
  listTelephonyProviders() {
482
530
  return this.telephonyManager.listProviders();
483
531
  }
@@ -0,0 +1,115 @@
1
+ import type { AssetBytesLocation, AssetDescriptor, AssetInput, AssetProvenance } from '../types/images.js';
2
+ /** An asset input whose payload is already available locally as bytes. */
3
+ export interface ByteAssetInput extends Omit<AssetInput, 'location'> {
4
+ location: AssetBytesLocation;
5
+ }
6
+ /** A retrieved asset descriptor whose byte payload is available to the caller. */
7
+ export interface ByteAssetDescriptor extends Omit<AssetDescriptor, 'location'> {
8
+ location: AssetBytesLocation;
9
+ }
10
+ export interface AssetPutOptions {
11
+ /** Tenant that owns the asset. This value is required for every later access. */
12
+ tenantId: string;
13
+ provenance: AssetProvenance;
14
+ width?: number;
15
+ height?: number;
16
+ /** Relative retention period. Mutually exclusive with `expiresAt`. */
17
+ ttlSeconds?: number;
18
+ /** Absolute retention deadline. Mutually exclusive with `ttlSeconds`. */
19
+ expiresAt?: Date | string;
20
+ }
21
+ export interface AssetStat {
22
+ assetId: string;
23
+ tenantId: string;
24
+ /** Metadata-only descriptor. Its location points back to this store, not to the byte payload. */
25
+ descriptor: AssetDescriptor;
26
+ createdAt: string;
27
+ expiresAt?: string;
28
+ }
29
+ export interface AssetSignOptions {
30
+ /** Requested signed-URL lifetime. Enforcement is delegated to the configured signer. */
31
+ expiresInSeconds?: number;
32
+ }
33
+ export interface AssetSignerContext extends AssetStat {
34
+ now: string;
35
+ expiresInSeconds?: number;
36
+ }
37
+ export type AssetSigner = (context: AssetSignerContext) => string | URL | Promise<string | URL>;
38
+ export type AssetStoreResult<T> = T | Promise<T>;
39
+ /**
40
+ * Provider-neutral asset persistence contract.
41
+ *
42
+ * A store must treat a missing asset and an asset owned by another tenant identically. Implementations
43
+ * may complete synchronously or asynchronously so the same contract can cover memory and remote stores.
44
+ */
45
+ export interface AssetStore {
46
+ put(input: ByteAssetInput, options: AssetPutOptions): AssetStoreResult<AssetStat>;
47
+ get(assetId: string, tenantId: string): AssetStoreResult<ByteAssetDescriptor | undefined>;
48
+ stat(assetId: string, tenantId: string): AssetStoreResult<AssetStat | undefined>;
49
+ delete(assetId: string, tenantId: string): AssetStoreResult<true | undefined>;
50
+ sign(assetId: string, tenantId: string, options?: AssetSignOptions): AssetStoreResult<string | undefined>;
51
+ purgeExpired(): AssetStoreResult<number>;
52
+ }
53
+ export interface MemoryAssetStoreOptions {
54
+ maxEntries?: number;
55
+ maxTotalBytes?: number;
56
+ maxAssetBytes?: number;
57
+ defaultTtlSeconds?: number;
58
+ createAssetId?: () => string;
59
+ now?: () => Date;
60
+ signer?: AssetSigner;
61
+ }
62
+ export interface MemoryAssetStoreSnapshot {
63
+ entries: number;
64
+ totalBytes: number;
65
+ maxEntries: number;
66
+ maxTotalBytes: number;
67
+ maxAssetBytes: number;
68
+ }
69
+ export declare class AssetStoreError extends Error {
70
+ readonly code: string;
71
+ readonly cause?: unknown | undefined;
72
+ constructor(message: string, code: string, cause?: unknown | undefined);
73
+ }
74
+ export declare class AssetStoreValidationError extends AssetStoreError {
75
+ constructor(message: string, cause?: unknown);
76
+ }
77
+ export type AssetCapacityConstraint = 'maxEntries' | 'maxTotalBytes' | 'maxAssetBytes';
78
+ export declare class AssetStoreCapacityError extends AssetStoreError {
79
+ readonly constraint: AssetCapacityConstraint;
80
+ readonly maximum: number;
81
+ readonly current: number;
82
+ readonly requested: number;
83
+ constructor(constraint: AssetCapacityConstraint, maximum: number, current: number, requested: number);
84
+ }
85
+ export declare class AssetStoreSigningError extends AssetStoreError {
86
+ constructor(message: string, cause?: unknown);
87
+ }
88
+ /**
89
+ * Process-local, bounded asset storage. Capacity exhaustion rejects the write; live entries are never
90
+ * silently evicted. Expired entries are purged before capacity and access decisions.
91
+ */
92
+ export declare class MemoryAssetStore implements AssetStore {
93
+ private readonly entries;
94
+ private readonly maxEntries;
95
+ private readonly maxTotalBytes;
96
+ private readonly maxAssetBytes;
97
+ private readonly defaultTtlSeconds?;
98
+ private readonly createAssetId;
99
+ private readonly clock;
100
+ private readonly signer?;
101
+ private totalBytes;
102
+ constructor(options?: MemoryAssetStoreOptions);
103
+ put(input: ByteAssetInput, options: AssetPutOptions): AssetStat;
104
+ get(assetId: string, tenantId: string): ByteAssetDescriptor | undefined;
105
+ stat(assetId: string, tenantId: string): AssetStat | undefined;
106
+ delete(assetId: string, tenantId: string): true | undefined;
107
+ sign(assetId: string, tenantId: string, options?: AssetSignOptions): Promise<string | undefined>;
108
+ purgeExpired(): number;
109
+ snapshot(): MemoryAssetStoreSnapshot;
110
+ private assertCapacity;
111
+ private ownedEntry;
112
+ private purgeExpiredAt;
113
+ private removeEntry;
114
+ private nowMs;
115
+ }