nexus-ai-pro 2.17.0 → 2.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API_STABILITY.md +50 -0
- package/CHANGELOG.md +122 -1
- package/README.md +12 -3
- package/dist/capabilities/negotiate.js +5 -0
- package/dist/decisions/completion.d.ts +48 -0
- package/dist/decisions/completion.js +195 -0
- package/dist/decisions/core.d.ts +525 -0
- package/dist/decisions/core.js +493 -0
- package/dist/decisions/index.d.ts +9 -0
- package/dist/decisions/index.js +3 -0
- package/dist/decisions/integrations.d.ts +142 -0
- package/dist/decisions/integrations.js +170 -0
- package/dist/decisions/mock.d.ts +32 -0
- package/dist/decisions/mock.js +47 -0
- package/dist/decisions/typesafe.d.ts +45 -0
- package/dist/decisions/typesafe.js +144 -0
- package/dist/evaluate/media.d.ts +6 -2
- package/dist/evaluate/media.js +7 -21
- package/dist/index.d.ts +1 -1
- package/dist/mcp/client.d.ts +25 -2
- package/dist/mcp/client.js +82 -47
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.js +4 -1
- package/dist/protocols/acp-editor.js +42 -15
- package/dist/providers/google.js +28 -0
- package/dist/providers/ollama.js +23 -0
- package/dist/providers/openai.d.ts +6 -0
- package/dist/providers/openai.js +26 -0
- package/dist/router/failover.js +19 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/list-page.d.ts +38 -0
- package/dist/server/list-page.js +49 -0
- package/dist/server/regions.d.ts +15 -1
- package/dist/server/regions.js +70 -0
- package/dist/server/runs.d.ts +24 -0
- package/dist/server/runs.js +18 -4
- package/dist/server/server.js +17 -5
- package/dist/tracing/index.d.ts +2 -2
- package/dist/tracing/instrument.d.ts +7 -2
- package/dist/tracing/instrument.js +69 -2
- package/dist/tracing/otlp-proto.js +18 -0
- package/dist/tracing/otlp.d.ts +26 -1
- package/dist/tracing/otlp.js +19 -0
- package/dist/tracing/tracer.d.ts +36 -1
- package/dist/tracing/tracer.js +68 -2
- package/dist/types/lifecycle.d.ts +1 -1
- package/dist/types/messages.d.ts +9 -0
- package/dist/types/providers.d.ts +2 -0
- package/dist/types/response.d.ts +35 -0
- package/dist/types/tracing.d.ts +33 -1
- package/dist/utils/media-duration.d.ts +20 -0
- package/dist/utils/media-duration.js +334 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/video/google.d.ts +8 -0
- package/dist/video/google.js +31 -9
- package/dist/video/index.d.ts +18 -3
- package/dist/video/index.js +46 -3
- package/dist/video/mock.js +3 -1
- package/dist/video/runway.d.ts +2 -0
- package/dist/video/runway.js +45 -11
- package/dist/workflow/chains.d.ts +13 -1
- package/dist/workflow/chains.js +40 -0
- package/dist-cjs/capabilities/negotiate.js +5 -0
- package/dist-cjs/decisions/completion.d.ts +48 -0
- package/dist-cjs/decisions/completion.js +198 -0
- package/dist-cjs/decisions/core.d.ts +525 -0
- package/dist-cjs/decisions/core.js +509 -0
- package/dist-cjs/decisions/index.d.ts +9 -0
- package/dist-cjs/decisions/index.js +27 -0
- package/dist-cjs/decisions/integrations.d.ts +142 -0
- package/dist-cjs/decisions/integrations.js +178 -0
- package/dist-cjs/decisions/mock.d.ts +32 -0
- package/dist-cjs/decisions/mock.js +51 -0
- package/dist-cjs/decisions/typesafe.d.ts +45 -0
- package/dist-cjs/decisions/typesafe.js +148 -0
- package/dist-cjs/evaluate/media.d.ts +6 -2
- package/dist-cjs/evaluate/media.js +9 -21
- package/dist-cjs/index.d.ts +1 -1
- package/dist-cjs/mcp/client.d.ts +25 -2
- package/dist-cjs/mcp/client.js +82 -47
- package/dist-cjs/mcp/server.d.ts +1 -1
- package/dist-cjs/mcp/server.js +4 -1
- package/dist-cjs/protocols/acp-editor.js +42 -15
- package/dist-cjs/providers/google.js +28 -0
- package/dist-cjs/providers/ollama.js +23 -0
- package/dist-cjs/providers/openai.d.ts +6 -0
- package/dist-cjs/providers/openai.js +26 -0
- package/dist-cjs/router/failover.js +19 -1
- package/dist-cjs/server/index.d.ts +1 -0
- package/dist-cjs/server/list-page.d.ts +38 -0
- package/dist-cjs/server/list-page.js +56 -0
- package/dist-cjs/server/regions.d.ts +15 -1
- package/dist-cjs/server/regions.js +70 -0
- package/dist-cjs/server/runs.d.ts +24 -0
- package/dist-cjs/server/runs.js +18 -4
- package/dist-cjs/server/server.js +17 -5
- package/dist-cjs/tracing/index.d.ts +2 -2
- package/dist-cjs/tracing/instrument.d.ts +7 -2
- package/dist-cjs/tracing/instrument.js +69 -2
- package/dist-cjs/tracing/otlp-proto.js +18 -0
- package/dist-cjs/tracing/otlp.d.ts +26 -1
- package/dist-cjs/tracing/otlp.js +19 -0
- package/dist-cjs/tracing/tracer.d.ts +36 -1
- package/dist-cjs/tracing/tracer.js +68 -2
- package/dist-cjs/types/lifecycle.d.ts +1 -1
- package/dist-cjs/types/messages.d.ts +9 -0
- package/dist-cjs/types/providers.d.ts +2 -0
- package/dist-cjs/types/response.d.ts +35 -0
- package/dist-cjs/types/tracing.d.ts +33 -1
- package/dist-cjs/utils/media-duration.d.ts +20 -0
- package/dist-cjs/utils/media-duration.js +337 -0
- package/dist-cjs/version.d.ts +1 -1
- package/dist-cjs/version.js +1 -1
- package/dist-cjs/video/google.d.ts +8 -0
- package/dist-cjs/video/google.js +31 -9
- package/dist-cjs/video/index.d.ts +18 -3
- package/dist-cjs/video/index.js +46 -3
- package/dist-cjs/video/mock.js +3 -1
- package/dist-cjs/video/runway.d.ts +2 -0
- package/dist-cjs/video/runway.js +45 -11
- package/dist-cjs/workflow/chains.d.ts +13 -1
- package/dist-cjs/workflow/chains.js +40 -0
- package/llms.txt +2 -1
- package/package.json +41 -2
package/API_STABILITY.md
CHANGED
|
@@ -41,6 +41,56 @@ Both are covered by this policy:
|
|
|
41
41
|
Deep imports into `dist`, `dist-cjs`, or `src` are not supported. Import only from `nexus-ai-pro` or
|
|
42
42
|
one of its explicit subpaths.
|
|
43
43
|
|
|
44
|
+
## 2.19 stage (2.19.0)
|
|
45
|
+
|
|
46
|
+
Everything 2.19 adds is stable from the start and additive, unless this section says otherwise.
|
|
47
|
+
|
|
48
|
+
New and stable:
|
|
49
|
+
- in the root: `CompletionRequest.logprobs`, `NexusResponse.logprobs`, `TokenLogprob`, and
|
|
50
|
+
`ModelCapabilities.logprobs`;
|
|
51
|
+
- in `nexus-ai-pro/workflows`: `ClassifyRouteOptions.decisions`.
|
|
52
|
+
|
|
53
|
+
New and experimental, until the live workflow passes against TypeSafe AI's Jev and a model that
|
|
54
|
+
reports log probabilities: the typed decisions family, `nexus-ai-pro/decisions`,
|
|
55
|
+
`nexus-ai-pro/decisions/typesafe`, and `nexus-ai-pro/decisions/mock`.
|
|
56
|
+
|
|
57
|
+
Changed on purpose: `OperationFamily` and `RunKind` widen with `decision`.
|
|
58
|
+
|
|
59
|
+
## 2.18 stage (2.18.0)
|
|
60
|
+
|
|
61
|
+
Everything 2.18 adds is stable from the start and additive, unless this section says otherwise.
|
|
62
|
+
|
|
63
|
+
New and stable:
|
|
64
|
+
- in `nexus-ai-pro/tracing`: `RunEvent`, `RunLink`, `RunAttributeValue`, `RunEventInput`, `RunLinkInput`,
|
|
65
|
+
`Run.events`, `droppedEvents`, and `links`, `Tracer.event()`, `RunHandle.event()` and `link()`,
|
|
66
|
+
`StartRunOptions.links`, `FinishRunOptions.events` and `links`, `TracerOptions.maxEventsPerRun`, and
|
|
67
|
+
the event names `retry`, `failover`, `progress`, `guardrail`, `cache`, and `capability_warning`;
|
|
68
|
+
- in `nexus-ai-pro/tracing/otlp`: `OtlpSpanEvent`, `OtlpSpanLink`, and `OtlpSpan.events`,
|
|
69
|
+
`droppedEventsCount`, and `links`;
|
|
70
|
+
- in the root: `ResponseAttempt` and `ResponseMeta.failedAttempts`;
|
|
71
|
+
- in `nexus-ai-pro/evaluate/media`: `readMediaDuration()` and `MediaDuration`;
|
|
72
|
+
- in `nexus-ai-pro/video`, experimental with the family: `VideoModelCapabilities.maxInputVideoSeconds`;
|
|
73
|
+
- in `nexus-ai-pro/server/regions`: `RegionalGatewayOptions.lists`, `RegionListGap`, and a merged
|
|
74
|
+
list's `regions`, `incomplete`, and `nextCursor`;
|
|
75
|
+
- in `nexus-ai-pro/server`: `ListPage`, `RunManager.listThreads()` and `listRuns()`, and the lists'
|
|
76
|
+
`cursor` and `nextCursor`;
|
|
77
|
+
- in `nexus-ai-pro/mcp`: `McpClientOptions.reconnect`, `McpClient.onToolsChanged()`, and
|
|
78
|
+
`SseClientOptions.reopen`;
|
|
79
|
+
- in `nexus-ai-pro/video`, experimental with the family: `VideoGenerateRequest.referenceVideos` and
|
|
80
|
+
`VideoProviderRequest.referenceVideos`, the `reference-videos` input kind,
|
|
81
|
+
`VideoModelCapabilities.maxReferenceVideos` and `referenceVideoSeconds`, and
|
|
82
|
+
`VideoConformanceOptions.referenceVideo`;
|
|
83
|
+
- in `nexus-ai-pro/video/google`, experimental with the family:
|
|
84
|
+
`GoogleVideoProviderConfig.interactionsBaseUrl`, and Gemini Omni Flash on Vertex AI.
|
|
85
|
+
|
|
86
|
+
Changed on purpose:
|
|
87
|
+
- a list through the regional gateway is merged from every region the tenant may reach;
|
|
88
|
+
`lists: 'home'` keeps the earlier answer;
|
|
89
|
+
- an agent server's lists are ordered by last write, then id, and a `limit` that is not a whole
|
|
90
|
+
number above 0 is refused;
|
|
91
|
+
- `McpServer` says `tools.listChanged`, and `register()` sends the notification;
|
|
92
|
+
- `VideoInputKind` widens with `reference-videos`.
|
|
93
|
+
|
|
44
94
|
## 2.17 stage (2.17.0)
|
|
45
95
|
|
|
46
96
|
Everything 2.17 adds is stable from the start and additive, unless this section says otherwise.
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,125 @@ Notable changes to this project are recorded here. The format follows [Keep a Ch
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [2.19.0] - 2026-10-24
|
|
8
|
+
|
|
9
|
+
Typed decisions: a yes or no, a choice, or a score, answered with probabilities and a confidence read
|
|
10
|
+
from them, from TypeSafe AI's Jev or from any model, acted on when sure and sent to a person when not.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Typed decisions.** `nexus-ai-pro/decisions` asks a `DecisionRequest`'s questions of one state: a
|
|
15
|
+
`YesNoQuestion`, a `ChoiceQuestion` of up to 255 options, or a `ScoreQuestion` of up to ten levels,
|
|
16
|
+
each answered in isolation with its probabilities and a confidence read the same way from any
|
|
17
|
+
provider. Answers are typed by their questions, so a choice's answer names only its options.
|
|
18
|
+
`DecisionManager` refuses a malformed question before it is sent, fails over, caches, traces each
|
|
19
|
+
decision as a run of kind `decision`, and runs it as an operation of the client, family
|
|
20
|
+
`decision`, budgeted at its model's price and on the tenant's key.
|
|
21
|
+
- **TypeSafe AI's Jev.** `TypeSafeDecisionProvider`, on `nexus-ai-pro/decisions/typesafe`, speaks the
|
|
22
|
+
System One API: every question of a decision in one call, its refusals naming the field at fault,
|
|
23
|
+
priced at $42 per billion input tokens with output free.
|
|
24
|
+
- **Decisions from any model.** `completionDecisions()` asks a completion model each question for a
|
|
25
|
+
one-token code, and reads its probabilities from that token's log probabilities where the provider
|
|
26
|
+
reports them, or counts them over samples where it does not, or refuses to.
|
|
27
|
+
- **Gates and calibration.** `gate()` acts, sends to a person, or abstains by confidence; `calibrate()`
|
|
28
|
+
measures how answers' probabilities track how often they are right, and chooses the threshold that
|
|
29
|
+
holds a target accuracy on the application's own labelled answers.
|
|
30
|
+
- **Decisions where the package decides.** `decisionRoute()` for a graph's branch, `decisionPermissions()`
|
|
31
|
+
for an agent's tool calls, `decisionGuard()` and `decisionModel()` as agent middleware,
|
|
32
|
+
`decisionEvaluator()` and `reviewUnsure()` for evaluation, and `classifyRoute()`'s `decisions`:
|
|
33
|
+
an uncertain answer goes to a person.
|
|
34
|
+
- **Log probabilities.** `CompletionRequest.logprobs` asks for each answer token's log probability and
|
|
35
|
+
its alternatives, on `NexusResponse.logprobs` as `TokenLogprob` values, from OpenAI's Chat
|
|
36
|
+
Completions, the OpenAI-compatible adapters that report them, Gemini, and Ollama;
|
|
37
|
+
`ModelCapabilities.logprobs` declares a model that cannot.
|
|
38
|
+
- `MockDecisionProvider`, on `nexus-ai-pro/decisions/mock`, and `runDecisionConformance()`.
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
|
|
42
|
+
- `OperationFamily` and `RunKind` have `decision`; a `switch` over either that names every one needs
|
|
43
|
+
the new one.
|
|
44
|
+
|
|
45
|
+
### Fixed
|
|
46
|
+
|
|
47
|
+
- A test that waits forever fails at five minutes, and every child process the tests start
|
|
48
|
+
synchronously has a timeout of its own: the 2.18.0 publish's first attempt waited two hours on one.
|
|
49
|
+
|
|
50
|
+
## [2.18.0] - 2026-10-22
|
|
51
|
+
|
|
52
|
+
Span events and links, media that knows its length, a tenant's threads and runs listed and paged
|
|
53
|
+
across all of its regions, MCP servers that reconnect and change their tools mid-session, and videos
|
|
54
|
+
made after reference videos.
|
|
55
|
+
|
|
56
|
+
### Added
|
|
57
|
+
|
|
58
|
+
- **Span events and links.** A run records what happened inside it as `RunEvent` values, each at its
|
|
59
|
+
time, and the runs it came from beside its parent as `RunLink` values. `Tracer.event()` records on
|
|
60
|
+
the run in scope, `RunHandle.event()` and `link()` on one run; `StartRunOptions.links` take a link
|
|
61
|
+
or a W3C `traceparent`, and `FinishRunOptions` take the `events` and `links` a run learns as it
|
|
62
|
+
ends. `maxEventsPerRun` (128) keeps a run's events bounded, counting the rest as `droppedEvents`.
|
|
63
|
+
Both reach OTLP as span events and links, in JSON, in protobuf, and over gRPC. The studio shows them
|
|
64
|
+
on a run, each link opening the run it names, and `#/traces/<trace>/<run>` opens one run of a trace.
|
|
65
|
+
- **Events where they happen.** A graph's retry is a `retry` event of its task's run, a tool's
|
|
66
|
+
progress a `progress` event of the tool's, and a task after a step of several, such as a fan-out's
|
|
67
|
+
join, links to each of them. A model run has the attempts that failed before its answer, as `retry`
|
|
68
|
+
and `failover` at the times they failed, and its guardrails, cache answer, and capability warnings.
|
|
69
|
+
- **Failed attempts on the response.** `ResponseMeta.failedAttempts` lists each `ResponseAttempt` that
|
|
70
|
+
failed before the answer: its provider, model, error, category, and time; a stream's last chunk
|
|
71
|
+
carries them too.
|
|
72
|
+
- **Media that knows its length.** `readMediaDuration()`, on `nexus-ai-pro/evaluate/media`, reads the
|
|
73
|
+
length of WAV, MP4 and MOV (fragmented MP4 included), WebM and Matroska, MP3, Ogg Vorbis and Opus,
|
|
74
|
+
and FLAC from their own headers, with no dependency. `mediaChecks()` holds audio and video to a
|
|
75
|
+
`durationMs` range with it, and a video model's `maxInputVideoSeconds` refuses a longer video before
|
|
76
|
+
it is sent: 10 seconds on Gemini Omni Flash, 30 on Veo.
|
|
77
|
+
- **Lists across regions.** The regional gateway answers `GET /threads` and `GET /runs` from every
|
|
78
|
+
region the tenant may reach, merged newest first and cut to the limit, naming the regions that
|
|
79
|
+
answered and, as `RegionListGap` values under `incomplete`, those that were down or failed. A
|
|
80
|
+
refusal every region gives is the answer. `lists: 'home'` keeps the home region's list alone.
|
|
81
|
+
- **Lists that page.** An agent server's `GET /threads`, `GET /runs`, and `GET /threads/:id/runs`
|
|
82
|
+
take a `cursor` and answer with a `nextCursor` while more follow; `RunManager.listThreads()` and
|
|
83
|
+
`listRuns()` return a `ListPage`. A cursor names a place in the order every region lists by, so the
|
|
84
|
+
regional gateway pages a merged list with it too, each record once, in order, however the records
|
|
85
|
+
fall between regions. A region that does not page by it contributes only what comes after it.
|
|
86
|
+
- **MCP that reconnects.** With `McpClientOptions.reconnect`, a client whose connection closed opens
|
|
87
|
+
it again at the next request and greets the server again: an SSE stream that ended, with
|
|
88
|
+
`SseClientOptions.reopen`, or a stdio server that exited, started again. A stream that cannot open,
|
|
89
|
+
or names no endpoint in time, is let go at once, so the next request tries again.
|
|
90
|
+
- **Tools that change.** `McpClient.onToolsChanged()` hears a server's
|
|
91
|
+
`notifications/tools/list_changed`. `McpServer.register()` sends one to a connected client, and the
|
|
92
|
+
server says it will. The editor bridge reconnects an editor's SSE servers within a session and lists
|
|
93
|
+
a server's tools again before the turn after it says they changed, keeping the last list when that
|
|
94
|
+
fails.
|
|
95
|
+
- **Reference videos.** `VideoGenerateRequest.referenceVideos` gives a model videos to take after.
|
|
96
|
+
Models declare `reference-videos` among their inputs, with `maxReferenceVideos` and
|
|
97
|
+
`referenceVideoSeconds`, each and together, read from a video's headers before it is sent. Gemini
|
|
98
|
+
Omni Flash takes three of three seconds each, as does Runway's `gemini_omni_flash_1.1`; Runway's
|
|
99
|
+
Seedance 2 models, `hailuo3`, and `wan3` take 15 seconds together, beside reference images. The
|
|
100
|
+
conformance suite checks them with `referenceVideo`.
|
|
101
|
+
- **Gemini Omni Flash on Vertex AI.** `GoogleVideoProvider` serves `gemini-omni-1.1-flash-preview` on
|
|
102
|
+
Vertex AI's Interactions API, at the global location for the project its `baseUrl` names, or at
|
|
103
|
+
`interactionsBaseUrl`.
|
|
104
|
+
|
|
105
|
+
### Changed
|
|
106
|
+
|
|
107
|
+
- A list of threads or runs through the regional gateway comes from every region the tenant may
|
|
108
|
+
reach, not its home region alone; `RegionalGatewayOptions.lists: 'home'` keeps the 2.17 answer.
|
|
109
|
+
- An agent server lists threads and runs by when each was last written, then by id, the order its
|
|
110
|
+
cursor pages by. A `limit` that is not a whole number above 0 is refused with a 400.
|
|
111
|
+
- `McpServer` says its tools change, as `tools.listChanged`, and `register()` tells a connected
|
|
112
|
+
client.
|
|
113
|
+
- `VideoInputKind` has `reference-videos`; a `switch` over it that names every kind needs the new one.
|
|
114
|
+
- Tested on Node.js 26 as well as 22 and 24: CI runs the release gate on each, on the newest patch of
|
|
115
|
+
its line, so a Node.js security release, such as 2026-10-21's, is what the package is tested and
|
|
116
|
+
published on the day it ships.
|
|
117
|
+
|
|
118
|
+
### Fixed
|
|
119
|
+
|
|
120
|
+
- A tenant's threads and runs on a server several tenants share: a list read the newest records of
|
|
121
|
+
every tenant and then kept the tenant's, so another tenant's newer ones could crowd its page out,
|
|
122
|
+
down to an empty one. A list now reads until the tenant's page is full.
|
|
123
|
+
- The studio showed a run's metadata, its feedback, and a tool's progress as
|
|
124
|
+
`[object HTMLHeadingElement]` rather than their content.
|
|
125
|
+
|
|
7
126
|
## [2.17.0] - 2026-10-21
|
|
8
127
|
|
|
9
128
|
The editor's MCP servers over every transport, decided by what their servers say they do; and video
|
|
@@ -3103,7 +3222,9 @@ shared policy vocabulary is in place for a future release that revisits this.
|
|
|
3103
3222
|
- URL fetching rejects unsafe private-network targets and limits response reads.
|
|
3104
3223
|
- CLI and audit findings no longer disclose detected secret values.
|
|
3105
3224
|
|
|
3106
|
-
[Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.
|
|
3225
|
+
[Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.19.0...HEAD
|
|
3226
|
+
[2.19.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.18.0...v2.19.0
|
|
3227
|
+
[2.18.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.17.0...v2.18.0
|
|
3107
3228
|
[2.17.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.16.0...v2.17.0
|
|
3108
3229
|
[2.16.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.15.1...v2.16.0
|
|
3109
3230
|
[2.15.1]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.15.0...v2.15.1
|
package/README.md
CHANGED
|
@@ -156,6 +156,7 @@ from the doc comments. The guides live in the repository, so these links go to G
|
|
|
156
156
|
| [Retrieval](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/retrieval.md) | Hybrid search, reranking, MMR, parent documents, and Redis, Pinecone, Weaviate, and Chroma stores |
|
|
157
157
|
| [Embeddings and retrieval](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/embeddings.md) | Embeddings as a routed operation family, and RAG helpers |
|
|
158
158
|
| [Media understanding](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/media.md) | Reading documents and images page by page, describing images, and answering questions about them, through any vision model or OCR |
|
|
159
|
+
| [Typed decisions](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/decisions.md) | A yes or no, a choice, or a score, answered with probabilities and a calibrated confidence, from TypeSafe AI's Jev or any model, so a graph, an approval, or a guardrail acts when it is sure and asks a person when not |
|
|
159
160
|
| [Video generation](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/video.md) | Videos from a prompt or a first frame, on Google Veo and Runway, as durable jobs streamed into an asset store and priced per second |
|
|
160
161
|
| [Images (experimental)](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/images.md) | Generation and masked edits over three backends, input safety, moderation, asset stores |
|
|
161
162
|
| [Voice](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/voice.md) | Transcription, speech, voice turns, and voice sessions |
|
|
@@ -327,12 +328,20 @@ Shipped so far:
|
|
|
327
328
|
as it is written;
|
|
328
329
|
- 2.17 the editor's MCP servers over every transport, and video you can extend or edit: an ACP agent
|
|
329
330
|
connects stdio, HTTP, and SSE servers and decides each tool by what its server says it does, and a
|
|
330
|
-
video is extended from its own job or edited on Gemini Omni Flash, Veo, and Runway
|
|
331
|
+
video is extended from its own job or edited on Gemini Omni Flash, Veo, and Runway;
|
|
332
|
+
- 2.18 span events and links, as OpenTelemetry has them, media that knows its length from its own
|
|
333
|
+
headers, a tenant's threads and runs listed and paged across all of its regions, MCP servers that
|
|
334
|
+
reconnect and change their tools within a session, and videos made after reference videos;
|
|
335
|
+
- 2.19 typed decisions: a yes or no, a choice, or a score, answered with probabilities and a
|
|
336
|
+
confidence, from TypeSafe AI's Jev or from any model's log probabilities, gated by thresholds a
|
|
337
|
+
calibration chooses, so a graph's branch, an approval, or a guardrail acts when it is sure and asks
|
|
338
|
+
a person when it is not.
|
|
331
339
|
|
|
332
340
|
Upgrading from 1.x: `npx nexus migrate src --write` moves your imports, and
|
|
333
341
|
[MIGRATING.md](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/MIGRATING.md) covers the rest.
|
|
334
|
-
Next, 2.
|
|
335
|
-
|
|
342
|
+
Next, 2.20 reads a service's own API description into emulated tools, and lets an emulator hold
|
|
343
|
+
what a whole evaluation did, so examples that build on each other are evaluated with nothing sent.
|
|
344
|
+
Pinecone, images, the Gemini
|
|
336
345
|
embedding batch tier, media understanding, video, Gemini Live, SIP, and both phone providers
|
|
337
346
|
graduate once the live workflow passes against each. The
|
|
338
347
|
[roadmap](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/ROADMAP.md) lists what comes after, in the order
|
|
@@ -95,6 +95,11 @@ export function negotiateCompletionRequest(request, capabilities, options = {})
|
|
|
95
95
|
target.seed = undefined;
|
|
96
96
|
});
|
|
97
97
|
}
|
|
98
|
+
if (request.logprobs !== undefined && capabilities.logprobs === false) {
|
|
99
|
+
refuse('logprobs', request.logprobs, 'the model does not report token log probabilities', (target) => {
|
|
100
|
+
target.logprobs = undefined;
|
|
101
|
+
});
|
|
102
|
+
}
|
|
98
103
|
if (request.topK !== undefined && capabilities.topK === false) {
|
|
99
104
|
refuse('topK', request.topK, 'the model does not support top-k sampling', (target) => {
|
|
100
105
|
target.topK = undefined;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decisions from any completion model: each question asked on its own, answered with a one-token code,
|
|
3
|
+
* and its probabilities read from the code token's log probabilities, or counted over samples where
|
|
4
|
+
* the model reports none.
|
|
5
|
+
*/
|
|
6
|
+
import type { CompletionRequest } from '../types/messages.js';
|
|
7
|
+
import type { NexusResponse } from '../types/response.js';
|
|
8
|
+
import { type DecisionProvider } from './core.js';
|
|
9
|
+
/** Anything that completes a request, such as a `NexusAI` client. */
|
|
10
|
+
export interface DecisionCompletionClient {
|
|
11
|
+
/** Runs one completion. */
|
|
12
|
+
complete(request: CompletionRequest): Promise<NexusResponse>;
|
|
13
|
+
}
|
|
14
|
+
/** How a completion model's probabilities are read. */
|
|
15
|
+
export type CompletionDecisionMethod = 'auto' | 'logprobs' | 'sampling';
|
|
16
|
+
/** Options for `completionDecisions()`. */
|
|
17
|
+
export interface CompletionDecisionsOptions {
|
|
18
|
+
/** The model to ask, as the client names it. */
|
|
19
|
+
model: string;
|
|
20
|
+
/** The provider's name in results. Defaults to `model`. */
|
|
21
|
+
name?: string;
|
|
22
|
+
/**
|
|
23
|
+
* How probabilities are read: `logprobs`, from one call's token log probabilities; `sampling`, by
|
|
24
|
+
* counting the answers of several calls; or `auto`, the default: log probabilities, and sampling for
|
|
25
|
+
* a model that reports none or refuses to, which it then asks by sampling from the start.
|
|
26
|
+
*/
|
|
27
|
+
method?: CompletionDecisionMethod;
|
|
28
|
+
/** Calls per question, when sampling. Defaults to 8. */
|
|
29
|
+
samples?: number;
|
|
30
|
+
/** The temperature samples are drawn at. Defaults to 1. */
|
|
31
|
+
temperature?: number;
|
|
32
|
+
/** Questions asked at once. Defaults to 8. */
|
|
33
|
+
concurrency?: number;
|
|
34
|
+
/** Instructions added to every question, such as what the application's terms mean. */
|
|
35
|
+
instructions?: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Any completion model as a decision provider, through a client's own `complete()`, so every call is
|
|
39
|
+
* routed, authorized, budgeted, traced, and priced as every completion is. Each question is asked on
|
|
40
|
+
* its own, at temperature 0, to answer with one code: a letter per option, Y or N, or a level's digit.
|
|
41
|
+
* Its probabilities are read from the log probabilities of that token's alternatives where the model
|
|
42
|
+
* reports them, the way TypeSafe AI's Jev reads its own; elsewhere the question is asked `samples`
|
|
43
|
+
* times, and each answer counted. Sampled probabilities come in steps of one sample, and cost as many
|
|
44
|
+
* calls.
|
|
45
|
+
*/
|
|
46
|
+
export declare function completionDecisions(client: DecisionCompletionClient, options: CompletionDecisionsOptions): DecisionProvider & {
|
|
47
|
+
readonly sampledModels: ReadonlySet<string>;
|
|
48
|
+
};
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { DecisionError, } from './core.js';
|
|
2
|
+
const LOGPROB_OPTIONS = 20;
|
|
3
|
+
export function completionDecisions(client, options) {
|
|
4
|
+
const method = options.method ?? 'auto';
|
|
5
|
+
const sampled = new Set();
|
|
6
|
+
const info = {
|
|
7
|
+
name: options.name ?? options.model,
|
|
8
|
+
capabilities: {
|
|
9
|
+
types: ['yes-no', 'choice', 'score'],
|
|
10
|
+
maxOptions: 255,
|
|
11
|
+
maxLevels: 10,
|
|
12
|
+
defaultModel: options.model,
|
|
13
|
+
},
|
|
14
|
+
chargedByItsCalls: true,
|
|
15
|
+
};
|
|
16
|
+
const ask = async (model, state, question, context, usage) => {
|
|
17
|
+
const codes = codesOf(question);
|
|
18
|
+
const prompt = promptOf(state, question, codes, options.instructions);
|
|
19
|
+
const request = (extra) => ({
|
|
20
|
+
model,
|
|
21
|
+
messages: [
|
|
22
|
+
{
|
|
23
|
+
role: 'system',
|
|
24
|
+
content: 'You answer one question about a state with exactly one of the codes given, and nothing else.',
|
|
25
|
+
},
|
|
26
|
+
{ role: 'user', content: prompt },
|
|
27
|
+
],
|
|
28
|
+
signal: context.signal,
|
|
29
|
+
...(context.tenantId !== undefined ? { tenantId: context.tenantId } : {}),
|
|
30
|
+
...extra,
|
|
31
|
+
});
|
|
32
|
+
const readable = codes.length <= LOGPROB_OPTIONS && codes.every((code) => code.length === 1);
|
|
33
|
+
if (method !== 'sampling' && readable && !sampled.has(model)) {
|
|
34
|
+
let response;
|
|
35
|
+
try {
|
|
36
|
+
response = await client.complete(request({ temperature: 0, maxTokens: 2, logprobs: { top: 20 } }));
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
if (method === 'logprobs' || !refusedOption(error))
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
if (response) {
|
|
43
|
+
usage.add(response);
|
|
44
|
+
const weights = weightsFrom(response.logprobs, codes);
|
|
45
|
+
if (weights)
|
|
46
|
+
return distributionOf(question, codes, weights);
|
|
47
|
+
if (method === 'logprobs') {
|
|
48
|
+
throw new DecisionError(`"${model}" reported no log probabilities for the codes it was given`, 'DECISION_FAILED');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
sampled.add(model);
|
|
52
|
+
}
|
|
53
|
+
if (!readable && method === 'logprobs') {
|
|
54
|
+
throw new DecisionError(`A question of ${codes.length} options cannot be read from one token's ${LOGPROB_OPTIONS} alternatives`, 'DECISION_UNSUPPORTED');
|
|
55
|
+
}
|
|
56
|
+
const count = Math.max(1, Math.floor(options.samples ?? 8));
|
|
57
|
+
const answers = await Promise.all(Array.from({ length: count }, () => client.complete(request({ temperature: options.temperature ?? 1, maxTokens: 4 })).then((response) => {
|
|
58
|
+
usage.add(response);
|
|
59
|
+
return codeIn(response.content, codes);
|
|
60
|
+
})));
|
|
61
|
+
const weights = codes.map((code) => answers.filter((answer) => answer === code).length);
|
|
62
|
+
if (weights.every((weight) => weight === 0)) {
|
|
63
|
+
throw new DecisionError(`"${model}" answered none of ${count} samples with a code it was given`, 'DECISION_FAILED');
|
|
64
|
+
}
|
|
65
|
+
return distributionOf(question, codes, weights);
|
|
66
|
+
};
|
|
67
|
+
return {
|
|
68
|
+
info,
|
|
69
|
+
sampledModels: sampled,
|
|
70
|
+
async decide(request, context) {
|
|
71
|
+
const totals = new Totals();
|
|
72
|
+
const entries = Object.entries(request.questions);
|
|
73
|
+
const distributions = {};
|
|
74
|
+
const limit = Math.max(1, Math.floor(options.concurrency ?? 8));
|
|
75
|
+
let next = 0;
|
|
76
|
+
const worker = async () => {
|
|
77
|
+
while (next < entries.length) {
|
|
78
|
+
const [name, question] = entries[next++];
|
|
79
|
+
distributions[name] = await ask(request.model, request.state, question, context, totals);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
await Promise.all(Array.from({ length: Math.min(limit, entries.length) }, worker));
|
|
83
|
+
return {
|
|
84
|
+
model: request.model,
|
|
85
|
+
distributions,
|
|
86
|
+
usage: totals.usage(),
|
|
87
|
+
...(totals.cost() ? { cost: totals.cost() } : {}),
|
|
88
|
+
};
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
class Totals {
|
|
93
|
+
inputTokens = 0;
|
|
94
|
+
outputTokens = 0;
|
|
95
|
+
calls = 0;
|
|
96
|
+
amount = 0;
|
|
97
|
+
priced = false;
|
|
98
|
+
add(response) {
|
|
99
|
+
this.calls += 1;
|
|
100
|
+
this.inputTokens += response.meta.usage?.inputTokens ?? 0;
|
|
101
|
+
this.outputTokens += response.meta.usage?.outputTokens ?? 0;
|
|
102
|
+
if (response.meta.cost) {
|
|
103
|
+
this.amount += response.meta.cost.amount;
|
|
104
|
+
this.priced = true;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
usage() {
|
|
108
|
+
return { inputTokens: this.inputTokens, outputTokens: this.outputTokens, calls: this.calls };
|
|
109
|
+
}
|
|
110
|
+
cost() {
|
|
111
|
+
return this.priced ? { amount: this.amount, currency: 'USD', basis: 'estimated' } : undefined;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function codesOf(question) {
|
|
115
|
+
if (question.type === 'yes-no')
|
|
116
|
+
return ['Y', 'N'];
|
|
117
|
+
if (question.type === 'score')
|
|
118
|
+
return question.levels.map((_, level) => String(level));
|
|
119
|
+
return question.options.length <= 26
|
|
120
|
+
? question.options.map((_, index) => String.fromCharCode(65 + index))
|
|
121
|
+
: question.options.map((_, index) => String(index + 1));
|
|
122
|
+
}
|
|
123
|
+
function promptOf(state, question, codes, instructions) {
|
|
124
|
+
const text = typeof state === 'string' ? state : JSON.stringify(state, null, 2);
|
|
125
|
+
const lines = [`State:\n${text}`, ''];
|
|
126
|
+
if (instructions)
|
|
127
|
+
lines.push(instructions, '');
|
|
128
|
+
if (question.type === 'yes-no') {
|
|
129
|
+
lines.push(`Question: ${question.question ?? 'Is the statement true of the state?'}`);
|
|
130
|
+
lines.push(`Y: yes${question.criteria?.yes ? `, ${question.criteria.yes}` : ''}`);
|
|
131
|
+
lines.push(`N: no${question.criteria?.no ? `, ${question.criteria.no}` : ''}`);
|
|
132
|
+
}
|
|
133
|
+
else if (question.type === 'choice') {
|
|
134
|
+
lines.push(`Question: ${question.question ?? 'Which option fits the state?'}`);
|
|
135
|
+
question.options.forEach((option, index) => {
|
|
136
|
+
lines.push(`${codes[index]}: ${option.name}${option.description ? `, ${option.description}` : ''}`);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
lines.push(`Question: ${question.question ?? 'Which level fits the state?'}`);
|
|
141
|
+
question.levels.forEach((level, index) => {
|
|
142
|
+
lines.push(`${codes[index]}: ${level}`);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
lines.push('', `Answer with one code: ${codes.join(', ')}.`);
|
|
146
|
+
return lines.join('\n');
|
|
147
|
+
}
|
|
148
|
+
function codeOf(token) {
|
|
149
|
+
return token.replace(/^[\s"'`*(]+|[\s"'`*).:,]+$/g, '').toUpperCase();
|
|
150
|
+
}
|
|
151
|
+
function weightsFrom(logprobs, codes) {
|
|
152
|
+
const first = logprobs?.find((item) => item.token.trim() !== '');
|
|
153
|
+
if (!first)
|
|
154
|
+
return undefined;
|
|
155
|
+
const candidates = first.top?.length ? first.top : [{ token: first.token, logprob: first.logprob }];
|
|
156
|
+
const weights = codes.map(() => 0);
|
|
157
|
+
let found = false;
|
|
158
|
+
for (const candidate of candidates) {
|
|
159
|
+
const index = matchCode(candidate.token, codes);
|
|
160
|
+
if (index < 0 || !Number.isFinite(candidate.logprob))
|
|
161
|
+
continue;
|
|
162
|
+
weights[index] = weights[index] + Math.exp(candidate.logprob);
|
|
163
|
+
found = true;
|
|
164
|
+
}
|
|
165
|
+
return found ? weights : undefined;
|
|
166
|
+
}
|
|
167
|
+
function codeIn(content, codes) {
|
|
168
|
+
const head = content.trim().split(/[\s:,.)]+/)[0] ?? '';
|
|
169
|
+
return codes[matchCode(head, codes)];
|
|
170
|
+
}
|
|
171
|
+
function matchCode(token, codes) {
|
|
172
|
+
const code = codeOf(token);
|
|
173
|
+
const index = codes.indexOf(code);
|
|
174
|
+
if (index >= 0 || codes.length !== 2 || codes[0] !== 'Y' || codes[1] !== 'N')
|
|
175
|
+
return index;
|
|
176
|
+
if (code === 'YES' || code === 'TRUE')
|
|
177
|
+
return 0;
|
|
178
|
+
return code === 'NO' || code === 'FALSE' ? 1 : -1;
|
|
179
|
+
}
|
|
180
|
+
function distributionOf(question, codes, weights) {
|
|
181
|
+
const total = weights.reduce((sum, weight) => sum + weight, 0);
|
|
182
|
+
const share = (index) => (total > 0 ? (weights[index] ?? 0) / total : 1 / codes.length);
|
|
183
|
+
if (question.type === 'yes-no')
|
|
184
|
+
return { type: 'yes-no', yes: share(0) };
|
|
185
|
+
if (question.type === 'score')
|
|
186
|
+
return { type: 'score', probabilities: codes.map((_, index) => share(index)) };
|
|
187
|
+
return {
|
|
188
|
+
type: 'choice',
|
|
189
|
+
probabilities: Object.fromEntries(question.options.map((option, index) => [option.name, share(index)])),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
function refusedOption(error) {
|
|
193
|
+
const status = error?.status;
|
|
194
|
+
return status === 400 || status === 422;
|
|
195
|
+
}
|