@nathapp/nax-agent 0.1.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/CHANGELOG.md +24 -0
- package/LICENSE +21 -0
- package/README.md +49 -0
- package/dist/coding-tools/coding-tool-bash.d.ts +46 -0
- package/dist/coding-tools/coding-tool-bash.js +44 -0
- package/dist/coding-tools/coding-tool-extras.d.ts +35 -0
- package/dist/coding-tools/coding-tool-extras.js +56 -0
- package/dist/coding-tools/coding-tool-sandbox.d.ts +74 -0
- package/dist/coding-tools/coding-tool-sandbox.js +215 -0
- package/dist/coding-tools/coding-tool-support.d.ts +132 -0
- package/dist/coding-tools/coding-tool-support.js +159 -0
- package/dist/coding-tools/universal-coding-tools.d.ts +11 -0
- package/dist/coding-tools/universal-coding-tools.js +5 -0
- package/dist/command-interceptor/index.d.ts +55 -0
- package/dist/command-interceptor/index.js +60 -0
- package/dist/command-interceptor/shell.d.ts +36 -0
- package/dist/command-interceptor/shell.js +137 -0
- package/dist/command-safety/build.d.ts +19 -0
- package/dist/command-safety/build.js +47 -0
- package/dist/command-safety/guard.d.ts +32 -0
- package/dist/command-safety/guard.js +179 -0
- package/dist/command-safety/identifiers.d.ts +24 -0
- package/dist/command-safety/identifiers.js +20 -0
- package/dist/command-safety/index.d.ts +17 -0
- package/dist/command-safety/index.js +11 -0
- package/dist/command-safety/questions.d.ts +19 -0
- package/dist/command-safety/questions.js +60 -0
- package/dist/command-safety/row.d.ts +2 -0
- package/dist/command-safety/row.js +14 -0
- package/dist/command-safety/rule-scorer.d.ts +9 -0
- package/dist/command-safety/rule-scorer.js +391 -0
- package/dist/command-safety/shadow.d.ts +33 -0
- package/dist/command-safety/shadow.js +188 -0
- package/dist/command-safety/systemone-client.d.ts +16 -0
- package/dist/command-safety/systemone-client.js +92 -0
- package/dist/command-safety/tap.d.ts +37 -0
- package/dist/command-safety/tap.js +77 -0
- package/dist/command-safety/temp-only.d.ts +6 -0
- package/dist/command-safety/temp-only.js +215 -0
- package/dist/command-safety/tmp-write.d.ts +2 -0
- package/dist/command-safety/tmp-write.js +112 -0
- package/dist/command-safety/types.d.ts +185 -0
- package/dist/command-safety/types.js +21 -0
- package/dist/config/bash-approval.d.ts +33 -0
- package/dist/config/bash-approval.js +30 -0
- package/dist/config/catalog-overrides.d.ts +94 -0
- package/dist/config/catalog-overrides.js +7 -0
- package/dist/config/native-agent/index.d.ts +8 -0
- package/dist/config/native-agent/index.js +8 -0
- package/dist/config/schemas-sandbox.d.ts +31 -0
- package/dist/config/schemas-sandbox.js +45 -0
- package/dist/cost/core/index.d.ts +8 -0
- package/dist/cost/core/index.js +7 -0
- package/dist/cost/estimate.d.ts +34 -0
- package/dist/cost/estimate.js +65 -0
- package/dist/cost/model-spec.d.ts +28 -0
- package/dist/cost/model-spec.js +29 -0
- package/dist/cost/standard-types.d.ts +9 -0
- package/dist/cost/standard-types.js +1 -0
- package/dist/cost/usage-math.d.ts +18 -0
- package/dist/cost/usage-math.js +46 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +21 -0
- package/dist/infra/agent-logger.d.ts +22 -0
- package/dist/infra/agent-logger.js +19 -0
- package/dist/infra/credentials-config.d.ts +20 -0
- package/dist/infra/credentials-config.js +15 -0
- package/dist/infra/errors.d.ts +9 -0
- package/dist/infra/errors.js +11 -0
- package/dist/infra/index.d.ts +7 -0
- package/dist/infra/index.js +7 -0
- package/dist/infra/nax-error.d.ts +10 -0
- package/dist/infra/nax-error.js +16 -0
- package/dist/infra/spin-breaker/hash.d.ts +2 -0
- package/dist/infra/spin-breaker/hash.js +5 -0
- package/dist/infra/spin-breaker/index.d.ts +120 -0
- package/dist/infra/spin-breaker/index.js +340 -0
- package/dist/internal/agent-output-env.d.ts +44 -0
- package/dist/internal/agent-output-env.js +57 -0
- package/dist/internal/argv-exec.d.ts +58 -0
- package/dist/internal/argv-exec.js +224 -0
- package/dist/internal/bounded-io.d.ts +24 -0
- package/dist/internal/bounded-io.js +51 -0
- package/dist/internal/command-spec/index.d.ts +29 -0
- package/dist/internal/command-spec/index.js +36 -0
- package/dist/internal/describe-value-type.d.ts +8 -0
- package/dist/internal/describe-value-type.js +14 -0
- package/dist/internal/exec-framing.d.ts +18 -0
- package/dist/internal/exec-framing.js +22 -0
- package/dist/internal/file-lock.d.ts +91 -0
- package/dist/internal/file-lock.js +212 -0
- package/dist/internal/file-size.d.ts +2 -0
- package/dist/internal/file-size.js +12 -0
- package/dist/internal/git-add.d.ts +43 -0
- package/dist/internal/git-add.js +59 -0
- package/dist/internal/git-env.d.ts +59 -0
- package/dist/internal/git-env.js +93 -0
- package/dist/internal/git-exec.d.ts +58 -0
- package/dist/internal/git-exec.js +115 -0
- package/dist/internal/path-file-lock.d.ts +23 -0
- package/dist/internal/path-file-lock.js +25 -0
- package/dist/internal/process-alive.d.ts +42 -0
- package/dist/internal/process-alive.js +56 -0
- package/dist/internal/process-kill.d.ts +27 -0
- package/dist/internal/process-kill.js +54 -0
- package/dist/internal/realpath.d.ts +29 -0
- package/dist/internal/realpath.js +52 -0
- package/dist/internal/redact.d.ts +40 -0
- package/dist/internal/redact.js +161 -0
- package/dist/internal/shell-quote.d.ts +2 -0
- package/dist/internal/shell-quote.js +4 -0
- package/dist/internal/sort.d.ts +20 -0
- package/dist/internal/sort.js +20 -0
- package/dist/internal/strip-control-chars.d.ts +19 -0
- package/dist/internal/strip-control-chars.js +38 -0
- package/dist/internal/thenable.d.ts +10 -0
- package/dist/internal/thenable.js +15 -0
- package/dist/internal.d.ts +91 -0
- package/dist/internal.js +91 -0
- package/dist/native/adapter-deps.d.ts +46 -0
- package/dist/native/adapter-deps.js +43 -0
- package/dist/native/auth-types.d.ts +70 -0
- package/dist/native/auth-types.js +13 -0
- package/dist/native/auth.d.ts +133 -0
- package/dist/native/auth.js +362 -0
- package/dist/native/client.d.ts +50 -0
- package/dist/native/client.js +174 -0
- package/dist/native/complete.d.ts +33 -0
- package/dist/native/complete.js +88 -0
- package/dist/native/credentials/chained-store.d.ts +30 -0
- package/dist/native/credentials/chained-store.js +68 -0
- package/dist/native/credentials/change-guard.d.ts +51 -0
- package/dist/native/credentials/change-guard.js +136 -0
- package/dist/native/credentials/exec-source.d.ts +50 -0
- package/dist/native/credentials/exec-source.js +222 -0
- package/dist/native/credentials/fingerprint.d.ts +26 -0
- package/dist/native/credentials/fingerprint.js +174 -0
- package/dist/native/credentials/helper-process.d.ts +79 -0
- package/dist/native/credentials/helper-process.js +259 -0
- package/dist/native/credentials/helper-protocol.d.ts +36 -0
- package/dist/native/credentials/helper-protocol.js +79 -0
- package/dist/native/credentials/index.d.ts +60 -0
- package/dist/native/credentials/index.js +184 -0
- package/dist/native/errors.d.ts +34 -0
- package/dist/native/errors.js +109 -0
- package/dist/native/index.d.ts +25 -0
- package/dist/native/index.js +23 -0
- package/dist/native/model-resolver.d.ts +57 -0
- package/dist/native/model-resolver.js +73 -0
- package/dist/native/models.d.ts +116 -0
- package/dist/native/models.js +183 -0
- package/dist/native/session/ask-human.d.ts +15 -0
- package/dist/native/session/ask-human.js +22 -0
- package/dist/native/session/compaction.d.ts +94 -0
- package/dist/native/session/compaction.js +170 -0
- package/dist/native/session/handle-invalid-tool-call.d.ts +111 -0
- package/dist/native/session/handle-invalid-tool-call.js +159 -0
- package/dist/native/session/loop-events/cache-boundary.d.ts +11 -0
- package/dist/native/session/loop-events/cache-boundary.js +29 -0
- package/dist/native/session/loop-events/external-handler.d.ts +38 -0
- package/dist/native/session/loop-events/external-handler.js +193 -0
- package/dist/native/session/loop-events/index.d.ts +16 -0
- package/dist/native/session/loop-events/index.js +15 -0
- package/dist/native/session/loop-events/payload-guard.d.ts +9 -0
- package/dist/native/session/loop-events/payload-guard.js +31 -0
- package/dist/native/session/loop-events/registry.d.ts +84 -0
- package/dist/native/session/loop-events/registry.js +305 -0
- package/dist/native/session/loop-events/types.d.ts +251 -0
- package/dist/native/session/loop-events/types.js +14 -0
- package/dist/native/session/loop-handlers.d.ts +73 -0
- package/dist/native/session/loop-handlers.js +137 -0
- package/dist/native/session/nudge.d.ts +29 -0
- package/dist/native/session/nudge.js +35 -0
- package/dist/native/session/rate-provenance.d.ts +17 -0
- package/dist/native/session/rate-provenance.js +39 -0
- package/dist/native/session/session.d.ts +139 -0
- package/dist/native/session/session.js +208 -0
- package/dist/native/session/tool-input-exemplar.d.ts +2 -0
- package/dist/native/session/tool-input-exemplar.js +66 -0
- package/dist/native/session/tool-input-validation.d.ts +15 -0
- package/dist/native/session/tool-input-validation.js +146 -0
- package/dist/native/session/tool-mapping.d.ts +18 -0
- package/dist/native/session/tool-mapping.js +27 -0
- package/dist/native/session/tool-result.d.ts +37 -0
- package/dist/native/session/tool-result.js +25 -0
- package/dist/native/session/transcript-store.d.ts +77 -0
- package/dist/native/session/transcript-store.js +200 -0
- package/dist/native/session/truncation-handler.d.ts +47 -0
- package/dist/native/session/truncation-handler.js +83 -0
- package/dist/native/session/turn-accumulator.d.ts +45 -0
- package/dist/native/session/turn-accumulator.js +67 -0
- package/dist/native/session/turn-ask-human.d.ts +23 -0
- package/dist/native/session/turn-ask-human.js +41 -0
- package/dist/native/session/turn-compaction-step.d.ts +74 -0
- package/dist/native/session/turn-compaction-step.js +128 -0
- package/dist/native/session/turn-complete-step.d.ts +77 -0
- package/dist/native/session/turn-complete-step.js +159 -0
- package/dist/native/session/turn-end-event.d.ts +3 -0
- package/dist/native/session/turn-end-event.js +17 -0
- package/dist/native/session/turn-events.d.ts +63 -0
- package/dist/native/session/turn-events.js +48 -0
- package/dist/native/session/turn-loop-round-trip.d.ts +116 -0
- package/dist/native/session/turn-loop-round-trip.js +318 -0
- package/dist/native/session/turn-loop.d.ts +23 -0
- package/dist/native/session/turn-loop.js +250 -0
- package/dist/native/session/turn-result.d.ts +39 -0
- package/dist/native/session/turn-result.js +64 -0
- package/dist/native/session/turn-retry.d.ts +120 -0
- package/dist/native/session/turn-retry.js +182 -0
- package/dist/native/session/turn-tool-batch.d.ts +61 -0
- package/dist/native/session/turn-tool-batch.js +259 -0
- package/dist/native/session/turn-types.d.ts +164 -0
- package/dist/native/session/turn-types.js +36 -0
- package/dist/native/session-adapter.d.ts +56 -0
- package/dist/native/session-adapter.js +369 -0
- package/dist/native/session-affinity.d.ts +36 -0
- package/dist/native/session-affinity.js +43 -0
- package/dist/native/tier-providers.d.ts +18 -0
- package/dist/native/tier-providers.js +20 -0
- package/dist/permissions/approval-audit.d.ts +10 -0
- package/dist/permissions/approval-audit.js +22 -0
- package/dist/permissions/approvals-link.d.ts +20 -0
- package/dist/permissions/approvals-link.js +62 -0
- package/dist/permissions/approvals-store.d.ts +168 -0
- package/dist/permissions/approvals-store.js +259 -0
- package/dist/permissions/approvals-taint.d.ts +33 -0
- package/dist/permissions/approvals-taint.js +138 -0
- package/dist/permissions/ask-chain.d.ts +69 -0
- package/dist/permissions/ask-chain.js +32 -0
- package/dist/permissions/ask.d.ts +23 -0
- package/dist/permissions/ask.js +25 -0
- package/dist/permissions/bash-lex.d.ts +59 -0
- package/dist/permissions/bash-lex.js +279 -0
- package/dist/permissions/grammar.d.ts +12 -0
- package/dist/permissions/grammar.js +24 -0
- package/dist/permissions/index.d.ts +11 -0
- package/dist/permissions/index.js +9 -0
- package/dist/permissions/secret-spans.d.ts +19 -0
- package/dist/permissions/secret-spans.js +88 -0
- package/dist/permissions/types.d.ts +85 -0
- package/dist/permissions/types.js +1 -0
- package/dist/runtime/index.d.ts +3 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/node-glob.d.ts +3 -0
- package/dist/runtime/node-glob.js +83 -0
- package/dist/runtime/node-runtime.d.ts +3 -0
- package/dist/runtime/node-runtime.js +126 -0
- package/dist/runtime/slot.d.ts +11 -0
- package/dist/runtime/slot.js +17 -0
- package/dist/runtime/types.d.ts +51 -0
- package/dist/runtime/types.js +1 -0
- package/dist/runtime/which.d.ts +8 -0
- package/dist/runtime/which.js +32 -0
- package/dist/sandbox/argv-quote.d.ts +11 -0
- package/dist/sandbox/argv-quote.js +13 -0
- package/dist/sandbox/defaults.d.ts +27 -0
- package/dist/sandbox/defaults.js +47 -0
- package/dist/sandbox/git-guards.d.ts +64 -0
- package/dist/sandbox/git-guards.js +161 -0
- package/dist/sandbox/index.d.ts +11 -0
- package/dist/sandbox/index.js +10 -0
- package/dist/sandbox/launcher.d.ts +37 -0
- package/dist/sandbox/launcher.js +160 -0
- package/dist/sandbox/messages.d.ts +17 -0
- package/dist/sandbox/messages.js +46 -0
- package/dist/sandbox/policy-builder.d.ts +44 -0
- package/dist/sandbox/policy-builder.js +149 -0
- package/dist/sandbox/policy-inputs.d.ts +53 -0
- package/dist/sandbox/policy-inputs.js +76 -0
- package/dist/sandbox/probe.d.ts +8 -0
- package/dist/sandbox/probe.js +66 -0
- package/dist/sandbox/registry.d.ts +17 -0
- package/dist/sandbox/registry.js +57 -0
- package/dist/sandbox/session-tmp.d.ts +20 -0
- package/dist/sandbox/session-tmp.js +95 -0
- package/dist/sandbox/srt-backend.d.ts +21 -0
- package/dist/sandbox/srt-backend.js +99 -0
- package/dist/sandbox/types.d.ts +102 -0
- package/dist/sandbox/types.js +1 -0
- package/dist/session/adapter-failure.d.ts +60 -0
- package/dist/session/adapter-failure.js +11 -0
- package/dist/session/agent-stream-events.d.ts +103 -0
- package/dist/session/agent-stream-events.js +6 -0
- package/dist/session/interaction-handler.d.ts +53 -0
- package/dist/session/interaction-handler.js +2 -0
- package/dist/session/no-op-interaction-handler.d.ts +9 -0
- package/dist/session/no-op-interaction-handler.js +11 -0
- package/dist/session/protocol-types.d.ts +12 -0
- package/dist/session/protocol-types.js +1 -0
- package/dist/session/session-types.d.ts +456 -0
- package/dist/session/session-types.js +64 -0
- package/dist/session/tool-descriptor.d.ts +23 -0
- package/dist/session/tool-descriptor.js +5 -0
- package/dist/session/turn-deadline.d.ts +17 -0
- package/dist/session/turn-deadline.js +23 -0
- package/dist/tools/ask-request.d.ts +35 -0
- package/dist/tools/ask-request.js +57 -0
- package/dist/tools/bash-cwd.d.ts +82 -0
- package/dist/tools/bash-cwd.js +55 -0
- package/dist/tools/bash.d.ts +69 -0
- package/dist/tools/bash.js +283 -0
- package/dist/tools/delete.d.ts +57 -0
- package/dist/tools/delete.js +154 -0
- package/dist/tools/denial-redirect.d.ts +42 -0
- package/dist/tools/denial-redirect.js +266 -0
- package/dist/tools/deny-paths.d.ts +35 -0
- package/dist/tools/deny-paths.js +76 -0
- package/dist/tools/edit-region.d.ts +50 -0
- package/dist/tools/edit-region.js +90 -0
- package/dist/tools/edit.d.ts +25 -0
- package/dist/tools/edit.js +114 -0
- package/dist/tools/exec-allowlist-text.d.ts +18 -0
- package/dist/tools/exec-allowlist-text.js +24 -0
- package/dist/tools/exec-guard.d.ts +69 -0
- package/dist/tools/exec-guard.js +151 -0
- package/dist/tools/git-commit.d.ts +59 -0
- package/dist/tools/git-commit.js +220 -0
- package/dist/tools/git-flags/index.d.ts +10 -0
- package/dist/tools/git-flags/index.js +10 -0
- package/dist/tools/git.d.ts +88 -0
- package/dist/tools/git.js +397 -0
- package/dist/tools/glob.d.ts +29 -0
- package/dist/tools/glob.js +153 -0
- package/dist/tools/grep.d.ts +21 -0
- package/dist/tools/grep.js +162 -0
- package/dist/tools/index.d.ts +36 -0
- package/dist/tools/index.js +28 -0
- package/dist/tools/narrow-grants.d.ts +19 -0
- package/dist/tools/narrow-grants.js +54 -0
- package/dist/tools/nax-owned-writes.d.ts +135 -0
- package/dist/tools/nax-owned-writes.js +259 -0
- package/dist/tools/package-managers-table.d.ts +146 -0
- package/dist/tools/package-managers-table.js +325 -0
- package/dist/tools/package-managers-types.d.ts +36 -0
- package/dist/tools/package-managers-types.js +6 -0
- package/dist/tools/package-managers.d.ts +85 -0
- package/dist/tools/package-managers.js +391 -0
- package/dist/tools/path-list.d.ts +38 -0
- package/dist/tools/path-list.js +62 -0
- package/dist/tools/policy-bash-raw.d.ts +19 -0
- package/dist/tools/policy-bash-raw.js +210 -0
- package/dist/tools/policy-bash.d.ts +64 -0
- package/dist/tools/policy-bash.js +232 -0
- package/dist/tools/policy-command-branch.d.ts +32 -0
- package/dist/tools/policy-command-branch.js +63 -0
- package/dist/tools/policy-input.d.ts +2 -0
- package/dist/tools/policy-input.js +10 -0
- package/dist/tools/policy-match.d.ts +75 -0
- package/dist/tools/policy-match.js +117 -0
- package/dist/tools/policy-paths-branch.d.ts +97 -0
- package/dist/tools/policy-paths-branch.js +314 -0
- package/dist/tools/policy.d.ts +65 -0
- package/dist/tools/policy.js +232 -0
- package/dist/tools/protected-paths.d.ts +26 -0
- package/dist/tools/protected-paths.js +8 -0
- package/dist/tools/provider-adapt.d.ts +4 -0
- package/dist/tools/provider-adapt.js +32 -0
- package/dist/tools/provider-advertise.d.ts +39 -0
- package/dist/tools/provider-advertise.js +50 -0
- package/dist/tools/provider-grants.d.ts +36 -0
- package/dist/tools/provider-grants.js +72 -0
- package/dist/tools/provider-sanitize.d.ts +19 -0
- package/dist/tools/provider-sanitize.js +53 -0
- package/dist/tools/provider-types.d.ts +41 -0
- package/dist/tools/provider-types.js +26 -0
- package/dist/tools/read-continuation.d.ts +147 -0
- package/dist/tools/read-continuation.js +176 -0
- package/dist/tools/read-file.d.ts +32 -0
- package/dist/tools/read-file.js +85 -0
- package/dist/tools/read.d.ts +25 -0
- package/dist/tools/read.js +182 -0
- package/dist/tools/registry.d.ts +125 -0
- package/dist/tools/registry.js +67 -0
- package/dist/tools/request-capability.d.ts +13 -0
- package/dist/tools/request-capability.js +23 -0
- package/dist/tools/run-command-exec.d.ts +14 -0
- package/dist/tools/run-command-exec.js +161 -0
- package/dist/tools/run-command.d.ts +107 -0
- package/dist/tools/run-command.js +398 -0
- package/dist/tools/runtime-calltool.d.ts +146 -0
- package/dist/tools/runtime-calltool.js +223 -0
- package/dist/tools/runtime.d.ts +149 -0
- package/dist/tools/runtime.js +345 -0
- package/dist/tools/scratchpad.d.ts +20 -0
- package/dist/tools/scratchpad.js +197 -0
- package/dist/tools/spill.d.ts +81 -0
- package/dist/tools/spill.js +251 -0
- package/dist/tools/tool-audit.d.ts +171 -0
- package/dist/tools/tool-audit.js +183 -0
- package/dist/tools/truncate.d.ts +98 -0
- package/dist/tools/truncate.js +297 -0
- package/dist/tools/types.d.ts +145 -0
- package/dist/tools/types.js +24 -0
- package/dist/tools/verb-denial-argv-hint.d.ts +35 -0
- package/dist/tools/verb-denial-argv-hint.js +40 -0
- package/dist/tools/write.d.ts +9 -0
- package/dist/tools/write.js +45 -0
- package/package.json +54 -0
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Obtaining and managing credentials, in nax's vocabulary.
|
|
3
|
+
*
|
|
4
|
+
* This file and its siblings are the only place in src/ permitted to import
|
|
5
|
+
* nax-ai (packages/repo-tooling/scripts/check-nax-ai-imports.ts). Nothing it exports carries a
|
|
6
|
+
* nax-ai type, so src/cli/auth.ts can consume it without breaching that gate.
|
|
7
|
+
*/
|
|
8
|
+
import { readFile } from "node:fs/promises";
|
|
9
|
+
import { homedir } from "node:os";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { ambientAuthAvailable, defaultProviders, login, } from "@nathapp/nax-ai";
|
|
12
|
+
import { NaxError } from "#src/infra/index";
|
|
13
|
+
import { naxCredentialStore, readStoredEntries } from "./credentials/index.js";
|
|
14
|
+
/**
|
|
15
|
+
* Deliberately dumb: the two vocabularies are one-for-one by design, so this
|
|
16
|
+
* is a rename boundary rather than a translation with opinions. Both sides use
|
|
17
|
+
* kebab-case, so the names pass straight through.
|
|
18
|
+
*/
|
|
19
|
+
export function toLoginInteraction(interaction) {
|
|
20
|
+
return {
|
|
21
|
+
prompt: async (prompt) => interaction.prompt(prompt),
|
|
22
|
+
notify: (event) => interaction.notify(event),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Cancellation is not a failure, and the CLI needs to tell them apart to exit
|
|
27
|
+
* 130 silently. A marker class rather than a code match: an exit status should
|
|
28
|
+
* not depend on message text.
|
|
29
|
+
*/
|
|
30
|
+
export class AuthCancelledError extends Error {
|
|
31
|
+
providerId;
|
|
32
|
+
constructor(providerId) {
|
|
33
|
+
super(`Login for "${providerId}" was cancelled`);
|
|
34
|
+
this.providerId = providerId;
|
|
35
|
+
this.name = "AuthCancelledError";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** Test seam, following the _clientDeps precedent. */
|
|
39
|
+
export const _authDeps = {
|
|
40
|
+
login,
|
|
41
|
+
ambientAuthAvailable,
|
|
42
|
+
providerIds: async () => (await defaultProviders()).map((provider) => provider.id),
|
|
43
|
+
/**
|
|
44
|
+
* Injectable timer pair — lets tests drive `anyAmbientCredential`'s
|
|
45
|
+
* expiry branch off a virtual clock instead of waiting the full 2s
|
|
46
|
+
* production timeout. Mirrors `_heartbeatDeps` / `_idleWatchdogDeps`.
|
|
47
|
+
*
|
|
48
|
+
* @internal
|
|
49
|
+
*/
|
|
50
|
+
setTimeout: ((fn, ms) => setTimeout(fn, ms)),
|
|
51
|
+
clearTimeout: ((id) => clearTimeout(id)),
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* nax-ai's errors are mapped here rather than in the CLI, so its type names
|
|
55
|
+
* stay behind this directory's boundary.
|
|
56
|
+
*
|
|
57
|
+
* A prohibited flow keeps its message: the recorded reason is the whole point
|
|
58
|
+
* of the policy file, and a generic failure would discard it.
|
|
59
|
+
*/
|
|
60
|
+
function toNaxError(error, providerId) {
|
|
61
|
+
if (error instanceof Error) {
|
|
62
|
+
// Nax's own prompts reject with PromptCancelledError from inside the
|
|
63
|
+
// interaction callback, so it surfaces here rather than being converted
|
|
64
|
+
// by nax-ai's login() — that conversion only fires on an aborted signal,
|
|
65
|
+
// and runLogin never threads one.
|
|
66
|
+
if (error.name === "LoginCancelledError" || error.name === "PromptCancelledError") {
|
|
67
|
+
return new AuthCancelledError(providerId);
|
|
68
|
+
}
|
|
69
|
+
if (error.name === "OAuthFlowProhibitedError") {
|
|
70
|
+
return new NaxError(error.message, "AUTH_OAUTH_PROHIBITED", { providerId });
|
|
71
|
+
}
|
|
72
|
+
if (error.name === "AuthMethodUnavailableError") {
|
|
73
|
+
// "requested" separates "this provider offers nothing we can run" from
|
|
74
|
+
// "it offers something, just not the --method you asked for". Collapsing
|
|
75
|
+
// them tells a user whose provider supports oauth that nothing is
|
|
76
|
+
// available, and sends them to a config problem that does not exist.
|
|
77
|
+
const requested = error.requested;
|
|
78
|
+
return new NaxError(requested === undefined
|
|
79
|
+
? `No login method is available for "${providerId}".`
|
|
80
|
+
: `"${providerId}" does not offer "${requested}" login. Omit --method to see what it does offer.`, "AUTH_METHOD_UNAVAILABLE", { providerId, ...(requested !== undefined ? { requested } : {}) });
|
|
81
|
+
}
|
|
82
|
+
return new NaxError(`Login for "${providerId}" failed: ${error.message}`, "AUTH_LOGIN_FAILED", { providerId });
|
|
83
|
+
}
|
|
84
|
+
return new NaxError(`Login for "${providerId}" failed.`, "AUTH_LOGIN_FAILED", { providerId });
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Obtain a credential and write it to the store.
|
|
88
|
+
*
|
|
89
|
+
* `method` is forwarded, not interpreted: nax-ai owns the table of what each
|
|
90
|
+
* provider offers and rejects an unavailable choice itself. Omitted, it runs
|
|
91
|
+
* its own selection prompt. Deciding here is how the two drift apart.
|
|
92
|
+
*
|
|
93
|
+
* The result is reported exactly as returned. kind is never derived from
|
|
94
|
+
* method — M5's design predicted openrouter would report kind "api-key" and
|
|
95
|
+
* its live run reported "oauth".
|
|
96
|
+
*/
|
|
97
|
+
export async function runLogin(providerId, interaction, method) {
|
|
98
|
+
try {
|
|
99
|
+
const result = await _authDeps.login({
|
|
100
|
+
providerId,
|
|
101
|
+
credentials: naxCredentialStore(),
|
|
102
|
+
interaction: toLoginInteraction(interaction),
|
|
103
|
+
...(method !== undefined ? { method } : {}),
|
|
104
|
+
});
|
|
105
|
+
return { providerId: result.providerId, method: result.method, kind: result.kind };
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
throw toNaxError(error, providerId);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
export const DEFAULT_PI_AUTH_PATH = join(homedir(), ".pi", "agent", "auth.json");
|
|
112
|
+
/**
|
|
113
|
+
* pi's on-disk shape is flat and snake-cased; the store's is versioned and
|
|
114
|
+
* kebab-cased. accountId is deliberately dropped: pi derives it from the
|
|
115
|
+
* access-token JWT at request time rather than trusting what is stored, and
|
|
116
|
+
* nax-ai's own credential round-trip already drops it.
|
|
117
|
+
*/
|
|
118
|
+
function fromPiEntry(entry) {
|
|
119
|
+
if (entry.type === "api_key" && typeof entry.key === "string") {
|
|
120
|
+
return { kind: "api-key", key: entry.key };
|
|
121
|
+
}
|
|
122
|
+
if (entry.type === "oauth" &&
|
|
123
|
+
typeof entry.access === "string" &&
|
|
124
|
+
typeof entry.refresh === "string" &&
|
|
125
|
+
typeof entry.expires === "number") {
|
|
126
|
+
return { kind: "oauth", access: entry.access, refresh: entry.refresh, expires: entry.expires };
|
|
127
|
+
}
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Bring pi's credentials across.
|
|
132
|
+
*
|
|
133
|
+
* Existing entries are skipped rather than overwritten: import plausibly runs
|
|
134
|
+
* after a fresh login, and silently replacing a credential just obtained would
|
|
135
|
+
* be the worst kind of quiet data loss.
|
|
136
|
+
*
|
|
137
|
+
* The presence check happens inside modify()'s callback, not before it: that
|
|
138
|
+
* is what holds the store's cross-process lock across the whole
|
|
139
|
+
* read-modify-write. Deciding "already present" from a separate, unlocked
|
|
140
|
+
* read would race a concurrent login completing between the two calls.
|
|
141
|
+
*/
|
|
142
|
+
export async function importPiCredentials(options) {
|
|
143
|
+
const path = options?.from ?? DEFAULT_PI_AUTH_PATH;
|
|
144
|
+
let raw;
|
|
145
|
+
try {
|
|
146
|
+
raw = await readFile(path, "utf8");
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
if (error.code === "ENOENT") {
|
|
150
|
+
throw new NaxError(`No credential file to import at ${path}.`, "AUTH_IMPORT_SOURCE_MISSING", {
|
|
151
|
+
path,
|
|
152
|
+
cause: error,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
throw new NaxError(`The file at ${path} could not be read.`, "AUTH_IMPORT_SOURCE_UNREADABLE", {
|
|
156
|
+
path,
|
|
157
|
+
cause: error,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
let parsed;
|
|
161
|
+
try {
|
|
162
|
+
parsed = JSON.parse(raw);
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
throw new NaxError(`The file at ${path} is not valid JSON.`, "AUTH_IMPORT_SOURCE_UNREADABLE", {
|
|
166
|
+
path,
|
|
167
|
+
cause: error,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
const store = naxCredentialStore();
|
|
171
|
+
const outcomes = [];
|
|
172
|
+
for (const providerId of Object.keys(parsed).sort()) {
|
|
173
|
+
const entry = parsed[providerId];
|
|
174
|
+
const credential = entry === undefined ? undefined : fromPiEntry(entry);
|
|
175
|
+
if (credential === undefined) {
|
|
176
|
+
outcomes.push({ providerId, status: "unsupported" });
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
let status = "imported";
|
|
180
|
+
await store.modify(providerId, async (existing) => {
|
|
181
|
+
if (existing !== undefined && options?.force !== true) {
|
|
182
|
+
status = "skipped";
|
|
183
|
+
return existing;
|
|
184
|
+
}
|
|
185
|
+
return credential;
|
|
186
|
+
});
|
|
187
|
+
outcomes.push({ providerId, status });
|
|
188
|
+
}
|
|
189
|
+
return outcomes;
|
|
190
|
+
}
|
|
191
|
+
export async function listStoredProviders() {
|
|
192
|
+
return readStoredEntries();
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Removal, not revocation. pi has no revocation anywhere — its own types
|
|
196
|
+
* define logout as deletion — so the provider-side token stays live until it
|
|
197
|
+
* expires. Callers must not describe this as logging out.
|
|
198
|
+
*/
|
|
199
|
+
export async function removeStoredProvider(providerId) {
|
|
200
|
+
await naxCredentialStore().delete(providerId);
|
|
201
|
+
}
|
|
202
|
+
export function authImportOutcomeLabel(status) {
|
|
203
|
+
if (status === "imported")
|
|
204
|
+
return "imported";
|
|
205
|
+
if (status === "skipped")
|
|
206
|
+
return "skipped, already present";
|
|
207
|
+
return "unsupported credential type";
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Of these providers, which would ambient auth satisfy on its own?
|
|
211
|
+
*
|
|
212
|
+
* A stored credential owns its provider in pi's resolution order, so any
|
|
213
|
+
* provider named here has a working environment variable that the stored
|
|
214
|
+
* credential is shadowing. This only ever decorates a diagnostic, so a failing
|
|
215
|
+
* probe reports nothing rather than breaking the command around it.
|
|
216
|
+
*/
|
|
217
|
+
export async function ambientShadows(providerIds) {
|
|
218
|
+
const checked = await Promise.all(providerIds.map(async (providerId) => {
|
|
219
|
+
try {
|
|
220
|
+
return (await _authDeps.ambientAuthAvailable(providerId)) ? providerId : undefined;
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
225
|
+
}));
|
|
226
|
+
return checked.filter((id) => id !== undefined);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Of these providers, which have NO credential at all — neither stored nor
|
|
230
|
+
* ambient?
|
|
231
|
+
*
|
|
232
|
+
* Asked for the providers the default native tier map names, so an install
|
|
233
|
+
* whose only credential belongs to another provider fails before a billed call
|
|
234
|
+
* rather than at the first request.
|
|
235
|
+
*
|
|
236
|
+
* What counts as "has a credential": a credential the assembled store serves
|
|
237
|
+
* for that provider, or nax-ai's ambient probe answering true. That probe
|
|
238
|
+
* swallows its own check/resolve errors and answers false, so a provider whose
|
|
239
|
+
* resolution fails IS reported missing — the caller must exclude providers it
|
|
240
|
+
* knows authenticate some other way (catalog overrides). The two cases here
|
|
241
|
+
* that do not guess "no" are the ones this function can see: an unreadable
|
|
242
|
+
* store, and a sweep that outlives AMBIENT_PROBE_TIMEOUT_MS (pi's resolve()
|
|
243
|
+
* "may execute commands") — both report nothing missing.
|
|
244
|
+
*
|
|
245
|
+
* US-004: "has a stored credential" is a read through `naxCredentialStore()`,
|
|
246
|
+
* per provider, rather than a listing of the file. With an exec helper
|
|
247
|
+
* configured only the store knows which source serves a provider, and a helper
|
|
248
|
+
* failure (CREDENTIAL_HELPER_FAILED, CREDENTIAL_HELPER_INVALID) or a refused
|
|
249
|
+
* change (CREDENTIAL_CHANGED) is a run-level fault, not a missing credential —
|
|
250
|
+
* it propagates and refuses the run. The reads also record each provider's
|
|
251
|
+
* baseline (`credential.resolved`) before any story starts, which is what the
|
|
252
|
+
* guard later compares against. They happen before, and outside, the ambient
|
|
253
|
+
* sweep's race below, so a helper slower than the sweep's timeout is still
|
|
254
|
+
* asked — and they are issued as one batch, so N helper-served providers cost
|
|
255
|
+
* the slowest helper rather than the sum of all of them.
|
|
256
|
+
*/
|
|
257
|
+
export async function providersWithoutCredentials(providerIds) {
|
|
258
|
+
const unique = [...new Set(providerIds)];
|
|
259
|
+
const store = naxCredentialStore();
|
|
260
|
+
const stored = new Set();
|
|
261
|
+
// `allSettled`, not `all`: a rejection here is fatal for the run, and `all`
|
|
262
|
+
// would return the moment the first one failed while every sibling read kept
|
|
263
|
+
// going — leaving up to N-1 helper subprocesses alive for their own full
|
|
264
|
+
// timeout, still writing `credential.resolved` entries and guard baselines for
|
|
265
|
+
// a caller that has already thrown. Settling first costs nothing on the happy
|
|
266
|
+
// path (it is one batch either way) and makes the failure path clean.
|
|
267
|
+
// (These reads sit outside the ambient race below on purpose — a helper slower
|
|
268
|
+
// than AMBIENT_PROBE_TIMEOUT_MS is still asked.)
|
|
269
|
+
const served = await Promise.allSettled(unique.map(async (providerId) => ({ providerId, credential: await store.read(providerId) })));
|
|
270
|
+
for (const outcome of served) {
|
|
271
|
+
if (outcome.status === "rejected") {
|
|
272
|
+
// A damaged credential file stays "unknown", which is the behaviour this
|
|
273
|
+
// check has always had: reporting the provider missing would look exactly
|
|
274
|
+
// like "you have no credentials" for a file that is merely unreadable.
|
|
275
|
+
if (outcome.reason instanceof NaxError && outcome.reason.code === "CREDENTIAL_FILE_UNREADABLE")
|
|
276
|
+
return [];
|
|
277
|
+
throw outcome.reason;
|
|
278
|
+
}
|
|
279
|
+
if (outcome.value.credential !== undefined)
|
|
280
|
+
stored.add(outcome.value.providerId);
|
|
281
|
+
}
|
|
282
|
+
const sweep = Promise.all(unique
|
|
283
|
+
.filter((providerId) => !stored.has(providerId))
|
|
284
|
+
.map(async (providerId) => {
|
|
285
|
+
try {
|
|
286
|
+
return (await _authDeps.ambientAuthAvailable(providerId)) ? undefined : providerId;
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
return undefined;
|
|
290
|
+
}
|
|
291
|
+
})).then((missing) => missing.filter((id) => id !== undefined));
|
|
292
|
+
let timer;
|
|
293
|
+
const expiry = new Promise((resolve) => {
|
|
294
|
+
timer = setTimeout(() => resolve([]), AMBIENT_PROBE_TIMEOUT_MS);
|
|
295
|
+
});
|
|
296
|
+
try {
|
|
297
|
+
return await Promise.race([sweep, expiry]);
|
|
298
|
+
}
|
|
299
|
+
finally {
|
|
300
|
+
if (timer !== undefined)
|
|
301
|
+
clearTimeout(timer);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* How long the whole ambient sweep may take before it gives up and reports
|
|
306
|
+
* "credentialed".
|
|
307
|
+
*
|
|
308
|
+
* Measured today the sweep is ~17ms: the catalog is memoised upstream after a
|
|
309
|
+
* ~15ms first load, and 39 ambient probes take ~2ms because no bundled pi
|
|
310
|
+
* provider defines check() and every resolve() reads environment variables and
|
|
311
|
+
* credential files only. That is a snapshot, not a guarantee — pi's own
|
|
312
|
+
* contract warns resolve() "may execute commands", and sweeping the catalog
|
|
313
|
+
* amplifies that across every provider.
|
|
314
|
+
*/
|
|
315
|
+
const AMBIENT_PROBE_TIMEOUT_MS = 2_000;
|
|
316
|
+
/**
|
|
317
|
+
* Is ANY provider satisfied by ambient auth alone?
|
|
318
|
+
*
|
|
319
|
+
* Deliberately not "is provider X satisfied": the caller
|
|
320
|
+
* (NativeAgentAdapter.hasCredentials) has no provider to ask about, because
|
|
321
|
+
* the model — and so the provider — is chosen per request.
|
|
322
|
+
*
|
|
323
|
+
* Expiry resolves TRUE, not false. Pruning an agent that would have worked
|
|
324
|
+
* kills a run; reporting one that cannot authenticate costs a single
|
|
325
|
+
* request-time auth error that is already mapped and handled. Where this
|
|
326
|
+
* cannot answer, it must not guess "no".
|
|
327
|
+
*/
|
|
328
|
+
export async function anyAmbientCredential() {
|
|
329
|
+
let timer;
|
|
330
|
+
const sweep = (async () => {
|
|
331
|
+
const ids = await _authDeps.providerIds();
|
|
332
|
+
if (ids.length === 0)
|
|
333
|
+
return false;
|
|
334
|
+
// Resolve on the first success rather than awaiting every probe, so one
|
|
335
|
+
// satisfied provider does not wait behind a slow one.
|
|
336
|
+
return new Promise((resolve) => {
|
|
337
|
+
let outstanding = ids.length;
|
|
338
|
+
const settleOne = (satisfied) => {
|
|
339
|
+
if (satisfied)
|
|
340
|
+
resolve(true);
|
|
341
|
+
else if (--outstanding === 0)
|
|
342
|
+
resolve(false);
|
|
343
|
+
};
|
|
344
|
+
for (const id of ids) {
|
|
345
|
+
_authDeps.ambientAuthAvailable(id).then((ok) => settleOne(ok),
|
|
346
|
+
// A probe that throws is not a satisfied provider. It is also not a
|
|
347
|
+
// reason to fail the sweep: the other providers still count.
|
|
348
|
+
() => settleOne(false));
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
})();
|
|
352
|
+
const expiry = new Promise((resolve) => {
|
|
353
|
+
timer = _authDeps.setTimeout(() => resolve(true), AMBIENT_PROBE_TIMEOUT_MS);
|
|
354
|
+
});
|
|
355
|
+
try {
|
|
356
|
+
return await Promise.race([sweep, expiry]);
|
|
357
|
+
}
|
|
358
|
+
finally {
|
|
359
|
+
if (timer !== undefined)
|
|
360
|
+
_authDeps.clearTimeout(timer);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The nax-ai client, built once per process.
|
|
3
|
+
*
|
|
4
|
+
* defaultProviders() loads nax-ai's bundled catalog (~1290 models), so building per
|
|
5
|
+
* call would pay that cost on every completion. The build is memoised, but a
|
|
6
|
+
* FAILED build is not: a transient failure must not poison the process.
|
|
7
|
+
*
|
|
8
|
+
* This file and its siblings are the only place in src/ permitted to import
|
|
9
|
+
* nax-ai (packages/repo-tooling/scripts/check-nax-ai-imports.ts).
|
|
10
|
+
*/
|
|
11
|
+
import { type Client, defaultProtocols } from "@nathapp/nax-ai";
|
|
12
|
+
import type { ProviderCatalogOverride } from "#src/config/catalog-overrides";
|
|
13
|
+
/** The catalog overrides a native client is built with (`agent.native.catalogOverrides`). */
|
|
14
|
+
export type NativeCatalogOverrides = readonly ProviderCatalogOverride[];
|
|
15
|
+
/**
|
|
16
|
+
* Options for `buildNativeClient`. Production callers leave this empty —
|
|
17
|
+
* every field has a sensible nax-ai default. The seam exists so tests can
|
|
18
|
+
* opt out of paths the production retry policy was never meant to exercise.
|
|
19
|
+
*/
|
|
20
|
+
export interface BuildNativeClientOptions {
|
|
21
|
+
/**
|
|
22
|
+
* Transport-fault retry budget (nax-ai's `ClientOptions.transportRetries`,
|
|
23
|
+
* default 2). Tests that want a single attempt pass `0`; the production
|
|
24
|
+
* 2-retry schedule sleeps through 250ms + 500ms of backoff on the first
|
|
25
|
+
* thrown "Provider is not configured" / 5xx, which is most of the wall-clock
|
|
26
|
+
* in tests that intentionally fail at the protocol boundary.
|
|
27
|
+
*/
|
|
28
|
+
readonly transportRetries?: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The real builder. Exported on its own — not just as `_clientDeps.build` —
|
|
32
|
+
* because test/preload.ts overwrites `_clientDeps.build` with a sentinel
|
|
33
|
+
* before any test file loads (to stop a real client leaking into the
|
|
34
|
+
* module-level cache across files), which would otherwise make this
|
|
35
|
+
* synchronous, no-network construction path uncoverable by any test.
|
|
36
|
+
*/
|
|
37
|
+
export declare function buildNativeClient(catalogOverrides?: readonly ProviderCatalogOverride[], options?: BuildNativeClientOptions): Promise<Client>;
|
|
38
|
+
/**
|
|
39
|
+
* Test seam. `build` is replaced in tests so no catalog is loaded and no
|
|
40
|
+
* network is reached. `defaultProtocols` is separately injectable so a test can
|
|
41
|
+
* observe what buildNativeClient passes it (the credentials wiring above)
|
|
42
|
+
* without loading the real catalog — see client.test.ts.
|
|
43
|
+
*/
|
|
44
|
+
export declare const _clientDeps: {
|
|
45
|
+
build: typeof buildNativeClient;
|
|
46
|
+
defaultProtocols: typeof defaultProtocols;
|
|
47
|
+
};
|
|
48
|
+
export declare function getNativeClient(catalogOverrides?: NativeCatalogOverrides): Promise<Client>;
|
|
49
|
+
/** Clears the memo. Tests only. */
|
|
50
|
+
export declare function _resetNativeClient(): void;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The nax-ai client, built once per process.
|
|
3
|
+
*
|
|
4
|
+
* defaultProviders() loads nax-ai's bundled catalog (~1290 models), so building per
|
|
5
|
+
* call would pay that cost on every completion. The build is memoised, but a
|
|
6
|
+
* FAILED build is not: a transient failure must not poison the process.
|
|
7
|
+
*
|
|
8
|
+
* This file and its siblings are the only place in src/ permitted to import
|
|
9
|
+
* nax-ai (packages/repo-tooling/scripts/check-nax-ai-imports.ts).
|
|
10
|
+
*/
|
|
11
|
+
import { createHash } from "node:crypto";
|
|
12
|
+
import { createClient, defaultProtocols, defaultProviders } from "@nathapp/nax-ai";
|
|
13
|
+
import { NaxError } from "#src/infra/index";
|
|
14
|
+
import { byCodePoint } from "#src/internal/sort";
|
|
15
|
+
import { naxCredentialStore } from "./credentials/index.js";
|
|
16
|
+
import { toProviderOverrides } from "./models.js";
|
|
17
|
+
/**
|
|
18
|
+
* How nax names itself to a provider that reports on the calling application.
|
|
19
|
+
*
|
|
20
|
+
* nax-ai owns which vendor spells this in which header — OpenRouter reads
|
|
21
|
+
* `HTTP-Referer` and `X-Title` into the `app_id`, `origin` and `http_referer`
|
|
22
|
+
* fields of every generation record — and nax owns only the identity. Without
|
|
23
|
+
* it, the sole identifying header on the wire is pi-ai's hardcoded
|
|
24
|
+
* `User-Agent: pi (<platform> <release>; <arch>)`, which every pi-ai consumer
|
|
25
|
+
* sends, so nax traffic cannot be told apart from anything else in the provider
|
|
26
|
+
* account paying for it.
|
|
27
|
+
*
|
|
28
|
+
* A literal rather than a read of package.json: the bundle is built with
|
|
29
|
+
* `bun build --target bun` and does not carry a package.json to read at
|
|
30
|
+
* runtime. No version in the name — a display name that changed every release
|
|
31
|
+
* would split one application into many rows on a provider's dashboard.
|
|
32
|
+
*/
|
|
33
|
+
const NAX_CLIENT_APP = { name: "nax", url: "https://github.com/nathapp-io/nax" };
|
|
34
|
+
/**
|
|
35
|
+
* The real builder. Exported on its own — not just as `_clientDeps.build` —
|
|
36
|
+
* because test/preload.ts overwrites `_clientDeps.build` with a sentinel
|
|
37
|
+
* before any test file loads (to stop a real client leaking into the
|
|
38
|
+
* module-level cache across files), which would otherwise make this
|
|
39
|
+
* synchronous, no-network construction path uncoverable by any test.
|
|
40
|
+
*/
|
|
41
|
+
export async function buildNativeClient(catalogOverrides = [], options = {}) {
|
|
42
|
+
return createClient({
|
|
43
|
+
providers: await defaultProviders(),
|
|
44
|
+
// The factory form, not the entries form: nax-ai has TWO catalogs — the
|
|
45
|
+
// client's, which `model()` and `pricing()` read, and the protocol layer's,
|
|
46
|
+
// which request-time resolution reads — and an override has to reach both.
|
|
47
|
+
// Entries built without it reproduce #1982 exactly: the model resolves and
|
|
48
|
+
// prices happily, then throws `Unknown model ... in the pi-ai catalog` on
|
|
49
|
+
// the first real request. createClient hands the factory the array it was
|
|
50
|
+
// constructed with, so the two sides cannot disagree — and nax-ai 0.1.11
|
|
51
|
+
// rejects at construction a client whose entries never heard about an
|
|
52
|
+
// override the client itself declared (nax-ai#36).
|
|
53
|
+
protocols: ({ providerOverrides }) => _clientDeps.defaultProtocols({
|
|
54
|
+
// The credential seam: pi resolves the store first, then ambient sources
|
|
55
|
+
// (env vars, AWS profiles, ADC), so a stored credential owns its provider
|
|
56
|
+
// and CI with only an environment variable keeps working. Passing it here
|
|
57
|
+
// is what makes `nax auth login` reach a run. This is the only inlet:
|
|
58
|
+
// ClientOptions once carried a `credentials` field that createClient
|
|
59
|
+
// never read, and nax-ai 0.1.4 removed it for exactly that reason.
|
|
60
|
+
credentials: naxCredentialStore(),
|
|
61
|
+
// Construction-time, like `credentials`: the identity is a constant of
|
|
62
|
+
// the process, so nax-ai takes it here rather than on every request.
|
|
63
|
+
clientApp: NAX_CLIENT_APP,
|
|
64
|
+
providerOverrides,
|
|
65
|
+
}),
|
|
66
|
+
// nax-ai applies these last (`normaliseCatalog`), replacing any bundled
|
|
67
|
+
// entry with the same id and lazily creating the provider bucket when the
|
|
68
|
+
// id is unknown to pi-ai — that is what makes a model newer than the
|
|
69
|
+
// snapshot resolvable (#1982). Omitted entirely when empty so the
|
|
70
|
+
// no-override path stays byte-identical to before.
|
|
71
|
+
...(catalogOverrides.length > 0 ? { providerOverrides: toProviderOverrides(catalogOverrides) } : {}),
|
|
72
|
+
// nax-ai defaults `transportRetries` to 2 (250ms + 500ms backoff).
|
|
73
|
+
// Passed through verbatim when the caller asks, omitted when it doesn't,
|
|
74
|
+
// so the production path stays byte-identical to before this seam.
|
|
75
|
+
...(options.transportRetries !== undefined ? { transportRetries: options.transportRetries } : {}),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Test seam. `build` is replaced in tests so no catalog is loaded and no
|
|
80
|
+
* network is reached. `defaultProtocols` is separately injectable so a test can
|
|
81
|
+
* observe what buildNativeClient passes it (the credentials wiring above)
|
|
82
|
+
* without loading the real catalog — see client.test.ts.
|
|
83
|
+
*/
|
|
84
|
+
export const _clientDeps = {
|
|
85
|
+
build: buildNativeClient,
|
|
86
|
+
defaultProtocols,
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Recursively rebuild plain objects with keys sorted by code point. Array
|
|
90
|
+
* order is preserved — model lists keep their declared order because nax-ai
|
|
91
|
+
* replaces entries by id and a duplicate id is last-wins. Built with
|
|
92
|
+
* `Object.fromEntries` (not key assignment) so a header literally named
|
|
93
|
+
* `__proto__` survives as an own property instead of being swallowed by the
|
|
94
|
+
* prototype setter.
|
|
95
|
+
*/
|
|
96
|
+
function canonicalise(value) {
|
|
97
|
+
if (Array.isArray(value))
|
|
98
|
+
return value.map((item) => canonicalise(item));
|
|
99
|
+
if (value !== null && typeof value === "object") {
|
|
100
|
+
return Object.fromEntries(Object.entries(value)
|
|
101
|
+
.sort(([a], [b]) => byCodePoint(a, b))
|
|
102
|
+
.map(([key, val]) => [key, canonicalise(val)]));
|
|
103
|
+
}
|
|
104
|
+
return value;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Deterministic serialisation of an override set (nax#2025): overrides sorted
|
|
108
|
+
* by provider and object keys sorted by code point (`byCodePoint`, per CTX-5 —
|
|
109
|
+
* the ordering feeds both the cache key and the digest, so it must be stable
|
|
110
|
+
* across locales and ICU versions), so two sets that differ only in listing
|
|
111
|
+
* order or header key order — semantically identical on the nax-ai side —
|
|
112
|
+
* serialise identically. Two overrides with the SAME provider keep their
|
|
113
|
+
* declared order: duplicate providers are last-wins by design, so their
|
|
114
|
+
* relative order is part of the semantics.
|
|
115
|
+
*/
|
|
116
|
+
function canonicalOverrideKey(overrides) {
|
|
117
|
+
const sorted = [...overrides].sort((a, b) => byCodePoint(a.provider, b.provider));
|
|
118
|
+
return JSON.stringify(canonicalise(sorted));
|
|
119
|
+
}
|
|
120
|
+
/** Hex prefix length of the set digest used in error summaries. */
|
|
121
|
+
const DIGEST_LENGTH = 12;
|
|
122
|
+
/**
|
|
123
|
+
* Secret-free summary of an override set for error context: provider names,
|
|
124
|
+
* header key NAMES (never values — an `Authorization` header is a credential,
|
|
125
|
+
* nax#2025), and a digest of the canonical key so sets that differ only in
|
|
126
|
+
* redacted material stay distinguishable.
|
|
127
|
+
*
|
|
128
|
+
* The digest is an IDENTIFIER, not a security boundary: it hashes the raw
|
|
129
|
+
* canonical key, so it must only ever appear next to the sanitised summary —
|
|
130
|
+
* never logged beside the redacted-but-inferable override set.
|
|
131
|
+
*/
|
|
132
|
+
function summariseOverrides(overrides) {
|
|
133
|
+
return {
|
|
134
|
+
providers: overrides.map((o) => o.provider).sort(),
|
|
135
|
+
headerKeys: [...new Set(overrides.flatMap((o) => Object.keys(o.headers ?? {})))].sort(),
|
|
136
|
+
digest: createHash("sha256").update(canonicalOverrideKey(overrides)).digest("hex").slice(0, DIGEST_LENGTH),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
let cached;
|
|
140
|
+
/** Override set the cached build was created for — kept for secret-free summarising. */
|
|
141
|
+
let cachedOverrides;
|
|
142
|
+
/** Serialised override set the cached build was created for. */
|
|
143
|
+
let cachedOverridesKey;
|
|
144
|
+
export async function getNativeClient(catalogOverrides = []) {
|
|
145
|
+
const overridesKey = canonicalOverrideKey(catalogOverrides);
|
|
146
|
+
if (cached !== undefined && overridesKey !== cachedOverridesKey) {
|
|
147
|
+
// The client is a constant of the process (catalog load is ~50ms / ~650KB),
|
|
148
|
+
// so overrides must be collected into ONE set before the first build. A
|
|
149
|
+
// silent second build would swap the client under in-flight sessions.
|
|
150
|
+
throw new NaxError("The native client was already built for a different catalog-override set. " +
|
|
151
|
+
"Collect every override into one agent.native.catalogOverrides list instead of varying them per call.", "NATIVE_CLIENT_OVERRIDES_MISMATCH", { builtFor: summariseOverrides(cachedOverrides ?? []), requested: summariseOverrides(catalogOverrides) });
|
|
152
|
+
}
|
|
153
|
+
if (cached === undefined) {
|
|
154
|
+
// Shallow copy: the caller's array may later be mutated (e.g. a push), which
|
|
155
|
+
// would mislabel `builtFor` — the summary must describe the set that was built.
|
|
156
|
+
cachedOverrides = [...catalogOverrides];
|
|
157
|
+
cachedOverridesKey = overridesKey;
|
|
158
|
+
// Cache the promise, not the value, so concurrent callers share one build.
|
|
159
|
+
// Drop it on rejection: a failed catalog load should not be permanent.
|
|
160
|
+
cached = _clientDeps.build(catalogOverrides).catch((err) => {
|
|
161
|
+
cached = undefined;
|
|
162
|
+
cachedOverrides = undefined;
|
|
163
|
+
cachedOverridesKey = undefined;
|
|
164
|
+
throw err;
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
return cached;
|
|
168
|
+
}
|
|
169
|
+
/** Clears the memo. Tests only. */
|
|
170
|
+
export function _resetNativeClient() {
|
|
171
|
+
cached = undefined;
|
|
172
|
+
cachedOverrides = undefined;
|
|
173
|
+
cachedOverridesKey = undefined;
|
|
174
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-shot completion over nax-ai, with no session (S1 port 2). Package-owned
|
|
3
|
+
* options: the caller passes a `SessionModel` whose pricing is already in the
|
|
4
|
+
* standard vocabulary, and the per-adapter one-shot session key.
|
|
5
|
+
*/
|
|
6
|
+
import type { AdapterFailure } from "#src/session/adapter-failure";
|
|
7
|
+
import type { AuthStamp, SessionModel } from "#src/session/session-types";
|
|
8
|
+
import type { PricingRates, TokenUsage } from "../cost/standard-types.ts";
|
|
9
|
+
import { type NativeCatalogOverrides } from "./client.ts";
|
|
10
|
+
export interface NativeCompleteOptions {
|
|
11
|
+
readonly model: SessionModel;
|
|
12
|
+
readonly maxTokens?: number;
|
|
13
|
+
readonly timeoutMs?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface NativeCompleteContext {
|
|
16
|
+
readonly catalogOverrides: NativeCatalogOverrides;
|
|
17
|
+
/**
|
|
18
|
+
* The caller's one-shot session key (`newSessionKey()`), held per adapter
|
|
19
|
+
* instance so a run's one-shots share a backend and keep a cache warm.
|
|
20
|
+
*/
|
|
21
|
+
readonly sessionKey: string;
|
|
22
|
+
}
|
|
23
|
+
export interface NativeCompleteResult {
|
|
24
|
+
output: string;
|
|
25
|
+
tokenUsage: TokenUsage;
|
|
26
|
+
estimatedCostUsd: number;
|
|
27
|
+
sessionId?: string;
|
|
28
|
+
pricingSource?: "catalog-rates" | "config-override";
|
|
29
|
+
rates?: PricingRates;
|
|
30
|
+
auth?: AuthStamp;
|
|
31
|
+
adapterFailure?: AdapterFailure;
|
|
32
|
+
}
|
|
33
|
+
export declare function nativeComplete(prompt: string, options: NativeCompleteOptions, context: NativeCompleteContext): Promise<NativeCompleteResult>;
|