diff --git a/.env.example b/.env.example index 4aaa4b31fb..724b6a899a 100644 --- a/.env.example +++ b/.env.example @@ -132,6 +132,11 @@ OMNIROUTE_USE_TURBOPACK=1 # Used by: src/lib/credentialHealth/scheduler.ts # OMNIROUTE_DISABLE_CREDENTIAL_HEALTH_CHECK=false +# Set to "true" to emit `[ProxyFetch]` debug logs from the Vercel relay path +# in open-sse/utils/proxyFetch.ts. Off by default to avoid leaking routing +# hints in production logs. +# OMNIROUTE_PROXY_FETCH_DEBUG=true + # Docker production port mappings (docker-compose.prod.yml only). # These set the HOST-side published ports. Container ports use PORT/API_PORT. # PROD_DASHBOARD_PORT=20130 @@ -666,6 +671,11 @@ CODEX_USER_AGENT="codex-cli/0.132.0 (Windows 10.0.26200; x64)" GITHUB_USER_AGENT="GitHubCopilotChat/0.45.1" ANTIGRAVITY_USER_AGENT="antigravity/2.0.1 linux/arm64 google-api-nodejs-client/10.3.0" KIRO_USER_AGENT="AWS-SDK-JS/3.0.0 kiro-ide/1.0.0" +# Optional override for the Kiro social device-code OAuth clientId. Kiro's +# device endpoint accepts any non-empty string and behaves like a User-Agent +# rather than a secret. Only override if AWS ever starts enforcing this field. +# Used by: src/lib/oauth/constants/oauth.ts (KIRO_CONFIG.socialClientId). +# KIRO_OAUTH_CLIENT_ID=kiro-cli QODER_USER_AGENT="Qoder-Cli" QWEN_USER_AGENT="QwenCode/0.15.11 (linux; x64)" CURSOR_USER_AGENT="Cursor/3.4" diff --git a/.source/browser.ts b/.source/browser.ts index c3bc050c5b..11f0eef61a 100644 --- a/.source/browser.ts +++ b/.source/browser.ts @@ -7,6 +7,6 @@ const create = browser(); const browserCollections = { - docs: create.doc("docs", {"architecture/ARCHITECTURE.md": () => import("../docs/architecture/ARCHITECTURE.md?collection=docs"), "architecture/AUTHZ_GUIDE.md": () => import("../docs/architecture/AUTHZ_GUIDE.md?collection=docs"), "architecture/CODEBASE_DOCUMENTATION.md": () => import("../docs/architecture/CODEBASE_DOCUMENTATION.md?collection=docs"), "architecture/REPOSITORY_MAP.md": () => import("../docs/architecture/REPOSITORY_MAP.md?collection=docs"), "architecture/RESILIENCE_GUIDE.md": () => import("../docs/architecture/RESILIENCE_GUIDE.md?collection=docs"), "compression/COMPRESSION_ENGINES.md": () => import("../docs/compression/COMPRESSION_ENGINES.md?collection=docs"), "compression/COMPRESSION_GUIDE.md": () => import("../docs/compression/COMPRESSION_GUIDE.md?collection=docs"), "compression/COMPRESSION_LANGUAGE_PACKS.md": () => import("../docs/compression/COMPRESSION_LANGUAGE_PACKS.md?collection=docs"), "compression/COMPRESSION_RULES_FORMAT.md": () => import("../docs/compression/COMPRESSION_RULES_FORMAT.md?collection=docs"), "compression/RTK_COMPRESSION.md": () => import("../docs/compression/RTK_COMPRESSION.md?collection=docs"), "frameworks/A2A-SERVER.md": () => import("../docs/frameworks/A2A-SERVER.md?collection=docs"), "frameworks/AGENT_PROTOCOLS_GUIDE.md": () => import("../docs/frameworks/AGENT_PROTOCOLS_GUIDE.md?collection=docs"), "frameworks/CLOUD_AGENT.md": () => import("../docs/frameworks/CLOUD_AGENT.md?collection=docs"), "frameworks/EMBEDDED-SERVICES.md": () => import("../docs/frameworks/EMBEDDED-SERVICES.md?collection=docs"), "frameworks/EVALS.md": () => import("../docs/frameworks/EVALS.md?collection=docs"), "frameworks/GAMIFICATION.md": () => import("../docs/frameworks/GAMIFICATION.md?collection=docs"), "frameworks/MCP-SERVER.md": () => import("../docs/frameworks/MCP-SERVER.md?collection=docs"), "frameworks/MEMORY.md": () => import("../docs/frameworks/MEMORY.md?collection=docs"), "frameworks/OPENCODE.md": () => import("../docs/frameworks/OPENCODE.md?collection=docs"), "frameworks/SKILLS.md": () => import("../docs/frameworks/SKILLS.md?collection=docs"), "frameworks/WEBHOOKS.md": () => import("../docs/frameworks/WEBHOOKS.md?collection=docs"), "guides/DOCKER_GUIDE.md": () => import("../docs/guides/DOCKER_GUIDE.md?collection=docs"), "guides/ELECTRON_GUIDE.md": () => import("../docs/guides/ELECTRON_GUIDE.md?collection=docs"), "guides/FEATURES.md": () => import("../docs/guides/FEATURES.md?collection=docs"), "guides/I18N.md": () => import("../docs/guides/I18N.md?collection=docs"), "guides/KIRO_SETUP.md": () => import("../docs/guides/KIRO_SETUP.md?collection=docs"), "guides/PWA_GUIDE.md": () => import("../docs/guides/PWA_GUIDE.md?collection=docs"), "guides/SETUP_GUIDE.md": () => import("../docs/guides/SETUP_GUIDE.md?collection=docs"), "guides/TERMUX_GUIDE.md": () => import("../docs/guides/TERMUX_GUIDE.md?collection=docs"), "guides/TROUBLESHOOTING.md": () => import("../docs/guides/TROUBLESHOOTING.md?collection=docs"), "guides/UNINSTALL.md": () => import("../docs/guides/UNINSTALL.md?collection=docs"), "guides/USER_GUIDE.md": () => import("../docs/guides/USER_GUIDE.md?collection=docs"), "ops/COVERAGE_PLAN.md": () => import("../docs/ops/COVERAGE_PLAN.md?collection=docs"), "ops/E2E_DASHBOARD_SHAKEDOWN_v3.8.0.md": () => import("../docs/ops/E2E_DASHBOARD_SHAKEDOWN_v3.8.0.md?collection=docs"), "ops/FLY_IO_DEPLOYMENT_GUIDE.md": () => import("../docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md?collection=docs"), "ops/PROXY_GUIDE.md": () => import("../docs/ops/PROXY_GUIDE.md?collection=docs"), "ops/RELEASE_CHECKLIST.md": () => import("../docs/ops/RELEASE_CHECKLIST.md?collection=docs"), "ops/SQLITE_RUNTIME.md": () => import("../docs/ops/SQLITE_RUNTIME.md?collection=docs"), "ops/TUNNELS_GUIDE.md": () => import("../docs/ops/TUNNELS_GUIDE.md?collection=docs"), "ops/VM_DEPLOYMENT_GUIDE.md": () => import("../docs/ops/VM_DEPLOYMENT_GUIDE.md?collection=docs"), "reference/API_REFERENCE.md": () => import("../docs/reference/API_REFERENCE.md?collection=docs"), "reference/CLI-TOOLS.md": () => import("../docs/reference/CLI-TOOLS.md?collection=docs"), "reference/ENVIRONMENT.md": () => import("../docs/reference/ENVIRONMENT.md?collection=docs"), "reference/FREE_TIERS.md": () => import("../docs/reference/FREE_TIERS.md?collection=docs"), "reference/PROVIDER_REFERENCE.md": () => import("../docs/reference/PROVIDER_REFERENCE.md?collection=docs"), "routing/AUTO-COMBO.md": () => import("../docs/routing/AUTO-COMBO.md?collection=docs"), "routing/REASONING_REPLAY.md": () => import("../docs/routing/REASONING_REPLAY.md?collection=docs"), "security/CLI_TOKEN.md": () => import("../docs/security/CLI_TOKEN.md?collection=docs"), "security/CLI_TOKEN_AUTH.md": () => import("../docs/security/CLI_TOKEN_AUTH.md?collection=docs"), "security/COMPLIANCE.md": () => import("../docs/security/COMPLIANCE.md?collection=docs"), "security/ERROR_SANITIZATION.md": () => import("../docs/security/ERROR_SANITIZATION.md?collection=docs"), "security/GUARDRAILS.md": () => import("../docs/security/GUARDRAILS.md?collection=docs"), "security/PUBLIC_CREDS.md": () => import("../docs/security/PUBLIC_CREDS.md?collection=docs"), "security/ROUTE_GUARD_TIERS.md": () => import("../docs/security/ROUTE_GUARD_TIERS.md?collection=docs"), "security/STEALTH_GUIDE.md": () => import("../docs/security/STEALTH_GUIDE.md?collection=docs"), }), + docs: create.doc("docs", {"architecture/ARCHITECTURE.md": () => import("../docs/architecture/ARCHITECTURE.md?collection=docs"), "architecture/AUTHZ_GUIDE.md": () => import("../docs/architecture/AUTHZ_GUIDE.md?collection=docs"), "architecture/CODEBASE_DOCUMENTATION.md": () => import("../docs/architecture/CODEBASE_DOCUMENTATION.md?collection=docs"), "architecture/REPOSITORY_MAP.md": () => import("../docs/architecture/REPOSITORY_MAP.md?collection=docs"), "architecture/RESILIENCE_GUIDE.md": () => import("../docs/architecture/RESILIENCE_GUIDE.md?collection=docs"), "compression/COMPRESSION_ENGINES.md": () => import("../docs/compression/COMPRESSION_ENGINES.md?collection=docs"), "compression/COMPRESSION_GUIDE.md": () => import("../docs/compression/COMPRESSION_GUIDE.md?collection=docs"), "compression/COMPRESSION_LANGUAGE_PACKS.md": () => import("../docs/compression/COMPRESSION_LANGUAGE_PACKS.md?collection=docs"), "compression/COMPRESSION_RULES_FORMAT.md": () => import("../docs/compression/COMPRESSION_RULES_FORMAT.md?collection=docs"), "compression/RTK_COMPRESSION.md": () => import("../docs/compression/RTK_COMPRESSION.md?collection=docs"), "guides/DOCKER_GUIDE.md": () => import("../docs/guides/DOCKER_GUIDE.md?collection=docs"), "guides/ELECTRON_GUIDE.md": () => import("../docs/guides/ELECTRON_GUIDE.md?collection=docs"), "guides/FEATURES.md": () => import("../docs/guides/FEATURES.md?collection=docs"), "guides/I18N.md": () => import("../docs/guides/I18N.md?collection=docs"), "guides/KIRO_SETUP.md": () => import("../docs/guides/KIRO_SETUP.md?collection=docs"), "guides/PWA_GUIDE.md": () => import("../docs/guides/PWA_GUIDE.md?collection=docs"), "guides/SETUP_GUIDE.md": () => import("../docs/guides/SETUP_GUIDE.md?collection=docs"), "guides/TERMUX_GUIDE.md": () => import("../docs/guides/TERMUX_GUIDE.md?collection=docs"), "guides/TROUBLESHOOTING.md": () => import("../docs/guides/TROUBLESHOOTING.md?collection=docs"), "guides/UNINSTALL.md": () => import("../docs/guides/UNINSTALL.md?collection=docs"), "guides/USER_GUIDE.md": () => import("../docs/guides/USER_GUIDE.md?collection=docs"), "frameworks/A2A-SERVER.md": () => import("../docs/frameworks/A2A-SERVER.md?collection=docs"), "frameworks/AGENT_PROTOCOLS_GUIDE.md": () => import("../docs/frameworks/AGENT_PROTOCOLS_GUIDE.md?collection=docs"), "frameworks/CLOUD_AGENT.md": () => import("../docs/frameworks/CLOUD_AGENT.md?collection=docs"), "frameworks/EMBEDDED-SERVICES.md": () => import("../docs/frameworks/EMBEDDED-SERVICES.md?collection=docs"), "frameworks/EVALS.md": () => import("../docs/frameworks/EVALS.md?collection=docs"), "frameworks/GAMIFICATION.md": () => import("../docs/frameworks/GAMIFICATION.md?collection=docs"), "frameworks/MCP-SERVER.md": () => import("../docs/frameworks/MCP-SERVER.md?collection=docs"), "frameworks/MEMORY.md": () => import("../docs/frameworks/MEMORY.md?collection=docs"), "frameworks/OPENCODE.md": () => import("../docs/frameworks/OPENCODE.md?collection=docs"), "frameworks/SKILLS.md": () => import("../docs/frameworks/SKILLS.md?collection=docs"), "frameworks/WEBHOOKS.md": () => import("../docs/frameworks/WEBHOOKS.md?collection=docs"), "ops/COVERAGE_PLAN.md": () => import("../docs/ops/COVERAGE_PLAN.md?collection=docs"), "ops/E2E_DASHBOARD_SHAKEDOWN_v3.8.0.md": () => import("../docs/ops/E2E_DASHBOARD_SHAKEDOWN_v3.8.0.md?collection=docs"), "ops/FLY_IO_DEPLOYMENT_GUIDE.md": () => import("../docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md?collection=docs"), "ops/PROXY_GUIDE.md": () => import("../docs/ops/PROXY_GUIDE.md?collection=docs"), "ops/RELEASE_CHECKLIST.md": () => import("../docs/ops/RELEASE_CHECKLIST.md?collection=docs"), "ops/SQLITE_RUNTIME.md": () => import("../docs/ops/SQLITE_RUNTIME.md?collection=docs"), "ops/TUNNELS_GUIDE.md": () => import("../docs/ops/TUNNELS_GUIDE.md?collection=docs"), "ops/VM_DEPLOYMENT_GUIDE.md": () => import("../docs/ops/VM_DEPLOYMENT_GUIDE.md?collection=docs"), "reference/API_REFERENCE.md": () => import("../docs/reference/API_REFERENCE.md?collection=docs"), "reference/CLI-TOOLS.md": () => import("../docs/reference/CLI-TOOLS.md?collection=docs"), "reference/ENVIRONMENT.md": () => import("../docs/reference/ENVIRONMENT.md?collection=docs"), "reference/FREE_TIERS.md": () => import("../docs/reference/FREE_TIERS.md?collection=docs"), "reference/PROVIDER_REFERENCE.md": () => import("../docs/reference/PROVIDER_REFERENCE.md?collection=docs"), "routing/AUTO-COMBO.md": () => import("../docs/routing/AUTO-COMBO.md?collection=docs"), "routing/REASONING_REPLAY.md": () => import("../docs/routing/REASONING_REPLAY.md?collection=docs"), "security/CLI_TOKEN.md": () => import("../docs/security/CLI_TOKEN.md?collection=docs"), "security/CLI_TOKEN_AUTH.md": () => import("../docs/security/CLI_TOKEN_AUTH.md?collection=docs"), "security/COMPLIANCE.md": () => import("../docs/security/COMPLIANCE.md?collection=docs"), "security/ERROR_SANITIZATION.md": () => import("../docs/security/ERROR_SANITIZATION.md?collection=docs"), "security/GUARDRAILS.md": () => import("../docs/security/GUARDRAILS.md?collection=docs"), "security/PUBLIC_CREDS.md": () => import("../docs/security/PUBLIC_CREDS.md?collection=docs"), "security/ROUTE_GUARD_TIERS.md": () => import("../docs/security/ROUTE_GUARD_TIERS.md?collection=docs"), "security/STEALTH_GUIDE.md": () => import("../docs/security/STEALTH_GUIDE.md?collection=docs"), }), }; export default browserCollections; \ No newline at end of file diff --git a/.source/server.ts b/.source/server.ts index ea427fcb51..fd7a6a93e8 100644 --- a/.source/server.ts +++ b/.source/server.ts @@ -4,9 +4,9 @@ import { default as __fd_glob_63 } from "../docs/routing/meta.json?collection=do import { default as __fd_glob_62 } from "../docs/reference/openapi.yaml?collection=docs" import { default as __fd_glob_61 } from "../docs/reference/meta.json?collection=docs" import { default as __fd_glob_60 } from "../docs/ops/meta.json?collection=docs" -import { default as __fd_glob_59 } from "../docs/guides/meta.json?collection=docs" -import { default as __fd_glob_58 } from "../docs/frameworks/meta.json?collection=docs" -import { default as __fd_glob_57 } from "../docs/compression/meta.json?collection=docs" +import { default as __fd_glob_59 } from "../docs/compression/meta.json?collection=docs" +import { default as __fd_glob_58 } from "../docs/guides/meta.json?collection=docs" +import { default as __fd_glob_57 } from "../docs/frameworks/meta.json?collection=docs" import { default as __fd_glob_56 } from "../docs/architecture/meta.json?collection=docs" import { default as __fd_glob_55 } from "../docs/meta.json?collection=docs" import * as __fd_glob_54 from "../docs/security/STEALTH_GUIDE.md?collection=docs" @@ -32,28 +32,28 @@ import * as __fd_glob_35 from "../docs/ops/PROXY_GUIDE.md?collection=docs" import * as __fd_glob_34 from "../docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md?collection=docs" import * as __fd_glob_33 from "../docs/ops/E2E_DASHBOARD_SHAKEDOWN_v3.8.0.md?collection=docs" import * as __fd_glob_32 from "../docs/ops/COVERAGE_PLAN.md?collection=docs" -import * as __fd_glob_31 from "../docs/guides/USER_GUIDE.md?collection=docs" -import * as __fd_glob_30 from "../docs/guides/UNINSTALL.md?collection=docs" -import * as __fd_glob_29 from "../docs/guides/TROUBLESHOOTING.md?collection=docs" -import * as __fd_glob_28 from "../docs/guides/TERMUX_GUIDE.md?collection=docs" -import * as __fd_glob_27 from "../docs/guides/SETUP_GUIDE.md?collection=docs" -import * as __fd_glob_26 from "../docs/guides/PWA_GUIDE.md?collection=docs" -import * as __fd_glob_25 from "../docs/guides/KIRO_SETUP.md?collection=docs" -import * as __fd_glob_24 from "../docs/guides/I18N.md?collection=docs" -import * as __fd_glob_23 from "../docs/guides/FEATURES.md?collection=docs" -import * as __fd_glob_22 from "../docs/guides/ELECTRON_GUIDE.md?collection=docs" -import * as __fd_glob_21 from "../docs/guides/DOCKER_GUIDE.md?collection=docs" -import * as __fd_glob_20 from "../docs/frameworks/WEBHOOKS.md?collection=docs" -import * as __fd_glob_19 from "../docs/frameworks/SKILLS.md?collection=docs" -import * as __fd_glob_18 from "../docs/frameworks/OPENCODE.md?collection=docs" -import * as __fd_glob_17 from "../docs/frameworks/MEMORY.md?collection=docs" -import * as __fd_glob_16 from "../docs/frameworks/MCP-SERVER.md?collection=docs" -import * as __fd_glob_15 from "../docs/frameworks/GAMIFICATION.md?collection=docs" -import * as __fd_glob_14 from "../docs/frameworks/EVALS.md?collection=docs" -import * as __fd_glob_13 from "../docs/frameworks/EMBEDDED-SERVICES.md?collection=docs" -import * as __fd_glob_12 from "../docs/frameworks/CLOUD_AGENT.md?collection=docs" -import * as __fd_glob_11 from "../docs/frameworks/AGENT_PROTOCOLS_GUIDE.md?collection=docs" -import * as __fd_glob_10 from "../docs/frameworks/A2A-SERVER.md?collection=docs" +import * as __fd_glob_31 from "../docs/frameworks/WEBHOOKS.md?collection=docs" +import * as __fd_glob_30 from "../docs/frameworks/SKILLS.md?collection=docs" +import * as __fd_glob_29 from "../docs/frameworks/OPENCODE.md?collection=docs" +import * as __fd_glob_28 from "../docs/frameworks/MEMORY.md?collection=docs" +import * as __fd_glob_27 from "../docs/frameworks/MCP-SERVER.md?collection=docs" +import * as __fd_glob_26 from "../docs/frameworks/GAMIFICATION.md?collection=docs" +import * as __fd_glob_25 from "../docs/frameworks/EVALS.md?collection=docs" +import * as __fd_glob_24 from "../docs/frameworks/EMBEDDED-SERVICES.md?collection=docs" +import * as __fd_glob_23 from "../docs/frameworks/CLOUD_AGENT.md?collection=docs" +import * as __fd_glob_22 from "../docs/frameworks/AGENT_PROTOCOLS_GUIDE.md?collection=docs" +import * as __fd_glob_21 from "../docs/frameworks/A2A-SERVER.md?collection=docs" +import * as __fd_glob_20 from "../docs/guides/USER_GUIDE.md?collection=docs" +import * as __fd_glob_19 from "../docs/guides/UNINSTALL.md?collection=docs" +import * as __fd_glob_18 from "../docs/guides/TROUBLESHOOTING.md?collection=docs" +import * as __fd_glob_17 from "../docs/guides/TERMUX_GUIDE.md?collection=docs" +import * as __fd_glob_16 from "../docs/guides/SETUP_GUIDE.md?collection=docs" +import * as __fd_glob_15 from "../docs/guides/PWA_GUIDE.md?collection=docs" +import * as __fd_glob_14 from "../docs/guides/KIRO_SETUP.md?collection=docs" +import * as __fd_glob_13 from "../docs/guides/I18N.md?collection=docs" +import * as __fd_glob_12 from "../docs/guides/FEATURES.md?collection=docs" +import * as __fd_glob_11 from "../docs/guides/ELECTRON_GUIDE.md?collection=docs" +import * as __fd_glob_10 from "../docs/guides/DOCKER_GUIDE.md?collection=docs" import * as __fd_glob_9 from "../docs/compression/RTK_COMPRESSION.md?collection=docs" import * as __fd_glob_8 from "../docs/compression/COMPRESSION_RULES_FORMAT.md?collection=docs" import * as __fd_glob_7 from "../docs/compression/COMPRESSION_LANGUAGE_PACKS.md?collection=docs" @@ -72,4 +72,4 @@ const create = server({"doc":{"passthroughs":["extractedReferences"]}}); -export const docs = await create.docs("docs", "docs", {"meta.json": __fd_glob_55, "architecture/meta.json": __fd_glob_56, "compression/meta.json": __fd_glob_57, "frameworks/meta.json": __fd_glob_58, "guides/meta.json": __fd_glob_59, "ops/meta.json": __fd_glob_60, "reference/meta.json": __fd_glob_61, "reference/openapi.yaml": __fd_glob_62, "routing/meta.json": __fd_glob_63, "security/meta.json": __fd_glob_64, }, {"architecture/ARCHITECTURE.md": __fd_glob_0, "architecture/AUTHZ_GUIDE.md": __fd_glob_1, "architecture/CODEBASE_DOCUMENTATION.md": __fd_glob_2, "architecture/REPOSITORY_MAP.md": __fd_glob_3, "architecture/RESILIENCE_GUIDE.md": __fd_glob_4, "compression/COMPRESSION_ENGINES.md": __fd_glob_5, "compression/COMPRESSION_GUIDE.md": __fd_glob_6, "compression/COMPRESSION_LANGUAGE_PACKS.md": __fd_glob_7, "compression/COMPRESSION_RULES_FORMAT.md": __fd_glob_8, "compression/RTK_COMPRESSION.md": __fd_glob_9, "frameworks/A2A-SERVER.md": __fd_glob_10, "frameworks/AGENT_PROTOCOLS_GUIDE.md": __fd_glob_11, "frameworks/CLOUD_AGENT.md": __fd_glob_12, "frameworks/EMBEDDED-SERVICES.md": __fd_glob_13, "frameworks/EVALS.md": __fd_glob_14, "frameworks/GAMIFICATION.md": __fd_glob_15, "frameworks/MCP-SERVER.md": __fd_glob_16, "frameworks/MEMORY.md": __fd_glob_17, "frameworks/OPENCODE.md": __fd_glob_18, "frameworks/SKILLS.md": __fd_glob_19, "frameworks/WEBHOOKS.md": __fd_glob_20, "guides/DOCKER_GUIDE.md": __fd_glob_21, "guides/ELECTRON_GUIDE.md": __fd_glob_22, "guides/FEATURES.md": __fd_glob_23, "guides/I18N.md": __fd_glob_24, "guides/KIRO_SETUP.md": __fd_glob_25, "guides/PWA_GUIDE.md": __fd_glob_26, "guides/SETUP_GUIDE.md": __fd_glob_27, "guides/TERMUX_GUIDE.md": __fd_glob_28, "guides/TROUBLESHOOTING.md": __fd_glob_29, "guides/UNINSTALL.md": __fd_glob_30, "guides/USER_GUIDE.md": __fd_glob_31, "ops/COVERAGE_PLAN.md": __fd_glob_32, "ops/E2E_DASHBOARD_SHAKEDOWN_v3.8.0.md": __fd_glob_33, "ops/FLY_IO_DEPLOYMENT_GUIDE.md": __fd_glob_34, "ops/PROXY_GUIDE.md": __fd_glob_35, "ops/RELEASE_CHECKLIST.md": __fd_glob_36, "ops/SQLITE_RUNTIME.md": __fd_glob_37, "ops/TUNNELS_GUIDE.md": __fd_glob_38, "ops/VM_DEPLOYMENT_GUIDE.md": __fd_glob_39, "reference/API_REFERENCE.md": __fd_glob_40, "reference/CLI-TOOLS.md": __fd_glob_41, "reference/ENVIRONMENT.md": __fd_glob_42, "reference/FREE_TIERS.md": __fd_glob_43, "reference/PROVIDER_REFERENCE.md": __fd_glob_44, "routing/AUTO-COMBO.md": __fd_glob_45, "routing/REASONING_REPLAY.md": __fd_glob_46, "security/CLI_TOKEN.md": __fd_glob_47, "security/CLI_TOKEN_AUTH.md": __fd_glob_48, "security/COMPLIANCE.md": __fd_glob_49, "security/ERROR_SANITIZATION.md": __fd_glob_50, "security/GUARDRAILS.md": __fd_glob_51, "security/PUBLIC_CREDS.md": __fd_glob_52, "security/ROUTE_GUARD_TIERS.md": __fd_glob_53, "security/STEALTH_GUIDE.md": __fd_glob_54, }); \ No newline at end of file +export const docs = await create.docs("docs", "docs", {"meta.json": __fd_glob_55, "architecture/meta.json": __fd_glob_56, "frameworks/meta.json": __fd_glob_57, "guides/meta.json": __fd_glob_58, "compression/meta.json": __fd_glob_59, "ops/meta.json": __fd_glob_60, "reference/meta.json": __fd_glob_61, "reference/openapi.yaml": __fd_glob_62, "routing/meta.json": __fd_glob_63, "security/meta.json": __fd_glob_64, }, {"architecture/ARCHITECTURE.md": __fd_glob_0, "architecture/AUTHZ_GUIDE.md": __fd_glob_1, "architecture/CODEBASE_DOCUMENTATION.md": __fd_glob_2, "architecture/REPOSITORY_MAP.md": __fd_glob_3, "architecture/RESILIENCE_GUIDE.md": __fd_glob_4, "compression/COMPRESSION_ENGINES.md": __fd_glob_5, "compression/COMPRESSION_GUIDE.md": __fd_glob_6, "compression/COMPRESSION_LANGUAGE_PACKS.md": __fd_glob_7, "compression/COMPRESSION_RULES_FORMAT.md": __fd_glob_8, "compression/RTK_COMPRESSION.md": __fd_glob_9, "guides/DOCKER_GUIDE.md": __fd_glob_10, "guides/ELECTRON_GUIDE.md": __fd_glob_11, "guides/FEATURES.md": __fd_glob_12, "guides/I18N.md": __fd_glob_13, "guides/KIRO_SETUP.md": __fd_glob_14, "guides/PWA_GUIDE.md": __fd_glob_15, "guides/SETUP_GUIDE.md": __fd_glob_16, "guides/TERMUX_GUIDE.md": __fd_glob_17, "guides/TROUBLESHOOTING.md": __fd_glob_18, "guides/UNINSTALL.md": __fd_glob_19, "guides/USER_GUIDE.md": __fd_glob_20, "frameworks/A2A-SERVER.md": __fd_glob_21, "frameworks/AGENT_PROTOCOLS_GUIDE.md": __fd_glob_22, "frameworks/CLOUD_AGENT.md": __fd_glob_23, "frameworks/EMBEDDED-SERVICES.md": __fd_glob_24, "frameworks/EVALS.md": __fd_glob_25, "frameworks/GAMIFICATION.md": __fd_glob_26, "frameworks/MCP-SERVER.md": __fd_glob_27, "frameworks/MEMORY.md": __fd_glob_28, "frameworks/OPENCODE.md": __fd_glob_29, "frameworks/SKILLS.md": __fd_glob_30, "frameworks/WEBHOOKS.md": __fd_glob_31, "ops/COVERAGE_PLAN.md": __fd_glob_32, "ops/E2E_DASHBOARD_SHAKEDOWN_v3.8.0.md": __fd_glob_33, "ops/FLY_IO_DEPLOYMENT_GUIDE.md": __fd_glob_34, "ops/PROXY_GUIDE.md": __fd_glob_35, "ops/RELEASE_CHECKLIST.md": __fd_glob_36, "ops/SQLITE_RUNTIME.md": __fd_glob_37, "ops/TUNNELS_GUIDE.md": __fd_glob_38, "ops/VM_DEPLOYMENT_GUIDE.md": __fd_glob_39, "reference/API_REFERENCE.md": __fd_glob_40, "reference/CLI-TOOLS.md": __fd_glob_41, "reference/ENVIRONMENT.md": __fd_glob_42, "reference/FREE_TIERS.md": __fd_glob_43, "reference/PROVIDER_REFERENCE.md": __fd_glob_44, "routing/AUTO-COMBO.md": __fd_glob_45, "routing/REASONING_REPLAY.md": __fd_glob_46, "security/CLI_TOKEN.md": __fd_glob_47, "security/CLI_TOKEN_AUTH.md": __fd_glob_48, "security/COMPLIANCE.md": __fd_glob_49, "security/ERROR_SANITIZATION.md": __fd_glob_50, "security/GUARDRAILS.md": __fd_glob_51, "security/PUBLIC_CREDS.md": __fd_glob_52, "security/ROUTE_GUARD_TIERS.md": __fd_glob_53, "security/STEALTH_GUIDE.md": __fd_glob_54, }); \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b33cf1497..4bb773aed0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,54 +2,12 @@ ## [Unreleased] -## [3.8.5] β€” 2026-05-26 +## [3.8.4] β€” 2026-05-26 ### πŸ”’ Security -- **authz:** redirect `/home` and `/home/:path*` to `/login` when unauthenticated β€” Next.js middleware matcher omitted `/home`, so any visit reached the page directly on `REQUIRE_LOGIN` deployments (#2712) -- **review:** resolve v3.8.4 important + minor findings from consolidated review including SSRF guards (#2749) - -### πŸ”§ Bug Fixes - -- **mcp:** break callLogs ↔ compliance ESM cycle that deadlocks the bundled MCP server on Node.js 24 β€” extract no-log state to `compliance/noLog.ts`, switch callers to the leaf module, keep `compliance/index.ts` re-exports for backwards compat (#2650) -- **deepseek:** guard PoW solver Web Worker handler so `require()` no longer throws `ReferenceError: onmessage is not defined` under Node strict mode (#2724) -- **combos:** include no-auth providers (FreeAIAPIKey, BluesMinds, FreeModel.dev, opencode, …) in the combo builder picker β€” they were invisible because they never get rows in `provider_connections` (#2737) -- **translator:** allow the `web_search` server-tool family (`web_search_20250305`, `web_search_20250101`, plain `web_search`) in the Responses API translator and preserve the original versioned name on output (#2695) -- **oauth:** register the missing `trae` provider with `import_token` flow so the Trae IDE no longer 500s during token import (#2658) -- **model:** merge settings-based aliases with the legacy DB alias namespace so aliases set via the Settings UI (e.g. `gpt-5.4 β†’ cx/gpt-5.4`) are honored instead of being overridden by provider inference (#2618, #2208) -- **kiro:** fall back to `document.execCommand("copy")` when the Clipboard API is unavailable (HTTP/non-secure contexts), so the "Copy authorization link" button works on LAN deployments (#2689) -- **cli:** raise `omniroute serve` ready timeout from 20s to 60s and add a TCP-listening fallback so Windows users no longer get phantom timeouts during slow Next.js cold start (#2460) -- **mcp:** break circular await deadlock in complianceβ†’callLogs + Kiro refresh resilience (#2747) -- **ui:** claude-web provider shows 'API Key' label instead of 'Session Cookie' (#2744) -- **deepseek-web:** lazy start session refresh (#2742) -- **docker:** keep fumadocs doc assets in Docker build context (#2741) -- **vision-bridge:** force bridge for opencode-go/zen models that overstate vision support (#2740) -- **combos:** enable universal handoff by default to preserve cross-model conversation context (#2736) - -### πŸ† Hall de Contribuidores - -Um agradecimento especial a todos que contribuΓ­ram com cΓ³digo, revisΓ΅es e testes para este release: -@diegosouzapw, @disonjer, @herjarsa, @janeza2, @oyi77, @thanet-s - -## [Unreleased] - -### ✨ New Features - -- **proxy:** serverless relay endpoints with rate limiting (#2734) -- **pwa:** enhanced manifest + push notification support (#2733) -- **auth:** API key groups with model-level permissions (#2732) -- **playground:** combo routing visual simulator (#2731) -- **resilience:** credential health check + adaptive circuit breaker (#2730) - -### ♻️ Refactors - -- **openapi:** api endpoints audit (#2729) - -### πŸ”§ Bug Fixes - -- **db:** hotfix migration version collision (068_services + 068_webhooks_kind_metadata) (#2727) - -## [3.8.4] β€” 2026-05-25 +- **authz:** redirect `/home` and `/home/:path*` to `/login` when unauthenticated β€” Next.js middleware matcher omitted `/home`, so any visit reached the page directly on `REQUIRE_LOGIN` deployments (#2712 β€” thanks @diegosouzapw) +- **review:** resolve v3.8.4 important + minor findings from consolidated review including SSRF guards (#2749 β€” thanks @diegosouzapw) ### ✨ New Features @@ -71,6 +29,11 @@ Um agradecimento especial a todos que contribuΓ­ram com cΓ³digo, revisΓ΅es e tes - **feat(sla):** SLA dashboard with uptime/latency/error rate queries, summary/trend APIs, uptime badges and sparklines - **feat(routing-analytics):** AI-powered usage pattern analysis and routing recommendations β€” combo_metrics queries, hourly failure heatmap, provider breakdown, cost-vs-latency scatter chart - **feat(teams):** fixed team execution with 13 git worktrees and project-level team configs +- **feat(providers):** add Inner.ai provider support with native executor, translation support, and model catalog definitions (#2704 β€” thanks @df4p) +- **feat(proxy):** unified free proxy pool, Vercel Relay serverless endpoints, and a redesigned 4-tab proxy dashboard interface (#2705 β€” thanks @diegosouzapw) +- **feat(webhooks):** 3-step configuration wizard for Slack, Telegram, Discord, and Custom webhook destinations, with reorganized React components (#2703 β€” thanks @diegosouzapw) +- **feat(openapi):** comprehensive API endpoints content audit with 100% schema coverage, authz security tiers, and full i18n localization support (#2701 β€” thanks @diegosouzapw) +- **feat(providers):** add BluesMinds, FreeModel.dev, and FreeAIAPIKey to the provider catalog (#2709 β€” thanks @oyi77) - **chore(deps):** added ws + @types/ws for WebSocket support, recharts ^3.8.1 for analytics charts ### πŸ”§ Bug Fixes @@ -78,6 +41,30 @@ Um agradecimento especial a todos que contribuΓ­ram com cΓ³digo, revisΓ΅es e tes - Fix combo cascade skipping on credential check timeout - Fix team sessions going idle (worktree initialization) - **feat(providers):** enhance Google Gemini, CLI, and Antigravity resilience and features β€” introduces explicit TypeScript typing to translation layers, adds new Gemini 2.0 models, implements backoff and retry logic in the Gemini CLI executor, extracts Google Search grounding metadata into standard `citations`, and adds backend definitions for the `vertex-partner` provider. ([#2676](https://github.com/diegosouzapw/OmniRoute/pull/2676) β€” thanks @alltomatos) +- **fix(proxy):** atomically save and assign custom dashboard proxies in a single SQLite transaction, preventing orphan configuration rows (#2697 β€” thanks @terence71-glitch) +- **fix(reasoning):** inject thinking blocks into Claude-format messages for Kimi K2 to prevent infinite tool-calling loops (#2699 β€” thanks @herjarsa) +- **fix(antigravity):** default exhausted quota status display to 0% instead of 100% (#2700 β€” thanks @ahmet-cetinkaya) +- **fix(electron):** add Caps Lock indicator, custom reset warnings, and suppress shell window spawning on startup (#2714 β€” thanks @benzntech) +- **fix(combos):** resolve context handoff tags ordering issue and enforce a 60-second request timeout limit per combo target to prevent capacity leaks (#2717 β€” thanks @herjarsa) +- **fix(oauth):** resolve parallel token refresh race conditions in Codex and implement comprehensive error checking across OAuth providers (#2718 β€” thanks @diegosouzapw) +- **fix(docker):** install `python3`, `make`, and `g++` in the Docker builder stage to support native Node.js addon compilation (#2713 β€” thanks @mrmm) +- **fix(i18n):** restore real hint and placeholder translation strings for web-cookie providers in `en.json` (#2694 β€” thanks @diegosouzapw) +- **fix(db):** resolve migration version prefix collision between services and webhook metadata tables (#2727 β€” thanks @diegosouzapw) +- **fix(vision-bridge):** ensure images are processed when a vision-capable model is matched through a combo routing mapping (#2706 β€” thanks @herjarsa) +- **mcp:** break callLogs ↔ compliance ESM cycle that deadlocks the bundled MCP server on Node.js 24 β€” extract no-log state to `compliance/noLog.ts`, switch callers to the leaf module, keep `compliance/index.ts` re-exports for backwards compat (#2650 β€” thanks @disonjer) +- **deepseek:** guard PoW solver Web Worker handler so `require()` no longer throws `ReferenceError: onmessage is not defined` under Node strict mode (#2724 β€” thanks @thanet-s) +- **combos:** include no-auth providers (FreeAIAPIKey, BluesMinds, FreeModel.dev, opencode, …) in the combo builder picker β€” they were invisible because they never get rows in `provider_connections` (#2737 β€” thanks @herjarsa) +- **translator:** allow the `web_search` server-tool family (`web_search_20250305`, `web_search_20250101`, plain `web_search`) in the Responses API translator and preserve the original versioned name on output (#2695 β€” thanks @diegosouzapw) +- **oauth:** register the missing `trae` provider with `import_token` flow so the Trae IDE no longer 500s during token import (#2658 β€” thanks @diegosouzapw) +- **model:** merge settings-based aliases with the legacy DB alias namespace so aliases set via the Settings UI (e.g. `gpt-5.4 β†’ cx/gpt-5.4`) are honored instead of being overridden by provider inference (#2618, #2208 β€” thanks @diegosouzapw) +- **kiro:** fall back to `document.execCommand("copy")` when the Clipboard API is unavailable (HTTP/non-secure contexts), so the "Copy authorization link" button works on LAN deployments (#2689 β€” thanks @disonjer) +- **cli:** raise `omniroute serve` ready timeout from 20s to 60s and add a TCP-listening fallback so Windows users no longer get phantom timeouts during slow Next.js cold start (#2460 β€” thanks @benzntech) +- **mcp:** break circular await deadlock in complianceβ†’callLogs + Kiro refresh resilience (#2747 β€” thanks @disonjer) +- **ui:** claude-web provider shows 'API Key' label instead of 'Session Cookie' (#2744 β€” thanks @oyi77) +- **deepseek-web:** lazy start session refresh (#2742 β€” thanks @thanet-s) +- **docker:** keep fumadocs doc assets in Docker build context (#2741 β€” thanks @janeza2) +- **vision-bridge:** force bridge for opencode-go/zen models that overstate vision support (#2740 β€” thanks @herjarsa) +- **combos:** enable universal handoff by default to preserve cross-model conversation context (#2736 β€” thanks @herjarsa) ### πŸš€ Embedded Services @@ -95,6 +82,11 @@ Um agradecimento especial a todos que contribuΓ­ram com cΓ³digo, revisΓ΅es e tes - **DB migration 071** (originally 068, renumbered post-merge to avoid collision with `068_free_proxies` and `068_webhooks_kind_metadata`) β€” extends `version_manager` table with `autoStart`, `autoUpdate`, `providerExpose`, `apiKey`, and `port` columns. `migrationRunner.ts` now throws at boot if two `.sql` files share the same numeric prefix. - All service routes classified as `LOCAL_ONLY` in `routeGuard.ts`; loopback enforcement is unconditional before any auth check (leaked JWT via tunnel cannot trigger process spawning). +### πŸ† Hall de Contribuidores + +Um agradecimento especial a todos que contribuΓ­ram com cΓ³digo, revisΓ΅es e testes para este release: +@ahmet-cetinkaya, @alltomatos, @benzntech, @Chewji9875, @df4p, @diegosouzapw, @disonjer, @herjarsa, @janeza2, @mrmm, @oyi77, @thanet-s, @terence71-glitch + --- ## [3.8.3] β€” 2026-05-24 diff --git a/docs/reference/ENVIRONMENT.md b/docs/reference/ENVIRONMENT.md index 4b05c1646e..9fa757f844 100644 --- a/docs/reference/ENVIRONMENT.md +++ b/docs/reference/ENVIRONMENT.md @@ -93,6 +93,7 @@ OmniRoute uses **SQLite** (via `better-sqlite3`) for all persistence. These vari | `OMNIROUTE_MIGRATIONS_DIR` | _(auto-detect)_ | `src/lib/db/migrationRunner.ts` | Override the directory that the migration runner scans. Useful when shipping bundled migrations in custom builds. | | `OMNIROUTE_SPEND_FLUSH_INTERVAL_MS` | _(default in code)_ | `src/lib/spend/batchWriter.ts` | Flush interval (ms) for the batched spend/cost writer. Lower values reduce write coalescing; higher values reduce DB contention. | | `OMNIROUTE_SPEND_MAX_BUFFER_SIZE` | _(default in code)_ | `src/lib/spend/batchWriter.ts` | Max buffered spend entries before a forced flush. Raise on high-QPS deployments; lower when bounded memory matters more. | +| `OMNIROUTE_PROXY_FETCH_DEBUG` | _(unset)_ | `open-sse/utils/proxyFetch.ts` | Set to `"true"` to emit `[ProxyFetch]` debug logs on the Vercel relay path. Off by default to avoid leaking routing hints. | | `BATCH_RETRY_DURATION_MS` | `86400000` (24h) | `open-sse/services/batchProcessor.ts` | Maximum retry window for individual batch items (ms). Items exceeding this duration are marked failed. | | `BATCH_BACKOFF_BASE_MS` | `5000` | `open-sse/services/batchProcessor.ts` | Base delay (ms) for exponential backoff on batch item retries. | | `BATCH_BACKOFF_MAX_MS` | `3600000` (1h) | `open-sse/services/batchProcessor.ts` | Cap (ms) for exponential backoff between batch item retries. | @@ -430,6 +431,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`] | `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | | `ANTIGRAVITY_USER_AGENT` | `antigravity/2.0.1 darwin/arm64` | When Antigravity IDE updates | | `KIRO_USER_AGENT` | `AWS-SDK-JS/3.0.0 kiro-ide/1.0.0` | When Kiro IDE updates | +| `KIRO_OAUTH_CLIENT_ID` | `kiro-cli` | Override the Kiro social device-code `clientId` (public id) | | `QODER_USER_AGENT` | `Qoder-Cli` | When Qoder CLI updates | | `QWEN_USER_AGENT` | `QwenCode/0.15.9 (linux; x64)` | When Qwen Code updates | | `CURSOR_USER_AGENT` | `Cursor/3.3` | When Cursor updates | diff --git a/docs/reference/openapi.yaml b/docs/reference/openapi.yaml index 1bd8d0dc14..6ffd673952 100644 --- a/docs/reference/openapi.yaml +++ b/docs/reference/openapi.yaml @@ -1,7 +1,7 @@ openapi: 3.1.0 info: title: OmniRoute API - version: 3.8.5 + version: 3.8.4 description: | OmniRoute is a local-first AI API proxy router. It provides an OpenAI-compatible endpoint that routes requests to multiple AI providers with load balancing, diff --git a/open-sse/executors/base.ts b/open-sse/executors/base.ts index 50bd8ac16d..fb5fb9993c 100644 --- a/open-sse/executors/base.ts +++ b/open-sse/executors/base.ts @@ -590,25 +590,47 @@ export class BaseExecutor { ); if (refreshed && !proactivePersistRan) { - // Classify unrecoverable sentinels before spreading. Without this guard - // an { error: "unrecoverable_refresh_error", code } object passes the - // truthy check and pollutes activeCredentials with garbage, causing the - // executor to send a non-token to upstream. Mirrors the reactive path - // in chatCore.ts and keeps the proactive path's behaviour consistent. + // ───────────────────────────────────────────────────────────────────── + // ⚠️ SOURCE OF TRUTH β€” do not flip the proactive path back to + // "persist expired+inactive". Ask the operator first. + // + // History (do not repeat past regressions): + // - ad3d4b696 (#2718, 2026-05-25): per-connection mutex + onPersist + // wiring so multi-account Codex (rotating refresh tokens) stops + // hitting refresh_token_reused under concurrent load. + // - 0c94c397d (#2743, 2026-05-26): a multi-agent review added a + // `await onCredentialsRefreshed({ testStatus: "expired", + // isActive: false })` here. That BROKE multi-account Codex β€” + // transient sentinels (refresh_token_reused recoverable via + // rotation map; generic invalid_request blips) were treated as + // terminal, so the proactive path sequentially disabled + // working accounts in the DB before any upstream call confirmed + // the failure. Reverted intentionally. + // + // Contract for the PROACTIVE refresh path: + // - Classify the sentinel ONLY to avoid spreading it into + // activeCredentials (which would send a non-token upstream). + // - DO NOT persist `{ testStatus: "expired", isActive: false }` + // from here. That decision belongs to the REACTIVE path in + // open-sse/handlers/chatCore.ts:~3912, which runs AFTER the + // upstream confirmed the auth failure. By then the rotation + // map (tokenRefresh.ts:~1541) and the DB-staleness check have + // already had their chance to recover the request. + // + // If a future review/agent thinks the expired-flip is "missing" + // here, STOP β€” flipping it here re-introduces the multi-account + // Codex regression. Discuss with the operator before touching. + // ───────────────────────────────────────────────────────────────────── if (isUnrecoverableRefreshError(refreshed)) { const refreshCode = (refreshed as Record).code; - log?.error?.( + log?.warn?.( "TOKEN", - `${this.provider.toUpperCase()} | unrecoverable refresh β€” marking account expired (code=${String(refreshCode ?? "unknown")})` + `${this.provider.toUpperCase()} | proactive refresh returned unrecoverable sentinel (code=${String(refreshCode ?? "unknown")}); keeping stale credentials, deferring to reactive path.` ); - if (onCredentialsRefreshed) { - await onCredentialsRefreshed({ - testStatus: "expired", - isActive: false, - }); - } - // Don't spread the sentinel β€” keep stale credentials so the next - // upstream call surfaces the real auth error to the client. + // Intentionally NOT spreading the sentinel and NOT persisting + // expired status. The next upstream call either succeeds (rotation + // map / DB-staleness saved us) or fails β€” chatCore.ts then marks + // the account expired with confidence. } else { activeCredentials = { ...credentials, diff --git a/package-lock.json b/package-lock.json index 929734a310..c9b9cf2be6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "omniroute", - "version": "3.8.5", + "version": "3.8.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "omniroute", - "version": "3.8.5", + "version": "3.8.4", "hasInstallScript": true, "license": "MIT", "workspaces": [ @@ -90,6 +90,7 @@ "@testing-library/react": "^16.3.2", "@types/bcryptjs": "^3.0.0", "@types/better-sqlite3": "^7.6.13", + "@types/bun": "latest", "@types/keytar": "^4.4.2", "@types/node": "^25.9.1", "@types/react": "^19.2.15", @@ -5277,6 +5278,16 @@ "@types/node": "*" } }, + "node_modules/@types/bun": { + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.3.14.tgz", + "integrity": "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bun-types": "1.3.14" + } + }, "node_modules/@types/chai": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", @@ -7360,6 +7371,16 @@ "node": ">=8.0.0" } }, + "node_modules/bun-types": { + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.3.14.tgz", + "integrity": "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/bundle-name": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", diff --git a/package.json b/package.json index 884a186db3..2d3b3c8b88 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "omniroute", - "version": "3.8.5", + "version": "3.8.4", "description": "Unified AI router with 160+ providers, RTK+Caveman compression, auto fallback, MCP/A2A, desktop, PWA, and OpenAI-compatible APIs.", "type": "module", "bin": { @@ -259,7 +259,12 @@ "postcss": "^8.5.14", "ip-address": "10.2.0", "qs": "^6.15.2", - "uuid": "^14.0.0" + "uuid": "^14.0.0", + "cli-table3": { + "ansi-regex": "^5.0.1", + "strip-ansi": "^6.0.1", + "string-width": "^4.2.3" + } }, "private": true } diff --git a/src/app/(dashboard)/dashboard/providers/components/ProviderCard.tsx b/src/app/(dashboard)/dashboard/providers/components/ProviderCard.tsx index bb78097cab..962a5345b3 100644 --- a/src/app/(dashboard)/dashboard/providers/components/ProviderCard.tsx +++ b/src/app/(dashboard)/dashboard/providers/components/ProviderCard.tsx @@ -1,7 +1,7 @@ "use client"; import type { MouseEvent, ReactNode } from "react"; -import { useState } from "react"; +import { useState, useMemo } from "react"; import Image from "next/image"; import Link from "next/link"; import { useTranslations } from "next-intl"; @@ -29,19 +29,6 @@ interface ProviderStats { codexFastActive?: boolean; } -const KIND_LABEL: Record = { - llm: "Chat", - embedding: "Embed", - image: "Image", - imageToText: "Iβ†’T", - tts: "TTS", - stt: "STT", - webSearch: "Search", - webFetch: "Fetch", - video: "Video", - music: "Music", -}; - interface ProviderCardProps { providerId: string; provider: { @@ -62,20 +49,6 @@ interface ProviderCardProps { onToggle: (active: boolean) => void; } -const DOT_COLORS: Record = { - free: "bg-green-500", - "no-auth": "bg-stone-500", - oauth: "bg-blue-500", - apikey: "bg-amber-500", - compatible: "bg-orange-500", - "web-cookie": "bg-purple-500", - search: "bg-teal-500", - audio: "bg-rose-500", - local: "bg-emerald-500", - "upstream-proxy": "bg-indigo-500", - "cloud-agent": "bg-violet-500", -}; - function getStatusDisplay( connected: number, error: number, @@ -128,6 +101,39 @@ export default function ProviderCard({ const tp = useTranslations("miniPlayground"); const [testExpanded, setTestExpanded] = useState(false); + const KIND_LABEL = useMemo>( + () => ({ + llm: "Chat", + embedding: "Embed", + image: "Image", + imageToText: "Iβ†’T", + tts: "TTS", + stt: "STT", + webSearch: "Search", + webFetch: "Fetch", + video: "Video", + music: "Music", + }), + [] + ); + + const DOT_COLORS = useMemo>( + () => ({ + free: "bg-green-500", + "no-auth": "bg-stone-500", + oauth: "bg-blue-500", + apikey: "bg-amber-500", + compatible: "bg-orange-500", + "web-cookie": "bg-purple-500", + search: "bg-teal-500", + audio: "bg-rose-500", + local: "bg-emerald-500", + "upstream-proxy": "bg-indigo-500", + "cloud-agent": "bg-violet-500", + }), + [] + ); + // Show the Test button for LLM providers (when serviceKinds includes "llm" // OR when the provider has no explicit serviceKinds but is a regular LLM provider // i.e. not a search/audio/cloud-agent type). diff --git a/src/app/api/oauth/kiro/social-authorize/route.ts b/src/app/api/oauth/kiro/social-authorize/route.ts index 77003fdede..0afd321ef2 100755 --- a/src/app/api/oauth/kiro/social-authorize/route.ts +++ b/src/app/api/oauth/kiro/social-authorize/route.ts @@ -1,7 +1,6 @@ import { NextResponse } from "next/server"; import { isAuthRequired, isAuthenticated } from "@/shared/utils/apiAuth"; - -const KIRO_AUTH_SERVICE = "https://prod.us-east-1.auth.desktop.kiro.dev"; +import { KIRO_CONFIG } from "@/lib/oauth/constants/oauth"; /** * GET /api/oauth/kiro/social-authorize @@ -26,11 +25,11 @@ export async function GET(request) { const loginProvider = provider === "google" ? "Google" : "Github"; - const response = await fetch(`${KIRO_AUTH_SERVICE}/oauth/device/authorization`, { + const response = await fetch(KIRO_CONFIG.socialDeviceAuthorizeUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ - clientId: "kiro-cli", + clientId: KIRO_CONFIG.socialClientId, loginProvider, }), }); diff --git a/src/app/api/oauth/kiro/social-exchange/route.ts b/src/app/api/oauth/kiro/social-exchange/route.ts index 5b779a255e..f97ac7dd06 100755 --- a/src/app/api/oauth/kiro/social-exchange/route.ts +++ b/src/app/api/oauth/kiro/social-exchange/route.ts @@ -6,8 +6,7 @@ import { getConsistentMachineId } from "@/shared/utils/machineId"; import { syncToCloud } from "@/lib/cloudSync"; import { isAuthRequired, isAuthenticated } from "@/shared/utils/apiAuth"; import { validateBody, isValidationFailure } from "@/shared/validation/helpers"; - -const KIRO_AUTH_SERVICE = "https://prod.us-east-1.auth.desktop.kiro.dev"; +import { KIRO_CONFIG } from "@/lib/oauth/constants/oauth"; const socialExchangeSchema = z.object({ deviceCode: z.string().min(1, "Missing deviceCode or provider"), @@ -50,10 +49,10 @@ export async function POST(request: Request) { try { const { deviceCode, provider } = validation.data; - const response = await fetch(`${KIRO_AUTH_SERVICE}/oauth/device/poll`, { + const response = await fetch(KIRO_CONFIG.socialDevicePollUrl, { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ deviceCode, clientId: "kiro-cli" }), + body: JSON.stringify({ deviceCode, clientId: KIRO_CONFIG.socialClientId }), }); const data = await response.json(); diff --git a/src/lib/oauth/constants/oauth.ts b/src/lib/oauth/constants/oauth.ts index 5f0e1a1ef2..6dfa1ee5ab 100644 --- a/src/lib/oauth/constants/oauth.ts +++ b/src/lib/oauth/constants/oauth.ts @@ -258,6 +258,15 @@ export const KIRO_CONFIG = { socialLoginUrl: "https://prod.us-east-1.auth.desktop.kiro.dev/login", socialTokenUrl: "https://prod.us-east-1.auth.desktop.kiro.dev/oauth/token", socialRefreshUrl: "https://prod.us-east-1.auth.desktop.kiro.dev/refreshToken", + // Social device-code flow (Google/GitHub). + // `socialClientId` is a public CLI identifier β€” Kiro's device endpoint accepts + // any non-empty string and behaves like a User-Agent rather than a secret. + // The env override exists so operators on locked-down builds can pin a + // custom value if AWS ever starts enforcing this field (Hard Rule #11 spirit). + socialClientId: process.env.KIRO_OAUTH_CLIENT_ID || "kiro-cli", + socialDeviceAuthorizeUrl: + "https://prod.us-east-1.auth.desktop.kiro.dev/oauth/device/authorization", + socialDevicePollUrl: "https://prod.us-east-1.auth.desktop.kiro.dev/oauth/device/poll", // Auth methods authMethods: ["builder-id", "idc", "google", "github", "import"], }; diff --git a/tests/unit/authz/proxy-contract.test.ts b/tests/unit/authz/proxy-contract.test.ts new file mode 100644 index 0000000000..9989138fc4 --- /dev/null +++ b/tests/unit/authz/proxy-contract.test.ts @@ -0,0 +1,81 @@ +/** + * Next.js 16 Proxy File Contract β€” Lockdown Test + * + * Next.js 16 deprecated `middleware.ts` in favour of `proxy.ts` (commit + * 3fb72b973 renamed our copy to match the new convention). The framework + * only invokes this file when ALL of the following hold: + * + * 1. The file lives at `src/proxy.ts` (since `src/app` is the app dir). + * 2. It exports a function named exactly `proxy` (or default). + * 3. The function delegates to `runAuthzPipeline` with `enforce: true`. + * 4. The `config.matcher` covers every prefix routes are mounted under, + * so unauthenticated requests cannot slip past the centralized + * authorization tiers (PUBLIC / CLIENT_API / MANAGEMENT). + * + * Without ANY of these guarantees the pipeline silently becomes dead code + * and every `/api/*` route falls back to per-route self-enforcement, which + * is the failure mode that led to the v3.8.4 hardening pass. Lock the + * contract down here so a future rename / refactor cannot regress it + * unnoticed. + * + * @see docs/security/ROUTE_GUARD_TIERS.md + * @see https://nextjs.org/docs/app/api-reference/file-conventions/proxy + */ + +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; + +test("Next.js 16 proxy file exists at src/proxy.ts (not src/middleware.ts)", () => { + assert.ok(fs.existsSync("src/proxy.ts"), "src/proxy.ts must exist (Next.js 16 file convention)"); + assert.ok( + !fs.existsSync("src/middleware.ts"), + "src/middleware.ts must NOT exist β€” Next.js 16 deprecated middleware.ts, the active file is src/proxy.ts" + ); +}); + +test("proxy.ts exports a function named 'proxy' (Next.js 16 requires this exact name)", () => { + const content = fs.readFileSync("src/proxy.ts", "utf8"); + assert.match( + content, + /export\s+async\s+function\s+proxy\s*\(/, + "must export `async function proxy(...)` β€” Next.js 16 only invokes this exact name" + ); +}); + +test("proxy.ts delegates to runAuthzPipeline with enforce: true", () => { + const content = fs.readFileSync("src/proxy.ts", "utf8"); + assert.match( + content, + /runAuthzPipeline\([^)]*\{\s*enforce:\s*true\s*\}\s*\)/, + "must call runAuthzPipeline with { enforce: true } β€” otherwise the pipeline runs in observe-only mode and never blocks" + ); +}); + +test("proxy.ts config.matcher covers every /api/* route plus dashboard and v1 aliases", () => { + const content = fs.readFileSync("src/proxy.ts", "utf8"); + // Required prefixes β€” drop one and the corresponding routes go unguarded. + const requiredMatchers = [ + '"/api/:path*"', + '"/dashboard/:path*"', + '"/v1/:path*"', + '"/chat/:path*"', + '"/responses/:path*"', + '"/codex/:path*"', + '"/models"', + ]; + for (const matcher of requiredMatchers) { + assert.ok( + content.includes(matcher), + `proxy.ts config.matcher must include ${matcher} β€” otherwise routes under that prefix bypass the authz pipeline` + ); + } +}); + +test("proxy.ts does not declare runtime: 'edge' (Next.js 16 proxy is Node-only)", () => { + const content = fs.readFileSync("src/proxy.ts", "utf8"); + assert.ok( + !/runtime:\s*['"]edge['"]/.test(content), + "proxy.ts MUST NOT set runtime: 'edge' β€” Next.js 16 only supports nodejs in proxy.ts. The pipeline depends on Node-only modules (jose, better-sqlite3) and would crash at request time on the edge runtime." + ); +}); diff --git a/tests/unit/oauth-kiro.test.ts b/tests/unit/oauth-kiro.test.ts new file mode 100644 index 0000000000..ddf1fd62dc --- /dev/null +++ b/tests/unit/oauth-kiro.test.ts @@ -0,0 +1,76 @@ +/** + * Kiro IDE OAuth Configuration β€” Lockdown Tests + * + * Ensures the Kiro device-code social-login flow reads its CLI identifier and + * AWS auth-service URLs from `KIRO_CONFIG` instead of inline literals. The + * strings (`"kiro-cli"` and `https://prod.us-east-1.auth.desktop.kiro.dev/...`) + * used to live duplicated across `social-authorize/route.ts` and + * `social-exchange/route.ts`. Centralising them avoids drift if AWS ever + * publishes a per-customer client id and matches the + * `CLAUDE_CONFIG`/`CODEX_CONFIG` pattern that allows operators to override + * via `process.env.KIRO_OAUTH_CLIENT_ID` (Hard Rule #11 spirit β€” operators + * can pin a custom id without patching source). + */ + +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import { KIRO_CONFIG } from "../../src/lib/oauth/constants/oauth"; + +test("KIRO_CONFIG exposes the social-flow client id and device-code URLs", () => { + assert.equal(typeof KIRO_CONFIG.socialClientId, "string"); + assert.ok(KIRO_CONFIG.socialClientId.length > 0, "socialClientId must be non-empty"); + assert.ok( + KIRO_CONFIG.socialDeviceAuthorizeUrl.startsWith("https://"), + "socialDeviceAuthorizeUrl must use HTTPS" + ); + assert.ok( + KIRO_CONFIG.socialDevicePollUrl.startsWith("https://"), + "socialDevicePollUrl must use HTTPS" + ); + assert.ok( + KIRO_CONFIG.socialDeviceAuthorizeUrl.endsWith("/oauth/device/authorization"), + "socialDeviceAuthorizeUrl must point at the AWS Kiro device authorize endpoint" + ); + assert.ok( + KIRO_CONFIG.socialDevicePollUrl.endsWith("/oauth/device/poll"), + "socialDevicePollUrl must point at the AWS Kiro device poll endpoint" + ); +}); + +test("KIRO_CONFIG.socialClientId default matches the public CLI identifier 'kiro-cli'", () => { + // We only assert the default when no env override is set, so CI can pin a + // custom id via KIRO_OAUTH_CLIENT_ID without breaking this test. + if (!process.env.KIRO_OAUTH_CLIENT_ID) { + assert.equal(KIRO_CONFIG.socialClientId, "kiro-cli"); + } +}); + +test("Kiro social-flow routes do not duplicate the AWS auth URL or 'kiro-cli' literal", () => { + // Catches future refactors that copy a hard-coded URL/identifier back into + // either route. The string "kiro-cli" may still appear as an env-var name + // in comments, so we grep for the specific *literal* shapes we replaced. + const routePaths = [ + "src/app/api/oauth/kiro/social-authorize/route.ts", + "src/app/api/oauth/kiro/social-exchange/route.ts", + ]; + for (const routePath of routePaths) { + const content = fs.readFileSync(routePath, "utf8"); + assert.ok( + content.includes('from "@/lib/oauth/constants/oauth"'), + `${routePath} must import KIRO_CONFIG from @/lib/oauth/constants/oauth` + ); + assert.ok( + content.includes("KIRO_CONFIG.socialClientId"), + `${routePath} must reference KIRO_CONFIG.socialClientId instead of the "kiro-cli" literal` + ); + assert.ok( + !/['"]kiro-cli['"]/.test(content), + `${routePath} must NOT inline the "kiro-cli" literal β€” use KIRO_CONFIG.socialClientId` + ); + assert.ok( + !/['"]https:\/\/prod\.us-east-1\.auth\.desktop\.kiro\.dev/.test(content), + `${routePath} must NOT inline the Kiro auth service URL β€” use KIRO_CONFIG.socialDeviceAuthorizeUrl / socialDevicePollUrl` + ); + } +});