@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,278 @@
|
|
|
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 { appendFileSync } from "node:fs";
|
|
17
|
+
import { AssistantEventStreamImpl } from "../event-stream.js";
|
|
18
|
+
import { BlockTracker, createOutput, finishDone, finishError } from "../apis/shared.js";
|
|
19
|
+
import { contentText, normalizeContext } from "../context.js";
|
|
20
|
+
import { describeFakeError, loadFakeScript, parseFakeScript, } from "./fake-script.js";
|
|
21
|
+
export const FAKE_PROVIDER_ID = "fake";
|
|
22
|
+
export const FAKE_API_ID = "fake";
|
|
23
|
+
export const FAKE_SCRIPT_ENV = "AMA_FAKE_SCRIPT";
|
|
24
|
+
export const FAKE_RECORD_ENV = "AMA_FAKE_RECORD";
|
|
25
|
+
const FAKE_MODEL_BASE = {
|
|
26
|
+
provider: FAKE_PROVIDER_ID,
|
|
27
|
+
api: FAKE_API_ID,
|
|
28
|
+
baseUrl: "fake://local",
|
|
29
|
+
input: ["text", "image"],
|
|
30
|
+
contextWindow: 200_000,
|
|
31
|
+
maxTokens: 8192,
|
|
32
|
+
cost: { input: 1, output: 2, cacheRead: 0.1, cacheWrite: 1.25 },
|
|
33
|
+
requiresApiKey: false,
|
|
34
|
+
};
|
|
35
|
+
export const FAKE_MODELS = [
|
|
36
|
+
{ ...FAKE_MODEL_BASE, id: "echo", name: "Fake Echo", reasoning: false },
|
|
37
|
+
{ ...FAKE_MODEL_BASE, id: "reasoning", name: "Fake Reasoning", reasoning: true },
|
|
38
|
+
];
|
|
39
|
+
export function fakeProviderData() {
|
|
40
|
+
return {
|
|
41
|
+
id: FAKE_PROVIDER_ID,
|
|
42
|
+
name: "Fake (scripted)",
|
|
43
|
+
api: FAKE_API_ID,
|
|
44
|
+
baseUrl: "fake://local",
|
|
45
|
+
envKeys: [],
|
|
46
|
+
models: FAKE_MODELS.map((model) => ({ ...model })),
|
|
47
|
+
requiresApiKey: false,
|
|
48
|
+
builtin: true,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function lastUserText(context) {
|
|
52
|
+
for (let i = context.messages.length - 1; i >= 0; i--) {
|
|
53
|
+
const message = context.messages[i];
|
|
54
|
+
if (message?.role === "user")
|
|
55
|
+
return contentText(message.content);
|
|
56
|
+
}
|
|
57
|
+
return "";
|
|
58
|
+
}
|
|
59
|
+
function estimateTokens(text) {
|
|
60
|
+
return Math.ceil(text.length / 4);
|
|
61
|
+
}
|
|
62
|
+
function contextChars(context) {
|
|
63
|
+
let chars = 0;
|
|
64
|
+
for (const message of context.messages)
|
|
65
|
+
chars += JSON.stringify(message).length;
|
|
66
|
+
return chars;
|
|
67
|
+
}
|
|
68
|
+
function chunks(text, size) {
|
|
69
|
+
if (!size || size <= 0 || text.length <= size)
|
|
70
|
+
return text.length > 0 ? [text] : [];
|
|
71
|
+
const out = [];
|
|
72
|
+
for (let i = 0; i < text.length; i += size)
|
|
73
|
+
out.push(text.slice(i, i + size));
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
function sleep(ms, signal) {
|
|
77
|
+
return new Promise((resolve) => {
|
|
78
|
+
if (signal.aborted || ms <= 0) {
|
|
79
|
+
setTimeout(resolve, 0);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const timer = setTimeout(done, ms);
|
|
83
|
+
function done() {
|
|
84
|
+
clearTimeout(timer);
|
|
85
|
+
signal.removeEventListener("abort", done);
|
|
86
|
+
resolve();
|
|
87
|
+
}
|
|
88
|
+
signal.addEventListener("abort", done, { once: true });
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/** 让出事件循环,abort 能在两个事件之间插入。 */
|
|
92
|
+
function tick() {
|
|
93
|
+
return new Promise((resolve) => setImmediate(resolve));
|
|
94
|
+
}
|
|
95
|
+
class AbortedError extends Error {
|
|
96
|
+
}
|
|
97
|
+
export class FakeProvider {
|
|
98
|
+
calls = [];
|
|
99
|
+
script;
|
|
100
|
+
scriptLoader;
|
|
101
|
+
loaded = false;
|
|
102
|
+
recordFile;
|
|
103
|
+
constructor(script, options = {}) {
|
|
104
|
+
if (typeof script === "function")
|
|
105
|
+
this.scriptLoader = script;
|
|
106
|
+
else if (script !== undefined)
|
|
107
|
+
this.setScript(script);
|
|
108
|
+
const record = options.recordFile;
|
|
109
|
+
this.recordFile = typeof record === "function" ? record : () => record;
|
|
110
|
+
}
|
|
111
|
+
api = {
|
|
112
|
+
id: FAKE_API_ID,
|
|
113
|
+
stream: (model, context, options) => this.stream(model, context, options),
|
|
114
|
+
};
|
|
115
|
+
get callCount() {
|
|
116
|
+
return this.calls.length;
|
|
117
|
+
}
|
|
118
|
+
setScript(script) {
|
|
119
|
+
this.script = parseFakeScript(script);
|
|
120
|
+
this.loaded = true;
|
|
121
|
+
}
|
|
122
|
+
append(...responses) {
|
|
123
|
+
this.ensureLoaded();
|
|
124
|
+
this.script ??= { version: 1, responses: [] };
|
|
125
|
+
this.script.responses.push(...responses);
|
|
126
|
+
}
|
|
127
|
+
reset() {
|
|
128
|
+
this.calls.length = 0;
|
|
129
|
+
}
|
|
130
|
+
ensureLoaded() {
|
|
131
|
+
if (this.loaded)
|
|
132
|
+
return;
|
|
133
|
+
this.loaded = true;
|
|
134
|
+
this.script = this.scriptLoader?.();
|
|
135
|
+
}
|
|
136
|
+
/** 第 index 次调用的响应;undefined = 回显。 */
|
|
137
|
+
responseFor(index) {
|
|
138
|
+
this.ensureLoaded();
|
|
139
|
+
const script = this.script;
|
|
140
|
+
if (!script)
|
|
141
|
+
return undefined;
|
|
142
|
+
const direct = script.responses[index];
|
|
143
|
+
if (direct)
|
|
144
|
+
return direct;
|
|
145
|
+
const behavior = script.whenExhausted ?? "echo";
|
|
146
|
+
if (behavior === "repeat-last")
|
|
147
|
+
return script.responses[script.responses.length - 1];
|
|
148
|
+
if (behavior === "error") {
|
|
149
|
+
return { error: { kind: "custom", message: `fake script exhausted at call ${index + 1}` } };
|
|
150
|
+
}
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
stream(model, context, options) {
|
|
154
|
+
const index = this.calls.length;
|
|
155
|
+
const { signal: _s, onPayload: _p, onResponse: _r, ...rest } = options;
|
|
156
|
+
this.calls.push({ index, model, context, options: rest });
|
|
157
|
+
this.record(index, model, context, options);
|
|
158
|
+
const response = this.responseFor(index) ?? {
|
|
159
|
+
text: lastUserText(context) || "(no input)",
|
|
160
|
+
};
|
|
161
|
+
const events = new AssistantEventStreamImpl();
|
|
162
|
+
void this.run(events, model, context, options, response).finally(() => events.end());
|
|
163
|
+
return events;
|
|
164
|
+
}
|
|
165
|
+
/** 录制一行;写失败不影响请求(测试会因缺行而失败)。 */
|
|
166
|
+
record(index, model, context, options) {
|
|
167
|
+
const file = this.recordFile();
|
|
168
|
+
if (!file)
|
|
169
|
+
return;
|
|
170
|
+
const normalized = normalizeContext(context);
|
|
171
|
+
const line = {
|
|
172
|
+
index,
|
|
173
|
+
purpose: options.purpose ?? "turn",
|
|
174
|
+
model: `${model.provider}/${model.id}`,
|
|
175
|
+
system: normalized.systemPrompt,
|
|
176
|
+
tools: normalized.tools,
|
|
177
|
+
messagesCount: normalized.messages.length,
|
|
178
|
+
};
|
|
179
|
+
try {
|
|
180
|
+
appendFileSync(file, `${JSON.stringify(line)}\n`);
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
// 录制只服务测试;路径不可写时静默。
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
async run(stream, model, context, options, response) {
|
|
187
|
+
const tracker = new BlockTracker(stream, createOutput(model));
|
|
188
|
+
const { signal } = options;
|
|
189
|
+
const checkAbort = () => {
|
|
190
|
+
if (signal.aborted)
|
|
191
|
+
throw new AbortedError("aborted");
|
|
192
|
+
};
|
|
193
|
+
try {
|
|
194
|
+
options.onPayload?.({ fake: true, model: model.id, messages: context.messages.length });
|
|
195
|
+
if (response.delayMs)
|
|
196
|
+
await sleep(response.delayMs, signal);
|
|
197
|
+
checkAbort();
|
|
198
|
+
const error = response.error ? describeFakeError(response.error) : undefined;
|
|
199
|
+
if (error && response.error?.kind !== "disconnect") {
|
|
200
|
+
options.onResponse?.(error.status, new Headers());
|
|
201
|
+
throw new Error(error.message);
|
|
202
|
+
}
|
|
203
|
+
options.onResponse?.(200, new Headers({ "content-type": "text/event-stream" }));
|
|
204
|
+
if (options.thinkingLevel)
|
|
205
|
+
tracker.output.thinkingLevel = options.thinkingLevel;
|
|
206
|
+
stream.push({ type: "start", partial: tracker.output });
|
|
207
|
+
const steps = [...(response.steps ?? [])];
|
|
208
|
+
if (response.text !== undefined)
|
|
209
|
+
steps.push({ text: response.text });
|
|
210
|
+
let produced = "";
|
|
211
|
+
for (const step of steps) {
|
|
212
|
+
await tick();
|
|
213
|
+
checkAbort();
|
|
214
|
+
produced += await this.emitStep(step, tracker, signal);
|
|
215
|
+
}
|
|
216
|
+
await tick();
|
|
217
|
+
checkAbort();
|
|
218
|
+
const usage = tracker.output.usage;
|
|
219
|
+
usage.input = response.usage?.input ?? Math.ceil(contextChars(context) / 4);
|
|
220
|
+
usage.output = response.usage?.output ?? estimateTokens(produced);
|
|
221
|
+
usage.cacheRead = response.usage?.cacheRead ?? 0;
|
|
222
|
+
usage.cacheWrite = response.usage?.cacheWrite ?? 0;
|
|
223
|
+
if (error)
|
|
224
|
+
throw new Error(error.message);
|
|
225
|
+
const reason = response.stopReason ?? (tracker.hasToolCalls ? "toolUse" : "stop");
|
|
226
|
+
finishDone(stream, tracker, model, reason);
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
finishError(stream, tracker, model, error, signal);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/** 产出一步,返回产出的文本(估算 output token 用)。 */
|
|
233
|
+
async emitStep(step, tracker, signal) {
|
|
234
|
+
if ("delayMs" in step) {
|
|
235
|
+
await sleep(step.delayMs, signal);
|
|
236
|
+
return "";
|
|
237
|
+
}
|
|
238
|
+
if ("text" in step) {
|
|
239
|
+
const index = tracker.startText();
|
|
240
|
+
for (const part of chunks(step.text, step.chunkSize)) {
|
|
241
|
+
tracker.appendText(index, part);
|
|
242
|
+
await tick();
|
|
243
|
+
if (signal.aborted)
|
|
244
|
+
throw new AbortedError("aborted");
|
|
245
|
+
}
|
|
246
|
+
tracker.end(index);
|
|
247
|
+
return step.text;
|
|
248
|
+
}
|
|
249
|
+
if ("thinking" in step) {
|
|
250
|
+
const index = tracker.startThinking("", step.signature ?? "fake-signature");
|
|
251
|
+
for (const part of chunks(step.thinking, step.chunkSize)) {
|
|
252
|
+
tracker.appendThinking(index, part);
|
|
253
|
+
await tick();
|
|
254
|
+
if (signal.aborted)
|
|
255
|
+
throw new AbortedError("aborted");
|
|
256
|
+
}
|
|
257
|
+
tracker.end(index);
|
|
258
|
+
return step.thinking;
|
|
259
|
+
}
|
|
260
|
+
const id = step.toolCall.id ?? `fake_call_${tracker.output.content.length}`;
|
|
261
|
+
const index = tracker.startToolCall(id, step.toolCall.name);
|
|
262
|
+
const json = JSON.stringify(step.toolCall.arguments ?? {});
|
|
263
|
+
for (const part of chunks(json, step.chunkSize)) {
|
|
264
|
+
tracker.appendToolArgs(index, part);
|
|
265
|
+
await tick();
|
|
266
|
+
if (signal.aborted)
|
|
267
|
+
throw new AbortedError("aborted");
|
|
268
|
+
}
|
|
269
|
+
tracker.end(index);
|
|
270
|
+
return json;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
/** 进程级缺省实例(`--provider fake`):脚本来自 AMA_FAKE_SCRIPT,录制到 AMA_FAKE_RECORD。 */
|
|
274
|
+
export const defaultFakeProvider = new FakeProvider(() => {
|
|
275
|
+
const path = process.env[FAKE_SCRIPT_ENV];
|
|
276
|
+
return path ? loadFakeScript(path) : undefined;
|
|
277
|
+
}, { recordFile: () => process.env[FAKE_RECORD_ENV] || undefined });
|
|
278
|
+
export const fakeApi = defaultFakeProvider.api;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 脚本化供应商的脚本形状与加载(设计 §1.2 ai/fake/fake-script.ts、D16)。
|
|
3
|
+
*
|
|
4
|
+
* 第 n 次调用取 `responses[n]`;用完后按 `whenExhausted` 处理(缺省 echo:回显最后一条用户消息)。
|
|
5
|
+
*
|
|
6
|
+
* ```json
|
|
7
|
+
* { "version": 1, "responses": [
|
|
8
|
+
* { "steps": [{ "thinking": "先看文件" }, { "toolCall": { "name": "read", "arguments": { "path": "a.ts" } } }] },
|
|
9
|
+
* { "error": { "kind": "rate_limit" } },
|
|
10
|
+
* { "text": "完成", "usage": { "input": 1200, "output": 30 } },
|
|
11
|
+
* { "steps": [{ "text": "写到一半" }], "error": { "kind": "disconnect" } },
|
|
12
|
+
* { "delayMs": 500, "text": "慢响应", "stopReason": "length" }
|
|
13
|
+
* ] }
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* 错误种类与文案(与真实协议的 errorMessage 同形,便于会话层用同一套分类器):
|
|
17
|
+
* rate_limit 429 / overloaded 529 / server 500 / auth 401 / overflow 400(溢出文案)/
|
|
18
|
+
* network(连接失败)/ disconnect(已有内容后断流)/ custom(自定义 message 与 status)。
|
|
19
|
+
* 除 disconnect 外都发生在 start 之前(与真实 HTTP 错误一致:只有一个 error 事件)。
|
|
20
|
+
*/
|
|
21
|
+
import type { Usage } from "../types.js";
|
|
22
|
+
export type FakeStep = {
|
|
23
|
+
text: string;
|
|
24
|
+
chunkSize?: number;
|
|
25
|
+
} | {
|
|
26
|
+
thinking: string;
|
|
27
|
+
signature?: string;
|
|
28
|
+
chunkSize?: number;
|
|
29
|
+
} | {
|
|
30
|
+
toolCall: {
|
|
31
|
+
name: string;
|
|
32
|
+
arguments?: Record<string, unknown>;
|
|
33
|
+
id?: string;
|
|
34
|
+
};
|
|
35
|
+
chunkSize?: number;
|
|
36
|
+
} | {
|
|
37
|
+
delayMs: number;
|
|
38
|
+
};
|
|
39
|
+
export type FakeErrorKind = "rate_limit" | "overloaded" | "server" | "auth" | "overflow" | "network" | "disconnect" | "custom";
|
|
40
|
+
export interface FakeError {
|
|
41
|
+
kind: FakeErrorKind;
|
|
42
|
+
message?: string;
|
|
43
|
+
status?: number;
|
|
44
|
+
}
|
|
45
|
+
export interface FakeResponse {
|
|
46
|
+
/** 依次产出的块;与 `text` 简写二选一(都给时 text 在后)。 */
|
|
47
|
+
steps?: FakeStep[];
|
|
48
|
+
text?: string;
|
|
49
|
+
/** 缺省:有工具调用 → toolUse,否则 stop。 */
|
|
50
|
+
stopReason?: "stop" | "length" | "toolUse";
|
|
51
|
+
usage?: Partial<Pick<Usage, "input" | "output" | "cacheRead" | "cacheWrite">>;
|
|
52
|
+
error?: FakeError;
|
|
53
|
+
/** start 之前的延迟(毫秒,可被 abort 打断)。 */
|
|
54
|
+
delayMs?: number;
|
|
55
|
+
}
|
|
56
|
+
export type FakeExhaustedBehavior = "echo" | "repeat-last" | "error";
|
|
57
|
+
export interface FakeScript {
|
|
58
|
+
version: 1;
|
|
59
|
+
responses: FakeResponse[];
|
|
60
|
+
whenExhausted?: FakeExhaustedBehavior;
|
|
61
|
+
}
|
|
62
|
+
/** 校验脚本;接受完整脚本对象或响应数组简写。 */
|
|
63
|
+
export declare function parseFakeScript(value: unknown): FakeScript;
|
|
64
|
+
export declare function loadFakeScript(path: string): FakeScript;
|
|
65
|
+
/** 错误种类 → (HTTP 状态, errorMessage)。文案与 overflow.ts / 会话层重试分类器对齐。 */
|
|
66
|
+
export declare function describeFakeError(error: FakeError): {
|
|
67
|
+
status: number;
|
|
68
|
+
message: string;
|
|
69
|
+
};
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 脚本化供应商的脚本形状与加载(设计 §1.2 ai/fake/fake-script.ts、D16)。
|
|
3
|
+
*
|
|
4
|
+
* 第 n 次调用取 `responses[n]`;用完后按 `whenExhausted` 处理(缺省 echo:回显最后一条用户消息)。
|
|
5
|
+
*
|
|
6
|
+
* ```json
|
|
7
|
+
* { "version": 1, "responses": [
|
|
8
|
+
* { "steps": [{ "thinking": "先看文件" }, { "toolCall": { "name": "read", "arguments": { "path": "a.ts" } } }] },
|
|
9
|
+
* { "error": { "kind": "rate_limit" } },
|
|
10
|
+
* { "text": "完成", "usage": { "input": 1200, "output": 30 } },
|
|
11
|
+
* { "steps": [{ "text": "写到一半" }], "error": { "kind": "disconnect" } },
|
|
12
|
+
* { "delayMs": 500, "text": "慢响应", "stopReason": "length" }
|
|
13
|
+
* ] }
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* 错误种类与文案(与真实协议的 errorMessage 同形,便于会话层用同一套分类器):
|
|
17
|
+
* rate_limit 429 / overloaded 529 / server 500 / auth 401 / overflow 400(溢出文案)/
|
|
18
|
+
* network(连接失败)/ disconnect(已有内容后断流)/ custom(自定义 message 与 status)。
|
|
19
|
+
* 除 disconnect 外都发生在 start 之前(与真实 HTTP 错误一致:只有一个 error 事件)。
|
|
20
|
+
*/
|
|
21
|
+
import { readFileSync } from "node:fs";
|
|
22
|
+
import { AmaError } from "../../errors.js";
|
|
23
|
+
const ERROR_KINDS = [
|
|
24
|
+
"rate_limit",
|
|
25
|
+
"overloaded",
|
|
26
|
+
"server",
|
|
27
|
+
"auth",
|
|
28
|
+
"overflow",
|
|
29
|
+
"network",
|
|
30
|
+
"disconnect",
|
|
31
|
+
"custom",
|
|
32
|
+
];
|
|
33
|
+
function fail(path, message) {
|
|
34
|
+
throw new AmaError("invalid_arguments", `fake script ${path}: ${message}`);
|
|
35
|
+
}
|
|
36
|
+
function isRecord(value) {
|
|
37
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
38
|
+
}
|
|
39
|
+
function checkStep(step, path) {
|
|
40
|
+
if (!isRecord(step))
|
|
41
|
+
fail(path, "step must be an object");
|
|
42
|
+
if (typeof step["text"] === "string")
|
|
43
|
+
return step;
|
|
44
|
+
if (typeof step["thinking"] === "string")
|
|
45
|
+
return step;
|
|
46
|
+
if (typeof step["delayMs"] === "number")
|
|
47
|
+
return step;
|
|
48
|
+
const call = step["toolCall"];
|
|
49
|
+
if (isRecord(call) && typeof call["name"] === "string") {
|
|
50
|
+
if (call["arguments"] !== undefined && !isRecord(call["arguments"])) {
|
|
51
|
+
fail(`${path}.toolCall.arguments`, "must be an object");
|
|
52
|
+
}
|
|
53
|
+
return step;
|
|
54
|
+
}
|
|
55
|
+
return fail(path, "expected one of text / thinking / toolCall / delayMs");
|
|
56
|
+
}
|
|
57
|
+
function checkResponse(value, path) {
|
|
58
|
+
if (!isRecord(value))
|
|
59
|
+
fail(path, "response must be an object");
|
|
60
|
+
if (value["steps"] !== undefined) {
|
|
61
|
+
if (!Array.isArray(value["steps"]))
|
|
62
|
+
fail(`${path}.steps`, "must be an array");
|
|
63
|
+
value["steps"].forEach((step, i) => checkStep(step, `${path}.steps[${i}]`));
|
|
64
|
+
}
|
|
65
|
+
if (value["text"] !== undefined && typeof value["text"] !== "string") {
|
|
66
|
+
fail(`${path}.text`, "must be a string");
|
|
67
|
+
}
|
|
68
|
+
const stop = value["stopReason"];
|
|
69
|
+
if (stop !== undefined && stop !== "stop" && stop !== "length" && stop !== "toolUse") {
|
|
70
|
+
fail(`${path}.stopReason`, "must be stop / length / toolUse");
|
|
71
|
+
}
|
|
72
|
+
const error = value["error"];
|
|
73
|
+
if (error !== undefined) {
|
|
74
|
+
if (!isRecord(error) || !ERROR_KINDS.includes(error["kind"])) {
|
|
75
|
+
fail(`${path}.error.kind`, `must be one of ${ERROR_KINDS.join(" / ")}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return value;
|
|
79
|
+
}
|
|
80
|
+
/** 校验脚本;接受完整脚本对象或响应数组简写。 */
|
|
81
|
+
export function parseFakeScript(value) {
|
|
82
|
+
const script = Array.isArray(value) ? { version: 1, responses: value } : value;
|
|
83
|
+
if (!isRecord(script))
|
|
84
|
+
fail("$", "must be an object or an array of responses");
|
|
85
|
+
if (script["version"] !== 1)
|
|
86
|
+
fail("$.version", "must be 1");
|
|
87
|
+
if (!Array.isArray(script["responses"]))
|
|
88
|
+
fail("$.responses", "must be an array");
|
|
89
|
+
script["responses"].forEach((r, i) => checkResponse(r, `$.responses[${i}]`));
|
|
90
|
+
const exhausted = script["whenExhausted"];
|
|
91
|
+
if (exhausted !== undefined && !["echo", "repeat-last", "error"].includes(exhausted)) {
|
|
92
|
+
fail("$.whenExhausted", "must be echo / repeat-last / error");
|
|
93
|
+
}
|
|
94
|
+
return script;
|
|
95
|
+
}
|
|
96
|
+
export function loadFakeScript(path) {
|
|
97
|
+
let raw;
|
|
98
|
+
try {
|
|
99
|
+
raw = readFileSync(path, "utf8");
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
throw new AmaError("invalid_arguments", `cannot read fake script ${path}`, { cause: error });
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
return parseFakeScript(JSON.parse(raw));
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
if (error instanceof AmaError)
|
|
109
|
+
throw error;
|
|
110
|
+
throw new AmaError("invalid_arguments", `fake script ${path} is not valid JSON`, {
|
|
111
|
+
cause: error,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/** 错误种类 → (HTTP 状态, errorMessage)。文案与 overflow.ts / 会话层重试分类器对齐。 */
|
|
116
|
+
export function describeFakeError(error) {
|
|
117
|
+
const custom = (fallback) => error.message ?? fallback;
|
|
118
|
+
switch (error.kind) {
|
|
119
|
+
case "rate_limit":
|
|
120
|
+
return { status: 429, message: `429 rate_limit_error: ${custom("Rate limit exceeded")}` };
|
|
121
|
+
case "overloaded":
|
|
122
|
+
return { status: 529, message: `529 overloaded_error: ${custom("Overloaded")}` };
|
|
123
|
+
case "server":
|
|
124
|
+
return { status: 500, message: `500 api_error: ${custom("Internal server error")}` };
|
|
125
|
+
case "auth":
|
|
126
|
+
return {
|
|
127
|
+
status: 401,
|
|
128
|
+
message: `401 authentication_error: ${custom("invalid x-api-key")}`,
|
|
129
|
+
};
|
|
130
|
+
case "overflow":
|
|
131
|
+
return {
|
|
132
|
+
status: 400,
|
|
133
|
+
message: `400 invalid_request_error: ${custom("prompt is too long: 250000 tokens > 200000 maximum")}`,
|
|
134
|
+
};
|
|
135
|
+
case "network":
|
|
136
|
+
return { status: 0, message: `fetch failed: ${custom("ECONNRESET socket hang up")}` };
|
|
137
|
+
case "disconnect":
|
|
138
|
+
return { status: 200, message: custom("Stream ended before completion") };
|
|
139
|
+
case "custom":
|
|
140
|
+
return { status: error.status ?? 400, message: custom("fake error") };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fetch 包装(设计 §1.2 ai/http.ts):超时、头合并(值为 null 删除)、错误体读取。
|
|
3
|
+
*
|
|
4
|
+
* 代理:Node 的全局 fetch 缺省不读 HTTP(S)_PROXY。Node ≥ 24 设 `NODE_USE_ENV_PROXY=1`
|
|
5
|
+
* 即可让 fetch 走环境变量代理;Node 22 需在启动参数里加 `--use-env-proxy`(22.21+)。
|
|
6
|
+
* 本模块不自行实现代理,env 原样透传给运行时处理。
|
|
7
|
+
*
|
|
8
|
+
* 协议层自身**不重试**(重试在会话层,§3.6)。
|
|
9
|
+
*/
|
|
10
|
+
import type { AuthHeader } from "./types.js";
|
|
11
|
+
export type HeaderSource = Readonly<Record<string, string | null | undefined>> | undefined;
|
|
12
|
+
/** 大小写不敏感地合并头;后者覆盖前者;值为 null 表示删除该头。键保留最后一次出现的写法。 */
|
|
13
|
+
export declare function mergeHeaders(...sources: HeaderSource[]): Record<string, string>;
|
|
14
|
+
export declare function hasHeader(headers: HeaderSource, name: string): boolean;
|
|
15
|
+
/** 头里已经带了鉴权(调用方自管 key)时,流函数不再要求 apiKey。 */
|
|
16
|
+
export declare function hasAuthHeader(headers: HeaderSource): boolean;
|
|
17
|
+
/** 按 AuthHeader 形状生成鉴权头;无 key 时返回空对象。 */
|
|
18
|
+
export declare function authHeaders(apiKey: string | undefined, shape: AuthHeader | undefined, fallback: AuthHeader): Record<string, string>;
|
|
19
|
+
export declare const USER_AGENT: string;
|
|
20
|
+
/** baseUrl 与路径拼接(去掉多余斜杠)。 */
|
|
21
|
+
export declare function joinUrl(baseUrl: string, path: string): string;
|
|
22
|
+
/** 非 2xx 响应;`message` 已是可展示、可被 overflow / 重试分类器匹配的文本。 */
|
|
23
|
+
export declare class HttpError extends Error {
|
|
24
|
+
readonly status: number;
|
|
25
|
+
readonly body: string;
|
|
26
|
+
readonly retryAfterMs: number | undefined;
|
|
27
|
+
constructor(status: number, message: string, body: string, retryAfterMs?: number);
|
|
28
|
+
}
|
|
29
|
+
export declare class RequestTimeoutError extends Error {
|
|
30
|
+
constructor(timeoutMs: number);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* 从错误体提取文本:识别 `{error:{type,message,code}}`、`{error:"..."}`、`{message}`、
|
|
34
|
+
* `{detail}` 等常见形状;不是 JSON 则截断原文。结果形如 `429 rate_limit_error: ...`。
|
|
35
|
+
*/
|
|
36
|
+
export declare function formatErrorBody(status: number, statusText: string, body: string): string;
|
|
37
|
+
/** 识别 JSON 错误体;无法识别返回 undefined。也用于 SSE 流内的 error 事件。 */
|
|
38
|
+
export declare function describeErrorJson(value: unknown): string | undefined;
|
|
39
|
+
export interface PostOptions {
|
|
40
|
+
headers: Record<string, string>;
|
|
41
|
+
body: unknown;
|
|
42
|
+
signal: AbortSignal;
|
|
43
|
+
timeoutMs?: number | undefined;
|
|
44
|
+
onResponse?: ((status: number, headers: Headers) => void) | undefined;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* POST JSON 并返回 2xx 响应(body 是 SSE 字节流)。非 2xx → HttpError;超时 → RequestTimeoutError;
|
|
48
|
+
* 调用方的 signal 中止 → 原样抛 AbortError(调用方据 signal.aborted 判 aborted)。
|
|
49
|
+
* 超时只覆盖到拿到响应头为止,流式读取期间由调用方的 signal 控制。
|
|
50
|
+
*/
|
|
51
|
+
export declare function postJson(url: string, options: PostOptions): Promise<Response>;
|
|
52
|
+
/** undici 的 `fetch failed` 把真正原因藏在 cause 里;拼出来便于重试分类与排错。 */
|
|
53
|
+
export declare function normalizeNetworkError(error: unknown): unknown;
|
|
54
|
+
export declare function errorText(error: unknown): string;
|