@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,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conversation persistence for the native transport.
|
|
3
|
+
*
|
|
4
|
+
* nax-ai's client is stateless — every call takes the whole message array — so
|
|
5
|
+
* nax keeps the conversation. Under ACP the acpx subprocess remembered it and
|
|
6
|
+
* nax stored nothing; SessionDescriptor still has no message field, and gains
|
|
7
|
+
* none. See ADR-028 sections 2 and 3.
|
|
8
|
+
*/
|
|
9
|
+
import { mkdir, readdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { getLogger, NaxError } from "#src/infra/index";
|
|
12
|
+
import { parseModelSpec } from "../models.js";
|
|
13
|
+
export function transcriptPath(dir, sessionName) {
|
|
14
|
+
return join(dir, `${sessionName}.transcript.json`);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The model half of `TranscriptIdentity`: the native `provider/model` id with
|
|
18
|
+
* the reasoning-effort suffix stripped. A thinking signature binds to the
|
|
19
|
+
* model, not to the effort — pi-ai's own `isSameModel` compares
|
|
20
|
+
* provider/api/model for the same reason. `parseModelSpec`, not
|
|
21
|
+
* `parseNativeModel`: this must never throw.
|
|
22
|
+
*/
|
|
23
|
+
export function transcriptModelIdentity(rawModel) {
|
|
24
|
+
return rawModel === undefined ? undefined : parseModelSpec(rawModel).model;
|
|
25
|
+
}
|
|
26
|
+
/** A bare array is a pre-#1877 transcript: real messages, no recorded owner. */
|
|
27
|
+
function isLegacyTranscript(parsed) {
|
|
28
|
+
return Array.isArray(parsed);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Missing file means a new conversation. Anything else is a real failure.
|
|
32
|
+
*
|
|
33
|
+
* Returns `[]` — a new conversation — when the transcript on disk belongs to a
|
|
34
|
+
* different `owner` or a different recorded `model` than the caller (nax#1877,
|
|
35
|
+
* nax#2150). A reader that declares no identity is not making a claim and
|
|
36
|
+
* reads whatever is there, which keeps non-op callers and unit tests working
|
|
37
|
+
* unchanged.
|
|
38
|
+
*/
|
|
39
|
+
export async function loadTranscript(dir, sessionName, identity = {}) {
|
|
40
|
+
let raw;
|
|
41
|
+
try {
|
|
42
|
+
raw = await readFile(transcriptPath(dir, sessionName), "utf8");
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
if (err.code === "ENOENT")
|
|
46
|
+
return [];
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
49
|
+
let parsed;
|
|
50
|
+
try {
|
|
51
|
+
parsed = JSON.parse(raw);
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
// Deliberately not [] — silently restarting a conversation would drop the
|
|
55
|
+
// history the model is mid-way through and look like a fresh session.
|
|
56
|
+
throw new NaxError(`transcript for session "${sessionName}" is unreadable: ${err instanceof Error ? err.message : String(err)}`, "TRANSCRIPT_CORRUPT", {
|
|
57
|
+
stage: "native-session",
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
if (isLegacyTranscript(parsed)) {
|
|
61
|
+
// Unowned history is foreign history to a reader that has an identity.
|
|
62
|
+
// Dropping it is the safe direction: the cost is one re-exploration, where
|
|
63
|
+
// inheriting it silently bills a conversation this session never had.
|
|
64
|
+
return identity.owner === undefined ? parsed : [];
|
|
65
|
+
}
|
|
66
|
+
const file = parsed;
|
|
67
|
+
if (isForeignTranscript(file, identity, sessionName))
|
|
68
|
+
return [];
|
|
69
|
+
return file.messages ?? [];
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Another invocation's history (nax#1877) or another model's (nax#2150, P3
|
|
73
|
+
* spec 8.3(c)) reads as a new conversation. An ABSENT file model reads, unlike
|
|
74
|
+
* an absent owner: every native production turn records one (the adapter parses
|
|
75
|
+
* the model before the loop runs), so an absent field is a pre-upgrade file —
|
|
76
|
+
* which the owner check already keeps out of new processes.
|
|
77
|
+
*/
|
|
78
|
+
function isForeignTranscript(file, identity, sessionName) {
|
|
79
|
+
const { owner, model } = identity;
|
|
80
|
+
if (owner !== undefined && file.owner !== owner) {
|
|
81
|
+
getLogger().debug("native-session", "Ignoring a transcript owned by another invocation", {
|
|
82
|
+
sessionName,
|
|
83
|
+
storedOwner: file.owner,
|
|
84
|
+
owner,
|
|
85
|
+
});
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
if (model !== undefined && file.model !== undefined && file.model !== model) {
|
|
89
|
+
getLogger().debug("native-session", "Ignoring a transcript written by another model", {
|
|
90
|
+
sessionName,
|
|
91
|
+
storedModel: file.model,
|
|
92
|
+
model,
|
|
93
|
+
});
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
export async function saveTranscript(dir, sessionName, messages, identity = {}) {
|
|
99
|
+
await mkdir(dir, { recursive: true });
|
|
100
|
+
const file = {
|
|
101
|
+
...(identity.owner !== undefined ? { owner: identity.owner } : {}),
|
|
102
|
+
...(identity.model !== undefined ? { model: identity.model } : {}),
|
|
103
|
+
savedAt: new Date().toISOString(),
|
|
104
|
+
messages: [...messages],
|
|
105
|
+
};
|
|
106
|
+
await writeFile(transcriptPath(dir, sessionName), JSON.stringify(file, null, 2), "utf8");
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Move a failed session's transcript off the path the next session reads.
|
|
110
|
+
*
|
|
111
|
+
* The kept-on-failure transcript (nax#1838) used to stay at its live name, which
|
|
112
|
+
* is exactly the name `loadTranscript` opens — so the post-mortem artifact and
|
|
113
|
+
* the resumption source were one file (nax#1877). Renaming keeps every byte of
|
|
114
|
+
* debugging value and removes the resumption entirely. It also stops the NEXT
|
|
115
|
+
* attempt's `saveTranscript` from overwriting the artifact #1838 wanted kept.
|
|
116
|
+
*
|
|
117
|
+
* A missing transcript is not an error: a session can fail before its first save.
|
|
118
|
+
*/
|
|
119
|
+
export async function retainTranscript(dir, sessionName) {
|
|
120
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
121
|
+
try {
|
|
122
|
+
await rename(transcriptPath(dir, sessionName), join(dir, `${sessionName}.transcript.failed-${stamp}.json`));
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
if (err.code === "ENOENT")
|
|
126
|
+
return;
|
|
127
|
+
throw err;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
export async function deleteTranscript(dir, sessionName) {
|
|
131
|
+
await rm(transcriptPath(dir, sessionName), { force: true });
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Cap on transcripts retained per feature's `sessions/` directory. Transcripts
|
|
135
|
+
* are deleted on a clean close (`closeNativeSession`) but kept when a turn
|
|
136
|
+
* failed, so the kept-on-failure set otherwise grows without bound — nothing
|
|
137
|
+
* else prunes it (issue #1445 is the same class of unbounded `~/.nax` growth).
|
|
138
|
+
* Follows the precedent of `MAX_RETAINED_RUNS` in `src/metrics/tracker.ts`.
|
|
139
|
+
*/
|
|
140
|
+
export const MAX_RETAINED_TRANSCRIPTS = 50;
|
|
141
|
+
/**
|
|
142
|
+
* One-shot dedupe flag, mirroring `hasWarnedAboutRunTruncation` in
|
|
143
|
+
* `src/metrics/tracker.ts`: pruning runs on every failed close, so once a
|
|
144
|
+
* feature is past the cap this would otherwise warn on every subsequent
|
|
145
|
+
* close. Log it once per process lifetime instead.
|
|
146
|
+
*/
|
|
147
|
+
let hasWarnedAboutTranscriptTruncation = false;
|
|
148
|
+
/**
|
|
149
|
+
* Reset the one-shot truncation-warning flag (for testing only).
|
|
150
|
+
* @internal
|
|
151
|
+
*/
|
|
152
|
+
export function _resetTranscriptTruncationWarningForTests() {
|
|
153
|
+
hasWarnedAboutTranscriptTruncation = false;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Prune a feature's `sessions/` directory to the `maxRetained` most recently
|
|
157
|
+
* modified transcript files, deleting the oldest first. Called from
|
|
158
|
+
* `closeNativeSession`'s kept-on-failure branch — the only place transcripts
|
|
159
|
+
* accumulate, since a clean close always deletes its own transcript.
|
|
160
|
+
*
|
|
161
|
+
* A missing directory is not an error (nothing to prune yet).
|
|
162
|
+
*
|
|
163
|
+
* Returns the number of transcript files deleted (0 when there is nothing to
|
|
164
|
+
* delete) so callers can report the sweep's work without re-reading the dir.
|
|
165
|
+
*/
|
|
166
|
+
export async function pruneRetainedTranscripts(dir, maxRetained = MAX_RETAINED_TRANSCRIPTS) {
|
|
167
|
+
let entries;
|
|
168
|
+
try {
|
|
169
|
+
entries = await readdir(dir);
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
if (err.code === "ENOENT")
|
|
173
|
+
return 0;
|
|
174
|
+
throw err;
|
|
175
|
+
}
|
|
176
|
+
// Matches both the live name and the `failed-<stamp>` names `retainTranscript`
|
|
177
|
+
// produces — the retained set is the one that accumulates, so a filter that
|
|
178
|
+
// missed it would leave the cap enforcing nothing.
|
|
179
|
+
const transcriptFiles = entries.filter((name) => name.includes(".transcript.") && name.endsWith(".json"));
|
|
180
|
+
if (transcriptFiles.length <= maxRetained)
|
|
181
|
+
return 0;
|
|
182
|
+
const withMtimes = await Promise.all(transcriptFiles.map(async (name) => {
|
|
183
|
+
const fullPath = join(dir, name);
|
|
184
|
+
const stats = await stat(fullPath);
|
|
185
|
+
return { path: fullPath, mtimeMs: stats.mtimeMs };
|
|
186
|
+
}));
|
|
187
|
+
// Oldest first, so the slice below drops exactly the excess over the cap.
|
|
188
|
+
withMtimes.sort((a, b) => a.mtimeMs - b.mtimeMs);
|
|
189
|
+
const toDelete = withMtimes.slice(0, withMtimes.length - maxRetained);
|
|
190
|
+
await Promise.all(toDelete.map((f) => rm(f.path, { force: true })));
|
|
191
|
+
if (!hasWarnedAboutTranscriptTruncation) {
|
|
192
|
+
hasWarnedAboutTranscriptTruncation = true;
|
|
193
|
+
getLogger().warn("native-session", "Transcript retention cap reached — oldest kept-on-failure transcripts pruned", {
|
|
194
|
+
droppedCount: toDelete.length,
|
|
195
|
+
maxRetained,
|
|
196
|
+
dir,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
return toDelete.length;
|
|
200
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* US-003 — the native session's model-facing truncation, applied to every
|
|
3
|
+
* genuine tool result before it enters the message array.
|
|
4
|
+
*
|
|
5
|
+
* This IS a registered `after_tool` handler now: `createTruncationHandler`
|
|
6
|
+
* builds it and `registerBuiltinLoopHandlers` registers it LAST, so it shapes
|
|
7
|
+
* whatever earlier handlers produced — the same position the loop's hardcoded
|
|
8
|
+
* call held before the seam carried it. It could not be registered while the
|
|
9
|
+
* dispatcher was synchronous: the spill write has to be awaited, because AC8's
|
|
10
|
+
* fail-open contract means the marker may only name the spill file when the
|
|
11
|
+
* write actually succeeded, so the write cannot be fired and forgotten and its
|
|
12
|
+
* outcome discovered afterwards. Dispatch is async now, so that documented
|
|
13
|
+
* exception is deleted rather than restated. The policy itself remains
|
|
14
|
+
* `applyModelTruncationPolicy` — the one implementation the coding-tool
|
|
15
|
+
* runtime applies too.
|
|
16
|
+
*
|
|
17
|
+
* The spill root is the session's scratchpad root, recorded at open from the
|
|
18
|
+
* session's workdir so the marker's relative path resolves through
|
|
19
|
+
* `ScratchpadRead`. A session driven without one (unit tests calling
|
|
20
|
+
* `runNativeTurn` directly) falls back to its transcript directory, the one path
|
|
21
|
+
* the session is known to own.
|
|
22
|
+
*/
|
|
23
|
+
import type { HandlerOf } from "./loop-events/types.ts";
|
|
24
|
+
/**
|
|
25
|
+
* Where this session's spills belong, or `undefined` when the session is not
|
|
26
|
+
* known to the runtime at all — in which case nothing is spilled and the marker
|
|
27
|
+
* names no path, rather than naming a file written somewhere unreachable.
|
|
28
|
+
*/
|
|
29
|
+
export declare function spillRootFor(sessionId: string): string | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Shape one tool result for the model: within every cap it is returned
|
|
32
|
+
* untouched (and nothing is spilled); over a cap it is cut, the untruncated
|
|
33
|
+
* body is written under the scratchpad, and the content ends with the marker
|
|
34
|
+
* naming the spill path and both byte counts.
|
|
35
|
+
*/
|
|
36
|
+
export declare function truncateNativeToolResult(sessionId: string, body: string, opts: {
|
|
37
|
+
readonly toolName?: string;
|
|
38
|
+
readonly callId?: string;
|
|
39
|
+
readonly reserveBytes?: number;
|
|
40
|
+
}): Promise<string>;
|
|
41
|
+
/**
|
|
42
|
+
* The `after_tool` handler the loop registers: the same policy, at the same
|
|
43
|
+
* chokepoint, reached through the seam instead of a hardcoded call. A nudge
|
|
44
|
+
* riding on the payload reserves its own bytes out of the result's budget, so
|
|
45
|
+
* the ceiling holds after `withNudge` prepends it.
|
|
46
|
+
*/
|
|
47
|
+
export declare function createTruncationHandler(sessionName: string): HandlerOf<"after_tool">;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* US-003 — the native session's model-facing truncation, applied to every
|
|
3
|
+
* genuine tool result before it enters the message array.
|
|
4
|
+
*
|
|
5
|
+
* This IS a registered `after_tool` handler now: `createTruncationHandler`
|
|
6
|
+
* builds it and `registerBuiltinLoopHandlers` registers it LAST, so it shapes
|
|
7
|
+
* whatever earlier handlers produced — the same position the loop's hardcoded
|
|
8
|
+
* call held before the seam carried it. It could not be registered while the
|
|
9
|
+
* dispatcher was synchronous: the spill write has to be awaited, because AC8's
|
|
10
|
+
* fail-open contract means the marker may only name the spill file when the
|
|
11
|
+
* write actually succeeded, so the write cannot be fired and forgotten and its
|
|
12
|
+
* outcome discovered afterwards. Dispatch is async now, so that documented
|
|
13
|
+
* exception is deleted rather than restated. The policy itself remains
|
|
14
|
+
* `applyModelTruncationPolicy` — the one implementation the coding-tool
|
|
15
|
+
* runtime applies too.
|
|
16
|
+
*
|
|
17
|
+
* The spill root is the session's scratchpad root, recorded at open from the
|
|
18
|
+
* session's workdir so the marker's relative path resolves through
|
|
19
|
+
* `ScratchpadRead`. A session driven without one (unit tests calling
|
|
20
|
+
* `runNativeTurn` directly) falls back to its transcript directory, the one path
|
|
21
|
+
* the session is known to own.
|
|
22
|
+
*/
|
|
23
|
+
import { applyModelTruncationPolicy, MODEL_MAX_BYTES } from "#src/tools/index";
|
|
24
|
+
import { nudgeOverheadBytes } from "./nudge.js";
|
|
25
|
+
import { nativeSessionScratchpadRoots, nativeTranscriptDirs } from "./session.js";
|
|
26
|
+
/**
|
|
27
|
+
* Where this session's spills belong, or `undefined` when the session is not
|
|
28
|
+
* known to the runtime at all — in which case nothing is spilled and the marker
|
|
29
|
+
* names no path, rather than naming a file written somewhere unreachable.
|
|
30
|
+
*/
|
|
31
|
+
export function spillRootFor(sessionId) {
|
|
32
|
+
return nativeSessionScratchpadRoots.get(sessionId) ?? nativeTranscriptDirs.get(sessionId);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Shape one tool result for the model: within every cap it is returned
|
|
36
|
+
* untouched (and nothing is spilled); over a cap it is cut, the untruncated
|
|
37
|
+
* body is written under the scratchpad, and the content ends with the marker
|
|
38
|
+
* naming the spill path and both byte counts.
|
|
39
|
+
*/
|
|
40
|
+
export async function truncateNativeToolResult(sessionId, body, opts) {
|
|
41
|
+
const root = spillRootFor(sessionId);
|
|
42
|
+
// `reserveBytes` is what the CALLER will add to this result after the policy
|
|
43
|
+
// has run -- today only the spin-breaker nudge, which the turn loop prepends.
|
|
44
|
+
// Anything appended or prepended downstream has to be charged against the
|
|
45
|
+
// same ceiling here, or the guarantee this chokepoint exists to give (the
|
|
46
|
+
// model-facing content is at most MODEL_MAX_BYTES) is broken by exactly the
|
|
47
|
+
// bytes the caller adds. Reserving is what keeps the ceiling unconditional.
|
|
48
|
+
const reserved = Math.max(0, opts.reserveBytes ?? 0);
|
|
49
|
+
// A session opened through the runtime has a scratchpad root recorded from its
|
|
50
|
+
// workdir, which IS the directory its tools and shell start in, so a
|
|
51
|
+
// root-relative marker resolves there. The transcript-directory fallback is
|
|
52
|
+
// not that directory, so its marker must name the absolute path or the model
|
|
53
|
+
// would look for the spill somewhere it is not.
|
|
54
|
+
return applyModelTruncationPolicy(body, {
|
|
55
|
+
toolName: opts.toolName ?? "",
|
|
56
|
+
callId: opts.callId ?? "",
|
|
57
|
+
...(root !== undefined
|
|
58
|
+
? {
|
|
59
|
+
root,
|
|
60
|
+
spillPathStyle: nativeSessionScratchpadRoots.has(sessionId)
|
|
61
|
+
? "root-relative"
|
|
62
|
+
: "absolute",
|
|
63
|
+
}
|
|
64
|
+
: {}),
|
|
65
|
+
maxBytes: Math.max(0, MODEL_MAX_BYTES - reserved),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The `after_tool` handler the loop registers: the same policy, at the same
|
|
70
|
+
* chokepoint, reached through the seam instead of a hardcoded call. A nudge
|
|
71
|
+
* riding on the payload reserves its own bytes out of the result's budget, so
|
|
72
|
+
* the ceiling holds after `withNudge` prepends it.
|
|
73
|
+
*/
|
|
74
|
+
export function createTruncationHandler(sessionName) {
|
|
75
|
+
return async (payload) => {
|
|
76
|
+
const shaped = await truncateNativeToolResult(sessionName, payload.content, {
|
|
77
|
+
toolName: payload.toolName,
|
|
78
|
+
callId: payload.callId,
|
|
79
|
+
...(payload.nudgeText !== undefined ? { reserveBytes: nudgeOverheadBytes(payload.nudgeText) } : {}),
|
|
80
|
+
});
|
|
81
|
+
return { content: shaped };
|
|
82
|
+
};
|
|
83
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The six usage/cost/rate counters `runNativeTurn` accumulates.
|
|
3
|
+
*
|
|
4
|
+
* Extracted because the same ten lines appeared three times — after a
|
|
5
|
+
* proactive compaction summary, after a reactive overflow summary, and after
|
|
6
|
+
* each round trip — and a fourth copy was one loop event away. A factory
|
|
7
|
+
* returning an object, matching `createRateTotals` and `createInvalidCallBudget`.
|
|
8
|
+
*
|
|
9
|
+
* `add` and `usageBeat` are deliberately separate: the round-trip beat carries
|
|
10
|
+
* `roundTrip` and the two summary beats do not, so folding the beat into `add`
|
|
11
|
+
* would either drop that field or fabricate it for the summaries.
|
|
12
|
+
*/
|
|
13
|
+
import type { PricingRates, TokenUsage } from "#src/cost/standard-types";
|
|
14
|
+
import type { NativeTurnActivity } from "./turn-events.ts";
|
|
15
|
+
/**
|
|
16
|
+
* Token counts ONLY. `costUsd` is deliberately NOT a field here: both readers
|
|
17
|
+
* (`recordNativeTurnFailureUsage` at turn-loop.ts:542 and the TurnResult at
|
|
18
|
+
* :580) take `tokenUsage` and the cost as SEPARATE arguments, so folding cost
|
|
19
|
+
* in would force every call site to destructure it back out.
|
|
20
|
+
*/
|
|
21
|
+
export interface TurnTokenTotals {
|
|
22
|
+
readonly inputTokens: number;
|
|
23
|
+
readonly outputTokens: number;
|
|
24
|
+
/**
|
|
25
|
+
* Undefined until something reports cache data, then a running sum. Staying
|
|
26
|
+
* undefined when nothing ever reports it preserves the absent/zero
|
|
27
|
+
* distinction nax-ai's `toTokenUsage` establishes: "no cache data" and "zero
|
|
28
|
+
* cache tokens" must stay distinguishable downstream (nax#2045).
|
|
29
|
+
*/
|
|
30
|
+
readonly cacheReadTokens?: number;
|
|
31
|
+
readonly cacheWriteTokens?: number;
|
|
32
|
+
}
|
|
33
|
+
export interface TurnAccumulator {
|
|
34
|
+
add(usage: TokenUsage, costUsd: number, rates?: PricingRates): void;
|
|
35
|
+
tokens(): TurnTokenTotals;
|
|
36
|
+
costUsd(): number;
|
|
37
|
+
/** Aggregated rate provenance, or undefined when nothing priced. */
|
|
38
|
+
rates(): PricingRates | undefined;
|
|
39
|
+
}
|
|
40
|
+
export declare function createTurnAccumulator(): TurnAccumulator;
|
|
41
|
+
/**
|
|
42
|
+
* The `onActivity` usage beat. `roundTrip` is 1-based and omitted for the two
|
|
43
|
+
* compaction-summary beats, which are not round trips.
|
|
44
|
+
*/
|
|
45
|
+
export declare function usageBeat(usage: TokenUsage, costUsd: number, roundTrip?: number): NativeTurnActivity;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The six usage/cost/rate counters `runNativeTurn` accumulates.
|
|
3
|
+
*
|
|
4
|
+
* Extracted because the same ten lines appeared three times — after a
|
|
5
|
+
* proactive compaction summary, after a reactive overflow summary, and after
|
|
6
|
+
* each round trip — and a fourth copy was one loop event away. A factory
|
|
7
|
+
* returning an object, matching `createRateTotals` and `createInvalidCallBudget`.
|
|
8
|
+
*
|
|
9
|
+
* `add` and `usageBeat` are deliberately separate: the round-trip beat carries
|
|
10
|
+
* `roundTrip` and the two summary beats do not, so folding the beat into `add`
|
|
11
|
+
* would either drop that field or fabricate it for the summaries.
|
|
12
|
+
*/
|
|
13
|
+
import { addRateTotals, aggregateRates, createRateTotals } from "./rate-provenance.js";
|
|
14
|
+
import { cacheUsageFields } from "./turn-types.js";
|
|
15
|
+
export function createTurnAccumulator() {
|
|
16
|
+
let inputTokens = 0;
|
|
17
|
+
let outputTokens = 0;
|
|
18
|
+
let cacheReadTokens;
|
|
19
|
+
let cacheWriteTokens;
|
|
20
|
+
let costUsd = 0;
|
|
21
|
+
const rateTotals = createRateTotals();
|
|
22
|
+
return {
|
|
23
|
+
add(usage, addedCostUsd, rates) {
|
|
24
|
+
inputTokens += usage.inputTokens;
|
|
25
|
+
outputTokens += usage.outputTokens;
|
|
26
|
+
if (usage.cacheReadTokens !== undefined) {
|
|
27
|
+
cacheReadTokens = (cacheReadTokens ?? 0) + usage.cacheReadTokens;
|
|
28
|
+
}
|
|
29
|
+
if (usage.cacheWriteTokens !== undefined) {
|
|
30
|
+
cacheWriteTokens = (cacheWriteTokens ?? 0) + usage.cacheWriteTokens;
|
|
31
|
+
}
|
|
32
|
+
costUsd += addedCostUsd;
|
|
33
|
+
addRateTotals(rateTotals, usage, rates);
|
|
34
|
+
},
|
|
35
|
+
tokens() {
|
|
36
|
+
return {
|
|
37
|
+
inputTokens,
|
|
38
|
+
outputTokens,
|
|
39
|
+
...(cacheReadTokens !== undefined ? { cacheReadTokens } : {}),
|
|
40
|
+
...(cacheWriteTokens !== undefined ? { cacheWriteTokens } : {}),
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
costUsd() {
|
|
44
|
+
return costUsd;
|
|
45
|
+
},
|
|
46
|
+
rates() {
|
|
47
|
+
return aggregateRates(rateTotals);
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The `onActivity` usage beat. `roundTrip` is 1-based and omitted for the two
|
|
53
|
+
* compaction-summary beats, which are not round trips.
|
|
54
|
+
*/
|
|
55
|
+
export function usageBeat(usage, costUsd, roundTrip) {
|
|
56
|
+
return {
|
|
57
|
+
kind: "usage",
|
|
58
|
+
inputTokens: usage.inputTokens,
|
|
59
|
+
outputTokens: usage.outputTokens,
|
|
60
|
+
costUsd,
|
|
61
|
+
// Absent stays absent (never 0): `cacheReadTokens` stays
|
|
62
|
+
// `number | undefined` so "no cache data" and "zero cache tokens"
|
|
63
|
+
// remain distinguishable downstream (nax#2045).
|
|
64
|
+
...cacheUsageFields(usage),
|
|
65
|
+
...(roundTrip !== undefined ? { roundTrip } : {}),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `ask_human` branch of the turn loop's tool-call batch.
|
|
3
|
+
*
|
|
4
|
+
* Extracted unchanged. It is not an ordinary tool call: no tool produced it,
|
|
5
|
+
* so it deliberately fires no `before_tool`/`after_tool` event — a policy that
|
|
6
|
+
* shapes tool output has nothing to shape here.
|
|
7
|
+
*/
|
|
8
|
+
import type { InteractionExchange, SendTurnOpts } from "#src/session/session-types";
|
|
9
|
+
import { type ToolResultMessage } from "./tool-result.ts";
|
|
10
|
+
export interface AskHumanOutcome {
|
|
11
|
+
/** Appended to the message array by the caller. */
|
|
12
|
+
readonly result: ToolResultMessage;
|
|
13
|
+
/** Present only when a real exchange happened; the caller pushes it. */
|
|
14
|
+
readonly exchange?: InteractionExchange;
|
|
15
|
+
}
|
|
16
|
+
export declare function handleAskHumanCall(args: {
|
|
17
|
+
readonly toolCallId: string;
|
|
18
|
+
readonly question: string;
|
|
19
|
+
readonly interactionsSoFar: number;
|
|
20
|
+
readonly maxInteractions: number;
|
|
21
|
+
readonly roundTrips: number;
|
|
22
|
+
readonly interactionHandler: SendTurnOpts["interactionHandler"];
|
|
23
|
+
}): Promise<AskHumanOutcome>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `ask_human` branch of the turn loop's tool-call batch.
|
|
3
|
+
*
|
|
4
|
+
* Extracted unchanged. It is not an ordinary tool call: no tool produced it,
|
|
5
|
+
* so it deliberately fires no `before_tool`/`after_tool` event — a policy that
|
|
6
|
+
* shapes tool output has nothing to shape here.
|
|
7
|
+
*/
|
|
8
|
+
import { buildToolResult } from "./tool-result.js";
|
|
9
|
+
export async function handleAskHumanCall(args) {
|
|
10
|
+
const { toolCallId, question, interactionsSoFar, maxInteractions, roundTrips, interactionHandler } = args;
|
|
11
|
+
// An unset budget (maxInteractions undefined -> 0) keeps the tool unadvertised
|
|
12
|
+
// above AND refuses a call made anyway. "No budget configured" must not
|
|
13
|
+
// read as "unlimited" — that inverts the property this budget provides.
|
|
14
|
+
if (interactionsSoFar >= maxInteractions) {
|
|
15
|
+
return {
|
|
16
|
+
result: buildToolResult({
|
|
17
|
+
toolCallId,
|
|
18
|
+
content: "The human Q&A budget for this turn is spent. Proceed on your best judgement.",
|
|
19
|
+
isError: true,
|
|
20
|
+
}),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
const answer = await interactionHandler.onInteraction({ kind: "question", text: question });
|
|
24
|
+
// A null answer means no operator is reachable — run-interaction-handler
|
|
25
|
+
// returns null for kind:"question" when no interactionBridge is
|
|
26
|
+
// configured. That is not an exchange: it must not consume budget and
|
|
27
|
+
// must not be recorded as a question the operator answered with "".
|
|
28
|
+
if (answer === null) {
|
|
29
|
+
return {
|
|
30
|
+
result: buildToolResult({
|
|
31
|
+
toolCallId,
|
|
32
|
+
content: "No human operator is available for this run. Proceed on your best judgement.",
|
|
33
|
+
isError: true,
|
|
34
|
+
}),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
result: buildToolResult({ toolCallId, content: answer.answer }),
|
|
39
|
+
exchange: { turnIndex: roundTrips, question, reply: answer.answer },
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two compaction steps of the native turn loop.
|
|
3
|
+
*
|
|
4
|
+
* `runProactiveCompaction` runs before a round trip when the estimate crosses
|
|
5
|
+
* the threshold; `runOverflowCompaction` is the backstop when a round trip
|
|
6
|
+
* throws a context overflow anyway. They are deliberately TWO functions, not one
|
|
7
|
+
* with a `reactive` flag, because they differ in exactly three ways a boolean
|
|
8
|
+
* would hide:
|
|
9
|
+
*
|
|
10
|
+
* 1. the backstop halves the keep budget (`keepBudget(..., true)`);
|
|
11
|
+
* 2. a summarizer throw is swallowed by the proactive step (the request may
|
|
12
|
+
* still fit) and propagates out of the backstop;
|
|
13
|
+
* 3. only the proactive step logs its progress (Finding 2, whole-branch
|
|
14
|
+
* review 2026-09-04);
|
|
15
|
+
* 4. a `before_compaction` decline is HONOURED proactively — the branch runs
|
|
16
|
+
* before any request, so sending uncompacted is a legitimate outcome — and
|
|
17
|
+
* IGNORED + logged at overflow, where the request has already failed with
|
|
18
|
+
* a context overflow and declining would leave no recovery (spec 6.2).
|
|
19
|
+
*
|
|
20
|
+
* They share only the summarize-and-apply core below. The failure semantics
|
|
21
|
+
* stay with the callers on purpose — see each function's catch.
|
|
22
|
+
*/
|
|
23
|
+
import { type TranscriptMessage as NativeTranscriptMessage } from "./compaction.ts";
|
|
24
|
+
import type { LoopEventRegistry } from "./loop-events/index.ts";
|
|
25
|
+
import { type TurnAccumulator } from "./turn-accumulator.ts";
|
|
26
|
+
import type { TurnDeps } from "./turn-types.ts";
|
|
27
|
+
/**
|
|
28
|
+
* The compaction dependencies, with the three the step cannot run without
|
|
29
|
+
* required. `TurnDeps` has them optional (absent disables compaction); the call
|
|
30
|
+
* site narrows them to defined with the guard before calling, and requiring them
|
|
31
|
+
* here is what carries that narrowing into this module.
|
|
32
|
+
*/
|
|
33
|
+
export interface CompactionStepDeps {
|
|
34
|
+
readonly summarize: NonNullable<TurnDeps["summarize"]>;
|
|
35
|
+
readonly contextWindow: NonNullable<TurnDeps["contextWindow"]>;
|
|
36
|
+
readonly compaction: NonNullable<TurnDeps["compaction"]>;
|
|
37
|
+
readonly onActivity?: TurnDeps["onActivity"];
|
|
38
|
+
readonly deadline?: TurnDeps["deadline"];
|
|
39
|
+
}
|
|
40
|
+
export interface CompactionStepArgs {
|
|
41
|
+
readonly messages: readonly NativeTranscriptMessage[];
|
|
42
|
+
readonly usage: TurnAccumulator;
|
|
43
|
+
readonly sessionName: string;
|
|
44
|
+
readonly lastUsage: {
|
|
45
|
+
readonly promptTokens: number;
|
|
46
|
+
} | undefined;
|
|
47
|
+
readonly anchorIndex: number | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* The registry actually in use — the loop's local (`deps.loopEvents ??
|
|
50
|
+
* createLoopEventRegistry()`), not `deps.loopEvents` itself, which is
|
|
51
|
+
* usually absent and would dispatch to nothing. Threaded from `turn-loop.ts`
|
|
52
|
+
* and from `completeWithRecovery`'s own arg, the same as `CompleteStepArgs`.
|
|
53
|
+
*/
|
|
54
|
+
readonly loopEvents: LoopEventRegistry;
|
|
55
|
+
readonly deps: CompactionStepDeps;
|
|
56
|
+
readonly signal?: AbortSignal;
|
|
57
|
+
}
|
|
58
|
+
export interface CompactionStepResult {
|
|
59
|
+
readonly messages: readonly NativeTranscriptMessage[];
|
|
60
|
+
/** True when the array was rebound, so the caller clears lastUsage/anchorIndex. */
|
|
61
|
+
readonly compacted: boolean;
|
|
62
|
+
/** Proactive only: the summarizer threw, so the overflow retry must not try again. */
|
|
63
|
+
readonly summarizeFailed: boolean;
|
|
64
|
+
}
|
|
65
|
+
export interface OverflowCompactionArgs extends CompactionStepArgs {
|
|
66
|
+
/**
|
|
67
|
+
* The original context-overflow error. `prepareCompaction` returning undefined
|
|
68
|
+
* means there is nothing safe left to drop, so it is rethrown verbatim —
|
|
69
|
+
* tests and the failure-usage ledger key on the error's own identity.
|
|
70
|
+
*/
|
|
71
|
+
readonly error: unknown;
|
|
72
|
+
}
|
|
73
|
+
export declare function runProactiveCompaction(args: CompactionStepArgs): Promise<CompactionStepResult>;
|
|
74
|
+
export declare function runOverflowCompaction(args: OverflowCompactionArgs): Promise<CompactionStepResult>;
|