nexus-ai-pro 2.12.0 → 2.14.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 +55 -0
- package/CHANGELOG.md +112 -1
- package/README.md +16 -6
- package/dist/batch/anthropic.js +3 -1
- package/dist/batch/manager.d.ts +5 -0
- package/dist/batch/manager.js +25 -9
- package/dist/batch/openai-client.js +3 -1
- package/dist/cli/policy.d.ts +10 -0
- package/dist/cli/policy.js +116 -0
- package/dist/cli.js +15 -7
- package/dist/core/lifecycle.d.ts +25 -2
- package/dist/core/lifecycle.js +172 -6
- package/dist/core/nexus.d.ts +25 -15
- package/dist/core/nexus.js +72 -26
- package/dist/credentials/index.d.ts +179 -0
- package/dist/credentials/index.js +206 -0
- package/dist/embeddings/adapters.d.ts +3 -3
- package/dist/embeddings/adapters.js +16 -10
- package/dist/embeddings/batch-google.js +7 -1
- package/dist/embeddings/batch.d.ts +7 -0
- package/dist/embeddings/batch.js +32 -9
- package/dist/embeddings/engine.js +7 -2
- package/dist/embeddings/errors.d.ts +5 -1
- package/dist/embeddings/errors.js +3 -1
- package/dist/evaluate/emulate.d.ts +143 -0
- package/dist/evaluate/emulate.js +214 -0
- package/dist/images/google.js +6 -1
- package/dist/images/manager.js +3 -0
- package/dist/images/openai.js +4 -1
- package/dist/images/operation.js +1 -0
- package/dist/lifecycle/continue.d.ts +12 -0
- package/dist/lifecycle/continue.js +14 -0
- package/dist/media/index.d.ts +14 -1
- package/dist/media/index.js +16 -6
- package/dist/media/mistral.js +3 -1
- package/dist/policy/index.d.ts +184 -0
- package/dist/policy/index.js +490 -0
- package/dist/providers/anthropic.js +11 -5
- package/dist/providers/azure-openai.js +1 -0
- package/dist/providers/cohere.d.ts +3 -2
- package/dist/providers/cohere.js +6 -4
- package/dist/providers/google.d.ts +3 -2
- package/dist/providers/google.js +9 -7
- package/dist/providers/openai.js +17 -7
- package/dist/realtime/gemini-live.transport.js +3 -0
- package/dist/realtime/openai-websocket.transport.d.ts +1 -0
- package/dist/realtime/openai-websocket.transport.js +12 -7
- package/dist/realtime/session.js +8 -2
- package/dist/realtime/types.d.ts +8 -1
- package/dist/residency/errors.d.ts +18 -0
- package/dist/residency/errors.js +18 -0
- package/dist/residency/index.d.ts +93 -0
- package/dist/residency/index.js +225 -0
- package/dist/router/failover.d.ts +13 -0
- package/dist/router/failover.js +26 -4
- package/dist/server/http.js +3 -0
- package/dist/server/regions.d.ts +88 -0
- package/dist/server/regions.js +167 -0
- package/dist/server/runs.d.ts +18 -0
- package/dist/server/runs.js +21 -2
- package/dist/server/server.js +3 -2
- package/dist/telephony/manager.js +21 -15
- package/dist/telephony/providers/telnyx.d.ts +14 -13
- package/dist/telephony/providers/telnyx.js +36 -20
- package/dist/telephony/providers/twilio.d.ts +18 -13
- package/dist/telephony/providers/twilio.js +48 -24
- package/dist/types/batch.d.ts +5 -0
- package/dist/types/config.d.ts +8 -1
- package/dist/types/embeddings.d.ts +5 -0
- package/dist/types/images.d.ts +5 -0
- package/dist/types/lifecycle.d.ts +111 -0
- package/dist/types/residency.d.ts +78 -0
- package/dist/types/residency.js +1 -0
- package/dist/types/server.d.ts +6 -0
- package/dist/types/telephony.d.ts +26 -0
- package/dist/types/voice.d.ts +4 -0
- package/dist/utils/scrub.d.ts +12 -0
- package/dist/utils/scrub.js +49 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/video/google.js +11 -2
- package/dist/video/index.d.ts +10 -0
- package/dist/video/index.js +39 -14
- package/dist/video/runway.js +4 -1
- package/dist/voice/manager.js +14 -2
- package/dist/voice/providers/openai.d.ts +3 -2
- package/dist/voice/providers/openai.js +9 -10
- package/dist-cjs/batch/anthropic.js +3 -1
- package/dist-cjs/batch/manager.d.ts +5 -0
- package/dist-cjs/batch/manager.js +25 -9
- package/dist-cjs/batch/openai-client.js +3 -1
- package/dist-cjs/cli/policy.d.ts +10 -0
- package/dist-cjs/cli/policy.js +122 -0
- package/dist-cjs/core/lifecycle.d.ts +25 -2
- package/dist-cjs/core/lifecycle.js +174 -6
- package/dist-cjs/core/nexus.d.ts +25 -15
- package/dist-cjs/core/nexus.js +80 -34
- package/dist-cjs/credentials/index.d.ts +179 -0
- package/dist-cjs/credentials/index.js +219 -0
- package/dist-cjs/embeddings/adapters.d.ts +3 -3
- package/dist-cjs/embeddings/adapters.js +16 -10
- package/dist-cjs/embeddings/batch-google.js +7 -1
- package/dist-cjs/embeddings/batch.d.ts +7 -0
- package/dist-cjs/embeddings/batch.js +32 -9
- package/dist-cjs/embeddings/engine.js +7 -2
- package/dist-cjs/embeddings/errors.d.ts +5 -1
- package/dist-cjs/embeddings/errors.js +3 -1
- package/dist-cjs/evaluate/emulate.d.ts +143 -0
- package/dist-cjs/evaluate/emulate.js +221 -0
- package/dist-cjs/images/google.js +6 -1
- package/dist-cjs/images/manager.js +3 -0
- package/dist-cjs/images/openai.js +4 -1
- package/dist-cjs/images/operation.js +1 -0
- package/dist-cjs/lifecycle/continue.d.ts +12 -0
- package/dist-cjs/lifecycle/continue.js +17 -0
- package/dist-cjs/media/index.d.ts +14 -1
- package/dist-cjs/media/index.js +16 -6
- package/dist-cjs/media/mistral.js +3 -1
- package/dist-cjs/policy/index.d.ts +184 -0
- package/dist-cjs/policy/index.js +500 -0
- package/dist-cjs/providers/anthropic.js +11 -5
- package/dist-cjs/providers/azure-openai.js +1 -0
- package/dist-cjs/providers/cohere.d.ts +3 -2
- package/dist-cjs/providers/cohere.js +6 -4
- package/dist-cjs/providers/google.d.ts +3 -2
- package/dist-cjs/providers/google.js +9 -7
- package/dist-cjs/providers/openai.js +17 -7
- package/dist-cjs/realtime/gemini-live.transport.js +3 -0
- package/dist-cjs/realtime/openai-websocket.transport.d.ts +1 -0
- package/dist-cjs/realtime/openai-websocket.transport.js +12 -7
- package/dist-cjs/realtime/session.js +8 -2
- package/dist-cjs/realtime/types.d.ts +8 -1
- package/dist-cjs/residency/errors.d.ts +18 -0
- package/dist-cjs/residency/errors.js +22 -0
- package/dist-cjs/residency/index.d.ts +93 -0
- package/dist-cjs/residency/index.js +232 -0
- package/dist-cjs/router/failover.d.ts +13 -0
- package/dist-cjs/router/failover.js +26 -4
- package/dist-cjs/server/http.js +3 -0
- package/dist-cjs/server/regions.d.ts +88 -0
- package/dist-cjs/server/regions.js +170 -0
- package/dist-cjs/server/runs.d.ts +18 -0
- package/dist-cjs/server/runs.js +21 -2
- package/dist-cjs/server/server.js +3 -2
- package/dist-cjs/telephony/manager.js +21 -15
- package/dist-cjs/telephony/providers/telnyx.d.ts +14 -13
- package/dist-cjs/telephony/providers/telnyx.js +36 -20
- package/dist-cjs/telephony/providers/twilio.d.ts +18 -13
- package/dist-cjs/telephony/providers/twilio.js +48 -24
- package/dist-cjs/types/batch.d.ts +5 -0
- package/dist-cjs/types/config.d.ts +8 -1
- package/dist-cjs/types/embeddings.d.ts +5 -0
- package/dist-cjs/types/images.d.ts +5 -0
- package/dist-cjs/types/lifecycle.d.ts +111 -0
- package/dist-cjs/types/residency.d.ts +78 -0
- package/dist-cjs/types/residency.js +2 -0
- package/dist-cjs/types/server.d.ts +6 -0
- package/dist-cjs/types/telephony.d.ts +26 -0
- package/dist-cjs/types/voice.d.ts +4 -0
- package/dist-cjs/utils/scrub.d.ts +12 -0
- package/dist-cjs/utils/scrub.js +53 -0
- package/dist-cjs/version.d.ts +1 -1
- package/dist-cjs/version.js +1 -1
- package/dist-cjs/video/google.js +11 -2
- package/dist-cjs/video/index.d.ts +10 -0
- package/dist-cjs/video/index.js +39 -14
- package/dist-cjs/video/runway.js +4 -1
- package/dist-cjs/voice/manager.js +14 -2
- package/dist-cjs/voice/providers/openai.d.ts +3 -2
- package/dist-cjs/voice/providers/openai.js +9 -10
- package/llms.txt +4 -2
- package/package.json +66 -1
package/API_STABILITY.md
CHANGED
|
@@ -41,6 +41,61 @@ Both are covered by this policy:
|
|
|
41
41
|
Deep imports into `dist`, `dist-cjs`, or `src` are not supported. Import only from `nexus-ai-pro` or
|
|
42
42
|
one of its explicit subpaths.
|
|
43
43
|
|
|
44
|
+
## 2.14 stage (2.14.0)
|
|
45
|
+
|
|
46
|
+
Everything 2.14 adds is stable from the start and additive, unless this section says otherwise.
|
|
47
|
+
|
|
48
|
+
New and stable:
|
|
49
|
+
- `nexus-ai-pro/policy`: `parsePolicy()`, `applyPolicy()`, `comparePolicies()`, `checkPolicyChange()`,
|
|
50
|
+
`providerGate()`, `policyHash()`, `PolicyError`, `Policy`, `PolicyDocument`, `PolicyAccess`,
|
|
51
|
+
`PolicyRouting`, `PolicyBudgets`, `PolicyResidency`, `PolicyCredentials`, `PolicyIssue`,
|
|
52
|
+
`ApplyPolicyOptions`, `PolicyChange`, `PolicyChangeKind`, and `PolicyCheck`;
|
|
53
|
+
- `LifecycleConfig.providers` and `policy`, `ProviderGate`, `PolicyReference`,
|
|
54
|
+
`OperationDescriptor.policy`, `AuditLogEvent.policy`, and `OperationLifecycle.access()`;
|
|
55
|
+
- the `nexus policy check` command, its flags, its exit codes, and its `--json` output;
|
|
56
|
+
- `nexus-ai-pro/evaluate/emulate`: `emulatedTools()`, `EmulatedToolsOptions`, `EmulatorClient`,
|
|
57
|
+
`EmulatedToolCall`, `EmulatedResult`, `EmulationStore`, `MemoryEmulationStore`, `EmulationError`, and
|
|
58
|
+
`emulationKey()`. What the emulator is told may be reworded in a minor release; a store's keys never
|
|
59
|
+
change within a major;
|
|
60
|
+
- `CallResidency.refusal()`, optional, and the optional `model` its `allows()`, `place()`, and
|
|
61
|
+
`check()` take;
|
|
62
|
+
- `OperationLifecycle.callContext()`, the optional `OperationLifecycleLike.callContext`, and
|
|
63
|
+
`ContinuedCallContext`;
|
|
64
|
+
- `tenantId` on `BatchSubmitRequest`, `EmbeddingBatchSubmitRequest`, `MediaReadRequest`,
|
|
65
|
+
`MediaDescribeRequest`, `MediaAnswerRequest`, `TranscriptionRequest`, `SpeechRequest`, and every
|
|
66
|
+
telephony request that takes a `signal`; `MediaCallContext.tenantId` and `credential`;
|
|
67
|
+
`RealtimeSessionConfig.credential`; the optional context the OpenAI voice adapter and the Twilio and
|
|
68
|
+
Telnyx adapters take on each method that calls their API.
|
|
69
|
+
|
|
70
|
+
The kinds `comparePolicies()` gives a change may be refined in a minor release: a change found to widen
|
|
71
|
+
what a tenant may reach is reported as `widened` from then on, so a check may start to ask for an
|
|
72
|
+
acknowledgment it did not ask for before. A check never stops asking for one it asked for.
|
|
73
|
+
|
|
74
|
+
## 2.13 stage (2.13.0)
|
|
75
|
+
|
|
76
|
+
Everything 2.13 adds is stable from the start and additive, unless this section says otherwise.
|
|
77
|
+
|
|
78
|
+
New and stable:
|
|
79
|
+
- `nexus-ai-pro/server/regions`: `regionalGateway()`, `RegionalGateway`, `RegionalGatewayOptions`,
|
|
80
|
+
`RegionTarget`, `RegionHealth`, and `RegionHealthOptions`;
|
|
81
|
+
- `RunManagerOptions.region` and `residency`, as `AgentServerOptions` carries them; `ThreadRecord.region`,
|
|
82
|
+
`RunRecord.region`, and `ReplicaReport.region`; a regional server's ids start with its region;
|
|
83
|
+
- `nexus-ai-pro/residency`: `residency()`, `Residency`, `ResidencyOptions`, `RegionRule`,
|
|
84
|
+
`RegionDefinition`, `ResidencyPolicy`, `ResidencyDecision`, `ResidencyTarget`, `ResidencyPlacement`,
|
|
85
|
+
`CallResidency`, `ResidencyError`, `regionalTraceStore()`, `RegionalTraceStore`,
|
|
86
|
+
`RegionalTraceStoreOptions`, `regionalAssetStore()`, and `RegionalAssetStoreOptions`;
|
|
87
|
+
- `LifecycleConfig.residency`, `ProviderCallContext.residency`, and `ImageRequestBase.tenantId`;
|
|
88
|
+
- `nexus-ai-pro/credentials`: `tenantCredentials()`, `TenantCredentials`, `TenantCredentialsOptions`,
|
|
89
|
+
`TenantCredentialStats`, `CredentialError`, `parseSecret()`, `credentialRequest()`, `SecretSource`,
|
|
90
|
+
`awsSecrets()`, `gcpSecrets()`, `azureSecrets()`, `vaultSecrets()`, `envSecrets()`,
|
|
91
|
+
`memorySecrets()`, `MemorySecrets`, `AwsSecretsClient`, `GcpSecretsClient`, `AzureSecretsClient`,
|
|
92
|
+
and `VaultSecretsOptions`;
|
|
93
|
+
- `LifecycleConfig.credentials`, `CredentialResolver`, `CredentialRequest`, and `ProviderCredential`;
|
|
94
|
+
`ProviderCallContext.tenantId` and `credential`; `OperationTicket.refreshCredential()` and
|
|
95
|
+
`scrub()`;
|
|
96
|
+
- `OpenAIProviderConfig.credentialHeader`, `EmbeddingRequest.tenantId`, and
|
|
97
|
+
`EmbeddingProviderError.attempts`.
|
|
98
|
+
|
|
44
99
|
## 2.12 stage (2.12.0)
|
|
45
100
|
|
|
46
101
|
Everything 2.12 adds is stable from the start and additive, unless this section says otherwise.
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,115 @@ Notable changes to this project are recorded here. The format follows [Keep a Ch
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [2.14.0] - 2026-10-19
|
|
8
|
+
|
|
9
|
+
Policy you can review, evaluation without side effects, and credentials per tenant in every family.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **Policy you can review.** `nexus-ai-pro/policy` reads guardrails, routing, budgets, residency, and
|
|
14
|
+
credentials from one versioned file. `parsePolicy()` validates it as the client starts, naming every
|
|
15
|
+
issue; `applyPolicy()` turns it into the client's configuration; and every operation and audit event
|
|
16
|
+
records the policy's name, version, and content hash. `nexus policy check` compares a version with
|
|
17
|
+
the one before it, a file or `git:<ref>`, and fails the build on a change that widens what a tenant
|
|
18
|
+
may reach (a region, a provider, a model, a budget, lowered guardrails, a shared key) until it is
|
|
19
|
+
acknowledged in the file or with `--accept`, and on content changed without a new version.
|
|
20
|
+
- **Per-tenant routing rules.** `LifecycleConfig.providers` takes a `ProviderGate` that admits or
|
|
21
|
+
refuses each provider and model an operation would reach, checked wherever residency is: a refused
|
|
22
|
+
provider is refused with an `OperationDeniedError` naming the rule before anything is sent, a
|
|
23
|
+
completion's refused attempt is dropped from its route, and an embedding never falls back to a
|
|
24
|
+
refused model. `providerGate()` builds one from a policy's routing rules, with `*` wildcards.
|
|
25
|
+
- **Evaluation without side effects.** `emulatedTools()`, on the new `nexus-ai-pro/evaluate/emulate`,
|
|
26
|
+
returns an agent's tools answered by a model playing each one, told the tool, its result schema, a
|
|
27
|
+
scenario, real calls, and the run's earlier calls, and never run, so an evaluation of an agent that
|
|
28
|
+
charges cards or writes to a database sends nothing. Answers are kept by tool, arguments, scenario,
|
|
29
|
+
and call, and replayed, so a suite runs the same answers in CI without a model; tools that change
|
|
30
|
+
nothing can run for real.
|
|
31
|
+
- **Credentials per tenant in every family.** Video, media understanding, completion and embedding
|
|
32
|
+
batches, voice, telephony, and realtime sessions send the tenant's key, resolved by
|
|
33
|
+
`LifecycleConfig.credentials`, in place of the configured one, as completions, embeddings, and images
|
|
34
|
+
have since 2.13. Each takes `tenantId` on its requests. A video job's or a batch's ref records its
|
|
35
|
+
tenant, so `status()`, `resume()`, `cancel()`, and `download()` use the tenant's key and regions from
|
|
36
|
+
any process, through the new `OperationLifecycle.callContext()`. A Twilio tenant's calls go out on its
|
|
37
|
+
subaccount; an OpenAI SIP call keeps its project's key.
|
|
38
|
+
- `ai.lifecycle.access()` returns the `CallResidency` an operation would get, and
|
|
39
|
+
`CallResidency.refusal()` says why a provider would be refused once placed on its twin; `allows()`,
|
|
40
|
+
`place()`, and `check()` take the model as well.
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
|
|
44
|
+
- `VideoManager.start()` and the calls on a video ref, and `EmbeddingBatchManager.start()` and the
|
|
45
|
+
calls on its refs, ran outside the client's residency policy; they are held to the tenant's regions
|
|
46
|
+
now. A durable video generation ran its operation without its tenant, so a tenant's budget and rate
|
|
47
|
+
limit did not apply to it.
|
|
48
|
+
|
|
49
|
+
## [2.13.0] - 2026-10-19
|
|
50
|
+
|
|
51
|
+
Regions and residency, and credentials per tenant.
|
|
52
|
+
|
|
53
|
+
### Added
|
|
54
|
+
|
|
55
|
+
- **Agent servers across regions.** `regionalGateway()`, on the new `nexus-ai-pro/server/regions`, sits
|
|
56
|
+
in front of one agent server per region. A thread or run goes to the region its id names and waits
|
|
57
|
+
there while that region is down, since its data is there and nowhere else; new work goes to the
|
|
58
|
+
tenant's home region and fails over only to another region the tenant may reach; a tenant that may
|
|
59
|
+
reach no region served is refused with a 403 naming the rule. Regions are checked through their
|
|
60
|
+
`/health`, marked down by failed requests, and taken out by an operator with `markDown()`. An agent
|
|
61
|
+
server given `region` stamps it on its threads, runs, ids, replica report, and health, and, given
|
|
62
|
+
`residency`, refuses tenants that may not reach it.
|
|
63
|
+
- **Residency: each tenant's requests and data kept in its regions.** `residency()`, on the new
|
|
64
|
+
`nexus-ai-pro/residency`, says which regions each tenant may reach (a map, or a function over the
|
|
65
|
+
tenant's record), which providers run in each region, and each provider's twin inside a region.
|
|
66
|
+
`LifecycleConfig.residency` applies it to every family: a completion or stream, its fallbacks and
|
|
67
|
+
its context summary included, is sent to the provider routing chose when that runs inside the
|
|
68
|
+
tenant's regions and to its twin there when not; an embedding never falls back outside them; and a
|
|
69
|
+
provider anything would reach outside them is refused with a `ResidencyError` naming the rule,
|
|
70
|
+
before anything is sent. `regionalTraceStore()` and `regionalAssetStore()` write each tenant's
|
|
71
|
+
traces, every run of a trace together, and assets in its home region, and read them only from its
|
|
72
|
+
regions.
|
|
73
|
+
- **Credentials per tenant.** `LifecycleConfig.credentials` resolves each provider's key per tenant,
|
|
74
|
+
when a call is made, in every family that runs through the lifecycle: completions, streams,
|
|
75
|
+
embeddings, and images send it in place of the configured key. `tenantCredentials()`, on the new
|
|
76
|
+
`nexus-ai-pro/credentials`, reads it from a secret source, cached for `cacheMs`, shared by
|
|
77
|
+
concurrent calls, carried by the last key while the source is down for up to `staleMs`, and
|
|
78
|
+
refusing a tenant with no key unless told to fall back. Sources for AWS Secrets Manager, Google
|
|
79
|
+
Secret Manager, Azure Key Vault, HashiCorp Vault, the environment, and memory, each over the
|
|
80
|
+
vendor's own client with no dependency.
|
|
81
|
+
- A completion or stream whose provider refuses its credential is resolved again and made once more
|
|
82
|
+
on the new key; in any other family, the next operation reads the key again. Every key and header
|
|
83
|
+
value an operation resolves is removed from the errors it raises, before an audit event, a log,
|
|
84
|
+
a trace, or the health report sees them.
|
|
85
|
+
- `ProviderCallContext.residency`, the operation's `CallResidency`; `ImageRequestBase.tenantId`; and
|
|
86
|
+
an agent server's trace root records its run's tenant as `metadata.nexus.tenant`.
|
|
87
|
+
- `ProviderCallContext.tenantId` and `credential()`; `OperationTicket.refreshCredential()` and
|
|
88
|
+
`scrub()`; `OpenAIProviderConfig.credentialHeader`; `EmbeddingRequest.tenantId`; and
|
|
89
|
+
`EmbeddingProviderError.attempts`, each attempt's error.
|
|
90
|
+
|
|
91
|
+
### Proof
|
|
92
|
+
|
|
93
|
+
- `tests/regions.test.ts`: the proof that closes 2.13. Three regions, each an agent server with its own
|
|
94
|
+
state, traces, and provider, behind a gateway, with every tenant's key from one secret manager. A run
|
|
95
|
+
of four model calls has its key rotated and revoked after the second; the third and fourth are
|
|
96
|
+
answered on the new key. Then the EU tenant's home region goes down: its new work fails over to the
|
|
97
|
+
other EU region, its thread waits for its own, a tenant held to that region waits too, and nothing
|
|
98
|
+
of either reaches the US: no thread, run, trace, or provider call. Without region ids, the server's
|
|
99
|
+
check, a thread's region kept, new work held to the tenant's regions, failover, checks, the
|
|
100
|
+
operator's mark, or the gateway's own refusal, a test fails.
|
|
101
|
+
- `tests/residency.test.ts`: against a provider server per region, a tenant pinned to the EU is
|
|
102
|
+
answered by the EU twin of the provider routing chose, completions and streams alike, and nothing
|
|
103
|
+
of it reaches the US; a provider with no twin there is refused with the rule, nothing sent; an
|
|
104
|
+
embedding whose EU provider fails never falls back to the US; an image provider outside the regions
|
|
105
|
+
is refused; and each tenant's traces and assets land in its home region and are read only from its
|
|
106
|
+
regions. Without placement, twins, the decision, the routed check, the fallback filter, the image
|
|
107
|
+
check, traces kept together, the tenant view, the asset home, or the rule checks, a test fails.
|
|
108
|
+
- `tests/tenant-credentials.test.ts`: against a provider of the test's own that takes only live keys,
|
|
109
|
+
each tenant's calls carry its own key through completions, streams, and embeddings; in a run of five
|
|
110
|
+
calls whose key is rotated and revoked after the second, the third is answered on the new key and
|
|
111
|
+
every call after uses it; a key the provider quotes in its refusal reaches no error, stack, cause,
|
|
112
|
+
audit event, log, trace, or health report. Without the call's credential, the refresh, its limit,
|
|
113
|
+
either scrub, an adapter's use of the key, the cache's expiry, its single read, its stale key, or
|
|
114
|
+
its refusal, a test fails.
|
|
115
|
+
|
|
7
116
|
## [2.12.0] - 2026-10-18
|
|
8
117
|
|
|
9
118
|
Observability that loses nothing, and media you can evaluate.
|
|
@@ -2879,7 +2988,9 @@ shared policy vocabulary is in place for a future release that revisits this.
|
|
|
2879
2988
|
- URL fetching rejects unsafe private-network targets and limits response reads.
|
|
2880
2989
|
- CLI and audit findings no longer disclose detected secret values.
|
|
2881
2990
|
|
|
2882
|
-
[Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.
|
|
2991
|
+
[Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.14.0...HEAD
|
|
2992
|
+
[2.14.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.13.0...v2.14.0
|
|
2993
|
+
[2.13.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.12.0...v2.13.0
|
|
2883
2994
|
[2.12.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.11.0...v2.12.0
|
|
2884
2995
|
[2.11.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.10.0...v2.11.0
|
|
2885
2996
|
[2.10.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.9.0...v2.10.0
|
package/README.md
CHANGED
|
@@ -141,7 +141,7 @@ from the doc comments. The guides live in the repository, so these links go to G
|
|
|
141
141
|
| [MCP](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/mcp.md) | Borrowing tools from MCP servers, a registry of many, and serving your own |
|
|
142
142
|
| [Agent protocols](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/protocols.md) | Serving an agent to user interfaces over AG-UI, to other agents over A2A, and to code editors over ACP, and calling remote agents as tools |
|
|
143
143
|
| [Traces](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/tracing.md) | Run trees, queries, feedback, comparison, and alerts |
|
|
144
|
-
| [Evaluation](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/evaluation.md) | Datasets, evaluators, experiments, comparisons with a verdict, review queues, LLM judges |
|
|
144
|
+
| [Evaluation](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/evaluation.md) | Datasets, evaluators, experiments, comparisons with a verdict, review queues, LLM judges, and tools emulated by a model so an evaluation sends nothing |
|
|
145
145
|
| [Context hub](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/context-hub.md) | Prompts, instructions, tools, and skills versioned together, promoted through gates, and moved between projects |
|
|
146
146
|
| [Insights](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/insights.md) | Failing and slow runs clustered, regressions between time windows, and evaluated fix proposals |
|
|
147
147
|
| [Prompts](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/prompts.md) | Typed templates, content versions, gated promotion, rollback, A/B splits, serving through outages |
|
|
@@ -167,6 +167,8 @@ from the doc comments. The guides live in the repository, so these links go to G
|
|
|
167
167
|
| [Agent server](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/server.md) | Self-hosted HTTP server for assistants: threads, durable runs, resumable streams, cron, a worker queue, and metrics |
|
|
168
168
|
| [Deployments](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/deployments.md) | Revisions and canaries with automatic rollback, autoscaling on Kubernetes and Helm, and per-tenant limits |
|
|
169
169
|
| [Tenancy](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/tenancy.md) | One tenant model: a tenant's view of every store, tenant graph runs, and the isolation suite |
|
|
170
|
+
| [Policy](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/policy.md) | Guardrails, routing, budgets, residency, and credentials as one versioned file, validated as the client starts, checked by `nexus policy check` in CI, and recorded on every operation |
|
|
171
|
+
| [Regions and credentials](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/regions.md) | Agent servers across regions behind a gateway, residency rules that keep each tenant's requests, traces, and assets in its regions, and each tenant's provider keys from a secret manager |
|
|
170
172
|
| [Workflows](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/workflows.md) | Ready-made chains and domain workflows |
|
|
171
173
|
| [Command line](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/cli.md) | The `nexus` command |
|
|
172
174
|
| [Packaging and install weight](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/packaging.md) | Every entry point and what it costs |
|
|
@@ -307,14 +309,22 @@ Shipped so far:
|
|
|
307
309
|
- 2.12 observability that loses nothing, and media you can evaluate: spans spooled through a collector
|
|
308
310
|
outage, OTLP over gRPC, deployment evaluation that reads each run's whole trace, evaluators that
|
|
309
311
|
read and judge images, documents, audio, and video, a catalog of every stored asset, and automation
|
|
310
|
-
rules that roll a canary back when a score falls
|
|
312
|
+
rules that roll a canary back when a score falls;
|
|
313
|
+
- 2.13 regions and residency, and credentials per tenant: agent servers across regions behind a
|
|
314
|
+
gateway that fails over only within a tenant's regions, residency rules that keep each tenant's
|
|
315
|
+
provider calls, traces, and assets in its regions, and each tenant's provider keys from a secret
|
|
316
|
+
manager, rotated without a restart and kept out of every error;
|
|
317
|
+
- 2.14 policy you can review, and evaluation without side effects: guardrails, routing, budgets,
|
|
318
|
+
residency, and credentials as one versioned file that `nexus policy check` compares with the version
|
|
319
|
+
before it in CI and every operation records, tools emulated by a model so an evaluation sends
|
|
320
|
+
nothing, and each tenant's key in every family.
|
|
311
321
|
|
|
312
322
|
Upgrading from 1.x: `npx nexus migrate src --write` moves your imports, and
|
|
313
323
|
[MIGRATING.md](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/MIGRATING.md) covers the rest.
|
|
314
|
-
Next, 2.
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
324
|
+
Next, 2.15 publishes a compatibility record for community adapters: the contract suite each passed,
|
|
325
|
+
against which version, with its benchmark, on one page. Pinecone, images, the Gemini embedding batch
|
|
326
|
+
tier, media understanding, video, Gemini Live, SIP, and both phone providers graduate once the live
|
|
327
|
+
workflow passes against each. The
|
|
318
328
|
[roadmap](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/ROADMAP.md) lists what comes after, in the order
|
|
319
329
|
applications ask for it.
|
|
320
330
|
|
package/dist/batch/anthropic.js
CHANGED
|
@@ -127,13 +127,15 @@ export class AnthropicBatchProvider {
|
|
|
127
127
|
async send(path, init, context) {
|
|
128
128
|
const fetchImpl = this.options.fetch ?? fetch;
|
|
129
129
|
const baseUrl = (this.options.baseUrl || 'https://api.anthropic.com/v1').replace(/\/$/, '');
|
|
130
|
+
const credential = await context.credential?.(this.info.name);
|
|
130
131
|
const response = await fetchImpl(`${baseUrl}${path}`, {
|
|
131
132
|
...init,
|
|
132
133
|
headers: {
|
|
133
|
-
'x-api-key': this.options.apiKey,
|
|
134
|
+
'x-api-key': credential?.apiKey ?? this.options.apiKey,
|
|
134
135
|
'anthropic-version': this.options.version ?? '2023-06-01',
|
|
135
136
|
'content-type': 'application/json',
|
|
136
137
|
...this.options.headers,
|
|
138
|
+
...credential?.headers,
|
|
137
139
|
...(init.headers ?? {}),
|
|
138
140
|
},
|
|
139
141
|
signal: context.signal,
|
package/dist/batch/manager.d.ts
CHANGED
|
@@ -60,6 +60,11 @@ export declare class BatchManager {
|
|
|
60
60
|
status(ref: BatchJobRef): Promise<BatchJobState>;
|
|
61
61
|
/** Cancels a batch. Throws when the provider cannot cancel. */
|
|
62
62
|
cancel(ref: BatchJobRef): Promise<BatchJobState>;
|
|
63
|
+
/**
|
|
64
|
+
* A call on a batch outside its operation, as the tenant its ref records, or `tenantId` for a ref
|
|
65
|
+
* made before refs recorded one: with the tenant's key, inside its regions.
|
|
66
|
+
*/
|
|
67
|
+
private continuing;
|
|
63
68
|
private collect;
|
|
64
69
|
/**
|
|
65
70
|
* Prices a finished batch at the provider's discounted rate.
|
package/dist/batch/manager.js
CHANGED
|
@@ -2,6 +2,7 @@ import { OperationRunner } from '../operations/runner.js';
|
|
|
2
2
|
import { estimateCost } from '../optimizer/cost.js';
|
|
3
3
|
import { DEFAULT_CURRENCY } from '../optimizer/cost.js';
|
|
4
4
|
import { generateRequestId } from '../utils/ids.js';
|
|
5
|
+
import { continuing } from '../lifecycle/continue.js';
|
|
5
6
|
import { withLifecycle } from '../lifecycle/run.js';
|
|
6
7
|
import { BatchCapabilityError, BatchProviderNotFoundError, BatchValidationError } from './errors.js';
|
|
7
8
|
import { pollUntilSettled } from './poll.js';
|
|
@@ -46,6 +47,7 @@ export class BatchManager {
|
|
|
46
47
|
operation: 'batch.submit',
|
|
47
48
|
requestId,
|
|
48
49
|
provider: providerName,
|
|
50
|
+
...(request.tenantId !== undefined ? { tenantId: request.tenantId } : {}),
|
|
49
51
|
metadata: { items: request.items.length },
|
|
50
52
|
}, {
|
|
51
53
|
signal: operation.signal,
|
|
@@ -60,7 +62,7 @@ export class BatchManager {
|
|
|
60
62
|
};
|
|
61
63
|
let ref = operation.previousHeartbeat?.batchRef;
|
|
62
64
|
if (!ref) {
|
|
63
|
-
ref = await provider.submit(request, context);
|
|
65
|
+
ref = withTenant(await provider.submit(request, context), request.tenantId);
|
|
64
66
|
await operation.heartbeat({ batchRef: ref });
|
|
65
67
|
}
|
|
66
68
|
operation.report({ completed: 0, total: request.items.length, message: `submitted as ${ref.id}` });
|
|
@@ -77,34 +79,41 @@ export class BatchManager {
|
|
|
77
79
|
const provider = this.providers.get(ref.provider);
|
|
78
80
|
if (!provider)
|
|
79
81
|
throw new BatchProviderNotFoundError(ref.provider);
|
|
80
|
-
|
|
82
|
+
return this.continuing('batch.collect', ref, options.tenantId, (continued) => this.collect(provider, ref, options, undefined, {
|
|
83
|
+
...continued,
|
|
81
84
|
requestId: generateRequestId(),
|
|
82
85
|
signal: options.signal ?? new AbortController().signal,
|
|
83
86
|
attempt: 1,
|
|
84
|
-
};
|
|
85
|
-
return this.collect(provider, ref, options, undefined, context);
|
|
87
|
+
}));
|
|
86
88
|
}
|
|
87
89
|
async status(ref) {
|
|
88
90
|
const provider = this.providers.get(ref.provider);
|
|
89
91
|
if (!provider)
|
|
90
92
|
throw new BatchProviderNotFoundError(ref.provider);
|
|
91
|
-
return provider.poll(ref, {
|
|
93
|
+
return this.continuing('batch.status', ref, undefined, (continued) => provider.poll(ref, {
|
|
94
|
+
...continued,
|
|
92
95
|
requestId: generateRequestId(),
|
|
93
96
|
signal: new AbortController().signal,
|
|
94
97
|
attempt: 1,
|
|
95
|
-
});
|
|
98
|
+
}));
|
|
96
99
|
}
|
|
97
100
|
async cancel(ref) {
|
|
98
101
|
const provider = this.providers.get(ref.provider);
|
|
99
102
|
if (!provider)
|
|
100
103
|
throw new BatchProviderNotFoundError(ref.provider);
|
|
101
|
-
|
|
104
|
+
const cancel = provider.cancel;
|
|
105
|
+
if (!cancel)
|
|
102
106
|
throw new BatchCapabilityError(ref.provider, 'cancel');
|
|
103
|
-
return
|
|
107
|
+
return this.continuing('batch.cancel', ref, undefined, (continued) => cancel.call(provider, ref, {
|
|
108
|
+
...continued,
|
|
104
109
|
requestId: generateRequestId(),
|
|
105
110
|
signal: new AbortController().signal,
|
|
106
111
|
attempt: 1,
|
|
107
|
-
});
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
continuing(operation, ref, tenantId, call) {
|
|
115
|
+
const tenant = tenantOf(ref) ?? tenantId;
|
|
116
|
+
return continuing(this.runtime.lifecycle, { family: 'batch', operation, provider: ref.provider, ...(tenant !== undefined ? { tenantId: tenant } : {}) }, call);
|
|
108
117
|
}
|
|
109
118
|
async collect(provider, ref, request, operation, context) {
|
|
110
119
|
const state = await pollUntilSettled(() => provider.poll(ref, context), {
|
|
@@ -230,3 +239,10 @@ function sumUsage(items) {
|
|
|
230
239
|
}
|
|
231
240
|
return { inputTokens, outputTokens, totalTokens: inputTokens + outputTokens };
|
|
232
241
|
}
|
|
242
|
+
function withTenant(ref, tenantId) {
|
|
243
|
+
return tenantId === undefined ? ref : { ...ref, metadata: { ...ref.metadata, batch: { tenantId } } };
|
|
244
|
+
}
|
|
245
|
+
function tenantOf(ref) {
|
|
246
|
+
const notes = ref.metadata?.batch;
|
|
247
|
+
return typeof notes?.tenantId === 'string' ? notes.tenantId : undefined;
|
|
248
|
+
}
|
|
@@ -84,10 +84,12 @@ export class OpenAIBatchClient {
|
|
|
84
84
|
async send(path, init, context) {
|
|
85
85
|
const fetchImpl = this.options.fetch ?? fetch;
|
|
86
86
|
const baseUrl = (this.options.baseUrl || 'https://api.openai.com/v1').replace(/\/$/, '');
|
|
87
|
+
const credential = await context.credential?.('openai');
|
|
87
88
|
const headers = {
|
|
88
|
-
authorization: `Bearer ${this.options.apiKey}`,
|
|
89
|
+
authorization: `Bearer ${credential?.apiKey ?? this.options.apiKey}`,
|
|
89
90
|
...(this.options.organization ? { 'openai-organization': this.options.organization } : {}),
|
|
90
91
|
...this.options.headers,
|
|
92
|
+
...credential?.headers,
|
|
91
93
|
...(init.headers ?? {}),
|
|
92
94
|
};
|
|
93
95
|
const response = await fetchImpl(`${baseUrl}${path}`, { ...init, headers, signal: context.signal });
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ParsedArgs } from './args.js';
|
|
2
|
+
/**
|
|
3
|
+
* `nexus policy check <policy.json> [--base <file>|git:<ref>] [--accept path,path] [--json]`.
|
|
4
|
+
*
|
|
5
|
+
* Validates the policy, and, given the version it replaces, lists every change: each that widens what
|
|
6
|
+
* a tenant may reach, or that needs a reviewer's eye, must be acknowledged in the file's
|
|
7
|
+
* `acknowledged` or with `--accept`, and content that changed must come with a new version. Exits 1
|
|
8
|
+
* when the policy is not valid or a change is not acknowledged.
|
|
9
|
+
*/
|
|
10
|
+
export declare function runPolicyCommand(subcommand: string | undefined, args: ParsedArgs): Promise<void>;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { promisify } from 'node:util';
|
|
5
|
+
import { checkPolicyChange, PolicyError, parsePolicy } from '../policy/index.js';
|
|
6
|
+
import { CliUsageError, flagBool, listFlag, printTable, stringFlag, writeJson } from './args.js';
|
|
7
|
+
const run = promisify(execFile);
|
|
8
|
+
export async function runPolicyCommand(subcommand, args) {
|
|
9
|
+
if (subcommand !== 'check')
|
|
10
|
+
throw new CliUsageError(`Unknown policy command "${subcommand ?? ''}". Use check.`);
|
|
11
|
+
const [file] = args.positionals;
|
|
12
|
+
if (!file)
|
|
13
|
+
throw new CliUsageError('nexus policy check needs a policy file: nexus policy check policy.json [--base git:main].');
|
|
14
|
+
const json = flagBool(args.flags, 'json');
|
|
15
|
+
const base = stringFlag(args.flags, 'base');
|
|
16
|
+
const after = parse(await readFile(file, 'utf8'), file, json);
|
|
17
|
+
if (!after)
|
|
18
|
+
return;
|
|
19
|
+
if (base === undefined) {
|
|
20
|
+
report(json, { policy: after.reference, valid: true }, `${describe(after)} is valid.`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const text = await readBase(base, file);
|
|
24
|
+
if (text === undefined) {
|
|
25
|
+
report(json, { policy: after.reference, valid: true, base: null }, `${describe(after)} is valid. ${base} has no policy at ${file}: nothing to compare.`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const before = parse(text, base, json);
|
|
29
|
+
if (!before)
|
|
30
|
+
return;
|
|
31
|
+
const check = checkPolicyChange(before.document, after.document, { accept: listFlag(args.flags, 'accept') ?? [] });
|
|
32
|
+
if (!check.ok)
|
|
33
|
+
process.exitCode = 1;
|
|
34
|
+
if (json) {
|
|
35
|
+
writeJson({ policy: after.reference, valid: true, base: before.reference, ...check });
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
console.log(`${describe(after)} is valid. Compared with ${before.name}@${before.version} from ${base}:`);
|
|
39
|
+
if (check.changes.length === 0)
|
|
40
|
+
console.log('No changes.');
|
|
41
|
+
else {
|
|
42
|
+
const pending = new Set(check.unacknowledged);
|
|
43
|
+
printTable(check.changes.map((change) => ({
|
|
44
|
+
kind: change.kind,
|
|
45
|
+
path: change.path,
|
|
46
|
+
review: change.kind === 'narrowed' ? '' : pending.has(change) ? 'NOT ACKNOWLEDGED' : 'acknowledged',
|
|
47
|
+
summary: change.summary,
|
|
48
|
+
})), ['kind', 'path', 'review', 'summary']);
|
|
49
|
+
}
|
|
50
|
+
if (check.versionUnchanged) {
|
|
51
|
+
console.log('');
|
|
52
|
+
console.log(`The content changed, but the version is still ${after.version}: raise it.`);
|
|
53
|
+
}
|
|
54
|
+
if (check.unacknowledged.length > 0) {
|
|
55
|
+
const paths = check.unacknowledged.map((change) => change.path);
|
|
56
|
+
console.log('');
|
|
57
|
+
console.log(`${paths.length} change${paths.length === 1 ? '' : 's'} need${paths.length === 1 ? 's' : ''} acknowledging: add ${paths.map((item) => `"${item}"`).join(', ')} to "acknowledged" in ${file}, or pass --accept ${paths.join(',')}.`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function parse(text, source, json) {
|
|
61
|
+
try {
|
|
62
|
+
return parsePolicy(text);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
if (!(error instanceof PolicyError))
|
|
66
|
+
throw error;
|
|
67
|
+
process.exitCode = 1;
|
|
68
|
+
if (json)
|
|
69
|
+
writeJson({ source, valid: false, issues: error.issues });
|
|
70
|
+
else {
|
|
71
|
+
console.log(`${source} is not a valid policy:`);
|
|
72
|
+
for (const issue of error.issues)
|
|
73
|
+
console.log(` ${issue.path || '(document)'}: ${issue.message}`);
|
|
74
|
+
}
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async function readBase(base, file) {
|
|
79
|
+
if (!base.startsWith('git:'))
|
|
80
|
+
return readFile(base, 'utf8');
|
|
81
|
+
const ref = base.slice(4);
|
|
82
|
+
if (!ref)
|
|
83
|
+
throw new CliUsageError('--base git: needs a ref, such as git:main or git:HEAD~1.');
|
|
84
|
+
const git = async (args) => {
|
|
85
|
+
try {
|
|
86
|
+
return (await run('git', args, { cwd: path.dirname(path.resolve(file)), maxBuffer: 16 * 1024 * 1024 })).stdout;
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
const stderr = String(error.stderr ?? '').trim();
|
|
90
|
+
throw new Error(stderr || error.message);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
try {
|
|
94
|
+
await git(['rev-parse', '--verify', '--quiet', `${ref}^{commit}`]);
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
throw new Error(`git has no commit ${ref} where ${file} is: ${error.message || 'unknown ref'}`);
|
|
98
|
+
}
|
|
99
|
+
const object = `${ref}:./${path.basename(file)}`;
|
|
100
|
+
try {
|
|
101
|
+
await git(['cat-file', '-e', object]);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
return git(['show', object]);
|
|
107
|
+
}
|
|
108
|
+
function describe(policy) {
|
|
109
|
+
return `Policy ${policy.name}@${policy.version} (sha256 ${policy.hash.slice(0, 12)})`;
|
|
110
|
+
}
|
|
111
|
+
function report(json, value, text) {
|
|
112
|
+
if (json)
|
|
113
|
+
writeJson(value);
|
|
114
|
+
else
|
|
115
|
+
console.log(text);
|
|
116
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
2
|
+
import { readdir, readFile, stat } from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import process from 'node:process';
|
|
4
5
|
import { pathToFileURL } from 'node:url';
|
|
5
|
-
import {
|
|
6
|
-
import { EvalRunner } from './evals/runner.js';
|
|
6
|
+
import { CliUsageError, flagBool, isRecord, numberFlag, parseArgs, printTable, stringFlag, writeJson, } from './cli/args.js';
|
|
7
7
|
import { NexusAI } from './core/nexus.js';
|
|
8
|
-
import {
|
|
8
|
+
import { EvalRunner } from './evals/runner.js';
|
|
9
|
+
import { getModelCapabilities, listKnownModels, listModelsForProvider } from './models/registry.js';
|
|
10
|
+
import { TokenOptimizer } from './optimizer/index.js';
|
|
9
11
|
import { SecurityPipeline } from './security/index.js';
|
|
10
12
|
import { redactSensitiveText } from './security/output-guard.js';
|
|
11
13
|
import { UploadScanner } from './security/upload-scanner.js';
|
|
12
|
-
import { TokenOptimizer } from './optimizer/index.js';
|
|
13
|
-
import { CliUsageError, flagBool, isRecord, numberFlag, parseArgs, printTable, stringFlag, writeJson, } from './cli/args.js';
|
|
14
14
|
const DEFAULT_SCAN_MAX_BYTES = 1_000_000;
|
|
15
15
|
const IGNORED_DIRS = new Set(['.git', 'node_modules', 'dist', 'coverage', '.next', '.turbo']);
|
|
16
16
|
async function main() {
|
|
@@ -78,6 +78,12 @@ async function main() {
|
|
|
78
78
|
await runDeployCommand(subcommand, { positionals: args, flags: parsed.flags });
|
|
79
79
|
return;
|
|
80
80
|
}
|
|
81
|
+
case 'policy': {
|
|
82
|
+
const [subcommand, ...args] = parsed.positionals;
|
|
83
|
+
const { runPolicyCommand } = await import('./cli/policy.js');
|
|
84
|
+
await runPolicyCommand(subcommand, { positionals: args, flags: parsed.flags });
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
81
87
|
case 'optimize':
|
|
82
88
|
await runOptimize(parsed);
|
|
83
89
|
return;
|
|
@@ -431,6 +437,7 @@ Usage:
|
|
|
431
437
|
nexus deploy canary <assistant> <revision> <percent> --url <server> [--reason r] [--version n]
|
|
432
438
|
nexus deploy promote <assistant> <revision> --url <server> [--reason r] [--version n]
|
|
433
439
|
nexus deploy rollback <assistant> [--to revision] --url <server> [--reason r] [--version n]
|
|
440
|
+
nexus policy check <policy.json> [--base <policy.json>|git:<ref>] [--accept path,path] [--json]
|
|
434
441
|
nexus optimize [request.json|prompt.txt] [--model gpt-5-mini] [--max-input-tokens 4000] [--densify] [--json]
|
|
435
442
|
|
|
436
443
|
Eval files can export { cases, client } or { cases, config }. JSON cases may use "expected", "contains", and "match": "includes" | "exact" | "regex".
|
|
@@ -442,7 +449,8 @@ Database modules for "db status" and "db migrate" export { client }, and optiona
|
|
|
442
449
|
"doctor" exits 1 when a check fails, or with --strict when one warns. Its module exports any of { database, client, redis, operations, ai, deployment, close }.
|
|
443
450
|
"deploy" reads NEXUS_SERVER_URL and NEXUS_SERVER_TOKEN when --url and --token are not given.
|
|
444
451
|
"migrate" moves 1.x root imports to their 2.0 subpaths and reports removed options; --check exits 1 while any remain.
|
|
445
|
-
"graph lint" reads a compiled graph's shape and exits 1 on an error finding, or on a warning with --strict
|
|
452
|
+
"graph lint" reads a compiled graph's shape and exits 1 on an error finding, or on a warning with --strict.
|
|
453
|
+
"policy check" exits 1 when the policy is not valid, or, against --base, when a change that widens what a tenant may reach is not acknowledged.`);
|
|
446
454
|
}
|
|
447
455
|
main().catch((error) => {
|
|
448
456
|
const message = error instanceof Error ? error.message : String(error);
|
package/dist/core/lifecycle.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type { RateLimitConfig } from '../types/config.js';
|
|
2
|
-
import type { ProviderHealthOutcome, LifecycleConfig, LifecycleStage, OperationDescriptor, OperationLifecycleLike, OperationResultInfo, OperationStartOptions, OperationTicket, ProviderCallContext } from '../types/lifecycle.js';
|
|
3
1
|
import type { AuditLogger } from '../ops/audit-logger.js';
|
|
4
2
|
import type { MetricsCollector } from '../ops/metrics.js';
|
|
5
3
|
import { type RateLimiter } from '../ops/rate-limiter.js';
|
|
4
|
+
import type { RateLimitConfig } from '../types/config.js';
|
|
5
|
+
import type { ContinuedCallContext, LifecycleConfig, LifecycleStage, OperationDescriptor, OperationLifecycleLike, OperationResultInfo, OperationStartOptions, OperationTicket, ProviderCallContext, ProviderGate, ProviderHealthOutcome } from '../types/lifecycle.js';
|
|
6
|
+
import type { CallResidency, ResidencyDecision, ResidencyPolicy } from '../types/residency.js';
|
|
6
7
|
/** Every lifecycle stage, in the order an operation passes through them. */
|
|
7
8
|
export declare const LIFECYCLE_STAGES: readonly LifecycleStage[];
|
|
8
9
|
/** Raised when `lifecycle.authorize` refuses an operation, before any provider is called. */
|
|
@@ -104,6 +105,21 @@ export declare class OperationLifecycle implements OperationLifecycleLike {
|
|
|
104
105
|
}, plan: OperationPlan<T>, options?: OperationStartOptions & {
|
|
105
106
|
audit?: Record<string, unknown>;
|
|
106
107
|
}): Promise<T>;
|
|
108
|
+
/**
|
|
109
|
+
* What a provider call that continues an operation admitted earlier is given, such as a video job
|
|
110
|
+
* polled after a restart or a batch collected by another worker: its tenant's credentials, resolved
|
|
111
|
+
* as `start()` resolves them, its residency, and a `scrub()` that keeps them out of its errors.
|
|
112
|
+
* Nothing is authorized, budgeted, or audited: the operation it continues was.
|
|
113
|
+
*/
|
|
114
|
+
callContext(input: Omit<OperationDescriptor, 'requestId'> & {
|
|
115
|
+
requestId?: string;
|
|
116
|
+
}): Promise<ContinuedCallContext>;
|
|
117
|
+
/**
|
|
118
|
+
* Which providers and models an operation may reach, under the residency policy and the provider
|
|
119
|
+
* gate, or `undefined` when there is neither: what `start()` gives each operation's context, for
|
|
120
|
+
* code that decides outside one.
|
|
121
|
+
*/
|
|
122
|
+
access(descriptor: Pick<OperationDescriptor, 'tenantId'> & Partial<OperationDescriptor>): Promise<CallResidency | undefined>;
|
|
107
123
|
/**
|
|
108
124
|
* Reports how a provider answered a call a family made itself, such as a realtime connection, to the
|
|
109
125
|
* client's provider health and circuit breaker.
|
|
@@ -114,3 +130,10 @@ export declare class OperationLifecycle implements OperationLifecycleLike {
|
|
|
114
130
|
private checkRateLimit;
|
|
115
131
|
private authorize;
|
|
116
132
|
}
|
|
133
|
+
/** An operation's residency, as the families that call providers check it. */
|
|
134
|
+
export declare function callResidency(policy: ResidencyPolicy, decision: CallResidency['decision']): CallResidency;
|
|
135
|
+
/**
|
|
136
|
+
* What an operation may reach: the providers its residency policy allows, and among them those the
|
|
137
|
+
* provider gate admits, model by model.
|
|
138
|
+
*/
|
|
139
|
+
export declare function callAccess(descriptor: OperationDescriptor | undefined, policy: ResidencyPolicy | undefined, decision: ResidencyDecision | undefined, gate: ProviderGate | undefined): CallResidency;
|