@remodex/rmx 1.0.2
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/AGENTS_INSTALL.md +91 -0
- package/LICENSE +21 -0
- package/README.md +242 -0
- package/assets/architecture.png +0 -0
- package/assets/banner.png +0 -0
- package/assets/claude-code-models.gif +0 -0
- package/assets/codex-app-picker.png +0 -0
- package/bin/ocx.mjs +584 -0
- package/bin/package-main.mjs +9 -0
- package/gui/dist/assets/index-CZqebSPQ.css +1 -0
- package/gui/dist/assets/index-CkETtt7P.js +71 -0
- package/gui/dist/favicon.png +0 -0
- package/gui/dist/fonts/google-sans-cyrillic.woff2 +0 -0
- package/gui/dist/fonts/google-sans-latin.woff2 +0 -0
- package/gui/dist/icons.svg +24 -0
- package/gui/dist/index.html +25 -0
- package/gui/dist/logo.png +0 -0
- package/gui/dist/provider-icons/alibaba-color.svg +1 -0
- package/gui/dist/provider-icons/antigravity-color.svg +1 -0
- package/gui/dist/provider-icons/claude-color.svg +1 -0
- package/gui/dist/provider-icons/cline-color.svg +16 -0
- package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -0
- package/gui/dist/provider-icons/commandcode-color.svg +1 -0
- package/gui/dist/provider-icons/copilot-color.svg +1 -0
- package/gui/dist/provider-icons/cursor-color.svg +2 -0
- package/gui/dist/provider-icons/deepseek-color.svg +1 -0
- package/gui/dist/provider-icons/discord.svg +1 -0
- package/gui/dist/provider-icons/firepass-color.svg +1 -0
- package/gui/dist/provider-icons/fireworks-color.svg +1 -0
- package/gui/dist/provider-icons/gemini-color.svg +1 -0
- package/gui/dist/provider-icons/github-copilot-color.svg +1 -0
- package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -0
- package/gui/dist/provider-icons/grok.svg +1 -0
- package/gui/dist/provider-icons/groq-color.svg +1 -0
- package/gui/dist/provider-icons/huggingface-color.svg +1 -0
- package/gui/dist/provider-icons/kimi-color.svg +1 -0
- package/gui/dist/provider-icons/kiro-color.svg +15 -0
- package/gui/dist/provider-icons/lm-studio-color.svg +1 -0
- package/gui/dist/provider-icons/mistral-color.svg +1 -0
- package/gui/dist/provider-icons/moonshot-color.svg +1 -0
- package/gui/dist/provider-icons/nvidia-color.svg +1 -0
- package/gui/dist/provider-icons/ollama-color.svg +1 -0
- package/gui/dist/provider-icons/openai.svg +1 -0
- package/gui/dist/provider-icons/opencode.svg +2 -0
- package/gui/dist/provider-icons/openrouter-color.svg +1 -0
- package/gui/dist/provider-icons/pi.svg +21 -0
- package/gui/dist/provider-icons/qianfan-color.svg +1 -0
- package/gui/dist/provider-icons/qwen-portal-color.svg +1 -0
- package/gui/dist/provider-icons/telegram.svg +1 -0
- package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -0
- package/gui/dist/provider-icons/vllm-color.svg +1 -0
- package/gui/dist/provider-icons/xiaomi-color.svg +1 -0
- package/package.json +118 -0
- package/src/AGENTS.md +28 -0
- package/src/adapters/anthropic-image-guard.ts +251 -0
- package/src/adapters/anthropic-image-normalize.ts +518 -0
- package/src/adapters/anthropic.ts +1205 -0
- package/src/adapters/azure.ts +36 -0
- package/src/adapters/base.ts +83 -0
- package/src/adapters/client-fingerprint.ts +59 -0
- package/src/adapters/command-code.ts +453 -0
- package/src/adapters/cursor/arg-codec.ts +38 -0
- package/src/adapters/cursor/arg-normalize.ts +104 -0
- package/src/adapters/cursor/cursor-errors.ts +165 -0
- package/src/adapters/cursor/discovery.ts +276 -0
- package/src/adapters/cursor/effort-map.ts +139 -0
- package/src/adapters/cursor/exec-policy.ts +88 -0
- package/src/adapters/cursor/framing.ts +250 -0
- package/src/adapters/cursor/gen/agent_pb.ts +15274 -0
- package/src/adapters/cursor/kv-store.ts +52 -0
- package/src/adapters/cursor/live-models.ts +153 -0
- package/src/adapters/cursor/live-smoke-gate.ts +41 -0
- package/src/adapters/cursor/live-transport.ts +1235 -0
- package/src/adapters/cursor/mcp-config.ts +42 -0
- package/src/adapters/cursor/mcp-manager.ts +333 -0
- package/src/adapters/cursor/message-mapper.ts +49 -0
- package/src/adapters/cursor/native-exec-common.ts +59 -0
- package/src/adapters/cursor/native-exec-desktop.ts +184 -0
- package/src/adapters/cursor/native-exec-fs.ts +332 -0
- package/src/adapters/cursor/native-exec-mcp.ts +153 -0
- package/src/adapters/cursor/native-exec-network.ts +43 -0
- package/src/adapters/cursor/native-exec-shell.ts +548 -0
- package/src/adapters/cursor/native-exec-tools.ts +118 -0
- package/src/adapters/cursor/native-exec.ts +604 -0
- package/src/adapters/cursor/protobuf-events.ts +735 -0
- package/src/adapters/cursor/protobuf-request.ts +719 -0
- package/src/adapters/cursor/request-builder.ts +280 -0
- package/src/adapters/cursor/thread-continuity.ts +67 -0
- package/src/adapters/cursor/tool-definitions.ts +621 -0
- package/src/adapters/cursor/transport-retry.ts +132 -0
- package/src/adapters/cursor/transport.ts +57 -0
- package/src/adapters/cursor/types.ts +59 -0
- package/src/adapters/cursor.ts +196 -0
- package/src/adapters/google-antigravity-replay.ts +520 -0
- package/src/adapters/google-antigravity-wire.ts +140 -0
- package/src/adapters/google-errors.ts +85 -0
- package/src/adapters/google-http.ts +100 -0
- package/src/adapters/google-tool-schema.ts +173 -0
- package/src/adapters/google-truncation.ts +24 -0
- package/src/adapters/google-wire-compiler.ts +232 -0
- package/src/adapters/google.ts +859 -0
- package/src/adapters/identity.ts +77 -0
- package/src/adapters/image.ts +23 -0
- package/src/adapters/kiro-constants.ts +16 -0
- package/src/adapters/kiro-errors.ts +208 -0
- package/src/adapters/kiro-events.ts +197 -0
- package/src/adapters/kiro-images.ts +129 -0
- package/src/adapters/kiro-retry.ts +312 -0
- package/src/adapters/kiro-thinking.ts +104 -0
- package/src/adapters/kiro-tool-fallback.ts +36 -0
- package/src/adapters/kiro-tools.ts +224 -0
- package/src/adapters/kiro-truncation.ts +33 -0
- package/src/adapters/kiro-wire.ts +129 -0
- package/src/adapters/kiro.ts +1924 -0
- package/src/adapters/mimo-free.ts +263 -0
- package/src/adapters/openai-chat.ts +1265 -0
- package/src/adapters/openai-responses.ts +1309 -0
- package/src/adapters/run-turn-queue.ts +114 -0
- package/src/adapters/tool-catalog-nudge.ts +71 -0
- package/src/adapters/upstream-http-error.ts +48 -0
- package/src/android-remote/assets.ts +218 -0
- package/src/android-remote/attachments.ts +168 -0
- package/src/android-remote/auth.ts +237 -0
- package/src/android-remote/cloudflare-provisioning.ts +409 -0
- package/src/android-remote/cloudflare-secret.ts +106 -0
- package/src/android-remote/cloudflare-tunnel.ts +488 -0
- package/src/android-remote/cloudflared.ts +286 -0
- package/src/android-remote/codex-app-server.ts +565 -0
- package/src/android-remote/desktop-history-page.ts +936 -0
- package/src/android-remote/desktop-ipc.ts +3643 -0
- package/src/android-remote/desktop-ownership-store.ts +98 -0
- package/src/android-remote/desktop-project-registration.ts +129 -0
- package/src/android-remote/desktop-session-stream.ts +1110 -0
- package/src/android-remote/desktop-workspace-state.ts +443 -0
- package/src/android-remote/file-change-parser.ts +112 -0
- package/src/android-remote/gateway.ts +9108 -0
- package/src/android-remote/mutation-store.ts +299 -0
- package/src/android-remote/projection.ts +1780 -0
- package/src/android-remote/queued-turn-store.ts +248 -0
- package/src/android-remote/session-command-recovery.ts +1384 -0
- package/src/android-remote/store.ts +466 -0
- package/src/android-remote/thread-reconciliation.ts +133 -0
- package/src/android-remote/thread-source-paths.ts +307 -0
- package/src/android-remote/thread-stream.ts +546 -0
- package/src/android-remote/turn-activity.ts +235 -0
- package/src/android-remote/user-message-identity.ts +94 -0
- package/src/bridge.ts +1793 -0
- package/src/chat/inbound.ts +295 -0
- package/src/chat/outbound.ts +821 -0
- package/src/claude/agents-inject.ts +267 -0
- package/src/claude/alias.ts +149 -0
- package/src/claude/auth-detect.ts +229 -0
- package/src/claude/auth-mode-migration.ts +32 -0
- package/src/claude/auth-mode.ts +62 -0
- package/src/claude/context-windows.ts +189 -0
- package/src/claude/desktop-3p-guard.ts +35 -0
- package/src/claude/desktop-3p-paths.ts +84 -0
- package/src/claude/desktop-3p.ts +601 -0
- package/src/claude/desktop-health.ts +26 -0
- package/src/claude/desktop-profile.ts +263 -0
- package/src/claude/gateway-cache.ts +70 -0
- package/src/claude/inbound-debug.ts +163 -0
- package/src/claude/inbound.ts +519 -0
- package/src/claude/model-info.ts +154 -0
- package/src/claude/outbound.ts +898 -0
- package/src/cli/access.ts +108 -0
- package/src/cli/account-api.ts +296 -0
- package/src/cli/account-auth.ts +250 -0
- package/src/cli/account-catalog-refresh.ts +14 -0
- package/src/cli/account-extended.ts +476 -0
- package/src/cli/account-main.ts +317 -0
- package/src/cli/account.ts +297 -0
- package/src/cli/agent-driven.ts +70 -0
- package/src/cli/agent.ts +184 -0
- package/src/cli/catalog-prewarm.ts +27 -0
- package/src/cli/claude-desktop.ts +211 -0
- package/src/cli/claude.ts +302 -0
- package/src/cli/codex-shim-autorestore.ts +45 -0
- package/src/cli/codex-shim-readiness.ts +69 -0
- package/src/cli/combo.ts +124 -0
- package/src/cli/config-command.ts +183 -0
- package/src/cli/debug.ts +228 -0
- package/src/cli/desktop-first-run.ts +25 -0
- package/src/cli/doctor.ts +1022 -0
- package/src/cli/export-command.ts +201 -0
- package/src/cli/help.ts +370 -0
- package/src/cli/index.ts +1565 -0
- package/src/cli/init.ts +224 -0
- package/src/cli/integrations.ts +225 -0
- package/src/cli/interactive-confirm.ts +133 -0
- package/src/cli/internal-dispatch.ts +35 -0
- package/src/cli/launcher-context.ts +77 -0
- package/src/cli/models-runtime.ts +224 -0
- package/src/cli/models.ts +340 -0
- package/src/cli/observe.ts +170 -0
- package/src/cli/opencode.ts +587 -0
- package/src/cli/provider-runtime.ts +179 -0
- package/src/cli/provider.ts +476 -0
- package/src/cli/ready.ts +301 -0
- package/src/cli/route-policy.ts +92 -0
- package/src/cli/runtime-api.ts +328 -0
- package/src/cli/star-prompt.ts +211 -0
- package/src/cli/status-oauth.ts +78 -0
- package/src/cli/status.ts +321 -0
- package/src/cli/system-command.ts +196 -0
- package/src/cli/system-restart-client.ts +146 -0
- package/src/cli/tray-proxy.ts +205 -0
- package/src/cli/v2.ts +200 -0
- package/src/cli.ts +10 -0
- package/src/clients/config-export.ts +1109 -0
- package/src/codex/account-id.ts +34 -0
- package/src/codex/account-label.ts +34 -0
- package/src/codex/account-lifecycle.ts +172 -0
- package/src/codex/account-namespace-match.ts +63 -0
- package/src/codex/account-namespaces.ts +195 -0
- package/src/codex/account-pause.ts +20 -0
- package/src/codex/account-priority.ts +83 -0
- package/src/codex/account-runtime-state.ts +31 -0
- package/src/codex/account-store.ts +517 -0
- package/src/codex/account-usability.ts +40 -0
- package/src/codex/admission.ts +263 -0
- package/src/codex/app-server-processes.ts +799 -0
- package/src/codex/auth-api.ts +2098 -0
- package/src/codex/auth-collision.ts +107 -0
- package/src/codex/auth-context.ts +480 -0
- package/src/codex/autostart-health.ts +156 -0
- package/src/codex/catalog/account-models.ts +67 -0
- package/src/codex/catalog/aggregation.ts +471 -0
- package/src/codex/catalog/bundled.ts +533 -0
- package/src/codex/catalog/effort.ts +432 -0
- package/src/codex/catalog/filesystem-evidence.ts +302 -0
- package/src/codex/catalog/kinds.ts +2 -0
- package/src/codex/catalog/metadata.ts +287 -0
- package/src/codex/catalog/native-models.ts +7 -0
- package/src/codex/catalog/parsing.ts +503 -0
- package/src/codex/catalog/provider-fetch.ts +2267 -0
- package/src/codex/catalog/sync.ts +1606 -0
- package/src/codex/catalog-admission.ts +197 -0
- package/src/codex/catalog-refresh-status.ts +87 -0
- package/src/codex/catalog-write-serialization.ts +242 -0
- package/src/codex/catalog.ts +15 -0
- package/src/codex/codex-write-lock.ts +384 -0
- package/src/codex/convergence-types.ts +593 -0
- package/src/codex/convergence.ts +580 -0
- package/src/codex/custom-model-catalog-migration.ts +176 -0
- package/src/codex/data/upstream-models.json +830 -0
- package/src/codex/desired-state.ts +230 -0
- package/src/codex/desktop-client-processes.ts +521 -0
- package/src/codex/exec-invocation.ts +22 -0
- package/src/codex/features.ts +1091 -0
- package/src/codex/generation.ts +202 -0
- package/src/codex/history-job.ts +347 -0
- package/src/codex/history-lock.ts +242 -0
- package/src/codex/history-migration-guardian.ts +115 -0
- package/src/codex/history-provider.ts +1075 -0
- package/src/codex/history-transition.ts +105 -0
- package/src/codex/history-worker.ts +204 -0
- package/src/codex/home.ts +206 -0
- package/src/codex/inject-coordination.ts +257 -0
- package/src/codex/inject.ts +1857 -0
- package/src/codex/injected-marker.ts +79 -0
- package/src/codex/integration-record.ts +266 -0
- package/src/codex/internal/catalog-writer.ts +203 -0
- package/src/codex/internal/history-writer.ts +105 -0
- package/src/codex/journal.ts +172 -0
- package/src/codex/main-account-cache.ts +56 -0
- package/src/codex/main-account.ts +40 -0
- package/src/codex/management-convergence.ts +114 -0
- package/src/codex/model-cache.ts +267 -0
- package/src/codex/native-main-admission.ts +47 -0
- package/src/codex/native-main-auth-temp.ts +187 -0
- package/src/codex/native-main-claim.ts +167 -0
- package/src/codex/native-main-lock-file.ts +162 -0
- package/src/codex/native-main-owner.ts +329 -0
- package/src/codex/native-profile-api.ts +247 -0
- package/src/codex/native-profile-manager.ts +1531 -0
- package/src/codex/native-profile-processes.ts +121 -0
- package/src/codex/native-profile-recovery.ts +99 -0
- package/src/codex/native-profile-stage-store.ts +387 -0
- package/src/codex/native-profile-startup.ts +348 -0
- package/src/codex/native-profile-store.ts +855 -0
- package/src/codex/native-profile-types.ts +120 -0
- package/src/codex/native-residue.ts +691 -0
- package/src/codex/paths.ts +78 -0
- package/src/codex/plugins-doctor.ts +242 -0
- package/src/codex/pool-rotation.ts +295 -0
- package/src/codex/project-config-warnings.ts +426 -0
- package/src/codex/prompt-journal.ts +311 -0
- package/src/codex/prompt-layers.ts +967 -0
- package/src/codex/prompt-lock.ts +143 -0
- package/src/codex/provider-adoption.ts +242 -0
- package/src/codex/quota-rejection.ts +224 -0
- package/src/codex/quota.ts +494 -0
- package/src/codex/refresh.ts +60 -0
- package/src/codex/routing.ts +1855 -0
- package/src/codex/runtime.ts +659 -0
- package/src/codex/shim.ts +1215 -0
- package/src/codex/subagent-defaults.ts +557 -0
- package/src/codex/subagent-model-fallback.ts +560 -0
- package/src/codex/sync.ts +238 -0
- package/src/codex/transition-state.ts +612 -0
- package/src/codex/upstream-host-health.ts +368 -0
- package/src/codex/user-identity.ts +374 -0
- package/src/codex/warmup.ts +192 -0
- package/src/codex/websocket-registry.ts +100 -0
- package/src/codex/write-coordination.ts +114 -0
- package/src/combos/failover.ts +140 -0
- package/src/combos/index.ts +44 -0
- package/src/combos/request.ts +64 -0
- package/src/combos/resolve.ts +232 -0
- package/src/combos/types.ts +392 -0
- package/src/config.ts +3270 -0
- package/src/generated/model-metadata.ts +144 -0
- package/src/github/star-state.ts +203 -0
- package/src/grok/inject.ts +540 -0
- package/src/grok/inspect.ts +45 -0
- package/src/grok/status.ts +127 -0
- package/src/grok/sync.ts +66 -0
- package/src/images/artifacts.ts +516 -0
- package/src/images/fulfill-video.ts +163 -0
- package/src/images/fulfill.ts +149 -0
- package/src/images/index.ts +4 -0
- package/src/images/loop.ts +922 -0
- package/src/images/plan.ts +133 -0
- package/src/images/synthetic-tool.ts +133 -0
- package/src/images/types.ts +41 -0
- package/src/images/xai-client.ts +141 -0
- package/src/images/xai-video-client.ts +163 -0
- package/src/index.ts +22 -0
- package/src/integrations/config-io.ts +151 -0
- package/src/integrations/journal.ts +315 -0
- package/src/integrations/merge.ts +135 -0
- package/src/integrations/native/ownership-preflight.ts +202 -0
- package/src/integrations/ownership.ts +111 -0
- package/src/integrations/registry.ts +108 -0
- package/src/integrations/serialize.ts +235 -0
- package/src/integrations/state.ts +290 -0
- package/src/integrations/store.ts +103 -0
- package/src/integrations/writer.ts +492 -0
- package/src/lib/abort.ts +146 -0
- package/src/lib/admin-secrets.ts +25 -0
- package/src/lib/admission.ts +83 -0
- package/src/lib/app-owned-memory-stores.ts +173 -0
- package/src/lib/app-owned-memory.ts +265 -0
- package/src/lib/bounded-body.ts +242 -0
- package/src/lib/bun-binary-validator.d.mts +3 -0
- package/src/lib/bun-binary-validator.mjs +18 -0
- package/src/lib/bun-runtime.ts +184 -0
- package/src/lib/bun-stream-caps.ts +127 -0
- package/src/lib/config-ownership.ts +438 -0
- package/src/lib/crash-guard.ts +344 -0
- package/src/lib/debug-log-buffer.ts +83 -0
- package/src/lib/debug-settings.ts +108 -0
- package/src/lib/debug.ts +31 -0
- package/src/lib/destination-policy.ts +316 -0
- package/src/lib/errors.ts +364 -0
- package/src/lib/eventstream-decoder.ts +253 -0
- package/src/lib/gcp-adc.ts +341 -0
- package/src/lib/injection-debug-log.ts +58 -0
- package/src/lib/local-management-attestation.ts +51 -0
- package/src/lib/open-url.ts +25 -0
- package/src/lib/pinned-http.ts +182 -0
- package/src/lib/privacy.ts +20 -0
- package/src/lib/process-control.ts +168 -0
- package/src/lib/provider-environment.ts +470 -0
- package/src/lib/provider-outbound.ts +203 -0
- package/src/lib/provider-url.ts +14 -0
- package/src/lib/proxy-env.ts +18 -0
- package/src/lib/redact.ts +510 -0
- package/src/lib/remodex-home.ts +616 -0
- package/src/lib/retry-after.ts +55 -0
- package/src/lib/service-secrets.ts +178 -0
- package/src/lib/shadow-call.ts +54 -0
- package/src/lib/sidecar-tracker.ts +52 -0
- package/src/lib/sse-decoder.ts +364 -0
- package/src/lib/state-store-registrations.ts +109 -0
- package/src/lib/state-store-sweeper.ts +184 -0
- package/src/lib/system-restart-contract.ts +73 -0
- package/src/lib/test-home-guard.ts +98 -0
- package/src/lib/token-estimate.ts +69 -0
- package/src/lib/translator-budget.ts +366 -0
- package/src/lib/upstream-reachability.ts +91 -0
- package/src/lib/upstream-retry.ts +508 -0
- package/src/lib/win-exec.ts +115 -0
- package/src/lib/win-paths.ts +68 -0
- package/src/lib/windows-elevation.ts +705 -0
- package/src/lib/windows-secret-acl.ts +817 -0
- package/src/lib/windows-user-principal.ts +283 -0
- package/src/lib/winsw.ts +402 -0
- package/src/model-sources.ts +73 -0
- package/src/oauth/anthropic-routing.ts +594 -0
- package/src/oauth/anthropic.ts +177 -0
- package/src/oauth/callback-server.ts +294 -0
- package/src/oauth/chatgpt.ts +150 -0
- package/src/oauth/command-code.ts +239 -0
- package/src/oauth/cursor.ts +231 -0
- package/src/oauth/github-copilot.ts +428 -0
- package/src/oauth/google-antigravity.ts +230 -0
- package/src/oauth/health.ts +443 -0
- package/src/oauth/index.ts +1280 -0
- package/src/oauth/key-providers.ts +128 -0
- package/src/oauth/kimi.ts +213 -0
- package/src/oauth/kiro-credentials.ts +726 -0
- package/src/oauth/kiro.ts +621 -0
- package/src/oauth/local-token-detect.ts +121 -0
- package/src/oauth/log.ts +48 -0
- package/src/oauth/login-cli.ts +163 -0
- package/src/oauth/pkce.ts +15 -0
- package/src/oauth/store.ts +655 -0
- package/src/oauth/token-guardian.ts +309 -0
- package/src/oauth/types.ts +62 -0
- package/src/oauth/xai.ts +241 -0
- package/src/providers/alibaba-region-backup.ts +75 -0
- package/src/providers/alibaba-region-migration.ts +156 -0
- package/src/providers/alibaba-region-startup.ts +36 -0
- package/src/providers/antigravity-models.ts +317 -0
- package/src/providers/api-keys.ts +140 -0
- package/src/providers/base-url-choices.ts +64 -0
- package/src/providers/codex-capacity.ts +288 -0
- package/src/providers/command-code-efforts.ts +85 -0
- package/src/providers/context-cap.ts +73 -0
- package/src/providers/derive.ts +451 -0
- package/src/providers/free-directory.ts +187 -0
- package/src/providers/github-copilot-transport.ts +56 -0
- package/src/providers/google-vertex-location.ts +14 -0
- package/src/providers/key-failover.ts +271 -0
- package/src/providers/kiro-models.ts +67 -0
- package/src/providers/label.ts +19 -0
- package/src/providers/model-discovery-limits.ts +16 -0
- package/src/providers/model-discovery.ts +361 -0
- package/src/providers/openai-sidecar.ts +235 -0
- package/src/providers/openai-tier-startup.ts +27 -0
- package/src/providers/openai-tiers.ts +301 -0
- package/src/providers/openai-virtual-models.ts +83 -0
- package/src/providers/openrouter-routing.ts +102 -0
- package/src/providers/provider-id-rewrite.ts +179 -0
- package/src/providers/quota.ts +1942 -0
- package/src/providers/reasoning-capabilities.ts +336 -0
- package/src/providers/registry.ts +2375 -0
- package/src/providers/slug-codec.ts +74 -0
- package/src/providers/xai-transport.ts +149 -0
- package/src/reasoning-effort.ts +243 -0
- package/src/responses/compaction.ts +124 -0
- package/src/responses/hosted-tool-policy.ts +9 -0
- package/src/responses/parser.ts +714 -0
- package/src/responses/reasoning-envelope.ts +60 -0
- package/src/responses/reasoning-replay-cache.ts +106 -0
- package/src/responses/schema.ts +159 -0
- package/src/responses/spill-store.ts +431 -0
- package/src/responses/state.ts +1039 -0
- package/src/responses/tool-groups.ts +19 -0
- package/src/router.ts +802 -0
- package/src/routing/analytics.ts +377 -0
- package/src/routing/capability.ts +205 -0
- package/src/routing/cost.ts +77 -0
- package/src/routing/evaluator.ts +444 -0
- package/src/routing/health.ts +401 -0
- package/src/routing/history/cursor.ts +43 -0
- package/src/routing/history/indexer.ts +605 -0
- package/src/routing/history/schema.ts +72 -0
- package/src/routing/profile-namespace.ts +15 -0
- package/src/routing/profile.ts +424 -0
- package/src/routing/quota.ts +145 -0
- package/src/routing/request-evidence.ts +45 -0
- package/src/routing/trace.ts +686 -0
- package/src/server/adapter-resolve.ts +83 -0
- package/src/server/auth-cors.ts +606 -0
- package/src/server/chat-completions.ts +379 -0
- package/src/server/claude-messages.ts +980 -0
- package/src/server/effort-policy.ts +251 -0
- package/src/server/github-copilot-responses-repair.ts +338 -0
- package/src/server/gui-static.ts +152 -0
- package/src/server/image-retry.ts +42 -0
- package/src/server/images.ts +485 -0
- package/src/server/index.ts +1633 -0
- package/src/server/lifecycle.ts +482 -0
- package/src/server/live.ts +609 -0
- package/src/server/management/agent-settings-routes.ts +1180 -0
- package/src/server/management/android-remote-routes.ts +390 -0
- package/src/server/management/api-access.ts +141 -0
- package/src/server/management/api-key-usage.ts +167 -0
- package/src/server/management/body.ts +35 -0
- package/src/server/management/combo-routes.ts +244 -0
- package/src/server/management/config-routes.ts +602 -0
- package/src/server/management/context.ts +88 -0
- package/src/server/management/integration-routes.ts +538 -0
- package/src/server/management/logs-usage-routes.ts +516 -0
- package/src/server/management/model-routes.ts +519 -0
- package/src/server/management/model-rows.ts +143 -0
- package/src/server/management/native-integration-routes.ts +781 -0
- package/src/server/management/oauth-account-routes.ts +573 -0
- package/src/server/management/provider-routes.ts +781 -0
- package/src/server/management/request-history-routes.ts +191 -0
- package/src/server/management/routing-analytics-routes.ts +74 -0
- package/src/server/management/routing-profile-routes.ts +384 -0
- package/src/server/management/shared.ts +277 -0
- package/src/server/management/sidebar-routes.ts +106 -0
- package/src/server/management/sync-response.ts +69 -0
- package/src/server/management/system-restart.ts +433 -0
- package/src/server/management/system-routes.ts +141 -0
- package/src/server/management/usage-summary-cache.ts +86 -0
- package/src/server/management-api.ts +269 -0
- package/src/server/management-auth.ts +353 -0
- package/src/server/memory-watchdog.ts +156 -0
- package/src/server/port-reclaim.ts +307 -0
- package/src/server/ports.ts +156 -0
- package/src/server/proxy-liveness.ts +326 -0
- package/src/server/proxy-stop.ts +92 -0
- package/src/server/readiness.ts +99 -0
- package/src/server/relay-eager.ts +353 -0
- package/src/server/relay.ts +1179 -0
- package/src/server/request-decompress.ts +132 -0
- package/src/server/request-log-conversation.ts +168 -0
- package/src/server/request-log.ts +1072 -0
- package/src/server/responses/collaboration.ts +409 -0
- package/src/server/responses/compact.ts +710 -0
- package/src/server/responses/core.ts +3561 -0
- package/src/server/responses/encrypted-payload.ts +308 -0
- package/src/server/responses/fetch-helpers.ts +171 -0
- package/src/server/responses/passthrough-error.ts +78 -0
- package/src/server/responses/policy-fallback.ts +152 -0
- package/src/server/responses/terminal-guard.ts +230 -0
- package/src/server/responses/upstream-error.ts +48 -0
- package/src/server/responses-image-gen-repair.ts +132 -0
- package/src/server/responses-item-id-repair.ts +272 -0
- package/src/server/responses-json-events.ts +52 -0
- package/src/server/responses-model-rewrite.ts +29 -0
- package/src/server/responses-snapshot-repair.ts +621 -0
- package/src/server/responses.ts +10 -0
- package/src/server/search.ts +181 -0
- package/src/server/sse-frame-buffer.ts +292 -0
- package/src/server/sse-payload-rewrite.ts +263 -0
- package/src/server/startup-action-control.ts +308 -0
- package/src/server/startup-health-cache.ts +119 -0
- package/src/server/system-env.ts +418 -0
- package/src/server/windows-tcp-drop.ts +184 -0
- package/src/server/windows-tray-control.ts +41 -0
- package/src/server/ws-bridge.ts +470 -0
- package/src/service-manager-probe.ts +824 -0
- package/src/service.ts +3011 -0
- package/src/stall-timeout.ts +20 -0
- package/src/storage/cleanup-job.ts +57 -0
- package/src/storage/cleanup.ts +3085 -0
- package/src/storage/policy-job.ts +457 -0
- package/src/storage/policy-scheduler.ts +40 -0
- package/src/storage/policy-worker.ts +59 -0
- package/src/storage/policy.ts +527 -0
- package/src/storage/restore-job.ts +299 -0
- package/src/storage/restore-worker.ts +58 -0
- package/src/storage/scanner.ts +238 -0
- package/src/storage/storage-mutation-coordinator.ts +139 -0
- package/src/storage/worker-lifecycle.ts +215 -0
- package/src/tray/assets/opencodex-tray-offline.ico +0 -0
- package/src/tray/assets/opencodex-tray-online.ico +0 -0
- package/src/tray/assets/opencodex-tray-warning.ico +0 -0
- package/src/tray/assets/opencodex-tray.png +0 -0
- package/src/tray/windows-tray.ps1 +364 -0
- package/src/tray/windows.ts +738 -0
- package/src/types.ts +1531 -0
- package/src/update/badge.ts +72 -0
- package/src/update/desktop-release.ts +1620 -0
- package/src/update/index.ts +402 -0
- package/src/update/job.ts +1906 -0
- package/src/update/notify.ts +261 -0
- package/src/update/npm-cache-preflight.d.mts +47 -0
- package/src/update/npm-cache-preflight.mjs +201 -0
- package/src/update/npm-invocation.d.mts +23 -0
- package/src/update/npm-invocation.mjs +94 -0
- package/src/update/tray-update-plan.d.mts +18 -0
- package/src/update/tray-update-plan.mjs +38 -0
- package/src/usage/cost.ts +0 -0
- package/src/usage/debug.ts +97 -0
- package/src/usage/expected-prices.ts +283 -0
- package/src/usage/log.ts +695 -0
- package/src/usage/summary.ts +585 -0
- package/src/usage/totals.ts +14 -0
- package/src/vision/anthropic-describe.ts +185 -0
- package/src/vision/describe.ts +127 -0
- package/src/vision/index.ts +558 -0
- package/src/vision/reasoning.ts +55 -0
- package/src/web-search/anthropic-executor.ts +189 -0
- package/src/web-search/executor.ts +105 -0
- package/src/web-search/format-result.ts +89 -0
- package/src/web-search/index.ts +196 -0
- package/src/web-search/loop.ts +791 -0
- package/src/web-search/parse.ts +235 -0
- package/src/web-search/progress-stream.ts +342 -0
- package/src/web-search/synthetic-tool.ts +47 -0
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { lookup } from "node:dns/promises";
|
|
2
|
+
import { isIP } from "node:net";
|
|
3
|
+
import { getProviderRegistryEntry } from "../providers/registry";
|
|
4
|
+
import type { OcxProviderConfig } from "../types";
|
|
5
|
+
|
|
6
|
+
const BLOCKED_METADATA_HOSTS = new Set([
|
|
7
|
+
"instance-data.ec2.internal",
|
|
8
|
+
"metadata.azure.internal",
|
|
9
|
+
"metadata.google.internal",
|
|
10
|
+
]);
|
|
11
|
+
|
|
12
|
+
const BLOCKED_METADATA_IPV4 = new Set([
|
|
13
|
+
"100.100.100.200",
|
|
14
|
+
"169.254.169.254",
|
|
15
|
+
"169.254.170.2",
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
const BLOCKED_METADATA_IPV6 = new Set([
|
|
19
|
+
"fd00:ec2::254",
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
export type DestinationKind =
|
|
23
|
+
| "public"
|
|
24
|
+
| "hostname"
|
|
25
|
+
| "localhost"
|
|
26
|
+
| "loopback"
|
|
27
|
+
| "private"
|
|
28
|
+
| "link-local"
|
|
29
|
+
| "unspecified"
|
|
30
|
+
| "metadata";
|
|
31
|
+
|
|
32
|
+
interface DestinationAssessment {
|
|
33
|
+
kind: DestinationKind;
|
|
34
|
+
detail: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function normalizeHostname(hostname: string): string {
|
|
38
|
+
const trimmed = hostname.trim().toLowerCase().replace(/\.+$/, "");
|
|
39
|
+
return trimmed.startsWith("[") && trimmed.endsWith("]") ? trimmed.slice(1, -1) : trimmed;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function parseIpv4(hostname: string): number[] | null {
|
|
43
|
+
const parts = hostname.split(".");
|
|
44
|
+
if (parts.length !== 4) return null;
|
|
45
|
+
const octets = parts.map(part => Number(part));
|
|
46
|
+
return octets.every(octet => Number.isInteger(octet) && octet >= 0 && octet <= 255) ? octets : null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function classifyIpv4(hostname: string): DestinationAssessment {
|
|
50
|
+
if (BLOCKED_METADATA_IPV4.has(hostname)) return { kind: "metadata", detail: "blocked metadata endpoint" };
|
|
51
|
+
const octets = parseIpv4(hostname);
|
|
52
|
+
if (!octets) return { kind: "public", detail: "public IP" };
|
|
53
|
+
const [a, b, c] = octets;
|
|
54
|
+
if (a === 127) return { kind: "loopback", detail: "loopback address" };
|
|
55
|
+
if (a === 10 || (a === 172 && b >= 16 && b <= 31) || (a === 192 && b === 168) || (a === 100 && b >= 64 && b <= 127)) {
|
|
56
|
+
return { kind: "private", detail: "private-network address" };
|
|
57
|
+
}
|
|
58
|
+
if (a === 169 && b === 254) return { kind: "link-local", detail: "link-local address" };
|
|
59
|
+
if (a === 0) return { kind: "unspecified", detail: "unspecified address" };
|
|
60
|
+
// Reserved / non-public ranges (review finding, PR #96): protocol-assignment,
|
|
61
|
+
// documentation, benchmark, multicast, and reserved-future space never name a
|
|
62
|
+
// legitimate provider endpoint.
|
|
63
|
+
if (a === 192 && b === 0 && (c === 0 || c === 2)) return { kind: "private", detail: "reserved address" };
|
|
64
|
+
if (a === 198 && (b === 18 || b === 19)) return { kind: "private", detail: "benchmark address" };
|
|
65
|
+
if (a === 198 && b === 51 && c === 100) return { kind: "private", detail: "documentation address" };
|
|
66
|
+
if (a === 203 && b === 0 && c === 113) return { kind: "private", detail: "documentation address" };
|
|
67
|
+
if (a >= 224) return { kind: "private", detail: "multicast/reserved address" };
|
|
68
|
+
return { kind: "public", detail: "public IP" };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function firstIpv6Hextet(hostname: string): number | null {
|
|
72
|
+
const head = hostname.split(":")[0];
|
|
73
|
+
if (!head) return 0;
|
|
74
|
+
const parsed = Number.parseInt(head, 16);
|
|
75
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function classifyIpv6(hostname: string): DestinationAssessment {
|
|
79
|
+
if (BLOCKED_METADATA_IPV6.has(hostname)) return { kind: "metadata", detail: "blocked metadata endpoint" };
|
|
80
|
+
const mappedIpv4 = hostname.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/i)?.[1];
|
|
81
|
+
if (mappedIpv4) return classifyIpv4(mappedIpv4);
|
|
82
|
+
// Decode hex IPv4-mapped IPv6: ::ffff:7f00:1 → 127.0.0.1
|
|
83
|
+
// The dotted-decimal regex above only matches ::ffff:127.0.0.1; without this,
|
|
84
|
+
// hex form bypasses all private/loopback checks (hextet is 0 → classified "public").
|
|
85
|
+
const hexMapped = hostname.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i);
|
|
86
|
+
if (hexMapped) {
|
|
87
|
+
const hi = Number.parseInt(hexMapped[1], 16);
|
|
88
|
+
const lo = Number.parseInt(hexMapped[2], 16);
|
|
89
|
+
const ipv4 = `${(hi >> 8) & 255}.${hi & 255}.${(lo >> 8) & 255}.${lo & 255}`;
|
|
90
|
+
return classifyIpv4(ipv4);
|
|
91
|
+
}
|
|
92
|
+
if (hostname === "::1") return { kind: "loopback", detail: "loopback address" };
|
|
93
|
+
if (hostname === "::") return { kind: "unspecified", detail: "unspecified address" };
|
|
94
|
+
const hextet = firstIpv6Hextet(hostname);
|
|
95
|
+
if (hextet === null) return { kind: "private", detail: "non-global address" };
|
|
96
|
+
// Multicast ff00::/8, deprecated site-local fec0::/10, ULA fc00::/7, link-local fe80::/10.
|
|
97
|
+
if (hextet >= 0xff00) return { kind: "private", detail: "multicast address" };
|
|
98
|
+
if (hextet >= 0xfe80 && hextet <= 0xfebf) return { kind: "link-local", detail: "link-local address" };
|
|
99
|
+
if (hextet >= 0xfec0 && hextet <= 0xfeff) return { kind: "private", detail: "site-local address" };
|
|
100
|
+
if (hextet >= 0xfc00 && hextet <= 0xfdff) return { kind: "private", detail: "private-network address" };
|
|
101
|
+
// Documentation 2001:db8::/32 (inside global-unicast 2000::/3).
|
|
102
|
+
if (hextet === 0x2001) {
|
|
103
|
+
const second = Number.parseInt(hostname.split(":")[1] || "0", 16);
|
|
104
|
+
if (second === 0xdb8) return { kind: "private", detail: "documentation address" };
|
|
105
|
+
}
|
|
106
|
+
// Only global unicast 2000::/3 is treated as a public image/CDN peer.
|
|
107
|
+
if (hextet >= 0x2000 && hextet <= 0x3fff) return { kind: "public", detail: "public IP" };
|
|
108
|
+
return { kind: "private", detail: "non-global address" };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function assessDestination(baseUrl: string): DestinationAssessment | null {
|
|
112
|
+
try {
|
|
113
|
+
const parsed = new URL(baseUrl.trim());
|
|
114
|
+
const hostname = normalizeHostname(parsed.hostname);
|
|
115
|
+
if (!hostname) return null;
|
|
116
|
+
if (BLOCKED_METADATA_HOSTS.has(hostname)) return { kind: "metadata", detail: "blocked metadata endpoint" };
|
|
117
|
+
if (hostname === "localhost" || hostname.endsWith(".localhost")) {
|
|
118
|
+
return { kind: "localhost", detail: "localhost destination" };
|
|
119
|
+
}
|
|
120
|
+
const ipKind = isIP(hostname);
|
|
121
|
+
if (ipKind === 4) return classifyIpv4(hostname);
|
|
122
|
+
if (ipKind === 6) return classifyIpv6(hostname);
|
|
123
|
+
return { kind: "hostname", detail: "hostname destination" };
|
|
124
|
+
} catch {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function registryAllowsPrivateNetwork(name: string): boolean {
|
|
130
|
+
return getProviderRegistryEntry(name)?.allowPrivateNetworkByDefault === true;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Whether a provider may reach loopback/private addresses.
|
|
135
|
+
*
|
|
136
|
+
* Two sources, and both have to be consulted at every boundary: the operator's explicit
|
|
137
|
+
* `allowPrivateNetwork`, and the registry's `allowPrivateNetworkByDefault` for entries that are
|
|
138
|
+
* local BY DEFINITION (Ollama, vLLM, LM Studio, LiteLLM). Config validation already read both;
|
|
139
|
+
* outbound discovery read only the first, so a stock Ollama entry passed validation and was then
|
|
140
|
+
* refused at the fetch (#758).
|
|
141
|
+
*
|
|
142
|
+
* This grants nothing new. Metadata, link-local and unspecified destinations are rejected before
|
|
143
|
+
* this is consulted, and a provider without either source still cannot reach a private address.
|
|
144
|
+
*/
|
|
145
|
+
export function providerAllowsPrivateNetwork(
|
|
146
|
+
name: string,
|
|
147
|
+
provider: Pick<OcxProviderConfig, "allowPrivateNetwork">,
|
|
148
|
+
): boolean {
|
|
149
|
+
return provider.allowPrivateNetwork === true || registryAllowsPrivateNetwork(name);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function providerDestinationConfigError(name: string, provider: Pick<OcxProviderConfig, "baseUrl" | "allowPrivateNetwork">): string | null {
|
|
153
|
+
const assessment = assessDestination(provider.baseUrl);
|
|
154
|
+
if (!assessment) return null;
|
|
155
|
+
if (assessment.kind === "public" || assessment.kind === "hostname") return null;
|
|
156
|
+
if (assessment.kind === "metadata") return "baseUrl targets a blocked metadata endpoint";
|
|
157
|
+
if (providerAllowsPrivateNetwork(name, provider)) return null;
|
|
158
|
+
return `baseUrl points to a ${assessment.detail}; set allowPrivateNetwork:true only for intentionally local/self-hosted providers`;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function assertProviderDestinationAllowed(name: string, provider: Pick<OcxProviderConfig, "baseUrl" | "allowPrivateNetwork">): void {
|
|
162
|
+
const error = providerDestinationConfigError(name, provider);
|
|
163
|
+
if (error) throw new Error(`provider ${name} ${error}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Async companion to {@link providerDestinationConfigError} for hostname destinations:
|
|
168
|
+
* resolves A/AAAA records and classifies every address, so a hostname that points at
|
|
169
|
+
* loopback/private/metadata space is caught at provider write time (review finding,
|
|
170
|
+
* PR #96 — the sync path must stay literal-only because the router hot path and
|
|
171
|
+
* config load are synchronous). DNS failures return null: config-time validation is
|
|
172
|
+
* advisory and must not hard-fail offline startups. DNS rebinding after validation is
|
|
173
|
+
* a recorded residual for this loopback proxy (devlog 260712_pr_batch_landing 000).
|
|
174
|
+
*
|
|
175
|
+
* `allowBenchmarkAddresses` is only for the exact canonical ChatGPT Codex seed under
|
|
176
|
+
* Clash fake-IP DNS (198.18.0.0/15). Every other non-public answer — including mixed
|
|
177
|
+
* benchmark + private/metadata sets — still fails.
|
|
178
|
+
*/
|
|
179
|
+
export async function providerDestinationResolvedError(
|
|
180
|
+
name: string,
|
|
181
|
+
provider: Pick<OcxProviderConfig, "baseUrl" | "allowPrivateNetwork">,
|
|
182
|
+
options?: { allowBenchmarkAddresses?: boolean },
|
|
183
|
+
): Promise<string | null> {
|
|
184
|
+
const syncError = providerDestinationConfigError(name, provider);
|
|
185
|
+
if (syncError) return syncError;
|
|
186
|
+
let hostname: string;
|
|
187
|
+
try {
|
|
188
|
+
hostname = normalizeHostname(new URL(provider.baseUrl.trim()).hostname);
|
|
189
|
+
} catch {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
if (!hostname || isIP(hostname) !== 0 || hostname === "localhost" || hostname.endsWith(".localhost")) {
|
|
193
|
+
return null; // literals and localhost are fully handled by the sync path
|
|
194
|
+
}
|
|
195
|
+
if (providerAllowsPrivateNetwork(name, provider)) return null;
|
|
196
|
+
let addresses: { address: string }[];
|
|
197
|
+
try {
|
|
198
|
+
addresses = await lookup(hostname, { all: true, verbatim: true });
|
|
199
|
+
} catch {
|
|
200
|
+
return null; // unresolvable now ≠ malicious; the provider simply won't connect
|
|
201
|
+
}
|
|
202
|
+
for (const { address } of addresses) {
|
|
203
|
+
const ipKind = isIP(address);
|
|
204
|
+
const assessment = ipKind === 4 ? classifyIpv4(address) : ipKind === 6 ? classifyIpv6(normalizeHostname(address)) : null;
|
|
205
|
+
if (!assessment || assessment.kind === "public") continue;
|
|
206
|
+
// Clash fake-IP only: 198.18/19 benchmark detail. Mixed dangerous sets still reject.
|
|
207
|
+
if (
|
|
208
|
+
options?.allowBenchmarkAddresses
|
|
209
|
+
&& assessment.kind === "private"
|
|
210
|
+
&& assessment.detail === "benchmark address"
|
|
211
|
+
) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (assessment.kind === "metadata") return `baseUrl hostname ${hostname} resolves to a blocked metadata endpoint (${address})`;
|
|
215
|
+
return `baseUrl hostname ${hostname} resolves to a ${assessment.detail} (${address}); set allowPrivateNetwork:true only for intentionally local/self-hosted providers`;
|
|
216
|
+
}
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export interface UrlDestinationAssessment {
|
|
221
|
+
kind: DestinationKind;
|
|
222
|
+
detail: string;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export class DestinationDnsResolutionError extends Error {
|
|
226
|
+
override readonly name = "DestinationDnsResolutionError";
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Synchronous literal URL destination assessment — classifies the hostname
|
|
231
|
+
* without DNS resolution. Returns null for unparseable URLs.
|
|
232
|
+
*/
|
|
233
|
+
export function assessUrlDestination(url: string): UrlDestinationAssessment | null {
|
|
234
|
+
return assessDestination(url);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Async DNS-resolved URL safety check. By default, rejects every non-public
|
|
239
|
+
* address. Provider diagnostics may explicitly admit loopback/private answers;
|
|
240
|
+
* metadata, link-local, and unspecified addresses remain unconditionally denied.
|
|
241
|
+
* Returns the validated addresses so direct callers can pin the connect peer and
|
|
242
|
+
* avoid a second, rebindable resolution. DNS failures remain fail-closed here;
|
|
243
|
+
* the provider proxy wrapper alone may recognize that typed failure and degrade.
|
|
244
|
+
*/
|
|
245
|
+
export async function resolvePublicAddresses(
|
|
246
|
+
url: string,
|
|
247
|
+
options?: string | { context?: string; allowPrivateNetwork?: boolean },
|
|
248
|
+
): Promise<{
|
|
249
|
+
hostname: string;
|
|
250
|
+
addresses: { address: string; family: number }[];
|
|
251
|
+
privateNetwork: boolean;
|
|
252
|
+
}> {
|
|
253
|
+
const context = typeof options === "string"
|
|
254
|
+
? `${options.trim() || "image"} URL`
|
|
255
|
+
: options?.context?.trim() || "image URL";
|
|
256
|
+
const privateNetworkAllowed = typeof options === "object" && options?.allowPrivateNetwork === true;
|
|
257
|
+
let hostname: string;
|
|
258
|
+
try {
|
|
259
|
+
hostname = normalizeHostname(new URL(url.trim()).hostname);
|
|
260
|
+
} catch {
|
|
261
|
+
throw new Error(`${context} is not a valid URL`);
|
|
262
|
+
}
|
|
263
|
+
if (!hostname) throw new Error(`${context} has no hostname`);
|
|
264
|
+
const literalAssessment = assessDestination(url);
|
|
265
|
+
let privateNetwork = false;
|
|
266
|
+
if (literalAssessment && literalAssessment.kind !== "public" && literalAssessment.kind !== "hostname") {
|
|
267
|
+
const allowedPrivateLiteral = privateNetworkAllowed
|
|
268
|
+
&& (literalAssessment.kind === "localhost"
|
|
269
|
+
|| literalAssessment.kind === "loopback"
|
|
270
|
+
|| literalAssessment.kind === "private");
|
|
271
|
+
if (!allowedPrivateLiteral) throw new Error(`${context} targets ${literalAssessment.detail}`);
|
|
272
|
+
privateNetwork = true;
|
|
273
|
+
}
|
|
274
|
+
// Literal public IPs: no DNS round-trip; pin the literal itself.
|
|
275
|
+
const literalKind = isIP(hostname);
|
|
276
|
+
if (literalKind !== 0) {
|
|
277
|
+
return { hostname, addresses: [{ address: hostname, family: literalKind }], privateNetwork };
|
|
278
|
+
}
|
|
279
|
+
let addresses: { address: string; family: number }[];
|
|
280
|
+
try {
|
|
281
|
+
addresses = await lookup(hostname, { all: true, verbatim: true });
|
|
282
|
+
} catch {
|
|
283
|
+
// If DNS fails, we can't verify — fail-closed (unlike provider config-time validation,
|
|
284
|
+
// this is a runtime fetch to an untrusted URL, so be conservative).
|
|
285
|
+
throw new DestinationDnsResolutionError(`${context} hostname ${hostname} could not be resolved`);
|
|
286
|
+
}
|
|
287
|
+
if (addresses.length === 0) {
|
|
288
|
+
throw new DestinationDnsResolutionError(`${context} hostname ${hostname} could not be resolved`);
|
|
289
|
+
}
|
|
290
|
+
const validatedAddresses: { address: string; family: number }[] = [];
|
|
291
|
+
for (const { address, family } of addresses) {
|
|
292
|
+
// Prefer classifying from the address string itself — do not trust a mislabeled
|
|
293
|
+
// resolver `family` that could skip IPv4/IPv6 private checks.
|
|
294
|
+
const ipKind = isIP(address) || (family === 4 || family === 6 ? family : 0);
|
|
295
|
+
const assessment = ipKind === 4 ? classifyIpv4(address) : ipKind === 6 ? classifyIpv6(normalizeHostname(address)) : null;
|
|
296
|
+
if (!assessment || assessment.kind !== "public") {
|
|
297
|
+
const allowedPrivateAddress = privateNetworkAllowed
|
|
298
|
+
&& assessment
|
|
299
|
+
&& (assessment.kind === "loopback" || assessment.kind === "private");
|
|
300
|
+
if (!allowedPrivateAddress) {
|
|
301
|
+
throw new Error(`${context} hostname ${hostname} resolves to ${assessment?.detail ?? "an unsafe address"} (${address})`);
|
|
302
|
+
}
|
|
303
|
+
privateNetwork = true;
|
|
304
|
+
}
|
|
305
|
+
validatedAddresses.push({ address, family: ipKind === 4 || ipKind === 6 ? ipKind : (family || 4) });
|
|
306
|
+
}
|
|
307
|
+
return { hostname, addresses: validatedAddresses, privateNetwork };
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Void assertion wrapper around {@link resolvePublicAddresses} for call sites
|
|
312
|
+
* that only need the safety check.
|
|
313
|
+
*/
|
|
314
|
+
export async function assertUrlResolvesPublic(url: string): Promise<void> {
|
|
315
|
+
await resolvePublicAddresses(url);
|
|
316
|
+
}
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
export interface OcxErrorPayload {
|
|
2
|
+
message: string;
|
|
3
|
+
type: string;
|
|
4
|
+
code: string | null;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/** OpenAI / Codex hard block for high-risk cybersecurity activity (HTTP 400 or mid-stream). */
|
|
8
|
+
export const CYBER_POLICY_ERROR_CODE = "cyber_policy";
|
|
9
|
+
|
|
10
|
+
export function isCyberPolicyCode(code: string | null | undefined): boolean {
|
|
11
|
+
return code === CYBER_POLICY_ERROR_CODE;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Detect OpenAI cyber-policy refusals from message text when structured `code` was stripped.
|
|
16
|
+
* Matches Codex fallback copy and Cursor/API agent wording (session evidence 2026-07-24).
|
|
17
|
+
* Does not treat a bare `cyber_policy` token as conclusive — model ids / routing errors can
|
|
18
|
+
* include that substring without being a policy refusal.
|
|
19
|
+
*/
|
|
20
|
+
export function isCyberPolicyMessage(text: string): boolean {
|
|
21
|
+
const lower = text.toLowerCase();
|
|
22
|
+
// Serialized error-code signatures only (not bare model-id collisions).
|
|
23
|
+
if (/"code"\s*:\s*"cyber_policy"/.test(lower)) return true;
|
|
24
|
+
if (/\bcode\s*[:=]\s*["']?cyber_policy\b/.test(lower)) return true;
|
|
25
|
+
if (lower.includes("high-risk cybersecurity")) return true;
|
|
26
|
+
if (lower.includes("high-risk cyber activity") || lower.includes("high-risk cyber ")) return true;
|
|
27
|
+
if (lower.includes("possible cybersecurity risk")) return true;
|
|
28
|
+
if (lower.includes("flagged") && lower.includes("cybersecurity")) return true;
|
|
29
|
+
if (lower.includes("flagged") && lower.includes("cyber activity")) return true;
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function isSubscriptionGateMessage(text: string): boolean {
|
|
34
|
+
return (
|
|
35
|
+
text.includes("requires a subscription") ||
|
|
36
|
+
text.includes("requires subscription") ||
|
|
37
|
+
text.includes("subscription required") ||
|
|
38
|
+
text.includes("upgrade for access") ||
|
|
39
|
+
text.includes("upgrade to pro") ||
|
|
40
|
+
text.includes("pro subscription") ||
|
|
41
|
+
text.includes("ollama.com/upgrade") ||
|
|
42
|
+
(text.includes("upgrade") && text.includes("subscription"))
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function isAuthenticationMessage(text: string): boolean {
|
|
47
|
+
const accessDeniedWithCredentialCue = (
|
|
48
|
+
text.includes("access denied") ||
|
|
49
|
+
text.includes("accessdeniedexception")
|
|
50
|
+
) && (
|
|
51
|
+
text.includes("authentication") ||
|
|
52
|
+
text.includes("credential") ||
|
|
53
|
+
text.includes("api key") ||
|
|
54
|
+
text.includes("token") ||
|
|
55
|
+
text.includes("signature")
|
|
56
|
+
);
|
|
57
|
+
return (
|
|
58
|
+
text.includes("authentication failed") ||
|
|
59
|
+
text.includes("authentication") ||
|
|
60
|
+
text.includes("invalid_api_key") ||
|
|
61
|
+
text.includes("invalid api key") ||
|
|
62
|
+
text.includes("invalid token") ||
|
|
63
|
+
text.includes("unauthorizedexception") ||
|
|
64
|
+
text.includes("unrecognizedclientexception") ||
|
|
65
|
+
text.includes("unrecognizedclient") ||
|
|
66
|
+
text.includes("expired token") ||
|
|
67
|
+
text.includes("expiredtoken") ||
|
|
68
|
+
text.includes("unauthenticated") ||
|
|
69
|
+
text.includes("unauthorized") ||
|
|
70
|
+
accessDeniedWithCredentialCue
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function isPermissionMessage(text: string): boolean {
|
|
75
|
+
return (
|
|
76
|
+
text.includes("permission_denied") ||
|
|
77
|
+
text.includes("permission denied") ||
|
|
78
|
+
text.includes("forbidden") ||
|
|
79
|
+
text.includes("access denied") ||
|
|
80
|
+
text.includes("accessdeniedexception") ||
|
|
81
|
+
text.includes("not allowed to use") ||
|
|
82
|
+
text.includes("model access")
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Client cancelled / closed the turn. Matches ONLY abort phrases this codebase
|
|
88
|
+
* produces — "client closed request during web-search" (src/web-search/loop.ts),
|
|
89
|
+
* "Client cancelled request" (src/server/responses.ts) — plus the explicit
|
|
90
|
+
* "request cancel(l)ed by client" forms. Deliberately narrow: bare "client closed"
|
|
91
|
+
* would also swallow legitimate upstream failures like "upstream HTTP client
|
|
92
|
+
* closed idle connection" and turn a real 502 into a 499.
|
|
93
|
+
*/
|
|
94
|
+
export function isClientClosedMessage(text: string): boolean {
|
|
95
|
+
const lower = text.toLowerCase();
|
|
96
|
+
return (
|
|
97
|
+
lower.includes("client closed request") ||
|
|
98
|
+
lower.includes("client cancelled request") ||
|
|
99
|
+
lower.includes("client canceled request") ||
|
|
100
|
+
lower.includes("request canceled by client") ||
|
|
101
|
+
lower.includes("request cancelled by client")
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function classifyError(status: number, type: string, message: string): OcxErrorPayload {
|
|
106
|
+
const text = message.toLowerCase();
|
|
107
|
+
if (type === "previous_response_not_found") {
|
|
108
|
+
return { message, type: "invalid_request_error", code: "previous_response_not_found" };
|
|
109
|
+
}
|
|
110
|
+
// Preserve explicit cancel types used by compact/combo JSON errors; unify message-inferred
|
|
111
|
+
// client closes (web-search abort text) onto client_closed_request for /api/logs.
|
|
112
|
+
if (type === "client_cancelled") {
|
|
113
|
+
return { message, type: "client_cancelled", code: "client_cancelled" };
|
|
114
|
+
}
|
|
115
|
+
if (
|
|
116
|
+
status === 499 ||
|
|
117
|
+
type === "client_closed_request" ||
|
|
118
|
+
isClientClosedMessage(text)
|
|
119
|
+
) {
|
|
120
|
+
return { message, type: "invalid_request_error", code: "client_closed_request" };
|
|
121
|
+
}
|
|
122
|
+
// Codex only shows the dedicated cyber UI when error.code === "cyber_policy".
|
|
123
|
+
// Prefer that code (and invalid_request_error) over generic remaps / 502 upstream_server_error.
|
|
124
|
+
if (type === CYBER_POLICY_ERROR_CODE || isCyberPolicyMessage(text)) {
|
|
125
|
+
return { message, type: "invalid_request_error", code: CYBER_POLICY_ERROR_CODE };
|
|
126
|
+
}
|
|
127
|
+
if (
|
|
128
|
+
text.includes("context_length_exceeded") ||
|
|
129
|
+
text.includes("context window") ||
|
|
130
|
+
text.includes("context length") ||
|
|
131
|
+
text.includes("maximum context") ||
|
|
132
|
+
text.includes("too many tokens")
|
|
133
|
+
) {
|
|
134
|
+
return { message, type: "invalid_request_error", code: "context_length_exceeded" };
|
|
135
|
+
}
|
|
136
|
+
// "Cursor resource limit exceeded" is emitted only for explicit request-size overflow
|
|
137
|
+
// details (isCursorRequestTooLargeDetail in cursor-errors.ts); quota-style resource
|
|
138
|
+
// exhaustion arrives as "Cursor rate limit exceeded" and falls through to 429 below.
|
|
139
|
+
if (text.includes("cursor resource limit exceeded")) {
|
|
140
|
+
return { message, type: "invalid_request_error", code: "tool_catalog_too_large" };
|
|
141
|
+
}
|
|
142
|
+
// The Cursor adapter's classified rate-limit prefix is authoritative: its DETAIL may echo
|
|
143
|
+
// quota wording ("... quota exhausted") that would otherwise hit the insufficient_quota
|
|
144
|
+
// branch below and break the planned retry-with-backoff contract (WP3 review blocker 1).
|
|
145
|
+
if (text.includes("cursor rate limit exceeded")) {
|
|
146
|
+
return { message, type: "rate_limit_error", code: "rate_limit_exceeded" };
|
|
147
|
+
}
|
|
148
|
+
if (
|
|
149
|
+
text.includes("insufficient_quota") ||
|
|
150
|
+
text.includes("exceeded your current quota") ||
|
|
151
|
+
text.includes("quota exhausted") ||
|
|
152
|
+
text.includes("account quota exceeded") ||
|
|
153
|
+
text.includes("monthly quota exceeded") ||
|
|
154
|
+
text.includes("daily quota exceeded")
|
|
155
|
+
) {
|
|
156
|
+
return { message, type: "insufficient_quota", code: "insufficient_quota" };
|
|
157
|
+
}
|
|
158
|
+
if (
|
|
159
|
+
status === 429 ||
|
|
160
|
+
text.includes("rate limit") ||
|
|
161
|
+
text.includes("rate limited") ||
|
|
162
|
+
text.includes("too many requests") ||
|
|
163
|
+
text.includes("resource_exhausted") ||
|
|
164
|
+
text.includes("resource exhausted") ||
|
|
165
|
+
text.includes("throttlingexception") ||
|
|
166
|
+
text.includes("throttling")
|
|
167
|
+
) {
|
|
168
|
+
return { message, type: "rate_limit_error", code: "rate_limit_exceeded" };
|
|
169
|
+
}
|
|
170
|
+
if (type === "origin_rejected") {
|
|
171
|
+
return { message, type: "invalid_request_error", code: "origin_rejected" };
|
|
172
|
+
}
|
|
173
|
+
// HTTP 401 and explicit auth failures are authoritative even when provider text
|
|
174
|
+
// also advertises an upgrade or subscription.
|
|
175
|
+
if (
|
|
176
|
+
status === 401 ||
|
|
177
|
+
type === "authentication_error" ||
|
|
178
|
+
isAuthenticationMessage(text)
|
|
179
|
+
) {
|
|
180
|
+
return { message, type: "authentication_error", code: "invalid_api_key" };
|
|
181
|
+
}
|
|
182
|
+
// Subscription labels are valid only in a known permission context.
|
|
183
|
+
if (
|
|
184
|
+
(status === 403 || type === "permission_error") &&
|
|
185
|
+
isSubscriptionGateMessage(text)
|
|
186
|
+
) {
|
|
187
|
+
return { message, type: "permission_error", code: "subscription_required" };
|
|
188
|
+
}
|
|
189
|
+
if (
|
|
190
|
+
status === 403 ||
|
|
191
|
+
type === "permission_error" ||
|
|
192
|
+
isPermissionMessage(text)
|
|
193
|
+
) {
|
|
194
|
+
return { message, type: "permission_error", code: "permission_denied" };
|
|
195
|
+
}
|
|
196
|
+
if (
|
|
197
|
+
status === 503 ||
|
|
198
|
+
text.includes("overloaded") ||
|
|
199
|
+
text.includes("server is busy") ||
|
|
200
|
+
text.includes("temporarily unavailable")
|
|
201
|
+
) {
|
|
202
|
+
// Codex recognizes "server_is_overloaded" and applies retry-after backoff
|
|
203
|
+
// (responses.rs is_server_overloaded_error); generic "upstream_server_error" is not recognized.
|
|
204
|
+
return { message, type: "server_error", code: "server_is_overloaded" };
|
|
205
|
+
}
|
|
206
|
+
if (
|
|
207
|
+
text.includes("validationexception") ||
|
|
208
|
+
text.includes("invalid request") ||
|
|
209
|
+
text.includes("model unavailable") ||
|
|
210
|
+
text.includes("model not found") ||
|
|
211
|
+
text.includes("unsupported model") ||
|
|
212
|
+
text.includes("profile arn") ||
|
|
213
|
+
text.includes("wrong region") ||
|
|
214
|
+
text.includes("invalid region")
|
|
215
|
+
) {
|
|
216
|
+
return { message, type: "invalid_request_error", code: "invalid_request_error" };
|
|
217
|
+
}
|
|
218
|
+
if (status >= 500) {
|
|
219
|
+
return { message, type: "server_error", code: "upstream_server_error" };
|
|
220
|
+
}
|
|
221
|
+
if (status === 400 || type === "invalid_request_error") {
|
|
222
|
+
return { message, type: "invalid_request_error", code: "invalid_request_error" };
|
|
223
|
+
}
|
|
224
|
+
return { message, type, code: type || null };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* True when a provider failure should participate in rate-limit / quota health blocking.
|
|
229
|
+
* Reuses {@link classifyError} so generic 429 wording and quota phrases stay aligned.
|
|
230
|
+
*/
|
|
231
|
+
export function isRateLimitOrQuotaFailureMessage(message: string): boolean {
|
|
232
|
+
const normalized = String(message ?? "").trim();
|
|
233
|
+
if (!normalized) return false;
|
|
234
|
+
const numericStatus = Number(normalized);
|
|
235
|
+
if (numericStatus === 429 || numericStatus === 402) return true;
|
|
236
|
+
const statusHint = Number.isInteger(numericStatus) && numericStatus > 0 ? numericStatus : 0;
|
|
237
|
+
const classified = classifyError(statusHint, "", normalized);
|
|
238
|
+
if (
|
|
239
|
+
classified.type === "rate_limit_error"
|
|
240
|
+
|| classified.code === "rate_limit_exceeded"
|
|
241
|
+
|| classified.type === "insufficient_quota"
|
|
242
|
+
|| classified.code === "insufficient_quota"
|
|
243
|
+
) {
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
// Retained quota cue used by subagent health before classifyError covered it.
|
|
247
|
+
return normalized.toLowerCase().includes("usage limit");
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** Best-effort parse of a retry delay embedded in an upstream error message. */
|
|
251
|
+
export function parseRetryAfterFromMessage(message: string): number | undefined {
|
|
252
|
+
const patterns = [
|
|
253
|
+
/try again in (\d+(?:\.\d+)?)\s*s(?:ec(?:ond)?s?)?/i,
|
|
254
|
+
/retry after (\d+(?:\.\d+)?)\s*s(?:ec(?:ond)?s?)?/i,
|
|
255
|
+
/retry[- ]after[:\s]+(\d+)/i,
|
|
256
|
+
];
|
|
257
|
+
for (const pattern of patterns) {
|
|
258
|
+
const match = message.match(pattern);
|
|
259
|
+
if (!match?.[1]) continue;
|
|
260
|
+
const seconds = Number.parseFloat(match[1]);
|
|
261
|
+
if (Number.isFinite(seconds) && seconds > 0) return Math.ceil(seconds);
|
|
262
|
+
}
|
|
263
|
+
return undefined;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Infer HTTP status from adapter terminal error text (provider-agnostic keyword matching). */
|
|
267
|
+
export function inferHttpStatusFromAdapterMessage(message: string): number {
|
|
268
|
+
const lower = message.toLowerCase();
|
|
269
|
+
// Client aborts (e.g. mid web-search loop) must not look like upstream 502s in /api/logs.
|
|
270
|
+
if (isClientClosedMessage(lower)) return 499;
|
|
271
|
+
// Codex Transport maps cyber_policy only on HTTP 400 (SSE is code-based).
|
|
272
|
+
if (isCyberPolicyMessage(lower)) return 400;
|
|
273
|
+
// See classifyError: this prefix now only means explicit request-size overflow (400);
|
|
274
|
+
// quota-style Cursor resource exhaustion carries the rate-limit prefix and maps to 429.
|
|
275
|
+
if (lower.includes("cursor resource limit exceeded")) return 400;
|
|
276
|
+
if (
|
|
277
|
+
lower.includes("resource_exhausted") ||
|
|
278
|
+
lower.includes("resource exhausted") ||
|
|
279
|
+
lower.includes("rate limit") ||
|
|
280
|
+
lower.includes("too many requests") ||
|
|
281
|
+
lower.includes("throttling")
|
|
282
|
+
) return 429;
|
|
283
|
+
// Strong authentication signals win when a message contains mixed auth and
|
|
284
|
+
// subscription/permission wording.
|
|
285
|
+
if (isAuthenticationMessage(lower)) return 401;
|
|
286
|
+
if (isSubscriptionGateMessage(lower) || isPermissionMessage(lower)) return 403;
|
|
287
|
+
if (
|
|
288
|
+
lower.includes("unavailable") ||
|
|
289
|
+
lower.includes("overloaded") ||
|
|
290
|
+
lower.includes("temporarily") ||
|
|
291
|
+
lower.includes("server is busy")
|
|
292
|
+
) return 503;
|
|
293
|
+
if (
|
|
294
|
+
lower.includes("invalid") ||
|
|
295
|
+
lower.includes("not found") ||
|
|
296
|
+
lower.includes("unsupported") ||
|
|
297
|
+
lower.includes("malformed") ||
|
|
298
|
+
lower.includes("unimplemented")
|
|
299
|
+
) return 400;
|
|
300
|
+
if (
|
|
301
|
+
lower.includes("timed out") ||
|
|
302
|
+
lower.includes("timeout") ||
|
|
303
|
+
lower.includes("etimedout") ||
|
|
304
|
+
lower.includes("deadline")
|
|
305
|
+
) return 504;
|
|
306
|
+
return 502;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/** Map an adapter terminal error message to HTTP status + classified Codex error payload. */
|
|
310
|
+
export function adapterFailureFromMessage(message: string): { httpStatus: number; error: OcxErrorPayload } {
|
|
311
|
+
const httpStatus = inferHttpStatusFromAdapterMessage(message);
|
|
312
|
+
let finalMessage = message;
|
|
313
|
+
const retryAfterSeconds = parseRetryAfterFromMessage(message);
|
|
314
|
+
if (retryAfterSeconds && !/please try again in /i.test(message)) {
|
|
315
|
+
finalMessage = `${message} Please try again in ${retryAfterSeconds}s.`;
|
|
316
|
+
}
|
|
317
|
+
const errorType = httpStatus === 499
|
|
318
|
+
? "client_closed_request"
|
|
319
|
+
: httpStatus === 429
|
|
320
|
+
? "rate_limit_error"
|
|
321
|
+
: httpStatus === 401
|
|
322
|
+
? "authentication_error"
|
|
323
|
+
: httpStatus === 403
|
|
324
|
+
? "permission_error"
|
|
325
|
+
: httpStatus === 503 || httpStatus === 504
|
|
326
|
+
? "server_error"
|
|
327
|
+
: httpStatus === 400
|
|
328
|
+
? "invalid_request_error"
|
|
329
|
+
: "upstream_error";
|
|
330
|
+
return {
|
|
331
|
+
httpStatus,
|
|
332
|
+
error: classifyError(httpStatus, errorType, finalMessage),
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/** Map a terminal Responses error object to the HTTP status we record in /api/logs. */
|
|
337
|
+
export function httpStatusFromTerminalError(error: {
|
|
338
|
+
type?: string;
|
|
339
|
+
code?: string | null;
|
|
340
|
+
message?: string;
|
|
341
|
+
} | undefined): number {
|
|
342
|
+
if (!error) return 502;
|
|
343
|
+
if (error.code === "client_closed_request" || error.code === "client_cancelled") return 499;
|
|
344
|
+
if (isCyberPolicyCode(error.code) || (error.message ? isCyberPolicyMessage(error.message) : false)) {
|
|
345
|
+
return 400;
|
|
346
|
+
}
|
|
347
|
+
if (error.type === "rate_limit_error" || error.code === "rate_limit_exceeded") return 429;
|
|
348
|
+
if (error.type === "authentication_error" || error.code === "invalid_api_key") return 401;
|
|
349
|
+
if (
|
|
350
|
+
error.type === "permission_error" ||
|
|
351
|
+
error.code === "permission_denied" ||
|
|
352
|
+
error.code === "subscription_required"
|
|
353
|
+
) return 403;
|
|
354
|
+
if (error.type === "insufficient_quota" || error.code === "insufficient_quota") return 429;
|
|
355
|
+
if (error.type === "server_error" && error.code === "server_is_overloaded") return 503;
|
|
356
|
+
// Client-closed messages often arrive as invalid_request_error after classifyError; check message
|
|
357
|
+
// before treating every invalid_request_error as HTTP 400.
|
|
358
|
+
const message = error.message ?? "";
|
|
359
|
+
if (message && isClientClosedMessage(message)) return 499;
|
|
360
|
+
if (error.type === "invalid_request_error") return 400;
|
|
361
|
+
if (error.type === "proxy_error") return 500;
|
|
362
|
+
if (message) return inferHttpStatusFromAdapterMessage(message);
|
|
363
|
+
return 502;
|
|
364
|
+
}
|