@bastani/atomic 0.8.26-alpha.1 → 0.8.26-alpha.11
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 +79 -0
- package/README.md +5 -5
- package/dist/builtin/intercom/CHANGELOG.md +60 -0
- package/dist/builtin/intercom/package.json +2 -2
- package/dist/builtin/mcp/CHANGELOG.md +60 -0
- package/dist/builtin/mcp/package.json +3 -3
- package/dist/builtin/subagents/CHANGELOG.md +61 -0
- package/dist/builtin/subagents/agents/codebase-analyzer.md +1 -1
- package/dist/builtin/subagents/agents/codebase-locator.md +1 -1
- package/dist/builtin/subagents/agents/codebase-online-researcher.md +9 -9
- package/dist/builtin/subagents/agents/codebase-pattern-finder.md +1 -1
- package/dist/builtin/subagents/agents/codebase-research-analyzer.md +1 -1
- package/dist/builtin/subagents/agents/codebase-research-locator.md +1 -1
- package/dist/builtin/subagents/agents/debugger.md +6 -6
- package/dist/builtin/subagents/package.json +4 -4
- package/dist/builtin/subagents/prompts/parallel-handoff-plan.md +1 -1
- package/dist/builtin/subagents/skills/browser/EXAMPLES.md +151 -0
- package/dist/builtin/subagents/skills/browser/LICENSE.txt +21 -0
- package/dist/builtin/subagents/skills/browser/REFERENCE.md +451 -0
- package/dist/builtin/subagents/skills/browser/SKILL.md +170 -0
- package/dist/builtin/subagents/skills/subagent/SKILL.md +4 -4
- package/dist/builtin/subagents/src/runs/background/subagent-runner.ts +55 -12
- package/dist/builtin/subagents/src/runs/foreground/execution.ts +71 -12
- package/dist/builtin/subagents/src/runs/shared/acceptance.ts +2 -1
- package/dist/builtin/subagents/src/runs/shared/final-drain.ts +34 -0
- package/dist/builtin/subagents/src/runs/shared/model-fallback.ts +416 -7
- package/dist/builtin/subagents/src/runs/shared/worktree.ts +2 -2
- package/dist/builtin/web-access/CHANGELOG.md +60 -0
- package/dist/builtin/web-access/package.json +2 -2
- package/dist/builtin/workflows/CHANGELOG.md +72 -0
- package/dist/builtin/workflows/README.md +10 -8
- package/dist/builtin/workflows/builtin/deep-research-codebase.ts +11 -8
- package/dist/builtin/workflows/builtin/goal.ts +137 -109
- package/dist/builtin/workflows/builtin/index.d.ts +2 -0
- package/dist/builtin/workflows/builtin/open-claude-design.ts +228 -151
- package/dist/builtin/workflows/builtin/ralph.d.ts +2 -0
- package/dist/builtin/workflows/builtin/ralph.ts +452 -279
- package/dist/builtin/workflows/package.json +2 -2
- package/dist/builtin/workflows/skills/create-spec/SKILL.md +14 -0
- package/dist/builtin/workflows/skills/research-codebase/SKILL.md +29 -10
- package/dist/builtin/workflows/src/extension/index.ts +10 -2
- package/dist/builtin/workflows/src/extension/runtime.ts +35 -3
- package/dist/builtin/workflows/src/extension/wiring.ts +13 -1
- package/dist/builtin/workflows/src/runs/background/status.ts +52 -6
- package/dist/builtin/workflows/src/runs/foreground/executor.ts +453 -21
- package/dist/builtin/workflows/src/runs/foreground/stage-runner.ts +77 -11
- package/dist/builtin/workflows/src/runs/shared/model-fallback.ts +402 -8
- package/dist/builtin/workflows/src/runs/shared/worktree.ts +2 -2
- package/dist/builtin/workflows/src/shared/authoring-contract.d.ts +2 -2
- package/dist/builtin/workflows/src/shared/persistence-restore.ts +182 -6
- package/dist/builtin/workflows/src/shared/persistence-session-entries.ts +76 -6
- package/dist/builtin/workflows/src/shared/stage-prompt.ts +33 -2
- package/dist/builtin/workflows/src/shared/store-types.ts +31 -0
- package/dist/builtin/workflows/src/shared/store.ts +160 -18
- package/dist/builtin/workflows/src/shared/types.ts +3 -3
- package/dist/builtin/workflows/src/shared/workflow-failures.ts +758 -132
- package/dist/builtin/workflows/src/tui/stage-chat-view.ts +39 -3
- package/dist/builtin/workflows/src/tui/store-widget-installer.ts +74 -74
- package/dist/core/agent-session.d.ts +33 -6
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +157 -182
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/atomic-guide-command.d.ts.map +1 -1
- package/dist/core/atomic-guide-command.js +11 -9
- package/dist/core/atomic-guide-command.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts +1 -1
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +6 -3
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +23 -10
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/compaction/context-compaction.d.ts +175 -0
- package/dist/core/compaction/context-compaction.d.ts.map +1 -0
- package/dist/core/compaction/context-compaction.js +1636 -0
- package/dist/core/compaction/context-compaction.js.map +1 -0
- package/dist/core/compaction/index.d.ts +1 -0
- package/dist/core/compaction/index.d.ts.map +1 -1
- package/dist/core/compaction/index.js +1 -0
- package/dist/core/compaction/index.js.map +1 -1
- package/dist/core/extensions/types.d.ts +3 -2
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +3 -0
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +14 -7
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/session-manager.d.ts +41 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +146 -7
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +1 -1
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/tools/ask-user-question/tool/format-answer.d.ts +5 -5
- package/dist/core/tools/ask-user-question/tool/format-answer.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/tool/format-answer.js +5 -5
- package/dist/core/tools/ask-user-question/tool/format-answer.js.map +1 -1
- package/dist/core/tools/ask-user-question/tool/response-envelope.d.ts +16 -3
- package/dist/core/tools/ask-user-question/tool/response-envelope.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/tool/response-envelope.js +21 -3
- package/dist/core/tools/ask-user-question/tool/response-envelope.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/modes/index.d.ts +1 -1
- package/dist/modes/index.d.ts.map +1 -1
- package/dist/modes/index.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host.js +17 -0
- package/dist/modes/interactive/components/chat-session-host.js.map +1 -1
- package/dist/modes/interactive/components/context-compaction-summary-message.d.ts +17 -0
- package/dist/modes/interactive/components/context-compaction-summary-message.d.ts.map +1 -0
- package/dist/modes/interactive/components/context-compaction-summary-message.js +83 -0
- package/dist/modes/interactive/components/context-compaction-summary-message.js.map +1 -0
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +4 -1
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/index.d.ts +1 -0
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js +1 -0
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +75 -10
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +13 -8
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +8 -1
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +4 -0
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +14 -3
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/utils/git-env.d.ts +10 -0
- package/dist/utils/git-env.d.ts.map +1 -0
- package/dist/utils/git-env.js +33 -0
- package/dist/utils/git-env.js.map +1 -0
- package/docs/compaction.md +185 -50
- package/docs/custom-provider.md +11 -9
- package/docs/extensions.md +46 -42
- package/docs/index.md +13 -6
- package/docs/json.md +15 -12
- package/docs/packages.md +2 -0
- package/docs/providers.md +4 -1
- package/docs/quickstart.md +18 -11
- package/docs/rpc.md +38 -23
- package/docs/sdk.md +17 -8
- package/docs/session-format.md +26 -13
- package/docs/sessions.md +3 -3
- package/docs/settings.md +2 -2
- package/docs/skills.md +1 -15
- package/docs/termux.md +9 -10
- package/docs/themes.md +2 -2
- package/docs/tmux.md +3 -3
- package/docs/tui.md +19 -32
- package/docs/usage.md +2 -2
- package/docs/workflows.md +60 -16
- package/package.json +6 -12
- package/dist/builtin/subagents/skills/browser-use/SKILL.md +0 -234
- package/dist/builtin/subagents/skills/browser-use/references/cdp-python.md +0 -76
- package/dist/builtin/subagents/skills/browser-use/references/multi-session.md +0 -92
- package/node_modules/@earendil-works/pi-tui/README.md +0 -779
- package/node_modules/@earendil-works/pi-tui/dist/autocomplete.d.ts +0 -54
- package/node_modules/@earendil-works/pi-tui/dist/autocomplete.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/autocomplete.js +0 -632
- package/node_modules/@earendil-works/pi-tui/dist/autocomplete.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/box.d.ts +0 -22
- package/node_modules/@earendil-works/pi-tui/dist/components/box.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/box.js +0 -104
- package/node_modules/@earendil-works/pi-tui/dist/components/box.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/cancellable-loader.d.ts +0 -22
- package/node_modules/@earendil-works/pi-tui/dist/components/cancellable-loader.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/cancellable-loader.js +0 -35
- package/node_modules/@earendil-works/pi-tui/dist/components/cancellable-loader.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts +0 -249
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +0 -1857
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/image.d.ts +0 -28
- package/node_modules/@earendil-works/pi-tui/dist/components/image.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/image.js +0 -89
- package/node_modules/@earendil-works/pi-tui/dist/components/image.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/input.d.ts +0 -37
- package/node_modules/@earendil-works/pi-tui/dist/components/input.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/input.js +0 -378
- package/node_modules/@earendil-works/pi-tui/dist/components/input.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/loader.d.ts +0 -31
- package/node_modules/@earendil-works/pi-tui/dist/components/loader.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/loader.js +0 -69
- package/node_modules/@earendil-works/pi-tui/dist/components/loader.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/markdown.d.ts +0 -96
- package/node_modules/@earendil-works/pi-tui/dist/components/markdown.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/markdown.js +0 -644
- package/node_modules/@earendil-works/pi-tui/dist/components/markdown.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/select-list.d.ts +0 -50
- package/node_modules/@earendil-works/pi-tui/dist/components/select-list.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/select-list.js +0 -159
- package/node_modules/@earendil-works/pi-tui/dist/components/select-list.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/settings-list.d.ts +0 -50
- package/node_modules/@earendil-works/pi-tui/dist/components/settings-list.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/settings-list.js +0 -185
- package/node_modules/@earendil-works/pi-tui/dist/components/settings-list.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/spacer.d.ts +0 -12
- package/node_modules/@earendil-works/pi-tui/dist/components/spacer.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/spacer.js +0 -23
- package/node_modules/@earendil-works/pi-tui/dist/components/spacer.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/text.d.ts +0 -19
- package/node_modules/@earendil-works/pi-tui/dist/components/text.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/text.js +0 -89
- package/node_modules/@earendil-works/pi-tui/dist/components/text.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/truncated-text.d.ts +0 -13
- package/node_modules/@earendil-works/pi-tui/dist/components/truncated-text.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/truncated-text.js +0 -51
- package/node_modules/@earendil-works/pi-tui/dist/components/truncated-text.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/editor-component.d.ts +0 -39
- package/node_modules/@earendil-works/pi-tui/dist/editor-component.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/editor-component.js +0 -2
- package/node_modules/@earendil-works/pi-tui/dist/editor-component.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/fuzzy.d.ts +0 -16
- package/node_modules/@earendil-works/pi-tui/dist/fuzzy.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/fuzzy.js +0 -110
- package/node_modules/@earendil-works/pi-tui/dist/fuzzy.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/index.d.ts +0 -23
- package/node_modules/@earendil-works/pi-tui/dist/index.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/index.js +0 -32
- package/node_modules/@earendil-works/pi-tui/dist/index.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/keybindings.d.ts +0 -193
- package/node_modules/@earendil-works/pi-tui/dist/keybindings.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/keybindings.js +0 -174
- package/node_modules/@earendil-works/pi-tui/dist/keybindings.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/keys.d.ts +0 -184
- package/node_modules/@earendil-works/pi-tui/dist/keys.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/keys.js +0 -1173
- package/node_modules/@earendil-works/pi-tui/dist/keys.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/kill-ring.d.ts +0 -28
- package/node_modules/@earendil-works/pi-tui/dist/kill-ring.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/kill-ring.js +0 -44
- package/node_modules/@earendil-works/pi-tui/dist/kill-ring.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/native-modifiers.d.ts +0 -3
- package/node_modules/@earendil-works/pi-tui/dist/native-modifiers.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/native-modifiers.js +0 -53
- package/node_modules/@earendil-works/pi-tui/dist/native-modifiers.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/stdin-buffer.d.ts +0 -50
- package/node_modules/@earendil-works/pi-tui/dist/stdin-buffer.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/stdin-buffer.js +0 -361
- package/node_modules/@earendil-works/pi-tui/dist/stdin-buffer.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal-image.d.ts +0 -90
- package/node_modules/@earendil-works/pi-tui/dist/terminal-image.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal-image.js +0 -366
- package/node_modules/@earendil-works/pi-tui/dist/terminal-image.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts +0 -113
- package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal.js +0 -472
- package/node_modules/@earendil-works/pi-tui/dist/terminal.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/tui.d.ts +0 -227
- package/node_modules/@earendil-works/pi-tui/dist/tui.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/tui.js +0 -1106
- package/node_modules/@earendil-works/pi-tui/dist/tui.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/undo-stack.d.ts +0 -17
- package/node_modules/@earendil-works/pi-tui/dist/undo-stack.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/undo-stack.js +0 -25
- package/node_modules/@earendil-works/pi-tui/dist/undo-stack.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/utils.d.ts +0 -84
- package/node_modules/@earendil-works/pi-tui/dist/utils.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/utils.js +0 -1029
- package/node_modules/@earendil-works/pi-tui/dist/utils.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/word-navigation.d.ts +0 -25
- package/node_modules/@earendil-works/pi-tui/dist/word-navigation.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-tui/dist/word-navigation.js +0 -96
- package/node_modules/@earendil-works/pi-tui/dist/word-navigation.js.map +0 -1
- package/node_modules/@earendil-works/pi-tui/native/darwin/prebuilds/darwin-arm64/darwin-modifiers.node +0 -0
- package/node_modules/@earendil-works/pi-tui/native/darwin/prebuilds/darwin-x64/darwin-modifiers.node +0 -0
- package/node_modules/@earendil-works/pi-tui/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
- package/node_modules/@earendil-works/pi-tui/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
- package/node_modules/@earendil-works/pi-tui/package.json +0 -47
- package/node_modules/get-east-asian-width/index.d.ts +0 -60
- package/node_modules/get-east-asian-width/index.js +0 -30
- package/node_modules/get-east-asian-width/license +0 -9
- package/node_modules/get-east-asian-width/lookup-data.js +0 -21
- package/node_modules/get-east-asian-width/lookup.js +0 -138
- package/node_modules/get-east-asian-width/package.json +0 -71
- package/node_modules/get-east-asian-width/readme.md +0 -65
- package/node_modules/get-east-asian-width/utilities.js +0 -24
- package/node_modules/marked/LICENSE.md +0 -44
- package/node_modules/marked/README.md +0 -106
- package/node_modules/marked/bin/main.js +0 -282
- package/node_modules/marked/bin/marked.js +0 -15
- package/node_modules/marked/lib/marked.cjs +0 -2211
- package/node_modules/marked/lib/marked.cjs.map +0 -7
- package/node_modules/marked/lib/marked.d.cts +0 -728
- package/node_modules/marked/lib/marked.d.ts +0 -728
- package/node_modules/marked/lib/marked.esm.js +0 -2189
- package/node_modules/marked/lib/marked.esm.js.map +0 -7
- package/node_modules/marked/lib/marked.umd.js +0 -2213
- package/node_modules/marked/lib/marked.umd.js.map +0 -7
- package/node_modules/marked/man/marked.1 +0 -111
- package/node_modules/marked/man/marked.1.md +0 -92
- package/node_modules/marked/marked.min.js +0 -69
- package/node_modules/marked/package.json +0 -111
|
@@ -1,19 +1,41 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
WorkflowFailureCode,
|
|
3
|
+
WorkflowFailureDisposition,
|
|
4
|
+
WorkflowFailureKind,
|
|
5
|
+
WorkflowFailureRecoverability,
|
|
6
|
+
} from "./store-types.js";
|
|
2
7
|
|
|
3
8
|
export interface WorkflowFailure {
|
|
4
9
|
readonly kind: WorkflowFailureKind;
|
|
5
|
-
/**
|
|
10
|
+
/** Specific additive reason within the existing broad failure kind. */
|
|
11
|
+
readonly code?: WorkflowFailureCode;
|
|
12
|
+
/** Redacted diagnostic text safe for snapshots and persistence. */
|
|
6
13
|
readonly message: string;
|
|
7
14
|
/** Sanitized workflow-facing text shown on run/stage snapshots. */
|
|
8
15
|
readonly userMessage: string;
|
|
9
16
|
readonly retryable: boolean;
|
|
10
17
|
readonly resumable: boolean;
|
|
18
|
+
readonly recoverability: WorkflowFailureRecoverability;
|
|
19
|
+
readonly disposition: WorkflowFailureDisposition;
|
|
20
|
+
readonly retryAfterMs?: number;
|
|
11
21
|
readonly cause?: unknown;
|
|
12
22
|
}
|
|
13
23
|
|
|
14
24
|
export const WORKFLOW_AUTH_FAILURE_MESSAGE =
|
|
15
25
|
"You must be logged in to run workflows. Run /login and try again.";
|
|
16
26
|
|
|
27
|
+
export const WORKFLOW_MISSING_API_KEY_FAILURE_MESSAGE =
|
|
28
|
+
"A required model provider API key is missing. Configure the provider credentials and resume the workflow.";
|
|
29
|
+
|
|
30
|
+
export const WORKFLOW_INVALID_PROVIDER_CREDENTIALS_MESSAGE =
|
|
31
|
+
"The configured model provider credentials are invalid. Update the provider API key, then start a new workflow run.";
|
|
32
|
+
|
|
33
|
+
export const WORKFLOW_FORBIDDEN_MODEL_CONFIG_MESSAGE =
|
|
34
|
+
"The configured model provider or model is not available with the current credentials. Update the model configuration, then start a new workflow run.";
|
|
35
|
+
|
|
36
|
+
export const WORKFLOW_UNKNOWN_MODEL_MESSAGE =
|
|
37
|
+
"The configured model is not available. Update the workflow model configuration, then start a new workflow run.";
|
|
38
|
+
|
|
17
39
|
const WORKFLOW_FAILURE_KINDS: ReadonlySet<WorkflowFailureKind> = new Set([
|
|
18
40
|
"auth",
|
|
19
41
|
"rate_limit",
|
|
@@ -26,22 +48,56 @@ export function isWorkflowFailureKind(kind: string): kind is WorkflowFailureKind
|
|
|
26
48
|
return WORKFLOW_FAILURE_KINDS.has(kind as WorkflowFailureKind);
|
|
27
49
|
}
|
|
28
50
|
|
|
51
|
+
export function isWorkflowFailureCode(code: string): code is WorkflowFailureCode {
|
|
52
|
+
switch (code) {
|
|
53
|
+
case "login_required":
|
|
54
|
+
case "missing_api_key":
|
|
55
|
+
case "invalid_api_key":
|
|
56
|
+
case "forbidden_config":
|
|
57
|
+
case "unknown_model":
|
|
58
|
+
case "rate_limited":
|
|
59
|
+
case "quota_limited":
|
|
60
|
+
case "provider_unavailable":
|
|
61
|
+
case "cancelled":
|
|
62
|
+
case "unknown":
|
|
63
|
+
return true;
|
|
64
|
+
default:
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function isWorkflowFailureRecoverability(value: string): value is WorkflowFailureRecoverability {
|
|
70
|
+
return value === "recoverable" || value === "non_recoverable" || value === "unknown";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function isWorkflowFailureDisposition(value: string): value is WorkflowFailureDisposition {
|
|
74
|
+
return value === "active_blocked" || value === "terminal_killed" || value === "terminal_failed";
|
|
75
|
+
}
|
|
76
|
+
|
|
29
77
|
function makeWorkflowFailure(
|
|
30
78
|
kind: WorkflowFailureKind,
|
|
31
79
|
message: string,
|
|
32
80
|
opts: {
|
|
33
81
|
readonly retryable: boolean;
|
|
34
82
|
readonly resumable: boolean;
|
|
83
|
+
readonly recoverability: WorkflowFailureRecoverability;
|
|
84
|
+
readonly disposition: WorkflowFailureDisposition;
|
|
35
85
|
readonly cause: unknown;
|
|
86
|
+
readonly code?: WorkflowFailureCode;
|
|
87
|
+
readonly retryAfterMs?: number;
|
|
36
88
|
readonly userMessage?: string;
|
|
37
89
|
},
|
|
38
90
|
): WorkflowFailure {
|
|
39
91
|
return {
|
|
40
92
|
kind,
|
|
93
|
+
...(opts.code !== undefined ? { code: opts.code } : {}),
|
|
41
94
|
message,
|
|
42
|
-
userMessage: opts.userMessage ?? message,
|
|
95
|
+
userMessage: opts.userMessage ?? redactSensitiveText(message),
|
|
43
96
|
retryable: opts.retryable,
|
|
44
97
|
resumable: opts.resumable,
|
|
98
|
+
recoverability: opts.recoverability,
|
|
99
|
+
disposition: opts.disposition,
|
|
100
|
+
...(opts.retryAfterMs !== undefined ? { retryAfterMs: opts.retryAfterMs } : {}),
|
|
45
101
|
cause: opts.cause,
|
|
46
102
|
};
|
|
47
103
|
}
|
|
@@ -82,6 +138,35 @@ type StructuredSignal = {
|
|
|
82
138
|
readonly code?: string | number;
|
|
83
139
|
readonly name?: string;
|
|
84
140
|
readonly stopReason?: string;
|
|
141
|
+
readonly message?: string;
|
|
142
|
+
readonly retryAfterMs?: number;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
type WorkflowFailureDecision = {
|
|
146
|
+
readonly kind: WorkflowFailureKind;
|
|
147
|
+
readonly code: WorkflowFailureCode;
|
|
148
|
+
readonly retryable: boolean;
|
|
149
|
+
readonly resumable: boolean;
|
|
150
|
+
readonly recoverability: WorkflowFailureRecoverability;
|
|
151
|
+
readonly disposition: WorkflowFailureDisposition;
|
|
152
|
+
readonly userMessage?: string;
|
|
153
|
+
readonly retryAfterMs?: number;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
type WorkflowFailureClassificationSource =
|
|
157
|
+
| "top_level"
|
|
158
|
+
| "diagnostic"
|
|
159
|
+
| "nested"
|
|
160
|
+
| "cause"
|
|
161
|
+
| "aggregate";
|
|
162
|
+
|
|
163
|
+
type WorkflowFailureEvidence = "strong_signal" | "weak_signal" | "message" | "status";
|
|
164
|
+
|
|
165
|
+
type WorkflowFailureClassification = {
|
|
166
|
+
readonly decision: WorkflowFailureDecision;
|
|
167
|
+
readonly source: WorkflowFailureClassificationSource;
|
|
168
|
+
readonly evidence: WorkflowFailureEvidence;
|
|
169
|
+
readonly message?: string;
|
|
85
170
|
};
|
|
86
171
|
|
|
87
172
|
function integerFrom(value: unknown): number | undefined {
|
|
@@ -91,17 +176,61 @@ function integerFrom(value: unknown): number | undefined {
|
|
|
91
176
|
return Number.isInteger(parsed) ? parsed : undefined;
|
|
92
177
|
}
|
|
93
178
|
|
|
179
|
+
function numberFrom(value: unknown): number | undefined {
|
|
180
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
181
|
+
if (typeof value !== "string" || value.trim().length === 0) return undefined;
|
|
182
|
+
const parsed = Number(value.trim());
|
|
183
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function retryAfterHeaderMs(value: unknown): number | undefined {
|
|
187
|
+
const numeric = numberFrom(value);
|
|
188
|
+
if (numeric !== undefined && numeric >= 0) return Math.round(numeric * 1000);
|
|
189
|
+
if (typeof value !== "string" || value.trim().length === 0) return undefined;
|
|
190
|
+
const dateMs = Date.parse(value);
|
|
191
|
+
if (!Number.isFinite(dateMs)) return undefined;
|
|
192
|
+
return Math.max(0, dateMs - Date.now());
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function retryAfterMsFrom(error: unknown): number | undefined {
|
|
196
|
+
const directMs = numberFrom(field(error, "retryAfterMs"));
|
|
197
|
+
if (directMs !== undefined && directMs >= 0) return Math.round(directMs);
|
|
198
|
+
|
|
199
|
+
const seconds = numberFrom(field(error, "retryAfterSeconds"));
|
|
200
|
+
if (seconds !== undefined && seconds >= 0) return Math.round(seconds * 1000);
|
|
201
|
+
|
|
202
|
+
// Provider SDKs commonly mirror the HTTP Retry-After header as retryAfter,
|
|
203
|
+
// so the ambiguous bare field follows header semantics (seconds/date). Use
|
|
204
|
+
// retryAfterMs for explicit millisecond values.
|
|
205
|
+
const retryAfter = retryAfterHeaderMs(field(error, "retryAfter"));
|
|
206
|
+
if (retryAfter !== undefined) return retryAfter;
|
|
207
|
+
|
|
208
|
+
const retryAfterHeader = retryAfterHeaderMs(field(error, "retry-after"));
|
|
209
|
+
if (retryAfterHeader !== undefined) return retryAfterHeader;
|
|
210
|
+
|
|
211
|
+
const headers = field(error, "headers");
|
|
212
|
+
const headerRecord = asRecord(headers);
|
|
213
|
+
const headerValue = headerRecord?.["retry-after"] ?? headerRecord?.["Retry-After"];
|
|
214
|
+
return retryAfterHeaderMs(headerValue);
|
|
215
|
+
}
|
|
216
|
+
|
|
94
217
|
function structuredSignal(error: unknown): StructuredSignal {
|
|
95
218
|
const status = integerFrom(field(error, "status"))
|
|
96
219
|
?? integerFrom(field(error, "statusCode"))
|
|
97
220
|
?? integerFrom(field(error, "httpStatus"));
|
|
98
221
|
const rawCode = field(error, "code");
|
|
99
222
|
const code = typeof rawCode === "string" || typeof rawCode === "number" ? rawCode : undefined;
|
|
223
|
+
const name = errorName(error);
|
|
224
|
+
const stopReason = stringField(error, "stopReason");
|
|
225
|
+
const message = structuredErrorMessage(error);
|
|
226
|
+
const retryAfterMs = retryAfterMsFrom(error);
|
|
100
227
|
return {
|
|
101
228
|
...(status !== undefined ? { status } : {}),
|
|
102
229
|
...(code !== undefined ? { code } : {}),
|
|
103
|
-
...(
|
|
104
|
-
...(
|
|
230
|
+
...(name !== undefined ? { name } : {}),
|
|
231
|
+
...(stopReason !== undefined ? { stopReason } : {}),
|
|
232
|
+
...(message !== undefined ? { message } : {}),
|
|
233
|
+
...(retryAfterMs !== undefined ? { retryAfterMs } : {}),
|
|
105
234
|
};
|
|
106
235
|
}
|
|
107
236
|
|
|
@@ -121,121 +250,38 @@ function diagnosticErrors(error: unknown): readonly unknown[] {
|
|
|
121
250
|
return errors;
|
|
122
251
|
}
|
|
123
252
|
|
|
253
|
+
function nestedProviderError(error: unknown): unknown {
|
|
254
|
+
return field(error, "error") ?? field(error, "response") ?? field(error, "body");
|
|
255
|
+
}
|
|
256
|
+
|
|
124
257
|
function normalizeCode(value: string | number | undefined): string | undefined {
|
|
125
258
|
if (value === undefined) return undefined;
|
|
126
|
-
return String(value).trim().toLowerCase().replaceAll("-", "_");
|
|
259
|
+
return String(value).trim().toLowerCase().replaceAll("-", "_").replaceAll(" ", "_");
|
|
127
260
|
}
|
|
128
261
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
case 403:
|
|
133
|
-
return "auth";
|
|
134
|
-
case 429:
|
|
135
|
-
return "rate_limit";
|
|
136
|
-
case 500:
|
|
137
|
-
case 502:
|
|
138
|
-
case 503:
|
|
139
|
-
case 504:
|
|
140
|
-
return "provider";
|
|
141
|
-
default:
|
|
142
|
-
return undefined;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
262
|
+
type StructuredCodeEvidence =
|
|
263
|
+
| { readonly kind: "semantic_code"; readonly normalized: string }
|
|
264
|
+
| { readonly kind: "wrapper_http_status"; readonly status: number };
|
|
145
265
|
|
|
146
|
-
function
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
return undefined;
|
|
151
|
-
case "401":
|
|
152
|
-
case "403":
|
|
153
|
-
case "auth":
|
|
154
|
-
case "auth_required":
|
|
155
|
-
case "authentication_required":
|
|
156
|
-
case "unauthorized":
|
|
157
|
-
case "forbidden":
|
|
158
|
-
case "invalid_api_key":
|
|
159
|
-
case "missing_api_key":
|
|
160
|
-
return "auth";
|
|
161
|
-
case "429":
|
|
162
|
-
case "rate_limit":
|
|
163
|
-
case "rate_limit_exceeded":
|
|
164
|
-
case "too_many_requests":
|
|
165
|
-
case "quota_exceeded":
|
|
166
|
-
return "rate_limit";
|
|
167
|
-
case "aborterror":
|
|
168
|
-
case "aborted":
|
|
169
|
-
case "cancelled":
|
|
170
|
-
case "canceled":
|
|
171
|
-
return "cancelled";
|
|
172
|
-
case "500":
|
|
173
|
-
case "502":
|
|
174
|
-
case "503":
|
|
175
|
-
case "504":
|
|
176
|
-
case "provider_error":
|
|
177
|
-
case "service_unavailable":
|
|
178
|
-
case "temporarily_unavailable":
|
|
179
|
-
case "overloaded":
|
|
180
|
-
return "provider";
|
|
181
|
-
default:
|
|
182
|
-
return undefined;
|
|
266
|
+
function httpStatusFromCode(value: string | number | undefined): number | undefined {
|
|
267
|
+
if (value === undefined) return undefined;
|
|
268
|
+
if (typeof value === "number") {
|
|
269
|
+
return Number.isInteger(value) && value >= 100 && value <= 599 ? value : undefined;
|
|
183
270
|
}
|
|
271
|
+
const trimmed = value.trim();
|
|
272
|
+
if (!/^\d{3}$/.test(trimmed)) return undefined;
|
|
273
|
+
const parsed = Number(trimmed);
|
|
274
|
+
return parsed >= 100 && parsed <= 599 ? parsed : undefined;
|
|
184
275
|
}
|
|
185
276
|
|
|
186
|
-
function
|
|
187
|
-
|
|
188
|
-
if (
|
|
189
|
-
|
|
190
|
-
const signal = structuredSignal(error);
|
|
191
|
-
if (signal.stopReason?.toLowerCase() === "aborted") return "cancelled";
|
|
192
|
-
const statusKind = kindFromStatus(signal.status);
|
|
193
|
-
if (statusKind !== undefined) return statusKind;
|
|
194
|
-
const codeKind = kindFromCode(signal.code) ?? kindFromCode(signal.name);
|
|
195
|
-
if (codeKind !== undefined) return codeKind;
|
|
196
|
-
|
|
197
|
-
for (const diagnosticError of diagnosticErrors(error)) {
|
|
198
|
-
const diagnosticKind = structuredKind(diagnosticError, seen);
|
|
199
|
-
if (diagnosticKind !== undefined) return diagnosticKind;
|
|
200
|
-
}
|
|
277
|
+
function codeEvidenceFrom(value: string | number | undefined): StructuredCodeEvidence | undefined {
|
|
278
|
+
const status = httpStatusFromCode(value);
|
|
279
|
+
if (status !== undefined) return { kind: "wrapper_http_status", status };
|
|
201
280
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
switch (kind) {
|
|
207
|
-
case "auth":
|
|
208
|
-
return makeWorkflowFailure("auth", message, {
|
|
209
|
-
userMessage: WORKFLOW_AUTH_FAILURE_MESSAGE,
|
|
210
|
-
retryable: true,
|
|
211
|
-
resumable: true,
|
|
212
|
-
cause,
|
|
213
|
-
});
|
|
214
|
-
case "rate_limit":
|
|
215
|
-
return makeWorkflowFailure("rate_limit", message, {
|
|
216
|
-
retryable: true,
|
|
217
|
-
resumable: true,
|
|
218
|
-
cause,
|
|
219
|
-
});
|
|
220
|
-
case "cancelled":
|
|
221
|
-
return makeWorkflowFailure("cancelled", message, {
|
|
222
|
-
retryable: false,
|
|
223
|
-
resumable: false,
|
|
224
|
-
cause,
|
|
225
|
-
});
|
|
226
|
-
case "provider":
|
|
227
|
-
return makeWorkflowFailure("provider", message, {
|
|
228
|
-
retryable: true,
|
|
229
|
-
resumable: true,
|
|
230
|
-
cause,
|
|
231
|
-
});
|
|
232
|
-
case "unknown":
|
|
233
|
-
return makeWorkflowFailure("unknown", message, {
|
|
234
|
-
retryable: false,
|
|
235
|
-
resumable: true,
|
|
236
|
-
cause,
|
|
237
|
-
});
|
|
238
|
-
}
|
|
281
|
+
const normalized = normalizeCode(value);
|
|
282
|
+
return normalized !== undefined && normalized.length > 0
|
|
283
|
+
? { kind: "semantic_code", normalized }
|
|
284
|
+
: undefined;
|
|
239
285
|
}
|
|
240
286
|
|
|
241
287
|
type TokenMatch = readonly string[];
|
|
@@ -286,24 +332,141 @@ function tokenNearAny(tokens: readonly string[], anchor: string, candidates: Rea
|
|
|
286
332
|
return false;
|
|
287
333
|
}
|
|
288
334
|
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
335
|
+
const INVALID_API_KEY_CODES = new Set([
|
|
336
|
+
"401",
|
|
337
|
+
"invalid_api_key",
|
|
338
|
+
"incorrect_api_key",
|
|
339
|
+
"invalid_api_key_error",
|
|
340
|
+
"invalid_credentials",
|
|
341
|
+
"bad_credentials",
|
|
342
|
+
"authentication_error",
|
|
343
|
+
]);
|
|
344
|
+
|
|
345
|
+
const LOGIN_REQUIRED_CODES = new Set([
|
|
346
|
+
"auth",
|
|
347
|
+
"auth_required",
|
|
348
|
+
"authentication_required",
|
|
349
|
+
"login_required",
|
|
350
|
+
"not_logged_in",
|
|
351
|
+
]);
|
|
352
|
+
|
|
353
|
+
const MISSING_API_KEY_CODES = new Set([
|
|
354
|
+
"missing_api_key",
|
|
355
|
+
"api_key_missing",
|
|
356
|
+
"no_api_key",
|
|
357
|
+
]);
|
|
358
|
+
|
|
359
|
+
const RATE_LIMIT_CODES = new Set([
|
|
360
|
+
"429",
|
|
361
|
+
"rate_limit",
|
|
362
|
+
"rate_limited",
|
|
363
|
+
"rate_limit_exceeded",
|
|
364
|
+
"too_many_requests",
|
|
365
|
+
]);
|
|
366
|
+
|
|
367
|
+
const QUOTA_LIMIT_CODES = new Set([
|
|
368
|
+
"quota",
|
|
369
|
+
"quota_exceeded",
|
|
370
|
+
"insufficient_quota",
|
|
371
|
+
"usage_limit",
|
|
372
|
+
"usage_limit_exceeded",
|
|
373
|
+
]);
|
|
374
|
+
|
|
375
|
+
const CANCELLED_CODES = new Set([
|
|
376
|
+
"aborterror",
|
|
377
|
+
"aborted",
|
|
378
|
+
"cancelled",
|
|
379
|
+
"canceled",
|
|
380
|
+
]);
|
|
381
|
+
|
|
382
|
+
const PROVIDER_UNAVAILABLE_CODES = new Set([
|
|
383
|
+
"500",
|
|
384
|
+
"502",
|
|
385
|
+
"503",
|
|
386
|
+
"504",
|
|
387
|
+
"provider_error",
|
|
388
|
+
"service_unavailable",
|
|
389
|
+
"temporarily_unavailable",
|
|
390
|
+
"overloaded",
|
|
391
|
+
"timeout",
|
|
392
|
+
"network_error",
|
|
393
|
+
]);
|
|
394
|
+
|
|
395
|
+
const UNKNOWN_MODEL_CODES = new Set([
|
|
396
|
+
"unknown_model",
|
|
397
|
+
"model_not_found",
|
|
398
|
+
"model_not_available",
|
|
399
|
+
"unsupported_model",
|
|
400
|
+
]);
|
|
401
|
+
|
|
402
|
+
const FORBIDDEN_CONFIG_CODES = new Set([
|
|
403
|
+
"403",
|
|
404
|
+
"forbidden",
|
|
405
|
+
"permission_denied",
|
|
406
|
+
"access_denied",
|
|
407
|
+
"forbidden_config",
|
|
408
|
+
"invalid_model_config",
|
|
409
|
+
"model_access_denied",
|
|
410
|
+
]);
|
|
411
|
+
|
|
412
|
+
const LOGIN_REQUIRED_PHRASES: readonly TokenMatch[] = [
|
|
295
413
|
["not", "logged", "in"],
|
|
296
414
|
["log", "in"],
|
|
297
415
|
["login", "required"],
|
|
298
416
|
["authentication", "required"],
|
|
417
|
+
["please", "login"],
|
|
418
|
+
["please", "log", "in"],
|
|
299
419
|
["unauthorized"],
|
|
300
420
|
];
|
|
301
421
|
|
|
422
|
+
const LOCAL_LOGIN_REQUIRED_PHRASES: readonly TokenMatch[] = [
|
|
423
|
+
["not", "logged", "in"],
|
|
424
|
+
["login", "required"],
|
|
425
|
+
["please", "login"],
|
|
426
|
+
["please", "log", "in"],
|
|
427
|
+
["log", "in", "to", "continue"],
|
|
428
|
+
];
|
|
429
|
+
|
|
430
|
+
const PROVIDER_AUTH_FALLBACK_PHRASES: readonly TokenMatch[] = [
|
|
431
|
+
["unauthorized"],
|
|
432
|
+
["authentication", "required"],
|
|
433
|
+
];
|
|
434
|
+
|
|
435
|
+
const MISSING_API_KEY_PHRASES: readonly TokenMatch[] = [
|
|
436
|
+
["no", "api", "key"],
|
|
437
|
+
["api", "key", "not", "found"],
|
|
438
|
+
["missing", "api", "key"],
|
|
439
|
+
["api", "key", "missing"],
|
|
440
|
+
["no", "model", "selected"],
|
|
441
|
+
["no", "models", "available"],
|
|
442
|
+
];
|
|
443
|
+
|
|
444
|
+
const INVALID_API_KEY_PHRASES: readonly TokenMatch[] = [
|
|
445
|
+
["incorrect", "api", "key"],
|
|
446
|
+
["invalid", "api", "key"],
|
|
447
|
+
["api", "key", "invalid"],
|
|
448
|
+
["api", "key", "incorrect"],
|
|
449
|
+
["invalid", "credentials"],
|
|
450
|
+
["invalid", "credential"],
|
|
451
|
+
];
|
|
452
|
+
|
|
453
|
+
const INVALID_API_KEY_CONTEXT = new Set(["invalid", "incorrect"]);
|
|
454
|
+
|
|
455
|
+
const HTTP_RATE_LIMIT_PHRASES: readonly TokenMatch[] = [
|
|
456
|
+
["429"],
|
|
457
|
+
["too", "many", "requests"],
|
|
458
|
+
];
|
|
459
|
+
|
|
302
460
|
const RATE_LIMIT_PHRASES: readonly TokenMatch[] = [
|
|
303
461
|
["rate", "limit"],
|
|
304
|
-
["
|
|
462
|
+
["rate", "limited"],
|
|
463
|
+
];
|
|
464
|
+
|
|
465
|
+
const QUOTA_LIMIT_PHRASES: readonly TokenMatch[] = [
|
|
305
466
|
["quota"],
|
|
306
|
-
["
|
|
467
|
+
["quota", "exceeded"],
|
|
468
|
+
["insufficient", "quota"],
|
|
469
|
+
["usage", "limit"],
|
|
307
470
|
];
|
|
308
471
|
|
|
309
472
|
const CANCELLED_PHRASES: readonly TokenMatch[] = [
|
|
@@ -312,11 +475,30 @@ const CANCELLED_PHRASES: readonly TokenMatch[] = [
|
|
|
312
475
|
["canceled"],
|
|
313
476
|
];
|
|
314
477
|
|
|
315
|
-
const
|
|
478
|
+
const UNKNOWN_MODEL_PHRASES: readonly TokenMatch[] = [
|
|
316
479
|
["model", "not", "found"],
|
|
480
|
+
["unknown", "model"],
|
|
481
|
+
["unsupported", "model"],
|
|
482
|
+
["model", "does", "not", "exist"],
|
|
483
|
+
["model", "not", "available"],
|
|
484
|
+
];
|
|
485
|
+
|
|
486
|
+
const FORBIDDEN_CONFIG_PHRASES: readonly TokenMatch[] = [
|
|
487
|
+
["forbidden", "config"],
|
|
488
|
+
["forbidden", "configuration"],
|
|
489
|
+
["permission", "denied"],
|
|
490
|
+
["access", "denied"],
|
|
491
|
+
["not", "allowed", "to", "access", "model"],
|
|
492
|
+
["does", "not", "have", "access", "to", "model"],
|
|
493
|
+
];
|
|
494
|
+
|
|
495
|
+
const PROVIDER_UNAVAILABLE_PHRASES: readonly TokenMatch[] = [
|
|
317
496
|
["overloaded"],
|
|
318
497
|
["temporarily", "unavailable"],
|
|
319
498
|
["service", "unavailable"],
|
|
499
|
+
["provider", "unavailable"],
|
|
500
|
+
["provider", "error"],
|
|
501
|
+
["model", "unavailable"],
|
|
320
502
|
["503"],
|
|
321
503
|
];
|
|
322
504
|
|
|
@@ -350,26 +532,470 @@ const PROVIDER_CONTEXT = new Set([
|
|
|
350
532
|
"service",
|
|
351
533
|
]);
|
|
352
534
|
|
|
353
|
-
function
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
535
|
+
function redactedSecretReplacement(prefix: string): string {
|
|
536
|
+
return `${prefix}[redacted]`;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function redactSensitiveText(value: string): string {
|
|
540
|
+
return value
|
|
541
|
+
.replace(/(sk-[A-Za-z0-9_-]{8})[A-Za-z0-9_-]+/g, redactedSecretReplacement("$1"))
|
|
542
|
+
.replace(/\b(authorization\s*:\s*bearer\s+)[^\s,;]+/gi, "$1[redacted]")
|
|
543
|
+
.replace(/\b(bearer\s+)[A-Za-z0-9._~+/-]{8,}=*/gi, "$1[redacted]")
|
|
544
|
+
.replace(/((?:api[_-]?key|token|credential|secret)\s*[:=]\s*)[^\s,;]+/gi, "$1[redacted]");
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function authDecision(code: WorkflowFailureCode): WorkflowFailureDecision {
|
|
548
|
+
if (code === "invalid_api_key") {
|
|
549
|
+
return {
|
|
550
|
+
kind: "auth",
|
|
551
|
+
code,
|
|
552
|
+
retryable: false,
|
|
553
|
+
resumable: false,
|
|
554
|
+
recoverability: "non_recoverable",
|
|
555
|
+
disposition: "terminal_killed",
|
|
556
|
+
userMessage: WORKFLOW_INVALID_PROVIDER_CREDENTIALS_MESSAGE,
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
if (code === "missing_api_key") {
|
|
560
|
+
return {
|
|
561
|
+
kind: "auth",
|
|
562
|
+
code,
|
|
563
|
+
retryable: true,
|
|
564
|
+
resumable: true,
|
|
565
|
+
recoverability: "recoverable",
|
|
566
|
+
disposition: "active_blocked",
|
|
567
|
+
userMessage: WORKFLOW_MISSING_API_KEY_FAILURE_MESSAGE,
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
return {
|
|
571
|
+
kind: "auth",
|
|
572
|
+
code: "login_required",
|
|
573
|
+
retryable: true,
|
|
574
|
+
resumable: true,
|
|
575
|
+
recoverability: "recoverable",
|
|
576
|
+
disposition: "active_blocked",
|
|
577
|
+
userMessage: WORKFLOW_AUTH_FAILURE_MESSAGE,
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function rateLimitDecision(
|
|
582
|
+
code: "rate_limited" | "quota_limited",
|
|
583
|
+
retryAfterMs?: number,
|
|
584
|
+
): WorkflowFailureDecision {
|
|
585
|
+
return {
|
|
586
|
+
kind: "rate_limit",
|
|
587
|
+
code,
|
|
588
|
+
retryable: true,
|
|
589
|
+
resumable: true,
|
|
590
|
+
recoverability: "recoverable",
|
|
591
|
+
disposition: "active_blocked",
|
|
592
|
+
...(retryAfterMs !== undefined ? { retryAfterMs } : {}),
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function providerUnavailableDecision(retryAfterMs?: number): WorkflowFailureDecision {
|
|
597
|
+
return {
|
|
598
|
+
kind: "provider",
|
|
599
|
+
code: "provider_unavailable",
|
|
600
|
+
retryable: true,
|
|
601
|
+
resumable: true,
|
|
602
|
+
recoverability: "recoverable",
|
|
603
|
+
disposition: "active_blocked",
|
|
604
|
+
...(retryAfterMs !== undefined ? { retryAfterMs } : {}),
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function terminalProviderConfigDecision(code: "forbidden_config" | "unknown_model"): WorkflowFailureDecision {
|
|
609
|
+
return {
|
|
610
|
+
kind: "provider",
|
|
611
|
+
code,
|
|
612
|
+
retryable: false,
|
|
613
|
+
resumable: false,
|
|
614
|
+
recoverability: "non_recoverable",
|
|
615
|
+
disposition: "terminal_killed",
|
|
616
|
+
userMessage: code === "unknown_model" ? WORKFLOW_UNKNOWN_MODEL_MESSAGE : WORKFLOW_FORBIDDEN_MODEL_CONFIG_MESSAGE,
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function cancelledDecision(): WorkflowFailureDecision {
|
|
621
|
+
return {
|
|
622
|
+
kind: "cancelled",
|
|
623
|
+
code: "cancelled",
|
|
624
|
+
retryable: false,
|
|
625
|
+
resumable: false,
|
|
626
|
+
recoverability: "non_recoverable",
|
|
627
|
+
disposition: "terminal_killed",
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function unknownDecision(): WorkflowFailureDecision {
|
|
632
|
+
return {
|
|
633
|
+
kind: "unknown",
|
|
634
|
+
code: "unknown",
|
|
635
|
+
retryable: false,
|
|
636
|
+
resumable: true,
|
|
637
|
+
recoverability: "unknown",
|
|
638
|
+
disposition: "terminal_failed",
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function strongDecisionFromNormalizedCode(normalized: string | undefined, retryAfterMs?: number): WorkflowFailureDecision | undefined {
|
|
643
|
+
if (normalized === undefined) return undefined;
|
|
644
|
+
if (CANCELLED_CODES.has(normalized)) return cancelledDecision();
|
|
645
|
+
if (INVALID_API_KEY_CODES.has(normalized)) return authDecision("invalid_api_key");
|
|
646
|
+
if (MISSING_API_KEY_CODES.has(normalized)) return authDecision("missing_api_key");
|
|
647
|
+
if (RATE_LIMIT_CODES.has(normalized)) return rateLimitDecision("rate_limited", retryAfterMs);
|
|
648
|
+
if (QUOTA_LIMIT_CODES.has(normalized)) return rateLimitDecision("quota_limited", retryAfterMs);
|
|
649
|
+
if (UNKNOWN_MODEL_CODES.has(normalized)) return terminalProviderConfigDecision("unknown_model");
|
|
650
|
+
if (FORBIDDEN_CONFIG_CODES.has(normalized)) return terminalProviderConfigDecision("forbidden_config");
|
|
651
|
+
if (PROVIDER_UNAVAILABLE_CODES.has(normalized)) return providerUnavailableDecision(retryAfterMs);
|
|
652
|
+
return undefined;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function weakLoginDecisionFromNormalizedCode(normalized: string | undefined): WorkflowFailureDecision | undefined {
|
|
656
|
+
return normalized !== undefined && LOGIN_REQUIRED_CODES.has(normalized)
|
|
657
|
+
? authDecision("login_required")
|
|
658
|
+
: undefined;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
function classificationForDecision(
|
|
662
|
+
decision: WorkflowFailureDecision,
|
|
663
|
+
source: WorkflowFailureClassificationSource,
|
|
664
|
+
message: string | undefined,
|
|
665
|
+
evidence: WorkflowFailureEvidence = "message",
|
|
666
|
+
): WorkflowFailureClassification {
|
|
667
|
+
return {
|
|
668
|
+
decision,
|
|
669
|
+
source,
|
|
670
|
+
evidence,
|
|
671
|
+
...(message !== undefined ? { message } : {}),
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
function hasInvalidApiKeyMessage(tokens: readonly string[]): boolean {
|
|
676
|
+
return hasAnyPhrase(tokens, INVALID_API_KEY_PHRASES)
|
|
677
|
+
|| (hasPhrase(tokens, ["api", "key"]) && tokenNearAny(tokens, "key", INVALID_API_KEY_CONTEXT, 6));
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
function decisionFromMessageTokens(tokens: readonly string[], name: string | undefined, retryAfterMs?: number): WorkflowFailureDecision | undefined {
|
|
681
|
+
if (name?.toLowerCase() === "aborterror" || hasAnyPhrase(tokens, CANCELLED_PHRASES)) return cancelledDecision();
|
|
682
|
+
if (hasAnyPhrase(tokens, HTTP_RATE_LIMIT_PHRASES)) return rateLimitDecision("rate_limited", retryAfterMs);
|
|
683
|
+
if (hasAnyPhrase(tokens, QUOTA_LIMIT_PHRASES)) return rateLimitDecision("quota_limited", retryAfterMs);
|
|
684
|
+
if (hasAnyPhrase(tokens, RATE_LIMIT_PHRASES)) return rateLimitDecision("rate_limited", retryAfterMs);
|
|
685
|
+
if (hasInvalidApiKeyMessage(tokens)) return authDecision("invalid_api_key");
|
|
686
|
+
if (hasAnyPhrase(tokens, MISSING_API_KEY_PHRASES)) return authDecision("missing_api_key");
|
|
687
|
+
if (hasAnyPhrase(tokens, LOGIN_REQUIRED_PHRASES) || tokenNearAny(tokens, "oauth", AUTH_CONTEXT, 8)) return authDecision("login_required");
|
|
688
|
+
if (hasAnyPhrase(tokens, UNKNOWN_MODEL_PHRASES)) return terminalProviderConfigDecision("unknown_model");
|
|
689
|
+
if (hasAnyPhrase(tokens, FORBIDDEN_CONFIG_PHRASES)) return terminalProviderConfigDecision("forbidden_config");
|
|
358
690
|
if (
|
|
359
|
-
hasAnyPhrase(tokens,
|
|
691
|
+
hasAnyPhrase(tokens, PROVIDER_UNAVAILABLE_PHRASES)
|
|
360
692
|
|| tokenNearAny(tokens, "model", MODEL_PROVIDER_CONTEXT, 8)
|
|
361
693
|
|| tokenNearAny(tokens, "provider", PROVIDER_CONTEXT, 8)
|
|
362
|
-
) return
|
|
694
|
+
) return providerUnavailableDecision(retryAfterMs);
|
|
363
695
|
return undefined;
|
|
364
696
|
}
|
|
365
697
|
|
|
698
|
+
function decisionFromStatus(status: number | undefined, retryAfterMs: number | undefined): WorkflowFailureDecision | undefined {
|
|
699
|
+
switch (status) {
|
|
700
|
+
case 401:
|
|
701
|
+
return authDecision("invalid_api_key");
|
|
702
|
+
case 403:
|
|
703
|
+
return terminalProviderConfigDecision("forbidden_config");
|
|
704
|
+
case 429:
|
|
705
|
+
return rateLimitDecision("rate_limited", retryAfterMs);
|
|
706
|
+
case 500:
|
|
707
|
+
case 502:
|
|
708
|
+
case 503:
|
|
709
|
+
case 504:
|
|
710
|
+
return providerUnavailableDecision(retryAfterMs);
|
|
711
|
+
default:
|
|
712
|
+
return undefined;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
const STATUS_MESSAGE_REFINEMENT_CODES: ReadonlySet<WorkflowFailureCode> = new Set([
|
|
717
|
+
"invalid_api_key",
|
|
718
|
+
"missing_api_key",
|
|
719
|
+
"unknown_model",
|
|
720
|
+
"forbidden_config",
|
|
721
|
+
]);
|
|
722
|
+
|
|
723
|
+
const BROAD_AUTH_MESSAGE_REFINEMENT_CODES: ReadonlySet<WorkflowFailureCode> = new Set([
|
|
724
|
+
"invalid_api_key",
|
|
725
|
+
"missing_api_key",
|
|
726
|
+
]);
|
|
727
|
+
|
|
728
|
+
const STATUS_RELATED_MESSAGE_REFINEMENT_CODES: ReadonlySet<WorkflowFailureCode> = new Set([
|
|
729
|
+
"invalid_api_key",
|
|
730
|
+
"missing_api_key",
|
|
731
|
+
"unknown_model",
|
|
732
|
+
"forbidden_config",
|
|
733
|
+
"rate_limited",
|
|
734
|
+
"quota_limited",
|
|
735
|
+
"cancelled",
|
|
736
|
+
]);
|
|
737
|
+
|
|
738
|
+
function isRecoverableActiveBlocked(classification: WorkflowFailureClassification): boolean {
|
|
739
|
+
return classification.decision.disposition === "active_blocked"
|
|
740
|
+
&& classification.decision.recoverability === "recoverable";
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function canUseRelatedClassificationBeforeStatus(classification: WorkflowFailureClassification): boolean {
|
|
744
|
+
if (classification.evidence === "weak_signal") return false;
|
|
745
|
+
if (classification.evidence === "message") {
|
|
746
|
+
return STATUS_RELATED_MESSAGE_REFINEMENT_CODES.has(classification.decision.code);
|
|
747
|
+
}
|
|
748
|
+
return classification.decision.code !== "login_required";
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
function isClearLocalLoginMessage(message: string, tokens: readonly string[] = tokenize(message)): boolean {
|
|
752
|
+
if (message.toLowerCase().includes("/login")) return true;
|
|
753
|
+
return hasAnyPhrase(tokens, LOCAL_LOGIN_REQUIRED_PHRASES);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
function hasFallbackApiError401(tokens: readonly string[]): boolean {
|
|
757
|
+
return hasPhrase(tokens, ["401"]) && hasPhrase(tokens, ["api", "error"]);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
function classifyFallbackProviderAuthMessage(message: string, tokens: readonly string[]): WorkflowFailureDecision | undefined {
|
|
761
|
+
if (isClearLocalLoginMessage(message, tokens)) return undefined;
|
|
762
|
+
return hasAnyPhrase(tokens, PROVIDER_AUTH_FALLBACK_PHRASES) || hasFallbackApiError401(tokens)
|
|
763
|
+
? authDecision("invalid_api_key")
|
|
764
|
+
: undefined;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
function canUseLoginClassificationBeforeWrapper401(
|
|
768
|
+
classification: WorkflowFailureClassification | undefined,
|
|
769
|
+
): classification is WorkflowFailureClassification {
|
|
770
|
+
if (classification === undefined || classification.decision.code !== "login_required") return false;
|
|
771
|
+
return classification.evidence === "weak_signal"
|
|
772
|
+
|| classification.evidence === "strong_signal"
|
|
773
|
+
|| (classification.message !== undefined && isClearLocalLoginMessage(classification.message));
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
function classificationFromNormalizedCode(
|
|
777
|
+
normalized: string | undefined,
|
|
778
|
+
retryAfterMs: number | undefined,
|
|
779
|
+
source: WorkflowFailureClassificationSource,
|
|
780
|
+
message: string | undefined,
|
|
781
|
+
): { readonly strong?: WorkflowFailureClassification; readonly weak?: WorkflowFailureClassification } {
|
|
782
|
+
const strong = strongDecisionFromNormalizedCode(normalized, retryAfterMs);
|
|
783
|
+
if (strong !== undefined) {
|
|
784
|
+
return { strong: classificationForDecision(strong, source, message, "strong_signal") };
|
|
785
|
+
}
|
|
786
|
+
const weak = weakLoginDecisionFromNormalizedCode(normalized);
|
|
787
|
+
return weak !== undefined
|
|
788
|
+
? { weak: classificationForDecision(weak, source, message, "weak_signal") }
|
|
789
|
+
: {};
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
function aggregateErrorItems(error: unknown): readonly unknown[] {
|
|
793
|
+
const nativeErrors = error instanceof AggregateError ? error.errors as unknown : undefined;
|
|
794
|
+
const errors = nativeErrors ?? field(error, "errors");
|
|
795
|
+
return Array.isArray(errors) ? errors : [];
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
function fallbackAggregateClassification(innerError: unknown): WorkflowFailureClassification {
|
|
799
|
+
const message = errorMessage(innerError);
|
|
800
|
+
const fallback = fallbackDecisionFromMessage(message, errorName(innerError));
|
|
801
|
+
return classificationForDecision(fallback ?? unknownDecision(), "aggregate", message);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
function recoverableBlockedClassification(classifications: readonly WorkflowFailureClassification[]): WorkflowFailureClassification {
|
|
805
|
+
return classifications.find((classification) => classification.decision.retryAfterMs !== undefined)
|
|
806
|
+
?? classifications[0]!;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
function aggregateClassification(error: unknown, seen: Set<unknown>): WorkflowFailureClassification | undefined {
|
|
810
|
+
const innerErrors = aggregateErrorItems(error);
|
|
811
|
+
if (innerErrors.length === 0) return undefined;
|
|
812
|
+
|
|
813
|
+
const classifications = innerErrors.map((innerError) => {
|
|
814
|
+
const branchSeen = new Set(seen);
|
|
815
|
+
return structuredClassification(innerError, "aggregate", branchSeen) ?? fallbackAggregateClassification(innerError);
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
const terminalKilled = classifications.find(
|
|
819
|
+
(classification) => classification.decision.disposition === "terminal_killed",
|
|
820
|
+
);
|
|
821
|
+
if (terminalKilled !== undefined) return terminalKilled;
|
|
822
|
+
|
|
823
|
+
const allRecoverableBlocked = classifications.every(isRecoverableActiveBlocked);
|
|
824
|
+
if (allRecoverableBlocked) return recoverableBlockedClassification(classifications);
|
|
825
|
+
|
|
826
|
+
return classificationForDecision(unknownDecision(), "aggregate", errorMessage(error));
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
function selectDiagnosticFailureClassification(
|
|
830
|
+
diagnostics: readonly unknown[],
|
|
831
|
+
seen: ReadonlySet<unknown>,
|
|
832
|
+
): WorkflowFailureClassification | undefined {
|
|
833
|
+
const classifications: WorkflowFailureClassification[] = [];
|
|
834
|
+
for (const diagnosticError of diagnostics) {
|
|
835
|
+
const diagnosticSeen = new Set(seen);
|
|
836
|
+
const diagnosticClassification = structuredClassification(diagnosticError, "diagnostic", diagnosticSeen);
|
|
837
|
+
if (diagnosticClassification !== undefined) classifications.push(diagnosticClassification);
|
|
838
|
+
}
|
|
839
|
+
if (classifications.length === 0) return undefined;
|
|
840
|
+
|
|
841
|
+
const terminalKilled = classifications.find(
|
|
842
|
+
(classification) => classification.decision.disposition === "terminal_killed",
|
|
843
|
+
);
|
|
844
|
+
if (terminalKilled !== undefined) return terminalKilled;
|
|
845
|
+
|
|
846
|
+
const terminalFailed = classifications.find(
|
|
847
|
+
(classification) => classification.decision.disposition === "terminal_failed",
|
|
848
|
+
);
|
|
849
|
+
if (terminalFailed !== undefined) return terminalFailed;
|
|
850
|
+
|
|
851
|
+
const allRecoverableBlocked = classifications.every(isRecoverableActiveBlocked);
|
|
852
|
+
if (allRecoverableBlocked) return recoverableBlockedClassification(classifications);
|
|
853
|
+
|
|
854
|
+
return classifications[0]!;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
function relatedStructuredClassification(error: unknown, seen: Set<unknown>): WorkflowFailureClassification | undefined {
|
|
858
|
+
const diagnosticClassification = selectDiagnosticFailureClassification(diagnosticErrors(error), seen);
|
|
859
|
+
if (diagnosticClassification !== undefined) return diagnosticClassification;
|
|
860
|
+
|
|
861
|
+
const nested = nestedProviderError(error);
|
|
862
|
+
if (nested !== undefined && nested !== error) {
|
|
863
|
+
const nestedClassification = structuredClassification(nested, "nested", seen);
|
|
864
|
+
if (nestedClassification !== undefined) return nestedClassification;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
const causeClassification = structuredClassification(causeOf(error), "cause", seen);
|
|
868
|
+
if (causeClassification !== undefined) return causeClassification;
|
|
869
|
+
|
|
870
|
+
return aggregateClassification(error, seen);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
function structuredClassification(
|
|
874
|
+
error: unknown,
|
|
875
|
+
source: WorkflowFailureClassificationSource = "top_level",
|
|
876
|
+
seen = new Set<unknown>(),
|
|
877
|
+
): WorkflowFailureClassification | undefined {
|
|
878
|
+
if (error === undefined || error === null || seen.has(error)) return undefined;
|
|
879
|
+
if (typeof error === "object") seen.add(error);
|
|
880
|
+
|
|
881
|
+
const signal = structuredSignal(error);
|
|
882
|
+
const signalMessage = signal.message ?? (typeof error === "string" ? error : undefined);
|
|
883
|
+
if (signal.stopReason?.toLowerCase() === "aborted") {
|
|
884
|
+
return classificationForDecision(cancelledDecision(), source, signalMessage, "strong_signal");
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
const retryAfterMs = signal.retryAfterMs;
|
|
888
|
+
let weakClassification: WorkflowFailureClassification | undefined;
|
|
889
|
+
|
|
890
|
+
const codeEvidence = codeEvidenceFrom(signal.code);
|
|
891
|
+
if (codeEvidence?.kind === "semantic_code") {
|
|
892
|
+
const codeClassification = classificationFromNormalizedCode(codeEvidence.normalized, retryAfterMs, source, signalMessage);
|
|
893
|
+
if (codeClassification.strong !== undefined) return codeClassification.strong;
|
|
894
|
+
weakClassification = codeClassification.weak ?? weakClassification;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
const nameClassification = classificationFromNormalizedCode(normalizeCode(signal.name), retryAfterMs, source, signalMessage);
|
|
898
|
+
if (nameClassification.strong !== undefined) return nameClassification.strong;
|
|
899
|
+
weakClassification = nameClassification.weak ?? weakClassification;
|
|
900
|
+
|
|
901
|
+
const messageTokens = signalMessage !== undefined ? tokenize(signalMessage) : undefined;
|
|
902
|
+
const messageDecision = messageTokens !== undefined
|
|
903
|
+
? decisionFromMessageTokens(messageTokens, signal.name, retryAfterMs)
|
|
904
|
+
: undefined;
|
|
905
|
+
const providerAuthMessageDecision = signalMessage !== undefined && messageTokens !== undefined
|
|
906
|
+
? classifyFallbackProviderAuthMessage(signalMessage, messageTokens)
|
|
907
|
+
: undefined;
|
|
908
|
+
if (
|
|
909
|
+
weakClassification !== undefined &&
|
|
910
|
+
messageDecision !== undefined &&
|
|
911
|
+
BROAD_AUTH_MESSAGE_REFINEMENT_CODES.has(messageDecision.code)
|
|
912
|
+
) {
|
|
913
|
+
return classificationForDecision(messageDecision, source, signalMessage);
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
const relatedClassification = relatedStructuredClassification(error, seen);
|
|
917
|
+
const effectiveStatus = signal.status ?? (codeEvidence?.kind === "wrapper_http_status" ? codeEvidence.status : undefined);
|
|
918
|
+
const statusDecision = decisionFromStatus(effectiveStatus, retryAfterMs);
|
|
919
|
+
if (statusDecision !== undefined) {
|
|
920
|
+
if (relatedClassification !== undefined && canUseRelatedClassificationBeforeStatus(relatedClassification)) {
|
|
921
|
+
return relatedClassification;
|
|
922
|
+
}
|
|
923
|
+
if (
|
|
924
|
+
signalMessage !== undefined &&
|
|
925
|
+
(effectiveStatus === 401 || effectiveStatus === 403) &&
|
|
926
|
+
messageDecision !== undefined &&
|
|
927
|
+
STATUS_MESSAGE_REFINEMENT_CODES.has(messageDecision.code)
|
|
928
|
+
) {
|
|
929
|
+
return classificationForDecision(messageDecision, source, signalMessage);
|
|
930
|
+
}
|
|
931
|
+
if (effectiveStatus === 401) {
|
|
932
|
+
if (canUseLoginClassificationBeforeWrapper401(relatedClassification)) {
|
|
933
|
+
return relatedClassification;
|
|
934
|
+
}
|
|
935
|
+
if (canUseLoginClassificationBeforeWrapper401(weakClassification)) {
|
|
936
|
+
return weakClassification;
|
|
937
|
+
}
|
|
938
|
+
if (signalMessage !== undefined && isClearLocalLoginMessage(signalMessage)) {
|
|
939
|
+
return classificationForDecision(authDecision("login_required"), source, signalMessage);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
return classificationForDecision(statusDecision, source, signalMessage, "status");
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
if (source !== "top_level") {
|
|
946
|
+
if (
|
|
947
|
+
providerAuthMessageDecision !== undefined &&
|
|
948
|
+
(messageDecision === undefined || messageDecision.code === "login_required")
|
|
949
|
+
) {
|
|
950
|
+
return classificationForDecision(providerAuthMessageDecision, source, signalMessage);
|
|
951
|
+
}
|
|
952
|
+
if (messageDecision !== undefined) {
|
|
953
|
+
return classificationForDecision(messageDecision, source, signalMessage);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
if (relatedClassification !== undefined) return relatedClassification;
|
|
958
|
+
|
|
959
|
+
return weakClassification;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
function fallbackDecisionFromMessage(message: string, name: string | undefined): WorkflowFailureDecision | undefined {
|
|
963
|
+
const tokens = tokenize(message);
|
|
964
|
+
const decision = decisionFromMessageTokens(tokens, name);
|
|
965
|
+
const providerAuthDecision = classifyFallbackProviderAuthMessage(message, tokens);
|
|
966
|
+
if (providerAuthDecision !== undefined && (decision === undefined || decision.code === "login_required")) {
|
|
967
|
+
return providerAuthDecision;
|
|
968
|
+
}
|
|
969
|
+
if (decision === undefined && isClearLocalLoginMessage(message, tokens)) {
|
|
970
|
+
return authDecision("login_required");
|
|
971
|
+
}
|
|
972
|
+
return decision;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
function failureForDecision(decision: WorkflowFailureDecision, message: string, cause: unknown): WorkflowFailure {
|
|
976
|
+
const safeMessage = redactSensitiveText(message);
|
|
977
|
+
return makeWorkflowFailure(decision.kind, safeMessage, {
|
|
978
|
+
code: decision.code,
|
|
979
|
+
retryable: decision.retryable,
|
|
980
|
+
resumable: decision.resumable,
|
|
981
|
+
recoverability: decision.recoverability,
|
|
982
|
+
disposition: decision.disposition,
|
|
983
|
+
cause,
|
|
984
|
+
...(decision.userMessage !== undefined ? { userMessage: decision.userMessage } : {}),
|
|
985
|
+
...(decision.retryAfterMs !== undefined ? { retryAfterMs: decision.retryAfterMs } : {}),
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
|
|
366
989
|
export function classifyWorkflowFailure(error: unknown): WorkflowFailure {
|
|
367
990
|
const message = errorMessage(error);
|
|
368
|
-
const structured =
|
|
369
|
-
if (structured !== undefined)
|
|
991
|
+
const structured = structuredClassification(error);
|
|
992
|
+
if (structured !== undefined) {
|
|
993
|
+
const structuredMessage = structured.message ?? message;
|
|
994
|
+
return failureForDecision(structured.decision, structuredMessage, error);
|
|
995
|
+
}
|
|
370
996
|
|
|
371
|
-
const fallback =
|
|
372
|
-
if (fallback !== undefined) return
|
|
997
|
+
const fallback = fallbackDecisionFromMessage(message, errorName(error));
|
|
998
|
+
if (fallback !== undefined) return failureForDecision(fallback, message, error);
|
|
373
999
|
|
|
374
|
-
return
|
|
1000
|
+
return failureForDecision(unknownDecision(), message, error);
|
|
375
1001
|
}
|