@armadra/agent 0.0.0-stage → 0.3.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 +37 -0
- package/LICENSE +21 -0
- package/README.md +470 -2
- package/dist/agent/agent.d.ts +56 -0
- package/dist/agent/agent.js +129 -0
- package/dist/agent/loop.d.ts +51 -0
- package/dist/agent/loop.js +158 -0
- package/dist/agent/queue.d.ts +25 -0
- package/dist/agent/queue.js +55 -0
- package/dist/agent/retry.d.ts +22 -0
- package/dist/agent/retry.js +92 -0
- package/dist/agent/schema.d.ts +27 -0
- package/dist/agent/schema.js +186 -0
- package/dist/agent/session-cache.d.ts +100 -0
- package/dist/agent/session-cache.js +514 -0
- package/dist/agent/session-compaction.d.ts +49 -0
- package/dist/agent/session-compaction.js +251 -0
- package/dist/agent/session-core.d.ts +103 -0
- package/dist/agent/session-core.js +7 -0
- package/dist/agent/session-run.d.ts +55 -0
- package/dist/agent/session-run.js +246 -0
- package/dist/agent/session-state.d.ts +39 -0
- package/dist/agent/session-state.js +119 -0
- package/dist/agent/session-subagent.d.ts +54 -0
- package/dist/agent/session-subagent.js +157 -0
- package/dist/agent/session-sync.d.ts +20 -0
- package/dist/agent/session-sync.js +111 -0
- package/dist/agent/session-tools.d.ts +24 -0
- package/dist/agent/session-tools.js +277 -0
- package/dist/agent/session.d.ts +117 -0
- package/dist/agent/session.js +485 -0
- package/dist/agent/system-prompt.d.ts +49 -0
- package/dist/agent/system-prompt.js +121 -0
- package/dist/agent/tool-runner.d.ts +64 -0
- package/dist/agent/tool-runner.js +309 -0
- package/dist/agent/transform.d.ts +26 -0
- package/dist/agent/transform.js +171 -0
- package/dist/agent/types.d.ts +378 -0
- package/dist/agent/types.js +21 -0
- package/dist/ai/apis/anthropic-messages.d.ts +26 -0
- package/dist/ai/apis/anthropic-messages.js +214 -0
- package/dist/ai/apis/anthropic-request.d.ts +42 -0
- package/dist/ai/apis/anthropic-request.js +282 -0
- package/dist/ai/apis/api.d.ts +28 -0
- package/dist/ai/apis/api.js +102 -0
- package/dist/ai/apis/cache-params.d.ts +48 -0
- package/dist/ai/apis/cache-params.js +163 -0
- package/dist/ai/apis/google-generative-ai.d.ts +32 -0
- package/dist/ai/apis/google-generative-ai.js +249 -0
- package/dist/ai/apis/google-request.d.ts +40 -0
- package/dist/ai/apis/google-request.js +255 -0
- package/dist/ai/apis/openai-compat.d.ts +35 -0
- package/dist/ai/apis/openai-compat.js +142 -0
- package/dist/ai/apis/openai-completions.d.ts +28 -0
- package/dist/ai/apis/openai-completions.js +246 -0
- package/dist/ai/apis/openai-request.d.ts +35 -0
- package/dist/ai/apis/openai-request.js +303 -0
- package/dist/ai/apis/openai-responses-request.d.ts +52 -0
- package/dist/ai/apis/openai-responses-request.js +266 -0
- package/dist/ai/apis/openai-responses.d.ts +28 -0
- package/dist/ai/apis/openai-responses.js +304 -0
- package/dist/ai/apis/shared.d.ts +56 -0
- package/dist/ai/apis/shared.js +179 -0
- package/dist/ai/cache/economics.d.ts +24 -0
- package/dist/ai/cache/economics.js +69 -0
- package/dist/ai/cache/fingerprint.d.ts +17 -0
- package/dist/ai/cache/fingerprint.js +39 -0
- package/dist/ai/cache/miss.d.ts +49 -0
- package/dist/ai/cache/miss.js +96 -0
- package/dist/ai/cache/reporting.d.ts +39 -0
- package/dist/ai/cache/reporting.js +103 -0
- package/dist/ai/cache/types.d.ts +94 -0
- package/dist/ai/cache/types.js +19 -0
- package/dist/ai/cache/warmer.d.ts +94 -0
- package/dist/ai/cache/warmer.js +224 -0
- package/dist/ai/context.d.ts +35 -0
- package/dist/ai/context.js +114 -0
- package/dist/ai/cost.d.ts +21 -0
- package/dist/ai/cost.js +60 -0
- package/dist/ai/event-stream.d.ts +33 -0
- package/dist/ai/event-stream.js +101 -0
- package/dist/ai/fake/fake-provider.d.ts +68 -0
- package/dist/ai/fake/fake-provider.js +278 -0
- package/dist/ai/fake/fake-script.d.ts +69 -0
- package/dist/ai/fake/fake-script.js +142 -0
- package/dist/ai/http.d.ts +54 -0
- package/dist/ai/http.js +212 -0
- package/dist/ai/json-partial.d.ts +17 -0
- package/dist/ai/json-partial.js +243 -0
- package/dist/ai/overflow.d.ts +22 -0
- package/dist/ai/overflow.js +61 -0
- package/dist/ai/providers/auth.d.ts +71 -0
- package/dist/ai/providers/auth.js +202 -0
- package/dist/ai/providers/builtin.d.ts +23 -0
- package/dist/ai/providers/builtin.js +148 -0
- package/dist/ai/providers/catalog-data.d.ts +5 -0
- package/dist/ai/providers/catalog-data.js +19 -0
- package/dist/ai/providers/catalog.d.ts +28 -0
- package/dist/ai/providers/catalog.js +166 -0
- package/dist/ai/providers/channels.d.ts +51 -0
- package/dist/ai/providers/channels.js +96 -0
- package/dist/ai/providers/enrich.d.ts +34 -0
- package/dist/ai/providers/enrich.js +86 -0
- package/dist/ai/providers/models-dev-cache.d.ts +53 -0
- package/dist/ai/providers/models-dev-cache.js +147 -0
- package/dist/ai/providers/models-dev.d.ts +99 -0
- package/dist/ai/providers/models-dev.js +315 -0
- package/dist/ai/providers/registry.d.ts +114 -0
- package/dist/ai/providers/registry.js +458 -0
- package/dist/ai/sse.d.ts +32 -0
- package/dist/ai/sse.js +140 -0
- package/dist/ai/thinking.d.ts +42 -0
- package/dist/ai/thinking.js +101 -0
- package/dist/ai/types.d.ts +448 -0
- package/dist/ai/types.js +19 -0
- package/dist/bundle/ama-sandbox.cjs +324 -0
- package/dist/bundle/ama.cjs +27837 -0
- package/dist/bundle.d.ts +9 -0
- package/dist/bundle.js +15 -0
- package/dist/cli/args.d.ts +85 -0
- package/dist/cli/args.js +408 -0
- package/dist/cli/bootstrap.d.ts +18 -0
- package/dist/cli/bootstrap.js +399 -0
- package/dist/cli/compose-providers.d.ts +37 -0
- package/dist/cli/compose-providers.js +107 -0
- package/dist/cli/compose-session.d.ts +72 -0
- package/dist/cli/compose-session.js +370 -0
- package/dist/cli/compose-store.d.ts +24 -0
- package/dist/cli/compose-store.js +125 -0
- package/dist/cli/compose.d.ts +108 -0
- package/dist/cli/compose.js +221 -0
- package/dist/cli/default-model.d.ts +16 -0
- package/dist/cli/default-model.js +24 -0
- package/dist/cli/deps.d.ts +207 -0
- package/dist/cli/deps.js +12 -0
- package/dist/cli/exit-codes.d.ts +29 -0
- package/dist/cli/exit-codes.js +40 -0
- package/dist/cli/main.d.ts +27 -0
- package/dist/cli/main.js +177 -0
- package/dist/cli/runtime.d.ts +90 -0
- package/dist/cli/runtime.js +8 -0
- package/dist/cli/startup-screen.d.ts +15 -0
- package/dist/cli/startup-screen.js +56 -0
- package/dist/cli/startup-steps.d.ts +35 -0
- package/dist/cli/startup-steps.js +202 -0
- package/dist/cli/subcommands/auth.d.ts +12 -0
- package/dist/cli/subcommands/auth.js +91 -0
- package/dist/cli/subcommands/config.d.ts +53 -0
- package/dist/cli/subcommands/config.js +277 -0
- package/dist/cli/subcommands/context.d.ts +21 -0
- package/dist/cli/subcommands/context.js +42 -0
- package/dist/cli/subcommands/doctor.d.ts +11 -0
- package/dist/cli/subcommands/doctor.js +246 -0
- package/dist/cli/subcommands/init.d.ts +6 -0
- package/dist/cli/subcommands/init.js +22 -0
- package/dist/cli/subcommands/model-meta.d.ts +16 -0
- package/dist/cli/subcommands/model-meta.js +54 -0
- package/dist/cli/subcommands/models-cache-probe.d.ts +34 -0
- package/dist/cli/subcommands/models-cache-probe.js +190 -0
- package/dist/cli/subcommands/models-discover.d.ts +46 -0
- package/dist/cli/subcommands/models-discover.js +232 -0
- package/dist/cli/subcommands/models.d.ts +40 -0
- package/dist/cli/subcommands/models.js +152 -0
- package/dist/cli/subcommands/providers-list.d.ts +8 -0
- package/dist/cli/subcommands/providers-list.js +97 -0
- package/dist/cli/subcommands/providers-plan.d.ts +79 -0
- package/dist/cli/subcommands/providers-plan.js +215 -0
- package/dist/cli/subcommands/providers.d.ts +27 -0
- package/dist/cli/subcommands/providers.js +433 -0
- package/dist/cli/subcommands/sessions.d.ts +11 -0
- package/dist/cli/subcommands/sessions.js +100 -0
- package/dist/codemode/capability.d.ts +37 -0
- package/dist/codemode/capability.js +55 -0
- package/dist/codemode/declarations.d.ts +44 -0
- package/dist/codemode/declarations.js +111 -0
- package/dist/codemode/host-side.d.ts +59 -0
- package/dist/codemode/host-side.js +253 -0
- package/dist/codemode/modes.d.ts +23 -0
- package/dist/codemode/modes.js +40 -0
- package/dist/codemode/protocol.d.ts +91 -0
- package/dist/codemode/protocol.js +114 -0
- package/dist/codemode/sandbox-entry.d.ts +25 -0
- package/dist/codemode/sandbox-entry.js +323 -0
- package/dist/codemode/store.d.ts +26 -0
- package/dist/codemode/store.js +53 -0
- package/dist/codemode/tool.d.ts +105 -0
- package/dist/codemode/tool.js +283 -0
- package/dist/compaction/branch-summary.d.ts +30 -0
- package/dist/compaction/branch-summary.js +79 -0
- package/dist/compaction/breaker.d.ts +32 -0
- package/dist/compaction/breaker.js +65 -0
- package/dist/compaction/cut-point.d.ts +23 -0
- package/dist/compaction/cut-point.js +70 -0
- package/dist/compaction/estimate.d.ts +30 -0
- package/dist/compaction/estimate.js +114 -0
- package/dist/compaction/prune-tier.d.ts +31 -0
- package/dist/compaction/prune-tier.js +83 -0
- package/dist/compaction/serialize.d.ts +20 -0
- package/dist/compaction/serialize.js +115 -0
- package/dist/compaction/summarize-tier.d.ts +111 -0
- package/dist/compaction/summarize-tier.js +305 -0
- package/dist/config/auth-file.d.ts +39 -0
- package/dist/config/auth-file.js +92 -0
- package/dist/config/context-files.d.ts +30 -0
- package/dist/config/context-files.js +77 -0
- package/dist/config/init.d.ts +30 -0
- package/dist/config/init.js +92 -0
- package/dist/config/json-schema.d.ts +14 -0
- package/dist/config/json-schema.js +182 -0
- package/dist/config/load.d.ts +45 -0
- package/dist/config/load.js +97 -0
- package/dist/config/merge.d.ts +73 -0
- package/dist/config/merge.js +248 -0
- package/dist/config/paths.d.ts +38 -0
- package/dist/config/paths.js +108 -0
- package/dist/config/profile.d.ts +27 -0
- package/dist/config/profile.js +65 -0
- package/dist/config/schema.d.ts +34 -0
- package/dist/config/schema.js +476 -0
- package/dist/config/trust.d.ts +42 -0
- package/dist/config/trust.js +122 -0
- package/dist/config/types.d.ts +190 -0
- package/dist/config/types.js +29 -0
- package/dist/config/write.d.ts +10 -0
- package/dist/config/write.js +25 -0
- package/dist/errors.d.ts +28 -0
- package/dist/errors.js +30 -0
- package/dist/hooks/config.d.ts +49 -0
- package/dist/hooks/config.js +102 -0
- package/dist/hooks/dispatcher.d.ts +41 -0
- package/dist/hooks/dispatcher.js +104 -0
- package/dist/hooks/matcher.d.ts +22 -0
- package/dist/hooks/matcher.js +108 -0
- package/dist/hooks/protocol.d.ts +33 -0
- package/dist/hooks/protocol.js +240 -0
- package/dist/hooks/runner.d.ts +30 -0
- package/dist/hooks/runner.js +143 -0
- package/dist/hooks/types.d.ts +147 -0
- package/dist/hooks/types.js +20 -0
- package/dist/host/api-impl.d.ts +75 -0
- package/dist/host/api-impl.js +173 -0
- package/dist/host/loader.d.ts +31 -0
- package/dist/host/loader.js +147 -0
- package/dist/host/types.d.ts +169 -0
- package/dist/host/types.js +14 -0
- package/dist/host.d.ts +5 -0
- package/dist/host.js +4 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +28 -0
- package/dist/modes/commands-core.d.ts +44 -0
- package/dist/modes/commands-core.js +171 -0
- package/dist/modes/image-input.d.ts +27 -0
- package/dist/modes/image-input.js +78 -0
- package/dist/modes/interactive/approval-dialog.d.ts +48 -0
- package/dist/modes/interactive/approval-dialog.js +202 -0
- package/dist/modes/interactive/commands.d.ts +41 -0
- package/dist/modes/interactive/commands.js +201 -0
- package/dist/modes/interactive/completion.d.ts +53 -0
- package/dist/modes/interactive/completion.js +157 -0
- package/dist/modes/interactive/interactive-mode.d.ts +46 -0
- package/dist/modes/interactive/interactive-mode.js +491 -0
- package/dist/modes/interactive/key-dispatch.d.ts +29 -0
- package/dist/modes/interactive/key-dispatch.js +107 -0
- package/dist/modes/interactive/line/line-editor.d.ts +50 -0
- package/dist/modes/interactive/line/line-editor.js +232 -0
- package/dist/modes/interactive/line/line-mode.d.ts +20 -0
- package/dist/modes/interactive/line/line-mode.js +218 -0
- package/dist/modes/interactive/line/line-render.d.ts +35 -0
- package/dist/modes/interactive/line/line-render.js +171 -0
- package/dist/modes/interactive/line/paste-state.d.ts +26 -0
- package/dist/modes/interactive/line/paste-state.js +72 -0
- package/dist/modes/interactive/message-view.d.ts +77 -0
- package/dist/modes/interactive/message-view.js +269 -0
- package/dist/modes/interactive/pickers.d.ts +42 -0
- package/dist/modes/interactive/pickers.js +115 -0
- package/dist/modes/interactive/startup-ui.d.ts +33 -0
- package/dist/modes/interactive/startup-ui.js +268 -0
- package/dist/modes/interactive/status-bar.d.ts +48 -0
- package/dist/modes/interactive/status-bar.js +139 -0
- package/dist/modes/interactive/tool-view.d.ts +95 -0
- package/dist/modes/interactive/tool-view.js +344 -0
- package/dist/modes/print/json-event.d.ts +15 -0
- package/dist/modes/print/json-event.js +39 -0
- package/dist/modes/print/print-mode.d.ts +17 -0
- package/dist/modes/print/print-mode.js +107 -0
- package/dist/modes/rpc/commands.d.ts +40 -0
- package/dist/modes/rpc/commands.js +263 -0
- package/dist/modes/rpc/jsonl.d.ts +16 -0
- package/dist/modes/rpc/jsonl.js +55 -0
- package/dist/modes/rpc/rpc-mode.d.ts +22 -0
- package/dist/modes/rpc/rpc-mode.js +153 -0
- package/dist/modes/session-report.d.ts +55 -0
- package/dist/modes/session-report.js +278 -0
- package/dist/modes/shared.d.ts +13 -0
- package/dist/modes/shared.js +52 -0
- package/dist/modes/startup-ui-text.d.ts +16 -0
- package/dist/modes/startup-ui-text.js +103 -0
- package/dist/permissions/broker.d.ts +47 -0
- package/dist/permissions/broker.js +107 -0
- package/dist/permissions/dangerous.d.ts +47 -0
- package/dist/permissions/dangerous.js +370 -0
- package/dist/permissions/pipeline.d.ts +51 -0
- package/dist/permissions/pipeline.js +181 -0
- package/dist/permissions/preview.d.ts +43 -0
- package/dist/permissions/preview.js +463 -0
- package/dist/permissions/rules.d.ts +58 -0
- package/dist/permissions/rules.js +249 -0
- package/dist/permissions/types.d.ts +100 -0
- package/dist/permissions/types.js +18 -0
- package/dist/rpc.d.ts +173 -0
- package/dist/rpc.js +13 -0
- package/dist/sdk.d.ts +98 -0
- package/dist/sdk.js +195 -0
- package/dist/session/manager.d.ts +70 -0
- package/dist/session/manager.js +278 -0
- package/dist/session/migrate.d.ts +19 -0
- package/dist/session/migrate.js +53 -0
- package/dist/session/projection.d.ts +50 -0
- package/dist/session/projection.js +184 -0
- package/dist/session/store.d.ts +47 -0
- package/dist/session/store.js +210 -0
- package/dist/session/tree.d.ts +21 -0
- package/dist/session/tree.js +88 -0
- package/dist/session/types.d.ts +201 -0
- package/dist/session/types.js +18 -0
- package/dist/skills/discover.d.ts +60 -0
- package/dist/skills/discover.js +160 -0
- package/dist/skills/expand.d.ts +35 -0
- package/dist/skills/expand.js +46 -0
- package/dist/skills/frontmatter.d.ts +27 -0
- package/dist/skills/frontmatter.js +157 -0
- package/dist/skills/index-prompt.d.ts +13 -0
- package/dist/skills/index-prompt.js +42 -0
- package/dist/skills/templates.d.ts +52 -0
- package/dist/skills/templates.js +177 -0
- package/dist/tools/bash.d.ts +49 -0
- package/dist/tools/bash.js +222 -0
- package/dist/tools/edit-fuzzy.d.ts +36 -0
- package/dist/tools/edit-fuzzy.js +129 -0
- package/dist/tools/edit.d.ts +38 -0
- package/dist/tools/edit.js +241 -0
- package/dist/tools/file-mutex.d.ts +8 -0
- package/dist/tools/file-mutex.js +29 -0
- package/dist/tools/glob.d.ts +44 -0
- package/dist/tools/glob.js +217 -0
- package/dist/tools/grep.d.ts +39 -0
- package/dist/tools/grep.js +179 -0
- package/dist/tools/ignore.d.ts +40 -0
- package/dist/tools/ignore.js +174 -0
- package/dist/tools/image-file.d.ts +31 -0
- package/dist/tools/image-file.js +114 -0
- package/dist/tools/ls.d.ts +21 -0
- package/dist/tools/ls.js +96 -0
- package/dist/tools/output-accumulator.d.ts +48 -0
- package/dist/tools/output-accumulator.js +106 -0
- package/dist/tools/paths.d.ts +20 -0
- package/dist/tools/paths.js +58 -0
- package/dist/tools/presets.d.ts +71 -0
- package/dist/tools/presets.js +128 -0
- package/dist/tools/process-tree.d.ts +35 -0
- package/dist/tools/process-tree.js +125 -0
- package/dist/tools/read.d.ts +24 -0
- package/dist/tools/read.js +132 -0
- package/dist/tools/registry.d.ts +43 -0
- package/dist/tools/registry.js +106 -0
- package/dist/tools/shell.d.ts +38 -0
- package/dist/tools/shell.js +91 -0
- package/dist/tools/task.d.ts +29 -0
- package/dist/tools/task.js +107 -0
- package/dist/tools/todo.d.ts +28 -0
- package/dist/tools/todo.js +107 -0
- package/dist/tools/truncate.d.ts +44 -0
- package/dist/tools/truncate.js +175 -0
- package/dist/tools/types.d.ts +135 -0
- package/dist/tools/types.js +19 -0
- package/dist/tools/write.d.ts +20 -0
- package/dist/tools/write.js +84 -0
- package/dist/tui/ansi.d.ts +29 -0
- package/dist/tui/ansi.js +420 -0
- package/dist/tui/component.d.ts +38 -0
- package/dist/tui/component.js +13 -0
- package/dist/tui/components/box.d.ts +26 -0
- package/dist/tui/components/box.js +65 -0
- package/dist/tui/components/container.d.ts +14 -0
- package/dist/tui/components/container.js +39 -0
- package/dist/tui/components/editor-buffer.d.ts +67 -0
- package/dist/tui/components/editor-buffer.js +300 -0
- package/dist/tui/components/editor-paste.d.ts +29 -0
- package/dist/tui/components/editor-paste.js +64 -0
- package/dist/tui/components/editor.d.ts +114 -0
- package/dist/tui/components/editor.js +530 -0
- package/dist/tui/components/key-value.d.ts +29 -0
- package/dist/tui/components/key-value.js +48 -0
- package/dist/tui/components/loader.d.ts +35 -0
- package/dist/tui/components/loader.js +71 -0
- package/dist/tui/components/markdown.d.ts +90 -0
- package/dist/tui/components/markdown.js +462 -0
- package/dist/tui/components/meter.d.ts +29 -0
- package/dist/tui/components/meter.js +47 -0
- package/dist/tui/components/overlay.d.ts +24 -0
- package/dist/tui/components/overlay.js +52 -0
- package/dist/tui/components/select-list.d.ts +57 -0
- package/dist/tui/components/select-list.js +179 -0
- package/dist/tui/components/spacer.d.ts +11 -0
- package/dist/tui/components/spacer.js +16 -0
- package/dist/tui/components/text.d.ts +30 -0
- package/dist/tui/components/text.js +65 -0
- package/dist/tui/keybindings.d.ts +66 -0
- package/dist/tui/keybindings.js +120 -0
- package/dist/tui/keys.d.ts +38 -0
- package/dist/tui/keys.js +275 -0
- package/dist/tui/stdin-buffer.d.ts +41 -0
- package/dist/tui/stdin-buffer.js +206 -0
- package/dist/tui/terminal.d.ts +79 -0
- package/dist/tui/terminal.js +153 -0
- package/dist/tui/theme.d.ts +31 -0
- package/dist/tui/theme.js +199 -0
- package/dist/tui/tui.d.ts +103 -0
- package/dist/tui/tui.js +363 -0
- package/dist/tui/vt-screen.d.ts +46 -0
- package/dist/tui/vt-screen.js +251 -0
- package/dist/tui.d.ts +30 -0
- package/dist/tui.js +31 -0
- package/dist/version.d.ts +8 -0
- package/dist/version.js +24 -0
- package/docs/codemode.md +102 -0
- package/docs/hooks.md +196 -0
- package/docs/host-api.md +161 -0
- package/docs/providers.md +531 -0
- package/docs/rpc.md +253 -0
- package/docs/session-format.md +119 -0
- package/docs/tui.md +170 -0
- package/package.json +101 -4
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 缓存保温状态机(第三波 §1.7)。[W3-C1b]
|
|
3
|
+
*
|
|
4
|
+
* 注入 now / 计时器 / 发送函数,fake 时钟可测:
|
|
5
|
+
* - `start(record, ttlMs)`:每个 turn 请求完成后调用(替换上一轮);计时**从请求发出算**:
|
|
6
|
+
* `delay = max(1s, floor(min(0.9·TTL, TTL − 10s)))`,TTL ≤ 10 s 不保温。
|
|
7
|
+
* - 截止 `deadline = nextWarmAt + (TTL − delay) / 2`:计时器迟到超过截止(睡眠、事件循环阻塞)
|
|
8
|
+
* 直接停——迟到的刷新大概率是一次全价写入。
|
|
9
|
+
* - 上限:streaming 相 60 min、idle 相 30 min(从起始真实请求算);连续 2 次保温响应读写都为
|
|
10
|
+
* 0 即停。
|
|
11
|
+
* - 每次刷新前:`isCurrent(record)` 不成立 → 停;内置经济性决策交给否决钩子 `decide`(出错
|
|
12
|
+
* 回落内置决策)。
|
|
13
|
+
* - `onAgentSettled()`:streaming 模式静默结束本轮;idle 模式转空闲相。
|
|
14
|
+
* - `pause()`:真实请求发出时静默挂起(新请求本身就刷新了缓存);`cancel()` 同时丢弃记录。
|
|
15
|
+
* 只有已排期之后的异常停止(截止、上限、零命中、过期、否决、失败)回调 `onStopped`。
|
|
16
|
+
*/
|
|
17
|
+
import { expectedSavings } from "./economics.js";
|
|
18
|
+
export const WARM_MIN_TTL_MS = 10_000;
|
|
19
|
+
export const WARM_STREAMING_CAP_MS = 60 * 60_000;
|
|
20
|
+
export const WARM_IDLE_CAP_MS = 30 * 60_000;
|
|
21
|
+
export const WARM_ZERO_HIT_LIMIT = 2;
|
|
22
|
+
/** 真实计时器:`unref()`,保温不拖住进程退出。 */
|
|
23
|
+
export const REAL_TIMERS = {
|
|
24
|
+
set(fn, ms) {
|
|
25
|
+
const handle = setTimeout(fn, ms);
|
|
26
|
+
handle.unref?.();
|
|
27
|
+
return handle;
|
|
28
|
+
},
|
|
29
|
+
clear(handle) {
|
|
30
|
+
clearTimeout(handle);
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
export function warmDelay(ttlMs) {
|
|
34
|
+
return Math.max(1000, Math.floor(Math.min(0.9 * ttlMs, ttlMs - 10_000)));
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* 不保温的原因(undefined = 可以保温):
|
|
38
|
+
* - `cacheRetention: "none"` 的请求;被 `onPayload` 替换过请求体的请求;
|
|
39
|
+
* - anthropic-messages 开思考且不是自适应思考:预算从 `max_tokens` 推导,`max_tokens: 1` 会
|
|
40
|
+
* 去掉 thinking 块、改变缓存键;
|
|
41
|
+
* - 端点不是 `reported`(未知或不报缓存时看不出保温有没有用,第三波 §4 R2)。
|
|
42
|
+
*/
|
|
43
|
+
export function replayBlocker(check) {
|
|
44
|
+
if (check.options.cacheRetention === "none")
|
|
45
|
+
return "retention_none";
|
|
46
|
+
if (check.payloadReplaced)
|
|
47
|
+
return "payload_replaced";
|
|
48
|
+
const thinking = check.options.thinkingLevel !== undefined && check.options.thinkingLevel !== "off";
|
|
49
|
+
if (check.model.api === "anthropic-messages" &&
|
|
50
|
+
check.model.reasoning &&
|
|
51
|
+
thinking &&
|
|
52
|
+
check.model.compat?.adaptiveThinking !== true) {
|
|
53
|
+
return "thinking_budget";
|
|
54
|
+
}
|
|
55
|
+
if (check.reporting !== "reported")
|
|
56
|
+
return `reporting_${check.reporting}`;
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
export class CacheWarmer {
|
|
60
|
+
deps;
|
|
61
|
+
timers;
|
|
62
|
+
record;
|
|
63
|
+
origin = 0;
|
|
64
|
+
ttl = 0;
|
|
65
|
+
phase = "streaming";
|
|
66
|
+
timer;
|
|
67
|
+
inflight;
|
|
68
|
+
generation = 0;
|
|
69
|
+
zeroHits = 0;
|
|
70
|
+
state = "inactive";
|
|
71
|
+
reason;
|
|
72
|
+
nextWarmAt;
|
|
73
|
+
deadline = 0;
|
|
74
|
+
sent = 0;
|
|
75
|
+
costUsd = 0;
|
|
76
|
+
savings;
|
|
77
|
+
constructor(deps) {
|
|
78
|
+
this.deps = deps;
|
|
79
|
+
this.timers = deps.timers ?? REAL_TIMERS;
|
|
80
|
+
}
|
|
81
|
+
start(record, ttlMs) {
|
|
82
|
+
this.reset();
|
|
83
|
+
this.record = record;
|
|
84
|
+
this.origin = record.at;
|
|
85
|
+
this.ttl = ttlMs;
|
|
86
|
+
this.phase = "streaming";
|
|
87
|
+
this.zeroHits = 0;
|
|
88
|
+
if (this.deps.mode() === "off")
|
|
89
|
+
return;
|
|
90
|
+
if (ttlMs <= WARM_MIN_TTL_MS)
|
|
91
|
+
return this.block("ttl_too_short");
|
|
92
|
+
this.schedule(record.at);
|
|
93
|
+
}
|
|
94
|
+
/** 不排期并记下原因(无事件:不可重放、端点未报告、经济性不划算等)。 */
|
|
95
|
+
block(reason) {
|
|
96
|
+
this.reset();
|
|
97
|
+
this.state = "stopped";
|
|
98
|
+
this.reason = reason;
|
|
99
|
+
}
|
|
100
|
+
onAgentSettled() {
|
|
101
|
+
if (this.state !== "scheduled")
|
|
102
|
+
return;
|
|
103
|
+
if (this.deps.mode() === "idle")
|
|
104
|
+
this.phase = "idle";
|
|
105
|
+
else
|
|
106
|
+
this.reset();
|
|
107
|
+
}
|
|
108
|
+
/** 真实请求发出:静默挂起,保留统计。 */
|
|
109
|
+
pause() {
|
|
110
|
+
this.reset();
|
|
111
|
+
}
|
|
112
|
+
cancel() {
|
|
113
|
+
this.reset();
|
|
114
|
+
this.record = undefined;
|
|
115
|
+
}
|
|
116
|
+
get status() {
|
|
117
|
+
const status = { mode: this.deps.mode(), state: this.state };
|
|
118
|
+
if (this.state === "scheduled") {
|
|
119
|
+
status.phase = this.phase;
|
|
120
|
+
if (this.nextWarmAt !== undefined)
|
|
121
|
+
status.nextWarmAt = this.nextWarmAt;
|
|
122
|
+
}
|
|
123
|
+
if (this.state === "stopped" && this.reason !== undefined)
|
|
124
|
+
status.reason = this.reason;
|
|
125
|
+
if (this.sent > 0) {
|
|
126
|
+
status.sent = this.sent;
|
|
127
|
+
status.costUsd = this.costUsd;
|
|
128
|
+
}
|
|
129
|
+
if (this.savings !== undefined)
|
|
130
|
+
status.expectedSavingsUsd = this.savings;
|
|
131
|
+
return status;
|
|
132
|
+
}
|
|
133
|
+
reset() {
|
|
134
|
+
this.generation++;
|
|
135
|
+
if (this.timer !== undefined)
|
|
136
|
+
this.timers.clear(this.timer);
|
|
137
|
+
this.timer = undefined;
|
|
138
|
+
this.inflight?.abort();
|
|
139
|
+
this.inflight = undefined;
|
|
140
|
+
this.state = "inactive";
|
|
141
|
+
this.reason = undefined;
|
|
142
|
+
this.nextWarmAt = undefined;
|
|
143
|
+
}
|
|
144
|
+
stop(reason) {
|
|
145
|
+
this.reset();
|
|
146
|
+
this.state = "stopped";
|
|
147
|
+
this.reason = reason;
|
|
148
|
+
this.deps.onStopped(reason);
|
|
149
|
+
}
|
|
150
|
+
cap() {
|
|
151
|
+
return this.phase === "idle" ? WARM_IDLE_CAP_MS : WARM_STREAMING_CAP_MS;
|
|
152
|
+
}
|
|
153
|
+
schedule(base) {
|
|
154
|
+
const delay = warmDelay(this.ttl);
|
|
155
|
+
const next = base + delay;
|
|
156
|
+
if (next - this.origin > this.cap())
|
|
157
|
+
return this.stop("max_duration");
|
|
158
|
+
this.deadline = next + (this.ttl - delay) / 2;
|
|
159
|
+
this.state = "scheduled";
|
|
160
|
+
this.reason = undefined;
|
|
161
|
+
this.nextWarmAt = next;
|
|
162
|
+
const generation = this.generation;
|
|
163
|
+
this.timer = this.timers.set(() => void this.fire(generation), Math.max(0, next - this.deps.now()));
|
|
164
|
+
this.deps.onScheduled?.(next);
|
|
165
|
+
}
|
|
166
|
+
async decide(decision) {
|
|
167
|
+
const handler = this.deps.decide;
|
|
168
|
+
if (handler === undefined)
|
|
169
|
+
return decision.action;
|
|
170
|
+
try {
|
|
171
|
+
const answer = await handler(decision);
|
|
172
|
+
return answer === "warm" || answer === "stop" ? answer : decision.action;
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return decision.action;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async fire(generation) {
|
|
179
|
+
if (generation !== this.generation || this.record === undefined)
|
|
180
|
+
return;
|
|
181
|
+
this.timer = undefined;
|
|
182
|
+
const record = this.record;
|
|
183
|
+
const now = this.deps.now();
|
|
184
|
+
const mode = this.deps.mode();
|
|
185
|
+
if (now > this.deadline)
|
|
186
|
+
return this.stop("late");
|
|
187
|
+
if (mode === "off" || (this.phase === "idle" && mode !== "idle"))
|
|
188
|
+
return this.stop("off");
|
|
189
|
+
if (now - this.origin > this.cap())
|
|
190
|
+
return this.stop("max_duration");
|
|
191
|
+
if (!this.deps.isCurrent(record))
|
|
192
|
+
return this.stop("stale");
|
|
193
|
+
const decision = this.deps.evaluate(record, this.phase);
|
|
194
|
+
const action = await this.decide(decision);
|
|
195
|
+
if (generation !== this.generation)
|
|
196
|
+
return;
|
|
197
|
+
this.savings = expectedSavings(decision);
|
|
198
|
+
if (action === "stop")
|
|
199
|
+
return this.stop(decision.reason ?? "declined");
|
|
200
|
+
const controller = new AbortController();
|
|
201
|
+
this.inflight = controller;
|
|
202
|
+
const sentAt = this.deps.now();
|
|
203
|
+
let message;
|
|
204
|
+
try {
|
|
205
|
+
message = await this.deps.send(record, controller.signal);
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
message = undefined;
|
|
209
|
+
}
|
|
210
|
+
if (generation !== this.generation)
|
|
211
|
+
return;
|
|
212
|
+
this.inflight = undefined;
|
|
213
|
+
if (message === undefined || message.stopReason === "error" || message.stopReason === "aborted")
|
|
214
|
+
return this.stop("error");
|
|
215
|
+
this.sent++;
|
|
216
|
+
this.costUsd += message.usage.cost?.total ?? 0;
|
|
217
|
+
this.deps.onWarmed(record, message, sentAt);
|
|
218
|
+
const hits = message.usage.cacheRead + message.usage.cacheWrite;
|
|
219
|
+
this.zeroHits = hits === 0 ? this.zeroHits + 1 : 0;
|
|
220
|
+
if (this.zeroHits >= WARM_ZERO_HIT_LIMIT)
|
|
221
|
+
return this.stop("no_cache_hits");
|
|
222
|
+
this.schedule(sentAt);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* normalizeContext(设计 §1.2 ai/context.ts):把循环交来的转录(含 `system` 补丁消息)变成
|
|
3
|
+
* 协议可直接拼请求体的形状。
|
|
4
|
+
*
|
|
5
|
+
* - system 折叠:按出现顺序重放全部 system 消息的 `sections`(节名级替换,null 删除),得到
|
|
6
|
+
* `systemSections`(保持节首次出现的顺序)与拼好的 `systemPrompt`(节间空一行);
|
|
7
|
+
* - 工具表:按顺序重放 `toolsRemoved`(先)与 `toolsAdded`(后),同名后者覆盖;
|
|
8
|
+
* - 模态过滤:模型不收图片时,用户消息与工具结果里的图片块换成文字占位。
|
|
9
|
+
*
|
|
10
|
+
* 支持「中途 system 消息」的协议(compat.supportsMidConvoSystemMessages)用
|
|
11
|
+
* `normalizeContextInline()`:首条非 system 消息之前的 system 折叠进系统提示,之后的补丁
|
|
12
|
+
* 渲染为文本、按位置插回对话。
|
|
13
|
+
*/
|
|
14
|
+
import type { ContentBlock, Model, NormalizedContext, SystemMessage, TranscriptContext } from "./types.js";
|
|
15
|
+
export interface NormalizeOptions {
|
|
16
|
+
/** 提供时按 `model.input` 过滤模态。 */
|
|
17
|
+
model?: Pick<Model, "input"> | undefined;
|
|
18
|
+
}
|
|
19
|
+
export declare const IMAGE_OMITTED_TEXT = "[image omitted: the model does not accept image input]";
|
|
20
|
+
export declare function normalizeContext(context: TranscriptContext, options?: NormalizeOptions): NormalizedContext;
|
|
21
|
+
/** 渲染一条中途 system 补丁的文本(节变更;工具表变化由请求的工具列表体现)。 */
|
|
22
|
+
export declare function renderSystemUpdate(message: SystemMessage): string;
|
|
23
|
+
export interface InlineSystemUpdate {
|
|
24
|
+
/** 插在 `messages[beforeIndex]` 之前;等于 messages.length 表示放在末尾。 */
|
|
25
|
+
beforeIndex: number;
|
|
26
|
+
text: string;
|
|
27
|
+
}
|
|
28
|
+
export interface InlineNormalizedContext extends NormalizedContext {
|
|
29
|
+
systemUpdates: InlineSystemUpdate[];
|
|
30
|
+
}
|
|
31
|
+
export declare function normalizeContextInline(context: TranscriptContext, options?: NormalizeOptions): InlineNormalizedContext;
|
|
32
|
+
/** 文本内容(用户消息 / 工具结果),图片块忽略。 */
|
|
33
|
+
export declare function contentText(content: string | ContentBlock[], separator?: string): string;
|
|
34
|
+
/** 去掉孤立代理项(部分供应商对非法 UTF-16 直接 400)。 */
|
|
35
|
+
export declare function sanitizeText(text: string): string;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* normalizeContext(设计 §1.2 ai/context.ts):把循环交来的转录(含 `system` 补丁消息)变成
|
|
3
|
+
* 协议可直接拼请求体的形状。
|
|
4
|
+
*
|
|
5
|
+
* - system 折叠:按出现顺序重放全部 system 消息的 `sections`(节名级替换,null 删除),得到
|
|
6
|
+
* `systemSections`(保持节首次出现的顺序)与拼好的 `systemPrompt`(节间空一行);
|
|
7
|
+
* - 工具表:按顺序重放 `toolsRemoved`(先)与 `toolsAdded`(后),同名后者覆盖;
|
|
8
|
+
* - 模态过滤:模型不收图片时,用户消息与工具结果里的图片块换成文字占位。
|
|
9
|
+
*
|
|
10
|
+
* 支持「中途 system 消息」的协议(compat.supportsMidConvoSystemMessages)用
|
|
11
|
+
* `normalizeContextInline()`:首条非 system 消息之前的 system 折叠进系统提示,之后的补丁
|
|
12
|
+
* 渲染为文本、按位置插回对话。
|
|
13
|
+
*/
|
|
14
|
+
export const IMAGE_OMITTED_TEXT = "[image omitted: the model does not accept image input]";
|
|
15
|
+
class SystemState {
|
|
16
|
+
sections = new Map();
|
|
17
|
+
tools = new Map();
|
|
18
|
+
apply(message) {
|
|
19
|
+
for (const [name, value] of Object.entries(message.sections)) {
|
|
20
|
+
if (value === null)
|
|
21
|
+
this.sections.delete(name);
|
|
22
|
+
else
|
|
23
|
+
this.sections.set(name, value);
|
|
24
|
+
}
|
|
25
|
+
for (const name of message.toolsRemoved ?? [])
|
|
26
|
+
this.tools.delete(name);
|
|
27
|
+
for (const tool of message.toolsAdded ?? [])
|
|
28
|
+
this.tools.set(tool.name, tool);
|
|
29
|
+
}
|
|
30
|
+
snapshot() {
|
|
31
|
+
const systemSections = [...this.sections.entries()]
|
|
32
|
+
.filter(([, text]) => text.length > 0)
|
|
33
|
+
.map(([name, text]) => ({ name, text }));
|
|
34
|
+
return {
|
|
35
|
+
systemPrompt: systemSections.map((section) => section.text).join("\n\n"),
|
|
36
|
+
systemSections,
|
|
37
|
+
tools: [...this.tools.values()],
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function filterContent(content, allowImages) {
|
|
42
|
+
if (typeof content === "string" || allowImages)
|
|
43
|
+
return content;
|
|
44
|
+
if (!content.some((block) => block.type === "image"))
|
|
45
|
+
return content;
|
|
46
|
+
return content.map((block) => block.type === "image" ? { type: "text", text: IMAGE_OMITTED_TEXT } : block);
|
|
47
|
+
}
|
|
48
|
+
function filterMessage(message, allowImages) {
|
|
49
|
+
if (message.role === "assistant" || allowImages)
|
|
50
|
+
return message;
|
|
51
|
+
const content = filterContent(message.content, allowImages);
|
|
52
|
+
return content === message.content ? message : { ...message, content };
|
|
53
|
+
}
|
|
54
|
+
function allowsImages(options) {
|
|
55
|
+
return options?.model === undefined || options.model.input.includes("image");
|
|
56
|
+
}
|
|
57
|
+
export function normalizeContext(context, options) {
|
|
58
|
+
const state = new SystemState();
|
|
59
|
+
const messages = [];
|
|
60
|
+
const allowImages = allowsImages(options);
|
|
61
|
+
for (const message of context.messages) {
|
|
62
|
+
if (message.role === "system")
|
|
63
|
+
state.apply(message);
|
|
64
|
+
else
|
|
65
|
+
messages.push(filterMessage(message, allowImages));
|
|
66
|
+
}
|
|
67
|
+
return { ...state.snapshot(), messages };
|
|
68
|
+
}
|
|
69
|
+
/** 渲染一条中途 system 补丁的文本(节变更;工具表变化由请求的工具列表体现)。 */
|
|
70
|
+
export function renderSystemUpdate(message) {
|
|
71
|
+
const parts = [];
|
|
72
|
+
for (const [name, value] of Object.entries(message.sections)) {
|
|
73
|
+
parts.push(value === null
|
|
74
|
+
? `System prompt section "${name}" was removed.`
|
|
75
|
+
: `System prompt section "${name}" was updated:\n\n${value}`);
|
|
76
|
+
}
|
|
77
|
+
return parts.join("\n\n");
|
|
78
|
+
}
|
|
79
|
+
export function normalizeContextInline(context, options) {
|
|
80
|
+
const head = new SystemState();
|
|
81
|
+
const tools = new SystemState();
|
|
82
|
+
const messages = [];
|
|
83
|
+
const systemUpdates = [];
|
|
84
|
+
const allowImages = allowsImages(options);
|
|
85
|
+
for (const message of context.messages) {
|
|
86
|
+
if (message.role !== "system") {
|
|
87
|
+
messages.push(filterMessage(message, allowImages));
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
tools.apply(message);
|
|
91
|
+
if (messages.length === 0) {
|
|
92
|
+
head.apply(message);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const text = renderSystemUpdate(message);
|
|
96
|
+
if (text.length > 0)
|
|
97
|
+
systemUpdates.push({ beforeIndex: messages.length, text });
|
|
98
|
+
}
|
|
99
|
+
const snapshot = head.snapshot();
|
|
100
|
+
return { ...snapshot, tools: tools.snapshot().tools, messages, systemUpdates };
|
|
101
|
+
}
|
|
102
|
+
/** 文本内容(用户消息 / 工具结果),图片块忽略。 */
|
|
103
|
+
export function contentText(content, separator = "\n") {
|
|
104
|
+
if (typeof content === "string")
|
|
105
|
+
return content;
|
|
106
|
+
return content
|
|
107
|
+
.filter((block) => block.type === "text")
|
|
108
|
+
.map((block) => block.text)
|
|
109
|
+
.join(separator);
|
|
110
|
+
}
|
|
111
|
+
/** 去掉孤立代理项(部分供应商对非法 UTF-16 直接 400)。 */
|
|
112
|
+
export function sanitizeText(text) {
|
|
113
|
+
return text.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "�");
|
|
114
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 成本(设计 §3.6「成本」):价格单位为美元 / 百万 token。
|
|
3
|
+
*
|
|
4
|
+
* - 阶梯:按 `input + cacheRead + cacheWrite`(本次请求的全部输入)选**严格超过**阈值的最高一档,
|
|
5
|
+
* 整单按该档计价;
|
|
6
|
+
* - `Usage.input` 不含缓存部分;
|
|
7
|
+
* - 1 小时缓存写入(`cacheWrite1h`,已包含在 `cacheWrite` 中)按 2× 基础输入价计;
|
|
8
|
+
* - 模型无 `cost` → 不写 `usage.cost`(界面显示 `$?`);
|
|
9
|
+
* - `priceTokens` 给假想用量估价(保温经济性、未命中重计费、cache-probe 预估,第三波 §3.2)。
|
|
10
|
+
*/
|
|
11
|
+
import type { Model, Usage, UsageCost } from "./types.js";
|
|
12
|
+
export declare function emptyUsage(): Usage;
|
|
13
|
+
/** 计算并写回 `usage.cost`;无价格时删除 `usage.cost` 并返回 undefined。 */
|
|
14
|
+
export declare function calculateCost(model: Pick<Model, "cost">, usage: Usage): UsageCost | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* [W3-C1a] 按目录价给一组假想 token 估价(美元):与 `calculateCost` 同一套阶梯与 1h 写入规则,
|
|
17
|
+
* 缺省字段按 0;模型无 `cost` → undefined(价格为 0 时返回 0,「不可算」由调用方判断)。
|
|
18
|
+
*/
|
|
19
|
+
export declare function priceTokens(model: Pick<Model, "cost">, tokens: Partial<Pick<Usage, "input" | "output" | "cacheRead" | "cacheWrite">>): number | undefined;
|
|
20
|
+
/** 重新计算 totalTokens(各家多数不直接给,或给的口径不同)。 */
|
|
21
|
+
export declare function finalizeUsage(model: Pick<Model, "cost">, usage: Usage): Usage;
|
package/dist/ai/cost.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 成本(设计 §3.6「成本」):价格单位为美元 / 百万 token。
|
|
3
|
+
*
|
|
4
|
+
* - 阶梯:按 `input + cacheRead + cacheWrite`(本次请求的全部输入)选**严格超过**阈值的最高一档,
|
|
5
|
+
* 整单按该档计价;
|
|
6
|
+
* - `Usage.input` 不含缓存部分;
|
|
7
|
+
* - 1 小时缓存写入(`cacheWrite1h`,已包含在 `cacheWrite` 中)按 2× 基础输入价计;
|
|
8
|
+
* - 模型无 `cost` → 不写 `usage.cost`(界面显示 `$?`);
|
|
9
|
+
* - `priceTokens` 给假想用量估价(保温经济性、未命中重计费、cache-probe 预估,第三波 §3.2)。
|
|
10
|
+
*/
|
|
11
|
+
export function emptyUsage() {
|
|
12
|
+
return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0 };
|
|
13
|
+
}
|
|
14
|
+
function selectRates(cost, totalInput) {
|
|
15
|
+
let rates = cost;
|
|
16
|
+
let threshold = -1;
|
|
17
|
+
for (const tier of cost.tiers ?? []) {
|
|
18
|
+
if (totalInput > tier.inputTokensAbove && tier.inputTokensAbove > threshold) {
|
|
19
|
+
rates = tier;
|
|
20
|
+
threshold = tier.inputTokensAbove;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return rates;
|
|
24
|
+
}
|
|
25
|
+
/** 计算并写回 `usage.cost`;无价格时删除 `usage.cost` 并返回 undefined。 */
|
|
26
|
+
export function calculateCost(model, usage) {
|
|
27
|
+
if (!model.cost) {
|
|
28
|
+
delete usage.cost;
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
const rates = selectRates(model.cost, usage.input + usage.cacheRead + usage.cacheWrite);
|
|
32
|
+
const longWrite = Math.min(usage.cacheWrite1h ?? 0, usage.cacheWrite);
|
|
33
|
+
const shortWrite = usage.cacheWrite - longWrite;
|
|
34
|
+
const cost = {
|
|
35
|
+
input: (rates.input * usage.input) / 1e6,
|
|
36
|
+
output: (rates.output * usage.output) / 1e6,
|
|
37
|
+
cacheRead: (rates.cacheRead * usage.cacheRead) / 1e6,
|
|
38
|
+
cacheWrite: (rates.cacheWrite * shortWrite + rates.input * 2 * longWrite) / 1e6,
|
|
39
|
+
total: 0,
|
|
40
|
+
};
|
|
41
|
+
cost.total = cost.input + cost.output + cost.cacheRead + cost.cacheWrite;
|
|
42
|
+
usage.cost = cost;
|
|
43
|
+
return cost;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* [W3-C1a] 按目录价给一组假想 token 估价(美元):与 `calculateCost` 同一套阶梯与 1h 写入规则,
|
|
47
|
+
* 缺省字段按 0;模型无 `cost` → undefined(价格为 0 时返回 0,「不可算」由调用方判断)。
|
|
48
|
+
*/
|
|
49
|
+
export function priceTokens(model, tokens) {
|
|
50
|
+
if (!model.cost)
|
|
51
|
+
return undefined;
|
|
52
|
+
const usage = { ...emptyUsage(), ...tokens };
|
|
53
|
+
return calculateCost(model, usage)?.total;
|
|
54
|
+
}
|
|
55
|
+
/** 重新计算 totalTokens(各家多数不直接给,或给的口径不同)。 */
|
|
56
|
+
export function finalizeUsage(model, usage) {
|
|
57
|
+
usage.totalTokens = usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
|
|
58
|
+
calculateCost(model, usage);
|
|
59
|
+
return usage;
|
|
60
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 助手事件流(设计 §3.1):生产者 push 事件,消费者 `for await` 读取,`result()` 拿最终消息。
|
|
3
|
+
*
|
|
4
|
+
* 不变量(由本类守住,协议实现不必各自防御):
|
|
5
|
+
* - 终止事件(done / error)之后的 push 一律丢弃,只会有**一个**终止事件;
|
|
6
|
+
* - `result()` 在终止事件时 resolve(error 也 resolve,不 reject);
|
|
7
|
+
* - `end()` 前没有终止事件属于实现缺陷:补一个 error 事件兜底,消费者不会永远挂起。
|
|
8
|
+
*/
|
|
9
|
+
import type { AssistantEvent, AssistantEventStream, AssistantMessage } from "./types.js";
|
|
10
|
+
export declare class AssistantEventStreamImpl implements AssistantEventStream {
|
|
11
|
+
private readonly queue;
|
|
12
|
+
private readonly waiters;
|
|
13
|
+
private finished;
|
|
14
|
+
private closed;
|
|
15
|
+
private lastPartial;
|
|
16
|
+
private resolveResult;
|
|
17
|
+
private readonly resultPromise;
|
|
18
|
+
constructor();
|
|
19
|
+
/** 最近一个事件携带的部分消息。 */
|
|
20
|
+
get partial(): AssistantMessage | undefined;
|
|
21
|
+
/** 已经推过终止事件。 */
|
|
22
|
+
get isFinished(): boolean;
|
|
23
|
+
push(event: AssistantEvent): void;
|
|
24
|
+
/** 结束流;若还没有终止事件,补一个 error。 */
|
|
25
|
+
end(fallback?: AssistantMessage): void;
|
|
26
|
+
result(): Promise<AssistantMessage>;
|
|
27
|
+
[Symbol.asyncIterator](): AsyncIterator<AssistantEvent>;
|
|
28
|
+
private close;
|
|
29
|
+
}
|
|
30
|
+
/** 把一个异步生产函数包成事件流;生产函数抛出的异常编码为 error 事件(流函数不抛错)。 */
|
|
31
|
+
export declare function runEventStream(initial: () => AssistantMessage, produce: (stream: AssistantEventStreamImpl) => Promise<void>): AssistantEventStreamImpl;
|
|
32
|
+
/** 读完一个事件流,返回全部事件(测试与非流式调用用)。 */
|
|
33
|
+
export declare function collectEvents(stream: AsyncIterable<AssistantEvent>): Promise<AssistantEvent[]>;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 助手事件流(设计 §3.1):生产者 push 事件,消费者 `for await` 读取,`result()` 拿最终消息。
|
|
3
|
+
*
|
|
4
|
+
* 不变量(由本类守住,协议实现不必各自防御):
|
|
5
|
+
* - 终止事件(done / error)之后的 push 一律丢弃,只会有**一个**终止事件;
|
|
6
|
+
* - `result()` 在终止事件时 resolve(error 也 resolve,不 reject);
|
|
7
|
+
* - `end()` 前没有终止事件属于实现缺陷:补一个 error 事件兜底,消费者不会永远挂起。
|
|
8
|
+
*/
|
|
9
|
+
export class AssistantEventStreamImpl {
|
|
10
|
+
queue = [];
|
|
11
|
+
waiters = [];
|
|
12
|
+
finished = false;
|
|
13
|
+
closed = false;
|
|
14
|
+
lastPartial;
|
|
15
|
+
resolveResult;
|
|
16
|
+
resultPromise;
|
|
17
|
+
constructor() {
|
|
18
|
+
this.resultPromise = new Promise((resolve) => {
|
|
19
|
+
this.resolveResult = resolve;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/** 最近一个事件携带的部分消息。 */
|
|
23
|
+
get partial() {
|
|
24
|
+
return this.lastPartial;
|
|
25
|
+
}
|
|
26
|
+
/** 已经推过终止事件。 */
|
|
27
|
+
get isFinished() {
|
|
28
|
+
return this.finished;
|
|
29
|
+
}
|
|
30
|
+
push(event) {
|
|
31
|
+
if (this.finished || this.closed)
|
|
32
|
+
return;
|
|
33
|
+
if ("partial" in event)
|
|
34
|
+
this.lastPartial = event.partial;
|
|
35
|
+
if (event.type === "done" || event.type === "error") {
|
|
36
|
+
this.finished = true;
|
|
37
|
+
this.resolveResult(event.message);
|
|
38
|
+
}
|
|
39
|
+
const waiter = this.waiters.shift();
|
|
40
|
+
if (waiter)
|
|
41
|
+
waiter({ value: event, done: false });
|
|
42
|
+
else
|
|
43
|
+
this.queue.push(event);
|
|
44
|
+
if (this.finished)
|
|
45
|
+
this.close();
|
|
46
|
+
}
|
|
47
|
+
/** 结束流;若还没有终止事件,补一个 error。 */
|
|
48
|
+
end(fallback) {
|
|
49
|
+
if (!this.finished) {
|
|
50
|
+
const message = fallback ?? this.lastPartial;
|
|
51
|
+
if (message) {
|
|
52
|
+
message.stopReason = "error";
|
|
53
|
+
message.errorMessage ??= "Stream ended without a terminal event";
|
|
54
|
+
this.push({ type: "error", reason: "error", message });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
this.close();
|
|
58
|
+
}
|
|
59
|
+
result() {
|
|
60
|
+
return this.resultPromise;
|
|
61
|
+
}
|
|
62
|
+
[Symbol.asyncIterator]() {
|
|
63
|
+
return {
|
|
64
|
+
next: () => {
|
|
65
|
+
const value = this.queue.shift();
|
|
66
|
+
if (value)
|
|
67
|
+
return Promise.resolve({ value, done: false });
|
|
68
|
+
if (this.closed)
|
|
69
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
70
|
+
return new Promise((resolve) => this.waiters.push(resolve));
|
|
71
|
+
},
|
|
72
|
+
return: () => Promise.resolve({ value: undefined, done: true }),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
close() {
|
|
76
|
+
if (this.closed)
|
|
77
|
+
return;
|
|
78
|
+
this.closed = true;
|
|
79
|
+
for (const waiter of this.waiters.splice(0))
|
|
80
|
+
waiter({ value: undefined, done: true });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/** 把一个异步生产函数包成事件流;生产函数抛出的异常编码为 error 事件(流函数不抛错)。 */
|
|
84
|
+
export function runEventStream(initial, produce) {
|
|
85
|
+
const stream = new AssistantEventStreamImpl();
|
|
86
|
+
void produce(stream).then(() => stream.end(), (error) => {
|
|
87
|
+
const message = stream.partial ?? initial();
|
|
88
|
+
message.stopReason = "error";
|
|
89
|
+
message.errorMessage = error instanceof Error ? error.message : String(error);
|
|
90
|
+
stream.push({ type: "error", reason: "error", message });
|
|
91
|
+
stream.end();
|
|
92
|
+
});
|
|
93
|
+
return stream;
|
|
94
|
+
}
|
|
95
|
+
/** 读完一个事件流,返回全部事件(测试与非流式调用用)。 */
|
|
96
|
+
export async function collectEvents(stream) {
|
|
97
|
+
const events = [];
|
|
98
|
+
for await (const event of stream)
|
|
99
|
+
events.push(event);
|
|
100
|
+
return events;
|
|
101
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 脚本化供应商(设计 D16、§15):供应商 id `fake`、协议 id `fake`,模型 `fake/echo`、
|
|
3
|
+
* `fake/reasoning`。无脚本时回显最后一条用户消息;有脚本时第 n 次调用(跨模型计数)按
|
|
4
|
+
* `responses[n]` 产出文本 / 思考 / 工具调用 / 429 / 溢出 / 断流 / 延迟。
|
|
5
|
+
*
|
|
6
|
+
* 事件形状与真实协议完全一致(同一个 BlockTracker),契约测试对两条协议与 fake 跑同一套断言。
|
|
7
|
+
*
|
|
8
|
+
* 用法:
|
|
9
|
+
* - 测试:`const fake = new FakeProvider([...])`,把 `fake.api` 注册进 ApiRegistry(或直接
|
|
10
|
+
* `fake.api.stream(...)`),`fake.calls` 记录每次调用的上下文与选项;
|
|
11
|
+
* - CLI:`--provider fake`;`AMA_FAKE_SCRIPT=<path.json>` 指定脚本(首次调用时读取)。
|
|
12
|
+
* - 录制(第三波 §2.4,bundle 级缓存测试):`AMA_FAKE_RECORD=<file>` 时每次请求向文件追加一行
|
|
13
|
+
* JSON `{ index, purpose, model, system, tools, messagesCount }`——`system` 是折叠后的系统提示
|
|
14
|
+
* 原文,`tools` 是发给供应商的工具表(按注册顺序),与真实协议请求体的前缀同口径。
|
|
15
|
+
*/
|
|
16
|
+
import type { ApiImplementation, AssistantEventStream, Model, ProviderData, StreamOptions, TranscriptContext } from "../types.js";
|
|
17
|
+
import { type FakeResponse, type FakeScript } from "./fake-script.js";
|
|
18
|
+
export declare const FAKE_PROVIDER_ID = "fake";
|
|
19
|
+
export declare const FAKE_API_ID = "fake";
|
|
20
|
+
export declare const FAKE_SCRIPT_ENV = "AMA_FAKE_SCRIPT";
|
|
21
|
+
export declare const FAKE_RECORD_ENV = "AMA_FAKE_RECORD";
|
|
22
|
+
/** `AMA_FAKE_RECORD` 文件的一行。 */
|
|
23
|
+
export interface FakeRecordLine {
|
|
24
|
+
index: number;
|
|
25
|
+
purpose: string;
|
|
26
|
+
model: string;
|
|
27
|
+
system: string;
|
|
28
|
+
tools: unknown[];
|
|
29
|
+
messagesCount: number;
|
|
30
|
+
}
|
|
31
|
+
export interface FakeProviderOptions {
|
|
32
|
+
/** 录制文件路径(每次请求取一次;undefined = 不录)。 */
|
|
33
|
+
recordFile?: string | (() => string | undefined);
|
|
34
|
+
}
|
|
35
|
+
export declare const FAKE_MODELS: readonly Model[];
|
|
36
|
+
export declare function fakeProviderData(): ProviderData;
|
|
37
|
+
export interface FakeCall {
|
|
38
|
+
/** 从 0 开始。 */
|
|
39
|
+
index: number;
|
|
40
|
+
model: Model;
|
|
41
|
+
context: TranscriptContext;
|
|
42
|
+
options: Omit<StreamOptions, "signal" | "onPayload" | "onResponse">;
|
|
43
|
+
}
|
|
44
|
+
export declare class FakeProvider {
|
|
45
|
+
readonly calls: FakeCall[];
|
|
46
|
+
private script;
|
|
47
|
+
private readonly scriptLoader;
|
|
48
|
+
private loaded;
|
|
49
|
+
private readonly recordFile;
|
|
50
|
+
constructor(script?: FakeScript | FakeResponse[] | (() => FakeScript | undefined), options?: FakeProviderOptions);
|
|
51
|
+
readonly api: ApiImplementation;
|
|
52
|
+
get callCount(): number;
|
|
53
|
+
setScript(script: FakeScript | FakeResponse[]): void;
|
|
54
|
+
append(...responses: FakeResponse[]): void;
|
|
55
|
+
reset(): void;
|
|
56
|
+
private ensureLoaded;
|
|
57
|
+
/** 第 index 次调用的响应;undefined = 回显。 */
|
|
58
|
+
private responseFor;
|
|
59
|
+
stream(model: Model, context: TranscriptContext, options: StreamOptions): AssistantEventStream;
|
|
60
|
+
/** 录制一行;写失败不影响请求(测试会因缺行而失败)。 */
|
|
61
|
+
private record;
|
|
62
|
+
private run;
|
|
63
|
+
/** 产出一步,返回产出的文本(估算 output token 用)。 */
|
|
64
|
+
private emitStep;
|
|
65
|
+
}
|
|
66
|
+
/** 进程级缺省实例(`--provider fake`):脚本来自 AMA_FAKE_SCRIPT,录制到 AMA_FAKE_RECORD。 */
|
|
67
|
+
export declare const defaultFakeProvider: FakeProvider;
|
|
68
|
+
export declare const fakeApi: ApiImplementation;
|