nexus-ai-pro 2.20.2 → 2.22.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 +93 -0
- package/CHANGELOG.md +208 -1
- package/README.md +11 -6
- package/SECURITY.md +16 -2
- package/dist/adapter-kit/gather.d.ts +76 -0
- package/dist/adapter-kit/gather.js +147 -0
- package/dist/adapter-kit/index.d.ts +1 -0
- package/dist/adapter-kit/index.js +1 -0
- package/dist/adapter-kit/record.d.ts +3 -1
- package/dist/adapter-kit/record.js +20 -5
- package/dist/agent/openapi.d.ts +94 -8
- package/dist/agent/openapi.js +528 -32
- package/dist/cli/adapters.d.ts +4 -3
- package/dist/cli/adapters.js +71 -2
- package/dist/cli/doctor.js +3 -0
- package/dist/cli.js +3 -1
- package/dist/core/config-builder.d.ts +1 -0
- package/dist/core/config-builder.js +1 -0
- package/dist/core/defaults.d.ts +34 -0
- package/dist/core/defaults.js +39 -0
- package/dist/core/secure-stream.js +4 -1
- package/dist/decisions/completion.d.ts +10 -7
- package/dist/decisions/completion.js +82 -8
- package/dist/decisions/core.d.ts +88 -43
- package/dist/decisions/core.js +115 -28
- package/dist/decisions/errors.d.ts +25 -0
- package/dist/decisions/errors.js +12 -0
- package/dist/decisions/index.d.ts +6 -5
- package/dist/decisions/index.js +2 -1
- package/dist/decisions/integrations.d.ts +14 -5
- package/dist/decisions/integrations.js +16 -1
- package/dist/decisions/mock.d.ts +5 -3
- package/dist/decisions/mock.js +4 -1
- package/dist/decisions/pictures.d.ts +43 -0
- package/dist/decisions/pictures.js +103 -0
- package/dist/decisions/typesafe.d.ts +33 -5
- package/dist/decisions/typesafe.js +121 -8
- package/dist/doctor/index.d.ts +6 -9
- package/dist/doctor/index.js +41 -0
- package/dist/embeddings/adapters.d.ts +1 -1
- package/dist/embeddings/adapters.js +3 -3
- package/dist/embeddings/batch-google.d.ts +27 -8
- package/dist/embeddings/batch-google.js +112 -36
- package/dist/embeddings/batch-mock.js +1 -1
- package/dist/embeddings/batch.js +10 -4
- package/dist/embeddings/engine.js +14 -13
- package/dist/embeddings/index.d.ts +1 -1
- package/dist/embeddings/inputs.d.ts +26 -3
- package/dist/embeddings/inputs.js +84 -5
- package/dist/embeddings/mock.js +1 -1
- package/dist/embeddings/models.js +39 -4
- package/dist/loaders/csv.d.ts +23 -1
- package/dist/loaders/csv.js +125 -72
- package/dist/loaders/index.d.ts +7 -2
- package/dist/loaders/input.d.ts +6 -0
- package/dist/loaders/input.js +42 -1
- package/dist/loaders/json.d.ts +14 -3
- package/dist/loaders/json.js +171 -20
- package/dist/loaders/web.d.ts +55 -0
- package/dist/loaders/web.js +20 -1
- package/dist/mcp/client.d.ts +54 -5
- package/dist/mcp/client.js +171 -20
- package/dist/mcp/http.d.ts +45 -0
- package/dist/mcp/http.js +233 -0
- package/dist/mcp/index.d.ts +4 -2
- package/dist/mcp/index.js +3 -1
- package/dist/mcp/protocol.d.ts +107 -0
- package/dist/mcp/sampling.d.ts +41 -0
- package/dist/mcp/sampling.js +52 -0
- package/dist/mcp/server.d.ts +96 -14
- package/dist/mcp/server.js +274 -59
- package/dist/models/data.js +20 -1
- package/dist/operations/adapters.d.ts +1 -0
- package/dist/operations/adapters.js +3 -1
- package/dist/protocols/a2a-grpc.js +10 -3
- package/dist/protocols/a2a-signing.d.ts +65 -0
- package/dist/protocols/a2a-signing.js +133 -0
- package/dist/protocols/a2a.d.ts +14 -0
- package/dist/protocols/a2a.js +60 -5
- package/dist/providers/cohere.js +16 -2
- package/dist/providers/google.js +7 -1
- package/dist/providers/ollama.d.ts +1 -1
- package/dist/providers/ollama.js +35 -27
- package/dist/providers/openai.js +19 -2
- package/dist/rag/pinecone.d.ts +63 -2
- package/dist/rag/pinecone.js +80 -0
- package/dist/realtime/conversation.js +18 -0
- package/dist/realtime/gemini-live.transport.d.ts +28 -3
- package/dist/realtime/gemini-live.transport.js +170 -32
- package/dist/realtime/session.d.ts +2 -0
- package/dist/realtime/session.js +19 -4
- package/dist/realtime/types.d.ts +35 -3
- package/dist/security/output-guard.js +4 -1
- package/dist/server/auth.d.ts +263 -2
- package/dist/server/auth.js +432 -5
- package/dist/server/deployments.d.ts +3 -2
- package/dist/server/deployments.js +6 -1
- package/dist/telephony/index.d.ts +1 -1
- package/dist/telephony/manager.d.ts +8 -1
- package/dist/telephony/manager.js +13 -0
- package/dist/telephony/providers/telnyx.d.ts +18 -5
- package/dist/telephony/providers/telnyx.js +44 -7
- package/dist/telephony/providers/twilio.d.ts +3 -1
- package/dist/telephony/providers/twilio.js +16 -0
- package/dist/tracing/stores.d.ts +2 -0
- package/dist/tracing/stores.js +1 -0
- package/dist/tracing/tracer.d.ts +5 -2
- package/dist/tracing/tracer.js +6 -3
- package/dist/types/embeddings.d.ts +47 -5
- package/dist/types/messages.d.ts +7 -0
- package/dist/types/principal.d.ts +1 -1
- package/dist/types/response.d.ts +5 -0
- package/dist/types/telephony.d.ts +38 -0
- package/dist/types/tracing.d.ts +6 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/voice/providers/google.d.ts +44 -0
- package/dist/voice/providers/google.js +215 -0
- package/dist-cjs/adapter-kit/gather.d.ts +76 -0
- package/dist-cjs/adapter-kit/gather.js +152 -0
- package/dist-cjs/adapter-kit/index.d.ts +1 -0
- package/dist-cjs/adapter-kit/index.js +4 -1
- package/dist-cjs/adapter-kit/record.d.ts +3 -1
- package/dist-cjs/adapter-kit/record.js +20 -5
- package/dist-cjs/agent/openapi.d.ts +94 -8
- package/dist-cjs/agent/openapi.js +530 -32
- package/dist-cjs/cli/adapters.d.ts +4 -3
- package/dist-cjs/cli/adapters.js +70 -1
- package/dist-cjs/cli/doctor.js +3 -0
- package/dist-cjs/core/config-builder.d.ts +1 -0
- package/dist-cjs/core/config-builder.js +5 -1
- package/dist-cjs/core/defaults.d.ts +34 -0
- package/dist-cjs/core/defaults.js +44 -0
- package/dist-cjs/core/secure-stream.js +4 -1
- package/dist-cjs/decisions/completion.d.ts +10 -7
- package/dist-cjs/decisions/completion.js +81 -7
- package/dist-cjs/decisions/core.d.ts +88 -43
- package/dist-cjs/decisions/core.js +134 -46
- package/dist-cjs/decisions/errors.d.ts +25 -0
- package/dist-cjs/decisions/errors.js +16 -0
- package/dist-cjs/decisions/index.d.ts +6 -5
- package/dist-cjs/decisions/index.js +5 -1
- package/dist-cjs/decisions/integrations.d.ts +14 -5
- package/dist-cjs/decisions/integrations.js +16 -1
- package/dist-cjs/decisions/mock.d.ts +5 -3
- package/dist-cjs/decisions/mock.js +4 -1
- package/dist-cjs/decisions/pictures.d.ts +43 -0
- package/dist-cjs/decisions/pictures.js +109 -0
- package/dist-cjs/decisions/typesafe.d.ts +33 -5
- package/dist-cjs/decisions/typesafe.js +126 -13
- package/dist-cjs/doctor/index.d.ts +6 -9
- package/dist-cjs/doctor/index.js +41 -0
- package/dist-cjs/embeddings/adapters.d.ts +1 -1
- package/dist-cjs/embeddings/adapters.js +3 -3
- package/dist-cjs/embeddings/batch-google.d.ts +27 -8
- package/dist-cjs/embeddings/batch-google.js +112 -36
- package/dist-cjs/embeddings/batch-mock.js +1 -1
- package/dist-cjs/embeddings/batch.js +9 -3
- package/dist-cjs/embeddings/engine.js +13 -12
- package/dist-cjs/embeddings/index.d.ts +1 -1
- package/dist-cjs/embeddings/inputs.d.ts +26 -3
- package/dist-cjs/embeddings/inputs.js +89 -5
- package/dist-cjs/embeddings/mock.js +1 -1
- package/dist-cjs/embeddings/models.js +39 -4
- package/dist-cjs/loaders/csv.d.ts +23 -1
- package/dist-cjs/loaders/csv.js +126 -71
- package/dist-cjs/loaders/index.d.ts +7 -2
- package/dist-cjs/loaders/input.d.ts +6 -0
- package/dist-cjs/loaders/input.js +43 -1
- package/dist-cjs/loaders/json.d.ts +14 -3
- package/dist-cjs/loaders/json.js +170 -19
- package/dist-cjs/loaders/web.d.ts +55 -0
- package/dist-cjs/loaders/web.js +21 -1
- package/dist-cjs/mcp/client.d.ts +54 -5
- package/dist-cjs/mcp/client.js +171 -20
- package/dist-cjs/mcp/http.d.ts +45 -0
- package/dist-cjs/mcp/http.js +236 -0
- package/dist-cjs/mcp/index.d.ts +4 -2
- package/dist-cjs/mcp/index.js +5 -1
- package/dist-cjs/mcp/protocol.d.ts +107 -0
- package/dist-cjs/mcp/sampling.d.ts +41 -0
- package/dist-cjs/mcp/sampling.js +55 -0
- package/dist-cjs/mcp/server.d.ts +96 -14
- package/dist-cjs/mcp/server.js +273 -58
- package/dist-cjs/models/data.js +20 -1
- package/dist-cjs/operations/adapters.d.ts +1 -0
- package/dist-cjs/operations/adapters.js +3 -1
- package/dist-cjs/protocols/a2a-grpc.js +10 -3
- package/dist-cjs/protocols/a2a-signing.d.ts +65 -0
- package/dist-cjs/protocols/a2a-signing.js +140 -0
- package/dist-cjs/protocols/a2a.d.ts +14 -0
- package/dist-cjs/protocols/a2a.js +65 -6
- package/dist-cjs/providers/cohere.js +16 -2
- package/dist-cjs/providers/google.js +7 -1
- package/dist-cjs/providers/ollama.d.ts +1 -1
- package/dist-cjs/providers/ollama.js +35 -27
- package/dist-cjs/providers/openai.js +19 -2
- package/dist-cjs/rag/pinecone.d.ts +63 -2
- package/dist-cjs/rag/pinecone.js +81 -0
- package/dist-cjs/realtime/conversation.js +18 -0
- package/dist-cjs/realtime/gemini-live.transport.d.ts +28 -3
- package/dist-cjs/realtime/gemini-live.transport.js +170 -32
- package/dist-cjs/realtime/session.d.ts +2 -0
- package/dist-cjs/realtime/session.js +19 -4
- package/dist-cjs/realtime/types.d.ts +35 -3
- package/dist-cjs/security/output-guard.js +4 -1
- package/dist-cjs/server/auth.d.ts +263 -2
- package/dist-cjs/server/auth.js +437 -5
- package/dist-cjs/server/deployments.d.ts +3 -2
- package/dist-cjs/server/deployments.js +6 -1
- package/dist-cjs/telephony/index.d.ts +1 -1
- package/dist-cjs/telephony/manager.d.ts +8 -1
- package/dist-cjs/telephony/manager.js +13 -0
- package/dist-cjs/telephony/providers/telnyx.d.ts +18 -5
- package/dist-cjs/telephony/providers/telnyx.js +44 -7
- package/dist-cjs/telephony/providers/twilio.d.ts +3 -1
- package/dist-cjs/telephony/providers/twilio.js +16 -0
- package/dist-cjs/tracing/stores.d.ts +2 -0
- package/dist-cjs/tracing/stores.js +1 -0
- package/dist-cjs/tracing/tracer.d.ts +5 -2
- package/dist-cjs/tracing/tracer.js +6 -3
- package/dist-cjs/types/embeddings.d.ts +47 -5
- package/dist-cjs/types/messages.d.ts +7 -0
- package/dist-cjs/types/principal.d.ts +1 -1
- package/dist-cjs/types/response.d.ts +5 -0
- package/dist-cjs/types/telephony.d.ts +38 -0
- package/dist-cjs/types/tracing.d.ts +6 -0
- package/dist-cjs/version.d.ts +1 -1
- package/dist-cjs/version.js +1 -1
- package/dist-cjs/voice/providers/google.d.ts +44 -0
- package/dist-cjs/voice/providers/google.js +252 -0
- package/llms.txt +1 -1
- package/package.json +17 -1
package/API_STABILITY.md
CHANGED
|
@@ -67,6 +67,99 @@ graduation:
|
|
|
67
67
|
| Upgrade | A migration test from the previous minor |
|
|
68
68
|
| Soak | A long-running test with flat memory and no leaked handles |
|
|
69
69
|
|
|
70
|
+
## 2.22 stage (2.22.0)
|
|
71
|
+
|
|
72
|
+
Everything 2.22 adds is stable from the start and additive, unless this section says otherwise.
|
|
73
|
+
|
|
74
|
+
New and stable: `StreamChunk.logprobs`; in `nexus-ai-pro/protocols/a2a`, `signA2aCard()`,
|
|
75
|
+
`verifyA2aCard()`, `canonicalA2aCard()`, `A2aCardSignatureError`, `A2aCardSigner`, `A2aCardKeyResolver`,
|
|
76
|
+
`A2aSignatureAlgorithm`, `A2aJsonWebKey`, `A2aCryptoKey`, `A2aServerOptions.sign`, and
|
|
77
|
+
`A2aClientOptions.verifyCard`, which `A2aGrpcClientOptions` takes too; in `nexus-ai-pro/mcp`,
|
|
78
|
+
`mcpHttpHandler()`, `McpHttpOptions`, `McpHttpHandler`, `samplingFrom()`, `McpSamplingOptions`,
|
|
79
|
+
`McpSamplingClient`, `McpServerPrompt`, `McpPromptArgument`, `McpPromptMessage`, `McpServerResource`,
|
|
80
|
+
the server's `registerPrompt()`, `registerResource()`, `resourceUpdated()`, `createMessage()`,
|
|
81
|
+
`elicit()`, `listRoots()`, and `onRootsChanged()`, the client's `sampling`, `elicitation`, and `roots`
|
|
82
|
+
options, `onPromptsChanged()`, `onResourcesChanged()`, `onResourceUpdated()`, `subscribeResource()`,
|
|
83
|
+
`unsubscribeResource()`, and `rootsChanged()`, `HttpClientOptions.listen`, the protocol types
|
|
84
|
+
`McpSamplingRequest`, `McpSamplingResult`, `McpSamplingMessage`, `McpSamplingContent`,
|
|
85
|
+
`McpModelPreferences`, `McpElicitationRequest`, `McpElicitationResult`, `McpRoot`, and
|
|
86
|
+
`McpCallerRequests`, and `ToolContext.mcp`; in `nexus-ai-pro/agent/openapi`, `swaggerToOpenApi()`,
|
|
87
|
+
`bundleOpenApiDocument()`, `OpenApiBundleOptions`, `OpenApiCredential`, `OpenApiWebhook`,
|
|
88
|
+
`OpenApiToolsOptions.credentials`, `OpenApiOperation.security`, `OpenApiTools.webhooks`, and
|
|
89
|
+
`loadOpenApiTools()`'s `references`, `maxDocuments`, and `maxBytes`; in `nexus-ai-pro/server/auth`,
|
|
90
|
+
`createJwtSigner()`, `JwtSigner`, `JwtSignerOptions`, `JwtSignOptions`, `generateJwtKey()`,
|
|
91
|
+
`jwksHandler()`, `createApiKeyStore()`, `ApiKeyStore`, `ApiKeyStoreOptions`, `ApiKeyRecord`,
|
|
92
|
+
`IssuedApiKey`, `ApiKeyIssueOptions`, `createSessions()`, `Sessions`, `SessionOptions`,
|
|
93
|
+
`SessionCookieOptions`, `SessionRecord`, and `StartedSession`, and the principal method `session`; in
|
|
94
|
+
`nexus-ai-pro/rag/pinecone`, `PineconeVectorStore.create()`, `ensurePineconeIndex()`,
|
|
95
|
+
`PineconeIndexOptions`, `PineconeIndex`, and `PineconeCreateOptions`, experimental with the store;
|
|
96
|
+
`TypeSafeDecisionProviderOptions.pictures`; and in `nexus-ai-pro/embeddings`, `EmbeddingVideoPart`, the
|
|
97
|
+
modality `video`, `EmbeddingMeta.unpricedMedia`, and `EmbeddingModelCapabilities`'
|
|
98
|
+
`costPerAudioSecond`, `costPerVideoFrame`, `videoFrames`, `maxAudioSeconds`, and `maxVideoSeconds`; in
|
|
99
|
+
the loaders, `FileInput`'s stream form, `CsvRowParser`, `JsonLoaderOptions.recordsAt`,
|
|
100
|
+
`WebLoaderOptions.render` and `renderBelow`, `browserRenderer()`, `WebPageRenderer`, `WebPageToRender`,
|
|
101
|
+
`RenderingBrowser`, and `BrowserRendererOptions`; in the realtime family, `ImageItem`, the `message.image`
|
|
102
|
+
event, and `RealtimeConversationOptions.recordImages`; in telephony, `TelephonyGatherResult`,
|
|
103
|
+
`EndGatherRequest`, `parseGatherResult()` and `endGather()` on the manager and the providers, and
|
|
104
|
+
`TelnyxTelephonyProviderConfig.transcriptionEngine`; in the studio, `StudioOptions.tenantSources`,
|
|
105
|
+
`StudioUser.tenant`, `StudioAuditEntry.tenant`, `StudioComment.tenant`, `StudioAuditQuery`,
|
|
106
|
+
`HeaderAuthOptions.tenant`, `BearerAuthOptions.tenant`, `StudioSignIn`, and `StudioOptions.signIn`.
|
|
107
|
+
Experimental: in the studio, `oidcSignIn()`, `OidcSignInOptions`, `SIGN_IN_COOKIE`, and the
|
|
108
|
+
`/auth/` routes, until a sign-in against a live provider is recorded; `nexus-ai-pro/voice/google`,
|
|
109
|
+
`GoogleVoiceProvider` and `GoogleVoiceProviderConfig`, until a live run is recorded.
|
|
110
|
+
The state store keys and sessions are kept in, under `nexus/server`, is read by every later 2.x.
|
|
111
|
+
|
|
112
|
+
Changed on purpose: an answer the output guard changes or blocks keeps no `logprobs`, streamed or
|
|
113
|
+
not; a typed decision of a model that reports the chosen token alone is sampled; an Ollama stream
|
|
114
|
+
honours `stop`. An MCP server declares prompts and resource subscriptions at `initialize` whatever it
|
|
115
|
+
was given, and serves every transport connected to it rather than the last; a tool it runs gets a
|
|
116
|
+
`signal` and `mcp` in its context. The HTTP client transport returns from `send()` before a streamed
|
|
117
|
+
answer ends, sends `mcp-protocol-version`, and sends a `DELETE` when closed with a session. OpenAPI
|
|
118
|
+
tools are made of operations with any request body, where those with a body other than JSON or a form
|
|
119
|
+
were skipped, and of Swagger 2.0 documents, which were refused; `loadOpenApiTools()` reads no more of
|
|
120
|
+
a document than `maxBytes`. With `credentials` given, an operation whose security cannot be met fails
|
|
121
|
+
before it is sent.
|
|
122
|
+
|
|
123
|
+
A TypeSafe AI request with pictures is multipart unless `pictures: 'base64'`, and its size is counted
|
|
124
|
+
in bytes. gemini-embedding-2's audio and video are priced, where they cost nothing before, and audio
|
|
125
|
+
past 180 seconds or video past 120 is refused before the call. A Google embedding batch over 20 MB is
|
|
126
|
+
sent as a file, where it was refused. Gemini Live acts on `updateSession()`, which it ignored. A
|
|
127
|
+
realtime record holds the images sent, as `image` items, which a consumer that switches on every item
|
|
128
|
+
type meets for the first time. A Telnyx gather for speech transcribes the caller, where it was
|
|
129
|
+
refused.
|
|
130
|
+
|
|
131
|
+
Deprecated: `OpenApiTools.skipped`, always empty, is removed in 3.0.
|
|
132
|
+
|
|
133
|
+
## 2.21 stage (2.21.0)
|
|
134
|
+
|
|
135
|
+
Everything 2.21 adds is stable from the start and additive, unless this section says otherwise.
|
|
136
|
+
|
|
137
|
+
New and stable, in `nexus-ai-pro/decisions`: `BoxQuestion`, `BoxAnswer`, `DecisionBox`, `boxOverlap()`,
|
|
138
|
+
`statePictures()`, `readPicture()`, `DecisionPicture`, `ReadDecisionPicture`, the question type `box`
|
|
139
|
+
and its `DecisionDistribution`, `DecisionProviderCapabilities.images`, `answeredRight()`'s box and
|
|
140
|
+
`overlap`, `DecisionEvaluatorOptions.where` and `overlap`, `DecisionConformanceOptions.picture`,
|
|
141
|
+
`DecisionConformanceReport.skipped`, and `CompletionDecisionsOptions.images`; in
|
|
142
|
+
`nexus-ai-pro/decisions/typesafe`, `TypeSafeDecisionProviderOptions.media`; in `nexus-ai-pro/adapter-kit`,
|
|
143
|
+
`gatherRecords()`, `GatherRecordsOptions`, `GatherReport`, `GatheredSource`, `GatherOutcome`,
|
|
144
|
+
`RecordSource`, and `ADAPTER_KEYWORD`; the command `nexus adapters gather`; a package's
|
|
145
|
+
`nexus.compatibility` field, the URL of its record; in `nexus-ai-pro/config`, `useDefaults()`,
|
|
146
|
+
`nexusDefaults()`, `NexusDefaults`, `DEFAULT_CHANGES`, and `DefaultChange`, and the `NEXUS_DEFAULTS`
|
|
147
|
+
variable; `DoctorOptions.upgrade` and `nexus doctor --upgrade`; and `TraceStore.durable`. The
|
|
148
|
+
preview turns on exactly what 3.0 will; should 3.0 change its defaults again before it ships, the
|
|
149
|
+
preview follows, and `DEFAULT_CHANGES` says so. A box from TypeSafe AI rests
|
|
150
|
+
on what it marks as experimental, its pictures and boxes, whose request format may change between its
|
|
151
|
+
releases.
|
|
152
|
+
|
|
153
|
+
Changed on purpose: TypeSafe AI's 400 and 413 are `DECISION_INVALID`, not `DECISION_FAILED`; neither
|
|
154
|
+
was retried before or is now. A provider of your own that reads pictures declares `images: true`;
|
|
155
|
+
without it, a decision about a picture is not sent to it. A union over `DecisionQuestionType` or
|
|
156
|
+
`DecisionAnswer` that handled three kinds now meets a fourth, `box`. `compatibilityPage()` writes a
|
|
157
|
+
repository that is not an `http` or `https` address as text, not a link, and escapes a record's
|
|
158
|
+
Markdown. A doctor report has one more check, `upgrade`, skipped unless asked for. With `tenants`, an
|
|
159
|
+
A2A handler answers `/{tenant}/.well-known/agent-card.json` with that tenant's card, where it answered
|
|
160
|
+
the card itself; without `tenants`, nothing changes. A model on OpenAI's Responses API is sent
|
|
161
|
+
`include` and `top_logprobs` when a request asks for log probabilities, which it was not before.
|
|
162
|
+
|
|
70
163
|
## 2.20 stage (2.20.0)
|
|
71
164
|
|
|
72
165
|
Everything 2.20 adds is stable from the start and additive, unless this section says otherwise.
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,211 @@ Notable changes to this project are recorded here. The format follows [Keep a Ch
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [2.22.0] - 2026-10-29
|
|
8
|
+
|
|
9
|
+
MCP over streamable HTTP both ways, OpenAPI tools that send each operation's credentials, tokens, keys,
|
|
10
|
+
and sessions the server issues, and a studio that signs people in and serves many tenants; with signed
|
|
11
|
+
A2A cards, embeddings of video, loaders that stream, Gemini's voice, and the kernel in Firefox and
|
|
12
|
+
WebKit.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **Log probabilities from a stream.** Each `text` chunk's `StreamChunk.logprobs` holds its tokens'
|
|
17
|
+
log probabilities, from OpenAI's Chat Completions and Responses API, Gemini, Ollama, and Cohere, so
|
|
18
|
+
the chunks' together are what `complete()` reports.
|
|
19
|
+
- Cohere reports log probabilities, `complete()` and streamed: a piece of text at a time, each piece's
|
|
20
|
+
the sum of its tokens', with no alternatives.
|
|
21
|
+
- **MCP both ways.** A server offers prompts (`McpServerPrompt`, `registerPrompt()`), resources a client
|
|
22
|
+
subscribes to (`resourceUpdated()`), and tells every client each list's changes; a tool it runs may ask
|
|
23
|
+
the client that called it for a completion from its model, an answer from its person, or its roots,
|
|
24
|
+
through `context.mcp`, and is stopped when the client cancels it. A client answers a server's
|
|
25
|
+
`sampling`, `elicitation`, and `roots` with handlers of its own, `samplingFrom()` answering with
|
|
26
|
+
the application's own model, and hears each list change and each resource it subscribed to.
|
|
27
|
+
- **MCP over streamable HTTP, both ways.** `mcpHttpHandler()` serves an `McpServer` as a Web handler:
|
|
28
|
+
a session per client, a stream of events per request carrying the server's progress and questions
|
|
29
|
+
before its answer, a `GET` stream for what it sends of its own accord, `DELETE` to end a session, and
|
|
30
|
+
a request from another origin refused. The client's HTTP transport reads a stream as it comes, so a
|
|
31
|
+
question in the middle of a call arrives in time, listens on a `GET` stream with `listen`, sends the
|
|
32
|
+
protocol version, and ends its session on `close()`. The nightly interoperability run drives the
|
|
33
|
+
server with `@modelcontextprotocol/sdk`'s own client: its tools, a completion asked mid-call, its
|
|
34
|
+
progress, a prompt, and a subscribed resource's change.
|
|
35
|
+
- **Signed A2A cards.** `signA2aCard()` and `verifyA2aCard()`, with `canonicalA2aCard()`, sign and check
|
|
36
|
+
a card as protocol 1.0 does, a flattened JWS over its canonical form, which is the A2A project's SDK's
|
|
37
|
+
form: a card either signs, the other verifies. A handler's `sign` signs every card it serves, each
|
|
38
|
+
tenant's and the extended card included, and a client's `verifyCard` makes no call until a signature
|
|
39
|
+
on the card holds. ES256, ES384, ES512, RS256, RS384, RS512, PS256, PS384, PS512, and Ed25519, by the
|
|
40
|
+
Web Crypto API alone. The nightly interoperability run checks both directions against `@a2a-js/sdk`.
|
|
41
|
+
- **OpenAPI credentials.** Tools read from an OpenAPI document send the credentials each operation's
|
|
42
|
+
security asks for, from `credentials` given by scheme name, or chosen per call: an API key in a
|
|
43
|
+
header, query parameter, or cookie; HTTP Basic, bearer, or another HTTP scheme; and an OAuth 2.0 or
|
|
44
|
+
OpenID Connect token, held, or fetched by the client-credentials flow for the operation's scopes and
|
|
45
|
+
kept until a minute before it expires. An operation whose alternatives none can be met fails before
|
|
46
|
+
anything is sent, naming them. Each `OpenApiOperation` carries its `security`.
|
|
47
|
+
- **Swagger 2.0, and references to other documents.** A Swagger 2.0 document is read as the OpenAPI 3.0
|
|
48
|
+
one it describes, which `swaggerToOpenApi()` returns. `bundleOpenApiDocument()` reads in the other
|
|
49
|
+
documents a document's references name, on its own origin by default, or as `references` allows,
|
|
50
|
+
within `maxDocuments` and `maxBytes`; `loadOpenApiTools()` does so itself.
|
|
51
|
+
- OpenAPI tools send a multipart form, its file fields given as base64, a text or XML body, and a body
|
|
52
|
+
of bytes, such as an image, so every operation becomes a tool. `webhooks` lists what the API sends
|
|
53
|
+
the application, its webhooks and its operations' callbacks, each with its JSON body's schema.
|
|
54
|
+
- **Tokens, keys, and sessions the server issues.** `createJwtSigner()` issues tokens with a secret, a
|
|
55
|
+
PEM or JWK private key, or a Web Crypto key pair, and `issue()` writes a principal under the claim
|
|
56
|
+
names `jwtAuth()` reads back; `generateJwtKey()` makes a key, and `jwksHandler()` publishes the
|
|
57
|
+
public halves, for a rotation that refuses no live token. `createApiKeyStore()` issues, rotates with
|
|
58
|
+
a grace period, and revokes API keys, kept as hashes in the server's state store with when each was
|
|
59
|
+
last used. `createSessions()` signs browsers in after a sign-in of the application's own: an
|
|
60
|
+
`HttpOnly`, `Secure`, `SameSite=Lax` cookie of a random secret whose hash alone is kept, an idle
|
|
61
|
+
and a total lifetime, sign-out here or everywhere, and a request from another site's page refused.
|
|
62
|
+
- **A Pinecone store makes its own index.** `PineconeVectorStore.create()` creates a serverless index
|
|
63
|
+
with the cosine metric and the store's width, or uses one of that name that fits, waits until it is
|
|
64
|
+
ready, and returns a store over its host; `ensurePineconeIndex()` does the first part alone.
|
|
65
|
+
- TypeSafe AI is sent a decision's pictures as parts of a multipart request, as bytes, so three
|
|
66
|
+
pictures of 10 MiB fit in the 32 MiB a request holds; `TypeSafeDecisionProviderOptions.pictures`
|
|
67
|
+
set to `base64` sends them within the JSON, as before.
|
|
68
|
+
- **Embeddings of video, and audio and video priced by length.** An `EmbeddingVideoPart` holds a video
|
|
69
|
+
by its bytes, base64, or a URL, such as a Files API URI, and gemini-embedding-2 embeds it. Audio is
|
|
70
|
+
priced by the second and video by the frame the model takes, one a second and at most 32 on
|
|
71
|
+
gemini-embedding-2, at $0.00016 a second and $0.00079 a frame, each length read from the media's own
|
|
72
|
+
headers; a budget counts them before the call. Media longer than a model's `maxAudioSeconds` or
|
|
73
|
+
`maxVideoSeconds` is refused before it is sent, and media whose length cannot be read is counted in
|
|
74
|
+
`unpricedMedia`. A registry data file sets these prices and limits too, each checked when it loads.
|
|
75
|
+
- A Google embedding batch over 20 MB, up to 2 GB, is uploaded as a JSONL file through the Files API, and
|
|
76
|
+
its results are read from the file the batch writes, matched to their inputs by key.
|
|
77
|
+
- **CSV and JSON stream through.** `loadCsv()` and `loadJson()` read a file as it arrives and yield each
|
|
78
|
+
row or record as soon as it is read: a path in chunks, or a `FileInput` that is now also a stream,
|
|
79
|
+
such as a response's body. `CsvRowParser` parses CSV a piece at a time, and `recordsAt` names the
|
|
80
|
+
array of records inside a JSON document, such as `data.items`, read item by item. A file larger than
|
|
81
|
+
memory goes to the store a batch at a time.
|
|
82
|
+
- **Pages built by script.** `loadWebPages()` takes a `render` of the application's own for a page
|
|
83
|
+
whose text a script builds, and `browserRenderer()` makes one from a Playwright or Puppeteer browser,
|
|
84
|
+
with no browser a dependency of the package.
|
|
85
|
+
- **Gemini voice.** `GoogleVoiceProvider`, on `nexus-ai-pro/voice/google`, speaks in Gemini's voices, as
|
|
86
|
+
WAV or raw PCM, whichever its model answers with, directed by a system instruction that is never
|
|
87
|
+
spoken; and transcribes with a text model that hears the audio, inline or by a Files API URI, guided
|
|
88
|
+
by its language and the names it may hold. Experimental until a live run is recorded.
|
|
89
|
+
- **Gemini Live changes its setup mid-call.** `updateSession()` on Gemini Live sets the session up again
|
|
90
|
+
between turns, on a new socket resumed from the latest handle: instructions, voice, language,
|
|
91
|
+
modalities, tools, turn detection, the model, and Gemini's own setup fields. What is sent meanwhile
|
|
92
|
+
waits, and the conversation carries on.
|
|
93
|
+
- **Images in the realtime record.** An image sent in a realtime conversation is kept as an `ImageItem`,
|
|
94
|
+
with its digest and, unless `recordImages` is `digest`, the image, and a resumed session gives it
|
|
95
|
+
back to the model. Video frames are not kept.
|
|
96
|
+
- **Telnyx gathers speech.** A gather that listens for speech on Telnyx transcribes the caller's side of
|
|
97
|
+
the call, with its prompts spoken and keys still taken, until `endGather()`. `parseGatherResult()`
|
|
98
|
+
reads what a gather collected on either provider, Twilio's `SpeechResult` and `Digits` or Telnyx's
|
|
99
|
+
`call.transcription` and `call.gather.ended`, as one `TelephonyGatherResult`.
|
|
100
|
+
- **The studio signs people in, and serves many tenants.** `oidcSignIn()` signs people in with any
|
|
101
|
+
OpenID Connect provider, with no proxy in front: the code flow with PKCE, a state, and a nonce, the ID
|
|
102
|
+
token verified against the provider's keys, and a signed session cookie that replicas share, with
|
|
103
|
+
sign-out at the provider too. A person's `tenant`, from a claim, a header, or the users file, picks
|
|
104
|
+
the sources they read, from `tenantSources`; they see their tenant's comments and audit entries
|
|
105
|
+
alone, and their page token is bound to it.
|
|
106
|
+
- **The kernel in Firefox and WebKit.** The portable kernel's own tests, the file Node.js, Deno, Bun,
|
|
107
|
+
and the edge runtime run, now run in a browser page too, in headless Chromium on every build and in
|
|
108
|
+
Firefox and WebKit, Safari's engine, in CI, with a graph, an agent with a streaming tool, and AG-UI.
|
|
109
|
+
- **Cohere's Embed 5.** `embed-v5.0-pro` and `embed-v5.0-fast` are in the registry, with their
|
|
110
|
+
sizes from 256 to 2,048, 128,000 tokens, and images. Cohere publishes no per-token price for them,
|
|
111
|
+
so they are registered without one.
|
|
112
|
+
|
|
113
|
+
### Changed
|
|
114
|
+
|
|
115
|
+
- A TypeSafe AI request with pictures is multipart by default, and its 32 MiB is counted in bytes, not
|
|
116
|
+
characters, so a state of text outside ASCII is refused before it is sent rather than by the API.
|
|
117
|
+
- `loadCsv()` and `loadJson()` yield their first document before the rest of the file is read; a JSON
|
|
118
|
+
file whose array of records is cut short is refused as such, after the records before the cut.
|
|
119
|
+
- An OpenAPI operation with a multipart or other non-JSON body becomes a tool, where it was skipped; a
|
|
120
|
+
Swagger 2.0 document is read, where it was refused; and `loadOpenApiTools()` refuses a document over
|
|
121
|
+
`maxBytes`, 5 MB by default, reading no further.
|
|
122
|
+
|
|
123
|
+
### Deprecated
|
|
124
|
+
|
|
125
|
+
- `OpenApiTools.skipped`, always empty now that every operation becomes a tool. It is removed in 3.0.
|
|
126
|
+
|
|
127
|
+
### Fixed
|
|
128
|
+
|
|
129
|
+
- **An answer the output guard redacts keeps no log probabilities.** They name the tokens the model
|
|
130
|
+
wrote, so a redacted address or connection string was still spelled out in `response.logprobs`; now
|
|
131
|
+
they are dropped wherever the guard changes or blocks the text, streamed or not.
|
|
132
|
+
- The Google embedding batch adapter reads inline results under the name Google's API reference gives
|
|
133
|
+
them, `inlinedResponses`, as well as its clients' name.
|
|
134
|
+
- A typed decision asked of a model that reports only the chosen token's log probability, with no
|
|
135
|
+
alternatives, is sampled, where it was read as certain.
|
|
136
|
+
- An Ollama stream stops at the request's `stop` sequences, as a call does.
|
|
137
|
+
|
|
138
|
+
## [2.21.0] - 2026-10-26
|
|
139
|
+
|
|
140
|
+
Typed decisions about pictures, compatibility records gathered from community adapters' own CI, and
|
|
141
|
+
3.0's defaults behind one switch today; with log probabilities from OpenAI's Responses API, and a card
|
|
142
|
+
of its own for each tenant of an A2A agent.
|
|
143
|
+
|
|
144
|
+
### Added
|
|
145
|
+
|
|
146
|
+
- **Decisions about pictures.** A decision's state may hold pictures, as image parts or image assets
|
|
147
|
+
anywhere in it, and a question names each by its path in backticks, such as `photo` or
|
|
148
|
+
`frames[2].frame`. Every question type may ask about one, and a new `box` question, `BoxQuestion`,
|
|
149
|
+
asks where something is: its `BoxAnswer` is a `DecisionBox` of four edges, each a fraction of the
|
|
150
|
+
picture's height or width, with a confidence. `boxOverlap()` measures how far two boxes overlap,
|
|
151
|
+
`answeredRight()` counts a box right at half or more, `calibrate()` chooses a box's threshold as any
|
|
152
|
+
answer's, and `decisionEvaluator()` scores a box against `where` the thing is. `statePictures()`
|
|
153
|
+
lists a state's pictures and `readPicture()` reads one with the media family's readers.
|
|
154
|
+
- TypeSafe AI's Jev is sent a state's pictures as the API's assets, each under a new reference, with
|
|
155
|
+
the `TypeSafe-Danger-Zone-Images` header that pictures and boxes require, and answers boxes on Jev 1.14
|
|
156
|
+
and later. A picture it would refuse, outside PNG or JPEG, animated, over 16 MiB, or outside 280 to
|
|
157
|
+
1,568 pixels a side, or a request over 32 MiB, is refused first, naming the picture's path.
|
|
158
|
+
`TypeSafeDecisionProviderOptions.media` reads pictures held by file, URL, or stored asset.
|
|
159
|
+
- `completionDecisions()` shows a vision model the pictures a state holds, each labelled by its path,
|
|
160
|
+
and reads a box as the median of several samples, its confidence how far they agree; `images: false`
|
|
161
|
+
marks a text-only model.
|
|
162
|
+
- `DecisionProviderCapabilities.images`: a provider that does not read pictures is refused a decision
|
|
163
|
+
about one, and passed over as a fallback. The mock reads pictures and answers boxes.
|
|
164
|
+
- `DecisionConformanceOptions.picture` runs the conformance checks of pictures and boxes, and
|
|
165
|
+
`DecisionConformanceReport.skipped` lists the checks that could not run, rather than passing them.
|
|
166
|
+
- **Compatibility records from community CI.** An adapter's own CI publishes its sealed record at the
|
|
167
|
+
URL its package names as `nexus.compatibility`, beside the keyword `nexus-ai-pro-adapter`.
|
|
168
|
+
`nexus adapters gather`, and `gatherRecords()` in `nexus-ai-pro/adapter-kit`, find those packages on
|
|
169
|
+
npm, fetch each record, and keep only those whose digest matches and that name their own package,
|
|
170
|
+
from HTTPS; a source that is down keeps its last record, and a package that no longer names one
|
|
171
|
+
leaves the page. Nothing an adapter publishes is run. A daily `Community compatibility` workflow
|
|
172
|
+
writes the page, with the adapters this repository ships, to the `compatibility` branch, where a
|
|
173
|
+
record that says its adapter fails turns it red. `compatibility/sources.json` lists adapters not
|
|
174
|
+
on npm.
|
|
175
|
+
|
|
176
|
+
- **A preview of 3.0's defaults.** `useDefaults('3.0')`, from `nexus-ai-pro/config`, or
|
|
177
|
+
`NEXUS_DEFAULTS=3.0`, turns on the three settings 2.x keeps opt-in, as 3.0 will: incremental tracing on
|
|
178
|
+
a durable trace store, the Redis operation store's dispatch index where the client can keep it, and
|
|
179
|
+
`hash-v2` for a deployment first recorded. An option set explicitly wins, and without the switch
|
|
180
|
+
nothing changes. `DEFAULT_CHANGES` lists each, with what to do first; `nexusDefaults()` says which
|
|
181
|
+
defaults are in effect; and `nexus doctor --upgrade 3.0`, `DoctorOptions.upgrade`, says what 3.0
|
|
182
|
+
would change for a deployment. `TraceStore.durable` marks a store whose runs live only in the
|
|
183
|
+
process, as `MemoryTraceStore`'s.
|
|
184
|
+
|
|
185
|
+
- OpenAI's models on the Responses API, such as `gpt-5.5-pro`, report token log probabilities when a
|
|
186
|
+
request asks for them, as its chat models do: the request includes `message.output_text.logprobs`
|
|
187
|
+
with up to 20 alternatives a token, and each text part's are read back. A typed decision asked of
|
|
188
|
+
one is read from them in one call, rather than sampled; a model that refuses them is still sampled.
|
|
189
|
+
|
|
190
|
+
- With `tenants`, an A2A handler serves each tenant a card of its own at
|
|
191
|
+
`/{tenant}/.well-known/agent-card.json`, every interface naming the tenant, so a client that reads it,
|
|
192
|
+
such as the A2A project's reference client, calls as that tenant over each binding. The nightly
|
|
193
|
+
interoperability run drives a tenant through `@a2a-js/sdk` over JSON-RPC, HTTP+JSON, and gRPC, and
|
|
194
|
+
checks that a tenant it may not act for, and a call naming none, are refused.
|
|
195
|
+
|
|
196
|
+
### Fixed
|
|
197
|
+
|
|
198
|
+
- The compatibility page writes a record as text: an adapter links to its repository only at an
|
|
199
|
+
`http` or `https` address, so a record naming `javascript:` makes no link on the HTML page; a
|
|
200
|
+
record's line breaks, pipes, brackets, and stars no longer break or add to the Markdown; and the
|
|
201
|
+
HTML page colours each row by its state.
|
|
202
|
+
|
|
203
|
+
### Changed
|
|
204
|
+
|
|
205
|
+
- TypeSafe AI's 400 and 413 refuse a decision as `DECISION_INVALID`, as a 422 does, rather than
|
|
206
|
+
failing it as `DECISION_FAILED`: the request, such as a picture, is what was refused. Neither is
|
|
207
|
+
retried.
|
|
208
|
+
- A decision's trace and cache key hold a digest of each picture in its place, never its bytes, and the
|
|
209
|
+
budget holds a picture at 1,600 tokens rather than reading its base64 as text.
|
|
210
|
+
- A doctor report has one more check, `upgrade`, skipped unless `upgrade` asks about a release.
|
|
211
|
+
|
|
7
212
|
## [2.20.2] - 2026-10-24
|
|
8
213
|
|
|
9
214
|
The first published release of 2.20. The package is 2.20.0's: the publishes of 2.20.0 and 2.20.1
|
|
@@ -3287,7 +3492,9 @@ shared policy vocabulary is in place for a future release that revisits this.
|
|
|
3287
3492
|
- URL fetching rejects unsafe private-network targets and limits response reads.
|
|
3288
3493
|
- CLI and audit findings no longer disclose detected secret values.
|
|
3289
3494
|
|
|
3290
|
-
[Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.
|
|
3495
|
+
[Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.22.0...HEAD
|
|
3496
|
+
[2.22.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.21.0...v2.22.0
|
|
3497
|
+
[2.21.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.20.2...v2.21.0
|
|
3291
3498
|
[2.20.2]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.20.1...v2.20.2
|
|
3292
3499
|
[2.20.1]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.20.0...v2.20.1
|
|
3293
3500
|
[2.20.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.19.0...v2.20.0
|
package/README.md
CHANGED
|
@@ -338,15 +338,20 @@ Shipped so far:
|
|
|
338
338
|
a person when it is not;
|
|
339
339
|
- 2.20 tools read from a service's own OpenAPI document, which an agent calls or an emulator plays,
|
|
340
340
|
an emulator that holds what a whole evaluation did so examples that build on each other are
|
|
341
|
-
evaluated with nothing sent, and one A2A handler serving many tenants, each seeing only its own
|
|
341
|
+
evaluated with nothing sent, and one A2A handler serving many tenants, each seeing only its own;
|
|
342
|
+
- 2.21 typed decisions about pictures, a yes or no, a choice, a score, or a box around something in
|
|
343
|
+
one, from TypeSafe AI's Jev or any vision model; community adapters' compatibility records gathered
|
|
344
|
+
from their own CI, a failing one turning red; and 3.0's defaults behind one switch today;
|
|
345
|
+
- 2.22 MCP over streamable HTTP both ways, with prompts, resources, and a server's questions back to
|
|
346
|
+
the client; OpenAPI tools that send each operation's credentials; tokens, API keys, and sessions the
|
|
347
|
+
server issues; and a studio that signs people in with your identity provider and serves many
|
|
348
|
+
tenants, each seeing only its own.
|
|
342
349
|
|
|
343
350
|
Upgrading from 1.x: `npx nexus migrate src --write` moves your imports, and
|
|
344
351
|
[MIGRATING.md](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/MIGRATING.md) covers the rest.
|
|
345
|
-
Next, 2.
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
embedding batch tier, media understanding, video, Gemini Live, SIP, and both phone providers
|
|
349
|
-
graduate once the live workflow passes against each. The
|
|
352
|
+
Next, in 2.23, Pinecone, images, the Gemini embedding batch tier, media understanding, video, typed
|
|
353
|
+
decisions, Gemini Live, SIP, and both phone providers graduate once the live workflow passes against
|
|
354
|
+
each. The
|
|
350
355
|
[roadmap](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/ROADMAP.md) lists what comes after, in the order
|
|
351
356
|
applications ask for it.
|
|
352
357
|
|
package/SECURITY.md
CHANGED
|
@@ -281,9 +281,23 @@ Findings, each fixed in 2.5 with a test in `studio/test/security.test.ts`:
|
|
|
281
281
|
- **A proposal's pull-request link was rendered as stored.** The content security policy already
|
|
282
282
|
blocked a `javascript:` address. Only `http` and `https` links are rendered now.
|
|
283
283
|
|
|
284
|
+
Signing in and tenants, reviewed for 2.22, with tests in `studio/test/signin.test.ts` and
|
|
285
|
+
`studio/test/tenants.test.ts`:
|
|
286
|
+
- The provider's configuration must name the issuer the studio was given. An ID token must verify
|
|
287
|
+
against the provider's keys, name that issuer and the studio's client as its audience, and carry
|
|
288
|
+
the sign-in's nonce; a token for several clients must name the studio as `azp`.
|
|
289
|
+
- The state, nonce, and PKCE verifier travel in a cookie signed for that purpose alone, for ten
|
|
290
|
+
minutes, so a callback from another browser, a late one, or a changed one is refused, and a sign-in
|
|
291
|
+
cookie is never taken as a session.
|
|
292
|
+
- The page to come back to is always a path on the studio, never another site.
|
|
293
|
+
- A tenant's people read only what `tenantSources` gives for their tenant. The studio itself keeps
|
|
294
|
+
comments and audit entries to their tenant, whatever the store does, and binds the page token to it.
|
|
295
|
+
`headerAuth()` with a tenant header refuses a request that lacks it.
|
|
296
|
+
|
|
284
297
|
Documented limitations, which the studio guide states:
|
|
285
|
-
-
|
|
286
|
-
|
|
298
|
+
- Someone without a tenant reads the studio's own sources, the operators' view.
|
|
299
|
+
- A session is a signed cookie, so one person's cannot be ended before it expires; rotating the
|
|
300
|
+
sign-in secret signs everyone out. Signing out is a plain link, which another site can follow.
|
|
287
301
|
- A page token stays valid while the secret, the user, and their role do. Rotate `secret` to revoke
|
|
288
302
|
every page at once.
|
|
289
303
|
- Someone without a session who opens another person's personal link is signed in as that person.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compatibility records gathered from where adapters' own CI publishes them: packages found on npm by
|
|
3
|
+
* a keyword, each naming its record's URL in its `package.json`, and sources listed by hand. Each
|
|
4
|
+
* record is read as `nexus adapters verify` sealed it, and must name the package that points to it.
|
|
5
|
+
* Nothing an adapter publishes is run: a gathered record is listed as its CI recorded it.
|
|
6
|
+
*/
|
|
7
|
+
import { type CompatibilityRecord } from './record.js';
|
|
8
|
+
/** The npm keyword an adapter package carries to be found: `nexus-ai-pro-adapter`. */
|
|
9
|
+
export declare const ADAPTER_KEYWORD = "nexus-ai-pro-adapter";
|
|
10
|
+
/** Where one adapter's CI publishes its record. */
|
|
11
|
+
export interface RecordSource {
|
|
12
|
+
/** The adapter's package name, which its record must name. */
|
|
13
|
+
package: string;
|
|
14
|
+
/** The record's URL: HTTPS, or HTTP to this machine. */
|
|
15
|
+
url: string;
|
|
16
|
+
}
|
|
17
|
+
/** Options for `gatherRecords()`. */
|
|
18
|
+
export interface GatherRecordsOptions {
|
|
19
|
+
/**
|
|
20
|
+
* The keyword adapter packages are found by on npm, each naming its record's URL as
|
|
21
|
+
* `nexus.compatibility` in its `package.json`. Defaults to `ADAPTER_KEYWORD`; `false` finds none.
|
|
22
|
+
*/
|
|
23
|
+
keyword?: string | false;
|
|
24
|
+
/** Sources listed by hand, beside those found; a listed package's URL is used over its own. */
|
|
25
|
+
sources?: readonly RecordSource[];
|
|
26
|
+
/** The npm registry. Defaults to `https://registry.npmjs.org`. */
|
|
27
|
+
registry?: string;
|
|
28
|
+
/**
|
|
29
|
+
* The records gathered last time: a package whose record cannot be read now, because its source is
|
|
30
|
+
* down or what it serves is refused, keeps its last one, and says why.
|
|
31
|
+
*/
|
|
32
|
+
previous?: readonly CompatibilityRecord[];
|
|
33
|
+
/** The most bytes a record may have. Defaults to 1 MiB. */
|
|
34
|
+
maxBytes?: number;
|
|
35
|
+
/** How long each request may take, in milliseconds. Defaults to 15 seconds. */
|
|
36
|
+
timeoutMs?: number;
|
|
37
|
+
/** The most packages found by keyword. Defaults to 1,000. */
|
|
38
|
+
maxPackages?: number;
|
|
39
|
+
/** Replaces the global `fetch`. */
|
|
40
|
+
fetch?: typeof fetch;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* What became of one package's record: `gathered` now; `kept` from last time, as now it could not be;
|
|
44
|
+
* `refused` or `unreachable` with nothing to keep; or `undeclared`, a package that names no record.
|
|
45
|
+
*/
|
|
46
|
+
export type GatherOutcome = 'gathered' | 'kept' | 'refused' | 'unreachable' | 'undeclared';
|
|
47
|
+
/** One package's source, and what became of its record. */
|
|
48
|
+
export interface GatheredSource {
|
|
49
|
+
/** The package's name. */
|
|
50
|
+
package: string;
|
|
51
|
+
/** Where its record is published, when it says. */
|
|
52
|
+
url?: string;
|
|
53
|
+
/** What became of it. */
|
|
54
|
+
outcome: GatherOutcome;
|
|
55
|
+
/** Why, for every outcome but `gathered`. */
|
|
56
|
+
detail?: string;
|
|
57
|
+
/** The record listed for it: gathered now, or kept. */
|
|
58
|
+
record?: CompatibilityRecord;
|
|
59
|
+
}
|
|
60
|
+
/** What `gatherRecords()` found. */
|
|
61
|
+
export interface GatherReport {
|
|
62
|
+
/** Every record to list, gathered now or kept, by package name. */
|
|
63
|
+
records: CompatibilityRecord[];
|
|
64
|
+
/** Every package, by name, and what became of its record. */
|
|
65
|
+
sources: GatheredSource[];
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Gathers the compatibility records adapters' own CI publishes: finds adapter packages on npm by
|
|
69
|
+
* `keyword`, reads where each publishes its record, and fetches each, with `sources` listed by hand.
|
|
70
|
+
* A record is gathered only when it reads as sealed, with a digest that matches it, and names the
|
|
71
|
+
* package that points to it; a package whose record cannot be read now keeps its `previous` one.
|
|
72
|
+
* Throws when npm cannot be searched, rather than reporting every adapter gone.
|
|
73
|
+
*/
|
|
74
|
+
export declare function gatherRecords(options?: GatherRecordsOptions): Promise<GatherReport>;
|
|
75
|
+
/** The name of a package from how a record names it, such as `@acme/nexus-milvus` from `@acme/nexus-milvus@0.3.0`. */
|
|
76
|
+
export declare function packageName(specifier: string): string;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { readCompatibilityRecord } from './record.js';
|
|
2
|
+
export const ADAPTER_KEYWORD = 'nexus-ai-pro-adapter';
|
|
3
|
+
export async function gatherRecords(options = {}) {
|
|
4
|
+
const request = options.fetch ?? fetch;
|
|
5
|
+
const registry = (options.registry ?? 'https://registry.npmjs.org').replace(/\/+$/, '');
|
|
6
|
+
const timeoutMs = options.timeoutMs ?? 15_000;
|
|
7
|
+
const maxBytes = options.maxBytes ?? 1024 * 1024;
|
|
8
|
+
const get = (url) => request(url, { signal: AbortSignal.timeout(timeoutMs), redirect: 'follow' });
|
|
9
|
+
const found = new Map();
|
|
10
|
+
const keyword = options.keyword === undefined ? ADAPTER_KEYWORD : options.keyword;
|
|
11
|
+
if (keyword) {
|
|
12
|
+
for (const name of await search(get, registry, keyword, options.maxPackages ?? 1_000))
|
|
13
|
+
found.set(name, undefined);
|
|
14
|
+
}
|
|
15
|
+
for (const source of options.sources ?? [])
|
|
16
|
+
found.set(source.package, source.url);
|
|
17
|
+
const previous = new Map();
|
|
18
|
+
for (const record of options.previous ?? []) {
|
|
19
|
+
const name = record.adapter.package ? packageName(record.adapter.package) : undefined;
|
|
20
|
+
if (name)
|
|
21
|
+
previous.set(name, record);
|
|
22
|
+
}
|
|
23
|
+
const sources = [];
|
|
24
|
+
for (const name of [...found.keys()].sort()) {
|
|
25
|
+
const listed = found.get(name);
|
|
26
|
+
const result = await gatherOne(name, listed, { get, registry, maxBytes });
|
|
27
|
+
const last = previous.get(name);
|
|
28
|
+
if ((result.outcome === 'refused' || result.outcome === 'unreachable') && last) {
|
|
29
|
+
sources.push({ ...result, outcome: 'kept', record: last, detail: `${result.outcome}: ${result.detail}` });
|
|
30
|
+
}
|
|
31
|
+
else
|
|
32
|
+
sources.push(result);
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
records: sources.flatMap((source) => (source.record ? [source.record] : [])),
|
|
36
|
+
sources,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export function packageName(specifier) {
|
|
40
|
+
const at = specifier.lastIndexOf('@');
|
|
41
|
+
return at > 0 ? specifier.slice(0, at) : specifier;
|
|
42
|
+
}
|
|
43
|
+
async function search(get, registry, keyword, limit) {
|
|
44
|
+
const names = [];
|
|
45
|
+
for (let from = 0; names.length < limit;) {
|
|
46
|
+
const url = `${registry}/-/v1/search?text=keywords:${encodeURIComponent(keyword)}&size=250&from=${from}`;
|
|
47
|
+
let page;
|
|
48
|
+
try {
|
|
49
|
+
const response = await get(url);
|
|
50
|
+
if (!response.ok)
|
|
51
|
+
throw new Error(`it answered ${response.status}`);
|
|
52
|
+
page = (await response.json());
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
throw new Error(`npm could not be searched for "${keyword}": ${message(error)}`);
|
|
56
|
+
}
|
|
57
|
+
const objects = page.objects ?? [];
|
|
58
|
+
for (const item of objects)
|
|
59
|
+
if (typeof item.package?.name === 'string')
|
|
60
|
+
names.push(item.package.name);
|
|
61
|
+
from += objects.length;
|
|
62
|
+
if (objects.length === 0 || from >= (page.total ?? 0))
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
return [...new Set(names)].slice(0, limit);
|
|
66
|
+
}
|
|
67
|
+
async function gatherOne(name, listed, context) {
|
|
68
|
+
let url = listed;
|
|
69
|
+
if (url === undefined) {
|
|
70
|
+
try {
|
|
71
|
+
const response = await context.get(`${context.registry}/${name.replace('/', '%2f')}/latest`);
|
|
72
|
+
if (!response.ok)
|
|
73
|
+
return { package: name, outcome: 'unreachable', detail: `npm answered ${response.status}` };
|
|
74
|
+
const manifest = (await response.json());
|
|
75
|
+
const declared = manifest.nexus?.compatibility;
|
|
76
|
+
if (typeof declared !== 'string' || !declared.trim()) {
|
|
77
|
+
return { package: name, outcome: 'undeclared', detail: 'its package.json names no nexus.compatibility record' };
|
|
78
|
+
}
|
|
79
|
+
url = declared.trim();
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
return { package: name, outcome: 'unreachable', detail: `npm could not be read: ${message(error)}` };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (!allowed(url))
|
|
86
|
+
return { package: name, url, outcome: 'refused', detail: `${url} is not HTTPS` };
|
|
87
|
+
let text;
|
|
88
|
+
try {
|
|
89
|
+
const response = await context.get(url);
|
|
90
|
+
if (response.url && !allowed(response.url)) {
|
|
91
|
+
return {
|
|
92
|
+
package: name,
|
|
93
|
+
url,
|
|
94
|
+
outcome: 'refused',
|
|
95
|
+
detail: `${url} redirects to ${response.url}, which is not HTTPS`,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
if (!response.ok)
|
|
99
|
+
return { package: name, url, outcome: 'unreachable', detail: `${url} answered ${response.status}` };
|
|
100
|
+
const declared = Number(response.headers.get('content-length') ?? 0);
|
|
101
|
+
if (declared > context.maxBytes)
|
|
102
|
+
return tooLarge(name, url, declared, context.maxBytes);
|
|
103
|
+
text = await response.text();
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
return { package: name, url, outcome: 'unreachable', detail: `${url} could not be read: ${message(error)}` };
|
|
107
|
+
}
|
|
108
|
+
const size = new TextEncoder().encode(text).length;
|
|
109
|
+
if (size > context.maxBytes)
|
|
110
|
+
return tooLarge(name, url, size, context.maxBytes);
|
|
111
|
+
let record;
|
|
112
|
+
try {
|
|
113
|
+
record = readCompatibilityRecord(text);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
return { package: name, url, outcome: 'refused', detail: message(error) };
|
|
117
|
+
}
|
|
118
|
+
const named = record.adapter.package;
|
|
119
|
+
if (!named)
|
|
120
|
+
return { package: name, url, outcome: 'refused', detail: 'the record names no package' };
|
|
121
|
+
if (packageName(named) !== name) {
|
|
122
|
+
return { package: name, url, outcome: 'refused', detail: `the record names ${named}, not ${name}` };
|
|
123
|
+
}
|
|
124
|
+
return { package: name, url, outcome: 'gathered', record };
|
|
125
|
+
}
|
|
126
|
+
function allowed(url) {
|
|
127
|
+
try {
|
|
128
|
+
const parsed = new URL(url);
|
|
129
|
+
if (parsed.protocol === 'https:')
|
|
130
|
+
return true;
|
|
131
|
+
return parsed.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(parsed.hostname);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function tooLarge(name, url, size, limit) {
|
|
138
|
+
return {
|
|
139
|
+
package: name,
|
|
140
|
+
url,
|
|
141
|
+
outcome: 'refused',
|
|
142
|
+
detail: `the record is ${size} bytes, over the ${limit}-byte limit`,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function message(error) {
|
|
146
|
+
return error instanceof Error ? error.message : String(error);
|
|
147
|
+
}
|
|
@@ -20,6 +20,7 @@ import { type AdapterErrorClass } from './errors.js';
|
|
|
20
20
|
import { type CompatibilityRecord, type CompatibilityRecordOptions } from './record.js';
|
|
21
21
|
export { ADAPTER_CONTRACTS, ADAPTER_FIXTURE_PASSAGES, type AdapterCheck, type AdapterContract, type AdapterContractVersion, type RetrieverCapabilities, type RetrieverContractOptions, runRetrieverContract, runStoreContract, runVectorStoreContract, type StoreCapabilities, type VectorStoreCapabilities, type VectorStoreContractOptions, } from './contracts.js';
|
|
22
22
|
export { AdapterError, type AdapterErrorClass, type AdapterErrorCode, classifyAdapterError } from './errors.js';
|
|
23
|
+
export { ADAPTER_KEYWORD, type GatheredSource, type GatherOutcome, type GatherRecordsOptions, type GatherReport, gatherRecords, type RecordSource, } from './gather.js';
|
|
23
24
|
export { type CheckChange, COMPATIBILITY_RECORD_FORMAT, type CompatibilityComparison, type CompatibilityPageOptions, type CompatibilityRecord, type CompatibilityRecordAdapter, CompatibilityRecordError, type CompatibilityRecordOptions, type CompatibilityState, type CompatibilityStatus, compareRecords, compatibilityPage, compatibilityRecord, readCompatibilityRecord, recordDigest, recordStatus, } from './record.js';
|
|
24
25
|
export { parseVersion, satisfiesRange } from './semver.js';
|
|
25
26
|
/** The kinds of adapter the kit builds. */
|
|
@@ -7,6 +7,7 @@ import { compatibilityRecord } from './record.js';
|
|
|
7
7
|
import { satisfiesRange } from './semver.js';
|
|
8
8
|
export { ADAPTER_CONTRACTS, ADAPTER_FIXTURE_PASSAGES, runRetrieverContract, runStoreContract, runVectorStoreContract, } from './contracts.js';
|
|
9
9
|
export { AdapterError, classifyAdapterError } from './errors.js';
|
|
10
|
+
export { ADAPTER_KEYWORD, gatherRecords, } from './gather.js';
|
|
10
11
|
export { COMPATIBILITY_RECORD_FORMAT, CompatibilityRecordError, compareRecords, compatibilityPage, compatibilityRecord, readCompatibilityRecord, recordDigest, recordStatus, } from './record.js';
|
|
11
12
|
export { parseVersion, satisfiesRange } from './semver.js';
|
|
12
13
|
const OPERATIONS = {
|
|
@@ -145,6 +145,8 @@ export interface CompatibilityPageOptions {
|
|
|
145
145
|
/**
|
|
146
146
|
* One page listing every record: each adapter, its kind and version, the release and contract it was
|
|
147
147
|
* verified against, its state there, its checks, and its p95 per operation, by kind and name. The
|
|
148
|
-
* same records make the same page, so it can be checked for drift.
|
|
148
|
+
* same records make the same page, so it can be checked for drift. A record is written as text, never
|
|
149
|
+
* as markup: an adapter links to its repository only at an `http` or `https` address, and the HTML
|
|
150
|
+
* page colours each row by its state, a failing one red.
|
|
149
151
|
*/
|
|
150
152
|
export declare function compatibilityPage(records: readonly CompatibilityRecord[], options?: CompatibilityPageOptions): string;
|