@armadra/agent 0.0.0-stage → 0.2.1
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 +22 -0
- package/LICENSE +21 -0
- package/README.md +426 -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 +511 -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 +118 -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 +104 -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 +484 -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 +303 -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 +102 -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/registry.d.ts +83 -0
- package/dist/ai/providers/registry.js +305 -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 +428 -0
- package/dist/ai/types.js +19 -0
- package/dist/bundle/ama-sandbox.cjs +324 -0
- package/dist/bundle/ama.cjs +25770 -0
- package/dist/bundle.d.ts +9 -0
- package/dist/bundle.js +15 -0
- package/dist/cli/args.d.ts +82 -0
- package/dist/cli/args.js +381 -0
- package/dist/cli/bootstrap.d.ts +18 -0
- package/dist/cli/bootstrap.js +398 -0
- package/dist/cli/compose-providers.d.ts +37 -0
- package/dist/cli/compose-providers.js +104 -0
- package/dist/cli/compose-session.d.ts +69 -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 +106 -0
- package/dist/cli/compose.js +206 -0
- package/dist/cli/default-model.d.ts +16 -0
- package/dist/cli/default-model.js +24 -0
- package/dist/cli/deps.d.ts +205 -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 +26 -0
- package/dist/cli/main.js +165 -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 +197 -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 +44 -0
- package/dist/cli/subcommands/config.js +201 -0
- package/dist/cli/subcommands/context.d.ts +21 -0
- package/dist/cli/subcommands/context.js +41 -0
- package/dist/cli/subcommands/doctor.d.ts +11 -0
- package/dist/cli/subcommands/doctor.js +246 -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 +40 -0
- package/dist/cli/subcommands/models-discover.js +204 -0
- package/dist/cli/subcommands/models.d.ts +40 -0
- package/dist/cli/subcommands/models.js +144 -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/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 +413 -0
- package/dist/config/trust.d.ts +42 -0
- package/dist/config/trust.js +122 -0
- package/dist/config/types.d.ts +172 -0
- package/dist/config/types.js +27 -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/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 +202 -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 +487 -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 +216 -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 +28 -0
- package/dist/modes/interactive/startup-ui.js +238 -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 +15 -0
- package/dist/modes/print/print-mode.js +96 -0
- package/dist/modes/rpc/commands.d.ts +40 -0
- package/dist/modes/rpc/commands.js +258 -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 +171 -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 +180 -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 +199 -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/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 +28 -0
- package/dist/tools/read.js +181 -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 +316 -0
- package/docs/rpc.md +253 -0
- package/docs/session-format.md +119 -0
- package/docs/tui.md +168 -0
- package/package.json +101 -4
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 有状态 Agent(设计 §4.1–§4.3):持有上下文消息、两条队列、当前 run,发 agent_start / agent_end。[B2]
|
|
3
|
+
*
|
|
4
|
+
* - `run(prompts)`:运行中再调用 → `AmaError{code:"busy"}`;空 prompts = continue(末条须能转成
|
|
5
|
+
* user / toolResult)。返回 RunOutcome;`agent_end.willRetry` 由 `onRunEnd` 决定(会话层重试 /
|
|
6
|
+
* 溢出恢复在 agent_end 之前就要知道是否会重试)。
|
|
7
|
+
* - 监听器按订阅顺序被 await,计入本 run 的收尾;监听器抛错只记录不中断。
|
|
8
|
+
* - `abort()` 只中断当前 run(不清队列);`waitForIdle()` 在 run 与 agent_end 监听器都结束后 resolve。
|
|
9
|
+
* - 消息在 `message_end` 时进入 `messages`;会话层可在 run 之间(或 run 中、请求之前)整体替换。
|
|
10
|
+
*/
|
|
11
|
+
import { AmaError } from "../errors.js";
|
|
12
|
+
import { failureMessage, runLoop, } from "./loop.js";
|
|
13
|
+
import { PendingMessageQueue } from "./queue.js";
|
|
14
|
+
export class Agent {
|
|
15
|
+
messages;
|
|
16
|
+
steeringQueue;
|
|
17
|
+
followUpQueue;
|
|
18
|
+
listeners = new Set();
|
|
19
|
+
options;
|
|
20
|
+
active;
|
|
21
|
+
constructor(options) {
|
|
22
|
+
this.options = options;
|
|
23
|
+
this.messages = options.messages?.slice() ?? [];
|
|
24
|
+
this.steeringQueue = new PendingMessageQueue(options.steeringMode ?? "one-at-a-time");
|
|
25
|
+
this.followUpQueue = new PendingMessageQueue(options.followUpMode ?? "one-at-a-time");
|
|
26
|
+
}
|
|
27
|
+
subscribe(listener) {
|
|
28
|
+
this.listeners.add(listener);
|
|
29
|
+
return () => this.listeners.delete(listener);
|
|
30
|
+
}
|
|
31
|
+
get isRunning() {
|
|
32
|
+
return this.active !== undefined;
|
|
33
|
+
}
|
|
34
|
+
get signal() {
|
|
35
|
+
return this.active?.controller.signal;
|
|
36
|
+
}
|
|
37
|
+
steer(message) {
|
|
38
|
+
this.steeringQueue.enqueue(message);
|
|
39
|
+
}
|
|
40
|
+
followUp(message) {
|
|
41
|
+
this.followUpQueue.enqueue(message);
|
|
42
|
+
}
|
|
43
|
+
abort() {
|
|
44
|
+
this.active?.controller.abort();
|
|
45
|
+
}
|
|
46
|
+
waitForIdle() {
|
|
47
|
+
return this.active?.promise ?? Promise.resolve();
|
|
48
|
+
}
|
|
49
|
+
async emit(event) {
|
|
50
|
+
if (event.type === "message_end")
|
|
51
|
+
this.messages.push(event.message);
|
|
52
|
+
for (const listener of this.listeners) {
|
|
53
|
+
try {
|
|
54
|
+
await listener(event);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
this.options.onListenerError?.(error, event);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async run(prompts, runOptions = {}) {
|
|
62
|
+
if (this.active !== undefined) {
|
|
63
|
+
throw new AmaError("busy", "agent is already running; use steer() or followUp()");
|
|
64
|
+
}
|
|
65
|
+
const controller = new AbortController();
|
|
66
|
+
const parent = runOptions.signal;
|
|
67
|
+
const onParentAbort = () => controller.abort();
|
|
68
|
+
if (parent !== undefined) {
|
|
69
|
+
if (parent.aborted)
|
|
70
|
+
controller.abort();
|
|
71
|
+
else
|
|
72
|
+
parent.addEventListener("abort", onParentAbort, { once: true });
|
|
73
|
+
}
|
|
74
|
+
let resolveIdle = () => { };
|
|
75
|
+
const promise = new Promise((resolve) => {
|
|
76
|
+
resolveIdle = resolve;
|
|
77
|
+
});
|
|
78
|
+
this.active = { controller, promise };
|
|
79
|
+
const options = this.options;
|
|
80
|
+
const emit = (event) => this.emit(event);
|
|
81
|
+
let outcome;
|
|
82
|
+
try {
|
|
83
|
+
await emit({ type: "agent_start" });
|
|
84
|
+
try {
|
|
85
|
+
const loopOptions = runOptions.skipInitialSteering === true ? { skipInitialSteering: true } : {};
|
|
86
|
+
const config = {
|
|
87
|
+
hooks: options.hooks,
|
|
88
|
+
getSteeringMessages: () => this.steeringQueue.drain(),
|
|
89
|
+
getFollowUpMessages: () => this.followUpQueue.drain(),
|
|
90
|
+
getMessages: () => this.messages,
|
|
91
|
+
getModel: options.getModel,
|
|
92
|
+
getThinkingLevel: options.getThinkingLevel,
|
|
93
|
+
stream: options.stream,
|
|
94
|
+
tools: options.tools,
|
|
95
|
+
emit,
|
|
96
|
+
...(options.streamOptions === undefined ? {} : { streamOptions: options.streamOptions }),
|
|
97
|
+
...(options.beforeRequest === undefined ? {} : { beforeRequest: options.beforeRequest }),
|
|
98
|
+
};
|
|
99
|
+
outcome = await runLoop(prompts, config, controller.signal, loopOptions);
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
const aborted = controller.signal.aborted;
|
|
103
|
+
const message = failureMessage(options.getModel(), aborted ? "aborted" : "error", error instanceof Error ? error.message : String(error));
|
|
104
|
+
await emit({ type: "message_start", message });
|
|
105
|
+
await emit({ type: "message_end", message });
|
|
106
|
+
await emit({ type: "turn_end", message, toolResults: [] });
|
|
107
|
+
outcome = {
|
|
108
|
+
lastAssistant: message,
|
|
109
|
+
stopReason: message.stopReason,
|
|
110
|
+
newMessages: [message],
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
let willRetry = false;
|
|
114
|
+
try {
|
|
115
|
+
willRetry = (await runOptions.onRunEnd?.(outcome)) ?? false;
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
willRetry = false;
|
|
119
|
+
}
|
|
120
|
+
await emit({ type: "agent_end", stopReason: outcome.stopReason, willRetry });
|
|
121
|
+
return outcome;
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
parent?.removeEventListener("abort", onParentAbort);
|
|
125
|
+
this.active = undefined;
|
|
126
|
+
resolveIdle();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* runLoop 状态机(设计 §4.1)。[B2]
|
|
3
|
+
*
|
|
4
|
+
* ```text
|
|
5
|
+
* turn_start → 投递 prompts / steer → beforeRequest(system 补丁、首次落盘)→ 流式请求
|
|
6
|
+
* ├─ error / aborted:补齐悬空 tool_call 的结果 → finishTurn → turn_end → 返回
|
|
7
|
+
* ├─ length 且有 tool_call:整批判失败
|
|
8
|
+
* └─ 有 tool_call:tool-runner 执行,结果按原序入转录
|
|
9
|
+
* finishTurn → turn_end → abort? → 收 steer(投递点 = 本轮工具全部结束、下一次模型调用前)
|
|
10
|
+
* 内层结束(无 tool_call 且 steer 空)→ 收 followUp(有则继续外层)→ 返回
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* 循环不发 agent_start / agent_end(由 Agent 发,以便带上 willRetry);上下文每次请求前从
|
|
14
|
+
* `getMessages()` 现取(压缩 / context_edit 会替换 Agent 的消息数组)。
|
|
15
|
+
*/
|
|
16
|
+
import type { AssistantMessage, Model, ModelThinkingLevel, StreamOptions, Usage } from "../ai/types.js";
|
|
17
|
+
import type { StreamFn } from "../compaction/summarize-tier.js";
|
|
18
|
+
import type { AgentMessage } from "../session/types.js";
|
|
19
|
+
import { type LoopEmit, type ToolRunnerOptions } from "./tool-runner.js";
|
|
20
|
+
import type { LoopHooks } from "./types.js";
|
|
21
|
+
export type { StreamFn } from "../compaction/summarize-tier.js";
|
|
22
|
+
export type LoopCallbacks = Omit<LoopHooks, "getSteeringMessages" | "getFollowUpMessages">;
|
|
23
|
+
export interface LoopConfig {
|
|
24
|
+
hooks: LoopCallbacks;
|
|
25
|
+
getSteeringMessages(): AgentMessage[];
|
|
26
|
+
getFollowUpMessages(): AgentMessage[];
|
|
27
|
+
getMessages(): readonly AgentMessage[];
|
|
28
|
+
getModel(): Model;
|
|
29
|
+
getThinkingLevel(): ModelThinkingLevel;
|
|
30
|
+
stream: StreamFn;
|
|
31
|
+
/** 每次请求的额外选项(apiKey、sessionId 等);signal / thinkingLevel 由循环填。 */
|
|
32
|
+
streamOptions?(model: Model): Promise<Partial<StreamOptions>> | Partial<StreamOptions>;
|
|
33
|
+
/** 每次模型请求前:system 补丁、首次落盘、阈值检查等。 */
|
|
34
|
+
beforeRequest?(signal: AbortSignal): Promise<void>;
|
|
35
|
+
tools: ToolRunnerOptions;
|
|
36
|
+
emit: LoopEmit;
|
|
37
|
+
}
|
|
38
|
+
export interface RunOutcome {
|
|
39
|
+
/** 最后一条助手消息(本 run 未产生则 undefined)。 */
|
|
40
|
+
lastAssistant: AssistantMessage | undefined;
|
|
41
|
+
/** 最后一条助手消息的 stopReason;工具执行中被中断为 "aborted"。 */
|
|
42
|
+
stopReason: string;
|
|
43
|
+
newMessages: AgentMessage[];
|
|
44
|
+
}
|
|
45
|
+
export declare const ZERO_USAGE: Usage;
|
|
46
|
+
export declare function failureMessage(model: Model, stopReason: "error" | "aborted", errorMessage: string): AssistantMessage;
|
|
47
|
+
export interface RunLoopOptions {
|
|
48
|
+
/** continue 场景:开头不收 steer(调用方已把 steer 作为 prompts 传入)。 */
|
|
49
|
+
skipInitialSteering?: boolean;
|
|
50
|
+
}
|
|
51
|
+
export declare function runLoop(prompts: readonly AgentMessage[], config: LoopConfig, signal: AbortSignal, options?: RunLoopOptions): Promise<RunOutcome>;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* runLoop 状态机(设计 §4.1)。[B2]
|
|
3
|
+
*
|
|
4
|
+
* ```text
|
|
5
|
+
* turn_start → 投递 prompts / steer → beforeRequest(system 补丁、首次落盘)→ 流式请求
|
|
6
|
+
* ├─ error / aborted:补齐悬空 tool_call 的结果 → finishTurn → turn_end → 返回
|
|
7
|
+
* ├─ length 且有 tool_call:整批判失败
|
|
8
|
+
* └─ 有 tool_call:tool-runner 执行,结果按原序入转录
|
|
9
|
+
* finishTurn → turn_end → abort? → 收 steer(投递点 = 本轮工具全部结束、下一次模型调用前)
|
|
10
|
+
* 内层结束(无 tool_call 且 steer 空)→ 收 followUp(有则继续外层)→ 返回
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* 循环不发 agent_start / agent_end(由 Agent 发,以便带上 willRetry);上下文每次请求前从
|
|
14
|
+
* `getMessages()` 现取(压缩 / context_edit 会替换 Agent 的消息数组)。
|
|
15
|
+
*/
|
|
16
|
+
import { closeDanglingCalls, failTruncatedBatch, runToolBatch, toolCallsOf, } from "./tool-runner.js";
|
|
17
|
+
export const ZERO_USAGE = {
|
|
18
|
+
input: 0,
|
|
19
|
+
output: 0,
|
|
20
|
+
cacheRead: 0,
|
|
21
|
+
cacheWrite: 0,
|
|
22
|
+
totalTokens: 0,
|
|
23
|
+
};
|
|
24
|
+
export function failureMessage(model, stopReason, errorMessage) {
|
|
25
|
+
return {
|
|
26
|
+
role: "assistant",
|
|
27
|
+
content: [],
|
|
28
|
+
api: model.api,
|
|
29
|
+
provider: model.provider,
|
|
30
|
+
model: model.id,
|
|
31
|
+
usage: { ...ZERO_USAGE },
|
|
32
|
+
stopReason,
|
|
33
|
+
errorMessage,
|
|
34
|
+
timestamp: Date.now(),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
async function streamAssistant(request, config, signal) {
|
|
38
|
+
const { model, thinkingLevel } = request;
|
|
39
|
+
const emit = config.emit;
|
|
40
|
+
let started = false;
|
|
41
|
+
const finish = async (message) => {
|
|
42
|
+
const final = message.thinkingLevel === undefined ? { ...message, thinkingLevel } : message;
|
|
43
|
+
if (!started)
|
|
44
|
+
await emit({ type: "message_start", message: final });
|
|
45
|
+
await emit({ type: "message_end", message: final });
|
|
46
|
+
return final;
|
|
47
|
+
};
|
|
48
|
+
try {
|
|
49
|
+
const extra = (await config.streamOptions?.(model)) ?? {};
|
|
50
|
+
const options = { ...extra, signal, thinkingLevel };
|
|
51
|
+
const stream = config.stream(model, request.context, options);
|
|
52
|
+
for await (const event of stream) {
|
|
53
|
+
switch (event.type) {
|
|
54
|
+
case "start":
|
|
55
|
+
started = true;
|
|
56
|
+
await emit({ type: "message_start", message: { ...event.partial } });
|
|
57
|
+
break;
|
|
58
|
+
case "done":
|
|
59
|
+
case "error":
|
|
60
|
+
return finish(await stream.result());
|
|
61
|
+
default:
|
|
62
|
+
if (started) {
|
|
63
|
+
await emit({
|
|
64
|
+
type: "message_update",
|
|
65
|
+
message: { ...event.partial },
|
|
66
|
+
assistantMessageEvent: event,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return finish(await stream.result());
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
75
|
+
return finish(failureMessage(model, signal.aborted ? "aborted" : "error", text));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async function deliver(messages, emit, out) {
|
|
79
|
+
for (const message of messages) {
|
|
80
|
+
await emit({ type: "message_start", message });
|
|
81
|
+
await emit({ type: "message_end", message });
|
|
82
|
+
out.push(message);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
export async function runLoop(prompts, config, signal, options = {}) {
|
|
86
|
+
const { hooks, emit } = config;
|
|
87
|
+
const newMessages = [];
|
|
88
|
+
let lastAssistant;
|
|
89
|
+
let lastTurn;
|
|
90
|
+
const outcome = (stopReason) => ({ lastAssistant, stopReason, newMessages });
|
|
91
|
+
await emit({ type: "turn_start" });
|
|
92
|
+
await deliver(prompts, emit, newMessages);
|
|
93
|
+
let pending = options.skipInitialSteering === true ? [] : config.getSteeringMessages();
|
|
94
|
+
for (;;) {
|
|
95
|
+
let hasMoreToolCalls = true;
|
|
96
|
+
while (hasMoreToolCalls || pending.length > 0) {
|
|
97
|
+
if (lastTurn !== undefined) {
|
|
98
|
+
await hooks.prepareNextTurn?.(lastTurn);
|
|
99
|
+
if (pending.length === 0)
|
|
100
|
+
pending = config.getSteeringMessages();
|
|
101
|
+
await emit({ type: "turn_start" });
|
|
102
|
+
}
|
|
103
|
+
await deliver(pending, emit, newMessages);
|
|
104
|
+
pending = [];
|
|
105
|
+
await config.beforeRequest?.(signal);
|
|
106
|
+
if (signal.aborted)
|
|
107
|
+
return outcome("aborted");
|
|
108
|
+
let messages = [...config.getMessages()];
|
|
109
|
+
if (hooks.transformContext !== undefined)
|
|
110
|
+
messages = await hooks.transformContext(messages, signal);
|
|
111
|
+
let request = {
|
|
112
|
+
context: { messages: hooks.convertToLlm(messages) },
|
|
113
|
+
model: config.getModel(),
|
|
114
|
+
thinkingLevel: config.getThinkingLevel(),
|
|
115
|
+
};
|
|
116
|
+
if (hooks.prepareRequest !== undefined)
|
|
117
|
+
request = await hooks.prepareRequest(request);
|
|
118
|
+
const assistant = await streamAssistant(request, config, signal);
|
|
119
|
+
lastAssistant = assistant;
|
|
120
|
+
newMessages.push(assistant);
|
|
121
|
+
if (assistant.stopReason === "error" || assistant.stopReason === "aborted") {
|
|
122
|
+
const closed = await closeDanglingCalls(assistant, emit);
|
|
123
|
+
newMessages.push(...closed);
|
|
124
|
+
lastTurn = { assistant, toolResults: closed };
|
|
125
|
+
await hooks.finishTurn?.(lastTurn);
|
|
126
|
+
await emit({ type: "turn_end", message: assistant, toolResults: closed });
|
|
127
|
+
return outcome(assistant.stopReason);
|
|
128
|
+
}
|
|
129
|
+
const calls = toolCallsOf(assistant);
|
|
130
|
+
hasMoreToolCalls = false;
|
|
131
|
+
let toolResults = [];
|
|
132
|
+
if (calls.length > 0) {
|
|
133
|
+
const batch = assistant.stopReason === "length"
|
|
134
|
+
? await failTruncatedBatch(assistant, emit)
|
|
135
|
+
: await runToolBatch(assistant, config.tools, signal, emit);
|
|
136
|
+
toolResults = batch.messages;
|
|
137
|
+
newMessages.push(...toolResults);
|
|
138
|
+
hasMoreToolCalls = !batch.terminate;
|
|
139
|
+
}
|
|
140
|
+
lastTurn = { assistant, toolResults };
|
|
141
|
+
const decision = await hooks.finishTurn?.(lastTurn);
|
|
142
|
+
await emit({ type: "turn_end", message: assistant, toolResults });
|
|
143
|
+
if (signal.aborted)
|
|
144
|
+
return outcome("aborted");
|
|
145
|
+
// length 且无工具调用:输出被截断,交给会话层的溢出恢复,不投递 steer / followUp。
|
|
146
|
+
if (assistant.stopReason === "length" && calls.length === 0)
|
|
147
|
+
return outcome("length");
|
|
148
|
+
if (decision === "end")
|
|
149
|
+
return outcome(assistant.stopReason);
|
|
150
|
+
pending = config.getSteeringMessages();
|
|
151
|
+
}
|
|
152
|
+
const followUps = config.getFollowUpMessages();
|
|
153
|
+
if (followUps.length === 0)
|
|
154
|
+
break;
|
|
155
|
+
pending = followUps;
|
|
156
|
+
}
|
|
157
|
+
return outcome(lastAssistant?.stopReason ?? "stop");
|
|
158
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PendingMessageQueue(设计 §4.3):steer / followUp 两条队列共用。[B2]
|
|
3
|
+
*
|
|
4
|
+
* `one-at-a-time`:每个投递点只取一条;`all`:一次取空。abort 不清队列。
|
|
5
|
+
*/
|
|
6
|
+
import type { QueueMode } from "./types.js";
|
|
7
|
+
import type { AgentMessage } from "../session/types.js";
|
|
8
|
+
export declare class PendingMessageQueue {
|
|
9
|
+
private items;
|
|
10
|
+
mode: QueueMode;
|
|
11
|
+
constructor(mode?: QueueMode);
|
|
12
|
+
get size(): number;
|
|
13
|
+
enqueue(message: AgentMessage): void;
|
|
14
|
+
hasItems(): boolean;
|
|
15
|
+
/** 下一个投递点会取走的消息(不消费)。 */
|
|
16
|
+
peek(): AgentMessage[];
|
|
17
|
+
drain(): AgentMessage[];
|
|
18
|
+
/** 取走全部(clearQueue)。 */
|
|
19
|
+
clear(): AgentMessage[];
|
|
20
|
+
/** 取走最后一条(Alt+Up 取回)。 */
|
|
21
|
+
popLast(): AgentMessage | undefined;
|
|
22
|
+
snapshot(): readonly AgentMessage[];
|
|
23
|
+
}
|
|
24
|
+
/** 队列消息的显示文本(queue_update 事件用)。 */
|
|
25
|
+
export declare function queuedText(message: AgentMessage): string;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PendingMessageQueue(设计 §4.3):steer / followUp 两条队列共用。[B2]
|
|
3
|
+
*
|
|
4
|
+
* `one-at-a-time`:每个投递点只取一条;`all`:一次取空。abort 不清队列。
|
|
5
|
+
*/
|
|
6
|
+
export class PendingMessageQueue {
|
|
7
|
+
items = [];
|
|
8
|
+
mode;
|
|
9
|
+
constructor(mode = "one-at-a-time") {
|
|
10
|
+
this.mode = mode;
|
|
11
|
+
}
|
|
12
|
+
get size() {
|
|
13
|
+
return this.items.length;
|
|
14
|
+
}
|
|
15
|
+
enqueue(message) {
|
|
16
|
+
this.items.push(message);
|
|
17
|
+
}
|
|
18
|
+
hasItems() {
|
|
19
|
+
return this.items.length > 0;
|
|
20
|
+
}
|
|
21
|
+
/** 下一个投递点会取走的消息(不消费)。 */
|
|
22
|
+
peek() {
|
|
23
|
+
if (this.mode === "all")
|
|
24
|
+
return this.items.slice();
|
|
25
|
+
const first = this.items[0];
|
|
26
|
+
return first === undefined ? [] : [first];
|
|
27
|
+
}
|
|
28
|
+
drain() {
|
|
29
|
+
const drained = this.peek();
|
|
30
|
+
this.items = this.items.slice(drained.length);
|
|
31
|
+
return drained;
|
|
32
|
+
}
|
|
33
|
+
/** 取走全部(clearQueue)。 */
|
|
34
|
+
clear() {
|
|
35
|
+
const all = this.items;
|
|
36
|
+
this.items = [];
|
|
37
|
+
return all;
|
|
38
|
+
}
|
|
39
|
+
/** 取走最后一条(Alt+Up 取回)。 */
|
|
40
|
+
popLast() {
|
|
41
|
+
return this.items.pop();
|
|
42
|
+
}
|
|
43
|
+
snapshot() {
|
|
44
|
+
return this.items.slice();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** 队列消息的显示文本(queue_update 事件用)。 */
|
|
48
|
+
export function queuedText(message) {
|
|
49
|
+
if (message.role !== "user" && message.role !== "custom")
|
|
50
|
+
return "";
|
|
51
|
+
const { content } = message;
|
|
52
|
+
if (typeof content === "string")
|
|
53
|
+
return content;
|
|
54
|
+
return content.map((block) => (block.type === "text" ? block.text : "[image]")).join("");
|
|
55
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 会话层重试(设计 §3.6「重试」「溢出」)。[B2]
|
|
3
|
+
*
|
|
4
|
+
* 判定顺序:上下文溢出(不重试,走压缩)→ 不可重试(配额 / 计费 / key / 401 / 403,快速失败)→
|
|
5
|
+
* 可重试(429、5xx、overloaded、网络错误、断流)→ 其它(不重试)。
|
|
6
|
+
* 延迟 `baseDelayMs × 2^(attempt−1)`,上限 `maxDelayMs`;`sleep` 可被 abort 打断。
|
|
7
|
+
* 协议层自身不重试。溢出文案识别只有一份:ai/overflow.ts 的 `isOverflowErrorText`(缺省),可注入替换。
|
|
8
|
+
*/
|
|
9
|
+
import type { AssistantMessage } from "../ai/types.js";
|
|
10
|
+
import type { RetrySettings } from "./types.js";
|
|
11
|
+
export declare const DEFAULT_RETRY_SETTINGS: RetrySettings;
|
|
12
|
+
export type FailureKind = "overflow" | "fatal" | "retryable" | "other";
|
|
13
|
+
export interface ClassifyOptions {
|
|
14
|
+
isContextOverflow?: (errorMessage: string) => boolean;
|
|
15
|
+
}
|
|
16
|
+
/** 对 `stopReason: "error"` 的助手消息分类;`length` 且无工具调用也算 overflow。 */
|
|
17
|
+
export declare function classifyFailure(message: AssistantMessage, options?: ClassifyOptions): FailureKind;
|
|
18
|
+
export declare function resolveRetrySettings(partial?: Partial<RetrySettings>): RetrySettings;
|
|
19
|
+
/** 第 attempt 次重试(1 起)前的等待。 */
|
|
20
|
+
export declare function retryDelayMs(attempt: number, settings: RetrySettings): number;
|
|
21
|
+
/** 可被 abort 打断的等待;被打断时抛 AmaError{code:"aborted"}。 */
|
|
22
|
+
export declare function sleep(ms: number, signal?: AbortSignal): Promise<void>;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 会话层重试(设计 §3.6「重试」「溢出」)。[B2]
|
|
3
|
+
*
|
|
4
|
+
* 判定顺序:上下文溢出(不重试,走压缩)→ 不可重试(配额 / 计费 / key / 401 / 403,快速失败)→
|
|
5
|
+
* 可重试(429、5xx、overloaded、网络错误、断流)→ 其它(不重试)。
|
|
6
|
+
* 延迟 `baseDelayMs × 2^(attempt−1)`,上限 `maxDelayMs`;`sleep` 可被 abort 打断。
|
|
7
|
+
* 协议层自身不重试。溢出文案识别只有一份:ai/overflow.ts 的 `isOverflowErrorText`(缺省),可注入替换。
|
|
8
|
+
*/
|
|
9
|
+
import { isOverflowErrorText } from "../ai/overflow.js";
|
|
10
|
+
import { AmaError } from "../errors.js";
|
|
11
|
+
export const DEFAULT_RETRY_SETTINGS = {
|
|
12
|
+
enabled: true,
|
|
13
|
+
maxRetries: 3,
|
|
14
|
+
baseDelayMs: 2000,
|
|
15
|
+
maxDelayMs: 60_000,
|
|
16
|
+
};
|
|
17
|
+
const FATAL_PATTERNS = [
|
|
18
|
+
/insufficient[_ ]quota/i,
|
|
19
|
+
/quota exceeded/i,
|
|
20
|
+
/billing/i,
|
|
21
|
+
/monthly usage limit/i,
|
|
22
|
+
/invalid[_ ]api[_ ]key/i,
|
|
23
|
+
/incorrect api key/i,
|
|
24
|
+
/no[_ ]api[_ ]key/i,
|
|
25
|
+
/\b(?:401|403)\b/,
|
|
26
|
+
/unauthori[sz]ed/i,
|
|
27
|
+
/permission denied/i,
|
|
28
|
+
/authentication/i,
|
|
29
|
+
];
|
|
30
|
+
const RETRYABLE_PATTERNS = [
|
|
31
|
+
/\b429\b/,
|
|
32
|
+
/rate[_ ]?limit/i,
|
|
33
|
+
/too many requests/i,
|
|
34
|
+
/\b5\d\d\b/,
|
|
35
|
+
/overloaded/i,
|
|
36
|
+
/server error/i,
|
|
37
|
+
/service unavailable/i,
|
|
38
|
+
/bad gateway/i,
|
|
39
|
+
/gateway timeout/i,
|
|
40
|
+
/timed? ?out/i,
|
|
41
|
+
/network/i,
|
|
42
|
+
/fetch failed/i,
|
|
43
|
+
/ECONNRESET|ECONNREFUSED|ENOTFOUND|EAI_AGAIN|ETIMEDOUT|EPIPE/,
|
|
44
|
+
/socket hang up/i,
|
|
45
|
+
/stream (?:ended|closed|terminated)/i,
|
|
46
|
+
/premature (?:close|end)/i,
|
|
47
|
+
/terminated/i,
|
|
48
|
+
/connection (?:reset|closed|error)/i,
|
|
49
|
+
];
|
|
50
|
+
/** 对 `stopReason: "error"` 的助手消息分类;`length` 且无工具调用也算 overflow。 */
|
|
51
|
+
export function classifyFailure(message, options = {}) {
|
|
52
|
+
if (message.stopReason === "length") {
|
|
53
|
+
return message.content.some((block) => block.type === "toolCall") ? "other" : "overflow";
|
|
54
|
+
}
|
|
55
|
+
if (message.stopReason !== "error")
|
|
56
|
+
return "other";
|
|
57
|
+
const text = message.errorMessage ?? "";
|
|
58
|
+
const isOverflow = options.isContextOverflow ?? isOverflowErrorText;
|
|
59
|
+
if (isOverflow(text))
|
|
60
|
+
return "overflow";
|
|
61
|
+
if (FATAL_PATTERNS.some((pattern) => pattern.test(text)))
|
|
62
|
+
return "fatal";
|
|
63
|
+
if (RETRYABLE_PATTERNS.some((pattern) => pattern.test(text)))
|
|
64
|
+
return "retryable";
|
|
65
|
+
return "other";
|
|
66
|
+
}
|
|
67
|
+
export function resolveRetrySettings(partial = {}) {
|
|
68
|
+
return { ...DEFAULT_RETRY_SETTINGS, ...partial };
|
|
69
|
+
}
|
|
70
|
+
/** 第 attempt 次重试(1 起)前的等待。 */
|
|
71
|
+
export function retryDelayMs(attempt, settings) {
|
|
72
|
+
const raw = settings.baseDelayMs * 2 ** Math.max(0, attempt - 1);
|
|
73
|
+
return Math.min(raw, settings.maxDelayMs);
|
|
74
|
+
}
|
|
75
|
+
/** 可被 abort 打断的等待;被打断时抛 AmaError{code:"aborted"}。 */
|
|
76
|
+
export function sleep(ms, signal) {
|
|
77
|
+
return new Promise((resolve, reject) => {
|
|
78
|
+
if (signal?.aborted === true) {
|
|
79
|
+
reject(new AmaError("aborted", "aborted"));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const timer = setTimeout(() => {
|
|
83
|
+
signal?.removeEventListener("abort", onAbort);
|
|
84
|
+
resolve();
|
|
85
|
+
}, Math.max(0, ms));
|
|
86
|
+
const onAbort = () => {
|
|
87
|
+
clearTimeout(timer);
|
|
88
|
+
reject(new AmaError("aborted", "aborted"));
|
|
89
|
+
};
|
|
90
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
91
|
+
});
|
|
92
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON Schema 子集校验(设计 §2、§5.1)。[B0] 所有。
|
|
3
|
+
*
|
|
4
|
+
* 子集:type(object / string / number / integer / boolean / array)、properties、required、
|
|
5
|
+
* additionalProperties(布尔)、items、enum(原始值)、description / title / default(只做注释)。
|
|
6
|
+
*
|
|
7
|
+
* - `validateSchema(schema, value)` 返回错误列表;空数组 = 通过。路径形如 `$.edits[0].oldText`,
|
|
8
|
+
* 非标识符键用 `$["a b"]`。
|
|
9
|
+
* - `checkSchemaSubset(schema)` 检查 schema 本身只用了子集关键字(工具注册时调用)。
|
|
10
|
+
* - 不做类型强转("1" 不会当作 1),不填 default。
|
|
11
|
+
*/
|
|
12
|
+
import type { JsonSchema } from "../ai/types.js";
|
|
13
|
+
export type { JsonSchema, JsonSchemaType } from "../ai/types.js";
|
|
14
|
+
export type SchemaErrorKeyword = "type" | "required" | "additionalProperties" | "enum";
|
|
15
|
+
export interface SchemaError {
|
|
16
|
+
/** `$` 为根。 */
|
|
17
|
+
path: string;
|
|
18
|
+
keyword: SchemaErrorKeyword;
|
|
19
|
+
message: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function joinPath(base: string, key: string | number): string;
|
|
22
|
+
/** 校验 value;返回全部错误(不在第一处停下)。 */
|
|
23
|
+
export declare function validateSchema(schema: JsonSchema, value: unknown): SchemaError[];
|
|
24
|
+
/** 把错误列表格式化成给模型看的多行文本。 */
|
|
25
|
+
export declare function formatSchemaErrors(errors: readonly SchemaError[]): string;
|
|
26
|
+
/** 检查 schema 只用了子集关键字;返回问题列表,空数组 = 合规。 */
|
|
27
|
+
export declare function checkSchemaSubset(schema: unknown): string[];
|