nexus-ai-pro 2.19.0 → 2.21.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 +79 -1
- package/CHANGELOG.md +144 -1
- package/README.md +10 -6
- 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/capabilities.d.ts +3 -0
- package/dist/agent/openapi.d.ts +139 -0
- package/dist/agent/openapi.js +455 -0
- 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/decisions/completion.d.ts +10 -7
- package/dist/decisions/completion.js +80 -6
- 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 +25 -5
- package/dist/decisions/typesafe.js +101 -7
- package/dist/doctor/index.d.ts +6 -9
- package/dist/doctor/index.js +41 -0
- package/dist/evaluate/emulate.d.ts +73 -6
- package/dist/evaluate/emulate.js +114 -12
- package/dist/operations/adapters.d.ts +1 -0
- package/dist/operations/adapters.js +3 -1
- package/dist/optimizer/densifier.js +20 -11
- package/dist/protocols/a2a-grpc.js +1 -1
- package/dist/protocols/a2a-proto.d.ts +2 -2
- package/dist/protocols/a2a-proto.js +76 -1
- package/dist/protocols/a2a.d.ts +121 -0
- package/dist/protocols/a2a.js +110 -32
- package/dist/providers/openai.js +13 -0
- package/dist/server/deployments.d.ts +3 -2
- package/dist/server/deployments.js +6 -1
- 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/tracing.d.ts +6 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- 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/capabilities.d.ts +3 -0
- package/dist-cjs/agent/openapi.d.ts +139 -0
- package/dist-cjs/agent/openapi.js +461 -0
- 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/decisions/completion.d.ts +10 -7
- package/dist-cjs/decisions/completion.js +79 -5
- 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 +25 -5
- package/dist-cjs/decisions/typesafe.js +106 -12
- package/dist-cjs/doctor/index.d.ts +6 -9
- package/dist-cjs/doctor/index.js +41 -0
- package/dist-cjs/evaluate/emulate.d.ts +73 -6
- package/dist-cjs/evaluate/emulate.js +116 -13
- package/dist-cjs/operations/adapters.d.ts +1 -0
- package/dist-cjs/operations/adapters.js +3 -1
- package/dist-cjs/optimizer/densifier.js +20 -11
- package/dist-cjs/protocols/a2a-grpc.js +1 -1
- package/dist-cjs/protocols/a2a-proto.d.ts +2 -2
- package/dist-cjs/protocols/a2a-proto.js +76 -1
- package/dist-cjs/protocols/a2a.d.ts +121 -0
- package/dist-cjs/protocols/a2a.js +110 -32
- package/dist-cjs/providers/openai.js +13 -0
- package/dist-cjs/server/deployments.d.ts +3 -2
- package/dist-cjs/server/deployments.js +6 -1
- 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/tracing.d.ts +6 -0
- package/dist-cjs/version.d.ts +1 -1
- package/dist-cjs/version.js +1 -1
- package/llms.txt +1 -1
- package/package.json +14 -1
package/API_STABILITY.md
CHANGED
|
@@ -41,6 +41,84 @@ Both are covered by this policy:
|
|
|
41
41
|
Deep imports into `dist`, `dist-cjs`, or `src` are not supported. Import only from `nexus-ai-pro` or
|
|
42
42
|
one of its explicit subpaths.
|
|
43
43
|
|
|
44
|
+
## How an item graduates
|
|
45
|
+
|
|
46
|
+
A new surface starts this way:
|
|
47
|
+
|
|
48
|
+
1. Provider-neutral types and a deterministic mock land first.
|
|
49
|
+
2. One real adapter proves the contract; conformance fixtures cover it.
|
|
50
|
+
3. A second adapter on a different wire protocol proves portability.
|
|
51
|
+
4. Packed-package, clean-install, and consumer type-resolution tests pass on every supported Node
|
|
52
|
+
line.
|
|
53
|
+
5. Evaluation appropriate to the modality covers the supported capability matrix.
|
|
54
|
+
|
|
55
|
+
It leaves experimental status here only when every item below has evidence, published with the
|
|
56
|
+
graduation:
|
|
57
|
+
|
|
58
|
+
| Requirement | Evidence |
|
|
59
|
+
| --- | --- |
|
|
60
|
+
| API stability | Covered by the 2.x compatibility rules, with no planned breaking change |
|
|
61
|
+
| Conformance | Passes its contract suite on every supported backend |
|
|
62
|
+
| Crash recovery | Crash-injection tests at each durable boundary, where it has one |
|
|
63
|
+
| Concurrency | Race tests with several processes on the same records |
|
|
64
|
+
| Load | A published benchmark with a budget in CI |
|
|
65
|
+
| Tenant isolation | The isolation suite, where it stores tenant data |
|
|
66
|
+
| Security | A written threat review, with its findings closed |
|
|
67
|
+
| Upgrade | A migration test from the previous minor |
|
|
68
|
+
| Soak | A long-running test with flat memory and no leaked handles |
|
|
69
|
+
|
|
70
|
+
## 2.21 stage (2.21.0)
|
|
71
|
+
|
|
72
|
+
Everything 2.21 adds is stable from the start and additive, unless this section says otherwise.
|
|
73
|
+
|
|
74
|
+
New and stable, in `nexus-ai-pro/decisions`: `BoxQuestion`, `BoxAnswer`, `DecisionBox`, `boxOverlap()`,
|
|
75
|
+
`statePictures()`, `readPicture()`, `DecisionPicture`, `ReadDecisionPicture`, the question type `box`
|
|
76
|
+
and its `DecisionDistribution`, `DecisionProviderCapabilities.images`, `answeredRight()`'s box and
|
|
77
|
+
`overlap`, `DecisionEvaluatorOptions.where` and `overlap`, `DecisionConformanceOptions.picture`,
|
|
78
|
+
`DecisionConformanceReport.skipped`, and `CompletionDecisionsOptions.images`; in
|
|
79
|
+
`nexus-ai-pro/decisions/typesafe`, `TypeSafeDecisionProviderOptions.media`; in `nexus-ai-pro/adapter-kit`,
|
|
80
|
+
`gatherRecords()`, `GatherRecordsOptions`, `GatherReport`, `GatheredSource`, `GatherOutcome`,
|
|
81
|
+
`RecordSource`, and `ADAPTER_KEYWORD`; the command `nexus adapters gather`; a package's
|
|
82
|
+
`nexus.compatibility` field, the URL of its record; in `nexus-ai-pro/config`, `useDefaults()`,
|
|
83
|
+
`nexusDefaults()`, `NexusDefaults`, `DEFAULT_CHANGES`, and `DefaultChange`, and the `NEXUS_DEFAULTS`
|
|
84
|
+
variable; `DoctorOptions.upgrade` and `nexus doctor --upgrade`; and `TraceStore.durable`. The
|
|
85
|
+
preview turns on exactly what 3.0 will; should 3.0 change its defaults again before it ships, the
|
|
86
|
+
preview follows, and `DEFAULT_CHANGES` says so. A box from TypeSafe AI rests
|
|
87
|
+
on what it marks as experimental, its pictures and boxes, whose request format may change between its
|
|
88
|
+
releases.
|
|
89
|
+
|
|
90
|
+
Changed on purpose: TypeSafe AI's 400 and 413 are `DECISION_INVALID`, not `DECISION_FAILED`; neither
|
|
91
|
+
was retried before or is now. A provider of your own that reads pictures declares `images: true`;
|
|
92
|
+
without it, a decision about a picture is not sent to it. A union over `DecisionQuestionType` or
|
|
93
|
+
`DecisionAnswer` that handled three kinds now meets a fourth, `box`. `compatibilityPage()` writes a
|
|
94
|
+
repository that is not an `http` or `https` address as text, not a link, and escapes a record's
|
|
95
|
+
Markdown. A doctor report has one more check, `upgrade`, skipped unless asked for. With `tenants`, an
|
|
96
|
+
A2A handler answers `/{tenant}/.well-known/agent-card.json` with that tenant's card, where it answered
|
|
97
|
+
the card itself; without `tenants`, nothing changes. A model on OpenAI's Responses API is sent
|
|
98
|
+
`include` and `top_logprobs` when a request asks for log probabilities, which it was not before.
|
|
99
|
+
|
|
100
|
+
## 2.20 stage (2.20.0)
|
|
101
|
+
|
|
102
|
+
Everything 2.20 adds is stable from the start and additive, unless this section says otherwise.
|
|
103
|
+
|
|
104
|
+
New and stable:
|
|
105
|
+
- `nexus-ai-pro/agent/openapi`: `openApiTools()`, `loadOpenApiTools()`, `OpenApiError`,
|
|
106
|
+
`OpenApiDocument`, `OpenApiOperation`, `OpenApiTools`, `OpenApiToolsOptions`, and
|
|
107
|
+
`OpenApiHeadersRequest`;
|
|
108
|
+
- the capabilities `api.read`, `api.write`, and `api.destructive`;
|
|
109
|
+
- in `nexus-ai-pro/evaluate/emulate`: `EmulatedWorld`, `EmulatedWorldCall`, `EmulatedWorldSnapshot`,
|
|
110
|
+
`EmulatedToolsOptions.world` and `reads`, and `emulationKey()`'s `world`. A key without a world is
|
|
111
|
+
the key of every earlier release;
|
|
112
|
+
- in `nexus-ai-pro/protocols/a2a`: `A2aServerOptions.tenants`, `A2aTenancyOptions`,
|
|
113
|
+
`A2aClientOptions.tenant`, `A2aTaskRecord.tenant`, `A2aTaskQuery.tenant`, `A2A_ERRORS.permissionDenied`,
|
|
114
|
+
the card's `securitySchemes`, `securityRequirements`, and `signatures`, `A2aSkill.securityRequirements`,
|
|
115
|
+
`A2aSecurityScheme`, `A2aOAuthFlows`, `A2aSecurityRequirement`, and `A2aCardSignature`.
|
|
116
|
+
|
|
117
|
+
Changed on purpose: the gRPC binding writes and reads a card's security schemes, requirements, and
|
|
118
|
+
signatures, which it read past; without `tenants`, a request's tenant is still not read. A custom
|
|
119
|
+
`A2aTaskStore` serving several tenants should honour `A2aTaskQuery.tenant`, for full pages; the handler
|
|
120
|
+
filters by tenant either way.
|
|
121
|
+
|
|
44
122
|
## 2.19 stage (2.19.0)
|
|
45
123
|
|
|
46
124
|
Everything 2.19 adds is stable from the start and additive, unless this section says otherwise.
|
|
@@ -655,7 +733,7 @@ These security fixes tighten more behavior, each closing a finding of a graduati
|
|
|
655
733
|
### Graduation
|
|
656
734
|
|
|
657
735
|
A surface left experimental status in 2.4 only when every requirement in
|
|
658
|
-
[How an item graduates](
|
|
736
|
+
[How an item graduates](#how-an-item-graduates) had evidence. That evidence is below.
|
|
659
737
|
"n/a" names a requirement that does not apply, and says why. Every test named here runs in CI.
|
|
660
738
|
Load and soak budgets are checked by `npm run bench:surfaces` and `npm run bench:runtime`, which are
|
|
661
739
|
part of `check:release`. Upgrade tests run against the published 2.3.0 package, installed as a
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,145 @@ Notable changes to this project are recorded here. The format follows [Keep a Ch
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [2.21.0] - 2026-10-26
|
|
8
|
+
|
|
9
|
+
Typed decisions about pictures, compatibility records gathered from community adapters' own CI, and
|
|
10
|
+
3.0's defaults behind one switch today; with log probabilities from OpenAI's Responses API, and a card
|
|
11
|
+
of its own for each tenant of an A2A agent.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **Decisions about pictures.** A decision's state may hold pictures, as image parts or image assets
|
|
16
|
+
anywhere in it, and a question names each by its path in backticks, such as `photo` or
|
|
17
|
+
`frames[2].frame`. Every question type may ask about one, and a new `box` question, `BoxQuestion`,
|
|
18
|
+
asks where something is: its `BoxAnswer` is a `DecisionBox` of four edges, each a fraction of the
|
|
19
|
+
picture's height or width, with a confidence. `boxOverlap()` measures how far two boxes overlap,
|
|
20
|
+
`answeredRight()` counts a box right at half or more, `calibrate()` chooses a box's threshold as any
|
|
21
|
+
answer's, and `decisionEvaluator()` scores a box against `where` the thing is. `statePictures()`
|
|
22
|
+
lists a state's pictures and `readPicture()` reads one with the media family's readers.
|
|
23
|
+
- TypeSafe AI's Jev is sent a state's pictures as the API's assets, each under a new reference, with
|
|
24
|
+
the `TypeSafe-Danger-Zone-Images` header that pictures and boxes require, and answers boxes on Jev 1.14
|
|
25
|
+
and later. A picture it would refuse, outside PNG or JPEG, animated, over 16 MiB, or outside 280 to
|
|
26
|
+
1,568 pixels a side, or a request over 32 MiB, is refused first, naming the picture's path.
|
|
27
|
+
`TypeSafeDecisionProviderOptions.media` reads pictures held by file, URL, or stored asset.
|
|
28
|
+
- `completionDecisions()` shows a vision model the pictures a state holds, each labelled by its path,
|
|
29
|
+
and reads a box as the median of several samples, its confidence how far they agree; `images: false`
|
|
30
|
+
marks a text-only model.
|
|
31
|
+
- `DecisionProviderCapabilities.images`: a provider that does not read pictures is refused a decision
|
|
32
|
+
about one, and passed over as a fallback. The mock reads pictures and answers boxes.
|
|
33
|
+
- `DecisionConformanceOptions.picture` runs the conformance checks of pictures and boxes, and
|
|
34
|
+
`DecisionConformanceReport.skipped` lists the checks that could not run, rather than passing them.
|
|
35
|
+
- **Compatibility records from community CI.** An adapter's own CI publishes its sealed record at the
|
|
36
|
+
URL its package names as `nexus.compatibility`, beside the keyword `nexus-ai-pro-adapter`.
|
|
37
|
+
`nexus adapters gather`, and `gatherRecords()` in `nexus-ai-pro/adapter-kit`, find those packages on
|
|
38
|
+
npm, fetch each record, and keep only those whose digest matches and that name their own package,
|
|
39
|
+
from HTTPS; a source that is down keeps its last record, and a package that no longer names one
|
|
40
|
+
leaves the page. Nothing an adapter publishes is run. A daily `Community compatibility` workflow
|
|
41
|
+
writes the page, with the adapters this repository ships, to the `compatibility` branch, where a
|
|
42
|
+
record that says its adapter fails turns it red. `compatibility/sources.json` lists adapters not
|
|
43
|
+
on npm.
|
|
44
|
+
|
|
45
|
+
- **A preview of 3.0's defaults.** `useDefaults('3.0')`, from `nexus-ai-pro/config`, or
|
|
46
|
+
`NEXUS_DEFAULTS=3.0`, turns on the three settings 2.x keeps opt-in, as 3.0 will: incremental tracing on
|
|
47
|
+
a durable trace store, the Redis operation store's dispatch index where the client can keep it, and
|
|
48
|
+
`hash-v2` for a deployment first recorded. An option set explicitly wins, and without the switch
|
|
49
|
+
nothing changes. `DEFAULT_CHANGES` lists each, with what to do first; `nexusDefaults()` says which
|
|
50
|
+
defaults are in effect; and `nexus doctor --upgrade 3.0`, `DoctorOptions.upgrade`, says what 3.0
|
|
51
|
+
would change for a deployment. `TraceStore.durable` marks a store whose runs live only in the
|
|
52
|
+
process, as `MemoryTraceStore`'s.
|
|
53
|
+
|
|
54
|
+
- OpenAI's models on the Responses API, such as `gpt-5.5-pro`, report token log probabilities when a
|
|
55
|
+
request asks for them, as its chat models do: the request includes `message.output_text.logprobs`
|
|
56
|
+
with up to 20 alternatives a token, and each text part's are read back. A typed decision asked of
|
|
57
|
+
one is read from them in one call, rather than sampled; a model that refuses them is still sampled.
|
|
58
|
+
|
|
59
|
+
- With `tenants`, an A2A handler serves each tenant a card of its own at
|
|
60
|
+
`/{tenant}/.well-known/agent-card.json`, every interface naming the tenant, so a client that reads it,
|
|
61
|
+
such as the A2A project's reference client, calls as that tenant over each binding. The nightly
|
|
62
|
+
interoperability run drives a tenant through `@a2a-js/sdk` over JSON-RPC, HTTP+JSON, and gRPC, and
|
|
63
|
+
checks that a tenant it may not act for, and a call naming none, are refused.
|
|
64
|
+
|
|
65
|
+
### Fixed
|
|
66
|
+
|
|
67
|
+
- The compatibility page writes a record as text: an adapter links to its repository only at an
|
|
68
|
+
`http` or `https` address, so a record naming `javascript:` makes no link on the HTML page; a
|
|
69
|
+
record's line breaks, pipes, brackets, and stars no longer break or add to the Markdown; and the
|
|
70
|
+
HTML page colours each row by its state.
|
|
71
|
+
|
|
72
|
+
### Changed
|
|
73
|
+
|
|
74
|
+
- TypeSafe AI's 400 and 413 refuse a decision as `DECISION_INVALID`, as a 422 does, rather than
|
|
75
|
+
failing it as `DECISION_FAILED`: the request, such as a picture, is what was refused. Neither is
|
|
76
|
+
retried.
|
|
77
|
+
- A decision's trace and cache key hold a digest of each picture in its place, never its bytes, and the
|
|
78
|
+
budget holds a picture at 1,600 tokens rather than reading its base64 as text.
|
|
79
|
+
- A doctor report has one more check, `upgrade`, skipped unless `upgrade` asks about a release.
|
|
80
|
+
|
|
81
|
+
## [2.20.2] - 2026-10-24
|
|
82
|
+
|
|
83
|
+
The first published release of 2.20. The package is 2.20.0's: the publishes of 2.20.0 and 2.20.1
|
|
84
|
+
stopped at a studio test that failed by chance, so neither reached npm.
|
|
85
|
+
|
|
86
|
+
### Fixed
|
|
87
|
+
|
|
88
|
+
- The studio's load budget is judged by the fastest of five passes, after three warm-up passes
|
|
89
|
+
rather than one. The median of five failed the 2.20.1 publish at 3.61 calibration loops: its first
|
|
90
|
+
three passes took 91 to 102 ms and its last two 37 and 48 ms. A busy runner only ever adds time,
|
|
91
|
+
while a real slowdown slows every pass, so a request made 0.1 ms slower still fails it, at 4.3 loops.
|
|
92
|
+
|
|
93
|
+
## [2.20.1] - 2026-10-24 (not published)
|
|
94
|
+
|
|
95
|
+
The package is 2.20.0's. Its publish stopped at the same studio test, measured another way.
|
|
96
|
+
|
|
97
|
+
### Fixed
|
|
98
|
+
|
|
99
|
+
- The studio's load budget is measured as the median of five passes, as the surface budgets are,
|
|
100
|
+
rather than one: a single slow pass on a busy runner failed the 2.20.0 publish at 2.56 calibration
|
|
101
|
+
loops against a budget of 2.55, where the test measures 0.77. A real slowdown still fails it.
|
|
102
|
+
|
|
103
|
+
## [2.20.0] - 2026-10-24 (not published)
|
|
104
|
+
|
|
105
|
+
Tools read from a service's OpenAPI document, an emulator that remembers the evaluation, and one A2A
|
|
106
|
+
handler serving many tenants.
|
|
107
|
+
|
|
108
|
+
### Added
|
|
109
|
+
|
|
110
|
+
- **Tools from an OpenAPI document.** `openApiTools()` and `loadOpenApiTools()`, on
|
|
111
|
+
`nexus-ai-pro/agent/openapi`, read an OpenAPI 3.0 or 3.1 document into one tool per operation: its
|
|
112
|
+
name, description, and parameters the API's, its body's fields flattened, references followed, and
|
|
113
|
+
each declaring `api.read`, `api.write`, or `api.destructive` as its method says, with `network` for
|
|
114
|
+
the API's host. Given where the API lives, each tool calls it, the path encoded, the application's
|
|
115
|
+
headers sent, no redirect followed, and an answer read only up to a limit; a refusal is an
|
|
116
|
+
`OpenApiError` in the API's own words. `results` holds the schema of what each returns.
|
|
117
|
+
- **An emulator that remembers the evaluation.** An `EmulatedWorld`, given to `emulatedTools()` as
|
|
118
|
+
`world`, holds the scenario and every call across the runs of an evaluation, so a change one example
|
|
119
|
+
makes is there for the next; the emulator is told every change. `snapshot()` writes it as JSON,
|
|
120
|
+
`fork()` and `new EmulatedWorld(snapshot)` start runs from it, and the key of each answer kept carries
|
|
121
|
+
the world's `digest()`, so a recorded evaluation replays only after the same changes. `reads` names
|
|
122
|
+
tools that change nothing, beside those whose capabilities only read.
|
|
123
|
+
- **Capabilities for an API's operations:** `api.read:<api>/<operation>`, `api.write`, and
|
|
124
|
+
`api.destructive`, decided by a permission policy as MCP's are.
|
|
125
|
+
- **A2A for several tenants.** `a2aHandler({ tenants })` routes each request by the `tenant` it names,
|
|
126
|
+
in its params, its path (`/{tenant}/message:send`), or its gRPC message. Each tenant's tasks and
|
|
127
|
+
conversations are its own, its runs carry the tenant on their principal, and a caller that may not
|
|
128
|
+
act for the tenant it names is refused with `A2A_ERRORS.permissionDenied`, HTTP 403 and
|
|
129
|
+
`PERMISSION_DENIED` over gRPC. `A2aTenancyOptions` sets who may, and whether a tenant is required.
|
|
130
|
+
Task stores keep and list by tenant. A client names its interface's tenant, or its `tenant` option,
|
|
131
|
+
on every request, as protocol 1.0 requires.
|
|
132
|
+
- **A card's security.** `securitySchemes` (`A2aSecurityScheme`, with `A2aOAuthFlows`),
|
|
133
|
+
`securityRequirements` (`A2aSecurityRequirement`, on the card and on a skill), and `signatures`
|
|
134
|
+
(`A2aCardSignature`) are part of the card, and the gRPC binding writes and reads them, which it read
|
|
135
|
+
past before.
|
|
136
|
+
|
|
137
|
+
### Fixed
|
|
138
|
+
|
|
139
|
+
- The prompt densifier joined a code block's fences to the text around them, so a block it meant to
|
|
140
|
+
keep stopped being one; it shortened phrases inside other words, turning "in order today" into
|
|
141
|
+
"today"; and it lowercased a phrase that began a sentence.
|
|
142
|
+
- A test of a call that never answers, and the unit, coverage, and studio runs, no longer let a hung
|
|
143
|
+
test hold the release gate: every synchronous child the tests start has a timeout, and a test that
|
|
144
|
+
runs past five minutes fails.
|
|
145
|
+
|
|
7
146
|
## [2.19.0] - 2026-10-24
|
|
8
147
|
|
|
9
148
|
Typed decisions: a yes or no, a choice, or a score, answered with probabilities and a confidence read
|
|
@@ -3222,7 +3361,11 @@ shared policy vocabulary is in place for a future release that revisits this.
|
|
|
3222
3361
|
- URL fetching rejects unsafe private-network targets and limits response reads.
|
|
3223
3362
|
- CLI and audit findings no longer disclose detected secret values.
|
|
3224
3363
|
|
|
3225
|
-
[Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.
|
|
3364
|
+
[Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.21.0...HEAD
|
|
3365
|
+
[2.21.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.20.2...v2.21.0
|
|
3366
|
+
[2.20.2]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.20.1...v2.20.2
|
|
3367
|
+
[2.20.1]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.20.0...v2.20.1
|
|
3368
|
+
[2.20.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.19.0...v2.20.0
|
|
3226
3369
|
[2.19.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.18.0...v2.19.0
|
|
3227
3370
|
[2.18.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.17.0...v2.18.0
|
|
3228
3371
|
[2.17.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.16.0...v2.17.0
|
package/README.md
CHANGED
|
@@ -335,15 +335,19 @@ Shipped so far:
|
|
|
335
335
|
- 2.19 typed decisions: a yes or no, a choice, or a score, answered with probabilities and a
|
|
336
336
|
confidence, from TypeSafe AI's Jev or from any model's log probabilities, gated by thresholds a
|
|
337
337
|
calibration chooses, so a graph's branch, an approval, or a guardrail acts when it is sure and asks
|
|
338
|
-
a person when it is not
|
|
338
|
+
a person when it is not;
|
|
339
|
+
- 2.20 tools read from a service's own OpenAPI document, which an agent calls or an emulator plays,
|
|
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;
|
|
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.
|
|
339
345
|
|
|
340
346
|
Upgrading from 1.x: `npx nexus migrate src --write` moves your imports, and
|
|
341
347
|
[MIGRATING.md](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/MIGRATING.md) covers the rest.
|
|
342
|
-
Next, 2.
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
embedding batch tier, media understanding, video, Gemini Live, SIP, and both phone providers
|
|
346
|
-
graduate once the live workflow passes against each. The
|
|
348
|
+
Next, in 2.22, Pinecone, images, the Gemini embedding batch tier, media understanding, video, typed
|
|
349
|
+
decisions, Gemini Live, SIP, and both phone providers graduate once the live workflow passes against
|
|
350
|
+
each. The
|
|
347
351
|
[roadmap](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/ROADMAP.md) lists what comes after, in the order
|
|
348
352
|
applications ask for it.
|
|
349
353
|
|
|
@@ -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;
|
|
@@ -161,7 +161,7 @@ export function compatibilityPage(records, options = {}) {
|
|
|
161
161
|
record,
|
|
162
162
|
status,
|
|
163
163
|
cells: [
|
|
164
|
-
record.adapter.repository
|
|
164
|
+
record.adapter.repository && /^https?:\/\//i.test(record.adapter.repository)
|
|
165
165
|
? { text: record.adapter.name, href: record.adapter.repository }
|
|
166
166
|
: { text: record.adapter.name },
|
|
167
167
|
{ text: record.adapter.kind },
|
|
@@ -187,7 +187,8 @@ export function compatibilityPage(records, options = {}) {
|
|
|
187
187
|
const cell = (item) => item.href ? `<a href="${escapeHtml(item.href)}">${escapeHtml(item.text)}</a>` : escapeHtml(item.text);
|
|
188
188
|
return [
|
|
189
189
|
'<!doctype html>',
|
|
190
|
-
`<html lang="en"><head><meta charset="utf-8"><title>${escapeHtml(title)}</title
|
|
190
|
+
`<html lang="en"><head><meta charset="utf-8"><title>${escapeHtml(title)}</title>`,
|
|
191
|
+
`<style>${STATE_STYLE}</style></head><body>`,
|
|
191
192
|
`<h1>${escapeHtml(title)}</h1>`,
|
|
192
193
|
...(options.intro ? [`<p>${escapeHtml(options.intro)}</p>`] : []),
|
|
193
194
|
`<p>States are judged against nexus-ai-pro ${escapeHtml(nexusVersion)}.</p>`,
|
|
@@ -207,9 +208,11 @@ export function compatibilityPage(records, options = {}) {
|
|
|
207
208
|
'',
|
|
208
209
|
].join('\n');
|
|
209
210
|
}
|
|
211
|
+
const literal = (text) => oneLine(text).replace(/[|[\]*_`\\]/g, (char) => `\\${char}`);
|
|
210
212
|
const cell = (item) => {
|
|
211
|
-
const text = item.text
|
|
212
|
-
|
|
213
|
+
const text = literal(item.text);
|
|
214
|
+
const href = item.href?.replace(/[|\s()]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase().padStart(2, '0')}`);
|
|
215
|
+
return href ? `[${text}](${href})` : text;
|
|
213
216
|
};
|
|
214
217
|
return [
|
|
215
218
|
`# ${title}`,
|
|
@@ -221,7 +224,10 @@ export function compatibilityPage(records, options = {}) {
|
|
|
221
224
|
`| ${headers.map(() => '---').join(' | ')} |`,
|
|
222
225
|
...rows.map((row) => `| ${row.cells.map(cell).join(' | ')} |`),
|
|
223
226
|
...(notes.length > 0
|
|
224
|
-
? [
|
|
227
|
+
? [
|
|
228
|
+
'',
|
|
229
|
+
...notes.map((row) => `- **${literal(row.record.adapter.name)}**: ${row.status.reasons.map(literal).join('; ')}`),
|
|
230
|
+
]
|
|
225
231
|
: []),
|
|
226
232
|
'',
|
|
227
233
|
].join('\n');
|
|
@@ -240,6 +246,15 @@ function runtimeName() {
|
|
|
240
246
|
function isRecord(value) {
|
|
241
247
|
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
242
248
|
}
|
|
249
|
+
const STATE_STYLE = [
|
|
250
|
+
'tr[data-state="passing"] td{color:#1a7f37}',
|
|
251
|
+
'tr[data-state="failing"] td{color:#cf222e;font-weight:600}',
|
|
252
|
+
'tr[data-state="incompatible"] td{color:#9a6700}',
|
|
253
|
+
'tr[data-state="stale"] td{color:#57606a}',
|
|
254
|
+
].join('');
|
|
255
|
+
function oneLine(text) {
|
|
256
|
+
return text.replace(/\s+/g, ' ').trim();
|
|
257
|
+
}
|
|
243
258
|
function escapeHtml(text) {
|
|
244
259
|
return text.replace(/[&<>"']/g, (char) => `&#${char.charCodeAt(0)};`);
|
|
245
260
|
}
|
|
@@ -12,6 +12,9 @@ import type { ToolDefinition } from '../types/messages.js';
|
|
|
12
12
|
* | `mcp.read:<server>/<tool>` | An MCP tool its server says only reads |
|
|
13
13
|
* | `mcp.write:<server>/<tool>` | An MCP tool its server says changes things but destroys nothing |
|
|
14
14
|
* | `mcp.destructive:<server>/<tool>` | An MCP tool its server says may destroy or overwrite |
|
|
15
|
+
* | `api.read:<api>/<operation>` | An operation of an OpenAPI document whose method only reads |
|
|
16
|
+
* | `api.write:<api>/<operation>` | One whose method changes things |
|
|
17
|
+
* | `api.destructive:<api>/<operation>` | One whose method deletes |
|
|
15
18
|
* | `<name>`, `<name>:<detail>` | Anything the application names, such as `payments:refund` |
|
|
16
19
|
*
|
|
17
20
|
* A tool that declares `capabilities: []` does nothing to the world; one that declares none at all
|