@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,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* anthropic-messages 请求体(设计 §3.1、§3.3、§3.6):消息转换、三断点缓存、thinking、工具。
|
|
3
|
+
*
|
|
4
|
+
* 缓存断点(cacheRetention 缺省 short,未指定时读 `AMA_CACHE_RETENTION`;none 不打;long 加
|
|
5
|
+
* `ttl: "1h"`,仅 `supportsLongCacheRetention`——官方端点缺省开,中转缺省关、降为 short;最后做 TTL
|
|
6
|
+
* 顺序校验,5m 之后出现 1h 则全部降为 5m)按优先级取前
|
|
7
|
+
* `maxCacheBreakpoints` 个:① 最后一条 user 消息(含工具结果)的最后一个块 ② system 末块
|
|
8
|
+
* ③ 最后一个工具定义(`supportsCacheControlOnTools`)。
|
|
9
|
+
*
|
|
10
|
+
* thinking:`adaptiveThinking` 模型发 `{type:"adaptive"}` + `output_config.effort`;
|
|
11
|
+
* 其余推理模型按预算发 `{type:"enabled", budget_tokens}`,预算计入 max_tokens;off 发
|
|
12
|
+
* `{type:"disabled"}`(映射表 off 为 null 的模型不发)。思考开启时不发 temperature,
|
|
13
|
+
* 除非 `supportsTemperatureWithThinking`。`toolChoice: "none"`(有工具时)→ `tool_choice:{type:"none"}`。
|
|
14
|
+
*/
|
|
15
|
+
import { contentText, normalizeContext, sanitizeText } from "../context.js";
|
|
16
|
+
import { budgetedMaxTokens, clampThinkingLevel, mappedThinkingValue, thinkingBudget, } from "../thinking.js";
|
|
17
|
+
import { effectiveRetention, resolveCacheRetention, resolvePromptCacheCompat, } from "./cache-params.js";
|
|
18
|
+
export const ANTHROPIC_VERSION = "2023-06-01";
|
|
19
|
+
export const INTERLEAVED_THINKING_BETA = "interleaved-thinking-2025-05-14";
|
|
20
|
+
const ANTHROPIC_COMPAT_KEYS = [
|
|
21
|
+
"supportsCacheControlOnTools",
|
|
22
|
+
"supportsTemperatureWithThinking",
|
|
23
|
+
"adaptiveThinking",
|
|
24
|
+
"maxCacheBreakpoints",
|
|
25
|
+
];
|
|
26
|
+
export const DEFAULT_ANTHROPIC_COMPAT = {
|
|
27
|
+
supportsCacheControlOnTools: true,
|
|
28
|
+
supportsTemperatureWithThinking: false,
|
|
29
|
+
adaptiveThinking: false,
|
|
30
|
+
maxCacheBreakpoints: 4,
|
|
31
|
+
};
|
|
32
|
+
function pick(compat) {
|
|
33
|
+
const out = {};
|
|
34
|
+
for (const key of ANTHROPIC_COMPAT_KEYS) {
|
|
35
|
+
const value = compat?.[key];
|
|
36
|
+
if (value !== undefined)
|
|
37
|
+
out[key] = value;
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
export function detectAnthropicCompat(model, provider) {
|
|
42
|
+
return { ...DEFAULT_ANTHROPIC_COMPAT, ...pick(provider?.compat), ...pick(model.compat) };
|
|
43
|
+
}
|
|
44
|
+
export function cacheControlFor(retention) {
|
|
45
|
+
if (retention === "none")
|
|
46
|
+
return undefined;
|
|
47
|
+
return retention === "long" ? { type: "ephemeral", ttl: "1h" } : { type: "ephemeral" };
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* TTL 顺序校验:Anthropic 按 tools → system → messages 的顺序处理断点,要求 1h 条目都在 5m 之前。
|
|
51
|
+
* 出现「5m 之后的 1h」→ 全部降为 5m(去掉 ttl)。返回是否做了降级。
|
|
52
|
+
*/
|
|
53
|
+
export function enforceCacheTtlOrder(body) {
|
|
54
|
+
const marks = [];
|
|
55
|
+
const collect = (value) => {
|
|
56
|
+
if (Array.isArray(value))
|
|
57
|
+
value.forEach(collect);
|
|
58
|
+
else if (typeof value === "object" && value !== null) {
|
|
59
|
+
const record = value;
|
|
60
|
+
const mark = record["cache_control"];
|
|
61
|
+
if (typeof mark === "object" && mark !== null)
|
|
62
|
+
marks.push(mark);
|
|
63
|
+
if (Array.isArray(record["content"]))
|
|
64
|
+
collect(record["content"]);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
collect(body["tools"]);
|
|
68
|
+
collect(body["system"]);
|
|
69
|
+
collect(body["messages"]);
|
|
70
|
+
const firstShort = marks.findIndex((mark) => mark["ttl"] !== "1h");
|
|
71
|
+
if (firstShort < 0 || !marks.slice(firstShort).some((mark) => mark["ttl"] === "1h"))
|
|
72
|
+
return false;
|
|
73
|
+
for (const mark of marks)
|
|
74
|
+
delete mark["ttl"];
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
/** `{baseUrl}/v1/messages`;baseUrl 已以 `/v1` 结尾(中转常见写法)时不再重复。 */
|
|
78
|
+
export function anthropicMessagesUrl(baseUrl) {
|
|
79
|
+
const trimmed = baseUrl.replace(/\/+$/, "");
|
|
80
|
+
return /\/v1$/i.test(trimmed) ? `${trimmed}/messages` : `${trimmed}/v1/messages`;
|
|
81
|
+
}
|
|
82
|
+
/** Anthropic 要求 tool_use id 匹配 `^[a-zA-Z0-9_-]+$` 且 ≤ 64。 */
|
|
83
|
+
export function normalizeToolId(id) {
|
|
84
|
+
const cleaned = id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
|
|
85
|
+
return cleaned.length > 0 ? cleaned : "tool_call";
|
|
86
|
+
}
|
|
87
|
+
function convertBlocks(content) {
|
|
88
|
+
const blocks = [];
|
|
89
|
+
for (const block of content) {
|
|
90
|
+
if (block.type === "text") {
|
|
91
|
+
if (block.text.trim().length > 0)
|
|
92
|
+
blocks.push({ type: "text", text: sanitizeText(block.text) });
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
blocks.push({
|
|
96
|
+
type: "image",
|
|
97
|
+
source: { type: "base64", media_type: block.mimeType, data: block.data },
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return blocks;
|
|
102
|
+
}
|
|
103
|
+
function convertUser(message) {
|
|
104
|
+
if (typeof message.content === "string") {
|
|
105
|
+
if (message.content.trim().length === 0)
|
|
106
|
+
return undefined;
|
|
107
|
+
return { role: "user", content: sanitizeText(message.content) };
|
|
108
|
+
}
|
|
109
|
+
const blocks = convertBlocks(message.content);
|
|
110
|
+
return blocks.length > 0 ? { role: "user", content: blocks } : undefined;
|
|
111
|
+
}
|
|
112
|
+
function convertAssistant(message) {
|
|
113
|
+
const blocks = [];
|
|
114
|
+
for (const block of message.content) {
|
|
115
|
+
if (block.type === "text") {
|
|
116
|
+
if (block.text.trim().length > 0)
|
|
117
|
+
blocks.push({ type: "text", text: sanitizeText(block.text) });
|
|
118
|
+
}
|
|
119
|
+
else if (block.type === "thinking") {
|
|
120
|
+
const signature = block.thinkingSignature ?? "";
|
|
121
|
+
if (block.redacted && signature) {
|
|
122
|
+
blocks.push({ type: "redacted_thinking", data: signature });
|
|
123
|
+
}
|
|
124
|
+
else if (signature.trim().length > 0) {
|
|
125
|
+
blocks.push({ type: "thinking", thinking: sanitizeText(block.thinking), signature });
|
|
126
|
+
}
|
|
127
|
+
else if (block.thinking.trim().length > 0) {
|
|
128
|
+
// 无签名(中止或来自别家)的思考不能作为 thinking 回放,降级为文本
|
|
129
|
+
blocks.push({ type: "text", text: sanitizeText(block.thinking) });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
blocks.push({
|
|
134
|
+
type: "tool_use",
|
|
135
|
+
id: normalizeToolId(block.id),
|
|
136
|
+
name: block.name,
|
|
137
|
+
input: block.arguments,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return blocks.length > 0 ? { role: "assistant", content: blocks } : undefined;
|
|
142
|
+
}
|
|
143
|
+
function convertToolResult(message) {
|
|
144
|
+
const content = typeof message.content === "string" || !message.content.some((b) => b.type === "image")
|
|
145
|
+
? sanitizeText(contentText(message.content))
|
|
146
|
+
: convertBlocks(message.content);
|
|
147
|
+
return {
|
|
148
|
+
type: "tool_result",
|
|
149
|
+
tool_use_id: normalizeToolId(message.toolCallId),
|
|
150
|
+
content,
|
|
151
|
+
...(message.isError ? { is_error: true } : {}),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
export function convertMessages(messages) {
|
|
155
|
+
const out = [];
|
|
156
|
+
for (let i = 0; i < messages.length; i++) {
|
|
157
|
+
const message = messages[i];
|
|
158
|
+
if (!message)
|
|
159
|
+
continue;
|
|
160
|
+
if (message.role === "toolResult") {
|
|
161
|
+
const results = [];
|
|
162
|
+
while (i < messages.length && messages[i]?.role === "toolResult") {
|
|
163
|
+
results.push(convertToolResult(messages[i]));
|
|
164
|
+
i++;
|
|
165
|
+
}
|
|
166
|
+
i--;
|
|
167
|
+
out.push({ role: "user", content: results });
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const converted = message.role === "user" ? convertUser(message) : convertAssistant(message);
|
|
171
|
+
if (converted)
|
|
172
|
+
out.push(converted);
|
|
173
|
+
}
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
function convertTools(tools) {
|
|
177
|
+
return tools.map((tool) => ({
|
|
178
|
+
name: tool.name,
|
|
179
|
+
description: tool.description,
|
|
180
|
+
input_schema: { ...tool.parameters, type: "object" },
|
|
181
|
+
}));
|
|
182
|
+
}
|
|
183
|
+
/** 在最后一条 user 消息的最后一个块上打断点;返回是否打上。 */
|
|
184
|
+
function markLastUser(messages, cacheControl) {
|
|
185
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
186
|
+
const message = messages[i];
|
|
187
|
+
if (!message || message["role"] !== "user")
|
|
188
|
+
continue;
|
|
189
|
+
const content = message["content"];
|
|
190
|
+
if (typeof content === "string") {
|
|
191
|
+
message["content"] = [{ type: "text", text: content, cache_control: cacheControl }];
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
if (Array.isArray(content) && content.length > 0) {
|
|
195
|
+
const last = content[content.length - 1];
|
|
196
|
+
last["cache_control"] = cacheControl;
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
const DEFAULT_EFFORT = {
|
|
204
|
+
minimal: "low",
|
|
205
|
+
low: "low",
|
|
206
|
+
medium: "medium",
|
|
207
|
+
high: "high",
|
|
208
|
+
xhigh: "xhigh",
|
|
209
|
+
};
|
|
210
|
+
function applyThinking(body, model, compat, level, requestedMax, hasTools, betas) {
|
|
211
|
+
if (!model.reasoning)
|
|
212
|
+
return undefined;
|
|
213
|
+
if (level === "off") {
|
|
214
|
+
if (model.thinkingLevelMap?.off !== null)
|
|
215
|
+
body["thinking"] = { type: "disabled" };
|
|
216
|
+
return undefined;
|
|
217
|
+
}
|
|
218
|
+
if (compat.adaptiveThinking) {
|
|
219
|
+
const mapped = mappedThinkingValue(model, level);
|
|
220
|
+
const effort = typeof mapped === "string" ? mapped : DEFAULT_EFFORT[level];
|
|
221
|
+
body["thinking"] = { type: "adaptive", display: "summarized" };
|
|
222
|
+
body["output_config"] = { effort };
|
|
223
|
+
return effort;
|
|
224
|
+
}
|
|
225
|
+
const plan = budgetedMaxTokens(model.maxTokens, requestedMax, thinkingBudget(model, level));
|
|
226
|
+
body["max_tokens"] = plan.maxTokens;
|
|
227
|
+
if (plan.budget < 1024)
|
|
228
|
+
return undefined; // 回答上限太小,放弃思考
|
|
229
|
+
body["thinking"] = { type: "enabled", budget_tokens: plan.budget, display: "summarized" };
|
|
230
|
+
if (hasTools)
|
|
231
|
+
betas.push(INTERLEAVED_THINKING_BETA);
|
|
232
|
+
return String(plan.budget);
|
|
233
|
+
}
|
|
234
|
+
export function buildAnthropicRequest(model, context, options) {
|
|
235
|
+
const compat = detectAnthropicCompat(model);
|
|
236
|
+
const normalized = normalizeContext(context, { model });
|
|
237
|
+
const cacheCompat = resolvePromptCacheCompat(model, "anthropic-messages");
|
|
238
|
+
const retention = effectiveRetention(resolveCacheRetention(options.cacheRetention), cacheCompat);
|
|
239
|
+
const cacheControl = cacheControlFor(retention);
|
|
240
|
+
const messages = convertMessages(normalized.messages);
|
|
241
|
+
const level = clampThinkingLevel(model, options.thinkingLevel ?? "off");
|
|
242
|
+
const betas = [];
|
|
243
|
+
const body = {
|
|
244
|
+
model: model.id,
|
|
245
|
+
max_tokens: options.maxTokens ?? model.maxTokens,
|
|
246
|
+
stream: true,
|
|
247
|
+
};
|
|
248
|
+
const system = normalized.systemSections.map((section) => ({
|
|
249
|
+
type: "text",
|
|
250
|
+
text: sanitizeText(section.text),
|
|
251
|
+
}));
|
|
252
|
+
const tools = convertTools(normalized.tools);
|
|
253
|
+
let budget = cacheControl ? Math.max(0, compat.maxCacheBreakpoints) : 0;
|
|
254
|
+
if (cacheControl && budget > 0 && markLastUser(messages, cacheControl))
|
|
255
|
+
budget--;
|
|
256
|
+
const lastSystem = system[system.length - 1];
|
|
257
|
+
if (cacheControl && budget > 0 && lastSystem) {
|
|
258
|
+
lastSystem["cache_control"] = cacheControl;
|
|
259
|
+
budget--;
|
|
260
|
+
}
|
|
261
|
+
const lastTool = tools[tools.length - 1];
|
|
262
|
+
if (cacheControl && budget > 0 && lastTool && compat.supportsCacheControlOnTools) {
|
|
263
|
+
lastTool["cache_control"] = cacheControl;
|
|
264
|
+
}
|
|
265
|
+
if (system.length > 0)
|
|
266
|
+
body["system"] = system;
|
|
267
|
+
body["messages"] = messages;
|
|
268
|
+
if (tools.length > 0)
|
|
269
|
+
body["tools"] = tools;
|
|
270
|
+
if (tools.length > 0 && options.toolChoice === "none")
|
|
271
|
+
body["tool_choice"] = { type: "none" };
|
|
272
|
+
const providerLevel = applyThinking(body, model, compat, level, options.maxTokens, tools.length > 0, betas);
|
|
273
|
+
const thinkingOn = body["thinking"] !== undefined && level !== "off";
|
|
274
|
+
if (options.temperature !== undefined &&
|
|
275
|
+
(!thinkingOn || compat.supportsTemperatureWithThinking)) {
|
|
276
|
+
body["temperature"] = options.temperature;
|
|
277
|
+
}
|
|
278
|
+
if (model.samplingParams)
|
|
279
|
+
Object.assign(body, model.samplingParams);
|
|
280
|
+
enforceCacheTtlOrder(body);
|
|
281
|
+
return { body, betas, thinkingLevel: level, providerThinkingLevel: providerLevel };
|
|
282
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 协议实现注册表(设计 §3.1):按协议 id 懒加载实现模块。
|
|
3
|
+
*
|
|
4
|
+
* `get(id)` 同步返回一个包装实现:`stream()` 先同步做 key 预检(缺 key 同步抛
|
|
5
|
+
* `AmaError{code:"no_api_key"}`),再在首次调用时 `import()` 真正的实现并转发事件。bundle 里
|
|
6
|
+
* esbuild 把这些 `import()` 内联成同步 require,懒加载只在 ESM 产物里省启动时间。
|
|
7
|
+
*
|
|
8
|
+
* 内置四条协议(anthropic-messages、openai-completions、openai-responses、google-generative-ai)
|
|
9
|
+
* 与 fake;新增协议在 `createDefaultApiRegistry()` 里加一条 `register({ id, load, detectCompat })`。
|
|
10
|
+
*/
|
|
11
|
+
import type { Api, ApiImplementation, Model, ProviderData } from "../types.js";
|
|
12
|
+
export interface ApiEntry {
|
|
13
|
+
readonly id: Api;
|
|
14
|
+
load(): Promise<ApiImplementation>;
|
|
15
|
+
/** 同步可用的 compat 推断(不必等实现模块加载)。 */
|
|
16
|
+
detectCompat?(model: Model, provider: ProviderData): unknown;
|
|
17
|
+
}
|
|
18
|
+
export declare class ApiRegistry {
|
|
19
|
+
private readonly entries;
|
|
20
|
+
/** 注册(同 id 覆盖):可以是现成实现,也可以是懒加载条目。 */
|
|
21
|
+
register(entry: ApiEntry | ApiImplementation): void;
|
|
22
|
+
unregister(id: Api): boolean;
|
|
23
|
+
get(id: Api): ApiImplementation | undefined;
|
|
24
|
+
has(id: Api): boolean;
|
|
25
|
+
ids(): Api[];
|
|
26
|
+
}
|
|
27
|
+
/** 内置协议 + fake。每次返回新实例(测试可自由覆盖)。 */
|
|
28
|
+
export declare function createDefaultApiRegistry(): ApiRegistry;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 协议实现注册表(设计 §3.1):按协议 id 懒加载实现模块。
|
|
3
|
+
*
|
|
4
|
+
* `get(id)` 同步返回一个包装实现:`stream()` 先同步做 key 预检(缺 key 同步抛
|
|
5
|
+
* `AmaError{code:"no_api_key"}`),再在首次调用时 `import()` 真正的实现并转发事件。bundle 里
|
|
6
|
+
* esbuild 把这些 `import()` 内联成同步 require,懒加载只在 ESM 产物里省启动时间。
|
|
7
|
+
*
|
|
8
|
+
* 内置四条协议(anthropic-messages、openai-completions、openai-responses、google-generative-ai)
|
|
9
|
+
* 与 fake;新增协议在 `createDefaultApiRegistry()` 里加一条 `register({ id, load, detectCompat })`。
|
|
10
|
+
*/
|
|
11
|
+
import { AssistantEventStreamImpl } from "../event-stream.js";
|
|
12
|
+
import { errorText } from "../http.js";
|
|
13
|
+
import { detectAnthropicCompat } from "./anthropic-request.js";
|
|
14
|
+
import { detectGoogleCompat } from "./google-request.js";
|
|
15
|
+
import { detectCompat as detectOpenAICompat } from "./openai-compat.js";
|
|
16
|
+
import { detectResponsesCompat } from "./openai-responses-request.js";
|
|
17
|
+
import { createOutput, requireApiKey } from "./shared.js";
|
|
18
|
+
function isImplementation(value) {
|
|
19
|
+
return typeof value.stream === "function";
|
|
20
|
+
}
|
|
21
|
+
function lazyImplementation(entry) {
|
|
22
|
+
let loaded;
|
|
23
|
+
const impl = {
|
|
24
|
+
id: entry.id,
|
|
25
|
+
stream(model, context, options) {
|
|
26
|
+
requireApiKey(model, options);
|
|
27
|
+
const outer = new AssistantEventStreamImpl();
|
|
28
|
+
loaded ??= entry.load();
|
|
29
|
+
void loaded
|
|
30
|
+
.then(async (inner) => {
|
|
31
|
+
for await (const event of inner.stream(model, context, options))
|
|
32
|
+
outer.push(event);
|
|
33
|
+
outer.end();
|
|
34
|
+
})
|
|
35
|
+
.catch((error) => {
|
|
36
|
+
const message = outer.partial ?? createOutput(model);
|
|
37
|
+
message.stopReason = options.signal.aborted ? "aborted" : "error";
|
|
38
|
+
message.errorMessage = errorText(error);
|
|
39
|
+
outer.push({
|
|
40
|
+
type: "error",
|
|
41
|
+
reason: options.signal.aborted ? "aborted" : "error",
|
|
42
|
+
message,
|
|
43
|
+
});
|
|
44
|
+
outer.end();
|
|
45
|
+
});
|
|
46
|
+
return outer;
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
if (entry.detectCompat) {
|
|
50
|
+
const detect = entry.detectCompat.bind(entry);
|
|
51
|
+
impl.detectCompat = (model, provider) => detect(model, provider);
|
|
52
|
+
}
|
|
53
|
+
return impl;
|
|
54
|
+
}
|
|
55
|
+
export class ApiRegistry {
|
|
56
|
+
entries = new Map();
|
|
57
|
+
/** 注册(同 id 覆盖):可以是现成实现,也可以是懒加载条目。 */
|
|
58
|
+
register(entry) {
|
|
59
|
+
this.entries.set(entry.id, isImplementation(entry) ? entry : lazyImplementation(entry));
|
|
60
|
+
}
|
|
61
|
+
unregister(id) {
|
|
62
|
+
return this.entries.delete(id);
|
|
63
|
+
}
|
|
64
|
+
get(id) {
|
|
65
|
+
return this.entries.get(id);
|
|
66
|
+
}
|
|
67
|
+
has(id) {
|
|
68
|
+
return this.entries.has(id);
|
|
69
|
+
}
|
|
70
|
+
ids() {
|
|
71
|
+
return [...this.entries.keys()];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** 内置协议 + fake。每次返回新实例(测试可自由覆盖)。 */
|
|
75
|
+
export function createDefaultApiRegistry() {
|
|
76
|
+
const registry = new ApiRegistry();
|
|
77
|
+
registry.register({
|
|
78
|
+
id: "anthropic-messages",
|
|
79
|
+
load: async () => (await import("./anthropic-messages.js")).anthropicMessagesApi,
|
|
80
|
+
detectCompat: (model, provider) => detectAnthropicCompat(model, provider),
|
|
81
|
+
});
|
|
82
|
+
registry.register({
|
|
83
|
+
id: "openai-completions",
|
|
84
|
+
load: async () => (await import("./openai-completions.js")).openAICompletionsApi,
|
|
85
|
+
detectCompat: (model, provider) => detectOpenAICompat(model, provider),
|
|
86
|
+
});
|
|
87
|
+
registry.register({
|
|
88
|
+
id: "openai-responses",
|
|
89
|
+
load: async () => (await import("./openai-responses.js")).openAIResponsesApi,
|
|
90
|
+
detectCompat: (model, provider) => detectResponsesCompat(model, provider),
|
|
91
|
+
});
|
|
92
|
+
registry.register({
|
|
93
|
+
id: "google-generative-ai",
|
|
94
|
+
load: async () => (await import("./google-generative-ai.js")).googleGenerativeAiApi,
|
|
95
|
+
detectCompat: (model, provider) => detectGoogleCompat(model, provider),
|
|
96
|
+
});
|
|
97
|
+
registry.register({
|
|
98
|
+
id: "fake",
|
|
99
|
+
load: async () => (await import("../fake/fake-provider.js")).fakeApi,
|
|
100
|
+
});
|
|
101
|
+
return registry;
|
|
102
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 协议层缓存参数(第三波 §1.3):缓存兼容开关的端点推断、保留层级解析、亲和头。
|
|
3
|
+
*
|
|
4
|
+
* 推断只看最终请求的主机名(不看 provider id:`OPENAI_BASE_URL` 把 openai 指到中转时按中转处理):
|
|
5
|
+
* - `sendPromptCacheKey`、`supportsLongCacheRetention`:只有官方端点(api.openai.com /
|
|
6
|
+
* api.anthropic.com)缺省开;
|
|
7
|
+
* - `sendSessionAffinityHeaders`、`supportsExplicitPromptCacheMode`:缺省一律关(实测中转都接受
|
|
8
|
+
* 但未见命中提升,OpenRouter 未实测,见 docs/providers.md「缓存」);
|
|
9
|
+
* - `cacheReporting`:`auto`。
|
|
10
|
+
* 显式 `model.compat`(registry 已把 provider.compat 合进来)逐字段覆盖推断。
|
|
11
|
+
*
|
|
12
|
+
* 400 自动剥离(`postWithCacheFallback`):端点以 400 拒收并点名 prompt_cache_* / cache_control 时,
|
|
13
|
+
* 把 `${provider}/${model}` 记入进程级 `strippedCacheParams`,去掉这些字段在 `start` 之前重发一次,
|
|
14
|
+
* 并提示一次(实测:某中转的 Responses 对 DeepSeek 拒收 prompt_cache_retention / _options)。
|
|
15
|
+
*/
|
|
16
|
+
import { type PostOptions } from "../http.js";
|
|
17
|
+
import type { Api, CacheRetention, Model, PromptCacheCompat } from "../types.js";
|
|
18
|
+
/** 请求实际发往的主机名(小写);baseUrl 不可解析时返回空串。 */
|
|
19
|
+
export declare function endpointHost(model: Pick<Model, "baseUrl">, api: Api): string;
|
|
20
|
+
/** 解析后的缓存兼容开关:端点推断 ← `model.compat`。 */
|
|
21
|
+
export declare function resolvePromptCacheCompat(model: Pick<Model, "baseUrl" | "compat">, api: Api): PromptCacheCompat;
|
|
22
|
+
/**
|
|
23
|
+
* 本次请求的保留层级:显式 `options.cacheRetention` 优先;否则 `AMA_CACHE_RETENTION`
|
|
24
|
+
* (none | short | long,非法值忽略);都没有 → short。
|
|
25
|
+
*/
|
|
26
|
+
export declare function resolveCacheRetention(retention: CacheRetention | undefined, env?: NodeJS.ProcessEnv): CacheRetention;
|
|
27
|
+
/** `long` 在不支持长保留的端点上降为 `short`。 */
|
|
28
|
+
export declare function effectiveRetention(retention: CacheRetention, compat: Pick<PromptCacheCompat, "supportsLongCacheRetention">): CacheRetention;
|
|
29
|
+
/**
|
|
30
|
+
* 会话亲和头(`sendSessionAffinityHeaders`):OpenRouter 用 `x-session-id`,其余
|
|
31
|
+
* `x-session-affinity` + 每请求一个 `x-client-request-id`。无 sessionId 或 retention 为 none 时不发。
|
|
32
|
+
*/
|
|
33
|
+
export declare function affinityHeaders(model: Pick<Model, "baseUrl" | "compat">, api: Api, sessionId: string | undefined, retention: CacheRetention, newId?: () => string): Record<string, string>;
|
|
34
|
+
type Json = Record<string, unknown>;
|
|
35
|
+
/** 遇到过拒收的 `${provider}/${model}`:进程内之后的请求直接不带缓存参数。 */
|
|
36
|
+
export declare const strippedCacheParams: Set<string>;
|
|
37
|
+
/** 替换剥离提示的输出(宿主 / 测试用);返回原来的函数。 */
|
|
38
|
+
export declare function setCacheParamWarn(next: (message: string) => void): (message: string) => void;
|
|
39
|
+
/** HTTP 400 且错误体点名了缓存参数 → 返回被拒字段名(小写);否则 undefined。 */
|
|
40
|
+
export declare function isCacheParamRejection(error: unknown): string | undefined;
|
|
41
|
+
/** 去掉顶层 prompt_cache_* 与任意层级的 cache_control(返回新对象,不改入参)。 */
|
|
42
|
+
export declare function stripCacheParams(body: Json): Json;
|
|
43
|
+
/**
|
|
44
|
+
* `postJson` 加缓存参数兜底:已记入 `strippedCacheParams` 的模型直接剥离后发;否则照发,
|
|
45
|
+
* 遇到点名缓存参数的 400 且请求里确有可剥离字段 → 记入集合、提示一次、剥离后重发一次。
|
|
46
|
+
*/
|
|
47
|
+
export declare function postWithCacheFallback(model: Pick<Model, "provider" | "id">, url: string, options: PostOptions): Promise<Response>;
|
|
48
|
+
export {};
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 协议层缓存参数(第三波 §1.3):缓存兼容开关的端点推断、保留层级解析、亲和头。
|
|
3
|
+
*
|
|
4
|
+
* 推断只看最终请求的主机名(不看 provider id:`OPENAI_BASE_URL` 把 openai 指到中转时按中转处理):
|
|
5
|
+
* - `sendPromptCacheKey`、`supportsLongCacheRetention`:只有官方端点(api.openai.com /
|
|
6
|
+
* api.anthropic.com)缺省开;
|
|
7
|
+
* - `sendSessionAffinityHeaders`、`supportsExplicitPromptCacheMode`:缺省一律关(实测中转都接受
|
|
8
|
+
* 但未见命中提升,OpenRouter 未实测,见 docs/providers.md「缓存」);
|
|
9
|
+
* - `cacheReporting`:`auto`。
|
|
10
|
+
* 显式 `model.compat`(registry 已把 provider.compat 合进来)逐字段覆盖推断。
|
|
11
|
+
*
|
|
12
|
+
* 400 自动剥离(`postWithCacheFallback`):端点以 400 拒收并点名 prompt_cache_* / cache_control 时,
|
|
13
|
+
* 把 `${provider}/${model}` 记入进程级 `strippedCacheParams`,去掉这些字段在 `start` 之前重发一次,
|
|
14
|
+
* 并提示一次(实测:某中转的 Responses 对 DeepSeek 拒收 prompt_cache_retention / _options)。
|
|
15
|
+
*/
|
|
16
|
+
import { HttpError, postJson } from "../http.js";
|
|
17
|
+
const DEFAULT_BASE_URLS = {
|
|
18
|
+
"anthropic-messages": "https://api.anthropic.com",
|
|
19
|
+
"openai-completions": "https://api.openai.com/v1",
|
|
20
|
+
"openai-responses": "https://api.openai.com/v1",
|
|
21
|
+
};
|
|
22
|
+
const OFFICIAL_HOSTS = new Set(["api.openai.com", "api.anthropic.com"]);
|
|
23
|
+
const PROMPT_CACHE_KEYS = [
|
|
24
|
+
"sendPromptCacheKey",
|
|
25
|
+
"sendSessionAffinityHeaders",
|
|
26
|
+
"supportsLongCacheRetention",
|
|
27
|
+
"supportsExplicitPromptCacheMode",
|
|
28
|
+
"cacheReporting",
|
|
29
|
+
];
|
|
30
|
+
/** 请求实际发往的主机名(小写);baseUrl 不可解析时返回空串。 */
|
|
31
|
+
export function endpointHost(model, api) {
|
|
32
|
+
const baseUrl = model.baseUrl ?? DEFAULT_BASE_URLS[api] ?? "";
|
|
33
|
+
try {
|
|
34
|
+
return new URL(baseUrl).hostname.toLowerCase();
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return "";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** 解析后的缓存兼容开关:端点推断 ← `model.compat`。 */
|
|
41
|
+
export function resolvePromptCacheCompat(model, api) {
|
|
42
|
+
const official = OFFICIAL_HOSTS.has(endpointHost(model, api));
|
|
43
|
+
const out = {
|
|
44
|
+
sendPromptCacheKey: official && api !== "anthropic-messages",
|
|
45
|
+
sendSessionAffinityHeaders: false,
|
|
46
|
+
supportsLongCacheRetention: official,
|
|
47
|
+
supportsExplicitPromptCacheMode: false,
|
|
48
|
+
cacheReporting: "auto",
|
|
49
|
+
};
|
|
50
|
+
for (const key of PROMPT_CACHE_KEYS) {
|
|
51
|
+
const value = model.compat?.[key];
|
|
52
|
+
if (value !== undefined)
|
|
53
|
+
out[key] = value;
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
const RETENTIONS = ["none", "short", "long"];
|
|
58
|
+
/**
|
|
59
|
+
* 本次请求的保留层级:显式 `options.cacheRetention` 优先;否则 `AMA_CACHE_RETENTION`
|
|
60
|
+
* (none | short | long,非法值忽略);都没有 → short。
|
|
61
|
+
*/
|
|
62
|
+
export function resolveCacheRetention(retention, env = process.env) {
|
|
63
|
+
if (retention !== undefined)
|
|
64
|
+
return retention;
|
|
65
|
+
const fromEnv = env["AMA_CACHE_RETENTION"]?.trim().toLowerCase();
|
|
66
|
+
return RETENTIONS.find((value) => value === fromEnv) ?? "short";
|
|
67
|
+
}
|
|
68
|
+
/** `long` 在不支持长保留的端点上降为 `short`。 */
|
|
69
|
+
export function effectiveRetention(retention, compat) {
|
|
70
|
+
return retention === "long" && !compat.supportsLongCacheRetention ? "short" : retention;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* 会话亲和头(`sendSessionAffinityHeaders`):OpenRouter 用 `x-session-id`,其余
|
|
74
|
+
* `x-session-affinity` + 每请求一个 `x-client-request-id`。无 sessionId 或 retention 为 none 时不发。
|
|
75
|
+
*/
|
|
76
|
+
export function affinityHeaders(model, api, sessionId, retention, newId = () => crypto.randomUUID()) {
|
|
77
|
+
if (!sessionId || retention === "none")
|
|
78
|
+
return {};
|
|
79
|
+
if (!resolvePromptCacheCompat(model, api).sendSessionAffinityHeaders)
|
|
80
|
+
return {};
|
|
81
|
+
if (endpointHost(model, api).endsWith("openrouter.ai"))
|
|
82
|
+
return { "x-session-id": sessionId };
|
|
83
|
+
return { "x-session-affinity": sessionId, "x-client-request-id": newId() };
|
|
84
|
+
}
|
|
85
|
+
const CACHE_PARAM_PATTERN = /prompt_cache_key|prompt_cache_retention|prompt_cache_options|cache_control/i;
|
|
86
|
+
const TOP_LEVEL_PARAMS = ["prompt_cache_key", "prompt_cache_retention", "prompt_cache_options"];
|
|
87
|
+
/** 工具 schema 与调用参数原样保留(里面的同名属性是用户数据,不是缓存参数)。 */
|
|
88
|
+
const SCHEMA_KEYS = new Set(["parameters", "input_schema", "input", "arguments"]);
|
|
89
|
+
/** 遇到过拒收的 `${provider}/${model}`:进程内之后的请求直接不带缓存参数。 */
|
|
90
|
+
export const strippedCacheParams = new Set();
|
|
91
|
+
const SUGGESTION = {
|
|
92
|
+
prompt_cache_key: "compat.sendPromptCacheKey: false",
|
|
93
|
+
prompt_cache_retention: "compat.supportsLongCacheRetention: false",
|
|
94
|
+
prompt_cache_options: "compat.supportsExplicitPromptCacheMode: false",
|
|
95
|
+
cache_control: 'compat.cacheControlFormat: "none"',
|
|
96
|
+
};
|
|
97
|
+
let warn = (message) => {
|
|
98
|
+
if ((process.env["AMA_LOG"] ?? "warn") !== "error")
|
|
99
|
+
process.stderr.write(`ama: [warn] ${message}\n`);
|
|
100
|
+
};
|
|
101
|
+
/** 替换剥离提示的输出(宿主 / 测试用);返回原来的函数。 */
|
|
102
|
+
export function setCacheParamWarn(next) {
|
|
103
|
+
const previous = warn;
|
|
104
|
+
warn = next;
|
|
105
|
+
return previous;
|
|
106
|
+
}
|
|
107
|
+
/** HTTP 400 且错误体点名了缓存参数 → 返回被拒字段名(小写);否则 undefined。 */
|
|
108
|
+
export function isCacheParamRejection(error) {
|
|
109
|
+
if (!(error instanceof HttpError) || error.status !== 400)
|
|
110
|
+
return undefined;
|
|
111
|
+
return CACHE_PARAM_PATTERN.exec(`${error.body}\n${error.message}`)?.[0].toLowerCase();
|
|
112
|
+
}
|
|
113
|
+
function stripInto(value, removed) {
|
|
114
|
+
if (Array.isArray(value))
|
|
115
|
+
return value.map((item) => stripInto(item, removed));
|
|
116
|
+
if (typeof value !== "object" || value === null)
|
|
117
|
+
return value;
|
|
118
|
+
const out = {};
|
|
119
|
+
for (const [key, inner] of Object.entries(value)) {
|
|
120
|
+
if (key === "cache_control")
|
|
121
|
+
removed.count++;
|
|
122
|
+
else
|
|
123
|
+
out[key] = SCHEMA_KEYS.has(key) ? inner : stripInto(inner, removed);
|
|
124
|
+
}
|
|
125
|
+
return out;
|
|
126
|
+
}
|
|
127
|
+
function stripWithCount(body) {
|
|
128
|
+
const removed = { count: 0 };
|
|
129
|
+
if (typeof body !== "object" || body === null || Array.isArray(body))
|
|
130
|
+
return { body, removed: 0 };
|
|
131
|
+
const out = stripInto(body, removed);
|
|
132
|
+
for (const key of TOP_LEVEL_PARAMS) {
|
|
133
|
+
if (key in out) {
|
|
134
|
+
delete out[key];
|
|
135
|
+
removed.count++;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return { body: out, removed: removed.count };
|
|
139
|
+
}
|
|
140
|
+
/** 去掉顶层 prompt_cache_* 与任意层级的 cache_control(返回新对象,不改入参)。 */
|
|
141
|
+
export function stripCacheParams(body) {
|
|
142
|
+
return stripWithCount(body).body;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* `postJson` 加缓存参数兜底:已记入 `strippedCacheParams` 的模型直接剥离后发;否则照发,
|
|
146
|
+
* 遇到点名缓存参数的 400 且请求里确有可剥离字段 → 记入集合、提示一次、剥离后重发一次。
|
|
147
|
+
*/
|
|
148
|
+
export async function postWithCacheFallback(model, url, options) {
|
|
149
|
+
const key = `${model.provider}/${model.id}`;
|
|
150
|
+
const body = strippedCacheParams.has(key) ? stripWithCount(options.body).body : options.body;
|
|
151
|
+
try {
|
|
152
|
+
return await postJson(url, { ...options, body });
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
const field = isCacheParamRejection(error);
|
|
156
|
+
const stripped = field && !options.signal.aborted ? stripWithCount(body) : undefined;
|
|
157
|
+
if (!field || !stripped || stripped.removed === 0)
|
|
158
|
+
throw error;
|
|
159
|
+
strippedCacheParams.add(key);
|
|
160
|
+
warn(`${key}:该端点不支持 ${field},已自动去掉缓存参数重发;可在 config 里设 ${SUGGESTION[field] ?? "相应的 compat 开关"}`);
|
|
161
|
+
return postJson(url, { ...options, body: stripped.body });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* google-generative-ai 协议(设计 §3.1):POST `{baseUrl}/models/{id}:streamGenerateContent?alt=sse`,
|
|
3
|
+
* 每个 `data:` 是一个完整的 GenerateContentResponse(没有 `[DONE]`)。
|
|
4
|
+
*
|
|
5
|
+
* - `candidates[0].content.parts`:`text`(`thought: true` 为思考)→ 文本 / 思考块,相邻同类 part
|
|
6
|
+
* 并入同一块;`functionCall` 整块到达 → toolcall_start / delta(整段 JSON)/ end;
|
|
7
|
+
* - `thoughtSignature` 可挂在任何 part 上:思考 → `thinkingSignature`,文本 → `textSignature`,
|
|
8
|
+
* 工具调用 → `thoughtSignature`;同一块后到的空签名不覆盖先到的(签名常在末尾的空文本 part 上);
|
|
9
|
+
* - 工具调用缺 id(Gemini 2.5 起常见)时按 responseId + 内容下标生成确定的 id;
|
|
10
|
+
* - `finishReason`:STOP → stop(有工具调用则 toolUse)、MAX_TOKENS → length,其余(SAFETY、
|
|
11
|
+
* RECITATION、MALFORMED_FUNCTION_CALL…)→ error;`promptFeedback.blockReason` → error;
|
|
12
|
+
* 流里没出现过 finishReason → 断流错误;
|
|
13
|
+
* - `usageMetadata`(每块累计,取最后一次):input = promptTokenCount − cachedContentTokenCount,
|
|
14
|
+
* output = candidatesTokenCount + thoughtsTokenCount(思考按输出计费),reasoning = thoughts;
|
|
15
|
+
* cachedContentTokenCount 出现(含 0)即 `cacheReported: true`(隐式缓存未命中时常常不给该字段);
|
|
16
|
+
* - 错误体 `{error:{code,status,message}}` 重排为 `429 RESOURCE_EXHAUSTED: …`。
|
|
17
|
+
*/
|
|
18
|
+
import type { ApiImplementation, GoogleCompat, Model, Usage } from "../types.js";
|
|
19
|
+
type Json = Record<string, unknown>;
|
|
20
|
+
export declare function parseGoogleUsage(raw: Json): Usage;
|
|
21
|
+
type MappedFinish = {
|
|
22
|
+
reason: "stop" | "length" | "toolUse";
|
|
23
|
+
} | {
|
|
24
|
+
reason: "error";
|
|
25
|
+
message: string;
|
|
26
|
+
};
|
|
27
|
+
export declare function mapGoogleFinishReason(raw: string): MappedFinish;
|
|
28
|
+
/** Google 错误体 `{error:{code,status,message}}` → `status: message`;其它形状返回 undefined。 */
|
|
29
|
+
export declare function describeGoogleError(value: unknown): string | undefined;
|
|
30
|
+
export declare function googleStreamUrl(model: Model): string;
|
|
31
|
+
export declare const googleGenerativeAiApi: ApiImplementation<GoogleCompat>;
|
|
32
|
+
export {};
|