@wardby/cli 0.6.0 → 0.7.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/deploy/local/docker-compose.quickstart-coding.yml +13 -0
- package/dist/claude-coding-worker/driver.d.ts +21 -3
- package/dist/claude-coding-worker/driver.js +125 -17
- package/dist/claude-coding-worker/main.js +34 -6
- package/dist/claude-coding-worker/sdk.js +12 -1
- package/dist/claude-coding-worker/tool-socket.d.ts +27 -0
- package/dist/claude-coding-worker/tool-socket.js +111 -0
- package/dist/claude-coding-worker/transcript.d.ts +18 -0
- package/dist/claude-coding-worker/transcript.js +77 -0
- package/dist/cli-help.d.ts +1 -1
- package/dist/cli-help.js +1 -0
- package/dist/cli.js +24 -10
- package/dist/coding/continuation-wording.d.ts +35 -0
- package/dist/coding/continuation-wording.js +73 -0
- package/dist/coding/linked-issue-note.d.ts +23 -0
- package/dist/coding/linked-issue-note.js +47 -0
- package/dist/coding/merge-wording.d.ts +28 -0
- package/dist/coding/merge-wording.js +78 -0
- package/dist/coding/pr-description-note.d.ts +9 -0
- package/dist/coding/pr-description-note.js +32 -0
- package/dist/coding/profile.d.ts +46 -126
- package/dist/coding/profile.js +45 -4
- package/dist/coding/protected-path-wording.d.ts +8 -0
- package/dist/coding/protected-path-wording.js +1 -1
- package/dist/coding/protocol.d.ts +264 -346
- package/dist/coding/protocol.js +235 -12
- package/dist/coding/redact.d.ts +3 -0
- package/dist/coding/redact.js +12 -0
- package/dist/coding/registry/allowlist.d.ts +12 -0
- package/dist/coding/registry/allowlist.js +14 -1
- package/dist/coding/registry/audit-mode.d.ts +153 -0
- package/dist/coding/registry/audit-mode.js +304 -0
- package/dist/coding/registry/lockfile-shared.d.ts +36 -0
- package/dist/coding/registry/lockfile-shared.js +128 -0
- package/dist/coding/registry/lockfiles.js +1 -1
- package/dist/coding/registry/npm-lockfile-formats.d.ts +11 -0
- package/dist/coding/registry/npm-lockfile-formats.js +41 -0
- package/dist/coding/registry/npm-lockfile.d.ts +8 -4
- package/dist/coding/registry/npm-lockfile.js +4 -6
- package/dist/coding/registry/npm.js +78 -8
- package/dist/coding/registry/pnpm-lockfile.d.ts +17 -0
- package/dist/coding/registry/pnpm-lockfile.js +189 -0
- package/dist/coding/registry/pypi-plan.d.ts +50 -0
- package/dist/coding/registry/pypi-plan.js +406 -0
- package/dist/coding/registry/pypi-requirements.d.ts +58 -0
- package/dist/coding/registry/pypi-requirements.js +155 -0
- package/dist/coding/registry/pypi.d.ts +3 -27
- package/dist/coding/registry/pypi.js +40 -154
- package/dist/coding/registry/refusal-hints.d.ts +20 -0
- package/dist/coding/registry/refusal-hints.js +61 -0
- package/dist/coding/registry/refusal-wording.d.ts +11 -0
- package/dist/coding/registry/refusal-wording.js +56 -0
- package/dist/coding/registry/report.d.ts +70 -8
- package/dist/coding/registry/report.js +112 -5
- package/dist/coding/registry/types.d.ts +58 -8
- package/dist/coding/registry/yarn-lockfile.d.ts +30 -0
- package/dist/coding/registry/yarn-lockfile.js +367 -0
- package/dist/coding/services/catalog.d.ts +37 -351
- package/dist/coding/transcript/claude-code.d.ts +13 -0
- package/dist/coding/transcript/claude-code.js +273 -0
- package/dist/coding/transcript/download-protocol.d.ts +30 -0
- package/dist/coding/transcript/download-protocol.js +18 -0
- package/dist/coding/transcript/format.d.ts +20 -0
- package/dist/coding/transcript/format.js +32 -0
- package/dist/coding/transcript/recorder.d.ts +51 -0
- package/dist/coding/transcript/recorder.js +225 -0
- package/dist/coding/transcript/resume/checkout.d.ts +31 -0
- package/dist/coding/transcript/resume/checkout.js +203 -0
- package/dist/coding/transcript/resume/command.d.ts +17 -0
- package/dist/coding/transcript/resume/command.js +176 -0
- package/dist/coding/transcript/resume/download.d.ts +20 -0
- package/dist/coding/transcript/resume/download.js +153 -0
- package/dist/coding/transcript/resume/launch.d.ts +38 -0
- package/dist/coding/transcript/resume/launch.js +128 -0
- package/dist/coding/transcript/resume/session-file.d.ts +13 -0
- package/dist/coding/transcript/resume/session-file.js +79 -0
- package/dist/coding/transcript/resume/stand-in.d.ts +11 -0
- package/dist/coding/transcript/resume/stand-in.js +49 -0
- package/dist/coding-worker/artifact.d.ts +8 -3
- package/dist/coding-worker/artifact.js +3 -1
- package/dist/coding-worker/debug-trace.d.ts +6 -5
- package/dist/coding-worker/debug-trace.js +13 -18
- package/dist/coding-worker/dependency-cache.d.mts +229 -0
- package/dist/coding-worker/dependency-cache.mjs +2123 -0
- package/dist/coding-worker/driver.d.ts +4 -3
- package/dist/coding-worker/driver.js +116 -38
- package/dist/coding-worker/errors.d.ts +1 -0
- package/dist/coding-worker/errors.js +32 -0
- package/dist/coding-worker/task-note.d.ts +11 -0
- package/dist/coding-worker/task-note.js +13 -0
- package/dist/coding-worker/types.d.ts +11 -0
- package/dist/config/dependency-cache.d.ts +23 -0
- package/dist/config/dependency-cache.js +32 -0
- package/dist/config/providers.d.ts +12 -3
- package/dist/config/providers.js +55 -5
- package/dist/config/test-simulators.d.ts +82 -0
- package/dist/config/test-simulators.js +305 -0
- package/dist/core/async-context.d.ts +2 -0
- package/dist/core/async-context.js +12 -0
- package/dist/core/budget-events.d.ts +108 -0
- package/dist/core/budget-events.js +346 -0
- package/dist/core/budget-groups.d.ts +50 -1
- package/dist/core/budget-groups.js +236 -27
- package/dist/core/budget-stopped-head.d.ts +46 -0
- package/dist/core/budget-stopped-head.js +81 -0
- package/dist/core/budget-topup.d.ts +51 -0
- package/dist/core/budget-topup.js +156 -0
- package/dist/core/budget-wording.d.ts +16 -2
- package/dist/core/budget-wording.js +40 -2
- package/dist/core/coding-queue.d.ts +2 -1
- package/dist/core/coding-queue.js +7 -0
- package/dist/core/cron.d.ts +19 -1
- package/dist/core/cron.js +17 -0
- package/dist/core/datastores.js +1 -1
- package/dist/core/deferred-coding.d.ts +14 -0
- package/dist/core/deferred-coding.js +62 -0
- package/dist/core/delegation-child.d.ts +25 -0
- package/dist/core/delegation-child.js +37 -0
- package/dist/core/dispatch.d.ts +42 -0
- package/dist/core/dispatch.js +131 -56
- package/dist/core/engine-native.js +1 -1
- package/dist/core/error-code.d.ts +38 -0
- package/dist/core/error-code.js +168 -0
- package/dist/core/grants.d.ts +19 -6
- package/dist/core/grants.js +55 -6
- package/dist/core/host-events.d.ts +18 -2
- package/dist/core/host-events.js +284 -24
- package/dist/core/host-status.d.ts +85 -3
- package/dist/core/host-status.js +298 -20
- package/dist/core/issue-bridge.d.ts +50 -6
- package/dist/core/issue-bridge.js +124 -33
- package/dist/core/issue-events.d.ts +1 -1
- package/dist/core/issue-events.js +4 -10
- package/dist/core/issue-status.d.ts +1 -1
- package/dist/core/issue-status.js +54 -26
- package/dist/core/logger.d.ts +5 -0
- package/dist/core/logger.js +14 -1
- package/dist/core/lost-run-children.d.ts +6 -0
- package/dist/core/lost-run-children.js +32 -0
- package/dist/core/mark-ready.d.ts +34 -0
- package/dist/core/mark-ready.js +152 -0
- package/dist/core/markdown-code.d.ts +8 -0
- package/dist/core/markdown-code.js +107 -0
- package/dist/core/memory-tools.js +7 -0
- package/dist/core/merge-order-check.d.ts +2 -1
- package/dist/core/merge-order-check.js +8 -1
- package/dist/core/notification-templates.d.ts +1 -7
- package/dist/core/notification-templates.js +36 -2
- package/dist/core/pull-request-state-sync.js +2 -2
- package/dist/core/reconciler.d.ts +4 -3
- package/dist/core/reconciler.js +8 -1
- package/dist/core/related-pull-requests.d.ts +11 -3
- package/dist/core/related-pull-requests.js +27 -14
- package/dist/core/reply-parts.d.ts +21 -0
- package/dist/core/reply-parts.js +73 -0
- package/dist/core/review-fix.d.ts +1 -1
- package/dist/core/review-fix.js +2 -1
- package/dist/core/review-host-checks.d.ts +1 -1
- package/dist/core/review-host-tools.d.ts +3 -0
- package/dist/core/review-host-tools.js +99 -13
- package/dist/core/review-reads.d.ts +55 -0
- package/dist/core/review-reads.js +233 -0
- package/dist/core/run-status.d.ts +12 -0
- package/dist/core/run-status.js +12 -0
- package/dist/core/runner.d.ts +80 -34
- package/dist/core/runner.js +439 -144
- package/dist/core/schedule-start.d.ts +12 -0
- package/dist/core/schedule-start.js +14 -0
- package/dist/core/secret-broker-config.d.ts +10 -74
- package/dist/core/secret-guard.d.ts +46 -0
- package/dist/core/secret-guard.js +104 -0
- package/dist/core/secret-guarded-stores.d.ts +12 -0
- package/dist/core/secret-guarded-stores.js +41 -0
- package/dist/core/secret-spans.d.ts +50 -0
- package/dist/core/secret-spans.js +94 -0
- package/dist/core/stop-run.d.ts +46 -0
- package/dist/core/stop-run.js +61 -0
- package/dist/core/workflow-events.d.ts +14 -2
- package/dist/core/workflow-events.js +4 -0
- package/dist/core/workflow-run-events.d.ts +5 -3
- package/dist/core/workflow-run-events.js +43 -8
- package/dist/core/zod-json-schema.d.ts +4 -0
- package/dist/core/zod-json-schema.js +39 -0
- package/dist/core/zod3-window.d.ts +35 -0
- package/dist/core/zod3-window.js +124 -0
- package/dist/generated/prisma/browser.d.ts +34 -3
- package/dist/generated/prisma/client.d.ts +34 -3
- package/dist/generated/prisma/commonInputTypes.d.ts +220 -60
- package/dist/generated/prisma/enums.d.ts +10 -0
- package/dist/generated/prisma/enums.js +8 -0
- package/dist/generated/prisma/internal/class.d.ts +44 -0
- package/dist/generated/prisma/internal/class.js +4 -4
- package/dist/generated/prisma/internal/prismaNamespace.d.ts +428 -9
- package/dist/generated/prisma/internal/prismaNamespace.js +92 -1
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +95 -0
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +92 -1
- package/dist/generated/prisma/models/Agent.d.ts +106 -8
- package/dist/generated/prisma/models/AgentRepository.d.ts +42 -1
- package/dist/generated/prisma/models/CodingAgentProfile.d.ts +229 -1
- package/dist/generated/prisma/models/CodingProxyRequest.d.ts +207 -3
- package/dist/generated/prisma/models/CodingProxySession.d.ts +114 -1
- package/dist/generated/prisma/models/CodingRun.d.ts +1637 -1
- package/dist/generated/prisma/models/DependencySnapshot.d.ts +1668 -0
- package/dist/generated/prisma/models/DependencySnapshot.js +1 -0
- package/dist/generated/prisma/models/IssuePullRequest.d.ts +6 -2
- package/dist/generated/prisma/models/LocalReview.d.ts +40 -1
- package/dist/generated/prisma/models/NativeGatewayCall.d.ts +39 -1
- package/dist/generated/prisma/models/NativeGatewaySession.d.ts +35 -1
- package/dist/generated/prisma/models/RegistryAllowance.d.ts +41 -1
- package/dist/generated/prisma/models/RegistryApprovedVersion.d.ts +75 -1
- package/dist/generated/prisma/models/RegistryAuditRecord.d.ts +1364 -0
- package/dist/generated/prisma/models/RegistryAuditRecord.js +1 -0
- package/dist/generated/prisma/models/Run.d.ts +738 -2
- package/dist/generated/prisma/models/RunBudgetEvent.d.ts +1211 -0
- package/dist/generated/prisma/models/RunBudgetEvent.js +1 -0
- package/dist/generated/prisma/models/RunTranscript.d.ts +1339 -0
- package/dist/generated/prisma/models/RunTranscript.js +1 -0
- package/dist/generated/prisma/models.d.ts +4 -0
- package/dist/help-index.json +1320 -69
- package/dist/import/create.js +32 -3
- package/dist/import/neutral-schema.d.ts +50 -281
- package/dist/import/neutral-schema.js +5 -4
- package/dist/knowledge/check.d.ts +1 -1
- package/dist/knowledge/cli.d.ts +11 -1
- package/dist/knowledge/cli.js +37 -4
- package/dist/knowledge/concept.d.ts +3 -19
- package/dist/knowledge/location.d.ts +31 -0
- package/dist/knowledge/location.js +78 -0
- package/dist/mcp/auth/access.d.ts +10 -2
- package/dist/mcp/auth/access.js +15 -1
- package/dist/mcp/auth/ownership.d.ts +7 -7
- package/dist/mcp/auth/self-hosted/credentials.d.ts +3 -3
- package/dist/mcp/auth/self-hosted/session.d.ts +2 -2
- package/dist/mcp/host-events/github-ingress.d.ts +2 -0
- package/dist/mcp/host-events/github-ingress.js +2 -1
- package/dist/mcp/index.d.ts +6 -0
- package/dist/mcp/index.js +58 -10
- package/dist/mcp/server.d.ts +13 -0
- package/dist/mcp/server.js +33 -14
- package/dist/mcp/tools/agents.d.ts +4 -0
- package/dist/mcp/tools/agents.js +198 -16
- package/dist/mcp/tools/datastore.js +17 -8
- package/dist/mcp/tools/grants.js +9 -3
- package/dist/mcp/tools/memory.js +11 -1
- package/dist/mcp/tools/notification-channels.js +2 -2
- package/dist/mcp/tools/packages.d.ts +2 -0
- package/dist/mcp/tools/packages.js +195 -0
- package/dist/mcp/tools/repositories.js +18 -5
- package/dist/mcp/tools/run-access.d.ts +16 -0
- package/dist/mcp/tools/run-access.js +25 -0
- package/dist/mcp/tools/run-transcript-download.d.ts +30 -0
- package/dist/mcp/tools/run-transcript-download.js +129 -0
- package/dist/mcp/tools/run-transcript.d.ts +19 -0
- package/dist/mcp/tools/run-transcript.js +70 -0
- package/dist/mcp/tools/runs.js +34 -12
- package/dist/mcp/tools/scheduling.js +3 -1
- package/dist/mcp/tools/secret-elicitation-form.js +5 -0
- package/dist/mcp/tools/secret-elicitation-server.d.ts +6 -0
- package/dist/mcp/tools/secret-elicitation-server.js +10 -61
- package/dist/mcp/tools/secrets.js +2 -2
- package/dist/mcp/tools/stop-run.d.ts +2 -0
- package/dist/mcp/tools/stop-run.js +82 -0
- package/dist/mcp/tools/subagents.js +2 -2
- package/dist/mcp/tools/tools.d.ts +1 -1
- package/dist/mcp/tools/tools.js +25 -9
- package/dist/mcp/transport/stdio-link-host.d.ts +20 -0
- package/dist/mcp/transport/stdio-link-host.js +67 -0
- package/dist/mcp/transport/streamable-http.d.ts +13 -0
- package/dist/mcp/transport/streamable-http.js +41 -1
- package/dist/native-worker/docker-launcher.d.ts +38 -2
- package/dist/native-worker/docker-launcher.js +65 -5
- package/dist/native-worker/gateway-server.d.ts +41 -3
- package/dist/native-worker/gateway-server.js +124 -27
- package/dist/native-worker/gateway.d.ts +27 -1
- package/dist/native-worker/gateway.js +70 -4
- package/dist/native-worker/http-server.d.ts +3 -1
- package/dist/native-worker/http-server.js +11 -7
- package/dist/native-worker/http-transport.js +1 -1
- package/dist/native-worker/kubernetes-launcher.d.ts +7 -3
- package/dist/native-worker/kubernetes-launcher.js +43 -3
- package/dist/native-worker/launch.d.ts +18 -1
- package/dist/native-worker/launch.js +6 -1
- package/dist/native-worker/ledger.d.ts +18 -2
- package/dist/native-worker/ledger.js +57 -4
- package/dist/native-worker/loopback.js +3 -8
- package/dist/native-worker/main.d.ts +2 -1
- package/dist/native-worker/main.js +14 -3
- package/dist/native-worker/protocol.d.ts +129 -577
- package/dist/native-worker/protocol.js +47 -1
- package/dist/native-worker/sandbox-executor.d.ts +17 -2
- package/dist/native-worker/sandbox-executor.js +78 -4
- package/dist/native-worker/stderr-redaction.d.ts +77 -0
- package/dist/native-worker/stderr-redaction.js +237 -0
- package/dist/native-worker/stdio.d.ts +1 -1
- package/dist/native-worker/stdio.js +6 -5
- package/dist/native-worker/warm-pool.d.ts +2 -2
- package/dist/native-worker/warm-pool.js +4 -4
- package/dist/native-worker/worker.js +28 -2
- package/dist/observability/metrics.js +6 -1
- package/dist/providers/auth/self-hosted.d.ts +1 -1
- package/dist/providers/coding-proxy/dependency-cache/eviction.d.ts +29 -0
- package/dist/providers/coding-proxy/dependency-cache/eviction.js +87 -0
- package/dist/providers/coding-proxy/dependency-cache/key.d.ts +33 -0
- package/dist/providers/coding-proxy/dependency-cache/key.js +86 -0
- package/dist/providers/coding-proxy/dependency-cache/model-request-seal.d.ts +18 -0
- package/dist/providers/coding-proxy/dependency-cache/model-request-seal.js +40 -0
- package/dist/providers/coding-proxy/dependency-cache/prisma-store.d.ts +25 -0
- package/dist/providers/coding-proxy/dependency-cache/prisma-store.js +131 -0
- package/dist/providers/coding-proxy/dependency-cache/service.d.ts +70 -0
- package/dist/providers/coding-proxy/dependency-cache/service.js +409 -0
- package/dist/providers/coding-proxy/dependency-cache/store.d.ts +85 -0
- package/dist/providers/coding-proxy/dependency-cache/store.js +95 -0
- package/dist/providers/coding-proxy/hold.d.ts +32 -0
- package/dist/providers/coding-proxy/hold.js +86 -0
- package/dist/providers/coding-proxy/interrupted.d.ts +70 -0
- package/dist/providers/coding-proxy/interrupted.js +104 -0
- package/dist/providers/coding-proxy/memory-ledger.d.ts +15 -2
- package/dist/providers/coding-proxy/memory-ledger.js +84 -8
- package/dist/providers/coding-proxy/metering.d.ts +16 -0
- package/dist/providers/coding-proxy/metering.js +27 -4
- package/dist/providers/coding-proxy/prisma-ledger.d.ts +15 -2
- package/dist/providers/coding-proxy/prisma-ledger.js +179 -67
- package/dist/providers/coding-proxy/proxy.d.ts +14 -1
- package/dist/providers/coding-proxy/proxy.js +166 -14
- package/dist/providers/coding-proxy/registry/audit.d.ts +53 -10
- package/dist/providers/coding-proxy/registry/audit.js +94 -17
- package/dist/providers/coding-proxy/registry/lockfile-parse-worker.d.ts +36 -0
- package/dist/providers/coding-proxy/registry/lockfile-parse-worker.js +67 -0
- package/dist/providers/coding-proxy/registry/lockfile-parse.d.ts +16 -0
- package/dist/providers/coding-proxy/registry/lockfile-parse.js +110 -0
- package/dist/providers/coding-proxy/registry/plan.d.ts +9 -0
- package/dist/providers/coding-proxy/registry/plan.js +201 -48
- package/dist/providers/coding-proxy/registry/prisma-store.d.ts +11 -5
- package/dist/providers/coding-proxy/registry/prisma-store.js +120 -17
- package/dist/providers/coding-proxy/registry/service.d.ts +97 -16
- package/dist/providers/coding-proxy/registry/service.js +579 -112
- package/dist/providers/coding-proxy/registry/store.d.ts +84 -9
- package/dist/providers/coding-proxy/registry/store.js +54 -1
- package/dist/providers/coding-proxy/runtime.d.ts +4 -0
- package/dist/providers/coding-proxy/runtime.js +78 -16
- package/dist/providers/coding-proxy/server.d.ts +6 -1
- package/dist/providers/coding-proxy/server.js +45 -1
- package/dist/providers/coding-proxy/types.d.ts +65 -1
- package/dist/providers/datastore/types.d.ts +5 -0
- package/dist/providers/engine/types.d.ts +17 -2
- package/dist/providers/engine/types.js +17 -0
- package/dist/providers/executor/composition.d.ts +10 -3
- package/dist/providers/executor/composition.js +47 -7
- package/dist/providers/executor/container.d.ts +361 -8
- package/dist/providers/executor/container.js +1136 -91
- package/dist/providers/executor/dbos-status.d.ts +31 -0
- package/dist/providers/executor/dbos-status.js +64 -0
- package/dist/providers/executor/dbos.d.ts +50 -1
- package/dist/providers/executor/dbos.js +207 -20
- package/dist/providers/executor/deferred.d.ts +12 -24
- package/dist/providers/executor/deferred.js +10 -29
- package/dist/providers/executor/in-process.d.ts +9 -1
- package/dist/providers/executor/in-process.js +14 -4
- package/dist/providers/executor/linked-issue-reader.d.ts +16 -0
- package/dist/providers/executor/linked-issue-reader.js +30 -0
- package/dist/providers/executor/routing.d.ts +2 -0
- package/dist/providers/executor/routing.js +4 -0
- package/dist/providers/executor/types.d.ts +13 -0
- package/dist/providers/index.d.ts +2 -1
- package/dist/providers/issue-tracker/types.d.ts +17 -0
- package/dist/providers/issue-tracker/types.js +39 -1
- package/dist/providers/jobs/claude-tool-setup.d.ts +4 -0
- package/dist/providers/jobs/claude-tool-setup.js +5 -2
- package/dist/providers/jobs/docker-isolation.d.ts +8 -0
- package/dist/providers/jobs/docker-isolation.js +17 -0
- package/dist/providers/jobs/docker.d.ts +1 -0
- package/dist/providers/jobs/docker.js +27 -2
- package/dist/providers/jobs/fake-kubernetes-api.d.ts +2 -0
- package/dist/providers/jobs/fake-kubernetes-api.js +3 -0
- package/dist/providers/jobs/kubernetes-api.d.ts +8 -0
- package/dist/providers/jobs/kubernetes-api.js +10 -0
- package/dist/providers/jobs/kubernetes-client.js +10 -1
- package/dist/providers/jobs/kubernetes-isolation.d.ts +2 -0
- package/dist/providers/jobs/kubernetes-isolation.js +17 -1
- package/dist/providers/jobs/kubernetes.d.ts +6 -1
- package/dist/providers/jobs/kubernetes.js +105 -16
- package/dist/providers/jobs/types.d.ts +33 -0
- package/dist/providers/jobs/types.js +13 -1
- package/dist/providers/llm/anthropic.js +5 -1
- package/dist/providers/llm/bedrock.d.ts +0 -12
- package/dist/providers/llm/bedrock.js +4 -0
- package/dist/providers/llm/claude-messages.d.ts +3 -1
- package/dist/providers/llm/claude-messages.js +4 -1
- package/dist/providers/llm/claude-provider.d.ts +2 -2
- package/dist/providers/llm/claude-provider.js +2 -2
- package/dist/providers/llm/openai.d.ts +2 -2
- package/dist/providers/llm/openai.js +6 -2
- package/dist/providers/llm/routing.d.ts +2 -2
- package/dist/providers/llm/routing.js +2 -2
- package/dist/providers/llm/types.d.ts +12 -2
- package/dist/providers/review-host/github-events.d.ts +9 -1
- package/dist/providers/review-host/github-events.js +34 -12
- package/dist/providers/review-host/github.d.ts +29 -2
- package/dist/providers/review-host/github.js +224 -29
- package/dist/providers/review-host/local.d.ts +8 -4
- package/dist/providers/review-host/local.js +49 -31
- package/dist/providers/review-host/patch-pages.d.ts +41 -0
- package/dist/providers/review-host/patch-pages.js +176 -0
- package/dist/providers/review-host/review-format.d.ts +19 -1
- package/dist/providers/review-host/review-format.js +38 -7
- package/dist/providers/review-host/types.d.ts +134 -2
- package/dist/providers/review-host/types.js +8 -0
- package/dist/providers/storage/gcs.d.ts +63 -0
- package/dist/providers/storage/gcs.js +69 -0
- package/dist/providers/storage/index.d.ts +15 -0
- package/dist/providers/storage/index.js +14 -0
- package/dist/providers/storage/local.d.ts +24 -0
- package/dist/providers/storage/local.js +92 -0
- package/dist/providers/storage/meter.d.ts +14 -0
- package/dist/providers/storage/meter.js +34 -0
- package/dist/providers/storage/s3.d.ts +41 -0
- package/dist/providers/storage/s3.js +107 -0
- package/dist/providers/storage/types.d.ts +37 -3
- package/dist/providers/storage/types.js +21 -4
- package/dist/providers/vcs/git.d.ts +33 -2
- package/dist/providers/vcs/git.js +324 -22
- package/dist/providers/vcs/github-remote.d.ts +26 -3
- package/dist/providers/vcs/github-remote.js +124 -14
- package/dist/providers/vcs/github.d.ts +214 -6
- package/dist/providers/vcs/github.js +447 -45
- package/dist/providers/vcs/index.js +8 -1
- package/dist/providers/vcs/pr-description.d.ts +127 -0
- package/dist/providers/vcs/pr-description.js +270 -0
- package/dist/providers/vcs/remote.d.ts +33 -1
- package/dist/providers/vcs/routing.d.ts +4 -2
- package/dist/providers/vcs/routing.js +6 -0
- package/dist/providers/vcs/types.d.ts +80 -3
- package/dist/quickstart/coding-seed.d.ts +14 -3
- package/dist/quickstart/coding-seed.js +213 -4
- package/dist/quickstart/reviewer-prompt.d.ts +1 -1
- package/dist/quickstart/reviewer-prompt.js +6 -6
- package/dist/quickstart-images.json +1 -1
- package/dist/sandbox/generated/zod.bundle.js +62 -1
- package/dist/sandbox/generated/zod3.bundle.js +5 -0
- package/dist/sandbox/host-functions.d.ts +1 -0
- package/dist/sandbox/host-functions.js +6 -11
- package/dist/sandbox/secret-broker.js +3 -4
- package/dist/sandbox/tool-capabilities.d.ts +10 -30
- package/dist/sandbox/zod-params.d.ts +37 -5
- package/dist/sandbox/zod-params.js +181 -26
- package/dist/serve.js +8 -1
- package/dist/viewer/api-schema.d.ts +349 -2627
- package/dist/viewer/api-schema.js +7 -0
- package/dist/viewer/build-schemas.js +6 -2
- package/dist/viewer/openapi.js +2 -4
- package/dist/wardby-bin.js +10 -0
- package/docs/agent-recipes.md +58 -20
- package/docs/code-review-agents.md +551 -41
- package/docs/coding-agent-setup.md +489 -7
- package/docs/coding-packages.md +1033 -90
- package/docs/coding-services.md +27 -12
- package/docs/coding-worker-byo-images.md +20 -0
- package/docs/coding-worker-isolation.md +105 -30
- package/docs/getting-started-gke.md +124 -137
- package/docs/getting-started-identity-provider.md +10 -1
- package/docs/getting-started.md +9 -0
- package/docs/jira-agents.md +17 -8
- package/docs/knowledge/blob-not-found-semantics.md +41 -0
- package/docs/knowledge/bounded-file-descriptor.md +52 -0
- package/docs/knowledge/delegation-admission-slots.md +52 -0
- package/docs/knowledge/http-dispatcher-bootstrap.md +61 -0
- package/docs/knowledge/import-rename-target.md +52 -0
- package/docs/knowledge/index.md +57 -0
- package/docs/knowledge/log.md +9 -0
- package/docs/knowledge/mcp-cancelled-task-precedence.md +41 -0
- package/docs/knowledge/mcp-http-prompt-discovery.md +41 -0
- package/docs/knowledge/mcp-schema-module-boundary.md +41 -0
- package/docs/knowledge/nullable-tool-uniqueness.md +52 -0
- package/docs/knowledge/overview-cli.md +74 -0
- package/docs/knowledge/overview-coding-proxy.md +72 -0
- package/docs/knowledge/overview-coding.md +72 -0
- package/docs/knowledge/overview-config.md +60 -0
- package/docs/knowledge/overview-core.md +72 -0
- package/docs/knowledge/overview-deploy.md +83 -0
- package/docs/knowledge/overview-mcp.md +61 -0
- package/docs/knowledge/overview-prisma.md +72 -0
- package/docs/knowledge/overview-providers.md +89 -0
- package/docs/knowledge/overview-sandbox.md +83 -0
- package/docs/knowledge/overview-surfaces.md +83 -0
- package/docs/knowledge/private-datastore-shared-boundary.md +54 -0
- package/docs/knowledge/proxy-settlement-projections.md +41 -0
- package/docs/knowledge/run-pricing-first-writer.md +43 -0
- package/docs/knowledge/schedule-claim-transaction.md +41 -0
- package/docs/knowledge/secret-entry-replica-contract.md +52 -0
- package/docs/knowledge/terminal-run-first-writer.md +41 -0
- package/docs/knowledge/transactional-agent-authorization.md +52 -0
- package/docs/knowledge/viewer-notify-write-isolation.md +30 -0
- package/docs/knowledge/warm-input-publication.md +30 -0
- package/docs/knowledge/workspace-staging-failure.md +54 -0
- package/docs/knowledge.md +54 -14
- package/docs/native-sandbox.md +119 -23
- package/docs/observability.md +4 -0
- package/docs/security-deployment.md +33 -2
- package/docs/slack-notifications.md +35 -19
- package/docs/tool-secrets.md +42 -10
- package/docs/viewer-api.openapi.yml +1 -1
- package/help/admin-viewer.md +24 -2
- package/help/architecture-agent.md +11 -6
- package/help/build-worker-image.md +3 -1
- package/help/builder-agent.md +19 -6
- package/help/code-review-agents.md +177 -6
- package/help/coding-packages.md +156 -6
- package/help/cost-attribution.md +9 -5
- package/help/creating-agents.md +14 -1
- package/help/dependency-cache.md +143 -0
- package/help/deploy-gke.md +15 -8
- package/help/errors/budget-exhausted.md +187 -0
- package/help/errors/budget-group-exhausted.md +5 -1
- package/help/errors/coding-stream-quota-exceeded.md +33 -0
- package/help/errors/coding-workspace-disk-exceeds-limit.md +39 -0
- package/help/errors/continuation-closed.md +9 -12
- package/help/errors/delegation-conflict.md +24 -0
- package/help/errors/file-list-truncated.md +45 -0
- package/help/errors/full-read-required.md +49 -0
- package/help/errors/github-account-not-allowed.md +4 -0
- package/help/errors/graph-incomplete.md +43 -0
- package/help/errors/graph-limit.md +43 -0
- package/help/errors/merge-conflict.md +49 -0
- package/help/errors/native-sandbox-gateway-error.md +20 -0
- package/help/errors/native-sandbox-launch-abandoned.md +21 -0
- package/help/errors/native-sandbox-worker-exited.md +3 -2
- package/help/errors/package-not-allowed-pending.md +52 -0
- package/help/errors/package-not-allowed.md +51 -0
- package/help/errors/package-refusals.md +43 -0
- package/help/errors/patch-cursor-malformed.md +31 -0
- package/help/errors/run-lost.md +39 -0
- package/help/errors/run-transcript-download.md +44 -0
- package/help/errors/secret-in-value.md +69 -0
- package/help/errors/target-merge.md +40 -0
- package/help/fix-merge-conflicts.md +73 -0
- package/help/github-app-setup.md +132 -0
- package/help/github.md +22 -0
- package/help/identity-and-access.md +2 -0
- package/help/jira.md +23 -6
- package/help/knowledge.md +16 -7
- package/help/local-repositories.md +1 -1
- package/help/low-power-mode.md +7 -1
- package/help/merge-order-check.md +5 -0
- package/help/native-capabilities.md +29 -1
- package/help/native-sandbox.md +15 -1
- package/help/operating-agents.md +5 -0
- package/help/package-audit-mode.md +91 -0
- package/help/related-pull-requests.md +10 -7
- package/help/resume-runs-locally.md +131 -0
- package/help/review-fix-rounds.md +33 -1
- package/help/sharing-agents.md +59 -0
- package/help/slack-notifications.md +19 -5
- package/help/status-comments.md +31 -0
- package/help/stop-run.md +58 -0
- package/help/troubleshooting/budgets.md +28 -0
- package/help/troubleshooting/coding-workers.md +78 -7
- package/package.json +17 -10
- package/prisma/migrations/20261016000000_registry_approved_accepted_advisories/migration.sql +3 -0
- package/prisma/migrations/20261016000001_mark_ready_when_passing/migration.sql +3 -0
- package/prisma/migrations/20261017000000_debug_trace_max_mb/migration.sql +4 -0
- package/prisma/migrations/20261018000000_dependency_cache/migration.sql +53 -0
- package/prisma/migrations/20261019000000_claude_max_output_tokens/migration.sql +5 -0
- package/prisma/migrations/20261021000000_pr_description_hash/migration.sql +4 -0
- package/prisma/migrations/20261022000000_dependency_snapshot_validation_metadata/migration.sql +4 -0
- package/prisma/migrations/20261023000000_package_audit_mode/migration.sql +27 -0
- package/prisma/migrations/20261024000000_budget_events/migration.sql +26 -0
- package/prisma/migrations/20261025000000_cache_aware_holds/migration.sql +9 -0
- package/prisma/migrations/20261026000000_budget_overrun/migration.sql +4 -0
- package/prisma/migrations/20261027000000_coding_run_request/migration.sql +1 -0
- package/prisma/migrations/20261028000000_native_gateway_review_reads/migration.sql +6 -0
- package/prisma/migrations/20261029000000_lead_survives_deploy/migration.sql +6 -0
- package/prisma/migrations/20261030000000_local_review_covered_sha/migration.sql +2 -0
- package/prisma/migrations/20261031000000_coding_run_budget_limit/migration.sql +9 -0
- package/prisma/migrations/20261101000000_run_transcripts/migration.sql +22 -0
- package/prisma/schema.prisma +311 -97
- package/dist/sandbox/generated/zod-to-json-schema.bundle.js +0 -5
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { pipeline, Transform } from "node:stream";
|
|
3
|
+
import { logger } from "../../../core/logger.js";
|
|
4
|
+
import { BlobStoreError } from "../../storage/types.js";
|
|
5
|
+
import { capabilityHash } from "../proxy.js";
|
|
6
|
+
import { DEPENDENCY_CACHE_ECOSYSTEM, deriveSnapshotKey, isLockHash, newBlobKey, parseProjectPath, } from "./key.js";
|
|
7
|
+
const cacheLog = logger.child({ module: "dependency-cache" });
|
|
8
|
+
const ARCHIVE_SCAN_VERSION = 1;
|
|
9
|
+
const MAX_ARCHIVE_ENTRIES = 1_000_000;
|
|
10
|
+
/** Uploads streaming at once across every run. */
|
|
11
|
+
export const DEFAULT_MAX_CONCURRENT_UPLOADS = 4;
|
|
12
|
+
const SNAPSHOT_ID = /^[a-z0-9]{1,64}$/;
|
|
13
|
+
/** Matches a `/cache/...` path; a number is the refusal's status code. */
|
|
14
|
+
function matchRoute(method, pathname) {
|
|
15
|
+
const prefix = `/cache/${DEPENDENCY_CACHE_ECOSYSTEM}/`;
|
|
16
|
+
if (!pathname.startsWith(prefix))
|
|
17
|
+
return 404;
|
|
18
|
+
const rest = pathname.slice(prefix.length);
|
|
19
|
+
const expect = (wanted, route, id) => (method === wanted ? { route, id } : 405);
|
|
20
|
+
if (rest === "lookup")
|
|
21
|
+
return expect("GET", "lookup");
|
|
22
|
+
if (rest === "seal")
|
|
23
|
+
return expect("POST", "seal");
|
|
24
|
+
if (rest === "blob")
|
|
25
|
+
return expect("PUT", "upload");
|
|
26
|
+
if (rest.startsWith("blob/")) {
|
|
27
|
+
const id = rest.slice("blob/".length);
|
|
28
|
+
return SNAPSHOT_ID.test(id) ? expect("GET", "download", id) : 404;
|
|
29
|
+
}
|
|
30
|
+
return 404;
|
|
31
|
+
}
|
|
32
|
+
class CacheRefusal extends Error {
|
|
33
|
+
status;
|
|
34
|
+
code;
|
|
35
|
+
constructor(status, code) {
|
|
36
|
+
super(code);
|
|
37
|
+
this.status = status;
|
|
38
|
+
this.code = code;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function send(response, status, body, close = false) {
|
|
42
|
+
if (response.headersSent || response.destroyed)
|
|
43
|
+
return;
|
|
44
|
+
response.writeHead(status, {
|
|
45
|
+
"content-type": "application/json",
|
|
46
|
+
"cache-control": "no-store",
|
|
47
|
+
...(close ? { connection: "close" } : {}),
|
|
48
|
+
});
|
|
49
|
+
response.end(JSON.stringify(body));
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The archive scan the worker ran before uploading, recorded with the
|
|
53
|
+
* snapshot. Protocol: an upload carries `x-wardby-archive-scan-version`,
|
|
54
|
+
* `x-wardby-archive-entries` and `x-wardby-archive-unpacked-bytes`, all three
|
|
55
|
+
* or none (a partial or malformed set is refused). Only an upload accepted
|
|
56
|
+
* before the run's seal stores them, so they come from trusted pre-agent
|
|
57
|
+
* worker code, never from the agent. A lookup returns them as `manifest` (and
|
|
58
|
+
* `nearest.manifest`) with the sha256 this proxy computed and recorded for the
|
|
59
|
+
* row, so a worker trusts them only for bytes that verify against that digest.
|
|
60
|
+
* Optional so workers deployed before validation metadata remain compatible.
|
|
61
|
+
*/
|
|
62
|
+
function verifiedArchiveHeaders(headers) {
|
|
63
|
+
const version = headers["x-wardby-archive-scan-version"];
|
|
64
|
+
const entries = headers["x-wardby-archive-entries"];
|
|
65
|
+
const unpackedBytes = headers["x-wardby-archive-unpacked-bytes"];
|
|
66
|
+
if (version === undefined && entries === undefined && unpackedBytes === undefined)
|
|
67
|
+
return null;
|
|
68
|
+
if (typeof version !== "string" || typeof entries !== "string" || typeof unpackedBytes !== "string")
|
|
69
|
+
throw new CacheRefusal(400, "bad_request");
|
|
70
|
+
const parsedVersion = Number(version);
|
|
71
|
+
const parsedEntries = Number(entries);
|
|
72
|
+
const parsedUnpackedBytes = Number(unpackedBytes);
|
|
73
|
+
if (parsedVersion !== ARCHIVE_SCAN_VERSION ||
|
|
74
|
+
!Number.isSafeInteger(parsedEntries) ||
|
|
75
|
+
parsedEntries < 1 ||
|
|
76
|
+
parsedEntries > MAX_ARCHIVE_ENTRIES ||
|
|
77
|
+
!Number.isSafeInteger(parsedUnpackedBytes) ||
|
|
78
|
+
parsedUnpackedBytes < 0)
|
|
79
|
+
throw new CacheRefusal(400, "bad_request");
|
|
80
|
+
return { archiveScanVersion: parsedVersion, archiveEntries: parsedEntries, unpackedBytes: parsedUnpackedBytes };
|
|
81
|
+
}
|
|
82
|
+
function lookupManifest(row) {
|
|
83
|
+
const { archiveScanVersion, archiveEntries, unpackedBytes } = row;
|
|
84
|
+
if (archiveScanVersion !== ARCHIVE_SCAN_VERSION ||
|
|
85
|
+
archiveEntries === null ||
|
|
86
|
+
unpackedBytes === null ||
|
|
87
|
+
!Number.isSafeInteger(archiveEntries) ||
|
|
88
|
+
archiveEntries < 1 ||
|
|
89
|
+
archiveEntries > MAX_ARCHIVE_ENTRIES ||
|
|
90
|
+
!Number.isSafeInteger(unpackedBytes) ||
|
|
91
|
+
unpackedBytes < 0)
|
|
92
|
+
return undefined;
|
|
93
|
+
return { scanVersion: ARCHIVE_SCAN_VERSION, sha256: row.sha256, entries: archiveEntries, unpackedBytes };
|
|
94
|
+
}
|
|
95
|
+
export class DependencyCacheService {
|
|
96
|
+
options;
|
|
97
|
+
uploadMs;
|
|
98
|
+
now;
|
|
99
|
+
/** `runId\0projectPath` and key ids of uploads streaming right now (per replica). */
|
|
100
|
+
uploading = new Set();
|
|
101
|
+
activeUploads = 0;
|
|
102
|
+
constructor(options) {
|
|
103
|
+
this.options = options;
|
|
104
|
+
this.uploadMs = options.uploadMs;
|
|
105
|
+
this.now = options.now ?? (() => new Date());
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Answers one `/cache/...` request. `token` is the registry token the
|
|
109
|
+
* request carried. `releaseBodyTimer` lifts the server's per-request body
|
|
110
|
+
* timer; it is called only for an authenticated, eligible upload that holds
|
|
111
|
+
* its upload slot (the upload window bounds it from then on).
|
|
112
|
+
*/
|
|
113
|
+
async handle(request, response, token, releaseBodyTimer = () => { }) {
|
|
114
|
+
const url = new URL(request.url ?? "/", "http://proxy");
|
|
115
|
+
const matched = matchRoute(request.method, url.pathname);
|
|
116
|
+
const isUpload = typeof matched === "object" && matched.route === "upload";
|
|
117
|
+
// Filled in once the token is known to belong to a run, for the refusal log.
|
|
118
|
+
const known = {};
|
|
119
|
+
try {
|
|
120
|
+
if (matched === 404)
|
|
121
|
+
throw new CacheRefusal(404, "not_found");
|
|
122
|
+
if (matched === 405)
|
|
123
|
+
throw new CacheRefusal(405, "method_not_allowed");
|
|
124
|
+
await this.route(matched.route, matched.id, url.searchParams, token, request, response, releaseBodyTimer, known);
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
if (error instanceof CacheRefusal) {
|
|
128
|
+
// One line per refusal: a fixed route name and code, and the run when the token named one
|
|
129
|
+
// (never the token, the query or the URL).
|
|
130
|
+
cacheLog.info({
|
|
131
|
+
event: "dependency_cache.refused",
|
|
132
|
+
...(known.runId ? { runId: known.runId } : {}),
|
|
133
|
+
route: typeof matched === "object" ? matched.route : "unknown",
|
|
134
|
+
status: error.status,
|
|
135
|
+
code: error.code,
|
|
136
|
+
}, "dependency cache request refused");
|
|
137
|
+
// An upload refused before its body was read: close rather than read it.
|
|
138
|
+
send(response, error.status, { error: error.code }, isUpload);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
cacheLog.error({ event: "dependency_cache.failed", err: error }, "dependency cache request failed");
|
|
142
|
+
if (!response.headersSent)
|
|
143
|
+
send(response, 500, { error: "internal_error" }, isUpload);
|
|
144
|
+
else if (!response.destroyed)
|
|
145
|
+
response.destroy();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
async route(route, id, params, token, request, response, releaseBodyTimer, known) {
|
|
149
|
+
const context = await this.options.registry.findRunByRegistryTokenHash(capabilityHash(token), this.now());
|
|
150
|
+
if (!context)
|
|
151
|
+
throw new CacheRefusal(401, "invalid_capability");
|
|
152
|
+
known.runId = context.runId;
|
|
153
|
+
// The feature is off for this run: indistinguishable from a proxy without it.
|
|
154
|
+
if (!context.dependencyCache || !context.cacheImage)
|
|
155
|
+
throw new CacheRefusal(404, "not_found");
|
|
156
|
+
const session = await this.options.store.session(context.runId);
|
|
157
|
+
if (!session)
|
|
158
|
+
throw new CacheRefusal(401, "invalid_capability");
|
|
159
|
+
if (route === "seal") {
|
|
160
|
+
await this.options.store.seal(context.runId, this.now());
|
|
161
|
+
send(response, 200, { sealed: true });
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (session.sealedAt)
|
|
165
|
+
throw new CacheRefusal(409, "sealed");
|
|
166
|
+
for (const name of new Set(params.keys()))
|
|
167
|
+
if (params.getAll(name).length > 1)
|
|
168
|
+
throw new CacheRefusal(400, "bad_request");
|
|
169
|
+
const projectPath = parseProjectPath(params.get("project"));
|
|
170
|
+
if (projectPath === null)
|
|
171
|
+
throw new CacheRefusal(400, "invalid_project");
|
|
172
|
+
const lockHash = params.get("lock") ?? "";
|
|
173
|
+
if (!isLockHash(lockHash) || !session.lockHashes.includes(lockHash))
|
|
174
|
+
throw new CacheRefusal(409, "plan_required");
|
|
175
|
+
const key = deriveSnapshotKey({
|
|
176
|
+
repository: context.repository,
|
|
177
|
+
projectPath,
|
|
178
|
+
lockHash,
|
|
179
|
+
workerImage: context.cacheImage,
|
|
180
|
+
});
|
|
181
|
+
if (route === "lookup")
|
|
182
|
+
await this.lookup(key, session.createdAt, response);
|
|
183
|
+
else if (route === "download")
|
|
184
|
+
await this.download(context.runId, key, id ?? "", response);
|
|
185
|
+
else
|
|
186
|
+
await this.upload(context, session.createdAt, key, request, response, releaseBodyTimer);
|
|
187
|
+
}
|
|
188
|
+
/** When the run's upload window closes (epoch ms): the one deadline lookups report and uploads enforce. */
|
|
189
|
+
uploadDeadline(sessionCreatedAt) {
|
|
190
|
+
return sessionCreatedAt.getTime() + this.uploadMs;
|
|
191
|
+
}
|
|
192
|
+
async lookup(key, sessionCreatedAt, response) {
|
|
193
|
+
const row = await this.options.store.findByIdentity(key);
|
|
194
|
+
const nearest = row ? null : await this.options.store.findNearest(key, key.lockHash);
|
|
195
|
+
const manifest = row ? lookupManifest(row) : undefined;
|
|
196
|
+
const nearestManifest = nearest ? lookupManifest(nearest) : undefined;
|
|
197
|
+
send(response, 200, {
|
|
198
|
+
key: key.keyId,
|
|
199
|
+
hit: row !== null,
|
|
200
|
+
...(manifest ? { manifest } : {}),
|
|
201
|
+
...(nearest ? { nearest: { id: nearest.id, ...(nearestManifest ? { manifest: nearestManifest } : {}) } } : {}),
|
|
202
|
+
maxBytes: this.options.maxSnapshotBytes,
|
|
203
|
+
uploadExpiresInMs: Math.max(0, Math.floor(this.uploadDeadline(sessionCreatedAt) - this.now().getTime())),
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Serves the run's derived key (by its keyId) or the snapshot a lookup
|
|
208
|
+
* offers as nearest for the same project and lockfile, recomputed now with
|
|
209
|
+
* the same query: nothing is remembered between requests, and no other id
|
|
210
|
+
* is ever served.
|
|
211
|
+
*/
|
|
212
|
+
async download(runId, key, id, response) {
|
|
213
|
+
let row = null;
|
|
214
|
+
if (id === key.keyId)
|
|
215
|
+
row = await this.options.store.findByIdentity(key);
|
|
216
|
+
else {
|
|
217
|
+
const nearest = await this.options.store.findNearest(key, key.lockHash);
|
|
218
|
+
if (nearest?.id === id)
|
|
219
|
+
row = nearest;
|
|
220
|
+
}
|
|
221
|
+
if (!row)
|
|
222
|
+
throw new CacheRefusal(404, "not_found");
|
|
223
|
+
let stream;
|
|
224
|
+
try {
|
|
225
|
+
stream = await this.options.blobs.getStream(row.blobKey);
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
if (!(error instanceof BlobStoreError && error.code === "not_found"))
|
|
229
|
+
throw error;
|
|
230
|
+
// A row without its blob would answer hits forever: drop it.
|
|
231
|
+
await this.options.store.delete(row.id);
|
|
232
|
+
cacheLog.info({ event: "dependency_cache.blob_missing", runId, snapshotId: row.id }, "dependency cache snapshot had no blob and was removed");
|
|
233
|
+
throw new CacheRefusal(404, "not_found");
|
|
234
|
+
}
|
|
235
|
+
try {
|
|
236
|
+
await this.options.store.touch(row.id, this.now());
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
cacheLog.warn({ event: "dependency_cache.touch_failed", err: error }, "dependency cache: lastUsedAt not updated");
|
|
240
|
+
}
|
|
241
|
+
response.writeHead(200, {
|
|
242
|
+
"content-type": "application/gzip",
|
|
243
|
+
"content-length": String(row.sizeBytes),
|
|
244
|
+
"x-wardby-sha256": row.sha256,
|
|
245
|
+
"cache-control": "no-store",
|
|
246
|
+
});
|
|
247
|
+
// Hash and count while streaming. The flush runs only once the blob was
|
|
248
|
+
// read to its end (a client abort destroys the pipeline before it), so only
|
|
249
|
+
// a complete read that disagrees with the row (or a blob longer than it)
|
|
250
|
+
// condemns the snapshot. The response then fails at once (the pipeline
|
|
251
|
+
// destroys it) instead of ending, or leaving the client waiting for the
|
|
252
|
+
// rest of its content-length.
|
|
253
|
+
const hash = createHash("sha256");
|
|
254
|
+
let bytes = 0;
|
|
255
|
+
const corrupt = () => {
|
|
256
|
+
void this.dropCorrupt(runId, row, bytes);
|
|
257
|
+
return new Error("snapshot_corrupt");
|
|
258
|
+
};
|
|
259
|
+
const verify = new Transform({
|
|
260
|
+
transform(chunk, _encoding, callback) {
|
|
261
|
+
bytes += chunk.length;
|
|
262
|
+
if (bytes > row.sizeBytes)
|
|
263
|
+
return callback(corrupt());
|
|
264
|
+
hash.update(chunk);
|
|
265
|
+
callback(null, chunk);
|
|
266
|
+
},
|
|
267
|
+
flush: (callback) => {
|
|
268
|
+
if (bytes === row.sizeBytes && hash.digest("hex") === row.sha256)
|
|
269
|
+
callback();
|
|
270
|
+
else
|
|
271
|
+
callback(corrupt());
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
pipeline(stream, verify, response, (error) => {
|
|
275
|
+
if (error && !response.destroyed)
|
|
276
|
+
response.destroy();
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
/** Removes a snapshot whose blob failed its row's checksum, so the key can be rebuilt. */
|
|
280
|
+
async dropCorrupt(runId, row, readBytes) {
|
|
281
|
+
try {
|
|
282
|
+
// Only while the row still names the blob that was streamed: a re-upload may have replaced it.
|
|
283
|
+
if (!(await this.options.store.deleteIfBlob(row.id, row.blobKey)))
|
|
284
|
+
return;
|
|
285
|
+
await this.discard(row.blobKey);
|
|
286
|
+
cacheLog.warn({
|
|
287
|
+
event: "snapshot_corrupt",
|
|
288
|
+
runId,
|
|
289
|
+
expectedBytes: row.sizeBytes,
|
|
290
|
+
readBytes,
|
|
291
|
+
}, "dependency cache snapshot failed its checksum and was removed");
|
|
292
|
+
}
|
|
293
|
+
catch (error) {
|
|
294
|
+
cacheLog.warn({ event: "dependency_cache.corrupt_cleanup_failed", err: error }, "dependency cache: cleanup failed");
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
async upload(context, sessionCreatedAt, key, request, response, releaseBodyTimer) {
|
|
298
|
+
const deadline = this.uploadDeadline(sessionCreatedAt);
|
|
299
|
+
if (this.now().getTime() >= deadline)
|
|
300
|
+
throw new CacheRefusal(409, "upload_expired");
|
|
301
|
+
const encoding = request.headers["content-encoding"];
|
|
302
|
+
if (encoding && encoding !== "identity")
|
|
303
|
+
throw new CacheRefusal(400, "bad_request");
|
|
304
|
+
const length = request.headers["content-length"];
|
|
305
|
+
if (length !== undefined && (!/^\d+$/.test(length) || Number(length) > this.options.maxSnapshotBytes))
|
|
306
|
+
throw new CacheRefusal(413, "too_large");
|
|
307
|
+
const verifiedArchive = verifiedArchiveHeaders(request.headers);
|
|
308
|
+
const projectSlot = `${context.runId}\0${key.projectPath}`;
|
|
309
|
+
if (this.uploading.has(projectSlot) || this.uploading.has(key.keyId))
|
|
310
|
+
throw new CacheRefusal(409, "upload_in_progress");
|
|
311
|
+
if (await this.options.store.hasUploaded(context.runId, key.ecosystem, key.projectPath))
|
|
312
|
+
throw new CacheRefusal(409, "already_uploaded");
|
|
313
|
+
if (await this.options.store.findByIdentity(key)) {
|
|
314
|
+
send(response, 200, { stored: false, reason: "exists" }, true);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
if (this.activeUploads >= (this.options.maxConcurrentUploads ?? DEFAULT_MAX_CONCURRENT_UPLOADS))
|
|
318
|
+
throw new CacheRefusal(503, "busy");
|
|
319
|
+
// Re-checked after the awaits above: the slot is taken synchronously.
|
|
320
|
+
if (this.uploading.has(projectSlot) || this.uploading.has(key.keyId))
|
|
321
|
+
throw new CacheRefusal(409, "upload_in_progress");
|
|
322
|
+
this.uploading.add(projectSlot);
|
|
323
|
+
this.uploading.add(key.keyId);
|
|
324
|
+
this.activeUploads += 1;
|
|
325
|
+
releaseBodyTimer();
|
|
326
|
+
try {
|
|
327
|
+
await this.storeUpload(context, deadline, key, request, response, verifiedArchive);
|
|
328
|
+
}
|
|
329
|
+
finally {
|
|
330
|
+
this.uploading.delete(projectSlot);
|
|
331
|
+
this.uploading.delete(key.keyId);
|
|
332
|
+
this.activeUploads -= 1;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
async storeUpload(context, deadline, key, request, response, verifiedArchive) {
|
|
336
|
+
const blobKey = newBlobKey(key);
|
|
337
|
+
let expired = false;
|
|
338
|
+
const timer = setTimeout(() => {
|
|
339
|
+
expired = true;
|
|
340
|
+
request.destroy();
|
|
341
|
+
}, Math.max(0, deadline - this.now().getTime()));
|
|
342
|
+
let stored;
|
|
343
|
+
try {
|
|
344
|
+
stored = await this.options.blobs.putStream(blobKey, request, {
|
|
345
|
+
contentType: "application/gzip",
|
|
346
|
+
maxBytes: this.options.maxSnapshotBytes,
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
if (error instanceof BlobStoreError && error.code === "blob_too_large")
|
|
351
|
+
throw new CacheRefusal(413, "too_large");
|
|
352
|
+
if (expired)
|
|
353
|
+
throw new CacheRefusal(409, "upload_expired");
|
|
354
|
+
// The client went away mid-upload: nothing to answer.
|
|
355
|
+
if (request.destroyed || response.destroyed)
|
|
356
|
+
return;
|
|
357
|
+
throw error;
|
|
358
|
+
}
|
|
359
|
+
finally {
|
|
360
|
+
clearTimeout(timer);
|
|
361
|
+
}
|
|
362
|
+
// Checked only now, after the last byte arrived: every byte of an
|
|
363
|
+
// accepted upload was sent before the seal (so before the agent ran).
|
|
364
|
+
const session = await this.options.store.session(context.runId);
|
|
365
|
+
const refusal = !session || session.sealedAt ? "sealed" : this.now().getTime() >= deadline ? "upload_expired" : null;
|
|
366
|
+
if (refusal) {
|
|
367
|
+
await this.discard(blobKey);
|
|
368
|
+
throw new CacheRefusal(409, refusal);
|
|
369
|
+
}
|
|
370
|
+
let outcome;
|
|
371
|
+
try {
|
|
372
|
+
outcome = await this.options.store.insert({
|
|
373
|
+
ecosystem: key.ecosystem,
|
|
374
|
+
repository: key.repository,
|
|
375
|
+
projectPath: key.projectPath,
|
|
376
|
+
lockHash: key.lockHash,
|
|
377
|
+
workerImage: key.workerImage,
|
|
378
|
+
blobKey,
|
|
379
|
+
sha256: stored.sha256,
|
|
380
|
+
sizeBytes: stored.sizeBytes,
|
|
381
|
+
archiveScanVersion: verifiedArchive?.archiveScanVersion ?? null,
|
|
382
|
+
archiveEntries: verifiedArchive?.archiveEntries ?? null,
|
|
383
|
+
unpackedBytes: verifiedArchive?.unpackedBytes ?? null,
|
|
384
|
+
createdByRunId: context.runId,
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
catch (error) {
|
|
388
|
+
await this.discard(blobKey);
|
|
389
|
+
throw error;
|
|
390
|
+
}
|
|
391
|
+
if (outcome === "exists") {
|
|
392
|
+
// Another upload of this key committed first; its object is untouched.
|
|
393
|
+
await this.discard(blobKey);
|
|
394
|
+
send(response, 200, { stored: false, reason: "exists" });
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
cacheLog.info({ event: "dependency_cache.stored", runId: context.runId, sizeBytes: stored.sizeBytes }, "dependency cache snapshot stored");
|
|
398
|
+
send(response, 201, { stored: true, sha256: stored.sha256, sizeBytes: stored.sizeBytes });
|
|
399
|
+
}
|
|
400
|
+
/** Removes an upload's own blob object (unique to the upload, so no row refers to it). */
|
|
401
|
+
async discard(blobKey) {
|
|
402
|
+
try {
|
|
403
|
+
await this.options.blobs.delete(blobKey);
|
|
404
|
+
}
|
|
405
|
+
catch (error) {
|
|
406
|
+
cacheLog.warn({ event: "dependency_cache.discard_failed", err: error }, "dependency cache: blob not removed");
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { DependencyCacheSessionState } from "../registry/store.js";
|
|
2
|
+
/**
|
|
3
|
+
* The value every snapshot row stores in its `policyHash` column, and the only
|
|
4
|
+
* value lookups match. The column and its unique index predate dropping the
|
|
5
|
+
* agent's package policy from the key (see key.ts for why), so they stay
|
|
6
|
+
* unchanged; rows written before carry a real policy hash, never match again,
|
|
7
|
+
* and age out through eviction.
|
|
8
|
+
*/
|
|
9
|
+
export declare const POLICY_ANY = "any";
|
|
10
|
+
/** The parts every snapshot of one project shares, whatever its lockfile. */
|
|
11
|
+
export interface SnapshotScope {
|
|
12
|
+
ecosystem: string;
|
|
13
|
+
repository: string;
|
|
14
|
+
projectPath: string;
|
|
15
|
+
workerImage: string;
|
|
16
|
+
}
|
|
17
|
+
export interface SnapshotIdentity extends SnapshotScope {
|
|
18
|
+
lockHash: string;
|
|
19
|
+
}
|
|
20
|
+
export interface NewSnapshot extends SnapshotIdentity {
|
|
21
|
+
blobKey: string;
|
|
22
|
+
sha256: string;
|
|
23
|
+
sizeBytes: number;
|
|
24
|
+
archiveScanVersion: number | null;
|
|
25
|
+
archiveEntries: number | null;
|
|
26
|
+
unpackedBytes: number | null;
|
|
27
|
+
createdByRunId: string;
|
|
28
|
+
}
|
|
29
|
+
export interface SnapshotRow extends SnapshotIdentity {
|
|
30
|
+
id: string;
|
|
31
|
+
blobKey: string;
|
|
32
|
+
sha256: string;
|
|
33
|
+
sizeBytes: number;
|
|
34
|
+
archiveScanVersion: number | null;
|
|
35
|
+
archiveEntries: number | null;
|
|
36
|
+
unpackedBytes: number | null;
|
|
37
|
+
createdAt: Date;
|
|
38
|
+
lastUsedAt: Date;
|
|
39
|
+
createdByRunId: string | null;
|
|
40
|
+
}
|
|
41
|
+
/** What eviction needs of every snapshot. */
|
|
42
|
+
export type EvictionRow = Pick<SnapshotRow, "id" | "repository" | "blobKey" | "sizeBytes" | "createdAt" | "lastUsedAt">;
|
|
43
|
+
/** DependencySnapshot rows and the session fields the cache reads (Postgres in production). */
|
|
44
|
+
export interface DependencyCacheStore {
|
|
45
|
+
session(runId: string): Promise<DependencyCacheSessionState | null>;
|
|
46
|
+
/** Sets the session's sealedAt unless it is already set. */
|
|
47
|
+
seal(runId: string, now: Date): Promise<void>;
|
|
48
|
+
/** Like `seal`, but only for a run whose agent has the cache on (a model request's seal, ModelRequestSeal). */
|
|
49
|
+
sealForModelRequest(runId: string, now: Date): Promise<void>;
|
|
50
|
+
findByIdentity(identity: SnapshotIdentity): Promise<SnapshotRow | null>;
|
|
51
|
+
/** The most recent snapshot in the scope whose lockHash differs (ties broken by id), or null. */
|
|
52
|
+
findNearest(scope: SnapshotScope, excludeLockHash: string): Promise<SnapshotRow | null>;
|
|
53
|
+
/** Whether the run already created a snapshot for this project. */
|
|
54
|
+
hasUploaded(runId: string, ecosystem: string, projectPath: string): Promise<boolean>;
|
|
55
|
+
/** "exists" when a snapshot with the same identity (or blob key) is already stored. */
|
|
56
|
+
insert(snapshot: NewSnapshot): Promise<"inserted" | "exists">;
|
|
57
|
+
touch(id: string, now: Date): Promise<void>;
|
|
58
|
+
delete(id: string): Promise<void>;
|
|
59
|
+
/** Deletes the row only while it still points at `blobKey` (a re-upload may have replaced it); true when deleted. */
|
|
60
|
+
deleteIfBlob(id: string, blobKey: string): Promise<boolean>;
|
|
61
|
+
listForEviction(): Promise<EvictionRow[]>;
|
|
62
|
+
/** Runs `work` while holding the cluster-wide eviction lock; returns false (without running it) when another holder has it. */
|
|
63
|
+
withEvictionLock(work: () => Promise<void>): Promise<boolean>;
|
|
64
|
+
}
|
|
65
|
+
/** In-memory adapter for tests. `sessions` may be shared with a MemoryRegistryStore's `cacheSessions`. */
|
|
66
|
+
export declare class MemoryDependencyCacheStore implements DependencyCacheStore {
|
|
67
|
+
readonly sessions: Map<string, DependencyCacheSessionState>;
|
|
68
|
+
readonly rows: SnapshotRow[];
|
|
69
|
+
private evicting;
|
|
70
|
+
/** Runs whose agent has the cache off (sealForModelRequest leaves them alone). */
|
|
71
|
+
readonly cacheOff: Set<string>;
|
|
72
|
+
constructor(sessions?: Map<string, DependencyCacheSessionState>);
|
|
73
|
+
session(runId: string): Promise<DependencyCacheSessionState | null>;
|
|
74
|
+
seal(runId: string, now: Date): Promise<void>;
|
|
75
|
+
sealForModelRequest(runId: string, now: Date): Promise<void>;
|
|
76
|
+
findByIdentity(identity: SnapshotIdentity): Promise<SnapshotRow | null>;
|
|
77
|
+
findNearest(scope: SnapshotScope, excludeLockHash: string): Promise<SnapshotRow | null>;
|
|
78
|
+
hasUploaded(runId: string, ecosystem: string, projectPath: string): Promise<boolean>;
|
|
79
|
+
insert(snapshot: NewSnapshot): Promise<"inserted" | "exists">;
|
|
80
|
+
touch(id: string, now: Date): Promise<void>;
|
|
81
|
+
delete(id: string): Promise<void>;
|
|
82
|
+
deleteIfBlob(id: string, blobKey: string): Promise<boolean>;
|
|
83
|
+
listForEviction(): Promise<EvictionRow[]>;
|
|
84
|
+
withEvictionLock(work: () => Promise<void>): Promise<boolean>;
|
|
85
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
/**
|
|
3
|
+
* The value every snapshot row stores in its `policyHash` column, and the only
|
|
4
|
+
* value lookups match. The column and its unique index predate dropping the
|
|
5
|
+
* agent's package policy from the key (see key.ts for why), so they stay
|
|
6
|
+
* unchanged; rows written before carry a real policy hash, never match again,
|
|
7
|
+
* and age out through eviction.
|
|
8
|
+
*/
|
|
9
|
+
export const POLICY_ANY = "any";
|
|
10
|
+
const sameScope = (row, scope) => row.ecosystem === scope.ecosystem &&
|
|
11
|
+
row.repository === scope.repository &&
|
|
12
|
+
row.projectPath === scope.projectPath &&
|
|
13
|
+
row.workerImage === scope.workerImage;
|
|
14
|
+
/** In-memory adapter for tests. `sessions` may be shared with a MemoryRegistryStore's `cacheSessions`. */
|
|
15
|
+
export class MemoryDependencyCacheStore {
|
|
16
|
+
sessions;
|
|
17
|
+
rows = [];
|
|
18
|
+
evicting = false;
|
|
19
|
+
/** Runs whose agent has the cache off (sealForModelRequest leaves them alone). */
|
|
20
|
+
cacheOff = new Set();
|
|
21
|
+
constructor(sessions = new Map()) {
|
|
22
|
+
this.sessions = sessions;
|
|
23
|
+
}
|
|
24
|
+
async session(runId) {
|
|
25
|
+
const session = this.sessions.get(runId);
|
|
26
|
+
return session ? { ...session, lockHashes: [...session.lockHashes] } : null;
|
|
27
|
+
}
|
|
28
|
+
async seal(runId, now) {
|
|
29
|
+
const session = this.sessions.get(runId);
|
|
30
|
+
if (session && !session.sealedAt)
|
|
31
|
+
session.sealedAt = now;
|
|
32
|
+
}
|
|
33
|
+
async sealForModelRequest(runId, now) {
|
|
34
|
+
if (!this.cacheOff.has(runId))
|
|
35
|
+
await this.seal(runId, now);
|
|
36
|
+
}
|
|
37
|
+
async findByIdentity(identity) {
|
|
38
|
+
return this.rows.find((row) => sameScope(row, identity) && row.lockHash === identity.lockHash) ?? null;
|
|
39
|
+
}
|
|
40
|
+
async findNearest(scope, excludeLockHash) {
|
|
41
|
+
const candidates = this.rows
|
|
42
|
+
.filter((row) => sameScope(row, scope) && row.lockHash !== excludeLockHash)
|
|
43
|
+
.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime() || (a.id < b.id ? 1 : a.id > b.id ? -1 : 0));
|
|
44
|
+
return candidates[0] ?? null;
|
|
45
|
+
}
|
|
46
|
+
async hasUploaded(runId, ecosystem, projectPath) {
|
|
47
|
+
return this.rows.some((row) => row.createdByRunId === runId && row.ecosystem === ecosystem && row.projectPath === projectPath);
|
|
48
|
+
}
|
|
49
|
+
async insert(snapshot) {
|
|
50
|
+
if ((await this.findByIdentity(snapshot)) || this.rows.some((row) => row.blobKey === snapshot.blobKey))
|
|
51
|
+
return "exists";
|
|
52
|
+
const now = new Date();
|
|
53
|
+
this.rows.push({ ...snapshot, id: `c${randomUUID().replaceAll("-", "")}`, createdAt: now, lastUsedAt: now });
|
|
54
|
+
return "inserted";
|
|
55
|
+
}
|
|
56
|
+
async touch(id, now) {
|
|
57
|
+
const row = this.rows.find((candidate) => candidate.id === id);
|
|
58
|
+
if (row)
|
|
59
|
+
row.lastUsedAt = now;
|
|
60
|
+
}
|
|
61
|
+
async delete(id) {
|
|
62
|
+
const index = this.rows.findIndex((row) => row.id === id);
|
|
63
|
+
if (index >= 0)
|
|
64
|
+
this.rows.splice(index, 1);
|
|
65
|
+
}
|
|
66
|
+
async deleteIfBlob(id, blobKey) {
|
|
67
|
+
const index = this.rows.findIndex((row) => row.id === id && row.blobKey === blobKey);
|
|
68
|
+
if (index < 0)
|
|
69
|
+
return false;
|
|
70
|
+
this.rows.splice(index, 1);
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
async listForEviction() {
|
|
74
|
+
return this.rows.map(({ id, repository, blobKey, sizeBytes, createdAt, lastUsedAt }) => ({
|
|
75
|
+
id,
|
|
76
|
+
repository,
|
|
77
|
+
blobKey,
|
|
78
|
+
sizeBytes,
|
|
79
|
+
createdAt,
|
|
80
|
+
lastUsedAt,
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
async withEvictionLock(work) {
|
|
84
|
+
if (this.evicting)
|
|
85
|
+
return false;
|
|
86
|
+
this.evicting = true;
|
|
87
|
+
try {
|
|
88
|
+
await work();
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
this.evicting = false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ModelPricing } from "../llm/pricing-core.js";
|
|
2
|
+
import type { PreviousReply, ProxyProtocol, ReservationBasis } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Anthropic's prompt cache lives 5 minutes from when a request is processed, not from when its reply
|
|
5
|
+
* finishes: a previous request sent longer ago than this says nothing about the cache.
|
|
6
|
+
*/
|
|
7
|
+
export declare const CACHE_WINDOW_MS: number;
|
|
8
|
+
export declare const MIN_CACHED_SHARE = 0.5;
|
|
9
|
+
export type HoldFallback = "no_previous" | "model_changed" | "previous_estimated" | "previous_not_completed" | "no_request_bytes" | "cache_expired" | "body_shrank" | "low_cache_share";
|
|
10
|
+
export interface HoldEstimate {
|
|
11
|
+
holdUsd: number;
|
|
12
|
+
ceilingUsd: number;
|
|
13
|
+
basis: ReservationBasis;
|
|
14
|
+
fallback?: HoldFallback;
|
|
15
|
+
cachedTokens?: number;
|
|
16
|
+
uncachedTokens?: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* What identifies one conversation within a run (Claude Code runs several: the main one and its
|
|
20
|
+
* sub-agents). Anthropic: the first message without cache markers (the system prompt carries a
|
|
21
|
+
* per-version billing line). OpenAI: prompt_cache_key, else instructions + the first input item.
|
|
22
|
+
*/
|
|
23
|
+
export declare function conversationKey(protocol: ProxyProtocol, body: Record<string, unknown>): string | null;
|
|
24
|
+
export declare function estimateHold(input: {
|
|
25
|
+
protocol: ProxyProtocol;
|
|
26
|
+
model: string;
|
|
27
|
+
bodyBytes: number;
|
|
28
|
+
maxOutputTokens: number;
|
|
29
|
+
pricing: ModelPricing;
|
|
30
|
+
previous: PreviousReply | null;
|
|
31
|
+
now: Date;
|
|
32
|
+
}): HoldEstimate;
|