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.
Files changed (233) hide show
  1. package/API_STABILITY.md +93 -0
  2. package/CHANGELOG.md +208 -1
  3. package/README.md +11 -6
  4. package/SECURITY.md +16 -2
  5. package/dist/adapter-kit/gather.d.ts +76 -0
  6. package/dist/adapter-kit/gather.js +147 -0
  7. package/dist/adapter-kit/index.d.ts +1 -0
  8. package/dist/adapter-kit/index.js +1 -0
  9. package/dist/adapter-kit/record.d.ts +3 -1
  10. package/dist/adapter-kit/record.js +20 -5
  11. package/dist/agent/openapi.d.ts +94 -8
  12. package/dist/agent/openapi.js +528 -32
  13. package/dist/cli/adapters.d.ts +4 -3
  14. package/dist/cli/adapters.js +71 -2
  15. package/dist/cli/doctor.js +3 -0
  16. package/dist/cli.js +3 -1
  17. package/dist/core/config-builder.d.ts +1 -0
  18. package/dist/core/config-builder.js +1 -0
  19. package/dist/core/defaults.d.ts +34 -0
  20. package/dist/core/defaults.js +39 -0
  21. package/dist/core/secure-stream.js +4 -1
  22. package/dist/decisions/completion.d.ts +10 -7
  23. package/dist/decisions/completion.js +82 -8
  24. package/dist/decisions/core.d.ts +88 -43
  25. package/dist/decisions/core.js +115 -28
  26. package/dist/decisions/errors.d.ts +25 -0
  27. package/dist/decisions/errors.js +12 -0
  28. package/dist/decisions/index.d.ts +6 -5
  29. package/dist/decisions/index.js +2 -1
  30. package/dist/decisions/integrations.d.ts +14 -5
  31. package/dist/decisions/integrations.js +16 -1
  32. package/dist/decisions/mock.d.ts +5 -3
  33. package/dist/decisions/mock.js +4 -1
  34. package/dist/decisions/pictures.d.ts +43 -0
  35. package/dist/decisions/pictures.js +103 -0
  36. package/dist/decisions/typesafe.d.ts +33 -5
  37. package/dist/decisions/typesafe.js +121 -8
  38. package/dist/doctor/index.d.ts +6 -9
  39. package/dist/doctor/index.js +41 -0
  40. package/dist/embeddings/adapters.d.ts +1 -1
  41. package/dist/embeddings/adapters.js +3 -3
  42. package/dist/embeddings/batch-google.d.ts +27 -8
  43. package/dist/embeddings/batch-google.js +112 -36
  44. package/dist/embeddings/batch-mock.js +1 -1
  45. package/dist/embeddings/batch.js +10 -4
  46. package/dist/embeddings/engine.js +14 -13
  47. package/dist/embeddings/index.d.ts +1 -1
  48. package/dist/embeddings/inputs.d.ts +26 -3
  49. package/dist/embeddings/inputs.js +84 -5
  50. package/dist/embeddings/mock.js +1 -1
  51. package/dist/embeddings/models.js +39 -4
  52. package/dist/loaders/csv.d.ts +23 -1
  53. package/dist/loaders/csv.js +125 -72
  54. package/dist/loaders/index.d.ts +7 -2
  55. package/dist/loaders/input.d.ts +6 -0
  56. package/dist/loaders/input.js +42 -1
  57. package/dist/loaders/json.d.ts +14 -3
  58. package/dist/loaders/json.js +171 -20
  59. package/dist/loaders/web.d.ts +55 -0
  60. package/dist/loaders/web.js +20 -1
  61. package/dist/mcp/client.d.ts +54 -5
  62. package/dist/mcp/client.js +171 -20
  63. package/dist/mcp/http.d.ts +45 -0
  64. package/dist/mcp/http.js +233 -0
  65. package/dist/mcp/index.d.ts +4 -2
  66. package/dist/mcp/index.js +3 -1
  67. package/dist/mcp/protocol.d.ts +107 -0
  68. package/dist/mcp/sampling.d.ts +41 -0
  69. package/dist/mcp/sampling.js +52 -0
  70. package/dist/mcp/server.d.ts +96 -14
  71. package/dist/mcp/server.js +274 -59
  72. package/dist/models/data.js +20 -1
  73. package/dist/operations/adapters.d.ts +1 -0
  74. package/dist/operations/adapters.js +3 -1
  75. package/dist/protocols/a2a-grpc.js +10 -3
  76. package/dist/protocols/a2a-signing.d.ts +65 -0
  77. package/dist/protocols/a2a-signing.js +133 -0
  78. package/dist/protocols/a2a.d.ts +14 -0
  79. package/dist/protocols/a2a.js +60 -5
  80. package/dist/providers/cohere.js +16 -2
  81. package/dist/providers/google.js +7 -1
  82. package/dist/providers/ollama.d.ts +1 -1
  83. package/dist/providers/ollama.js +35 -27
  84. package/dist/providers/openai.js +19 -2
  85. package/dist/rag/pinecone.d.ts +63 -2
  86. package/dist/rag/pinecone.js +80 -0
  87. package/dist/realtime/conversation.js +18 -0
  88. package/dist/realtime/gemini-live.transport.d.ts +28 -3
  89. package/dist/realtime/gemini-live.transport.js +170 -32
  90. package/dist/realtime/session.d.ts +2 -0
  91. package/dist/realtime/session.js +19 -4
  92. package/dist/realtime/types.d.ts +35 -3
  93. package/dist/security/output-guard.js +4 -1
  94. package/dist/server/auth.d.ts +263 -2
  95. package/dist/server/auth.js +432 -5
  96. package/dist/server/deployments.d.ts +3 -2
  97. package/dist/server/deployments.js +6 -1
  98. package/dist/telephony/index.d.ts +1 -1
  99. package/dist/telephony/manager.d.ts +8 -1
  100. package/dist/telephony/manager.js +13 -0
  101. package/dist/telephony/providers/telnyx.d.ts +18 -5
  102. package/dist/telephony/providers/telnyx.js +44 -7
  103. package/dist/telephony/providers/twilio.d.ts +3 -1
  104. package/dist/telephony/providers/twilio.js +16 -0
  105. package/dist/tracing/stores.d.ts +2 -0
  106. package/dist/tracing/stores.js +1 -0
  107. package/dist/tracing/tracer.d.ts +5 -2
  108. package/dist/tracing/tracer.js +6 -3
  109. package/dist/types/embeddings.d.ts +47 -5
  110. package/dist/types/messages.d.ts +7 -0
  111. package/dist/types/principal.d.ts +1 -1
  112. package/dist/types/response.d.ts +5 -0
  113. package/dist/types/telephony.d.ts +38 -0
  114. package/dist/types/tracing.d.ts +6 -0
  115. package/dist/version.d.ts +1 -1
  116. package/dist/version.js +1 -1
  117. package/dist/voice/providers/google.d.ts +44 -0
  118. package/dist/voice/providers/google.js +215 -0
  119. package/dist-cjs/adapter-kit/gather.d.ts +76 -0
  120. package/dist-cjs/adapter-kit/gather.js +152 -0
  121. package/dist-cjs/adapter-kit/index.d.ts +1 -0
  122. package/dist-cjs/adapter-kit/index.js +4 -1
  123. package/dist-cjs/adapter-kit/record.d.ts +3 -1
  124. package/dist-cjs/adapter-kit/record.js +20 -5
  125. package/dist-cjs/agent/openapi.d.ts +94 -8
  126. package/dist-cjs/agent/openapi.js +530 -32
  127. package/dist-cjs/cli/adapters.d.ts +4 -3
  128. package/dist-cjs/cli/adapters.js +70 -1
  129. package/dist-cjs/cli/doctor.js +3 -0
  130. package/dist-cjs/core/config-builder.d.ts +1 -0
  131. package/dist-cjs/core/config-builder.js +5 -1
  132. package/dist-cjs/core/defaults.d.ts +34 -0
  133. package/dist-cjs/core/defaults.js +44 -0
  134. package/dist-cjs/core/secure-stream.js +4 -1
  135. package/dist-cjs/decisions/completion.d.ts +10 -7
  136. package/dist-cjs/decisions/completion.js +81 -7
  137. package/dist-cjs/decisions/core.d.ts +88 -43
  138. package/dist-cjs/decisions/core.js +134 -46
  139. package/dist-cjs/decisions/errors.d.ts +25 -0
  140. package/dist-cjs/decisions/errors.js +16 -0
  141. package/dist-cjs/decisions/index.d.ts +6 -5
  142. package/dist-cjs/decisions/index.js +5 -1
  143. package/dist-cjs/decisions/integrations.d.ts +14 -5
  144. package/dist-cjs/decisions/integrations.js +16 -1
  145. package/dist-cjs/decisions/mock.d.ts +5 -3
  146. package/dist-cjs/decisions/mock.js +4 -1
  147. package/dist-cjs/decisions/pictures.d.ts +43 -0
  148. package/dist-cjs/decisions/pictures.js +109 -0
  149. package/dist-cjs/decisions/typesafe.d.ts +33 -5
  150. package/dist-cjs/decisions/typesafe.js +126 -13
  151. package/dist-cjs/doctor/index.d.ts +6 -9
  152. package/dist-cjs/doctor/index.js +41 -0
  153. package/dist-cjs/embeddings/adapters.d.ts +1 -1
  154. package/dist-cjs/embeddings/adapters.js +3 -3
  155. package/dist-cjs/embeddings/batch-google.d.ts +27 -8
  156. package/dist-cjs/embeddings/batch-google.js +112 -36
  157. package/dist-cjs/embeddings/batch-mock.js +1 -1
  158. package/dist-cjs/embeddings/batch.js +9 -3
  159. package/dist-cjs/embeddings/engine.js +13 -12
  160. package/dist-cjs/embeddings/index.d.ts +1 -1
  161. package/dist-cjs/embeddings/inputs.d.ts +26 -3
  162. package/dist-cjs/embeddings/inputs.js +89 -5
  163. package/dist-cjs/embeddings/mock.js +1 -1
  164. package/dist-cjs/embeddings/models.js +39 -4
  165. package/dist-cjs/loaders/csv.d.ts +23 -1
  166. package/dist-cjs/loaders/csv.js +126 -71
  167. package/dist-cjs/loaders/index.d.ts +7 -2
  168. package/dist-cjs/loaders/input.d.ts +6 -0
  169. package/dist-cjs/loaders/input.js +43 -1
  170. package/dist-cjs/loaders/json.d.ts +14 -3
  171. package/dist-cjs/loaders/json.js +170 -19
  172. package/dist-cjs/loaders/web.d.ts +55 -0
  173. package/dist-cjs/loaders/web.js +21 -1
  174. package/dist-cjs/mcp/client.d.ts +54 -5
  175. package/dist-cjs/mcp/client.js +171 -20
  176. package/dist-cjs/mcp/http.d.ts +45 -0
  177. package/dist-cjs/mcp/http.js +236 -0
  178. package/dist-cjs/mcp/index.d.ts +4 -2
  179. package/dist-cjs/mcp/index.js +5 -1
  180. package/dist-cjs/mcp/protocol.d.ts +107 -0
  181. package/dist-cjs/mcp/sampling.d.ts +41 -0
  182. package/dist-cjs/mcp/sampling.js +55 -0
  183. package/dist-cjs/mcp/server.d.ts +96 -14
  184. package/dist-cjs/mcp/server.js +273 -58
  185. package/dist-cjs/models/data.js +20 -1
  186. package/dist-cjs/operations/adapters.d.ts +1 -0
  187. package/dist-cjs/operations/adapters.js +3 -1
  188. package/dist-cjs/protocols/a2a-grpc.js +10 -3
  189. package/dist-cjs/protocols/a2a-signing.d.ts +65 -0
  190. package/dist-cjs/protocols/a2a-signing.js +140 -0
  191. package/dist-cjs/protocols/a2a.d.ts +14 -0
  192. package/dist-cjs/protocols/a2a.js +65 -6
  193. package/dist-cjs/providers/cohere.js +16 -2
  194. package/dist-cjs/providers/google.js +7 -1
  195. package/dist-cjs/providers/ollama.d.ts +1 -1
  196. package/dist-cjs/providers/ollama.js +35 -27
  197. package/dist-cjs/providers/openai.js +19 -2
  198. package/dist-cjs/rag/pinecone.d.ts +63 -2
  199. package/dist-cjs/rag/pinecone.js +81 -0
  200. package/dist-cjs/realtime/conversation.js +18 -0
  201. package/dist-cjs/realtime/gemini-live.transport.d.ts +28 -3
  202. package/dist-cjs/realtime/gemini-live.transport.js +170 -32
  203. package/dist-cjs/realtime/session.d.ts +2 -0
  204. package/dist-cjs/realtime/session.js +19 -4
  205. package/dist-cjs/realtime/types.d.ts +35 -3
  206. package/dist-cjs/security/output-guard.js +4 -1
  207. package/dist-cjs/server/auth.d.ts +263 -2
  208. package/dist-cjs/server/auth.js +437 -5
  209. package/dist-cjs/server/deployments.d.ts +3 -2
  210. package/dist-cjs/server/deployments.js +6 -1
  211. package/dist-cjs/telephony/index.d.ts +1 -1
  212. package/dist-cjs/telephony/manager.d.ts +8 -1
  213. package/dist-cjs/telephony/manager.js +13 -0
  214. package/dist-cjs/telephony/providers/telnyx.d.ts +18 -5
  215. package/dist-cjs/telephony/providers/telnyx.js +44 -7
  216. package/dist-cjs/telephony/providers/twilio.d.ts +3 -1
  217. package/dist-cjs/telephony/providers/twilio.js +16 -0
  218. package/dist-cjs/tracing/stores.d.ts +2 -0
  219. package/dist-cjs/tracing/stores.js +1 -0
  220. package/dist-cjs/tracing/tracer.d.ts +5 -2
  221. package/dist-cjs/tracing/tracer.js +6 -3
  222. package/dist-cjs/types/embeddings.d.ts +47 -5
  223. package/dist-cjs/types/messages.d.ts +7 -0
  224. package/dist-cjs/types/principal.d.ts +1 -1
  225. package/dist-cjs/types/response.d.ts +5 -0
  226. package/dist-cjs/types/telephony.d.ts +38 -0
  227. package/dist-cjs/types/tracing.d.ts +6 -0
  228. package/dist-cjs/version.d.ts +1 -1
  229. package/dist-cjs/version.js +1 -1
  230. package/dist-cjs/voice/providers/google.d.ts +44 -0
  231. package/dist-cjs/voice/providers/google.js +252 -0
  232. package/llms.txt +1 -1
  233. 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.20.2...HEAD
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.21 asks typed decisions about pictures, and gathers community adapters' compatibility
346
- records from their own CI.
347
- Pinecone, images, the Gemini
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
- - The studio has no tenants of its own. Every account sees every source the studio is given, so a
286
- tenant's people get a studio over that tenant's `tenantScope()` views.
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;