@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,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model reference, usage and cost for the native path.
|
|
3
|
+
*
|
|
4
|
+
* The provider travels inside the model string, not beside it: a multi-provider
|
|
5
|
+
* agent needs it there, and opencode's entries already encode it that way
|
|
6
|
+
* (ADR-027 section 1). Under acpx the same string stays opaque.
|
|
7
|
+
*/
|
|
8
|
+
import type { Pricing, ProviderOverride, ThinkingLevel } from "@nathapp/nax-ai";
|
|
9
|
+
import type { ProviderCatalogOverride } from "#src/config/catalog-overrides";
|
|
10
|
+
import { parseModelSpec } from "#src/cost/model-spec";
|
|
11
|
+
/** The one agent name that routes to this transport. Defined once in the
|
|
12
|
+
* `@/config/native-agent` leaf (config validates against it); re-exported
|
|
13
|
+
* here, not in the barrel, so session-adapter.ts can import it without an
|
|
14
|
+
* index -> session-adapter -> index cycle. */
|
|
15
|
+
export { NATIVE_AGENT_NAME as NATIVE_AGENT } from "#src/config/native-agent/index";
|
|
16
|
+
export { parseModelSpec };
|
|
17
|
+
export interface NativeModelRef {
|
|
18
|
+
readonly provider: string;
|
|
19
|
+
readonly model: string;
|
|
20
|
+
/** Reasoning-effort suffix, when the config string carried one. Free-form —
|
|
21
|
+
* validate with `toThinkingLevel` before sending it on the wire. */
|
|
22
|
+
readonly effort?: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Strip the nax-level `[effort]` suffix FIRST, then split on the FIRST slash: a
|
|
26
|
+
* provider id never contains one, a model id often does
|
|
27
|
+
* (`huggingface/MiniMaxAI/MiniMax-M2.7`). Order matters — the suffix is
|
|
28
|
+
* trailing, so splitting on the slash before stripping it would leave the
|
|
29
|
+
* suffix glued onto the model id (`"claude-opus-5[high]"`) and
|
|
30
|
+
* `client.model()` would throw its own unknown-model error instead of this
|
|
31
|
+
* function's clearer one.
|
|
32
|
+
*
|
|
33
|
+
* A suffix with no slash (`"claude-opus-5[high]"`) still fails malformed-model
|
|
34
|
+
* validation: `parseModelSpec` only removes the suffix, it does not supply a
|
|
35
|
+
* missing provider.
|
|
36
|
+
*/
|
|
37
|
+
export declare function parseNativeModel(raw: string): NativeModelRef;
|
|
38
|
+
/**
|
|
39
|
+
* Every level nax-ai's `ThinkingLevel` union admits, as an exhaustive mapped
|
|
40
|
+
* type rather than a hand-rolled string array. `Record<ThinkingLevel, true>`
|
|
41
|
+
* only type-checks when every union member has an entry, so if nax-ai adds a
|
|
42
|
+
* level upstream, this fails to COMPILE — a real gate, not a comment that can
|
|
43
|
+
* silently drift out of sync with the union it mirrors.
|
|
44
|
+
*/
|
|
45
|
+
export declare const THINKING_LEVELS: Record<ThinkingLevel, true>;
|
|
46
|
+
/**
|
|
47
|
+
* Translate nax's config-side "effort" suffix into nax-ai's request-side
|
|
48
|
+
* "thinking" field. Kept in this one place, named for the seam it crosses: nax
|
|
49
|
+
* calls the concept "effort", nax-ai calls the identical value "thinking".
|
|
50
|
+
*
|
|
51
|
+
* The suffix is a free-form string (see model-spec.ts), so a value outside
|
|
52
|
+
* nax-ai's ThinkingLevel union is possible from a typo'd profile. This
|
|
53
|
+
* mirrors applyReasoningEffort's stance in
|
|
54
|
+
* src/agents/acp/reasoning-effort.ts: "Best-effort: a failure leaves the
|
|
55
|
+
* session at the adapter default rather than failing the whole run. The
|
|
56
|
+
* warning is what keeps that downgrade visible." nax does not clamp against
|
|
57
|
+
* per-model capabilities here — that is nax-ai's clampThinkingLevel's job,
|
|
58
|
+
* run inside client.complete against the resolved model's own
|
|
59
|
+
* thinkingLevels.
|
|
60
|
+
*/
|
|
61
|
+
export declare function toThinkingLevel(effort: string | undefined): ThinkingLevel | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* Translate nax's config-side catalog overrides (agent.native.catalogOverrides,
|
|
64
|
+
* nax#1982) into nax-ai's declaration-data `ProviderOverride` records.
|
|
65
|
+
*
|
|
66
|
+
* The override is a COMPLETE entry, not a patch: nax-ai's `normaliseCatalog`
|
|
67
|
+
* applies it last through `setModel`, which replaces any same-id entry and
|
|
68
|
+
* lazily creates the provider bucket — that is what makes an id absent from
|
|
69
|
+
* the bundled pi-ai snapshot resolvable (verified against nax-ai 0.1.10).
|
|
70
|
+
* `provider` is stamped from the outer record because nax-ai's `ResolvedModel`
|
|
71
|
+
* carries it per model.
|
|
72
|
+
*/
|
|
73
|
+
export declare function toProviderOverrides(overrides: readonly ProviderCatalogOverride[]): ProviderOverride[];
|
|
74
|
+
/**
|
|
75
|
+
* `estimateCostUsd` (and the tier-selection helper it relies on) was
|
|
76
|
+
* relocated to `src/agents/cost/` (US-001) so the ACP and native paths share
|
|
77
|
+
* one implementation. Native callers now import it from `@/agents/cost`.
|
|
78
|
+
*/
|
|
79
|
+
/**
|
|
80
|
+
* Turn nax-ai's catalog `Pricing` into the rate object `priceCall` prices from
|
|
81
|
+
* (nax#1843, nax#1847). Both call sites -- `nativeComplete()` (complete.ts)
|
|
82
|
+
* and `NativeSessionAdapter.sendTurn()` (session-adapter.ts) -- build the rate
|
|
83
|
+
* object this way, and both receive the override already converted by
|
|
84
|
+
* `toSessionModel`, so the fix cannot drift between them.
|
|
85
|
+
*
|
|
86
|
+
* An explicit `modelDef.pricing` override wins WHOLESALE: the caller converts
|
|
87
|
+
* the config override with `toPricing` (via `toSessionModel`), which fills
|
|
88
|
+
* the override's own missing cache rates from its input rate, but catalog
|
|
89
|
+
* values are never merged into an override -- that would silently rewrite
|
|
90
|
+
* rates the user configured on purpose.
|
|
91
|
+
*
|
|
92
|
+
* US-003 (#1817): reports which branch it took alongside the card, so the
|
|
93
|
+
* adapter can stamp `pricingSource` on the result without re-deriving the
|
|
94
|
+
* rate-card branch (US-003 retired the table-backed `MODEL_PRICING` lookup
|
|
95
|
+
* this resolver was the successor to). The card is the rate object; the
|
|
96
|
+
* source is the answer to "which one did we use".
|
|
97
|
+
*/
|
|
98
|
+
export declare function buildRateCard(catalog: Pricing, override: Pricing | undefined): {
|
|
99
|
+
rates: Pricing;
|
|
100
|
+
source: "config-override" | "catalog-rates";
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Resolve the context window `runNativeTurn` compacts against: an explicit
|
|
104
|
+
* `ModelDef.contextWindow` override, falling back to nax-ai's
|
|
105
|
+
* `ResolvedModel.contextWindow` (nax#1848). Same override-then-fallback shape
|
|
106
|
+
* as `buildRateCard`, but with a direction guard that has no pricing
|
|
107
|
+
* equivalent: the window never reaches the provider, so it feeds only
|
|
108
|
+
* `shouldCompact` / `keepBudget` (`session/turn-loop.ts`). Lowering it is
|
|
109
|
+
* therefore safe -- compaction just fires earlier against an otherwise real
|
|
110
|
+
* request. Raising it above the real window would defeat compaction and
|
|
111
|
+
* reintroduce the overflow it exists to prevent, so that direction is
|
|
112
|
+
* rejected outright rather than clamped: this is a deliberate testing / cost
|
|
113
|
+
* lever, not a value set by accident, and a wrong value should be loud. An
|
|
114
|
+
* override equal to the real window is accepted -- it changes nothing.
|
|
115
|
+
*/
|
|
116
|
+
export declare function resolveContextWindow(override: number | undefined, realWindow: number): number;
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model reference, usage and cost for the native path.
|
|
3
|
+
*
|
|
4
|
+
* The provider travels inside the model string, not beside it: a multi-provider
|
|
5
|
+
* agent needs it there, and opencode's entries already encode it that way
|
|
6
|
+
* (ADR-027 section 1). Under acpx the same string stays opaque.
|
|
7
|
+
*/
|
|
8
|
+
import { parseModelSpec } from "#src/cost/model-spec";
|
|
9
|
+
import { getSafeLogger, NaxError } from "#src/infra/index";
|
|
10
|
+
/** The one agent name that routes to this transport. Defined once in the
|
|
11
|
+
* `@/config/native-agent` leaf (config validates against it); re-exported
|
|
12
|
+
* here, not in the barrel, so session-adapter.ts can import it without an
|
|
13
|
+
* index -> session-adapter -> index cycle. */
|
|
14
|
+
export { NATIVE_AGENT_NAME as NATIVE_AGENT } from "#src/config/native-agent/index";
|
|
15
|
+
export { parseModelSpec };
|
|
16
|
+
/**
|
|
17
|
+
* Strip the nax-level `[effort]` suffix FIRST, then split on the FIRST slash: a
|
|
18
|
+
* provider id never contains one, a model id often does
|
|
19
|
+
* (`huggingface/MiniMaxAI/MiniMax-M2.7`). Order matters — the suffix is
|
|
20
|
+
* trailing, so splitting on the slash before stripping it would leave the
|
|
21
|
+
* suffix glued onto the model id (`"claude-opus-5[high]"`) and
|
|
22
|
+
* `client.model()` would throw its own unknown-model error instead of this
|
|
23
|
+
* function's clearer one.
|
|
24
|
+
*
|
|
25
|
+
* A suffix with no slash (`"claude-opus-5[high]"`) still fails malformed-model
|
|
26
|
+
* validation: `parseModelSpec` only removes the suffix, it does not supply a
|
|
27
|
+
* missing provider.
|
|
28
|
+
*/
|
|
29
|
+
export function parseNativeModel(raw) {
|
|
30
|
+
const { model: withoutSuffix, effort } = parseModelSpec(raw);
|
|
31
|
+
const slash = withoutSuffix.indexOf("/");
|
|
32
|
+
const provider = slash === -1 ? "" : withoutSuffix.slice(0, slash);
|
|
33
|
+
const model = slash === -1 ? "" : withoutSuffix.slice(slash + 1);
|
|
34
|
+
if (provider === "" || model === "") {
|
|
35
|
+
throw new NaxError(
|
|
36
|
+
// Naming the sibling field is the point (nax#1851): a reader looking at
|
|
37
|
+
// `{ provider: "anthropic", model: "claude-sonnet-5" }` assumes the field
|
|
38
|
+
// right there is the one being used, and the old message said nothing to
|
|
39
|
+
// correct that. No suggested id is composed from it — on this path the
|
|
40
|
+
// value reaching us may be `resolveModel`'s inference rather than
|
|
41
|
+
// anything configured, and a guessed suggestion is worse than none.
|
|
42
|
+
`Native model "${raw}" must be written "provider/model" (e.g. "openai/gpt-5.4-mini"). There is no default provider. A "provider" field beside "model" in config is NOT used on the native path — the provider belongs in the model id itself.`, "NATIVE_MODEL_MALFORMED", { stage: "complete", model: raw });
|
|
43
|
+
}
|
|
44
|
+
return { provider, model, ...(effort !== undefined ? { effort } : {}) };
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Every level nax-ai's `ThinkingLevel` union admits, as an exhaustive mapped
|
|
48
|
+
* type rather than a hand-rolled string array. `Record<ThinkingLevel, true>`
|
|
49
|
+
* only type-checks when every union member has an entry, so if nax-ai adds a
|
|
50
|
+
* level upstream, this fails to COMPILE — a real gate, not a comment that can
|
|
51
|
+
* silently drift out of sync with the union it mirrors.
|
|
52
|
+
*/
|
|
53
|
+
export const THINKING_LEVELS = {
|
|
54
|
+
off: true,
|
|
55
|
+
minimal: true,
|
|
56
|
+
low: true,
|
|
57
|
+
medium: true,
|
|
58
|
+
high: true,
|
|
59
|
+
xhigh: true,
|
|
60
|
+
max: true,
|
|
61
|
+
};
|
|
62
|
+
function isThinkingLevel(value) {
|
|
63
|
+
return Object.hasOwn(THINKING_LEVELS, value);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Translate nax's config-side "effort" suffix into nax-ai's request-side
|
|
67
|
+
* "thinking" field. Kept in this one place, named for the seam it crosses: nax
|
|
68
|
+
* calls the concept "effort", nax-ai calls the identical value "thinking".
|
|
69
|
+
*
|
|
70
|
+
* The suffix is a free-form string (see model-spec.ts), so a value outside
|
|
71
|
+
* nax-ai's ThinkingLevel union is possible from a typo'd profile. This
|
|
72
|
+
* mirrors applyReasoningEffort's stance in
|
|
73
|
+
* src/agents/acp/reasoning-effort.ts: "Best-effort: a failure leaves the
|
|
74
|
+
* session at the adapter default rather than failing the whole run. The
|
|
75
|
+
* warning is what keeps that downgrade visible." nax does not clamp against
|
|
76
|
+
* per-model capabilities here — that is nax-ai's clampThinkingLevel's job,
|
|
77
|
+
* run inside client.complete against the resolved model's own
|
|
78
|
+
* thinkingLevels.
|
|
79
|
+
*/
|
|
80
|
+
export function toThinkingLevel(effort) {
|
|
81
|
+
if (effort === undefined)
|
|
82
|
+
return undefined;
|
|
83
|
+
if (isThinkingLevel(effort))
|
|
84
|
+
return effort;
|
|
85
|
+
getSafeLogger()?.warn("native-adapter", `Unknown effort "${effort}"; continuing at provider default`, { effort });
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Translate nax's config-side catalog overrides (agent.native.catalogOverrides,
|
|
90
|
+
* nax#1982) into nax-ai's declaration-data `ProviderOverride` records.
|
|
91
|
+
*
|
|
92
|
+
* The override is a COMPLETE entry, not a patch: nax-ai's `normaliseCatalog`
|
|
93
|
+
* applies it last through `setModel`, which replaces any same-id entry and
|
|
94
|
+
* lazily creates the provider bucket — that is what makes an id absent from
|
|
95
|
+
* the bundled pi-ai snapshot resolvable (verified against nax-ai 0.1.10).
|
|
96
|
+
* `provider` is stamped from the outer record because nax-ai's `ResolvedModel`
|
|
97
|
+
* carries it per model.
|
|
98
|
+
*/
|
|
99
|
+
export function toProviderOverrides(overrides) {
|
|
100
|
+
return overrides.map((override) => ({
|
|
101
|
+
provider: override.provider,
|
|
102
|
+
// Omitted when undeclared, never passed as `undefined`: nax-ai reads
|
|
103
|
+
// `!== undefined` as a declaration and checks it against the protocol
|
|
104
|
+
// entries (nax#2019).
|
|
105
|
+
...(override.baseUrl !== undefined ? { baseUrl: override.baseUrl } : {}),
|
|
106
|
+
// Copied, not aliased: nax-ai's client catalog assigns this reference
|
|
107
|
+
// straight onto ResolvedProvider.headers (providers/catalog.ts) without a
|
|
108
|
+
// copy of its own, so forwarding the live config object would let a later
|
|
109
|
+
// mutation reach an already-built client.
|
|
110
|
+
...(override.headers !== undefined ? { headers: { ...override.headers } } : {}),
|
|
111
|
+
models: override.models.map((model) => ({
|
|
112
|
+
id: model.id,
|
|
113
|
+
provider: override.provider,
|
|
114
|
+
protocol: model.protocol,
|
|
115
|
+
pricing: model.pricing,
|
|
116
|
+
contextWindow: model.contextWindow,
|
|
117
|
+
// Omitted when the override declares none: nax-ai then falls back to the
|
|
118
|
+
// template sibling's ceiling, which is the only honest value available.
|
|
119
|
+
...(model.maxTokens !== undefined ? { maxTokens: model.maxTokens } : {}),
|
|
120
|
+
supportsTools: model.supportsTools,
|
|
121
|
+
thinkingLevels: model.thinkingLevels,
|
|
122
|
+
// nax#2191: forwarded verbatim. nax-ai's `assertOverrideModelRouting`
|
|
123
|
+
// rejects an empty `{}` (pi's check is truthiness, not emptiness — issue
|
|
124
|
+
// #43), so omission is the only safe absent shape. The protocol-side
|
|
125
|
+
// rejection on a non-`openai-completions` model is nax-ai's; nax's
|
|
126
|
+
// schema refuses an empty declaration at config load so the user gets
|
|
127
|
+
// the error there rather than on first dispatch.
|
|
128
|
+
...(model.openRouterRouting !== undefined ? { openRouterRouting: model.openRouterRouting } : {}),
|
|
129
|
+
})),
|
|
130
|
+
}));
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* `estimateCostUsd` (and the tier-selection helper it relies on) was
|
|
134
|
+
* relocated to `src/agents/cost/` (US-001) so the ACP and native paths share
|
|
135
|
+
* one implementation. Native callers now import it from `@/agents/cost`.
|
|
136
|
+
*/
|
|
137
|
+
/**
|
|
138
|
+
* Turn nax-ai's catalog `Pricing` into the rate object `priceCall` prices from
|
|
139
|
+
* (nax#1843, nax#1847). Both call sites -- `nativeComplete()` (complete.ts)
|
|
140
|
+
* and `NativeSessionAdapter.sendTurn()` (session-adapter.ts) -- build the rate
|
|
141
|
+
* object this way, and both receive the override already converted by
|
|
142
|
+
* `toSessionModel`, so the fix cannot drift between them.
|
|
143
|
+
*
|
|
144
|
+
* An explicit `modelDef.pricing` override wins WHOLESALE: the caller converts
|
|
145
|
+
* the config override with `toPricing` (via `toSessionModel`), which fills
|
|
146
|
+
* the override's own missing cache rates from its input rate, but catalog
|
|
147
|
+
* values are never merged into an override -- that would silently rewrite
|
|
148
|
+
* rates the user configured on purpose.
|
|
149
|
+
*
|
|
150
|
+
* US-003 (#1817): reports which branch it took alongside the card, so the
|
|
151
|
+
* adapter can stamp `pricingSource` on the result without re-deriving the
|
|
152
|
+
* rate-card branch (US-003 retired the table-backed `MODEL_PRICING` lookup
|
|
153
|
+
* this resolver was the successor to). The card is the rate object; the
|
|
154
|
+
* source is the answer to "which one did we use".
|
|
155
|
+
*/
|
|
156
|
+
export function buildRateCard(catalog, override) {
|
|
157
|
+
if (override !== undefined)
|
|
158
|
+
return { rates: override, source: "config-override" };
|
|
159
|
+
return { rates: catalog, source: "catalog-rates" };
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Resolve the context window `runNativeTurn` compacts against: an explicit
|
|
163
|
+
* `ModelDef.contextWindow` override, falling back to nax-ai's
|
|
164
|
+
* `ResolvedModel.contextWindow` (nax#1848). Same override-then-fallback shape
|
|
165
|
+
* as `buildRateCard`, but with a direction guard that has no pricing
|
|
166
|
+
* equivalent: the window never reaches the provider, so it feeds only
|
|
167
|
+
* `shouldCompact` / `keepBudget` (`session/turn-loop.ts`). Lowering it is
|
|
168
|
+
* therefore safe -- compaction just fires earlier against an otherwise real
|
|
169
|
+
* request. Raising it above the real window would defeat compaction and
|
|
170
|
+
* reintroduce the overflow it exists to prevent, so that direction is
|
|
171
|
+
* rejected outright rather than clamped: this is a deliberate testing / cost
|
|
172
|
+
* lever, not a value set by accident, and a wrong value should be loud. An
|
|
173
|
+
* override equal to the real window is accepted -- it changes nothing.
|
|
174
|
+
*/
|
|
175
|
+
export function resolveContextWindow(override, realWindow) {
|
|
176
|
+
if (override === undefined)
|
|
177
|
+
return realWindow;
|
|
178
|
+
if (override > realWindow) {
|
|
179
|
+
throw new NaxError(`configured contextWindow (${override}) exceeds the model's real context window (${realWindow}); ` +
|
|
180
|
+
"raising it above the real window would defeat compaction and risk a provider overflow", "CONTEXT_WINDOW_OVERRIDE_EXCEEDS_REAL_WINDOW", { configuredContextWindow: override, realContextWindow: realWindow });
|
|
181
|
+
}
|
|
182
|
+
return override;
|
|
183
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The native human-Q&A channel.
|
|
3
|
+
*
|
|
4
|
+
* `AdapterInteraction` has always declared `{ kind: "question" }`, but the
|
|
5
|
+
* native loop routed only context-tools and coding-tools, so on native the
|
|
6
|
+
* operator could never be asked anything — while `agent.maxInteractionTurns`,
|
|
7
|
+
* the budget that names exactly this, was being spent as a round-trip cap.
|
|
8
|
+
*
|
|
9
|
+
* A declared tool rather than acpx's output parsing (acp/adapter.ts): the
|
|
10
|
+
* native protocol has a structured tool channel, and parsing prose for a
|
|
11
|
+
* question when a structured call is available is the weaker mechanism.
|
|
12
|
+
*/
|
|
13
|
+
import type { ToolDefinition } from "@nathapp/nax-ai";
|
|
14
|
+
export declare const ASK_HUMAN_TOOL_NAME = "ask_human";
|
|
15
|
+
export declare const askHumanToolDefinition: ToolDefinition;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The native human-Q&A channel.
|
|
3
|
+
*
|
|
4
|
+
* `AdapterInteraction` has always declared `{ kind: "question" }`, but the
|
|
5
|
+
* native loop routed only context-tools and coding-tools, so on native the
|
|
6
|
+
* operator could never be asked anything — while `agent.maxInteractionTurns`,
|
|
7
|
+
* the budget that names exactly this, was being spent as a round-trip cap.
|
|
8
|
+
*
|
|
9
|
+
* A declared tool rather than acpx's output parsing (acp/adapter.ts): the
|
|
10
|
+
* native protocol has a structured tool channel, and parsing prose for a
|
|
11
|
+
* question when a structured call is available is the weaker mechanism.
|
|
12
|
+
*/
|
|
13
|
+
export const ASK_HUMAN_TOOL_NAME = "ask_human";
|
|
14
|
+
export const askHumanToolDefinition = {
|
|
15
|
+
name: ASK_HUMAN_TOOL_NAME,
|
|
16
|
+
description: "Ask the human operator a question and wait for their reply. Use only when genuinely blocked: the budget is small and each call costs a round trip.",
|
|
17
|
+
inputSchema: {
|
|
18
|
+
type: "object",
|
|
19
|
+
properties: { text: { type: "string", description: "The question to put to the operator." } },
|
|
20
|
+
required: ["text"],
|
|
21
|
+
},
|
|
22
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context compaction for the native turn loop.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions only: no file I/O, no model calls, no clock. turn-loop.ts owns
|
|
5
|
+
* the orchestration and the one model call. Keeping this half pure is what makes
|
|
6
|
+
* the cut-point rules testable directly rather than only end to end.
|
|
7
|
+
*
|
|
8
|
+
* See docs/superpowers/specs/2026-09-04-native-context-compaction-design.md.
|
|
9
|
+
*/
|
|
10
|
+
import type { ConversationMessage } from "@nathapp/nax-ai";
|
|
11
|
+
import type { AdapterInteractionResponse } from "#src/session/interaction-handler";
|
|
12
|
+
/**
|
|
13
|
+
* The transcript message nax stores: nax-ai's ConversationMessage widened with
|
|
14
|
+
* the coding-tool denial marker (ADR-029 s5). The marker is structural data the
|
|
15
|
+
* model must be able to act on — dropping it because the wire type does not
|
|
16
|
+
* know it yet is exactly the defect this widening exists to prevent.
|
|
17
|
+
*
|
|
18
|
+
* Lives here rather than in turn-loop.ts because both modules need it and this
|
|
19
|
+
* is the one without a dependency on the other.
|
|
20
|
+
*/
|
|
21
|
+
export type TranscriptMessage = ConversationMessage | {
|
|
22
|
+
readonly role: "tool-result";
|
|
23
|
+
readonly toolCallId: string;
|
|
24
|
+
readonly content: string;
|
|
25
|
+
readonly isError?: boolean;
|
|
26
|
+
readonly denied?: AdapterInteractionResponse["denied"];
|
|
27
|
+
};
|
|
28
|
+
/** Compaction settings after config resolution. Reaches the adapter as a primitive. */
|
|
29
|
+
export interface ResolvedCompaction {
|
|
30
|
+
readonly enabled: boolean;
|
|
31
|
+
readonly compactAtPercent: number;
|
|
32
|
+
readonly keepRecentPercent: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Characters over four. Deliberately crude and deliberately high: over-estimating
|
|
36
|
+
* compacts slightly early, under-estimating overflows.
|
|
37
|
+
*/
|
|
38
|
+
export declare function estimateTokens(message: TranscriptMessage): number;
|
|
39
|
+
/**
|
|
40
|
+
* Anchor on truth, guess only the tail.
|
|
41
|
+
*
|
|
42
|
+
* `lastUsage.promptTokens` is every token the provider counted for the prompt
|
|
43
|
+
* up to and including `anchorIndex` — uncached input plus cache reads plus
|
|
44
|
+
* cache writes (see `inputClassTokens`). It is deliberately not just the
|
|
45
|
+
* uncached input: under prompt caching the cached prefix is reported
|
|
46
|
+
* separately, and anchoring on that one field reads a 71k-token context as
|
|
47
|
+
* ~16 and silently disables compaction (nax#1852).
|
|
48
|
+
*
|
|
49
|
+
* With no anchor every message is estimated, which is the case the reactive
|
|
50
|
+
* backstop exists to cover.
|
|
51
|
+
*/
|
|
52
|
+
export declare function estimateContextTokens(messages: readonly TranscriptMessage[], lastUsage?: {
|
|
53
|
+
readonly promptTokens: number;
|
|
54
|
+
}, anchorIndex?: number): number;
|
|
55
|
+
/**
|
|
56
|
+
* The percentage governs; the floor only takes over when a percentage of the
|
|
57
|
+
* window would leave too little room for a reply. Both constants are a safety
|
|
58
|
+
* rail against a window smaller than the defaults assume, not tuning knobs —
|
|
59
|
+
* which is why they are not config.
|
|
60
|
+
*/
|
|
61
|
+
export declare function compactionThreshold(window: number, cfg: ResolvedCompaction): number;
|
|
62
|
+
export declare function shouldCompact(tokens: number, window: number, cfg: ResolvedCompaction): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* How much recent conversation survives verbatim, in tokens.
|
|
65
|
+
* `aggressive` halves it — the reactive backstop's only difference.
|
|
66
|
+
*/
|
|
67
|
+
export declare function keepBudget(window: number, cfg: ResolvedCompaction, aggressive?: boolean): number;
|
|
68
|
+
/**
|
|
69
|
+
* Marks the summary message. The summary carries no marker FIELD: transcript
|
|
70
|
+
* messages reach nax-ai structurally, so an extra property would travel to the
|
|
71
|
+
* wire (the existing `denied` marker already does). Position plus this prefix
|
|
72
|
+
* is how a later compaction finds the summary it must replace.
|
|
73
|
+
*/
|
|
74
|
+
export declare const COMPACTION_SUMMARY_PREFIX = "The conversation history before this point was compacted into the following summary:\n\n<summary>\n";
|
|
75
|
+
export declare const COMPACTION_SUMMARY_SUFFIX = "\n</summary>\n";
|
|
76
|
+
/**
|
|
77
|
+
* Walk backwards accumulating estimated size; once `keepTokens` is reached, take
|
|
78
|
+
* the nearest valid cut at or after that point.
|
|
79
|
+
*
|
|
80
|
+
* Two passes on purpose. A single backwards pass that remembers the last valid
|
|
81
|
+
* index it saw returns `messages.length` whenever trailing tool-results consume
|
|
82
|
+
* the whole budget before any valid cut appears — an out-of-range index the
|
|
83
|
+
* caller then dereferences. Collecting the valid cuts first makes "the nearest
|
|
84
|
+
* cut at or after i" answerable without that hole, and `cuts[0]` is the honest
|
|
85
|
+
* default: keep everything, which `prepareCompaction` reads as "no plan".
|
|
86
|
+
*/
|
|
87
|
+
export declare function findCutPoint(messages: readonly TranscriptMessage[], startIndex: number, keepTokens: number): number;
|
|
88
|
+
export interface CompactionPlan {
|
|
89
|
+
readonly cutIndex: number;
|
|
90
|
+
readonly toSummarize: readonly TranscriptMessage[];
|
|
91
|
+
readonly previousSummary?: string;
|
|
92
|
+
}
|
|
93
|
+
export declare function prepareCompaction(messages: readonly TranscriptMessage[], keepTokens: number): CompactionPlan | undefined;
|
|
94
|
+
export declare function applyCompaction(messages: readonly TranscriptMessage[], plan: CompactionPlan, summary: string): TranscriptMessage[];
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context compaction for the native turn loop.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions only: no file I/O, no model calls, no clock. turn-loop.ts owns
|
|
5
|
+
* the orchestration and the one model call. Keeping this half pure is what makes
|
|
6
|
+
* the cut-point rules testable directly rather than only end to end.
|
|
7
|
+
*
|
|
8
|
+
* See docs/superpowers/specs/2026-09-04-native-context-compaction-design.md.
|
|
9
|
+
*/
|
|
10
|
+
/** A reply needs room whatever the window is. */
|
|
11
|
+
const MIN_HEADROOM_TOKENS = 4096;
|
|
12
|
+
/** ...but never take a quarter of a small window for headroom. */
|
|
13
|
+
const MAX_HEADROOM_FRACTION = 0.25;
|
|
14
|
+
const CHARS_PER_TOKEN = 4;
|
|
15
|
+
/**
|
|
16
|
+
* Characters over four. Deliberately crude and deliberately high: over-estimating
|
|
17
|
+
* compacts slightly early, under-estimating overflows.
|
|
18
|
+
*/
|
|
19
|
+
export function estimateTokens(message) {
|
|
20
|
+
let chars = 0;
|
|
21
|
+
switch (message.role) {
|
|
22
|
+
case "user":
|
|
23
|
+
chars = message.content.length;
|
|
24
|
+
break;
|
|
25
|
+
case "assistant":
|
|
26
|
+
chars = message.content.length;
|
|
27
|
+
for (const block of message.thinking ?? [])
|
|
28
|
+
chars += block.text.length;
|
|
29
|
+
for (const call of message.toolCalls ?? [])
|
|
30
|
+
chars += call.name.length + JSON.stringify(call.input).length;
|
|
31
|
+
break;
|
|
32
|
+
case "tool-result":
|
|
33
|
+
chars = message.content.length;
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
return Math.ceil(chars / CHARS_PER_TOKEN);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Anchor on truth, guess only the tail.
|
|
40
|
+
*
|
|
41
|
+
* `lastUsage.promptTokens` is every token the provider counted for the prompt
|
|
42
|
+
* up to and including `anchorIndex` — uncached input plus cache reads plus
|
|
43
|
+
* cache writes (see `inputClassTokens`). It is deliberately not just the
|
|
44
|
+
* uncached input: under prompt caching the cached prefix is reported
|
|
45
|
+
* separately, and anchoring on that one field reads a 71k-token context as
|
|
46
|
+
* ~16 and silently disables compaction (nax#1852).
|
|
47
|
+
*
|
|
48
|
+
* With no anchor every message is estimated, which is the case the reactive
|
|
49
|
+
* backstop exists to cover.
|
|
50
|
+
*/
|
|
51
|
+
export function estimateContextTokens(messages, lastUsage, anchorIndex) {
|
|
52
|
+
if (lastUsage === undefined || anchorIndex === undefined) {
|
|
53
|
+
return messages.reduce((sum, m) => sum + estimateTokens(m), 0);
|
|
54
|
+
}
|
|
55
|
+
let trailing = 0;
|
|
56
|
+
for (let i = anchorIndex + 1; i < messages.length; i++)
|
|
57
|
+
trailing += estimateTokens(messages[i]);
|
|
58
|
+
return lastUsage.promptTokens + trailing;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The percentage governs; the floor only takes over when a percentage of the
|
|
62
|
+
* window would leave too little room for a reply. Both constants are a safety
|
|
63
|
+
* rail against a window smaller than the defaults assume, not tuning knobs —
|
|
64
|
+
* which is why they are not config.
|
|
65
|
+
*/
|
|
66
|
+
export function compactionThreshold(window, cfg) {
|
|
67
|
+
const headroom = Math.min(MIN_HEADROOM_TOKENS, Math.floor(window * MAX_HEADROOM_FRACTION));
|
|
68
|
+
return Math.min(Math.floor(window * (cfg.compactAtPercent / 100)), window - headroom);
|
|
69
|
+
}
|
|
70
|
+
export function shouldCompact(tokens, window, cfg) {
|
|
71
|
+
return cfg.enabled && tokens > compactionThreshold(window, cfg);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* How much recent conversation survives verbatim, in tokens.
|
|
75
|
+
* `aggressive` halves it — the reactive backstop's only difference.
|
|
76
|
+
*/
|
|
77
|
+
export function keepBudget(window, cfg, aggressive = false) {
|
|
78
|
+
const budget = Math.floor(window * (cfg.keepRecentPercent / 100));
|
|
79
|
+
return aggressive ? Math.floor(budget / 2) : budget;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Marks the summary message. The summary carries no marker FIELD: transcript
|
|
83
|
+
* messages reach nax-ai structurally, so an extra property would travel to the
|
|
84
|
+
* wire (the existing `denied` marker already does). Position plus this prefix
|
|
85
|
+
* is how a later compaction finds the summary it must replace.
|
|
86
|
+
*/
|
|
87
|
+
export const COMPACTION_SUMMARY_PREFIX = "The conversation history before this point was compacted into the following summary:\n\n<summary>\n";
|
|
88
|
+
export const COMPACTION_SUMMARY_SUFFIX = "\n</summary>\n";
|
|
89
|
+
/** Index of the pinned first message; never summarized, never dropped. */
|
|
90
|
+
const PIN_INDEX = 0;
|
|
91
|
+
/** Where the summary sits once written. */
|
|
92
|
+
const SUMMARY_INDEX = 1;
|
|
93
|
+
/**
|
|
94
|
+
* Valid cut points are user or assistant messages, NEVER a tool-result.
|
|
95
|
+
*
|
|
96
|
+
* That one rule carries both hard constraints: a tool-result can never become
|
|
97
|
+
* the first kept message (orphaning it from its tool_use, which the provider
|
|
98
|
+
* rejects), and cuts land between messages rather than inside an assistant
|
|
99
|
+
* message, so a kept thinking block keeps its exact text and signature.
|
|
100
|
+
*/
|
|
101
|
+
function isValidCut(message) {
|
|
102
|
+
return message.role === "user" || message.role === "assistant";
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Walk backwards accumulating estimated size; once `keepTokens` is reached, take
|
|
106
|
+
* the nearest valid cut at or after that point.
|
|
107
|
+
*
|
|
108
|
+
* Two passes on purpose. A single backwards pass that remembers the last valid
|
|
109
|
+
* index it saw returns `messages.length` whenever trailing tool-results consume
|
|
110
|
+
* the whole budget before any valid cut appears — an out-of-range index the
|
|
111
|
+
* caller then dereferences. Collecting the valid cuts first makes "the nearest
|
|
112
|
+
* cut at or after i" answerable without that hole, and `cuts[0]` is the honest
|
|
113
|
+
* default: keep everything, which `prepareCompaction` reads as "no plan".
|
|
114
|
+
*/
|
|
115
|
+
export function findCutPoint(messages, startIndex, keepTokens) {
|
|
116
|
+
const cuts = [];
|
|
117
|
+
for (let i = startIndex; i < messages.length; i++) {
|
|
118
|
+
if (isValidCut(messages[i]))
|
|
119
|
+
cuts.push(i);
|
|
120
|
+
}
|
|
121
|
+
if (cuts.length === 0)
|
|
122
|
+
return messages.length;
|
|
123
|
+
let candidate = cuts[0];
|
|
124
|
+
let accumulated = 0;
|
|
125
|
+
for (let i = messages.length - 1; i >= startIndex; i--) {
|
|
126
|
+
accumulated += estimateTokens(messages[i]);
|
|
127
|
+
if (accumulated >= keepTokens) {
|
|
128
|
+
const at = cuts.find((c) => c >= i);
|
|
129
|
+
if (at !== undefined)
|
|
130
|
+
candidate = at;
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return candidate;
|
|
135
|
+
}
|
|
136
|
+
/** Reads a previous summary out of the message at SUMMARY_INDEX, if one is there. */
|
|
137
|
+
function readPreviousSummary(messages) {
|
|
138
|
+
const candidate = messages[SUMMARY_INDEX];
|
|
139
|
+
if (candidate === undefined || candidate.role !== "user")
|
|
140
|
+
return undefined;
|
|
141
|
+
if (!candidate.content.startsWith(COMPACTION_SUMMARY_PREFIX))
|
|
142
|
+
return undefined;
|
|
143
|
+
return candidate.content.slice(COMPACTION_SUMMARY_PREFIX.length, -COMPACTION_SUMMARY_SUFFIX.length);
|
|
144
|
+
}
|
|
145
|
+
export function prepareCompaction(messages, keepTokens) {
|
|
146
|
+
const previousSummary = readPreviousSummary(messages);
|
|
147
|
+
// Everything from here is fair game; the pin, and any existing summary, are not.
|
|
148
|
+
const spanStart = previousSummary === undefined ? PIN_INDEX + 1 : SUMMARY_INDEX + 1;
|
|
149
|
+
const cutIndex = findCutPoint(messages, spanStart, keepTokens);
|
|
150
|
+
// An empty span is "nothing to do" whether or not a summary already exists
|
|
151
|
+
// (nax#1842). A merge plan is still returnable — that needs `previousSummary`
|
|
152
|
+
// AND new content, which is exactly `cutIndex > spanStart`. With an empty span
|
|
153
|
+
// there is nothing to fold in: summarize() would be paid to re-summarize the
|
|
154
|
+
// previous summary alone and applyCompaction would rebuild an array no smaller
|
|
155
|
+
// than the one it replaced, once per round trip for the rest of the turn.
|
|
156
|
+
if (cutIndex <= spanStart)
|
|
157
|
+
return undefined;
|
|
158
|
+
return {
|
|
159
|
+
cutIndex,
|
|
160
|
+
toSummarize: messages.slice(spanStart, cutIndex),
|
|
161
|
+
...(previousSummary !== undefined ? { previousSummary } : {}),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
export function applyCompaction(messages, plan, summary) {
|
|
165
|
+
return [
|
|
166
|
+
messages[PIN_INDEX],
|
|
167
|
+
{ role: "user", content: COMPACTION_SUMMARY_PREFIX + summary + COMPACTION_SUMMARY_SUFFIX },
|
|
168
|
+
...messages.slice(plan.cutIndex),
|
|
169
|
+
];
|
|
170
|
+
}
|