@clawpump/claw-agent 0.1.5 → 0.1.7
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/agent/.dockerignore +67 -0
- package/agent/.envrc +1 -1
- package/agent/.gitattributes +8 -0
- package/agent/AGENTS.md +216 -4
- package/agent/CONTRIBUTING.md +46 -8
- package/agent/Dockerfile +78 -35
- package/agent/MANIFEST.in +2 -0
- package/agent/README.md +12 -5
- package/agent/README.ur-pk.md +261 -0
- package/agent/README.zh-CN.md +11 -8
- package/agent/SECURITY.md +5 -4
- package/agent/acp_adapter/provenance.py +127 -0
- package/agent/acp_adapter/server.py +112 -5
- package/agent/acp_adapter/session.py +1 -6
- package/agent/acp_registry/agent.json +2 -2
- package/agent/agent/account_usage.py +313 -1
- package/agent/agent/agent_init.py +140 -37
- package/agent/agent/agent_runtime_helpers.py +342 -83
- package/agent/agent/anthropic_adapter.py +320 -33
- package/agent/agent/auxiliary_client.py +525 -105
- package/agent/agent/background_review.py +157 -19
- package/agent/agent/bedrock_adapter.py +71 -6
- package/agent/agent/billing_view.py +295 -0
- package/agent/agent/chat_completion_helpers.py +229 -4
- package/agent/agent/codex_responses_adapter.py +86 -10
- package/agent/agent/codex_runtime.py +153 -1
- package/agent/agent/coding_context.py +738 -0
- package/agent/agent/context_compressor.py +392 -44
- package/agent/agent/context_references.py +34 -1
- package/agent/agent/conversation_compression.py +159 -22
- package/agent/agent/conversation_loop.py +643 -908
- package/agent/agent/copilot_acp_client.py +4 -11
- package/agent/agent/credential_pool.py +5 -3
- package/agent/agent/credits_tracker.py +794 -0
- package/agent/agent/curator.py +91 -18
- package/agent/agent/curator_backup.py +26 -10
- package/agent/agent/display.py +42 -1
- package/agent/agent/error_classifier.py +52 -3
- package/agent/agent/errors.py +3 -0
- package/agent/agent/file_safety.py +0 -17
- package/agent/agent/gemini_native_adapter.py +31 -1
- package/agent/agent/i18n.py +48 -4
- package/agent/agent/image_gen_provider.py +74 -5
- package/agent/agent/image_routing.py +29 -0
- package/agent/agent/insights.py +8 -17
- package/agent/agent/lsp/install.py +3 -0
- package/agent/agent/memory_manager.py +326 -31
- package/agent/agent/message_content.py +50 -0
- package/agent/agent/model_metadata.py +214 -3
- package/agent/agent/moonshot_schema.py +8 -1
- package/agent/agent/onboarding.py +60 -0
- package/agent/agent/prompt_builder.py +327 -37
- package/agent/agent/redact.py +1 -0
- package/agent/agent/runtime_cwd.py +34 -5
- package/agent/agent/secret_scope.py +205 -0
- package/agent/agent/secret_sources/bitwarden.py +34 -2
- package/agent/agent/skill_commands.py +90 -1
- package/agent/agent/skill_preprocessing.py +1 -0
- package/agent/agent/skill_utils.py +209 -36
- package/agent/agent/ssl_guard.py +94 -0
- package/agent/agent/system_prompt.py +133 -5
- package/agent/agent/tool_executor.py +496 -70
- package/agent/agent/transports/anthropic.py +83 -21
- package/agent/agent/transports/chat_completions.py +94 -5
- package/agent/agent/transports/codex.py +67 -2
- package/agent/agent/transports/codex_app_server.py +1 -0
- package/agent/agent/transports/codex_app_server_session.py +30 -0
- package/agent/agent/transports/types.py +12 -0
- package/agent/agent/turn_context.py +408 -0
- package/agent/agent/turn_finalizer.py +428 -0
- package/agent/agent/turn_retry_state.py +68 -0
- package/agent/agent/usage_pricing.py +3 -0
- package/agent/apps/bootstrap-installer/package.json +6 -5
- package/agent/apps/bootstrap-installer/src/routes/failure.tsx +12 -5
- package/agent/apps/bootstrap-installer/src/routes/progress.tsx +1 -3
- package/agent/apps/bootstrap-installer/src/store.ts +3 -2
- package/agent/apps/bootstrap-installer/src-tauri/src/bootstrap.rs +172 -7
- package/agent/apps/bootstrap-installer/src-tauri/src/events.rs +14 -1
- package/agent/apps/bootstrap-installer/src-tauri/src/paths.rs +29 -0
- package/agent/apps/bootstrap-installer/src-tauri/src/powershell.rs +93 -3
- package/agent/apps/bootstrap-installer/src-tauri/src/update.rs +695 -39
- package/agent/apps/bootstrap-installer/tsconfig.json +3 -4
- package/agent/apps/desktop/DESIGN.md +167 -0
- package/agent/apps/desktop/README.md +20 -16
- package/agent/apps/desktop/assets/icon.icns +0 -0
- package/agent/apps/desktop/assets/icon.ico +0 -0
- package/agent/apps/desktop/assets/icon.png +0 -0
- package/agent/apps/desktop/electron/backend-env.cjs +112 -0
- package/agent/apps/desktop/electron/backend-env.test.cjs +111 -0
- package/agent/apps/desktop/electron/backend-probes.test.cjs +3 -1
- package/agent/apps/desktop/electron/backend-ready.cjs +66 -0
- package/agent/apps/desktop/electron/bootstrap-platform.cjs +52 -0
- package/agent/apps/desktop/electron/bootstrap-platform.test.cjs +59 -1
- package/agent/apps/desktop/electron/bootstrap-runner.cjs +176 -38
- package/agent/apps/desktop/electron/bootstrap-runner.test.cjs +112 -1
- package/agent/apps/desktop/electron/connection-config.cjs +288 -0
- package/agent/apps/desktop/electron/connection-config.test.cjs +396 -0
- package/agent/apps/desktop/electron/dashboard-token.cjs +99 -0
- package/agent/apps/desktop/electron/dashboard-token.test.cjs +142 -0
- package/agent/apps/desktop/electron/desktop-uninstall.cjs +232 -0
- package/agent/apps/desktop/electron/desktop-uninstall.test.cjs +246 -0
- package/agent/apps/desktop/electron/entitlements.mac.inherit.plist +2 -0
- package/agent/apps/desktop/electron/fs-read-dir.cjs +109 -0
- package/agent/apps/desktop/electron/fs-read-dir.test.cjs +364 -0
- package/agent/apps/desktop/electron/gateway-ws-probe.cjs +188 -0
- package/agent/apps/desktop/electron/gateway-ws-probe.test.cjs +122 -0
- package/agent/apps/desktop/electron/git-root.cjs +54 -0
- package/agent/apps/desktop/electron/git-root.test.cjs +40 -0
- package/agent/apps/desktop/electron/git-worktrees.cjs +174 -0
- package/agent/apps/desktop/electron/hardening.cjs +123 -28
- package/agent/apps/desktop/electron/hardening.test.cjs +163 -0
- package/agent/apps/desktop/electron/main.cjs +3121 -331
- package/agent/apps/desktop/electron/oauth-net-request.cjs +20 -0
- package/agent/apps/desktop/electron/oauth-net-request.test.cjs +34 -0
- package/agent/apps/desktop/electron/preload.cjs +52 -2
- package/agent/apps/desktop/electron/session-windows.cjs +124 -0
- package/agent/apps/desktop/electron/session-windows.test.cjs +199 -0
- package/agent/apps/desktop/electron/update-rebuild.cjs +29 -0
- package/agent/apps/desktop/electron/update-rebuild.test.cjs +55 -0
- package/agent/apps/desktop/electron/update-remote.cjs +56 -0
- package/agent/apps/desktop/electron/update-remote.test.cjs +78 -0
- package/agent/apps/desktop/electron/vscode-marketplace.cjs +331 -0
- package/agent/apps/desktop/electron/vscode-marketplace.test.cjs +113 -0
- package/agent/apps/desktop/electron/windows-child-process.test.cjs +57 -0
- package/agent/apps/desktop/electron/windows-user-env.cjs +76 -0
- package/agent/apps/desktop/electron/windows-user-env.test.cjs +90 -0
- package/agent/apps/desktop/electron/workspace-cwd.cjs +38 -0
- package/agent/apps/desktop/electron/workspace-cwd.test.cjs +45 -0
- package/agent/apps/desktop/eslint.config.mjs +0 -3
- package/agent/apps/desktop/index.html +27 -2
- package/agent/apps/desktop/package.json +31 -11
- package/agent/apps/desktop/pr-assets/session-source-folders.png +0 -0
- package/agent/apps/desktop/public/apple-touch-icon.png +0 -0
- package/agent/apps/desktop/public/nous-girl.jpg +0 -0
- package/agent/apps/desktop/scripts/assert-dist-built.cjs +70 -0
- package/agent/apps/desktop/scripts/assert-dist-built.test.cjs +84 -0
- package/agent/apps/desktop/scripts/before-pack.cjs +78 -0
- package/agent/apps/desktop/scripts/before-pack.test.cjs +53 -0
- package/agent/apps/desktop/scripts/diag-scroll-reset.mjs +229 -0
- package/agent/apps/desktop/scripts/patch-electron-builder-mac-binary.cjs +64 -0
- package/agent/apps/desktop/scripts/run-electron-builder.cjs +57 -0
- package/agent/apps/desktop/src/app/agents/index.tsx +53 -45
- package/agent/apps/desktop/src/app/artifacts/index.tsx +102 -83
- package/agent/apps/desktop/src/app/chat/chat-drop-overlay.tsx +29 -8
- package/agent/apps/desktop/src/app/chat/chat-swap-overlay.tsx +47 -0
- package/agent/apps/desktop/src/app/chat/composer/attachments.tsx +81 -45
- package/agent/apps/desktop/src/app/chat/composer/completion-drawer.tsx +13 -24
- package/agent/apps/desktop/src/app/chat/composer/context-menu.tsx +138 -88
- package/agent/apps/desktop/src/app/chat/composer/controls.tsx +138 -90
- package/agent/apps/desktop/src/app/chat/composer/enter-submit-dom-race.test.tsx +218 -0
- package/agent/apps/desktop/src/app/chat/composer/focus.ts +32 -0
- package/agent/apps/desktop/src/app/chat/composer/help-hint.tsx +38 -25
- package/agent/apps/desktop/src/app/chat/composer/hooks/use-live-completion-adapter.ts +7 -0
- package/agent/apps/desktop/src/app/chat/composer/hooks/use-mic-recorder.ts +22 -12
- package/agent/apps/desktop/src/app/chat/composer/hooks/use-slash-completions.ts +142 -14
- package/agent/apps/desktop/src/app/chat/composer/hooks/use-voice-conversation.ts +14 -11
- package/agent/apps/desktop/src/app/chat/composer/hooks/use-voice-recorder.ts +9 -6
- package/agent/apps/desktop/src/app/chat/composer/ime-composition-dom-repro.test.tsx +108 -0
- package/agent/apps/desktop/src/app/chat/composer/index.tsx +930 -180
- package/agent/apps/desktop/src/app/chat/composer/inline-refs.ts +136 -32
- package/agent/apps/desktop/src/app/chat/composer/model-pill.tsx +86 -0
- package/agent/apps/desktop/src/app/chat/composer/queue-panel.tsx +54 -75
- package/agent/apps/desktop/src/app/chat/composer/rich-editor.test.ts +117 -1
- package/agent/apps/desktop/src/app/chat/composer/rich-editor.ts +117 -6
- package/agent/apps/desktop/src/app/chat/composer/slash-nav-dom-repro.test.tsx +186 -0
- package/agent/apps/desktop/src/app/chat/composer/status-stack/index.tsx +202 -0
- package/agent/apps/desktop/src/app/chat/composer/status-stack/status-row.tsx +155 -0
- package/agent/apps/desktop/src/app/chat/composer/text-utils.test.ts +104 -0
- package/agent/apps/desktop/src/app/chat/composer/text-utils.ts +37 -9
- package/agent/apps/desktop/src/app/chat/composer/trigger-popover.test.tsx +50 -0
- package/agent/apps/desktop/src/app/chat/composer/trigger-popover.tsx +105 -40
- package/agent/apps/desktop/src/app/chat/composer/types.ts +5 -0
- package/agent/apps/desktop/src/app/chat/composer/url-dialog.tsx +11 -15
- package/agent/apps/desktop/src/app/chat/composer/voice-activity.tsx +8 -4
- package/agent/apps/desktop/src/app/chat/hooks/use-composer-actions.test.ts +57 -0
- package/agent/apps/desktop/src/app/chat/hooks/use-composer-actions.ts +70 -16
- package/agent/apps/desktop/src/app/chat/hooks/use-file-drop-zone.ts +52 -16
- package/agent/apps/desktop/src/app/chat/index.tsx +234 -81
- package/agent/apps/desktop/src/app/chat/perf-probe.tsx +69 -21
- package/agent/apps/desktop/src/app/chat/right-rail/preview-console.tsx +44 -40
- package/agent/apps/desktop/src/app/chat/right-rail/preview-file.tsx +71 -25
- package/agent/apps/desktop/src/app/chat/right-rail/preview-pane.test.tsx +40 -1
- package/agent/apps/desktop/src/app/chat/right-rail/preview-pane.tsx +55 -53
- package/agent/apps/desktop/src/app/chat/right-rail/preview.tsx +35 -17
- package/agent/apps/desktop/src/app/chat/scroll-to-bottom-button.test.tsx +67 -0
- package/agent/apps/desktop/src/app/chat/scroll-to-bottom-button.tsx +74 -0
- package/agent/apps/desktop/src/app/chat/sidebar/cron-jobs-section.tsx +356 -0
- package/agent/apps/desktop/src/app/chat/sidebar/index.tsx +1189 -364
- package/agent/apps/desktop/src/app/chat/sidebar/load-more-row.tsx +30 -0
- package/agent/apps/desktop/src/app/chat/sidebar/order.test.ts +21 -0
- package/agent/apps/desktop/src/app/chat/sidebar/order.ts +17 -0
- package/agent/apps/desktop/src/app/chat/sidebar/profile-switcher.tsx +524 -0
- package/agent/apps/desktop/src/app/chat/sidebar/session-actions-menu.tsx +80 -45
- package/agent/apps/desktop/src/app/chat/sidebar/session-row.tsx +120 -25
- package/agent/apps/desktop/src/app/chat/sidebar/virtual-session-list.tsx +7 -13
- package/agent/apps/desktop/src/app/chat/sidebar/workspace-groups.test.ts +149 -0
- package/agent/apps/desktop/src/app/chat/sidebar/workspace-groups.ts +326 -0
- package/agent/apps/desktop/src/app/chat/thread-loading.ts +7 -2
- package/agent/apps/desktop/src/app/command-center/index.tsx +320 -581
- package/agent/apps/desktop/src/app/command-palette/index.tsx +681 -0
- package/agent/apps/desktop/src/app/command-palette/marketplace-theme-page.tsx +157 -0
- package/agent/apps/desktop/src/app/cron/index.tsx +392 -324
- package/agent/apps/desktop/src/app/cron/job-state.ts +29 -0
- package/agent/apps/desktop/src/app/desktop-controller.tsx +618 -123
- package/agent/apps/desktop/src/app/floating-hud.ts +22 -0
- package/agent/apps/desktop/src/app/gateway/hooks/use-gateway-boot.test.tsx +265 -0
- package/agent/apps/desktop/src/app/gateway/hooks/use-gateway-boot.ts +260 -14
- package/agent/apps/desktop/src/app/gateway/hooks/use-gateway-request.ts +48 -4
- package/agent/apps/desktop/src/app/hooks/use-keybinds.ts +270 -0
- package/agent/apps/desktop/src/app/hooks/use-refresh-hotkey.ts +45 -0
- package/agent/apps/desktop/src/app/layout-constants.ts +19 -0
- package/agent/apps/desktop/src/app/messaging/index.tsx +136 -241
- package/agent/apps/desktop/src/app/messaging/platform-icon.tsx +95 -0
- package/agent/apps/desktop/src/app/model-visibility-overlay.tsx +31 -0
- package/agent/apps/desktop/src/app/overlays/overlay-search-input.tsx +18 -62
- package/agent/apps/desktop/src/app/overlays/overlay-split-layout.tsx +59 -7
- package/agent/apps/desktop/src/app/overlays/overlay-view.tsx +9 -5
- package/agent/apps/desktop/src/app/page-search-shell.tsx +42 -20
- package/agent/apps/desktop/src/app/profiles/create-profile-dialog.tsx +165 -0
- package/agent/apps/desktop/src/app/profiles/delete-profile-dialog.tsx +65 -0
- package/agent/apps/desktop/src/app/profiles/index.tsx +174 -199
- package/agent/apps/desktop/src/app/profiles/rename-profile-dialog.tsx +125 -0
- package/agent/apps/desktop/src/app/right-sidebar/files/dnd-manager.ts +27 -0
- package/agent/apps/desktop/src/app/right-sidebar/files/ipc.test.ts +100 -0
- package/agent/apps/desktop/src/app/right-sidebar/files/ipc.ts +12 -18
- package/agent/apps/desktop/src/app/right-sidebar/files/remote-picker.tsx +177 -0
- package/agent/apps/desktop/src/app/right-sidebar/files/tree.tsx +35 -21
- package/agent/apps/desktop/src/app/right-sidebar/files/use-project-tree.test.ts +75 -3
- package/agent/apps/desktop/src/app/right-sidebar/files/use-project-tree.ts +152 -5
- package/agent/apps/desktop/src/app/right-sidebar/index.test.tsx +75 -0
- package/agent/apps/desktop/src/app/right-sidebar/index.tsx +166 -129
- package/agent/apps/desktop/src/app/right-sidebar/store.ts +19 -4
- package/agent/apps/desktop/src/app/right-sidebar/terminal/buffer.ts +65 -0
- package/agent/apps/desktop/src/app/right-sidebar/terminal/index.tsx +29 -34
- package/agent/apps/desktop/src/app/right-sidebar/terminal/persistent.tsx +18 -6
- package/agent/apps/desktop/src/app/right-sidebar/terminal/selection.ts +93 -32
- package/agent/apps/desktop/src/app/right-sidebar/terminal/use-terminal-session.ts +381 -119
- package/agent/apps/desktop/src/app/routes.ts +9 -0
- package/agent/apps/desktop/src/app/session/hooks/use-cwd-actions.ts +17 -7
- package/agent/apps/desktop/src/app/session/hooks/use-message-stream.ts +365 -47
- package/agent/apps/desktop/src/app/session/hooks/use-model-controls.test.tsx +198 -0
- package/agent/apps/desktop/src/app/session/hooks/use-model-controls.ts +70 -34
- package/agent/apps/desktop/src/app/session/hooks/use-prompt-actions.test.tsx +1061 -0
- package/agent/apps/desktop/src/app/session/hooks/use-prompt-actions.ts +1143 -165
- package/agent/apps/desktop/src/app/session/hooks/use-route-resume.test.tsx +341 -2
- package/agent/apps/desktop/src/app/session/hooks/use-route-resume.ts +176 -5
- package/agent/apps/desktop/src/app/session/hooks/use-session-actions.test.tsx +259 -0
- package/agent/apps/desktop/src/app/session/hooks/use-session-actions.ts +452 -149
- package/agent/apps/desktop/src/app/session/hooks/use-session-state-cache.test.tsx +327 -0
- package/agent/apps/desktop/src/app/session/hooks/use-session-state-cache.ts +133 -4
- package/agent/apps/desktop/src/app/session-picker-overlay.tsx +32 -0
- package/agent/apps/desktop/src/app/session-switcher.tsx +107 -0
- package/agent/apps/desktop/src/app/settings/about-settings.tsx +45 -36
- package/agent/apps/desktop/src/app/settings/appearance-settings.tsx +243 -162
- package/agent/apps/desktop/src/app/settings/config-settings.tsx +86 -66
- package/agent/apps/desktop/src/app/settings/constants.ts +459 -122
- package/agent/apps/desktop/src/app/settings/credential-key-ui.tsx +373 -0
- package/agent/apps/desktop/src/app/settings/env-credentials.tsx +198 -0
- package/agent/apps/desktop/src/app/settings/env-var-actions-menu.tsx +136 -0
- package/agent/apps/desktop/src/app/settings/field-copy.ts +56 -0
- package/agent/apps/desktop/src/app/settings/gateway-settings.tsx +385 -72
- package/agent/apps/desktop/src/app/settings/helpers.test.ts +156 -1
- package/agent/apps/desktop/src/app/settings/helpers.ts +30 -2
- package/agent/apps/desktop/src/app/settings/index.tsx +118 -84
- package/agent/apps/desktop/src/app/settings/keys-settings.tsx +62 -419
- package/agent/apps/desktop/src/app/settings/mcp-settings.tsx +65 -60
- package/agent/apps/desktop/src/app/settings/model-settings.test.tsx +129 -5
- package/agent/apps/desktop/src/app/settings/model-settings.tsx +370 -65
- package/agent/apps/desktop/src/app/settings/notifications-settings.tsx +150 -0
- package/agent/apps/desktop/src/app/settings/primitives.tsx +5 -11
- package/agent/apps/desktop/src/app/settings/provider-config-panel.test.tsx +142 -0
- package/agent/apps/desktop/src/app/settings/provider-config-panel.tsx +182 -0
- package/agent/apps/desktop/src/app/settings/providers-settings.test.tsx +171 -0
- package/agent/apps/desktop/src/app/settings/providers-settings.tsx +471 -0
- package/agent/apps/desktop/src/app/settings/sessions-settings.tsx +183 -71
- package/agent/apps/desktop/src/app/settings/toolset-config-panel.test.tsx +135 -1
- package/agent/apps/desktop/src/app/settings/toolset-config-panel.tsx +180 -57
- package/agent/apps/desktop/src/app/settings/types.ts +9 -6
- package/agent/apps/desktop/src/app/settings/uninstall-section.tsx +185 -0
- package/agent/apps/desktop/src/app/settings/use-deep-link-highlight.ts +60 -0
- package/agent/apps/desktop/src/app/shell/app-shell.tsx +59 -13
- package/agent/apps/desktop/src/app/shell/gateway-menu-panel.tsx +37 -32
- package/agent/apps/desktop/src/app/shell/hooks/use-overlay-routing.ts +6 -3
- package/agent/apps/desktop/src/app/shell/hooks/use-statusbar-items.tsx +212 -53
- package/agent/apps/desktop/src/app/shell/keybind-panel.tsx +215 -0
- package/agent/apps/desktop/src/app/shell/model-edit-submenu.test.tsx +84 -0
- package/agent/apps/desktop/src/app/shell/model-edit-submenu.tsx +244 -0
- package/agent/apps/desktop/src/app/shell/model-menu-panel.tsx +392 -0
- package/agent/apps/desktop/src/app/shell/statusbar-controls.tsx +23 -33
- package/agent/apps/desktop/src/app/shell/titlebar-controls.tsx +79 -95
- package/agent/apps/desktop/src/app/shell/titlebar.ts +8 -2
- package/agent/apps/desktop/src/app/skills/index.test.tsx +11 -0
- package/agent/apps/desktop/src/app/skills/index.tsx +79 -64
- package/agent/apps/desktop/src/app/types.ts +85 -0
- package/agent/apps/desktop/src/app/updates-overlay.tsx +110 -105
- package/agent/apps/desktop/src/components/assistant-ui/ansi-text.tsx +34 -0
- package/agent/apps/desktop/src/components/assistant-ui/block-direction.test.tsx +129 -0
- package/agent/apps/desktop/src/components/assistant-ui/clarify-tool.tsx +102 -81
- package/agent/apps/desktop/src/components/assistant-ui/directive-text.tsx +92 -15
- package/agent/apps/desktop/src/components/assistant-ui/markdown-text.test.ts +38 -0
- package/agent/apps/desktop/src/components/assistant-ui/markdown-text.tsx +304 -45
- package/agent/apps/desktop/src/components/assistant-ui/message-render-boundary.test.tsx +80 -0
- package/agent/apps/desktop/src/components/assistant-ui/message-render-boundary.tsx +48 -0
- package/agent/apps/desktop/src/components/assistant-ui/streaming.test.tsx +142 -90
- package/agent/apps/desktop/src/components/assistant-ui/thread-list.tsx +337 -0
- package/agent/apps/desktop/src/components/assistant-ui/thread.tsx +667 -190
- package/agent/apps/desktop/src/components/assistant-ui/tool-approval-group.test.tsx +299 -0
- package/agent/apps/desktop/src/components/assistant-ui/tool-approval.test.tsx +133 -0
- package/agent/apps/desktop/src/components/assistant-ui/tool-approval.tsx +239 -0
- package/agent/apps/desktop/src/components/assistant-ui/tool-fallback-model.test.ts +31 -0
- package/agent/apps/desktop/src/components/assistant-ui/tool-fallback-model.ts +152 -134
- package/agent/apps/desktop/src/components/assistant-ui/tool-fallback.tsx +142 -150
- package/agent/apps/desktop/src/components/assistant-ui/tooltip-icon-button.tsx +14 -12
- package/agent/apps/desktop/src/components/assistant-ui/user-message-edit.test.tsx +141 -0
- package/agent/apps/desktop/src/components/assistant-ui/user-message-text.tsx +152 -0
- package/agent/apps/desktop/src/components/boot-failure-overlay.tsx +150 -33
- package/agent/apps/desktop/src/components/boot-failure-reauth.test.ts +100 -0
- package/agent/apps/desktop/src/components/boot-failure-reauth.ts +81 -0
- package/agent/apps/desktop/src/components/brand-mark.tsx +19 -0
- package/agent/apps/desktop/src/components/chat/code-card.tsx +1 -1
- package/agent/apps/desktop/src/components/chat/composer-dock.ts +31 -0
- package/agent/apps/desktop/src/components/chat/diff-lines.tsx +1 -1
- package/agent/apps/desktop/src/components/chat/disclosure-row.tsx +13 -3
- package/agent/apps/desktop/src/components/chat/expandable-block.tsx +52 -0
- package/agent/apps/desktop/src/components/chat/generated-image-result.tsx +174 -0
- package/agent/apps/desktop/src/components/chat/image-generation-placeholder.tsx +70 -37
- package/agent/apps/desktop/src/components/chat/intro.tsx +8 -7
- package/agent/apps/desktop/src/components/chat/preview-attachment.tsx +4 -2
- package/agent/apps/desktop/src/components/chat/shiki-highlighter.test.ts +37 -0
- package/agent/apps/desktop/src/components/chat/shiki-highlighter.tsx +96 -22
- package/agent/apps/desktop/src/components/chat/status-row.tsx +70 -0
- package/agent/apps/desktop/src/components/chat/status-section.tsx +42 -0
- package/agent/apps/desktop/src/components/chat/terminal-output.tsx +54 -0
- package/agent/apps/desktop/src/components/chat/zoomable-image.tsx +70 -109
- package/agent/apps/desktop/src/components/desktop-install-overlay.tsx +154 -84
- package/agent/apps/desktop/src/components/desktop-onboarding-overlay.test.tsx +38 -8
- package/agent/apps/desktop/src/components/desktop-onboarding-overlay.tsx +789 -233
- package/agent/apps/desktop/src/components/error-boundary.tsx +77 -0
- package/agent/apps/desktop/src/components/gateway-connecting-overlay.test.tsx +144 -0
- package/agent/apps/desktop/src/components/gateway-connecting-overlay.tsx +7 -1
- package/agent/apps/desktop/src/components/haptics-provider.tsx +24 -0
- package/agent/apps/desktop/src/components/language-switcher.test.tsx +53 -0
- package/agent/apps/desktop/src/components/language-switcher.tsx +175 -0
- package/agent/apps/desktop/src/components/model-picker.tsx +42 -40
- package/agent/apps/desktop/src/components/model-visibility-dialog.tsx +166 -0
- package/agent/apps/desktop/src/components/notifications.tsx +48 -27
- package/agent/apps/desktop/src/components/pane-shell/index.ts +1 -1
- package/agent/apps/desktop/src/components/pane-shell/pane-shell.tsx +146 -9
- package/agent/apps/desktop/src/components/prompt-overlays.tsx +234 -0
- package/agent/apps/desktop/src/components/session-picker.tsx +108 -0
- package/agent/apps/desktop/src/components/ui/action-status.tsx +25 -0
- package/agent/apps/desktop/src/components/ui/badge.tsx +35 -0
- package/agent/apps/desktop/src/components/ui/button.tsx +37 -13
- package/agent/apps/desktop/src/components/ui/confirm-dialog.tsx +109 -0
- package/agent/apps/desktop/src/components/ui/control.ts +25 -0
- package/agent/apps/desktop/src/components/ui/copy-button.test.tsx +36 -0
- package/agent/apps/desktop/src/components/ui/copy-button.tsx +38 -27
- package/agent/apps/desktop/src/components/ui/dialog.tsx +39 -11
- package/agent/apps/desktop/src/components/ui/dropdown-menu.tsx +98 -24
- package/agent/apps/desktop/src/components/ui/error-state.tsx +50 -0
- package/agent/apps/desktop/src/components/ui/fade-text.tsx +9 -2
- package/agent/apps/desktop/src/components/ui/{braille-spinner.tsx → glyph-spinner.tsx} +15 -13
- package/agent/apps/desktop/src/components/ui/input.tsx +5 -2
- package/agent/apps/desktop/src/components/ui/kbd.tsx +83 -12
- package/agent/apps/desktop/src/components/ui/log-view.tsx +19 -0
- package/agent/apps/desktop/src/components/ui/pagination.tsx +12 -5
- package/agent/apps/desktop/src/components/ui/popover.tsx +44 -0
- package/agent/apps/desktop/src/components/ui/search-field.tsx +80 -0
- package/agent/apps/desktop/src/components/ui/segmented-control.tsx +51 -0
- package/agent/apps/desktop/src/components/ui/select.tsx +10 -3
- package/agent/apps/desktop/src/components/ui/sheet.tsx +8 -2
- package/agent/apps/desktop/src/components/ui/sidebar.tsx +18 -25
- package/agent/apps/desktop/src/components/ui/switch.tsx +38 -15
- package/agent/apps/desktop/src/components/ui/textarea.tsx +4 -11
- package/agent/apps/desktop/src/components/ui/tool-icon.tsx +65 -0
- package/agent/apps/desktop/src/components/ui/tooltip.tsx +31 -4
- package/agent/apps/desktop/src/fonts/JetBrainsMono-Bold.woff2 +0 -0
- package/agent/apps/desktop/src/fonts/JetBrainsMono-Italic.woff2 +0 -0
- package/agent/apps/desktop/src/fonts/JetBrainsMono-Regular.woff2 +0 -0
- package/agent/apps/desktop/src/global.d.ts +181 -4
- package/agent/apps/desktop/src/hermes.test.ts +60 -0
- package/agent/apps/desktop/src/hermes.ts +190 -13
- package/agent/apps/desktop/src/hooks/use-image-download.ts +85 -0
- package/agent/apps/desktop/src/hooks/use-resize-observer.ts +13 -4
- package/agent/apps/desktop/src/hooks/use-worktree-info.ts +68 -0
- package/agent/apps/desktop/src/i18n/catalog.ts +12 -0
- package/agent/apps/desktop/src/i18n/context.test.tsx +232 -0
- package/agent/apps/desktop/src/i18n/context.tsx +183 -0
- package/agent/apps/desktop/src/i18n/define-locale.ts +41 -0
- package/agent/apps/desktop/src/i18n/en.ts +1921 -0
- package/agent/apps/desktop/src/i18n/index.ts +20 -0
- package/agent/apps/desktop/src/i18n/ja.ts +2053 -0
- package/agent/apps/desktop/src/i18n/languages.test.ts +43 -0
- package/agent/apps/desktop/src/i18n/languages.ts +86 -0
- package/agent/apps/desktop/src/i18n/runtime.test.ts +75 -0
- package/agent/apps/desktop/src/i18n/runtime.ts +53 -0
- package/agent/apps/desktop/src/i18n/types.ts +1559 -0
- package/agent/apps/desktop/src/i18n/zh-hant.ts +1992 -0
- package/agent/apps/desktop/src/i18n/zh.ts +2099 -0
- package/agent/apps/desktop/src/lib/ansi.test.ts +123 -0
- package/agent/apps/desktop/src/lib/ansi.ts +186 -0
- package/agent/apps/desktop/src/lib/chat-messages.test.ts +79 -0
- package/agent/apps/desktop/src/lib/chat-messages.ts +68 -29
- package/agent/apps/desktop/src/lib/chat-runtime.test.ts +65 -1
- package/agent/apps/desktop/src/lib/chat-runtime.ts +39 -3
- package/agent/apps/desktop/src/lib/completion-sound.ts +519 -0
- package/agent/apps/desktop/src/lib/desktop-fs.test.ts +116 -0
- package/agent/apps/desktop/src/lib/desktop-fs.ts +113 -0
- package/agent/apps/desktop/src/lib/desktop-slash-commands.test.ts +89 -6
- package/agent/apps/desktop/src/lib/desktop-slash-commands.ts +270 -131
- package/agent/apps/desktop/src/lib/external-link.test.tsx +27 -0
- package/agent/apps/desktop/src/lib/external-link.tsx +9 -2
- package/agent/apps/desktop/src/lib/gateway-events.test.ts +27 -0
- package/agent/apps/desktop/src/lib/gateway-events.ts +16 -0
- package/agent/apps/desktop/src/lib/gateway-ws-url.test.ts +78 -0
- package/agent/apps/desktop/src/lib/gateway-ws-url.ts +91 -0
- package/agent/apps/desktop/src/lib/generated-images.test.ts +97 -0
- package/agent/apps/desktop/src/lib/generated-images.ts +116 -0
- package/agent/apps/desktop/src/lib/haptics.ts +17 -0
- package/agent/apps/desktop/src/lib/icons.ts +10 -2
- package/agent/apps/desktop/src/lib/keybinds/actions.ts +137 -0
- package/agent/apps/desktop/src/lib/keybinds/combo.test.ts +86 -0
- package/agent/apps/desktop/src/lib/keybinds/combo.ts +195 -0
- package/agent/apps/desktop/src/lib/local-preview.ts +23 -2
- package/agent/apps/desktop/src/lib/markdown-preprocess.ts +20 -7
- package/agent/apps/desktop/src/lib/media.remote.test.ts +90 -0
- package/agent/apps/desktop/src/lib/media.ts +40 -1
- package/agent/apps/desktop/src/lib/model-status-label.test.ts +59 -0
- package/agent/apps/desktop/src/lib/model-status-label.ts +122 -0
- package/agent/apps/desktop/src/lib/mutable-ref.ts +6 -0
- package/agent/apps/desktop/src/lib/profile-color.ts +58 -0
- package/agent/apps/desktop/src/lib/query-client.ts +13 -0
- package/agent/apps/desktop/src/lib/remend-tail.test.ts +105 -0
- package/agent/apps/desktop/src/lib/remend-tail.ts +108 -0
- package/agent/apps/desktop/src/lib/session-export.ts +6 -3
- package/agent/apps/desktop/src/lib/session-ids.test.ts +44 -0
- package/agent/apps/desktop/src/lib/session-ids.ts +26 -0
- package/agent/apps/desktop/src/lib/session-search.test.ts +66 -0
- package/agent/apps/desktop/src/lib/session-search.ts +21 -0
- package/agent/apps/desktop/src/lib/session-source.ts +126 -0
- package/agent/apps/desktop/src/lib/storage.test.ts +25 -0
- package/agent/apps/desktop/src/lib/storage.ts +35 -1
- package/agent/apps/desktop/src/lib/todos.test.ts +46 -1
- package/agent/apps/desktop/src/lib/todos.ts +37 -0
- package/agent/apps/desktop/src/lib/tool-result-summary.ts +5 -1
- package/agent/apps/desktop/src/lib/update-copy.test.ts +38 -0
- package/agent/apps/desktop/src/lib/update-copy.ts +44 -0
- package/agent/apps/desktop/src/lib/use-enter-animation.ts +2 -2
- package/agent/apps/desktop/src/lib/yolo-session.ts +50 -0
- package/agent/apps/desktop/src/main.tsx +19 -19
- package/agent/apps/desktop/src/store/boot.ts +4 -3
- package/agent/apps/desktop/src/store/clarify.test.ts +81 -0
- package/agent/apps/desktop/src/store/clarify.ts +50 -13
- package/agent/apps/desktop/src/store/command-palette.ts +20 -0
- package/agent/apps/desktop/src/store/compaction.test.ts +53 -0
- package/agent/apps/desktop/src/store/compaction.ts +38 -0
- package/agent/apps/desktop/src/store/completion-sound.ts +32 -0
- package/agent/apps/desktop/src/store/composer-input-history.test.ts +147 -0
- package/agent/apps/desktop/src/store/composer-input-history.ts +158 -0
- package/agent/apps/desktop/src/store/composer-queue.test.ts +68 -0
- package/agent/apps/desktop/src/store/composer-queue.ts +76 -0
- package/agent/apps/desktop/src/store/composer-status.test.ts +99 -0
- package/agent/apps/desktop/src/store/composer-status.ts +277 -0
- package/agent/apps/desktop/src/store/composer.test.ts +106 -0
- package/agent/apps/desktop/src/store/composer.ts +116 -0
- package/agent/apps/desktop/src/store/cron.ts +19 -0
- package/agent/apps/desktop/src/store/gateway.ts +280 -6
- package/agent/apps/desktop/src/store/keybinds.ts +143 -0
- package/agent/apps/desktop/src/store/layout.ts +107 -9
- package/agent/apps/desktop/src/store/model-presets.test.ts +51 -0
- package/agent/apps/desktop/src/store/model-presets.ts +86 -0
- package/agent/apps/desktop/src/store/model-visibility.test.ts +99 -0
- package/agent/apps/desktop/src/store/model-visibility.ts +161 -0
- package/agent/apps/desktop/src/store/native-notifications.test.ts +192 -0
- package/agent/apps/desktop/src/store/native-notifications.ts +203 -0
- package/agent/apps/desktop/src/store/notifications.ts +10 -7
- package/agent/apps/desktop/src/store/onboarding.test.ts +271 -1
- package/agent/apps/desktop/src/store/onboarding.ts +268 -38
- package/agent/apps/desktop/src/store/preview.ts +10 -1
- package/agent/apps/desktop/src/store/profile.test.ts +89 -0
- package/agent/apps/desktop/src/store/profile.ts +395 -0
- package/agent/apps/desktop/src/store/prompts.test.ts +127 -0
- package/agent/apps/desktop/src/store/prompts.ts +117 -0
- package/agent/apps/desktop/src/store/session-switcher.test.ts +115 -0
- package/agent/apps/desktop/src/store/session-switcher.ts +128 -0
- package/agent/apps/desktop/src/store/session-sync.ts +25 -0
- package/agent/apps/desktop/src/store/session.test.ts +268 -2
- package/agent/apps/desktop/src/store/session.ts +392 -18
- package/agent/apps/desktop/src/store/subagents.ts +3 -0
- package/agent/apps/desktop/src/store/system-actions.ts +48 -0
- package/agent/apps/desktop/src/store/thread-scroll.ts +58 -5
- package/agent/apps/desktop/src/store/todos.test.ts +47 -0
- package/agent/apps/desktop/src/store/todos.ts +64 -0
- package/agent/apps/desktop/src/store/tool-dismiss.ts +45 -0
- package/agent/apps/desktop/src/store/translucency.ts +38 -0
- package/agent/apps/desktop/src/store/updates.test.ts +187 -2
- package/agent/apps/desktop/src/store/updates.ts +268 -18
- package/agent/apps/desktop/src/store/windows.test.ts +143 -0
- package/agent/apps/desktop/src/store/windows.ts +115 -0
- package/agent/apps/desktop/src/styles.css +510 -119
- package/agent/apps/desktop/src/themes/color.ts +142 -0
- package/agent/apps/desktop/src/themes/context.tsx +128 -75
- package/agent/apps/desktop/src/themes/install.test.ts +119 -0
- package/agent/apps/desktop/src/themes/install.ts +95 -0
- package/agent/apps/desktop/src/themes/presets.test.ts +33 -0
- package/agent/apps/desktop/src/themes/presets.ts +13 -4
- package/agent/apps/desktop/src/themes/profile-theme.test.ts +41 -0
- package/agent/apps/desktop/src/themes/types.ts +35 -0
- package/agent/apps/desktop/src/themes/user-themes.test.ts +63 -0
- package/agent/apps/desktop/src/themes/user-themes.ts +122 -0
- package/agent/apps/desktop/src/themes/vscode.test.ts +171 -0
- package/agent/apps/desktop/src/themes/vscode.ts +343 -0
- package/agent/apps/desktop/src/types/hermes.ts +138 -1
- package/agent/apps/desktop/tsconfig.json +2 -2
- package/agent/apps/desktop/vite.config.ts +18 -0
- package/agent/apps/shared/package.json +1 -1
- package/agent/apps/shared/src/json-rpc-gateway.ts +63 -2
- package/agent/apps/shared/tsconfig.json +2 -2
- package/agent/cli-config.yaml.example +78 -1
- package/agent/cli.py +2177 -3162
- package/agent/cron/blueprint_catalog.py +713 -0
- package/agent/cron/jobs.py +226 -110
- package/agent/cron/scheduler.py +468 -193
- package/agent/cron/scheduler_provider.py +177 -0
- package/agent/cron/scripts/__init__.py +1 -0
- package/agent/cron/scripts/classify_items.py +226 -0
- package/agent/cron/suggestion_catalog.py +154 -0
- package/agent/cron/suggestions.py +257 -0
- package/agent/docs/chronos-managed-cron-contract.md +196 -0
- package/agent/docs/design/profile-builder.md +146 -0
- package/agent/docs/middleware/README.md +260 -0
- package/agent/docs/observability/README.md +316 -0
- package/agent/docs/plans/2026-06-09-003-fix-telegram-stream-overflow-continuations-plan.md +240 -0
- package/agent/docs/rca-ssl-cacert-post-git-pull.md +54 -0
- package/agent/docs/relay-connector-contract.md +285 -0
- package/agent/gateway/authz_mixin.py +536 -0
- package/agent/gateway/channel_directory.py +65 -3
- package/agent/gateway/config.py +222 -12
- package/agent/gateway/display_config.py +10 -0
- package/agent/gateway/hooks.py +17 -0
- package/agent/gateway/kanban_watchers.py +1146 -0
- package/agent/gateway/message_timestamps.py +166 -0
- package/agent/gateway/platforms/ADDING_A_PLATFORM.md +29 -0
- package/agent/gateway/platforms/api_server.py +216 -38
- package/agent/gateway/platforms/base.py +210 -58
- package/agent/gateway/platforms/email.py +122 -12
- package/agent/gateway/platforms/feishu.py +80 -11
- package/agent/gateway/platforms/feishu_meeting_invite.py +212 -0
- package/agent/gateway/platforms/matrix.py +1498 -297
- package/agent/gateway/platforms/qqbot/adapter.py +6 -0
- package/agent/gateway/platforms/signal.py +8 -0
- package/agent/gateway/platforms/slack.py +308 -12
- package/agent/gateway/platforms/telegram.py +831 -24
- package/agent/gateway/platforms/webhook.py +109 -21
- package/agent/gateway/platforms/weixin.py +113 -2
- package/agent/gateway/platforms/whatsapp.py +94 -288
- package/agent/gateway/platforms/whatsapp_cloud.py +1956 -0
- package/agent/gateway/platforms/whatsapp_common.py +367 -0
- package/agent/gateway/platforms/yuanbao.py +608 -191
- package/agent/gateway/platforms/yuanbao_proto.py +232 -23
- package/agent/gateway/relay/__init__.py +375 -0
- package/agent/gateway/relay/adapter.py +222 -0
- package/agent/gateway/relay/auth.py +168 -0
- package/agent/gateway/relay/descriptor.py +118 -0
- package/agent/gateway/relay/transport.py +101 -0
- package/agent/gateway/relay/ws_transport.py +327 -0
- package/agent/gateway/response_filters.py +53 -0
- package/agent/gateway/rich_sent_store.py +80 -0
- package/agent/gateway/run.py +2940 -5001
- package/agent/gateway/session.py +109 -8
- package/agent/gateway/session_context.py +22 -4
- package/agent/gateway/slash_commands.py +3854 -0
- package/agent/gateway/status.py +141 -21
- package/agent/gateway/stream_consumer.py +288 -31
- package/agent/hermes-already-has-routines.md +1 -1
- package/agent/hermes_cli/__init__.py +62 -17
- package/agent/hermes_cli/_parser.py +30 -0
- package/agent/hermes_cli/_subprocess_compat.py +61 -0
- package/agent/hermes_cli/active_sessions.py +320 -0
- package/agent/hermes_cli/auth.py +707 -59
- package/agent/hermes_cli/auth_commands.py +39 -22
- package/agent/hermes_cli/backup.py +109 -7
- package/agent/hermes_cli/banner.py +88 -0
- package/agent/hermes_cli/blueprint_cmd.py +318 -0
- package/agent/hermes_cli/cli_agent_setup_mixin.py +684 -0
- package/agent/hermes_cli/cli_commands_mixin.py +2293 -0
- package/agent/hermes_cli/commands.py +215 -91
- package/agent/hermes_cli/config.py +967 -130
- package/agent/hermes_cli/container_boot.py +76 -11
- package/agent/hermes_cli/cron.py +5 -11
- package/agent/hermes_cli/curator.py +21 -0
- package/agent/hermes_cli/dashboard_auth/__init__.py +2 -0
- package/agent/hermes_cli/dashboard_auth/base.py +62 -0
- package/agent/hermes_cli/dashboard_auth/cookies.py +32 -19
- package/agent/hermes_cli/dashboard_auth/login_page.py +156 -6
- package/agent/hermes_cli/dashboard_auth/middleware.py +28 -4
- package/agent/hermes_cli/dashboard_auth/prefix.py +46 -2
- package/agent/hermes_cli/dashboard_auth/public_paths.py +6 -0
- package/agent/hermes_cli/dashboard_auth/routes.py +158 -2
- package/agent/hermes_cli/dashboard_auth/ws_tickets.py +85 -11
- package/agent/hermes_cli/dashboard_register.py +427 -0
- package/agent/hermes_cli/debug.py +155 -50
- package/agent/hermes_cli/doctor.py +255 -14
- package/agent/hermes_cli/dump.py +60 -6
- package/agent/hermes_cli/env_loader.py +33 -0
- package/agent/hermes_cli/gateway.py +755 -103
- package/agent/hermes_cli/gateway_enroll.py +250 -0
- package/agent/hermes_cli/gateway_windows.py +254 -11
- package/agent/hermes_cli/gui_uninstall.py +285 -0
- package/agent/hermes_cli/inventory.py +105 -4
- package/agent/hermes_cli/kanban.py +58 -71
- package/agent/hermes_cli/kanban_db.py +391 -14
- package/agent/hermes_cli/kanban_decompose.py +2 -2
- package/agent/hermes_cli/kanban_specify.py +3 -1
- package/agent/hermes_cli/logs.py +2 -0
- package/agent/hermes_cli/main.py +2889 -5287
- package/agent/hermes_cli/managed_scope.py +214 -0
- package/agent/hermes_cli/managed_uv.py +254 -0
- package/agent/hermes_cli/mcp_catalog.py +6 -3
- package/agent/hermes_cli/mcp_config.py +145 -21
- package/agent/hermes_cli/mcp_security.py +96 -0
- package/agent/hermes_cli/mcp_startup.py +32 -3
- package/agent/hermes_cli/memory_providers.py +149 -0
- package/agent/hermes_cli/memory_setup.py +97 -42
- package/agent/hermes_cli/middleware.py +313 -0
- package/agent/hermes_cli/model_catalog.py +31 -0
- package/agent/hermes_cli/model_cost_guard.py +134 -0
- package/agent/hermes_cli/model_normalize.py +2 -1
- package/agent/hermes_cli/model_setup_flows.py +2759 -0
- package/agent/hermes_cli/model_switch.py +242 -27
- package/agent/hermes_cli/models.py +284 -44
- package/agent/hermes_cli/nous_account.py +33 -6
- package/agent/hermes_cli/nous_billing.py +406 -0
- package/agent/hermes_cli/nous_subscription.py +202 -5
- package/agent/hermes_cli/platforms.py +1 -0
- package/agent/hermes_cli/plugins.py +218 -18
- package/agent/hermes_cli/plugins_cmd.py +249 -105
- package/agent/hermes_cli/portal_cli.py +56 -16
- package/agent/hermes_cli/profile_distribution.py +6 -1
- package/agent/hermes_cli/profiles.py +283 -32
- package/agent/hermes_cli/provider_catalog.py +170 -0
- package/agent/hermes_cli/providers.py +4 -1
- package/agent/hermes_cli/pty_bridge.py +53 -4
- package/agent/hermes_cli/runtime_provider.py +216 -34
- package/agent/hermes_cli/secret_prompt.py +4 -4
- package/agent/hermes_cli/secrets_cli.py +24 -0
- package/agent/hermes_cli/send_cmd.py +28 -2
- package/agent/hermes_cli/service_manager.py +166 -19
- package/agent/hermes_cli/session_listing.py +97 -0
- package/agent/hermes_cli/setup.py +158 -94
- package/agent/hermes_cli/setup_whatsapp_cloud.py +541 -0
- package/agent/hermes_cli/skills_config.py +8 -2
- package/agent/hermes_cli/skills_hub.py +149 -7
- package/agent/hermes_cli/status.py +2 -2
- package/agent/hermes_cli/subcommands/__init__.py +18 -0
- package/agent/hermes_cli/subcommands/_shared.py +29 -0
- package/agent/hermes_cli/subcommands/acp.py +52 -0
- package/agent/hermes_cli/subcommands/auth.py +109 -0
- package/agent/hermes_cli/subcommands/backup.py +38 -0
- package/agent/hermes_cli/subcommands/claw.py +92 -0
- package/agent/hermes_cli/subcommands/config.py +49 -0
- package/agent/hermes_cli/subcommands/cron.py +163 -0
- package/agent/hermes_cli/subcommands/dashboard.py +143 -0
- package/agent/hermes_cli/subcommands/debug.py +77 -0
- package/agent/hermes_cli/subcommands/doctor.py +35 -0
- package/agent/hermes_cli/subcommands/dump.py +28 -0
- package/agent/hermes_cli/subcommands/gateway.py +332 -0
- package/agent/hermes_cli/subcommands/gui.py +63 -0
- package/agent/hermes_cli/subcommands/hooks.py +77 -0
- package/agent/hermes_cli/subcommands/import_cmd.py +31 -0
- package/agent/hermes_cli/subcommands/insights.py +25 -0
- package/agent/hermes_cli/subcommands/login.py +78 -0
- package/agent/hermes_cli/subcommands/logout.py +28 -0
- package/agent/hermes_cli/subcommands/logs.py +78 -0
- package/agent/hermes_cli/subcommands/mcp.py +108 -0
- package/agent/hermes_cli/subcommands/memory.py +53 -0
- package/agent/hermes_cli/subcommands/model.py +72 -0
- package/agent/hermes_cli/subcommands/pairing.py +36 -0
- package/agent/hermes_cli/subcommands/plugins.py +94 -0
- package/agent/hermes_cli/subcommands/postinstall.py +23 -0
- package/agent/hermes_cli/subcommands/profile.py +203 -0
- package/agent/hermes_cli/subcommands/prompt_size.py +36 -0
- package/agent/hermes_cli/subcommands/security.py +62 -0
- package/agent/hermes_cli/subcommands/setup.py +58 -0
- package/agent/hermes_cli/subcommands/skills.py +298 -0
- package/agent/hermes_cli/subcommands/slack.py +60 -0
- package/agent/hermes_cli/subcommands/status.py +28 -0
- package/agent/hermes_cli/subcommands/tools.py +95 -0
- package/agent/hermes_cli/subcommands/uninstall.py +41 -0
- package/agent/hermes_cli/subcommands/update.py +70 -0
- package/agent/hermes_cli/subcommands/version.py +18 -0
- package/agent/hermes_cli/subcommands/webhook.py +76 -0
- package/agent/hermes_cli/subcommands/whatsapp.py +22 -0
- package/agent/hermes_cli/suggestions_cmd.py +153 -0
- package/agent/hermes_cli/telegram_managed_bot.py +358 -0
- package/agent/hermes_cli/tips.py +3 -4
- package/agent/hermes_cli/tools_config.py +155 -28
- package/agent/hermes_cli/uninstall.py +231 -35
- package/agent/hermes_cli/web_server.py +6190 -973
- package/agent/hermes_cli/win_pty_bridge.py +179 -0
- package/agent/hermes_cli/write_approval_commands.py +209 -0
- package/agent/hermes_constants.py +164 -33
- package/agent/hermes_logging.py +74 -2
- package/agent/hermes_state.py +919 -106
- package/agent/hermes_time.py +20 -0
- package/agent/locales/af.yaml +23 -0
- package/agent/locales/de.yaml +23 -0
- package/agent/locales/en.yaml +20 -0
- package/agent/locales/es.yaml +23 -0
- package/agent/locales/fr.yaml +23 -0
- package/agent/locales/ga.yaml +23 -0
- package/agent/locales/hu.yaml +23 -0
- package/agent/locales/it.yaml +23 -0
- package/agent/locales/ja.yaml +23 -0
- package/agent/locales/ko.yaml +23 -0
- package/agent/locales/pt.yaml +23 -0
- package/agent/locales/ru.yaml +23 -0
- package/agent/locales/tr.yaml +23 -0
- package/agent/locales/uk.yaml +23 -0
- package/agent/locales/zh-hant.yaml +23 -0
- package/agent/locales/zh.yaml +23 -0
- package/agent/model_tools.py +204 -40
- package/agent/optional-mcps/clawpump/manifest.yaml +4 -2
- package/agent/optional-mcps/clawpump-stdio/manifest.yaml +2 -0
- package/agent/optional-mcps/unreal-engine/manifest.yaml +54 -0
- package/agent/optional-skills/blockchain/hyperliquid/SKILL.md +2 -2
- package/agent/optional-skills/blockchain/hyperliquid/scripts/hyperliquid_client.py +1 -1
- package/agent/optional-skills/creative/kanban-video-orchestrator/SKILL.md +1 -1
- package/agent/optional-skills/creative/kanban-video-orchestrator/assets/setup.sh.tmpl +4 -3
- package/agent/optional-skills/creative/kanban-video-orchestrator/references/kanban-setup.md +6 -4
- package/agent/optional-skills/creative/kanban-video-orchestrator/references/tool-matrix.md +2 -2
- package/agent/{skills/software-development → optional-skills/devops}/hermes-s6-container-supervision/SKILL.md +2 -0
- package/agent/optional-skills/devops/watchers/SKILL.md +1 -1
- package/agent/optional-skills/devops/watchers/scripts/watch_github.py +2 -1
- package/agent/optional-skills/payments/mpp-agent/SKILL.md +124 -0
- package/agent/optional-skills/payments/stripe-link-cli/SKILL.md +184 -0
- package/agent/optional-skills/payments/stripe-projects/SKILL.md +120 -0
- package/agent/optional-skills/productivity/canvas/SKILL.md +1 -1
- package/agent/optional-skills/productivity/canvas/scripts/canvas_api.py +4 -1
- package/agent/optional-skills/productivity/shop/SKILL.md +224 -0
- package/agent/optional-skills/productivity/shop/references/catalog-mcp.md +236 -0
- package/agent/optional-skills/productivity/shop/references/direct-api.md +278 -0
- package/agent/optional-skills/productivity/shop/references/legal.md +3 -0
- package/agent/optional-skills/productivity/shop/references/safety.md +36 -0
- package/agent/optional-skills/productivity/shopify/SKILL.md +1 -1
- package/agent/optional-skills/productivity/siyuan/SKILL.md +1 -1
- package/agent/optional-skills/productivity/telephony/SKILL.md +4 -4
- package/agent/optional-skills/productivity/telephony/scripts/telephony.py +15 -15
- package/agent/optional-skills/security/1password/SKILL.md +1 -1
- package/agent/{skills/red-teaming → optional-skills/security}/godmode/SKILL.md +3 -4
- package/agent/{skills/red-teaming → optional-skills/security}/godmode/scripts/auto_jailbreak.py +3 -1
- package/agent/optional-skills/software-development/rest-graphql-debug/SKILL.md +1 -1
- package/agent/{skills → optional-skills}/software-development/subagent-driven-development/SKILL.md +5 -5
- package/agent/package-lock.json +4082 -7907
- package/agent/package.json +18 -3
- package/agent/plugins/browser/firecrawl/provider.py +4 -1
- package/agent/plugins/cron/__init__.py +344 -0
- package/agent/plugins/cron/chronos/__init__.py +241 -0
- package/agent/plugins/cron/chronos/_nas_client.py +123 -0
- package/agent/plugins/cron/chronos/plugin.yaml +9 -0
- package/agent/plugins/cron/chronos/verify.py +103 -0
- package/agent/plugins/dashboard_auth/basic/__init__.py +491 -0
- package/agent/plugins/dashboard_auth/basic/plugin.yaml +7 -0
- package/agent/plugins/dashboard_auth/nous/__init__.py +12 -14
- package/agent/plugins/dashboard_auth/self_hosted/__init__.py +736 -0
- package/agent/plugins/dashboard_auth/self_hosted/plugin.yaml +8 -0
- package/agent/plugins/disk-cleanup/disk_cleanup.py +100 -20
- package/agent/plugins/google_meet/audio_bridge.py +4 -0
- package/agent/plugins/google_meet/meet_bot.py +7 -1
- package/agent/plugins/hermes-achievements/dashboard/dist/index.js +9 -15
- package/agent/plugins/image_gen/fal/__init__.py +35 -6
- package/agent/plugins/image_gen/krea/__init__.py +56 -13
- package/agent/plugins/image_gen/openai/__init__.py +122 -24
- package/agent/plugins/image_gen/openai-codex/__init__.py +28 -2
- package/agent/plugins/image_gen/xai/__init__.py +92 -12
- package/agent/plugins/kanban/dashboard/dist/index.js +63 -48
- package/agent/plugins/kanban/dashboard/plugin_api.py +39 -35
- package/agent/plugins/memory/__init__.py +48 -5
- package/agent/plugins/memory/byterover/__init__.py +1 -0
- package/agent/plugins/memory/hindsight/README.md +1 -1
- package/agent/plugins/memory/hindsight/__init__.py +138 -24
- package/agent/plugins/memory/hindsight/plugin.yaml +1 -1
- package/agent/plugins/memory/honcho/README.md +13 -10
- package/agent/plugins/memory/honcho/cli.py +247 -122
- package/agent/plugins/memory/honcho/client.py +112 -102
- package/agent/plugins/memory/openviking/README.md +12 -1
- package/agent/plugins/memory/openviking/__init__.py +2281 -107
- package/agent/plugins/memory/openviking/plugin.yaml +1 -2
- package/agent/plugins/memory/supermemory/README.md +22 -10
- package/agent/plugins/memory/supermemory/__init__.py +142 -37
- package/agent/plugins/memory/supermemory/plugin.yaml +1 -1
- package/agent/plugins/model-providers/anthropic/__init__.py +1 -0
- package/agent/plugins/model-providers/bedrock/__init__.py +1 -0
- package/agent/plugins/model-providers/copilot-acp/__init__.py +1 -0
- package/agent/plugins/model-providers/custom/__init__.py +8 -2
- package/agent/plugins/model-providers/kimi-coding/__init__.py +16 -7
- package/agent/plugins/model-providers/minimax/__init__.py +60 -8
- package/agent/plugins/model-providers/opencode-zen/__init__.py +12 -3
- package/agent/plugins/model-providers/openrouter/__init__.py +75 -4
- package/agent/plugins/model-providers/xiaomi/__init__.py +2 -0
- package/agent/plugins/model-providers/zai/__init__.py +1 -0
- package/agent/plugins/observability/langfuse/__init__.py +147 -14
- package/agent/plugins/observability/nemo_relay/README.md +559 -0
- package/agent/plugins/observability/nemo_relay/__init__.py +962 -0
- package/agent/plugins/observability/nemo_relay/plugin.yaml +20 -0
- package/agent/plugins/platforms/discord/adapter.py +932 -61
- package/agent/plugins/platforms/discord/voice_mixer.py +379 -0
- package/agent/plugins/platforms/google_chat/adapter.py +9 -3
- package/agent/plugins/platforms/google_chat/oauth.py +1 -1
- package/agent/plugins/platforms/homeassistant/__init__.py +3 -0
- package/agent/{gateway/platforms/homeassistant.py → plugins/platforms/homeassistant/adapter.py} +128 -0
- package/agent/plugins/platforms/homeassistant/plugin.yaml +22 -0
- package/agent/plugins/platforms/irc/adapter.py +4 -1
- package/agent/plugins/platforms/line/adapter.py +16 -1
- package/agent/plugins/platforms/mattermost/adapter.py +100 -24
- package/agent/plugins/platforms/photon/README.md +179 -0
- package/agent/plugins/platforms/photon/__init__.py +4 -0
- package/agent/plugins/platforms/photon/adapter.py +1586 -0
- package/agent/plugins/platforms/photon/auth.py +1046 -0
- package/agent/plugins/platforms/photon/cli.py +439 -0
- package/agent/plugins/platforms/photon/plugin.yaml +88 -0
- package/agent/plugins/platforms/photon/sidecar/README.md +52 -0
- package/agent/plugins/platforms/photon/sidecar/index.mjs +720 -0
- package/agent/plugins/platforms/photon/sidecar/package-lock.json +1730 -0
- package/agent/plugins/platforms/photon/sidecar/package.json +25 -0
- package/agent/plugins/platforms/photon/sidecar/patch-spectrum-mixed-attachments.mjs +155 -0
- package/agent/plugins/platforms/raft/__init__.py +3 -0
- package/agent/plugins/platforms/raft/adapter.py +774 -0
- package/agent/plugins/platforms/raft/plugin.yaml +19 -0
- package/agent/plugins/platforms/simplex/adapter.py +777 -220
- package/agent/plugins/platforms/simplex/plugin.yaml +21 -2
- package/agent/plugins/platforms/teams/adapter.py +175 -5
- package/agent/plugins/plugin_utils.py +135 -0
- package/agent/plugins/video_gen/fal/__init__.py +10 -3
- package/agent/plugins/web/searxng/provider.py +15 -2
- package/agent/plugins/web/xai/provider.py +2 -2
- package/agent/providers/base.py +22 -3
- package/agent/pyproject.toml +115 -21
- package/agent/run_agent.py +733 -39
- package/agent/scripts/build_skills_index.py +51 -19
- package/agent/scripts/check_subprocess_stdin.py +177 -0
- package/agent/scripts/contributor_audit.py +2 -0
- package/agent/scripts/docker_config_migrate.py +67 -0
- package/agent/scripts/install.cmd +3 -3
- package/agent/scripts/install.ps1 +580 -154
- package/agent/scripts/install.sh +402 -185
- package/agent/scripts/lib/node-bootstrap.sh +39 -4
- package/agent/scripts/release.py +183 -0
- package/agent/scripts/run_tests.sh +1 -0
- package/agent/scripts/run_tests_parallel.py +18 -23
- package/agent/scripts/whatsapp-bridge/bridge.js +25 -4
- package/agent/setup.py +59 -0
- package/agent/skills/autonomous-ai-agents/codex/SKILL.md +19 -0
- package/agent/skills/autonomous-ai-agents/hermes-agent/SKILL.md +10 -3
- package/agent/skills/{mcp/native-mcp/SKILL.md → autonomous-ai-agents/hermes-agent/references/native-mcp.md} +0 -13
- package/agent/skills/{devops/webhook-subscriptions/SKILL.md → autonomous-ai-agents/hermes-agent/references/webhooks.md} +1 -11
- package/agent/skills/clawpump/SKILL.md +4 -1
- package/agent/skills/devops/kanban-orchestrator/SKILL.md +1 -0
- package/agent/skills/devops/kanban-worker/SKILL.md +1 -0
- package/agent/skills/github/github-auth/SKILL.md +2 -2
- package/agent/skills/github/github-auth/scripts/gh-env.sh +2 -2
- package/agent/skills/github/github-code-review/SKILL.md +2 -2
- package/agent/skills/github/github-issues/SKILL.md +2 -2
- package/agent/skills/github/github-pr-workflow/SKILL.md +2 -2
- package/agent/skills/github/github-repo-management/SKILL.md +2 -2
- package/agent/skills/media/gif-search/SKILL.md +1 -1
- package/agent/skills/media/youtube-content/SKILL.md +10 -7
- package/agent/skills/media/youtube-content/scripts/fetch_transcript.py +3 -3
- package/agent/skills/note-taking/obsidian/SKILL.md +1 -1
- package/agent/skills/productivity/airtable/SKILL.md +2 -2
- package/agent/skills/productivity/google-workspace/scripts/setup.py +33 -7
- package/agent/skills/productivity/notion/SKILL.md +2 -2
- package/agent/skills/productivity/teams-meeting-pipeline/SKILL.md +1 -1
- package/agent/skills/research/llm-wiki/SKILL.md +1 -1
- package/agent/skills/social-media/xurl/SKILL.md +9 -0
- package/agent/skills/software-development/hermes-agent-skill-authoring/SKILL.md +1 -1
- package/agent/skills/software-development/plan/SKILL.md +285 -5
- package/agent/skills/software-development/requesting-code-review/SKILL.md +2 -2
- package/agent/skills/software-development/simplify-code/SKILL.md +212 -0
- package/agent/skills/software-development/spike/SKILL.md +2 -2
- package/agent/skills/software-development/systematic-debugging/SKILL.md +1 -1
- package/agent/skills/software-development/test-driven-development/SKILL.md +1 -1
- package/agent/tools/approval.py +302 -4
- package/agent/tools/async_delegation.py +386 -0
- package/agent/tools/blueprints.py +325 -0
- package/agent/tools/browser_cdp_tool.py +3 -3
- package/agent/tools/browser_tool.py +34 -6
- package/agent/tools/checkpoint_manager.py +31 -1
- package/agent/tools/clarify_tool.py +55 -5
- package/agent/tools/code_execution_tool.py +31 -14
- package/agent/tools/computer_use/cua_backend.py +81 -3
- package/agent/tools/computer_use/tool.py +79 -5
- package/agent/tools/computer_use/vision_routing.py +55 -3
- package/agent/tools/credential_files.py +31 -12
- package/agent/tools/cronjob_tools.py +30 -20
- package/agent/tools/delegate_tool.py +356 -31
- package/agent/tools/env_probe.py +1 -0
- package/agent/tools/environments/docker.py +163 -8
- package/agent/tools/environments/file_sync.py +2 -1
- package/agent/tools/environments/local.py +74 -23
- package/agent/tools/environments/singularity.py +4 -1
- package/agent/tools/environments/ssh.py +78 -11
- package/agent/tools/file_operations.py +277 -41
- package/agent/tools/file_tools.py +166 -28
- package/agent/tools/image_generation_tool.py +515 -29
- package/agent/tools/kanban_tools.py +99 -0
- package/agent/tools/lazy_deps.py +33 -2
- package/agent/tools/mcp_oauth.py +5 -5
- package/agent/tools/mcp_oauth_manager.py +7 -5
- package/agent/tools/mcp_tool.py +840 -33
- package/agent/tools/memory_tool.py +335 -38
- package/agent/tools/osv_check.py +15 -1
- package/agent/tools/process_registry.py +155 -11
- package/agent/tools/read_extract.py +248 -0
- package/agent/tools/read_terminal_tool.py +93 -0
- package/agent/tools/schema_sanitizer.py +38 -0
- package/agent/tools/send_message_tool.py +163 -49
- package/agent/tools/session_search_tool.py +189 -7
- package/agent/tools/skill_manager_tool.py +202 -3
- package/agent/tools/skill_usage.py +52 -4
- package/agent/tools/skills_hub.py +184 -44
- package/agent/tools/skills_sync.py +232 -5
- package/agent/tools/skills_tool.py +125 -11
- package/agent/tools/terminal_tool.py +148 -26
- package/agent/tools/tirith_security.py +2 -0
- package/agent/tools/todo_tool.py +32 -1
- package/agent/tools/transcription_tools.py +13 -5
- package/agent/tools/tts_tool.py +332 -38
- package/agent/tools/url_safety.py +52 -1
- package/agent/tools/vision_tools.py +124 -39
- package/agent/tools/voice_mode.py +4 -3
- package/agent/tools/web_tools.py +45 -15
- package/agent/tools/write_approval.py +493 -0
- package/agent/toolsets.py +34 -10
- package/agent/trajectory_compressor.py +81 -10
- package/agent/tui_gateway/entry.py +43 -6
- package/agent/tui_gateway/server.py +3335 -330
- package/agent/tui_gateway/slash_worker.py +61 -0
- package/agent/tui_gateway/ws.py +67 -9
- package/agent/ui-tui/eslint.config.mjs +0 -4
- package/agent/ui-tui/package.json +6 -6
- package/agent/ui-tui/packages/hermes-ink/package.json +1 -1
- package/agent/ui-tui/packages/hermes-ink/src/ink/app-mouse.test.ts +34 -1
- package/agent/ui-tui/packages/hermes-ink/src/ink/app-rawmode-mouse.test.ts +91 -0
- package/agent/ui-tui/packages/hermes-ink/src/ink/components/App.tsx +35 -2
- package/agent/ui-tui/packages/hermes-ink/src/ink/events/input-event.ts +4 -11
- package/agent/ui-tui/packages/hermes-ink/src/ink/parse-keypress.test.ts +23 -57
- package/agent/ui-tui/packages/hermes-ink/src/ink/parse-keypress.ts +11 -135
- package/agent/ui-tui/packages/hermes-ink/src/ink/termio/tokenize.test.ts +185 -0
- package/agent/ui-tui/packages/hermes-ink/src/ink/termio/tokenize.ts +37 -3
- package/agent/ui-tui/packages/hermes-ink/src/utils/execFileNoThrow.ts +5 -5
- package/agent/ui-tui/src/__tests__/appChromeStatusRule.test.tsx +217 -0
- package/agent/ui-tui/src/__tests__/appChromeStatusRuleDevCredits.test.tsx +73 -0
- package/agent/ui-tui/src/__tests__/approvalAction.test.ts +11 -0
- package/agent/ui-tui/src/__tests__/billingCommand.test.ts +301 -0
- package/agent/ui-tui/src/__tests__/blockLayout.test.ts +122 -0
- package/agent/ui-tui/src/__tests__/brandingMcpCount.test.ts +111 -0
- package/agent/ui-tui/src/__tests__/completionApply.test.ts +51 -0
- package/agent/ui-tui/src/__tests__/createGatewayEventHandler.test.ts +487 -2
- package/agent/ui-tui/src/__tests__/createSlashHandler.test.ts +54 -0
- package/agent/ui-tui/src/__tests__/creditsCommand.test.ts +144 -0
- package/agent/ui-tui/src/__tests__/gatewayClient.test.ts +120 -99
- package/agent/ui-tui/src/__tests__/gracefulExit.test.ts +11 -0
- package/agent/ui-tui/src/__tests__/memoryMonitor.test.ts +102 -0
- package/agent/ui-tui/src/__tests__/paths.test.ts +41 -1
- package/agent/ui-tui/src/__tests__/terminalModes.test.ts +22 -0
- package/agent/ui-tui/src/__tests__/text.test.ts +23 -0
- package/agent/ui-tui/src/__tests__/textInputFastEcho.test.ts +37 -0
- package/agent/ui-tui/src/__tests__/turnControllerNotice.test.ts +43 -0
- package/agent/ui-tui/src/__tests__/useInputHandlers.test.ts +38 -1
- package/agent/ui-tui/src/__tests__/virtualHeights.test.ts +8 -0
- package/agent/ui-tui/src/app/createGatewayEventHandler.ts +102 -7
- package/agent/ui-tui/src/app/interfaces.ts +64 -1
- package/agent/ui-tui/src/app/overlayStore.ts +18 -2
- package/agent/ui-tui/src/app/slash/commands/billing.ts +332 -0
- package/agent/ui-tui/src/app/slash/commands/core.ts +31 -2
- package/agent/ui-tui/src/app/slash/commands/credits.ts +57 -0
- package/agent/ui-tui/src/app/slash/commands/ops.ts +28 -0
- package/agent/ui-tui/src/app/slash/commands/session.ts +32 -4
- package/agent/ui-tui/src/app/slash/registry.ts +4 -0
- package/agent/ui-tui/src/app/turnController.ts +145 -2
- package/agent/ui-tui/src/app/uiStore.ts +2 -0
- package/agent/ui-tui/src/app/useInputHandlers.ts +42 -4
- package/agent/ui-tui/src/app/useMainApp.ts +54 -8
- package/agent/ui-tui/src/app/useSessionLifecycle.ts +40 -31
- package/agent/ui-tui/src/app/useSubmission.ts +23 -31
- package/agent/ui-tui/src/components/appChrome.tsx +112 -5
- package/agent/ui-tui/src/components/appLayout.tsx +9 -0
- package/agent/ui-tui/src/components/appOverlays.tsx +25 -1
- package/agent/ui-tui/src/components/billingOverlay.tsx +684 -0
- package/agent/ui-tui/src/components/branding.tsx +15 -3
- package/agent/ui-tui/src/components/messageLine.tsx +25 -3
- package/agent/ui-tui/src/components/pluginsHub.tsx +238 -0
- package/agent/ui-tui/src/components/prompts.tsx +31 -17
- package/agent/ui-tui/src/components/streamingAssistant.tsx +63 -55
- package/agent/ui-tui/src/components/textInput.tsx +16 -0
- package/agent/ui-tui/src/config/env.ts +12 -0
- package/agent/ui-tui/src/config/limits.ts +13 -0
- package/agent/ui-tui/src/domain/blockLayout.ts +146 -0
- package/agent/ui-tui/src/domain/paths.ts +24 -0
- package/agent/ui-tui/src/domain/slash.ts +40 -0
- package/agent/ui-tui/src/entry.tsx +35 -4
- package/agent/ui-tui/src/gatewayClient.ts +22 -10
- package/agent/ui-tui/src/gatewayTypes.ts +130 -1
- package/agent/ui-tui/src/lib/gracefulExit.ts +24 -4
- package/agent/ui-tui/src/lib/memory.test.ts +162 -0
- package/agent/ui-tui/src/lib/memory.ts +60 -1
- package/agent/ui-tui/src/lib/memoryMonitor.ts +79 -4
- package/agent/ui-tui/src/lib/osc52.ts +1 -1
- package/agent/ui-tui/src/lib/text.test.ts +32 -1
- package/agent/ui-tui/src/lib/text.ts +29 -2
- package/agent/ui-tui/src/lib/virtualHeights.ts +13 -0
- package/agent/ui-tui/src/types.ts +5 -0
- package/agent/ui-tui/tsconfig.build.json +0 -1
- package/agent/ui-tui/tsconfig.json +2 -1
- package/agent/utils.py +66 -2
- package/agent/uv.lock +300 -684
- package/agent/web/index.html +2 -2
- package/agent/web/package.json +11 -6
- package/agent/web/public/claw-bg.webp +0 -0
- package/agent/web/public/claw-logo.webp +0 -0
- package/agent/web/src/App.tsx +138 -48
- package/agent/web/src/components/AutomationBlueprints.tsx +225 -0
- package/agent/web/src/components/Backdrop.tsx +15 -0
- package/agent/web/src/components/ChatSessionList.tsx +260 -0
- package/agent/web/src/components/ChatSidebar.tsx +262 -78
- package/agent/web/src/components/ConfirmDialog.tsx +122 -0
- package/agent/web/src/components/ModelPickerDialog.tsx +111 -16
- package/agent/web/src/components/ModelReloadConfirm.tsx +40 -0
- package/agent/web/src/components/ProfileScopeBanner.tsx +30 -0
- package/agent/web/src/components/ProfileSwitcher.tsx +67 -0
- package/agent/web/src/components/ReasoningPicker.tsx +167 -0
- package/agent/web/src/components/SkillEditorDialog.tsx +215 -0
- package/agent/web/src/components/ThemeSwitcher.tsx +119 -4
- package/agent/web/src/components/ToolsetConfigDrawer.tsx +457 -0
- package/agent/web/src/contexts/PageHeaderProvider.tsx +7 -4
- package/agent/web/src/contexts/ProfileProvider.tsx +137 -0
- package/agent/web/src/contexts/SystemActions.tsx +6 -8
- package/agent/web/src/contexts/profile-context.ts +19 -0
- package/agent/web/src/contexts/useProfileScope.ts +6 -0
- package/agent/web/src/i18n/af.ts +5 -4
- package/agent/web/src/i18n/de.ts +5 -4
- package/agent/web/src/i18n/en.ts +58 -4
- package/agent/web/src/i18n/es.ts +5 -3
- package/agent/web/src/i18n/fr.ts +5 -3
- package/agent/web/src/i18n/ga.ts +5 -4
- package/agent/web/src/i18n/hu.ts +5 -4
- package/agent/web/src/i18n/it.ts +5 -4
- package/agent/web/src/i18n/ja.ts +5 -4
- package/agent/web/src/i18n/ko.ts +5 -4
- package/agent/web/src/i18n/pt.ts +5 -3
- package/agent/web/src/i18n/ru.ts +5 -4
- package/agent/web/src/i18n/tr.ts +5 -4
- package/agent/web/src/i18n/types.ts +59 -1
- package/agent/web/src/i18n/uk.ts +5 -3
- package/agent/web/src/i18n/zh-hant.ts +5 -4
- package/agent/web/src/i18n/zh.ts +5 -4
- package/agent/web/src/index.css +2 -2
- package/agent/web/src/lib/api.ts +819 -52
- package/agent/web/src/lib/dashboard-flags.ts +16 -7
- package/agent/web/src/lib/reasoning-effort.test.ts +48 -0
- package/agent/web/src/lib/reasoning-effort.ts +36 -0
- package/agent/web/src/lib/session-refresh.test.ts +21 -0
- package/agent/web/src/lib/session-refresh.ts +26 -0
- package/agent/web/src/pages/ChannelsPage.tsx +529 -68
- package/agent/web/src/pages/ChatPage.tsx +249 -56
- package/agent/web/src/pages/ConfigPage.tsx +11 -1
- package/agent/web/src/pages/CronPage.tsx +219 -31
- package/agent/web/src/pages/EnvPage.tsx +25 -6
- package/agent/web/src/pages/FilesPage.tsx +525 -0
- package/agent/web/src/pages/McpPage.tsx +80 -3
- package/agent/web/src/pages/ModelsPage.tsx +97 -12
- package/agent/web/src/pages/PluginsPage.tsx +1 -1
- package/agent/web/src/pages/ProfileBuilderPage.tsx +611 -0
- package/agent/web/src/pages/ProfilesPage.tsx +1038 -172
- package/agent/web/src/pages/SessionsPage.tsx +144 -13
- package/agent/web/src/pages/SkillsPage.tsx +851 -70
- package/agent/web/src/pages/SystemPage.tsx +340 -4
- package/agent/web/src/pages/WalletPage.tsx +401 -0
- package/agent/web/src/pages/WebhooksPage.tsx +145 -15
- package/agent/web/src/pages/X402Page.tsx +207 -0
- package/agent/web/src/plugins/registry.ts +28 -11
- package/agent/web/src/plugins/sdk.d.ts +160 -0
- package/agent/web/src/themes/context.tsx +112 -5
- package/agent/web/src/themes/fonts.ts +167 -0
- package/agent/web/src/themes/index.ts +7 -0
- package/agent/web/tsconfig.app.json +0 -1
- package/agent/web/vite.config.ts +1 -8
- package/agent/web/vitest.config.ts +16 -0
- package/package.json +1 -1
- package/agent/apps/desktop/package-lock.json +0 -18363
- package/agent/apps/desktop/src/app/chat/composer/skin-slash-popover.tsx +0 -56
- package/agent/apps/desktop/src/components/assistant-ui/thread-virtualizer.tsx +0 -382
- package/agent/apps/desktop/src/components/assistant-ui/todo-tool.tsx +0 -109
- package/agent/apps/desktop/src/components/chat/generated-image-context.tsx +0 -19
- package/agent/optional-skills/productivity/shop-app/SKILL.md +0 -340
- package/agent/skills/autonomous-ai-agents/kanban-codex-lane/SKILL.md +0 -277
- package/agent/skills/autonomous-ai-agents/kanban-codex-lane/templates/pmb-codex-lane-prompt.md +0 -57
- package/agent/skills/diagramming/DESCRIPTION.md +0 -3
- package/agent/skills/domain/DESCRIPTION.md +0 -24
- package/agent/skills/gifs/DESCRIPTION.md +0 -3
- package/agent/skills/inference-sh/DESCRIPTION.md +0 -19
- package/agent/skills/mcp/DESCRIPTION.md +0 -3
- package/agent/skills/media/spotify/SKILL.md +0 -135
- package/agent/skills/mlops/training/DESCRIPTION.md +0 -3
- package/agent/skills/mlops/vector-databases/DESCRIPTION.md +0 -3
- package/agent/skills/productivity/linear/SKILL.md +0 -380
- package/agent/skills/productivity/linear/scripts/linear_api.py +0 -445
- package/agent/skills/software-development/debugging-hermes-tui-commands/SKILL.md +0 -152
- package/agent/skills/software-development/writing-plans/SKILL.md +0 -297
- package/agent/ui-tui/package-lock.json +0 -7449
- package/agent/ui-tui/packages/hermes-ink/package-lock.json +0 -1289
- package/agent/web/package-lock.json +0 -8887
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/PORT_NOTES.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/SKILL.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/prompts/system.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/palettes/macaron.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/palettes/mono-ink.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/palettes/neon.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/palettes/warm.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/prompt-construction.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/style-presets.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/blueprint.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/chalkboard.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/editorial.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/elegant.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/fantasy-animation.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/flat-doodle.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/flat.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/ink-notes.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/intuition-machine.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/minimal.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/nature.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/notion.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/pixel-art.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/playful.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/retro.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/scientific.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/screen-print.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/sketch-notes.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/sketch.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/vector-illustration.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/vintage.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/warm.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/watercolor.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/usage.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/workflow.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/PORT_NOTES.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/SKILL.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/analysis-framework.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/chalk.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/ink-brush.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/ligne-claire.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/manga.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/minimalist.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/realistic.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/auto-selection.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/base-prompt.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/character-template.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/cinematic.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/dense.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/four-panel.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/mixed.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/splash.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/standard.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/webtoon.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/ohmsha-guide.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/partial-workflows.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/concept-story.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/four-panel.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/ohmsha.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/shoujo.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/wuxia.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/storyboard-template.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/action.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/dramatic.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/energetic.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/neutral.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/romantic.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/vintage.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/warm.md +0 -0
- /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/workflow.md +0 -0
- /package/agent/{skills → optional-skills}/creative/creative-ideation/SKILL.md +0 -0
- /package/agent/{skills → optional-skills}/creative/creative-ideation/references/full-prompt-library.md +0 -0
- /package/agent/{skills → optional-skills}/creative/pixel-art/ATTRIBUTION.md +0 -0
- /package/agent/{skills → optional-skills}/creative/pixel-art/SKILL.md +0 -0
- /package/agent/{skills → optional-skills}/creative/pixel-art/references/palettes.md +0 -0
- /package/agent/{skills → optional-skills}/creative/pixel-art/scripts/__init__.py +0 -0
- /package/agent/{skills → optional-skills}/creative/pixel-art/scripts/palettes.py +0 -0
- /package/agent/{skills → optional-skills}/creative/pixel-art/scripts/pixel_art.py +0 -0
- /package/agent/{skills → optional-skills}/creative/pixel-art/scripts/pixel_art_video.py +0 -0
- /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/SKILL.md +0 -0
- /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/references/analysis-modules.md +0 -0
- /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/references/methods-guide.md +0 -0
- /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/templates/abliteration-config.yaml +0 -0
- /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/templates/analysis-study.yaml +0 -0
- /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/templates/batch-abliteration.yaml +0 -0
- /package/agent/{skills → optional-skills}/mlops/research/DESCRIPTION.md +0 -0
- /package/agent/{skills → optional-skills}/mlops/research/dspy/SKILL.md +0 -0
- /package/agent/{skills → optional-skills}/mlops/research/dspy/references/examples.md +0 -0
- /package/agent/{skills → optional-skills}/mlops/research/dspy/references/modules.md +0 -0
- /package/agent/{skills → optional-skills}/mlops/research/dspy/references/optimizers.md +0 -0
- /package/agent/{skills/red-teaming → optional-skills/security}/godmode/references/jailbreak-templates.md +0 -0
- /package/agent/{skills/red-teaming → optional-skills/security}/godmode/references/refusal-detection.md +0 -0
- /package/agent/{skills/red-teaming → optional-skills/security}/godmode/scripts/godmode_race.py +0 -0
- /package/agent/{skills/red-teaming → optional-skills/security}/godmode/scripts/load_godmode.py +0 -0
- /package/agent/{skills/red-teaming → optional-skills/security}/godmode/scripts/parseltongue.py +0 -0
- /package/agent/{skills/red-teaming → optional-skills/security}/godmode/templates/prefill-subtle.json +0 -0
- /package/agent/{skills/red-teaming → optional-skills/security}/godmode/templates/prefill.json +0 -0
- /package/agent/{skills → optional-skills}/software-development/subagent-driven-development/references/context-budget-discipline.md +0 -0
- /package/agent/{skills → optional-skills}/software-development/subagent-driven-development/references/gates-taxonomy.md +0 -0
|
@@ -102,7 +102,7 @@ OpenAI = _OpenAIProxy() # module-level name, resolves lazily on call/isinstance
|
|
|
102
102
|
from agent.credential_pool import load_pool
|
|
103
103
|
from hermes_cli.config import get_hermes_home
|
|
104
104
|
from hermes_constants import OPENROUTER_BASE_URL
|
|
105
|
-
from utils import base_url_host_matches, base_url_hostname, normalize_proxy_env_vars
|
|
105
|
+
from utils import base_url_host_matches, base_url_hostname, model_forces_max_completion_tokens, normalize_proxy_env_vars
|
|
106
106
|
|
|
107
107
|
logger = logging.getLogger(__name__)
|
|
108
108
|
|
|
@@ -202,6 +202,35 @@ def _is_arcee_trinity_thinking(model: Optional[str]) -> bool:
|
|
|
202
202
|
return bare == "trinity-large-thinking"
|
|
203
203
|
|
|
204
204
|
|
|
205
|
+
# Context window enforced by ChatGPT's Codex OAuth backend for gpt-5.5.
|
|
206
|
+
# The raw OpenAI API and OpenRouter expose 1.05M for the same slug, but the
|
|
207
|
+
# Codex backend hard-caps at 272K (verified live: a ~330K-token request to
|
|
208
|
+
# chatgpt.com/backend-api/codex/responses is rejected with
|
|
209
|
+
# ``context_length_exceeded`` while ~250K succeeds). With a 272K ceiling the
|
|
210
|
+
# default 50% compaction trigger fires at ~136K — wasteful, since the model
|
|
211
|
+
# can hold far more raw context before summarization actually buys anything.
|
|
212
|
+
# We raise the trigger to 85% (~231K) on this exact route so Codex gpt-5.5
|
|
213
|
+
# sessions use the window they actually have.
|
|
214
|
+
_CODEX_GPT55_COMPACTION_THRESHOLD = 0.85
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _is_codex_gpt55(model: Optional[str], provider: Optional[str] = None) -> bool:
|
|
218
|
+
"""True for gpt-5.5 accessed through the ChatGPT Codex OAuth backend.
|
|
219
|
+
|
|
220
|
+
Matches only the Codex OAuth route (provider ``openai-codex``), not the
|
|
221
|
+
direct OpenAI API, OpenRouter, or GitHub Copilot paths — those expose a
|
|
222
|
+
larger context window for the same slug and must keep the user's default
|
|
223
|
+
compaction threshold. ``gpt-5.5-pro`` and dated snapshots
|
|
224
|
+
(``gpt-5.5-2026-04-23``) are matched via prefix so the override tracks the
|
|
225
|
+
family without re-listing every variant.
|
|
226
|
+
"""
|
|
227
|
+
prov = (provider or "").strip().lower()
|
|
228
|
+
if prov != "openai-codex":
|
|
229
|
+
return False
|
|
230
|
+
bare = (model or "").strip().lower().rsplit("/", 1)[-1]
|
|
231
|
+
return bare == "gpt-5.5" or bare.startswith("gpt-5.5-") or bare.startswith("gpt-5.5.")
|
|
232
|
+
|
|
233
|
+
|
|
205
234
|
def _fixed_temperature_for_model(
|
|
206
235
|
model: Optional[str],
|
|
207
236
|
base_url: Optional[str] = None,
|
|
@@ -224,18 +253,32 @@ def _fixed_temperature_for_model(
|
|
|
224
253
|
return None
|
|
225
254
|
|
|
226
255
|
|
|
227
|
-
def _compression_threshold_for_model(
|
|
256
|
+
def _compression_threshold_for_model(
|
|
257
|
+
model: Optional[str],
|
|
258
|
+
provider: Optional[str] = None,
|
|
259
|
+
*,
|
|
260
|
+
allow_codex_gpt55_autoraise: bool = True,
|
|
261
|
+
) -> Optional[float]:
|
|
228
262
|
"""Return a context-compression threshold override for specific models.
|
|
229
263
|
|
|
230
264
|
The threshold is the fraction of the model's context window that must be
|
|
231
265
|
consumed before Hermes triggers summarization. Higher values delay
|
|
232
266
|
compression and preserve more raw context.
|
|
233
267
|
|
|
268
|
+
Per-model/route overrides:
|
|
269
|
+
- Arcee Trinity Large Thinking → 0.75 (preserve reasoning context).
|
|
270
|
+
- gpt-5.5 on the Codex OAuth route → 0.85, because Codex caps the window
|
|
271
|
+
at 272K and the default 50% trigger would compact at ~136K. Gated by
|
|
272
|
+
``allow_codex_gpt55_autoraise`` so the user can opt back down to the
|
|
273
|
+
global default (the caller passes the config flag through here).
|
|
274
|
+
|
|
234
275
|
Returns a float in (0, 1] to override the global ``compression.threshold``
|
|
235
276
|
config value, or ``None`` to leave the user's config value unchanged.
|
|
236
277
|
"""
|
|
237
278
|
if _is_arcee_trinity_thinking(model):
|
|
238
279
|
return 0.75
|
|
280
|
+
if allow_codex_gpt55_autoraise and _is_codex_gpt55(model, provider):
|
|
281
|
+
return _CODEX_GPT55_COMPACTION_THRESHOLD
|
|
239
282
|
return None
|
|
240
283
|
|
|
241
284
|
# Default auxiliary models for direct API-key providers (cheap/fast for side tasks)
|
|
@@ -265,9 +308,6 @@ _API_KEY_PROVIDER_AUX_MODELS_FALLBACK: Dict[str, str] = {
|
|
|
265
308
|
"stepfun": "step-3.5-flash",
|
|
266
309
|
"kimi-coding-cn": "kimi-k2-turbo-preview",
|
|
267
310
|
"gmi": "google/gemini-3.1-flash-lite-preview",
|
|
268
|
-
"minimax": "MiniMax-M2.7",
|
|
269
|
-
"minimax-oauth": "MiniMax-M2.7-highspeed",
|
|
270
|
-
"minimax-cn": "MiniMax-M2.7",
|
|
271
311
|
"anthropic": "claude-haiku-4-5-20251001",
|
|
272
312
|
"opencode-zen": "gemini-3-flash",
|
|
273
313
|
"opencode-go": "glm-5",
|
|
@@ -317,6 +357,35 @@ _OR_HEADERS_BASE = {
|
|
|
317
357
|
_TRUTHY_ENV_VALUES = frozenset({"1", "true", "yes", "on"})
|
|
318
358
|
|
|
319
359
|
|
|
360
|
+
def _apply_user_default_headers(headers: dict | None) -> dict | None:
|
|
361
|
+
"""Merge user-configured ``model.default_headers`` onto resolved headers.
|
|
362
|
+
|
|
363
|
+
User values take precedence over provider/SDK defaults, mirroring the main
|
|
364
|
+
agent client (``AIAgent._apply_user_default_headers``). This lets a
|
|
365
|
+
``custom`` OpenAI-compatible endpoint behind a gateway/WAF that rejects the
|
|
366
|
+
OpenAI SDK's identifying headers (``User-Agent: OpenAI/Python ...``,
|
|
367
|
+
``X-Stainless-*``) override them for auxiliary calls too — otherwise the
|
|
368
|
+
main turn would succeed but title/compression/vision calls to the same
|
|
369
|
+
endpoint would still fail. (#40033)
|
|
370
|
+
|
|
371
|
+
Returns the merged dict, or the original ``headers`` (possibly ``None``)
|
|
372
|
+
when nothing is configured. No allocation when there are no overrides.
|
|
373
|
+
"""
|
|
374
|
+
try:
|
|
375
|
+
from hermes_cli.config import cfg_get, load_config
|
|
376
|
+
user_headers = cfg_get(load_config(), "model", "default_headers")
|
|
377
|
+
except Exception:
|
|
378
|
+
return headers
|
|
379
|
+
if not isinstance(user_headers, dict) or not user_headers:
|
|
380
|
+
return headers
|
|
381
|
+
merged = dict(headers or {})
|
|
382
|
+
for key, value in user_headers.items():
|
|
383
|
+
if value is None:
|
|
384
|
+
continue
|
|
385
|
+
merged[str(key)] = str(value)
|
|
386
|
+
return merged or headers
|
|
387
|
+
|
|
388
|
+
|
|
320
389
|
def build_or_headers(or_config: dict | None = None) -> dict:
|
|
321
390
|
"""Build OpenRouter headers, optionally including response-cache headers.
|
|
322
391
|
|
|
@@ -568,54 +637,6 @@ def _pool_runtime_base_url(entry: Any, fallback: str = "") -> str:
|
|
|
568
637
|
# calls to the Codex Responses API so callers don't need any changes.
|
|
569
638
|
|
|
570
639
|
|
|
571
|
-
def _convert_content_for_responses(content: Any) -> Any:
|
|
572
|
-
"""Convert chat.completions content to Responses API format.
|
|
573
|
-
|
|
574
|
-
chat.completions uses:
|
|
575
|
-
{"type": "text", "text": "..."}
|
|
576
|
-
{"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}}
|
|
577
|
-
|
|
578
|
-
Responses API uses:
|
|
579
|
-
{"type": "input_text", "text": "..."}
|
|
580
|
-
{"type": "input_image", "image_url": "data:image/png;base64,..."}
|
|
581
|
-
|
|
582
|
-
If content is a plain string, it's returned as-is (the Responses API
|
|
583
|
-
accepts strings directly for text-only messages).
|
|
584
|
-
"""
|
|
585
|
-
if isinstance(content, str):
|
|
586
|
-
return content
|
|
587
|
-
if not isinstance(content, list):
|
|
588
|
-
return str(content) if content else ""
|
|
589
|
-
|
|
590
|
-
converted: List[Dict[str, Any]] = []
|
|
591
|
-
for part in content:
|
|
592
|
-
if not isinstance(part, dict):
|
|
593
|
-
continue
|
|
594
|
-
ptype = part.get("type", "")
|
|
595
|
-
if ptype == "text":
|
|
596
|
-
converted.append({"type": "input_text", "text": part.get("text", "")})
|
|
597
|
-
elif ptype == "image_url":
|
|
598
|
-
# chat.completions nests the URL: {"image_url": {"url": "..."}}
|
|
599
|
-
image_data = part.get("image_url", {})
|
|
600
|
-
url = image_data.get("url", "") if isinstance(image_data, dict) else str(image_data)
|
|
601
|
-
entry: Dict[str, Any] = {"type": "input_image", "image_url": url}
|
|
602
|
-
# Preserve detail if specified
|
|
603
|
-
detail = image_data.get("detail") if isinstance(image_data, dict) else None
|
|
604
|
-
if detail:
|
|
605
|
-
entry["detail"] = detail
|
|
606
|
-
converted.append(entry)
|
|
607
|
-
elif ptype in {"input_text", "input_image"}:
|
|
608
|
-
# Already in Responses format — pass through
|
|
609
|
-
converted.append(part)
|
|
610
|
-
else:
|
|
611
|
-
# Unknown content type — try to preserve as text
|
|
612
|
-
text = part.get("text", "")
|
|
613
|
-
if text:
|
|
614
|
-
converted.append({"type": "input_text", "text": text})
|
|
615
|
-
|
|
616
|
-
return converted or ""
|
|
617
|
-
|
|
618
|
-
|
|
619
640
|
class _CodexCompletionsAdapter:
|
|
620
641
|
"""Drop-in shim that accepts chat.completions.create() kwargs and
|
|
621
642
|
routes them through the Codex Responses streaming API."""
|
|
@@ -628,26 +649,37 @@ class _CodexCompletionsAdapter:
|
|
|
628
649
|
messages = kwargs.get("messages", [])
|
|
629
650
|
model = kwargs.get("model", self._model)
|
|
630
651
|
|
|
631
|
-
# Separate system/instructions from conversation messages
|
|
632
|
-
#
|
|
633
|
-
#
|
|
652
|
+
# Separate system/instructions from replayable conversation messages,
|
|
653
|
+
# then route the rest through the SINGLE shared chat->Responses
|
|
654
|
+
# converter used by the main agent transport
|
|
655
|
+
# (agent/transports/codex.py). Maintaining a private conversion loop
|
|
656
|
+
# here let chat-style messages with role="tool" leak straight into
|
|
657
|
+
# Responses input[] — which the Responses API rejects with
|
|
658
|
+
# "Invalid value: 'tool'. Supported values are: 'assistant', 'system',
|
|
659
|
+
# 'developer', and 'user'." (issue #5709, hit hard by flush_memories()
|
|
660
|
+
# / compression replaying real session history that includes assistant
|
|
661
|
+
# tool_calls + role="tool" results). The shared converter encodes
|
|
662
|
+
# assistant tool calls as `function_call` items and tool results as
|
|
663
|
+
# `function_call_output` items with a valid call_id, so every
|
|
664
|
+
# Responses path normalizes tool history identically and cannot drift.
|
|
665
|
+
from agent.codex_responses_adapter import _chat_messages_to_responses_input
|
|
666
|
+
|
|
634
667
|
instructions = "You are a helpful assistant."
|
|
635
|
-
|
|
668
|
+
replay_messages: List[Dict[str, Any]] = []
|
|
636
669
|
for msg in messages:
|
|
637
670
|
role = msg.get("role", "user")
|
|
638
671
|
content = msg.get("content") or ""
|
|
639
672
|
if role == "system":
|
|
640
673
|
instructions = content if isinstance(content, str) else str(content)
|
|
641
674
|
else:
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
})
|
|
675
|
+
replay_messages.append(msg)
|
|
676
|
+
|
|
677
|
+
input_items = _chat_messages_to_responses_input(replay_messages)
|
|
646
678
|
|
|
647
679
|
resp_kwargs: Dict[str, Any] = {
|
|
648
680
|
"model": model,
|
|
649
681
|
"instructions": instructions,
|
|
650
|
-
"input":
|
|
682
|
+
"input": input_items or [{"role": "user", "content": ""}],
|
|
651
683
|
"store": False,
|
|
652
684
|
}
|
|
653
685
|
|
|
@@ -965,7 +997,7 @@ class _AnthropicCompletionsAdapter:
|
|
|
965
997
|
self._is_oauth = is_oauth
|
|
966
998
|
|
|
967
999
|
def create(self, **kwargs) -> Any:
|
|
968
|
-
from agent.anthropic_adapter import build_anthropic_kwargs
|
|
1000
|
+
from agent.anthropic_adapter import build_anthropic_kwargs, create_anthropic_message
|
|
969
1001
|
from agent.transports import get_transport
|
|
970
1002
|
|
|
971
1003
|
messages = kwargs.get("messages", [])
|
|
@@ -1009,7 +1041,7 @@ class _AnthropicCompletionsAdapter:
|
|
|
1009
1041
|
if not _forbids_sampling_params(model):
|
|
1010
1042
|
anthropic_kwargs["temperature"] = temperature
|
|
1011
1043
|
|
|
1012
|
-
response = self._client
|
|
1044
|
+
response = create_anthropic_message(self._client, anthropic_kwargs)
|
|
1013
1045
|
_transport = get_transport("anthropic_messages")
|
|
1014
1046
|
_nr = _transport.normalize_response(
|
|
1015
1047
|
response, strip_tool_prefix=self._is_oauth
|
|
@@ -1112,7 +1144,8 @@ def _endpoint_speaks_anthropic_messages(base_url: str) -> bool:
|
|
|
1112
1144
|
normalized = (base_url or "").strip().lower().rstrip("/")
|
|
1113
1145
|
if not normalized:
|
|
1114
1146
|
return False
|
|
1115
|
-
|
|
1147
|
+
path = urlparse(normalized).path.rstrip("/")
|
|
1148
|
+
if path.endswith("/anthropic") or path.endswith("/anthropic/v1"):
|
|
1116
1149
|
return True
|
|
1117
1150
|
hostname = base_url_hostname(normalized)
|
|
1118
1151
|
if hostname == "api.anthropic.com":
|
|
@@ -1455,6 +1488,9 @@ def _resolve_api_key_provider() -> Tuple[Optional[OpenAI], Optional[str]]:
|
|
|
1455
1488
|
extra["default_headers"] = dict(_ph_aux.default_headers)
|
|
1456
1489
|
except Exception:
|
|
1457
1490
|
pass
|
|
1491
|
+
_merged_aux = _apply_user_default_headers(extra.get("default_headers"))
|
|
1492
|
+
if _merged_aux:
|
|
1493
|
+
extra["default_headers"] = _merged_aux
|
|
1458
1494
|
_client = OpenAI(api_key=api_key, base_url=base_url, **extra)
|
|
1459
1495
|
_client = _maybe_wrap_anthropic(_client, model, api_key, raw_base_url)
|
|
1460
1496
|
return _client, model
|
|
@@ -1492,6 +1528,9 @@ def _resolve_api_key_provider() -> Tuple[Optional[OpenAI], Optional[str]]:
|
|
|
1492
1528
|
extra["default_headers"] = dict(_ph_aux2.default_headers)
|
|
1493
1529
|
except Exception:
|
|
1494
1530
|
pass
|
|
1531
|
+
_merged_aux2 = _apply_user_default_headers(extra.get("default_headers"))
|
|
1532
|
+
if _merged_aux2:
|
|
1533
|
+
extra["default_headers"] = _merged_aux2
|
|
1495
1534
|
_client = OpenAI(api_key=api_key, base_url=base_url, **extra)
|
|
1496
1535
|
_client = _maybe_wrap_anthropic(_client, model, api_key, raw_base_url)
|
|
1497
1536
|
return _client, model
|
|
@@ -1621,6 +1660,47 @@ def _try_nous(vision: bool = False) -> Tuple[Optional[OpenAI], Optional[str]]:
|
|
|
1621
1660
|
)
|
|
1622
1661
|
|
|
1623
1662
|
|
|
1663
|
+
def _refresh_nous_recommended_model(
|
|
1664
|
+
*, vision: bool, stale_model: Optional[str]
|
|
1665
|
+
) -> Optional[str]:
|
|
1666
|
+
"""Re-fetch the Nous Portal's recommended model after a stale-model 404.
|
|
1667
|
+
|
|
1668
|
+
Long-lived processes (gateway, watchers) cache the Portal's
|
|
1669
|
+
``recommended-models`` payload for 10 minutes and, in practice, can pin a
|
|
1670
|
+
model for the whole process lifetime. When that model is later dropped from
|
|
1671
|
+
the Nous → OpenRouter catalog, every auxiliary call 404s with
|
|
1672
|
+
"model does not exist". This forces a fresh Portal fetch and returns a
|
|
1673
|
+
model name to retry with:
|
|
1674
|
+
|
|
1675
|
+
* the Portal's current recommendation for the task, if it differs from
|
|
1676
|
+
the model that just failed; otherwise
|
|
1677
|
+
* ``_NOUS_MODEL`` (google/gemini-3-flash-preview), the known-good default,
|
|
1678
|
+
if it too differs from the failed model.
|
|
1679
|
+
|
|
1680
|
+
Returns ``None`` when no usable alternative is available (e.g. the Portal
|
|
1681
|
+
still recommends the exact model that just 404'd and the default also
|
|
1682
|
+
matches it) — callers should then let the original error propagate.
|
|
1683
|
+
"""
|
|
1684
|
+
stale = (stale_model or "").strip().lower()
|
|
1685
|
+
fresh: Optional[str] = None
|
|
1686
|
+
try:
|
|
1687
|
+
from hermes_cli.models import get_nous_recommended_aux_model
|
|
1688
|
+
|
|
1689
|
+
fresh = get_nous_recommended_aux_model(vision=vision, force_refresh=True)
|
|
1690
|
+
except Exception as exc:
|
|
1691
|
+
logger.debug(
|
|
1692
|
+
"Nous recommended-model refresh failed (%s); using default %s",
|
|
1693
|
+
exc, _NOUS_MODEL,
|
|
1694
|
+
)
|
|
1695
|
+
if fresh and fresh.strip().lower() != stale:
|
|
1696
|
+
return fresh
|
|
1697
|
+
# Portal recommendation unchanged or unavailable — fall back to the
|
|
1698
|
+
# hardcoded known-good default, but only if it's actually different.
|
|
1699
|
+
if _NOUS_MODEL.strip().lower() != stale:
|
|
1700
|
+
return _NOUS_MODEL
|
|
1701
|
+
return None
|
|
1702
|
+
|
|
1703
|
+
|
|
1624
1704
|
def _read_main_model() -> str:
|
|
1625
1705
|
"""Read the user's configured main model from config.yaml.
|
|
1626
1706
|
|
|
@@ -1841,6 +1921,13 @@ def _try_custom_endpoint() -> Tuple[Optional[Any], Optional[str]]:
|
|
|
1841
1921
|
logger.debug("Auxiliary client: custom endpoint (%s, api_mode=%s)", model, custom_mode or "chat_completions")
|
|
1842
1922
|
_clean_base, _dq = _extract_url_query_params(custom_base)
|
|
1843
1923
|
_extra = {"default_query": _dq} if _dq else {}
|
|
1924
|
+
# User-configured model.default_headers override the SDK's identifying
|
|
1925
|
+
# headers (User-Agent: OpenAI/Python ..., X-Stainless-*) on this custom
|
|
1926
|
+
# endpoint's auxiliary calls too — matching the main agent client so the
|
|
1927
|
+
# whole session reaches a gateway/WAF that rejects the SDK fingerprint. (#40033)
|
|
1928
|
+
_custom_headers = _apply_user_default_headers(None)
|
|
1929
|
+
if _custom_headers:
|
|
1930
|
+
_extra["default_headers"] = _custom_headers
|
|
1844
1931
|
if custom_mode == "codex_responses":
|
|
1845
1932
|
real_client = OpenAI(api_key=custom_key, base_url=_clean_base, **_extra)
|
|
1846
1933
|
return CodexAuxiliaryClient(real_client, model), model
|
|
@@ -2390,6 +2477,25 @@ def _is_connection_error(exc: Exception) -> bool:
|
|
|
2390
2477
|
return False
|
|
2391
2478
|
|
|
2392
2479
|
|
|
2480
|
+
def _is_transient_transport_error(exc: Exception) -> bool:
|
|
2481
|
+
"""Return True for a one-off transport blip worth retrying ONCE on the
|
|
2482
|
+
same provider before any provider/model fallback.
|
|
2483
|
+
|
|
2484
|
+
Covers connection/streaming-close errors (via the canonical
|
|
2485
|
+
``_is_connection_error`` detector, shared so the two cannot drift) plus a
|
|
2486
|
+
pure 5xx/408 HTTP status. Deliberately narrow: this is the "retry the
|
|
2487
|
+
same target once" gate, distinct from ``_is_payment_error`` /
|
|
2488
|
+
``_is_auth_error`` / ``_is_rate_limit_error`` which the except-chain
|
|
2489
|
+
handles by switching provider, refreshing creds, or rotating the pool.
|
|
2490
|
+
"""
|
|
2491
|
+
if _is_connection_error(exc):
|
|
2492
|
+
return True
|
|
2493
|
+
status = getattr(exc, "status_code", None) or getattr(
|
|
2494
|
+
getattr(exc, "response", None), "status_code", None
|
|
2495
|
+
)
|
|
2496
|
+
return isinstance(status, int) and (status == 408 or 500 <= status < 600)
|
|
2497
|
+
|
|
2498
|
+
|
|
2393
2499
|
def _is_auth_error(exc: Exception) -> bool:
|
|
2394
2500
|
"""Detect auth failures that should trigger provider-specific refresh."""
|
|
2395
2501
|
status = getattr(exc, "status_code", None)
|
|
@@ -2451,6 +2557,46 @@ def _is_unsupported_temperature_error(exc: Exception) -> bool:
|
|
|
2451
2557
|
return _is_unsupported_parameter_error(exc, "temperature")
|
|
2452
2558
|
|
|
2453
2559
|
|
|
2560
|
+
def _is_model_not_found_error(exc: Exception) -> bool:
|
|
2561
|
+
"""Detect "the requested model doesn't exist" errors (404 / invalid model).
|
|
2562
|
+
|
|
2563
|
+
This fires when a resolved model name is no longer served by the endpoint
|
|
2564
|
+
— most commonly when a long-lived process pinned a Portal-recommended model
|
|
2565
|
+
that has since been dropped from the Nous → OpenRouter catalog. The Nous
|
|
2566
|
+
proxy returns 404 with a body like::
|
|
2567
|
+
|
|
2568
|
+
Model 'gpt-5.4-mini' not found. The requested model does not exist
|
|
2569
|
+
in our configuration or OpenRouter catalog.
|
|
2570
|
+
|
|
2571
|
+
Distinct from :func:`_is_payment_error` (which also matches some 404s for
|
|
2572
|
+
free-tier/credit language) — this one keys on "does not exist / not found /
|
|
2573
|
+
not a valid model" phrasing, and explicitly excludes the billing keywords
|
|
2574
|
+
that the payment path already owns so the two predicates don't overlap.
|
|
2575
|
+
"""
|
|
2576
|
+
status = getattr(exc, "status_code", None)
|
|
2577
|
+
err_lower = str(exc).lower()
|
|
2578
|
+
# Billing/quota 404s belong to _is_payment_error — don't claim them here.
|
|
2579
|
+
if any(kw in err_lower for kw in (
|
|
2580
|
+
"credits", "insufficient funds", "billing", "out of funds",
|
|
2581
|
+
"balance_depleted", "no usable credits", "free tier", "free-tier",
|
|
2582
|
+
"not available on the free tier",
|
|
2583
|
+
)):
|
|
2584
|
+
return False
|
|
2585
|
+
if status not in {404, 400, None}:
|
|
2586
|
+
return False
|
|
2587
|
+
return any(kw in err_lower for kw in (
|
|
2588
|
+
"model does not exist",
|
|
2589
|
+
"does not exist in our configuration",
|
|
2590
|
+
"openrouter catalog",
|
|
2591
|
+
"is not a valid model",
|
|
2592
|
+
"no such model",
|
|
2593
|
+
"model not found",
|
|
2594
|
+
"the model `", # OpenAI-style: "The model `X` does not exist"
|
|
2595
|
+
"model_not_found",
|
|
2596
|
+
"unknown model",
|
|
2597
|
+
))
|
|
2598
|
+
|
|
2599
|
+
|
|
2454
2600
|
def _evict_cached_clients(provider: str) -> None:
|
|
2455
2601
|
"""Drop cached auxiliary clients for a provider so fresh creds are used."""
|
|
2456
2602
|
normalized = _normalize_aux_provider(provider)
|
|
@@ -2933,23 +3079,20 @@ def _try_configured_fallback_chain(
|
|
|
2933
3079
|
if not fb_provider or fb_provider.lower() == skip:
|
|
2934
3080
|
continue
|
|
2935
3081
|
fb_model = str(entry.get("model", "")).strip() or None
|
|
2936
|
-
fb_base_url = str(entry.get("base_url", "")).strip() or None
|
|
2937
|
-
fb_api_key = str(entry.get("api_key", "")).strip() or None
|
|
2938
3082
|
|
|
2939
3083
|
label = f"fallback_chain[{i}]({fb_provider})"
|
|
2940
3084
|
|
|
2941
3085
|
try:
|
|
2942
|
-
fb_client =
|
|
2943
|
-
fb_provider, fb_model, fb_base_url, fb_api_key)
|
|
3086
|
+
fb_client, resolved_model = _resolve_fallback_entry(entry)
|
|
2944
3087
|
except Exception:
|
|
2945
|
-
fb_client = None
|
|
3088
|
+
fb_client, resolved_model = None, None
|
|
2946
3089
|
|
|
2947
3090
|
if fb_client is not None:
|
|
2948
3091
|
logger.info(
|
|
2949
3092
|
"Auxiliary %s: %s on %s — configured fallback to %s (%s)",
|
|
2950
|
-
task, reason, failed_provider, label, fb_model or "default",
|
|
3093
|
+
task, reason, failed_provider, label, resolved_model or fb_model or "default",
|
|
2951
3094
|
)
|
|
2952
|
-
return fb_client, fb_model, label
|
|
3095
|
+
return fb_client, resolved_model or fb_model, label
|
|
2953
3096
|
tried.append(label)
|
|
2954
3097
|
|
|
2955
3098
|
if tried:
|
|
@@ -2960,6 +3103,103 @@ def _try_configured_fallback_chain(
|
|
|
2960
3103
|
return None, None, ""
|
|
2961
3104
|
|
|
2962
3105
|
|
|
3106
|
+
def _fallback_entry_api_key(entry: Dict[str, Any]) -> Optional[str]:
|
|
3107
|
+
"""Resolve inline or env-backed API key from a fallback-chain entry."""
|
|
3108
|
+
explicit = str(entry.get("api_key") or "").strip()
|
|
3109
|
+
if explicit:
|
|
3110
|
+
return explicit
|
|
3111
|
+
key_env = str(entry.get("key_env") or entry.get("api_key_env") or "").strip()
|
|
3112
|
+
if key_env:
|
|
3113
|
+
return os.getenv(key_env, "").strip() or None
|
|
3114
|
+
return None
|
|
3115
|
+
|
|
3116
|
+
|
|
3117
|
+
def _resolve_fallback_entry(entry: Dict[str, Any]) -> Tuple[Optional[Any], Optional[str]]:
|
|
3118
|
+
"""Resolve one fallback entry through the central provider router."""
|
|
3119
|
+
provider = str(entry.get("provider") or "").strip()
|
|
3120
|
+
model = str(entry.get("model") or "").strip() or None
|
|
3121
|
+
if not provider or not model:
|
|
3122
|
+
return None, None
|
|
3123
|
+
base_url = str(entry.get("base_url") or "").strip() or None
|
|
3124
|
+
api_key = _fallback_entry_api_key(entry)
|
|
3125
|
+
api_mode = str(entry.get("api_mode") or entry.get("transport") or "").strip() or None
|
|
3126
|
+
return resolve_provider_client(
|
|
3127
|
+
provider,
|
|
3128
|
+
model=model,
|
|
3129
|
+
explicit_base_url=base_url,
|
|
3130
|
+
explicit_api_key=api_key,
|
|
3131
|
+
api_mode=api_mode,
|
|
3132
|
+
)
|
|
3133
|
+
|
|
3134
|
+
|
|
3135
|
+
def _try_main_fallback_chain(
|
|
3136
|
+
task: Optional[str],
|
|
3137
|
+
failed_provider: str = "",
|
|
3138
|
+
reason: str = "error",
|
|
3139
|
+
) -> Tuple[Optional[Any], Optional[str], str]:
|
|
3140
|
+
"""Try the top-level main-agent fallback chain for an auxiliary call.
|
|
3141
|
+
|
|
3142
|
+
``provider: auto`` auxiliary tasks should respect the user's declared
|
|
3143
|
+
main fallback policy before dropping into Hermes' built-in discovery
|
|
3144
|
+
chain. The top-level chain is read through ``get_fallback_chain`` so
|
|
3145
|
+
both modern ``fallback_providers`` and legacy ``fallback_model`` entries
|
|
3146
|
+
participate in the same order as the main agent.
|
|
3147
|
+
"""
|
|
3148
|
+
try:
|
|
3149
|
+
from hermes_cli.config import load_config
|
|
3150
|
+
from hermes_cli.fallback_config import get_fallback_chain
|
|
3151
|
+
|
|
3152
|
+
chain = get_fallback_chain(load_config())
|
|
3153
|
+
except Exception as exc:
|
|
3154
|
+
logger.debug("Auxiliary %s: could not load main fallback chain: %s", task or "call", exc)
|
|
3155
|
+
return None, None, ""
|
|
3156
|
+
|
|
3157
|
+
if not chain:
|
|
3158
|
+
return None, None, ""
|
|
3159
|
+
|
|
3160
|
+
failed_norm = (failed_provider or "").strip().lower()
|
|
3161
|
+
main_norm = (_read_main_provider() or "").strip().lower()
|
|
3162
|
+
skip = {p for p in (failed_norm, main_norm, "auto") if p}
|
|
3163
|
+
tried: List[str] = []
|
|
3164
|
+
|
|
3165
|
+
for i, entry in enumerate(chain):
|
|
3166
|
+
if not isinstance(entry, dict):
|
|
3167
|
+
continue
|
|
3168
|
+
fb_provider = str(entry.get("provider") or "").strip()
|
|
3169
|
+
fb_model = str(entry.get("model") or "").strip()
|
|
3170
|
+
if not fb_provider or not fb_model:
|
|
3171
|
+
continue
|
|
3172
|
+
fb_norm = fb_provider.lower()
|
|
3173
|
+
label = f"fallback_providers[{i}]({fb_provider})"
|
|
3174
|
+
if fb_norm in skip:
|
|
3175
|
+
tried.append(f"{label} (skipped)")
|
|
3176
|
+
continue
|
|
3177
|
+
if _is_provider_unhealthy(fb_norm):
|
|
3178
|
+
_log_skip_unhealthy(fb_norm, task)
|
|
3179
|
+
tried.append(f"{label} (unhealthy)")
|
|
3180
|
+
continue
|
|
3181
|
+
try:
|
|
3182
|
+
fb_client, resolved_model = _resolve_fallback_entry(entry)
|
|
3183
|
+
except Exception as exc:
|
|
3184
|
+
logger.debug("Auxiliary %s: main fallback %s failed to resolve: %s", task or "call", label, exc)
|
|
3185
|
+
fb_client, resolved_model = None, None
|
|
3186
|
+
if fb_client is not None:
|
|
3187
|
+
logger.info(
|
|
3188
|
+
"Auxiliary %s: %s on %s — main fallback chain to %s (%s)",
|
|
3189
|
+
task or "call", reason, failed_provider or "auto", label,
|
|
3190
|
+
resolved_model or fb_model,
|
|
3191
|
+
)
|
|
3192
|
+
return fb_client, resolved_model or fb_model, fb_provider
|
|
3193
|
+
tried.append(label)
|
|
3194
|
+
|
|
3195
|
+
if tried:
|
|
3196
|
+
logger.debug(
|
|
3197
|
+
"Auxiliary %s: main fallback chain exhausted (tried: %s)",
|
|
3198
|
+
task or "call", ", ".join(tried),
|
|
3199
|
+
)
|
|
3200
|
+
return None, None, ""
|
|
3201
|
+
|
|
3202
|
+
|
|
2963
3203
|
def _resolve_single_provider(
|
|
2964
3204
|
provider: str,
|
|
2965
3205
|
model: Optional[str] = None,
|
|
@@ -2970,16 +3210,19 @@ def _resolve_single_provider(
|
|
|
2970
3210
|
|
|
2971
3211
|
Uses the existing provider resolution infrastructure where possible.
|
|
2972
3212
|
"""
|
|
2973
|
-
# Reuse resolve_provider_client which handles provider→client mapping
|
|
3213
|
+
# Reuse resolve_provider_client which handles provider→client mapping.
|
|
2974
3214
|
client, resolved_model = resolve_provider_client(
|
|
2975
3215
|
provider=provider,
|
|
2976
3216
|
model=model,
|
|
2977
|
-
|
|
2978
|
-
|
|
3217
|
+
explicit_base_url=base_url,
|
|
3218
|
+
explicit_api_key=api_key,
|
|
2979
3219
|
)
|
|
2980
3220
|
return client
|
|
2981
3221
|
|
|
2982
|
-
def _resolve_auto(
|
|
3222
|
+
def _resolve_auto(
|
|
3223
|
+
main_runtime: Optional[Dict[str, Any]] = None,
|
|
3224
|
+
task: Optional[str] = None,
|
|
3225
|
+
) -> Tuple[Optional[OpenAI], Optional[str]]:
|
|
2983
3226
|
"""Full auto-detection chain.
|
|
2984
3227
|
|
|
2985
3228
|
Priority:
|
|
@@ -3045,7 +3288,7 @@ def _resolve_auto(main_runtime: Optional[Dict[str, Any]] = None) -> Tuple[Option
|
|
|
3045
3288
|
if (main_provider and main_model
|
|
3046
3289
|
and main_provider not in {"auto", ""}):
|
|
3047
3290
|
resolved_provider = main_provider
|
|
3048
|
-
explicit_base_url = None
|
|
3291
|
+
explicit_base_url = runtime_base_url or None
|
|
3049
3292
|
explicit_api_key = None
|
|
3050
3293
|
if runtime_base_url and (main_provider == "custom" or main_provider.startswith("custom:")):
|
|
3051
3294
|
resolved_provider = "custom"
|
|
@@ -3077,7 +3320,22 @@ def _resolve_auto(main_runtime: Optional[Dict[str, Any]] = None) -> Tuple[Option
|
|
|
3077
3320
|
main_provider, resolved or main_model)
|
|
3078
3321
|
return client, resolved or main_model
|
|
3079
3322
|
|
|
3080
|
-
# ── Step 2:
|
|
3323
|
+
# ── Step 2: user-configured fallback policy ─────────────────────────
|
|
3324
|
+
# In auto mode, respect the task-specific fallback chain first, then the
|
|
3325
|
+
# main agent's top-level fallback_providers/fallback_model chain. The
|
|
3326
|
+
# hardcoded provider discovery chain below is only the convenience default
|
|
3327
|
+
# for users who have not declared a fallback policy.
|
|
3328
|
+
if task:
|
|
3329
|
+
fb_client, fb_model, _fb_label = _try_configured_fallback_chain(
|
|
3330
|
+
task, main_provider or "auto", reason="main provider unavailable")
|
|
3331
|
+
if fb_client is not None:
|
|
3332
|
+
return fb_client, fb_model
|
|
3333
|
+
fb_client, fb_model, _fb_label = _try_main_fallback_chain(
|
|
3334
|
+
task, main_provider or "auto", reason="main provider unavailable")
|
|
3335
|
+
if fb_client is not None:
|
|
3336
|
+
return fb_client, fb_model
|
|
3337
|
+
|
|
3338
|
+
# ── Step 3: aggregator / fallback chain ──────────────────────────────
|
|
3081
3339
|
tried = []
|
|
3082
3340
|
for label, try_fn in _get_provider_chain():
|
|
3083
3341
|
if _is_provider_unhealthy(label):
|
|
@@ -3170,6 +3428,9 @@ def _to_async_client(sync_client, model: str, is_vision: bool = False):
|
|
|
3170
3428
|
async_kwargs["default_headers"] = dict(_ph_async.default_headers)
|
|
3171
3429
|
except Exception:
|
|
3172
3430
|
pass
|
|
3431
|
+
_merged_async = _apply_user_default_headers(async_kwargs.get("default_headers"))
|
|
3432
|
+
if _merged_async:
|
|
3433
|
+
async_kwargs["default_headers"] = _merged_async
|
|
3173
3434
|
return AsyncOpenAI(**async_kwargs), model
|
|
3174
3435
|
|
|
3175
3436
|
|
|
@@ -3195,6 +3456,7 @@ def resolve_provider_client(
|
|
|
3195
3456
|
api_mode: str = None,
|
|
3196
3457
|
main_runtime: Optional[Dict[str, Any]] = None,
|
|
3197
3458
|
is_vision: bool = False,
|
|
3459
|
+
task: Optional[str] = None,
|
|
3198
3460
|
) -> Tuple[Optional[Any], Optional[str]]:
|
|
3199
3461
|
"""Central router: given a provider name and optional model, return a
|
|
3200
3462
|
configured client with the correct auth, base URL, and API format.
|
|
@@ -3315,7 +3577,7 @@ def resolve_provider_client(
|
|
|
3315
3577
|
|
|
3316
3578
|
# ── Auto: try all providers in priority order ────────────────────
|
|
3317
3579
|
if provider == "auto":
|
|
3318
|
-
client, resolved = _resolve_auto(main_runtime=main_runtime)
|
|
3580
|
+
client, resolved = _resolve_auto(main_runtime=main_runtime, task=task)
|
|
3319
3581
|
if client is None:
|
|
3320
3582
|
return None, None
|
|
3321
3583
|
# When auto-detection lands on a non-OpenRouter provider (e.g. a
|
|
@@ -3457,6 +3719,9 @@ def resolve_provider_client(
|
|
|
3457
3719
|
extra["default_headers"] = dict(_ph_custom.default_headers)
|
|
3458
3720
|
except Exception:
|
|
3459
3721
|
pass
|
|
3722
|
+
_merged_custom = _apply_user_default_headers(extra.get("default_headers"))
|
|
3723
|
+
if _merged_custom:
|
|
3724
|
+
extra["default_headers"] = _merged_custom
|
|
3460
3725
|
client = OpenAI(api_key=custom_key, base_url=_clean_base, **extra)
|
|
3461
3726
|
client = _wrap_if_needed(client, final_model, custom_base, custom_key)
|
|
3462
3727
|
return (_to_async_client(client, final_model, is_vision=is_vision) if async_mode
|
|
@@ -3533,6 +3798,9 @@ def resolve_provider_client(
|
|
|
3533
3798
|
raw_base_for_wrap = custom_base
|
|
3534
3799
|
_clean_base2, _dq2 = _extract_url_query_params(openai_base)
|
|
3535
3800
|
_extra2 = {"default_query": _dq2} if _dq2 else {}
|
|
3801
|
+
_headers2 = _apply_user_default_headers(_extra2.get("default_headers"))
|
|
3802
|
+
if _headers2:
|
|
3803
|
+
_extra2["default_headers"] = _headers2
|
|
3536
3804
|
logger.debug(
|
|
3537
3805
|
"resolve_provider_client: named custom provider %r (%s, api_mode=%s)",
|
|
3538
3806
|
provider, final_model, entry_api_mode or "chat_completions")
|
|
@@ -3555,6 +3823,9 @@ def resolve_provider_client(
|
|
|
3555
3823
|
_fallback_base = _to_openai_base_url(custom_base)
|
|
3556
3824
|
_fb_clean, _fb_dq = _extract_url_query_params(_fallback_base)
|
|
3557
3825
|
_fb_extra = {"default_query": _fb_dq} if _fb_dq else {}
|
|
3826
|
+
_fb_headers = _apply_user_default_headers(_fb_extra.get("default_headers"))
|
|
3827
|
+
if _fb_headers:
|
|
3828
|
+
_fb_extra["default_headers"] = _fb_headers
|
|
3558
3829
|
client = OpenAI(api_key=custom_key, base_url=_fb_clean, **_fb_extra)
|
|
3559
3830
|
return (_to_async_client(client, final_model, is_vision=is_vision) if async_mode
|
|
3560
3831
|
else (client, final_model))
|
|
@@ -3703,6 +3974,9 @@ def resolve_provider_client(
|
|
|
3703
3974
|
headers.update(_ph_main.default_headers)
|
|
3704
3975
|
except Exception:
|
|
3705
3976
|
pass
|
|
3977
|
+
_merged_main = _apply_user_default_headers(headers)
|
|
3978
|
+
if _merged_main:
|
|
3979
|
+
headers = _merged_main
|
|
3706
3980
|
client = OpenAI(api_key=api_key, base_url=base_url,
|
|
3707
3981
|
**({"default_headers": headers} if headers else {}))
|
|
3708
3982
|
|
|
@@ -4140,13 +4414,15 @@ def get_auxiliary_extra_body() -> dict:
|
|
|
4140
4414
|
return _nous_extra_body() if auxiliary_is_nous else {}
|
|
4141
4415
|
|
|
4142
4416
|
|
|
4143
|
-
def auxiliary_max_tokens_param(value: int) -> dict:
|
|
4417
|
+
def auxiliary_max_tokens_param(value: int, *, model: Optional[str] = None) -> dict:
|
|
4144
4418
|
"""Return the correct max tokens kwarg for the auxiliary client's provider.
|
|
4145
|
-
|
|
4419
|
+
|
|
4146
4420
|
OpenRouter and local models use 'max_tokens'. Direct OpenAI with newer
|
|
4147
|
-
models (gpt-4o,
|
|
4421
|
+
models (gpt-4o, gpt-4.1, gpt-5+, o-series) requires 'max_completion_tokens'.
|
|
4148
4422
|
The Codex adapter translates max_tokens internally, so we use max_tokens
|
|
4149
|
-
for it as well.
|
|
4423
|
+
for it as well. Pass ``model`` so third-party OpenAI-compatible endpoints
|
|
4424
|
+
fronting the newer families are also recognised — URL-only detection
|
|
4425
|
+
misses the case where a custom base URL serves e.g. ``gpt-5.4``.
|
|
4150
4426
|
"""
|
|
4151
4427
|
custom_base = _current_custom_base_url()
|
|
4152
4428
|
or_key = os.getenv("OPENROUTER_API_KEY")
|
|
@@ -4156,6 +4432,9 @@ def auxiliary_max_tokens_param(value: int) -> dict:
|
|
|
4156
4432
|
and _read_nous_auth() is None
|
|
4157
4433
|
and base_url_hostname(custom_base) in {"api.openai.com", "api.githubcopilot.com"}):
|
|
4158
4434
|
return {"max_completion_tokens": value}
|
|
4435
|
+
# ...and for any caller serving a newer OpenAI-family model by name.
|
|
4436
|
+
if model_forces_max_completion_tokens(model):
|
|
4437
|
+
return {"max_completion_tokens": value}
|
|
4159
4438
|
return {"max_tokens": value}
|
|
4160
4439
|
|
|
4161
4440
|
|
|
@@ -4191,11 +4470,16 @@ def _client_cache_key(
|
|
|
4191
4470
|
api_mode: Optional[str] = None,
|
|
4192
4471
|
main_runtime: Optional[Dict[str, Any]] = None,
|
|
4193
4472
|
is_vision: bool = False,
|
|
4473
|
+
task: Optional[str] = None,
|
|
4194
4474
|
) -> tuple:
|
|
4195
4475
|
runtime = _normalize_main_runtime(main_runtime)
|
|
4196
4476
|
runtime_key = tuple(runtime.get(field, "") for field in _MAIN_RUNTIME_FIELDS) if provider == "auto" else ()
|
|
4477
|
+
# `auto` can now resolve through task-specific or main fallback policy,
|
|
4478
|
+
# so the task participates in the cache key. Non-auto providers keep the
|
|
4479
|
+
# old cache shape because the explicit provider/model tuple is sufficient.
|
|
4480
|
+
task_key = (task or "") if provider == "auto" else ""
|
|
4197
4481
|
pool_hint = _pool_cache_hint(provider, main_runtime=main_runtime)
|
|
4198
|
-
return (provider, async_mode, base_url or "", api_key or "", api_mode or "", runtime_key, is_vision, pool_hint)
|
|
4482
|
+
return (provider, async_mode, base_url or "", api_key or "", api_mode or "", runtime_key, is_vision, task_key, pool_hint)
|
|
4199
4483
|
|
|
4200
4484
|
|
|
4201
4485
|
def _store_cached_client(cache_key: tuple, client: Any, default_model: Optional[str], *, bound_loop: Any = None) -> None:
|
|
@@ -4388,6 +4672,7 @@ def _get_cached_client(
|
|
|
4388
4672
|
api_mode: str = None,
|
|
4389
4673
|
main_runtime: Optional[Dict[str, Any]] = None,
|
|
4390
4674
|
is_vision: bool = False,
|
|
4675
|
+
task: Optional[str] = None,
|
|
4391
4676
|
) -> Tuple[Optional[Any], Optional[str]]:
|
|
4392
4677
|
"""Get or create a cached client for the given provider.
|
|
4393
4678
|
|
|
@@ -4425,6 +4710,7 @@ def _get_cached_client(
|
|
|
4425
4710
|
api_mode=api_mode,
|
|
4426
4711
|
main_runtime=main_runtime,
|
|
4427
4712
|
is_vision=is_vision,
|
|
4713
|
+
task=task,
|
|
4428
4714
|
)
|
|
4429
4715
|
with _client_cache_lock:
|
|
4430
4716
|
if cache_key in _client_cache:
|
|
@@ -4469,6 +4755,7 @@ def _get_cached_client(
|
|
|
4469
4755
|
api_mode=api_mode,
|
|
4470
4756
|
main_runtime=runtime,
|
|
4471
4757
|
is_vision=is_vision,
|
|
4758
|
+
task=task,
|
|
4472
4759
|
)
|
|
4473
4760
|
if client is not None:
|
|
4474
4761
|
# For async clients, remember which loop they were created on so we
|
|
@@ -4675,10 +4962,14 @@ def _is_anthropic_compat_endpoint(provider: str, base_url: str) -> bool:
|
|
|
4675
4962
|
|
|
4676
4963
|
|
|
4677
4964
|
def _convert_openai_images_to_anthropic(messages: list) -> list:
|
|
4678
|
-
"""Convert OpenAI ``image_url``
|
|
4965
|
+
"""Convert OpenAI ``image_url``/``video_url`` blocks to Anthropic format.
|
|
4679
4966
|
|
|
4680
|
-
|
|
4681
|
-
|
|
4967
|
+
Converts:
|
|
4968
|
+
- ``image_url`` blocks to Anthropic ``image`` blocks
|
|
4969
|
+
- ``video_url`` blocks to Anthropic ``video`` blocks (MiniMax M3 compat)
|
|
4970
|
+
|
|
4971
|
+
Only touches messages that have list-type content with ``image_url`` or
|
|
4972
|
+
``video_url`` blocks; plain text messages pass through unchanged.
|
|
4682
4973
|
"""
|
|
4683
4974
|
converted = []
|
|
4684
4975
|
for msg in messages:
|
|
@@ -4715,6 +5006,39 @@ def _convert_openai_images_to_anthropic(messages: list) -> list:
|
|
|
4715
5006
|
},
|
|
4716
5007
|
})
|
|
4717
5008
|
changed = True
|
|
5009
|
+
elif block.get("type") == "video_url":
|
|
5010
|
+
# MiniMax's Anthropic-compatible endpoint expects a "video"
|
|
5011
|
+
# block (not OpenAI's "video_url", and not "input_video").
|
|
5012
|
+
# See https://platform.minimax.io/docs/api-reference/text-anthropic-api
|
|
5013
|
+
# — the Messages-field table lists type="video" (M3 only,
|
|
5014
|
+
# URL/base64/mm_file://). The source shape mirrors the "image"
|
|
5015
|
+
# block: base64 → {type:"base64", media_type, data}, URL →
|
|
5016
|
+
# {type:"url", url}.
|
|
5017
|
+
video_url_val = (block.get("video_url") or {}).get("url", "")
|
|
5018
|
+
if video_url_val.startswith("data:"):
|
|
5019
|
+
# Parse data URI: data:<media_type>;base64,<data>
|
|
5020
|
+
header, _, b64data = video_url_val.partition(",")
|
|
5021
|
+
media_type = "video/mp4"
|
|
5022
|
+
if ":" in header and ";" in header:
|
|
5023
|
+
media_type = header.split(":", 1)[1].split(";", 1)[0]
|
|
5024
|
+
new_content.append({
|
|
5025
|
+
"type": "video",
|
|
5026
|
+
"source": {
|
|
5027
|
+
"type": "base64",
|
|
5028
|
+
"media_type": media_type,
|
|
5029
|
+
"data": b64data,
|
|
5030
|
+
},
|
|
5031
|
+
})
|
|
5032
|
+
else:
|
|
5033
|
+
# URL-based video
|
|
5034
|
+
new_content.append({
|
|
5035
|
+
"type": "video",
|
|
5036
|
+
"source": {
|
|
5037
|
+
"type": "url",
|
|
5038
|
+
"url": video_url_val,
|
|
5039
|
+
},
|
|
5040
|
+
})
|
|
5041
|
+
changed = True
|
|
4718
5042
|
else:
|
|
4719
5043
|
new_content.append(block)
|
|
4720
5044
|
converted.append({**msg, "content": new_content} if changed else msg)
|
|
@@ -4802,7 +5126,7 @@ def _build_call_kwargs(
|
|
|
4802
5126
|
|
|
4803
5127
|
# Provider-specific extra_body
|
|
4804
5128
|
merged_extra = dict(extra_body or {})
|
|
4805
|
-
if provider == "nous"
|
|
5129
|
+
if provider == "nous":
|
|
4806
5130
|
merged_extra.setdefault("tags", []).extend(_nous_portal_tags())
|
|
4807
5131
|
if merged_extra:
|
|
4808
5132
|
kwargs["extra_body"] = merged_extra
|
|
@@ -4937,7 +5261,7 @@ def call_llm(
|
|
|
4937
5261
|
if not resolved_base_url:
|
|
4938
5262
|
logger.info("Auxiliary %s: provider %s unavailable, trying auto-detection chain",
|
|
4939
5263
|
task or "call", resolved_provider)
|
|
4940
|
-
client, final_model = _get_cached_client("auto", main_runtime=main_runtime)
|
|
5264
|
+
client, final_model = _get_cached_client("auto", main_runtime=main_runtime, task=task)
|
|
4941
5265
|
if client is None:
|
|
4942
5266
|
raise RuntimeError(
|
|
4943
5267
|
f"No LLM provider configured for task={task} provider={resolved_provider}. "
|
|
@@ -4969,8 +5293,28 @@ def call_llm(
|
|
|
4969
5293
|
# Handle unsupported temperature, max_tokens vs max_completion_tokens retry,
|
|
4970
5294
|
# then payment fallback.
|
|
4971
5295
|
try:
|
|
4972
|
-
|
|
4973
|
-
|
|
5296
|
+
# Retry ONCE on the same provider for a one-off transient transport
|
|
5297
|
+
# blip (streaming-close / incomplete chunked read / 5xx / 408) before
|
|
5298
|
+
# the except-chain below escalates to provider/model fallback. A
|
|
5299
|
+
# single dropped connection shouldn't abandon an otherwise-healthy
|
|
5300
|
+
# provider. A second failure (or any non-transient error) falls
|
|
5301
|
+
# through to ``first_err`` and the existing fallback handling
|
|
5302
|
+
# unchanged. This is the unified home for the transient retry that
|
|
5303
|
+
# every auxiliary task (compression, memory flush, title-gen,
|
|
5304
|
+
# session-search, vision) shares. (PR #16587)
|
|
5305
|
+
try:
|
|
5306
|
+
return _validate_llm_response(
|
|
5307
|
+
client.chat.completions.create(**kwargs), task)
|
|
5308
|
+
except Exception as transient_err:
|
|
5309
|
+
if not _is_transient_transport_error(transient_err):
|
|
5310
|
+
raise
|
|
5311
|
+
logger.info(
|
|
5312
|
+
"Auxiliary %s: transient transport error; retrying once on "
|
|
5313
|
+
"the same provider before fallback: %s",
|
|
5314
|
+
task or "call", transient_err,
|
|
5315
|
+
)
|
|
5316
|
+
return _validate_llm_response(
|
|
5317
|
+
client.chat.completions.create(**kwargs), task)
|
|
4974
5318
|
except Exception as first_err:
|
|
4975
5319
|
if "temperature" in kwargs and _is_unsupported_temperature_error(first_err):
|
|
4976
5320
|
retry_kwargs = dict(kwargs)
|
|
@@ -5027,6 +5371,32 @@ def call_llm(
|
|
|
5027
5371
|
raise
|
|
5028
5372
|
first_err = retry_err
|
|
5029
5373
|
|
|
5374
|
+
# ── Stale-model self-heal (Nous Portal recommendation drift) ───
|
|
5375
|
+
# A long-lived process can pin a Portal-recommended model that has
|
|
5376
|
+
# since been dropped from the Nous → OpenRouter catalog, so every
|
|
5377
|
+
# auxiliary call 404s with "model does not exist". Force a fresh
|
|
5378
|
+
# Portal fetch and retry once with the current recommendation (or the
|
|
5379
|
+
# known-good default). Only applies to Nous-routed calls.
|
|
5380
|
+
_heal_is_nous = (
|
|
5381
|
+
resolved_provider == "nous"
|
|
5382
|
+
or base_url_host_matches(_base_info, "inference-api.nousresearch.com")
|
|
5383
|
+
)
|
|
5384
|
+
if _is_model_not_found_error(first_err) and _heal_is_nous:
|
|
5385
|
+
healed_model = _refresh_nous_recommended_model(
|
|
5386
|
+
vision=(task == "vision"), stale_model=kwargs.get("model"))
|
|
5387
|
+
if healed_model and healed_model != kwargs.get("model"):
|
|
5388
|
+
logger.warning(
|
|
5389
|
+
"Auxiliary %s: model %r no longer in Nous catalog; "
|
|
5390
|
+
"retrying with refreshed recommendation %r",
|
|
5391
|
+
task or "call", kwargs.get("model"), healed_model,
|
|
5392
|
+
)
|
|
5393
|
+
kwargs["model"] = healed_model
|
|
5394
|
+
try:
|
|
5395
|
+
return _validate_llm_response(
|
|
5396
|
+
client.chat.completions.create(**kwargs), task)
|
|
5397
|
+
except Exception as retry_err:
|
|
5398
|
+
first_err = retry_err
|
|
5399
|
+
|
|
5030
5400
|
# ── Nous auth refresh parity with main agent ──────────────────
|
|
5031
5401
|
client_is_nous = (
|
|
5032
5402
|
resolved_provider == "nous"
|
|
@@ -5217,14 +5587,19 @@ def call_llm(
|
|
|
5217
5587
|
|
|
5218
5588
|
# Fallback order (#26882, #26803):
|
|
5219
5589
|
# 1. User-configured fallback_chain (per-task) if set
|
|
5220
|
-
# 2.
|
|
5221
|
-
# For auto
|
|
5222
|
-
#
|
|
5223
|
-
# model, so users on `auto` already get main-model fallback.
|
|
5590
|
+
# 2. For auto: top-level main fallback_providers/fallback_model
|
|
5591
|
+
# 3. For auto: built-in auxiliary discovery chain
|
|
5592
|
+
# 4. For explicit aux providers: main agent model safety net
|
|
5224
5593
|
fb_client, fb_model, fb_label = (None, None, "")
|
|
5225
5594
|
if is_auto:
|
|
5226
|
-
fb_client, fb_model, fb_label =
|
|
5227
|
-
|
|
5595
|
+
fb_client, fb_model, fb_label = _try_configured_fallback_chain(
|
|
5596
|
+
task, resolved_provider or "auto", reason=reason)
|
|
5597
|
+
if fb_client is None:
|
|
5598
|
+
fb_client, fb_model, fb_label = _try_main_fallback_chain(
|
|
5599
|
+
task, resolved_provider or "auto", reason=reason)
|
|
5600
|
+
if fb_client is None:
|
|
5601
|
+
fb_client, fb_model, fb_label = _try_payment_fallback(
|
|
5602
|
+
resolved_provider, task, reason=reason)
|
|
5228
5603
|
else:
|
|
5229
5604
|
fb_client, fb_model, fb_label = _try_configured_fallback_chain(
|
|
5230
5605
|
task, resolved_provider or "auto", reason=reason)
|
|
@@ -5387,7 +5762,7 @@ async def async_call_llm(
|
|
|
5387
5762
|
if not resolved_base_url:
|
|
5388
5763
|
logger.info("Auxiliary %s: provider %s unavailable, trying auto-detection chain",
|
|
5389
5764
|
task or "call", resolved_provider)
|
|
5390
|
-
client, final_model = _get_cached_client("auto", async_mode=True)
|
|
5765
|
+
client, final_model = _get_cached_client("auto", async_mode=True, main_runtime=main_runtime, task=task)
|
|
5391
5766
|
if client is None:
|
|
5392
5767
|
raise RuntimeError(
|
|
5393
5768
|
f"No LLM provider configured for task={task} provider={resolved_provider}. "
|
|
@@ -5410,8 +5785,22 @@ async def async_call_llm(
|
|
|
5410
5785
|
kwargs["messages"] = _convert_openai_images_to_anthropic(kwargs["messages"])
|
|
5411
5786
|
|
|
5412
5787
|
try:
|
|
5413
|
-
|
|
5414
|
-
|
|
5788
|
+
# Retry ONCE on the same provider for a transient transport blip
|
|
5789
|
+
# before the except-chain escalates to fallback — see call_llm()
|
|
5790
|
+
# for the rationale. (PR #16587)
|
|
5791
|
+
try:
|
|
5792
|
+
return _validate_llm_response(
|
|
5793
|
+
await client.chat.completions.create(**kwargs), task)
|
|
5794
|
+
except Exception as transient_err:
|
|
5795
|
+
if not _is_transient_transport_error(transient_err):
|
|
5796
|
+
raise
|
|
5797
|
+
logger.info(
|
|
5798
|
+
"Auxiliary %s (async): transient transport error; retrying "
|
|
5799
|
+
"once on the same provider before fallback: %s",
|
|
5800
|
+
task or "call", transient_err,
|
|
5801
|
+
)
|
|
5802
|
+
return _validate_llm_response(
|
|
5803
|
+
await client.chat.completions.create(**kwargs), task)
|
|
5415
5804
|
except Exception as first_err:
|
|
5416
5805
|
if "temperature" in kwargs and _is_unsupported_temperature_error(first_err):
|
|
5417
5806
|
retry_kwargs = dict(kwargs)
|
|
@@ -5464,6 +5853,31 @@ async def async_call_llm(
|
|
|
5464
5853
|
raise
|
|
5465
5854
|
first_err = retry_err
|
|
5466
5855
|
|
|
5856
|
+
# ── Stale-model self-heal (Nous Portal recommendation drift) ───
|
|
5857
|
+
# See the sync call_llm() path for the rationale: a long-lived process
|
|
5858
|
+
# can pin a Portal-recommended model that has since been dropped from
|
|
5859
|
+
# the Nous → OpenRouter catalog, 404'ing every auxiliary call. Force a
|
|
5860
|
+
# fresh Portal fetch and retry once with the current recommendation.
|
|
5861
|
+
_heal_is_nous = (
|
|
5862
|
+
resolved_provider == "nous"
|
|
5863
|
+
or base_url_host_matches(_client_base, "inference-api.nousresearch.com")
|
|
5864
|
+
)
|
|
5865
|
+
if _is_model_not_found_error(first_err) and _heal_is_nous:
|
|
5866
|
+
healed_model = _refresh_nous_recommended_model(
|
|
5867
|
+
vision=(task == "vision"), stale_model=kwargs.get("model"))
|
|
5868
|
+
if healed_model and healed_model != kwargs.get("model"):
|
|
5869
|
+
logger.warning(
|
|
5870
|
+
"Auxiliary %s (async): model %r no longer in Nous catalog; "
|
|
5871
|
+
"retrying with refreshed recommendation %r",
|
|
5872
|
+
task or "call", kwargs.get("model"), healed_model,
|
|
5873
|
+
)
|
|
5874
|
+
kwargs["model"] = healed_model
|
|
5875
|
+
try:
|
|
5876
|
+
return _validate_llm_response(
|
|
5877
|
+
await client.chat.completions.create(**kwargs), task)
|
|
5878
|
+
except Exception as retry_err:
|
|
5879
|
+
first_err = retry_err
|
|
5880
|
+
|
|
5467
5881
|
# ── Nous auth refresh parity with main agent ──────────────────
|
|
5468
5882
|
client_is_nous = (
|
|
5469
5883
|
resolved_provider == "nous"
|
|
@@ -5616,13 +6030,19 @@ async def async_call_llm(
|
|
|
5616
6030
|
|
|
5617
6031
|
# Fallback order (#26882, #26803):
|
|
5618
6032
|
# 1. User-configured fallback_chain (per-task) if set
|
|
5619
|
-
# 2.
|
|
5620
|
-
#
|
|
5621
|
-
#
|
|
6033
|
+
# 2. For auto: top-level main fallback_providers/fallback_model
|
|
6034
|
+
# 3. For auto: built-in auxiliary discovery chain
|
|
6035
|
+
# 4. For explicit aux providers: main agent model safety net
|
|
5622
6036
|
fb_client, fb_model, fb_label = (None, None, "")
|
|
5623
6037
|
if is_auto:
|
|
5624
|
-
fb_client, fb_model, fb_label =
|
|
5625
|
-
|
|
6038
|
+
fb_client, fb_model, fb_label = _try_configured_fallback_chain(
|
|
6039
|
+
task, resolved_provider or "auto", reason=reason)
|
|
6040
|
+
if fb_client is None:
|
|
6041
|
+
fb_client, fb_model, fb_label = _try_main_fallback_chain(
|
|
6042
|
+
task, resolved_provider or "auto", reason=reason)
|
|
6043
|
+
if fb_client is None:
|
|
6044
|
+
fb_client, fb_model, fb_label = _try_payment_fallback(
|
|
6045
|
+
resolved_provider, task, reason=reason)
|
|
5626
6046
|
else:
|
|
5627
6047
|
fb_client, fb_model, fb_label = _try_configured_fallback_chain(
|
|
5628
6048
|
task, resolved_provider or "auto", reason=reason)
|