miawrouter 1.2.3 → 1.2.4
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/README.md +7 -0
- package/app/.next-cli-build/BUILD_ID +1 -1
- package/app/.next-cli-build/app-path-routes-manifest.json +9 -9
- package/app/.next-cli-build/build-manifest.json +2 -2
- package/app/.next-cli-build/prerender-manifest.json +3 -3
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/a2a/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/a2a/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/activity/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/activity/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/agent-skills/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/agent-skills/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/api-endpoints/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/api-endpoints/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/basic-chat/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/basic-chat/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/batch/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/batch/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/cache/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/cache/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/cli-tools/[toolId]/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/cli-tools/[toolId]/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/cli-tools/page.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/cli-tools/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/cli-tools/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/cloud-agents/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/cloud-agents/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/combos/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/combos/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/console-log/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/console-log/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/costs/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/costs/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/discovery/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/discovery/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/endpoint/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/endpoint/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/free-provider-rankings/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/free-provider-rankings/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/free-tiers/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/free-tiers/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/guardrails/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/guardrails/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/health/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/health/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/logs/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/logs/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/mcp/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/mcp/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/media-providers/[kind]/[id]/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/media-providers/[kind]/[id]/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/media-providers/[kind]/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/media-providers/[kind]/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/media-providers/combo/[id]/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/media-providers/combo/[id]/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/media-providers/web/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/media-providers/web/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/memory/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/memory/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/mitm/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/mitm/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/playground/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/playground/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/privacy/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/privacy/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/profile/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/profile/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/provider-stats/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/provider-stats/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/providers/[id]/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/providers/[id]/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/providers/new/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/providers/new/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/providers/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/providers/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/proxy-pools/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/proxy-pools/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/pxpipe/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/pxpipe/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/quota/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/quota/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/runtime/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/runtime/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/settings/feature-flags/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/settings/feature-flags/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/settings/pricing/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/settings/pricing/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/skill-discovery/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/skill-discovery/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/skills/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/skills/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/token-saver/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/token-saver/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/translator/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/translator/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/usage/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/usage/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/webhooks/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/webhooks/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/.well-known/agent.json/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/_global-error/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/_global-error.html +1 -1
- package/app/.next-cli-build/server/app/_global-error.rsc +1 -1
- package/app/.next-cli-build/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/_not-found/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/_not-found.html +1 -1
- package/app/.next-cli-build/server/app/_not-found.rsc +5 -5
- package/app/.next-cli-build/server/app/_not-found.segments/_full.segment.rsc +5 -5
- package/app/.next-cli-build/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +5 -5
- package/app/.next-cli-build/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/api/a2a/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/a2a/status/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/agents/[id]/actions/route.js +1 -1
- package/app/.next-cli-build/server/app/api/agents/[id]/actions/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/agents/route.js +1 -1
- package/app/.next-cli-build/server/app/api/agents/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/auth/login/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/auth/oidc/callback/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/auth/oidc/start/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/auth/oidc/test/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/auth/reset-password/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/auth/status/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/batch/[id]/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/batch/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/batches/[id]/cancel/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/batches/[id]/errors/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/batches/[id]/results/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/batches/[id]/retry/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/batches/[id]/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/batches/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/cache/clear/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/cache/stats/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/catalog/opencode/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/cli-tools/all-statuses/route.js +5 -5
- package/app/.next-cli-build/server/app/api/cli-tools/all-statuses/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/cli-tools/antigravity-mitm/alias/route.js +2 -2
- package/app/.next-cli-build/server/app/api/cli-tools/antigravity-mitm/alias/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/cli-tools/antigravity-mitm/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/cli-tools/claude-settings/route.js +1 -1
- package/app/.next-cli-build/server/app/api/cli-tools/claude-settings/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/cli-tools/cline-settings/route.js +2 -2
- package/app/.next-cli-build/server/app/api/cli-tools/cline-settings/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/cli-tools/codex-settings/route.js +1 -1
- package/app/.next-cli-build/server/app/api/cli-tools/codex-settings/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/cli-tools/copilot-settings/route.js +2 -2
- package/app/.next-cli-build/server/app/api/cli-tools/copilot-settings/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/cli-tools/cowork-mcp-registry/route.js +2 -2
- package/app/.next-cli-build/server/app/api/cli-tools/cowork-mcp-registry/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/cli-tools/cowork-mcp-tools/route.js +1 -1
- package/app/.next-cli-build/server/app/api/cli-tools/cowork-mcp-tools/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/cli-tools/cowork-settings/route.js +1 -1
- package/app/.next-cli-build/server/app/api/cli-tools/cowork-settings/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/cli-tools/deepseek-tui-settings/route.js +3 -3
- package/app/.next-cli-build/server/app/api/cli-tools/deepseek-tui-settings/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/cli-tools/devin-settings/route.js +1 -1
- package/app/.next-cli-build/server/app/api/cli-tools/devin-settings/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/cli-tools/droid-settings/route.js +1 -1
- package/app/.next-cli-build/server/app/api/cli-tools/droid-settings/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/cli-tools/grok-build-settings/route.js +2 -2
- package/app/.next-cli-build/server/app/api/cli-tools/grok-build-settings/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/cli-tools/hermes-settings/route.js +2 -2
- package/app/.next-cli-build/server/app/api/cli-tools/hermes-settings/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/cli-tools/jcode-settings/route.js +2 -2
- package/app/.next-cli-build/server/app/api/cli-tools/jcode-settings/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/cli-tools/kilo-settings/route.js +2 -2
- package/app/.next-cli-build/server/app/api/cli-tools/kilo-settings/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/cli-tools/oh-my-pi-settings/route.js +2 -2
- package/app/.next-cli-build/server/app/api/cli-tools/oh-my-pi-settings/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/cli-tools/openclaw-settings/route.js +2 -2
- package/app/.next-cli-build/server/app/api/cli-tools/openclaw-settings/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/cli-tools/opencode-settings/route.js +2 -2
- package/app/.next-cli-build/server/app/api/cli-tools/opencode-settings/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/cli-tools/subagents/route.js +1 -1
- package/app/.next-cli-build/server/app/api/cli-tools/subagents/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/cloud-agents/status/route.js +1 -1
- package/app/.next-cli-build/server/app/api/cloud-agents/status/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/combos/[id]/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/combos/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/dashboard/chat/completions/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/discovery/endpoints/route.js +1 -1
- package/app/.next-cli-build/server/app/api/discovery/endpoints/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/discovery/route.js +1 -1
- package/app/.next-cli-build/server/app/api/discovery/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/discovery/skills/route.js +1 -1
- package/app/.next-cli-build/server/app/api/discovery/skills/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/events/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/headroom/extras/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/headroom/proxy/[...path]/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/headroom/restart/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/headroom/start/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/headroom/status/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/headroom/stop/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/health/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/hermes/autonomy/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/init/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/keys/[id]/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/keys/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/locale/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/mcp/[plugin]/message/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/mcp/[plugin]/sse/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/mcp/route.js +1 -1
- package/app/.next-cli-build/server/app/api/mcp/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/mcp/status/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/media-providers/tts/deepgram/voices/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/media-providers/tts/elevenlabs/voices/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/media-providers/tts/inworld/voices/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/media-providers/tts/minimax/voices/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/media-providers/tts/voices/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/memory/[id]/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/memory/health/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/memory/reindex/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/memory/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/memory/search/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/metrics/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/models/alias/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/models/availability/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/models/custom/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/models/disabled/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/models/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/models/test/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/oauth/[provider]/[action]/route.js +1 -1
- package/app/.next-cli-build/server/app/api/oauth/[provider]/[action]/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/oauth/codex/bulk-import/route.js +1 -1
- package/app/.next-cli-build/server/app/api/oauth/codex/bulk-import/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/oauth/codex/import-token/route.js +1 -1
- package/app/.next-cli-build/server/app/api/oauth/codex/import-token/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/oauth/cursor/auto-import/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/oauth/cursor/import/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/oauth/gitlab/pat/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/oauth/iflow/cookie/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/oauth/kiro/api-key/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/oauth/kiro/auto-import/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/oauth/kiro/import/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/oauth/kiro/import-cli-proxy/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/oauth/kiro/social-authorize/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/oauth/kiro/social-exchange/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/pricing/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/provider-nodes/[id]/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/provider-nodes/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/provider-nodes/validate/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/providers/[id]/export/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/providers/[id]/models/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/providers/[id]/onboard/route.js +1 -1
- package/app/.next-cli-build/server/app/api/providers/[id]/onboard/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/providers/[id]/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/providers/[id]/test/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/providers/[id]/test-models/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/providers/client/route.js +1 -1
- package/app/.next-cli-build/server/app/api/providers/client/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/providers/kilo/free-models/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/providers/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/providers/suggested-models/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/providers/test-batch/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/providers/validate/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/proxy-pools/[id]/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/proxy-pools/[id]/test/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/proxy-pools/cloudflare-deploy/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/proxy-pools/deno-deploy/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/proxy-pools/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/proxy-pools/vercel-deploy/route.js +1 -1
- package/app/.next-cli-build/server/app/api/proxy-pools/vercel-deploy/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/pxpipe/health/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/pxpipe/install/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/pxpipe/logs/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/pxpipe/restart/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/pxpipe/start/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/pxpipe/stats/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/pxpipe/status/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/pxpipe/stop/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/saver/stats/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/settings/database/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/settings/proxy-test/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/settings/require-login/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/settings/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/shutdown/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/skill-discovery/[id]/install/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/skill-discovery/[id]/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/skill-discovery/[id]/uninstall/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/skill-discovery/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/skill-discovery/sync/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/tags/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/translator/console-logs/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/translator/console-logs/stream/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/translator/load/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/translator/save/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/translator/send/route.js +1 -1
- package/app/.next-cli-build/server/app/api/translator/send/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/translator/translate/route.js +1 -1
- package/app/.next-cli-build/server/app/api/translator/translate/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/tunnel/disable/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/tunnel/enable/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/tunnel/status/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/tunnel/tailscale-check/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/tunnel/tailscale-disable/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/tunnel/tailscale-enable/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/tunnel/tailscale-install/route.js +2 -2
- package/app/.next-cli-build/server/app/api/tunnel/tailscale-install/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/usage/[connectionId]/codex-reset-credits/route.js +1 -1
- package/app/.next-cli-build/server/app/api/usage/[connectionId]/codex-reset-credits/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/usage/[connectionId]/route.js +1 -1
- package/app/.next-cli-build/server/app/api/usage/[connectionId]/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/usage/chart/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/usage/history/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/usage/local-snapshot/route.js +1 -1
- package/app/.next-cli-build/server/app/api/usage/local-snapshot/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/usage/logs/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/usage/providers/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/usage/request-details/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/usage/request-logs/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/usage/stats/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/usage/stream/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/v1/api/chat/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/v1/audio/speech/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/v1/audio/transcriptions/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/v1/audio/voices/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/v1/chat/completions/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/v1/embeddings/route.js +1 -1
- package/app/.next-cli-build/server/app/api/v1/embeddings/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/v1/images/generations/route.js +1 -1
- package/app/.next-cli-build/server/app/api/v1/images/generations/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/v1/messages/count_tokens/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/v1/messages/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/v1/models/[kind]/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/v1/models/info/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/v1/models/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/v1/responses/compact/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/v1/responses/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/v1/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/v1/search/route.js +1 -1
- package/app/.next-cli-build/server/app/api/v1/search/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/v1/videos/[id]/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/v1/videos/edits/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/v1/videos/extensions/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/v1/videos/generations/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/v1/web/fetch/route.js +1 -1
- package/app/.next-cli-build/server/app/api/v1/web/fetch/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/v1beta/models/[...path]/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/v1beta/models/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/version/route.js +1 -1
- package/app/.next-cli-build/server/app/api/version/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/version/shutdown/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/version/update/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/webhooks/[id]/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/webhooks/events/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/webhooks/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/callback/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/callback/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/callback.html +1 -1
- package/app/.next-cli-build/server/app/callback.rsc +5 -5
- package/app/.next-cli-build/server/app/callback.segments/_full.segment.rsc +5 -5
- package/app/.next-cli-build/server/app/callback.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/callback.segments/callback/__PAGE__.segment.rsc +5 -5
- package/app/.next-cli-build/server/app/dashboard/a2a.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/a2a.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/a2a.segments/!KGRhc2hib2FyZCk/dashboard/a2a/__PAGE__.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/a2a.segments/_full.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/a2a.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/activity.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/activity.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/activity.segments/!KGRhc2hib2FyZCk/dashboard/activity/__PAGE__.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/activity.segments/_full.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/activity.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/agent-skills.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/agent-skills.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/agent-skills.segments/!KGRhc2hib2FyZCk/dashboard/agent-skills/__PAGE__.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/agent-skills.segments/_full.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/agent-skills.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/api-endpoints.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/api-endpoints.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/api-endpoints.segments/!KGRhc2hib2FyZCk/dashboard/api-endpoints/__PAGE__.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/api-endpoints.segments/_full.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/api-endpoints.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/basic-chat.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/basic-chat.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/basic-chat.segments/!KGRhc2hib2FyZCk/dashboard/basic-chat/__PAGE__.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/basic-chat.segments/_full.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/basic-chat.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/batch.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/batch.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/batch.segments/!KGRhc2hib2FyZCk/dashboard/batch/__PAGE__.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/batch.segments/_full.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/batch.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/cache.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/cache.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/cache.segments/!KGRhc2hib2FyZCk/dashboard/cache/__PAGE__.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/cache.segments/_full.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/cache.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/cli-tools.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/cli-tools.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/cli-tools.segments/!KGRhc2hib2FyZCk/dashboard/cli-tools/__PAGE__.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/cli-tools.segments/_full.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/cli-tools.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/cloud-agents.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/cloud-agents.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/cloud-agents.segments/!KGRhc2hib2FyZCk/dashboard/cloud-agents/__PAGE__.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/cloud-agents.segments/_full.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/cloud-agents.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/combos.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/combos.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/combos.segments/!KGRhc2hib2FyZCk/dashboard/combos/__PAGE__.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/combos.segments/_full.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/combos.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/costs.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/costs.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/costs.segments/!KGRhc2hib2FyZCk/dashboard/costs/__PAGE__.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/costs.segments/_full.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/costs.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/discovery.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/discovery.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/discovery.segments/!KGRhc2hib2FyZCk/dashboard/discovery/__PAGE__.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/discovery.segments/_full.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/discovery.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/endpoint.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/endpoint.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/endpoint.segments/!KGRhc2hib2FyZCk/dashboard/endpoint/__PAGE__.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/endpoint.segments/_full.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/endpoint.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/free-provider-rankings.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/free-provider-rankings.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/free-provider-rankings.segments/!KGRhc2hib2FyZCk/dashboard/free-provider-rankings/__PAGE__.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/free-provider-rankings.segments/_full.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/free-provider-rankings.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/free-tiers.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/free-tiers.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/free-tiers.segments/!KGRhc2hib2FyZCk/dashboard/free-tiers/__PAGE__.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/free-tiers.segments/_full.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/free-tiers.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/guardrails.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/guardrails.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/guardrails.segments/!KGRhc2hib2FyZCk/dashboard/guardrails/__PAGE__.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/guardrails.segments/_full.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/guardrails.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/health.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/health.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/health.segments/!KGRhc2hib2FyZCk/dashboard/health/__PAGE__.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/health.segments/_full.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/health.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/logs.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/logs.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/logs.segments/!KGRhc2hib2FyZCk/dashboard/logs/__PAGE__.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/logs.segments/_full.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/logs.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/mcp.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/mcp.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/mcp.segments/!KGRhc2hib2FyZCk/dashboard/mcp/__PAGE__.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/mcp.segments/_full.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/mcp.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/media-providers/web.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/media-providers/web.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/media-providers/web.segments/!KGRhc2hib2FyZCk/dashboard/media-providers/web/__PAGE__.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/media-providers/web.segments/_full.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/media-providers/web.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/memory.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/memory.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/memory.segments/!KGRhc2hib2FyZCk/dashboard/memory/__PAGE__.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/memory.segments/_full.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/memory.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/mitm.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/mitm.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/mitm.segments/!KGRhc2hib2FyZCk/dashboard/mitm/__PAGE__.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/mitm.segments/_full.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/mitm.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/playground.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/playground.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/playground.segments/!KGRhc2hib2FyZCk/dashboard/playground/__PAGE__.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/playground.segments/_full.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/playground.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/privacy.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/privacy.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/privacy.segments/!KGRhc2hib2FyZCk/dashboard/privacy/__PAGE__.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/privacy.segments/_full.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/privacy.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/profile.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/profile.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/profile.segments/!KGRhc2hib2FyZCk/dashboard/profile/__PAGE__.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/profile.segments/_full.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/profile.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/provider-stats.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/provider-stats.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/provider-stats.segments/!KGRhc2hib2FyZCk/dashboard/provider-stats/__PAGE__.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/provider-stats.segments/_full.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/provider-stats.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/providers/new.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/providers/new.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/providers/new.segments/!KGRhc2hib2FyZCk/dashboard/providers/new/__PAGE__.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/providers/new.segments/_full.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/providers/new.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/providers.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/providers.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/providers.segments/!KGRhc2hib2FyZCk/dashboard/providers/__PAGE__.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/providers.segments/_full.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/providers.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/proxy-pools.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/proxy-pools.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/proxy-pools.segments/!KGRhc2hib2FyZCk/dashboard/proxy-pools/__PAGE__.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/proxy-pools.segments/_full.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/proxy-pools.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/pxpipe.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/pxpipe.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/pxpipe.segments/!KGRhc2hib2FyZCk/dashboard/pxpipe/__PAGE__.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/pxpipe.segments/_full.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/pxpipe.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/quota.html +2 -2
- package/app/.next-cli-build/server/app/dashboard/quota.rsc +8 -8
- package/app/.next-cli-build/server/app/dashboard/quota.segments/!KGRhc2hib2FyZCk/dashboard/quota/__PAGE__.segment.rsc +8 -8
- package/app/.next-cli-build/server/app/dashboard/quota.segments/_full.segment.rsc +8 -8
- package/app/.next-cli-build/server/app/dashboard/quota.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/runtime.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/runtime.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/runtime.segments/!KGRhc2hib2FyZCk/dashboard/runtime/__PAGE__.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/runtime.segments/_full.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/runtime.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/settings/feature-flags.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/settings/feature-flags.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/settings/feature-flags.segments/!KGRhc2hib2FyZCk/dashboard/settings/feature-flags/__PAGE__.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/settings/feature-flags.segments/_full.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/settings/feature-flags.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/settings/pricing.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/settings/pricing.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/settings/pricing.segments/!KGRhc2hib2FyZCk/dashboard/settings/pricing/__PAGE__.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/settings/pricing.segments/_full.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/settings/pricing.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/skill-discovery.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/skill-discovery.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/skill-discovery.segments/!KGRhc2hib2FyZCk/dashboard/skill-discovery/__PAGE__.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/skill-discovery.segments/_full.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/skill-discovery.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/skills.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/skills.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/skills.segments/!KGRhc2hib2FyZCk/dashboard/skills/__PAGE__.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/skills.segments/_full.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/skills.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/token-saver.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/token-saver.rsc +8 -8
- package/app/.next-cli-build/server/app/dashboard/token-saver.segments/!KGRhc2hib2FyZCk/dashboard/token-saver/__PAGE__.segment.rsc +8 -8
- package/app/.next-cli-build/server/app/dashboard/token-saver.segments/_full.segment.rsc +8 -8
- package/app/.next-cli-build/server/app/dashboard/token-saver.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/translator.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/translator.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/translator.segments/!KGRhc2hib2FyZCk/dashboard/translator/__PAGE__.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/translator.segments/_full.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/translator.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/usage.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/usage.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/usage.segments/!KGRhc2hib2FyZCk/dashboard/usage/__PAGE__.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/usage.segments/_full.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/usage.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/webhooks.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/webhooks.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/webhooks.segments/!KGRhc2hib2FyZCk/dashboard/webhooks/__PAGE__.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/webhooks.segments/_full.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard/webhooks.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard.html +1 -1
- package/app/.next-cli-build/server/app/dashboard.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard.segments/!KGRhc2hib2FyZCk/dashboard/__PAGE__.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard.segments/_full.segment.rsc +7 -7
- package/app/.next-cli-build/server/app/dashboard.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/favicon.ico/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/index.html +1 -1
- package/app/.next-cli-build/server/app/index.rsc +5 -5
- package/app/.next-cli-build/server/app/index.segments/__PAGE__.segment.rsc +5 -5
- package/app/.next-cli-build/server/app/index.segments/_full.segment.rsc +5 -5
- package/app/.next-cli-build/server/app/index.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/landing/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/landing/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/landing.html +1 -1
- package/app/.next-cli-build/server/app/landing.rsc +5 -5
- package/app/.next-cli-build/server/app/landing.segments/_full.segment.rsc +5 -5
- package/app/.next-cli-build/server/app/landing.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/landing.segments/landing/__PAGE__.segment.rsc +5 -5
- package/app/.next-cli-build/server/app/login/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/login/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/login.html +1 -1
- package/app/.next-cli-build/server/app/login.rsc +6 -6
- package/app/.next-cli-build/server/app/login.segments/_full.segment.rsc +6 -6
- package/app/.next-cli-build/server/app/login.segments/_tree.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/login.segments/login/__PAGE__.segment.rsc +6 -6
- package/app/.next-cli-build/server/app/manifest.webmanifest/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app-paths-manifest.json +9 -9
- package/app/.next-cli-build/server/chunks/112.js +1 -1
- package/app/.next-cli-build/server/chunks/2638.js +1 -1
- package/app/.next-cli-build/server/chunks/2813.js +1 -1
- package/app/.next-cli-build/server/chunks/2974.js +1 -1
- package/app/.next-cli-build/server/chunks/318.js +1 -1
- package/app/.next-cli-build/server/chunks/3755.js +1 -1
- package/app/.next-cli-build/server/chunks/4037.js +1 -1
- package/app/.next-cli-build/server/chunks/4303.js +1 -1
- package/app/.next-cli-build/server/chunks/5243.js +1 -1
- package/app/.next-cli-build/server/chunks/5731.js +1 -1
- package/app/.next-cli-build/server/chunks/6270.js +1 -1
- package/app/.next-cli-build/server/chunks/6664.js +1 -1
- package/app/.next-cli-build/server/chunks/7403.js +1 -1
- package/app/.next-cli-build/server/chunks/772.js +1 -1
- package/app/.next-cli-build/server/chunks/{4018.js → 7937.js} +5 -5
- package/app/.next-cli-build/server/chunks/8499.js +1 -1
- package/app/.next-cli-build/server/chunks/8537.js +1 -1
- package/app/.next-cli-build/server/chunks/862.js +1 -1
- package/app/.next-cli-build/server/chunks/869.js +1 -1
- package/app/.next-cli-build/server/chunks/8750.js +1 -1
- package/app/.next-cli-build/server/instrumentation.js.nft.json +1 -1
- package/app/.next-cli-build/server/middleware-build-manifest.js +1 -1
- package/app/.next-cli-build/server/middleware.js +4 -4
- package/app/.next-cli-build/server/middleware.js.nft.json +1 -1
- package/app/.next-cli-build/server/pages/404.html +1 -1
- package/app/.next-cli-build/server/pages/500.html +1 -1
- package/app/.next-cli-build/server/server-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/server-reference-manifest.json +1 -1
- package/app/.next-cli-build/static/chunks/{1321-b974e2c6d758f2eb.js → 1321-6b997794bb843da8.js} +1 -1
- package/app/.next-cli-build/static/chunks/{7780-8fef40eb7ea5a323.js → 7780-f3508723257e47c1.js} +1 -1
- package/app/.next-cli-build/static/chunks/app/(dashboard)/dashboard/cli-tools/{page-8f34a6d3a99ebd37.js → page-f7cbd07558c7b42e.js} +1 -1
- package/app/.next-cli-build/static/css/01b1ec05918332c3.css +1 -0
- package/app/package.json +79 -78
- package/app/src/mitm/server.js +40 -42
- package/cli.js +10 -0
- package/hooks/opencodeBridgeRuntime.js +77 -0
- package/hooks/sqliteRuntime.js +156 -156
- package/hooks/trayRuntime.js +107 -107
- package/package.json +49 -49
- package/vendor/opencode-free-bridge/LICENSE +21 -0
- package/vendor/opencode-free-bridge/README.md +762 -0
- package/vendor/opencode-free-bridge/THIRD_PARTY_NOTICES.md +33 -0
- package/vendor/opencode-free-bridge/UPSTREAM_COMMIT +1 -0
- package/vendor/opencode-free-bridge/identity.js +2624 -0
- package/vendor/opencode-free-bridge/package.json +54 -0
- package/vendor/opencode-free-bridge/panel.js +515 -0
- package/vendor/opencode-free-bridge/proxy.js +980 -0
- package/vendor/opencode-free-bridge/server.js +3357 -0
- package/vendor/opencode-free-bridge/token-budget.js +107 -0
- package/vendor/opencode-free-bridge/transport-baseline.json +39 -0
- package/app/.next-cli-build/static/css/8ba5c8dbc4e30aa0.css +0 -1
- /package/app/.next-cli-build/static/{X4uxXfKI78mMCnlqFsTAr → DryEkcB2pIbzN1YYgNcUS}/_buildManifest.js +0 -0
- /package/app/.next-cli-build/static/{X4uxXfKI78mMCnlqFsTAr → DryEkcB2pIbzN1YYgNcUS}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,762 @@
|
|
|
1
|
+
# opencode-free-bridge
|
|
2
|
+
|
|
3
|
+
OpenAI-compatible local API bridge for [OpenCode](https://opencode.ai) free-tier models. No account and no API key required.
|
|
4
|
+
|
|
5
|
+
It runs a local server on `http://127.0.0.1:20129/v1` that any OpenAI-compatible client (9Router, Cursor, Continue, LiteLLM, Open WebUI, or the OpenAI SDK) can call directly.
|
|
6
|
+
|
|
7
|
+
## What's New in v2.0.0
|
|
8
|
+
|
|
9
|
+
- **Bun TLS transport parity:** The official OpenCode CLI ships as a compiled Bun binary, and upstream's free-tier gate inspects the TLS `ClientHello` fingerprint (17 cipher suites and 12 TLS extensions in exact order). The bridge runs on Bun (`bun server.js`), blocks accidental Node.js startups unless `ALLOW_NODE=1` is set for offline tests, and ships `transport-fingerprint.js` to verify TLS parity against `transport-baseline.json`.
|
|
10
|
+
- **Full source & catalog derivation:** All hardcoded model tables (`MODEL_MAP`) and model-name substring heuristics have been removed. Client version, commit SHA, session ID recipe (`ses_` + 12 hex + 14 base62), 7 gate headers, 13 tool plugins (parsed from upstream's Effect Schema AST in GitHub source, 12 active per model after `patch` vs `edit`/`write` swap), inference endpoints (`chatUrl` and `responsesUrl`), active free models, shorthand family aliases, and reasoning token limits are derived directly from OpenCode's update API, GitHub repository, and `models.opencode.ai/api.json`.
|
|
11
|
+
- **On-disk catalog cache for instant cold starts:** Caches `models.opencode.ai/api.json` at `~/.opencode-free-bridge/cache/catalog.json` and loads it at startup (`OPENCODE_CACHE_DIR=off` disables disk caching). Shorthand aliases (`mimo`, `longcat`, `beam`), deprecated/paid model checks, and reasoning token budgets work on the very first request and when offline.
|
|
12
|
+
- **Multi-layer reactive self-healing with IP-cooldown protection:** On `403 FreeTierError`, `426 UpgradeRequired`, or `404` on a migrated endpoint path, the bridge refreshes its recipe and catalog, evicts stale session IDs, mints a fresh session ID, escalates to the full tool profile, and retries once transparently. If a full-shape retry still returns `403 FreeTierError` (indicating a per-IP upstream cooldown rather than a stale request shape), subsequent requests within the 30s cooldown window (`GATE_RETRY_COOLDOWN_MS`, `GATE_REFRESH_COOLDOWN_MS`) skip redundant GitHub fetches and duplicate retries so the bridge never amplifies an IP penalty. **Exception:** the gateway's explicit IP-level abuse block (*"detected abusive traffic from this IP address"* / `Detect Abuse Traffic`) is terminal — the bridge answers it verbatim with `X-OpenCode-Gate: ip-abuse` and performs no refresh, session re-mint, or retry, because no client-side change can clear a flag keyed to the egress network.
|
|
13
|
+
- **Gate anchors, so a shape bug can never masquerade as an IP block:** Upstream returns one `403 FreeTierError` — *"OpenCode's free tier can only be used from within OpenCode"* — for at least four unrelated causes: a refused egress IP, a request without a tool named `read`, a User-Agent that is not `opencode/<channel>/<semver>/<name>`, and a request without a session header. Measured by replaying byte-identical requests from a refused egress and a clean one, varying one field at a time: `read` + 11 fabricated tools → `200`, the real `read` renamed → `403`, a fabricated tool *named* `read` → `200`. The bridge therefore pins those anchors (`GATE_ANCHOR_TOOL_NAMES`, `GATE_ANCHOR_HEADER_NAMES`) independently of what the derived recipe and tool profile contain, audits the outgoing request when a `403` arrives, and reports `X-OpenCode-Gate: shape-rejected` plus the missing anchors instead of blaming the network. A second, unambiguous message (*"detected abusive traffic from this IP address"* or `Detect Abuse Traffic`) identifies a network-level block and is answered immediately as `ip-abuse`; only the ambiguous free-tier message goes through the shape audit at all. Upstream's `x-opencode-log-id` is recorded and exposed at `/health` (`identity.gate.lastRejection`) for correlation.
|
|
14
|
+
- **Zero-dead-end universal installer:** `install.sh` finds or installs Bun (and `unzip` when missing on minimal Linux), stages and validates every file before touching a running bridge, retires legacy `opencode-bridge.service` units, rewrites `node` units to `bun`, configures `systemd` (Linux) or `launchd` (macOS), and verifies `/health` and the TLS fingerprint baseline before succeeding.
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- **Self-healing client identity:** Derives the OpenCode CLI wire identity at the current release commit SHA and updates automatically when upstream tightens the gate.
|
|
19
|
+
- **Live catalog discovery:** Reads `models.opencode.ai/api.json` (and `/v1/models`) to expose only active free-tier models (`cost.input === 0`, non-deprecated) and reject retired or paid model IDs locally with a clear error.
|
|
20
|
+
- **Dual-protocol support:** Routes requests to `/inference/openai/v1/chat/completions` or `/inference/openai/v1/responses` based on catalog metadata and automatic protocol-mismatch detection.
|
|
21
|
+
- **Reasoning-aware token budgeting:** Applies an additive token floor (`max(requested, 1024) + 4096`) for reasoning models (determined from catalog `reasoning: true` metadata) so short prompts do not return empty content with `finish_reason: "length"`.
|
|
22
|
+
- **Reasoning stream normalization:** Emits thinking tokens under `reasoning_content` for both streaming SSE and non-streaming JSON responses, and forwards client tool calls with automatic name de-collision (`read` -> `read_client` upstream, restored to `read` on return).
|
|
23
|
+
- **Zero dependencies:** Uses only built-in runtime modules (`http`, `crypto`, `fs`, `path`, `net`, `tls`). No `npm install` step.
|
|
24
|
+
- **Container deployment:** Ships a `Dockerfile` and `docker-compose.yaml` (contributed in PR #1 by @rustam76, completed in v2.1.0). The image pins `oven/bun:1.4.3-slim` — the Bun version is pinned because the free-tier gate inspects the TLS `ClientHello` — runs as the non-root `bun` user, and publishes the port on loopback only.
|
|
25
|
+
|
|
26
|
+
## Requirements: Bun >= 1.0
|
|
27
|
+
|
|
28
|
+
Bun is required to talk to the live OpenCode gateway. Node.js 18+ can run the offline unit test suite, but upstream rejects Node.js requests with `403 FreeTierError` because Node's OpenSSL TLS `ClientHello` differs from the compiled OpenCode CLI (which is built on Bun/BoringSSL). The installer installs Bun for you when it is missing.
|
|
29
|
+
|
|
30
|
+
Supported: Linux (systemd) and macOS (launchd). Windows is not supported; use WSL if you need it there.
|
|
31
|
+
|
|
32
|
+
Measured against a raw TCP listener and the live gateway from the same IP within one minute, using identical headers and payload:
|
|
33
|
+
|
|
34
|
+
| Property | OpenCode CLI v2.0.32 | Bridge on Bun | Bridge on Node.js |
|
|
35
|
+
|---|---|---|---|
|
|
36
|
+
| TLS cipher suites offered | 17 | **17 (identical)** | 52 |
|
|
37
|
+
| TLS extensions offered | 12 | **12 (identical order)** | 11 (different order) |
|
|
38
|
+
| Default `Accept-Encoding` | `gzip, deflate, br, zstd` | **identical** | `gzip, deflate` |
|
|
39
|
+
| Live gateway result | `200 OK` | **`200 OK` (6/6)** | `403 FreeTierError` (0/6) |
|
|
40
|
+
|
|
41
|
+
Starting the server with `node server.js` exits immediately with an explanation so a broken Node deployment cannot fail silently in the background:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
bun server.js # required for live traffic
|
|
45
|
+
node server.js # exits with code 1 and instructions
|
|
46
|
+
ALLOW_NODE=1 node server.js # starts for offline tests and mocked upstreams only
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
You can verify that your local Bun binary matches the recorded OpenCode CLI TLS baseline at any time:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
bun transport-fingerprint.js --check # exits 0 if ciphers and extensions match
|
|
53
|
+
bun transport-fingerprint.js # prints current TLS ClientHello JSON
|
|
54
|
+
bun transport-fingerprint.js --write-baseline # updates transport-baseline.json
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Install & Update
|
|
58
|
+
|
|
59
|
+
### One-line installer (Linux & macOS)
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
curl -fsSL https://raw.githubusercontent.com/azrinaldi/opencode-free-bridge/main/install.sh | bash
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
What `install.sh` does:
|
|
66
|
+
1. Finds `bun` across `PATH`, `$BUN_INSTALL/bin`, `~/.bun/bin`, `~/.local/bin`, `/usr/local/bin`, and `/opt/bun/bin`, or installs it via `https://bun.sh/install` (installing `unzip` first on minimal Linux images when needed).
|
|
67
|
+
2. Downloads every file into a staging directory and validates it (in-process JS syntax compilation, JSON parsing, and `bash -n` for `tunnel-doctor.sh`) before touching anything that is already running.
|
|
68
|
+
3. Checks port availability (`ss` or `lsof`), retires a pre-2.0 `opencode-bridge.service` that would hold port 20129, and stops its own running bridge when upgrading in place.
|
|
69
|
+
4. Backs up the previous install under `~/.opencode-free-bridge/.backup-*`, then moves the staged files into place.
|
|
70
|
+
5. Registers and starts the background service (`systemd` system or `--user` unit on Linux, `launchd` agent with `~/Library/Logs/opencode-free-bridge.log` on macOS).
|
|
71
|
+
6. Waits up to 20 seconds for `/health`, runs the quick self-check, and checks the Bun TLS fingerprint against `transport-baseline.json`.
|
|
72
|
+
|
|
73
|
+
If the bridge does not answer after 20 seconds, the installer exits with code `1` and prints the exact log inspection command. It only prints `Done.` when the bridge is answering.
|
|
74
|
+
|
|
75
|
+
Installer options:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
PORT=3000 BINDIR=/opt/opencode-free-bridge bash install.sh
|
|
79
|
+
NO_SERVICE=1 bash install.sh # install files only, skip systemd/launchd setup
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
An occupied port aborts the install before anything is changed. To install alongside an existing service, choose a free port:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
PORT=20130 bash install.sh
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
To update an existing installation, re-run the `curl ... | bash` command above. Running instances outside a git checkout also check for new releases in this repository every 6 hours (`AUTO_UPDATE=0` disables self-update).
|
|
89
|
+
|
|
90
|
+
### Run from a git clone
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
git clone https://github.com/azrinaldi/opencode-free-bridge.git
|
|
94
|
+
cd opencode-free-bridge
|
|
95
|
+
bun server.js
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`bash install.sh` inside the clone installs that working tree (including
|
|
99
|
+
uncommitted or unpushed files) instead of downloading from GitHub. Force a
|
|
100
|
+
download with `SOURCE=remote bash install.sh`.
|
|
101
|
+
|
|
102
|
+
The server listens on `http://127.0.0.1:20129` by default.
|
|
103
|
+
Open `http://127.0.0.1:20129/` for the panel: it shows the live egress state and sets
|
|
104
|
+
an HTTP proxy for every outbound request. See [Egress proxy](#egress-proxy).
|
|
105
|
+
|
|
106
|
+
### Docker
|
|
107
|
+
|
|
108
|
+
The repository ships a `Dockerfile` and a `docker-compose.yaml` for container
|
|
109
|
+
deployments (single host, no orchestrator needed):
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
git clone https://github.com/azrinaldi/opencode-free-bridge.git
|
|
113
|
+
cd opencode-free-bridge
|
|
114
|
+
docker compose up -d
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The bridge is then reachable at `http://127.0.0.1:20129/v1` (API key `public`),
|
|
118
|
+
exactly like a native install, and the model catalog cache survives restarts in
|
|
119
|
+
the `bridge-cache` volume.
|
|
120
|
+
|
|
121
|
+
What the container setup does:
|
|
122
|
+
|
|
123
|
+
- Runs on `oven/bun:1.4.3-slim` as the non-root `bun` user. The Bun version is
|
|
124
|
+
pinned because the free-tier gate inspects the TLS ClientHello: the image
|
|
125
|
+
must produce the same fingerprint as the OpenCode CLI, which
|
|
126
|
+
`transport-baseline.json` records.
|
|
127
|
+
- Publishes the port on **loopback only**. The bridge has no authentication
|
|
128
|
+
(the API key is literally `public`), so publishing on `0.0.0.0` would expose
|
|
129
|
+
an open relay to the whole network. For remote access, put a reverse proxy
|
|
130
|
+
or an SSH tunnel in front of it.
|
|
131
|
+
- Persists the on-disk catalog cache in the `bridge-cache` volume
|
|
132
|
+
(`OPENCODE_CACHE_DIR=/data/cache`), so a restart does not re-fetch the
|
|
133
|
+
upstream model catalog.
|
|
134
|
+
- Persists the panel's egress-proxy setting in the `bridge-state` volume
|
|
135
|
+
(`OPENCODE_BRIDGE_STATE_DIR=/data/state`). Without it, the container
|
|
136
|
+
filesystem is thrown away on recreate and the proxy would silently revert.
|
|
137
|
+
- Runs with `no-new-privileges` and all Linux capabilities dropped.
|
|
138
|
+
|
|
139
|
+
The image is the unit of deployment, so `AUTO_UPDATE` defaults to `0` in the
|
|
140
|
+
compose file: an in-place self-update is reverted by the next
|
|
141
|
+
`docker compose build`. Upgrade with:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
docker compose build --pull && docker compose up -d
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Plain `docker run` works too — set `HOST=0.0.0.0` and publish the port
|
|
148
|
+
yourself:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
docker build -t opencode-free-bridge .
|
|
152
|
+
docker run -d --name opencode-free-bridge \
|
|
153
|
+
-p 127.0.0.1:20129:20129 \
|
|
154
|
+
-e OPENCODE_CACHE_DIR=/data/cache \
|
|
155
|
+
-e OPENCODE_BRIDGE_STATE_DIR=/data/state \
|
|
156
|
+
-v opencode-free-bridge-cache:/data/cache \
|
|
157
|
+
-v opencode-free-bridge-state:/data/state \
|
|
158
|
+
opencode-free-bridge
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Usage & Client Setup
|
|
162
|
+
|
|
163
|
+
| Setting | Value |
|
|
164
|
+
|---|---|
|
|
165
|
+
| Base URL | `http://127.0.0.1:20129/v1` |
|
|
166
|
+
| API Key | `public` (any non-empty string is accepted) |
|
|
167
|
+
| Model ID | Any ID returned by `GET http://127.0.0.1:20129/v1/models` |
|
|
168
|
+
|
|
169
|
+
### 9Router
|
|
170
|
+
|
|
171
|
+
1. Add a custom OpenAI-compatible provider:
|
|
172
|
+
- **Base URL:** `http://127.0.0.1:20129/v1`
|
|
173
|
+
- **API Key:** `public`
|
|
174
|
+
- **Prefix:** `zen`
|
|
175
|
+
- **Model IDs:** leave empty and click **Import from /models endpoint**
|
|
176
|
+
2. Models are imported as `zen/big-pickle`, `zen/mimo-v2.6-flash-free`, `zen/nemotron-3-ultra-free`, etc.
|
|
177
|
+
3. Optional: if you run the bridge on multiple hosts (for example, a laptop and a VPS), register each host as a separate connection in 9Router so per-IP rate limits fail over automatically.
|
|
178
|
+
|
|
179
|
+
### LiteLLM, Open WebUI, Continue, Cursor
|
|
180
|
+
|
|
181
|
+
Use the same three settings: Base URL `http://127.0.0.1:20129/v1`, API key `public`, and model IDs from `/v1/models`.
|
|
182
|
+
|
|
183
|
+
```yaml
|
|
184
|
+
# LiteLLM config.yaml example
|
|
185
|
+
model_list:
|
|
186
|
+
- model_name: zen-mimo
|
|
187
|
+
litellm_params:
|
|
188
|
+
model: openai/mimo-v2.6-flash-free
|
|
189
|
+
api_base: http://127.0.0.1:20129/v1
|
|
190
|
+
api_key: public
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Coding agents & CLI tools
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
export OPENAI_BASE_URL="http://127.0.0.1:20129/v1"
|
|
197
|
+
export OPENAI_API_KEY="public"
|
|
198
|
+
export MODEL="mimo-v2.6-flash-free"
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Python (OpenAI SDK)
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
from openai import OpenAI
|
|
205
|
+
|
|
206
|
+
client = OpenAI(base_url="http://127.0.0.1:20129/v1", api_key="public")
|
|
207
|
+
|
|
208
|
+
response = client.chat.completions.create(
|
|
209
|
+
model="mimo-v2.6-flash-free",
|
|
210
|
+
messages=[{"role": "user", "content": "Write a Python palindrome checker."}],
|
|
211
|
+
)
|
|
212
|
+
print(response.choices[0].message.content)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### curl
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
curl -s http://127.0.0.1:20129/v1/chat/completions \
|
|
219
|
+
-H 'Content-Type: application/json' \
|
|
220
|
+
-d '{"model":"mimo-v2.6-flash-free","messages":[{"role":"user","content":"Reply with OK"}]}'
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Available Free Models
|
|
224
|
+
|
|
225
|
+
The model list is derived dynamically from `models.opencode.ai/api.json` (`cost.input === 0` and `status !== "deprecated"`) and cached at `~/.opencode-free-bridge/cache/catalog.json`.
|
|
226
|
+
|
|
227
|
+
Current active free-tier models in the upstream catalog:
|
|
228
|
+
|
|
229
|
+
| Model ID | Family Shorthand | Reasoning | Max Output Tokens |
|
|
230
|
+
|---|---|---|---|
|
|
231
|
+
| `big-pickle` | `big-pickle` | Yes | 32,000 |
|
|
232
|
+
| `beam-free` | `beam` | Yes | 131,072 |
|
|
233
|
+
| `mimo-v2.6-flash-free` | `mimo` | Yes | 32,000 |
|
|
234
|
+
| `nemotron-3-ultra-free` | `nemotron-free` | Yes | 128,000 |
|
|
235
|
+
| `nemotron-3.5-lightning-free` | - | Yes | 262,144 |
|
|
236
|
+
| `nemotron-3.5-super-vl-free` | - | Yes | 131,072 |
|
|
237
|
+
| `longcat-2.5-preview-free` | `longcat` | Yes | 131,072 |
|
|
238
|
+
| `deepseek-v4.1-flash-free` | `deepseek-flash` | Yes | 32,000 |
|
|
239
|
+
| `glm-5.3-flash-free` | `glm-flash` | Yes | 32,000 |
|
|
240
|
+
| `space-bunny-free` | - | Yes | 524,288 |
|
|
241
|
+
|
|
242
|
+
Query the live list from your running bridge:
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
curl -s http://127.0.0.1:20129/v1/models
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Requests for retired models (such as `cat-jam-free`, `muse-spark-1.3-contributor-free`, or `step-5-preview-free`) or paid models (such as `claude-sonnet-4-5` or `gpt-5.4`) are rejected locally with HTTP `404` and an explanation rather than being forwarded upstream to fail with `401 Missing API key`.
|
|
249
|
+
|
|
250
|
+
## Reasoning Models & Token Budgets
|
|
251
|
+
|
|
252
|
+
Reasoning models spend tokens on chain-of-thought before emitting visible text, and both share the same `max_completion_tokens` ceiling. When a client sends a small `max_tokens` (such as `50` or `100` for a health probe or title generator), a reasoning model can exhaust the entire budget while thinking and return an empty string with `finish_reason: "length"`.
|
|
253
|
+
|
|
254
|
+
`token-budget.js` checks the model's `reasoning` flag from the catalog (`identity.getModelFacts(modelId)`) and applies an additive floor:
|
|
255
|
+
|
|
256
|
+
| Request Type | Effective `max_completion_tokens` Sent Upstream |
|
|
257
|
+
|---|---|
|
|
258
|
+
| Catalog `reasoning: true` model (or request with `reasoning_effort` set) | `min(max(requested, 1024) + 4096, modelMaxOutput)` |
|
|
259
|
+
| Non-reasoning model | `min(max(requested, 512), modelMaxOutput)` |
|
|
260
|
+
|
|
261
|
+
The client's requested value is never lowered (unless it exceeds the model's catalog `limit.output` or `131072`).
|
|
262
|
+
|
|
263
|
+
## How Adaptive Identity & Self-Healing Work
|
|
264
|
+
|
|
265
|
+
```
|
|
266
|
+
Client ──▶ Bridge (:20129, Bun) ──▶ https://opencode.ai/inference/openai/v1/...
|
|
267
|
+
│
|
|
268
|
+
├─ 1. Handshake: opencode.ai/update/api/latest/cli/npm
|
|
269
|
+
│ -> resolves latest CLI version, minimum floor, and GitHub commit SHA
|
|
270
|
+
├─ 2. Source AST parser: raw.githubusercontent.com/anomalyco/opencode/<sha>/...
|
|
271
|
+
│ -> extracts session ID recipe, UA template, 7 gate headers,
|
|
272
|
+
│ 13 tool plugins (Effect Schema AST), and model-conditional tool swaps
|
|
273
|
+
├─ 3. Provider catalog: models.opencode.ai/api.json (cached on disk)
|
|
274
|
+
│ -> extracts inference endpoints, 10 free models, family aliases,
|
|
275
|
+
│ deprecated/paid verdicts, and per-model reasoning/output limits
|
|
276
|
+
└─ 4. On 403 / 426 / 404:
|
|
277
|
+
-> refreshes recipe + catalog, evicts stale session ID, mints fresh
|
|
278
|
+
ses_<12hex><14base62> ID, escalates to full tool profile, retries once
|
|
279
|
+
(throttled for 30s if full-shape retry is still refused by per-IP gate)
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### What is derived dynamically vs. fixed
|
|
283
|
+
|
|
284
|
+
| Layer | Source | Fallback if unreachable |
|
|
285
|
+
|---|---|---|
|
|
286
|
+
| CLI version & minimum floor | `opencode.ai/update/api/latest/cli/npm` (fallback: GitHub `releases.atom`) | `DEFAULT_RECIPE.version` (`2.0.32`) |
|
|
287
|
+
| Session ID charset, length, multiplier | `packages/schema/src/identifier.ts` | 62-char base62, length 26, mult `4096` |
|
|
288
|
+
| Session ID prefix | `packages/schema/src/session-id.ts` | `ses_` |
|
|
289
|
+
| User-Agent template | `packages/core/src/app.ts` | `opencode/${channel}/${version}/${cli}` |
|
|
290
|
+
| Required & optional gate headers | `packages/core/src/session/model-request.ts` | 7 core OpenCode headers |
|
|
291
|
+
| Tool plugin list & wire allowlist | GitHub tree listing of `packages/core/src/tool/plugin/*.ts` + `codemode/tool.ts` | 13 known plugins (`read`, `shell`, `edit`, etc.) |
|
|
292
|
+
| Tool JSON schemas & descriptions | Effect Schema AST parser across plugin `.ts`, sibling `.txt`, and `@opencode/schema/*` | `DEFAULT_TOOLS` (`read`, `shell`) |
|
|
293
|
+
| Model-conditional tool swaps (`edit`/`write` vs `patch`) | `packages/core/src/tool/plugin/patch.ts` | `gpt-*` condition rule |
|
|
294
|
+
| Inference endpoints (`chatUrl`, `responsesUrl`) | `models.opencode.ai/api.json` (`opencode.api`, validated under `*.opencode.ai`) | `https://opencode.ai/inference/openai/v1/*` |
|
|
295
|
+
| Free model list, paid/retired verdicts, family aliases, token limits | `models.opencode.ai/api.json` | `~/.opencode-free-bridge/cache/catalog.json` |
|
|
296
|
+
|
|
297
|
+
### Inspecting live bridge state
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
curl -s http://127.0.0.1:20129/health
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
```json
|
|
304
|
+
{
|
|
305
|
+
"status": "ok",
|
|
306
|
+
"provider": "opencode-free-bridge",
|
|
307
|
+
"version": "2.0.0",
|
|
308
|
+
"runtime": "bun/1.4.2",
|
|
309
|
+
"discoveredModels": 10,
|
|
310
|
+
"identity": {
|
|
311
|
+
"userAgent": "opencode/latest/2.0.32/cli",
|
|
312
|
+
"updateMode": "reactive",
|
|
313
|
+
"sourceTag": "d4a6223d017e17f1beaa91d3e6ecc6511c5132ad",
|
|
314
|
+
"versionFloor": null,
|
|
315
|
+
"lastCheckAgeMs": 1240,
|
|
316
|
+
"ttlMs": 900000,
|
|
317
|
+
"lastError": null,
|
|
318
|
+
"release": {
|
|
319
|
+
"version": "2.0.32",
|
|
320
|
+
"sha": "d4a6223d017e17f1beaa91d3e6ecc6511c5132ad",
|
|
321
|
+
"minimum": false,
|
|
322
|
+
"active": true,
|
|
323
|
+
"channel": "latest",
|
|
324
|
+
"source": "update-api",
|
|
325
|
+
"resolvedAt": 1760740000000,
|
|
326
|
+
"stale": false
|
|
327
|
+
},
|
|
328
|
+
"available": null,
|
|
329
|
+
"headers": [
|
|
330
|
+
"x-opencode-session-id",
|
|
331
|
+
"x-session-affinity",
|
|
332
|
+
"X-Session-Id",
|
|
333
|
+
"User-Agent",
|
|
334
|
+
"x-opencode-project",
|
|
335
|
+
"x-opencode-session",
|
|
336
|
+
"x-opencode-client"
|
|
337
|
+
],
|
|
338
|
+
"unsentHeaders": [],
|
|
339
|
+
"endpoints": {
|
|
340
|
+
"chatUrl": "https://opencode.ai/inference/openai/v1/chat/completions",
|
|
341
|
+
"responsesUrl": "https://opencode.ai/inference/openai/v1/responses",
|
|
342
|
+
"modelsUrl": "https://opencode.ai/inference/v1/models",
|
|
343
|
+
"catalogBase": "https://opencode.ai/inference/openai/v1"
|
|
344
|
+
},
|
|
345
|
+
"toolNames": [
|
|
346
|
+
"read", "shell", "edit", "write", "glob", "grep", "patch",
|
|
347
|
+
"question", "skill", "subagent", "webfetch", "websearch", "execute"
|
|
348
|
+
],
|
|
349
|
+
"tools": {
|
|
350
|
+
"source": "source",
|
|
351
|
+
"version": "2.0.32",
|
|
352
|
+
"toolNames": [
|
|
353
|
+
"execute", "edit", "glob", "grep", "patch", "question",
|
|
354
|
+
"read", "shell", "skill", "subagent", "webfetch", "websearch", "write"
|
|
355
|
+
]
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
## Testing, Verification & Gate Drills
|
|
362
|
+
|
|
363
|
+
```bash
|
|
364
|
+
bun test # 165 offline unit, contract, and regression tests
|
|
365
|
+
bun run verify # live check: probes every discovered free model
|
|
366
|
+
bun run verify:quick # live check: stops at the first model that answers
|
|
367
|
+
bun run drill # runs live + offline self-healing gate drills
|
|
368
|
+
bun run fingerprint:check # checks Bun TLS ClientHello against transport-baseline.json
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
`verify.js` exits `0` when a model answers, `2` when the bridge is healthy and its TLS fingerprint matches the baseline but upstream refuses this IP (free-tier allowance or an explicit abuse block), and `1` for a real failure (bridge unreachable, running on Node.js, or TLS fingerprint drift). The `2` case is what a throttled or flagged network looks like: the official OpenCode CLI gets the same `403 FreeTierError` from the same IP at the same moment, so it is not a reason to reinstall or edit headers. The bridge labels the refusal (`X-OpenCode-Gate: ip-throttled | ip-abuse | shape-rejected`), so `verify.js` no longer depends on upstream keeping identical wording.
|
|
372
|
+
|
|
373
|
+
`gate-drill.js` deliberately corrupts the bridge's state and verifies that a single client request triggers self-healing and still returns `HTTP 200`:
|
|
374
|
+
|
|
375
|
+
| Drill | Mode | What it corrupts / stages | What it verifies |
|
|
376
|
+
|---|---|---|---|
|
|
377
|
+
| `version-floor` | Live | User-Agent version below floor | `426 UpgradeRequired` -> floor learned -> identity refreshed -> retry succeeds |
|
|
378
|
+
| `empty-tools` | Live | Empty tool profile | Falls back to shipped tool definitions instead of sending `tools: []` |
|
|
379
|
+
| `session-shape` | Live | Unrecognized session ID prefix | `403 FreeTierError` -> recipe refreshed -> old ID evicted -> fresh `ses_...` ID minted |
|
|
380
|
+
| `legacy-shape` | Live | Pre-v1.5 request shape | Control check against live upstream |
|
|
381
|
+
| `routing` | Offline | `chatUrl` repointed after boot | Endpoints are resolved per request from recipe/catalog, not frozen at boot |
|
|
382
|
+
| `allowlist` | Offline | Upstream renames a tool (`shell` -> `run`) | Extracted tool name reaches the wire in `full` scope and retired name is dropped |
|
|
383
|
+
| `retry-endpoint` | Offline | `403` gate rejection + catalog URL change | The self-heal retry uses the newly refreshed endpoint URL |
|
|
384
|
+
| `endpoint-move` | Offline | `404` on retired endpoint path | Re-reads catalog on `404` and retries at the new path transparently |
|
|
385
|
+
| `gate-heal` | Offline | Stale prefix + minimal tool scope | Full heal order: `403` -> refresh -> re-mint session ID -> escalate to full tools -> `200` |
|
|
386
|
+
| `full-corrupt` | Offline | Version `0.9.9`, prefix `old_`, length `20`, hex charset, default tools | One client request heals all corrupted layers simultaneously from source and returns `200` |
|
|
387
|
+
|
|
388
|
+
Before running the 4 live drills, `gate-drill.js` sends a single CLI-shaped control probe. If the current IP is temporarily throttled by upstream (`403 FreeTierError` on the control probe), the 4 live drills report `SKIP` so they do not extend the IP cooldown, while the 6 offline drills still execute and determine the exit code.
|
|
389
|
+
|
|
390
|
+
## Configuration
|
|
391
|
+
|
|
392
|
+
| Environment Variable | Default | Description |
|
|
393
|
+
|---|---|---|
|
|
394
|
+
| `PORT` | `20129` | HTTP listen port |
|
|
395
|
+
| `HOST` | `127.0.0.1` | Bind address (keep on `127.0.0.1` or behind a firewall; the server has no auth) |
|
|
396
|
+
| `CORS_ORIGIN` | `""` (localhost only) | Allowed CORS origin(s), comma-separated or `*` |
|
|
397
|
+
| `UPSTREAM_TIMEOUT_MS` | `120000` | Upstream fetch timeout in milliseconds |
|
|
398
|
+
| `MAX_BODY_BYTES` | `10485760` | Maximum request body size in bytes (10 MiB) |
|
|
399
|
+
| `IDENTITY_TTL_MS` | `900000` | Identity freshness TTL in milliseconds (15 minutes) |
|
|
400
|
+
| `UPDATE_MODE` | `reactive` | `reactive` adopts new CLI releases only on `403`/`426` or `minimum: true`; `always` adopts every release on TTL expiry |
|
|
401
|
+
| `GATE_RETRY_COOLDOWN_MS` | `30000` | Cooldown window (ms) after a failed full-shape `403` retry during which duplicate retries are suppressed until identity changes |
|
|
402
|
+
| `GATE_REFRESH_COOLDOWN_MS` | `30000` | Cooldown window (ms) between forced GitHub/catalog refreshes during a `403` burst |
|
|
403
|
+
| `DNS_RESULT_ORDER` | `ipv4first` | DNS resolution order passed to `dns.setDefaultResultOrder` (`ipv4first`, `ipv6first`, `verbatim`) |
|
|
404
|
+
| `TOOL_PROFILE_SCOPE` | `core` | `core` sends the minimal gate anchor (`read`, `shell`) on first attempt and escalates to `full` (all model-applicable tools) on retry; `full` sends the full tool profile on every request |
|
|
405
|
+
| `OPENCODE_CHANNEL` | `latest` | OpenCode release channel (`latest`, `beta`, `dev`) |
|
|
406
|
+
| `OPENCODE_MAJOR` | _(unset)_ | Pin a major version line (for example `2`) |
|
|
407
|
+
| `OPENCODE_USER_AGENT` | _(derived)_ | Override `User-Agent` and disable dynamic identity derivation |
|
|
408
|
+
| `OPENCODE_CATALOG_URL` | `https://models.opencode.ai/api.json` | Provider catalog URL |
|
|
409
|
+
| `OPENCODE_CACHE_DIR` | `~/.opencode-free-bridge/cache` | Directory for the on-disk `catalog.json` cache (`off` disables disk caching) |
|
|
410
|
+
| `OPENCODE_CHAT_URL` | _(derived)_ | Override the upstream Chat Completions endpoint URL |
|
|
411
|
+
| `OPENCODE_RESPONSES_URL` | _(derived)_ | Override the upstream Responses API endpoint URL |
|
|
412
|
+
| `UPSTREAM_RELAY_URL` | _(unset)_ | Route inference through an egress relay (`x-relay-target` / `x-relay-path` contract); empty = direct. See [Optional egress relay](#optional-egress-relay) |
|
|
413
|
+
| `UPSTREAM_PROXY_URL` | _(unset)_ | Operator-level default for the egress proxy (`http://[user:pass@]host:port`). When set it wins over the panel and locks runtime edits. See [Egress proxy](#egress-proxy) |
|
|
414
|
+
| `PANEL_ALLOWED_HOSTS` | _(unset)_ | Extra `Host` names the panel and `/api/proxy` accept (comma-separated), for a reverse proxy in front of the bridge. Loopback names and the configured `HOST` are always accepted |
|
|
415
|
+
| `AUTO_UPDATE` | `1` | Poll GitHub for bridge updates every 6 hours (automatically skipped inside a `.git` checkout; set `0` to disable) |
|
|
416
|
+
| `ALLOW_NODE` | _(unset)_ | Set `ALLOW_NODE=1` to bypass the startup Bun check for offline tests |
|
|
417
|
+
|
|
418
|
+
### Optional egress relay
|
|
419
|
+
|
|
420
|
+
Set `UPSTREAM_RELAY_URL` to a relay that speaks the same contract as 9Router's
|
|
421
|
+
Cloudflare/Vercel/Deno/Netlify relays. The bridge POSTs to the relay with
|
|
422
|
+
`x-relay-target: <origin>` and `x-relay-path: <path>`, and the relay replays the
|
|
423
|
+
request at that origin; upstream therefore sees the relay's egress IP.
|
|
424
|
+
|
|
425
|
+
```bash
|
|
426
|
+
UPSTREAM_RELAY_URL="https://relay.example.workers.dev" bun server.js
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
The relay covers inference requests and their retries; model discovery and
|
|
430
|
+
identity synchronization stay direct. `/health` reports `egress.mode` (`direct`
|
|
431
|
+
or `relay`) and the relay's origin, without credentials or query strings. Use a
|
|
432
|
+
trusted HTTPS relay: it receives the request headers (including
|
|
433
|
+
`Authorization: Bearer public`) and the full prompt. Changing the variable
|
|
434
|
+
requires a restart.
|
|
435
|
+
|
|
436
|
+
### Egress proxy
|
|
437
|
+
|
|
438
|
+
Open `http://127.0.0.1:20129/` and enter an HTTP proxy. Every outbound request
|
|
439
|
+
the bridge makes then tunnels through it (`CONNECT`), so upstream sees the
|
|
440
|
+
proxy's IP rather than this machine's. That covers inference, model discovery,
|
|
441
|
+
identity sync, and the self-updater.
|
|
442
|
+
|
|
443
|
+
- **Protocol:** `http://` and `https://` only. Bun's fetch accepts those and
|
|
444
|
+
tunnels TLS itself, so `transport-baseline.json` still matches. SOCKS5 is
|
|
445
|
+
refused by Bun (`UnsupportedProxyProtocol`) and rejected in the panel with
|
|
446
|
+
that reason.
|
|
447
|
+
- **Credentials:** `http://user:pass@host:port`, percent-encoded if they contain
|
|
448
|
+
`@`, `:`, or `/`. They are stored in `~/.opencode-free-bridge/proxy.json`
|
|
449
|
+
(mode `0600`) and never returned by the panel, `/api/proxy`, or `/health`;
|
|
450
|
+
those show `host:port` only.
|
|
451
|
+
- **Test before trusting it:** *Uji koneksi* fetches the models endpoint through
|
|
452
|
+
the candidate proxy and reports the exit IP from `cdn-cgi/trace`. A 403 there
|
|
453
|
+
means the proxy reaches upstream but its egress is refused.
|
|
454
|
+
- **No restart:** the setting is read on every request. *Matikan proxy* returns
|
|
455
|
+
to direct egress.
|
|
456
|
+
- **Operator override:** `UPSTREAM_PROXY_URL` beats the panel and locks runtime
|
|
457
|
+
edits to it.
|
|
458
|
+
- **Only from this machine:** the panel and `/api/proxy` accept `Host` names of
|
|
459
|
+
loopback (or the configured bind address) plus anything in
|
|
460
|
+
`PANEL_ALLOWED_HOSTS`, reject cross-site requests, and require
|
|
461
|
+
`Content-Type: application/json` for writes. That blocks a random website from
|
|
462
|
+
re-pointing your egress and blocks DNS rebinding. Put a reverse proxy in front
|
|
463
|
+
and add its name to `PANEL_ALLOWED_HOSTS` if you reach the panel that way.
|
|
464
|
+
- **Container:** the compose file mounts the `bridge-state` volume at
|
|
465
|
+
`/data/state` (`OPENCODE_BRIDGE_STATE_DIR`) so the setting survives a
|
|
466
|
+
recreate. Plain `docker run` needs that env var and volume too.
|
|
467
|
+
|
|
468
|
+
### Egress pool
|
|
469
|
+
|
|
470
|
+
The free-tier allowance is per egress IP, not per account. Running one bridge
|
|
471
|
+
per host multiplies processes, service units and tunnels without giving anything
|
|
472
|
+
a pool does not: the pool rotates the egress while one process keeps the
|
|
473
|
+
identity, the catalog and the session cache in one place.
|
|
474
|
+
|
|
475
|
+
A pool is a list of egress entries. Each keeps its own health, so an entry that
|
|
476
|
+
upstream throttles is skipped until its cooldown ends instead of being retried
|
|
477
|
+
by every request.
|
|
478
|
+
|
|
479
|
+
```bash
|
|
480
|
+
# in the panel under "Pool egress", one entry per line
|
|
481
|
+
med1=http://pool:token@127.0.0.1:20251
|
|
482
|
+
med2=http://pool:token@127.0.0.1:20252
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
```bash
|
|
486
|
+
# operator override: comma- or newline-separated, label=url optional
|
|
487
|
+
UPSTREAM_PROXY_POOL="med1=http://a:1,med2=http://b:2" \
|
|
488
|
+
UPSTREAM_PROXY_STRATEGY=failover \
|
|
489
|
+
bun server.js
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
- **Entry shape:** a URL string, `{id,label,url,enabled,weight}`, or
|
|
493
|
+
`{direct: true}` for this machine's own address. `direct` is a first-class
|
|
494
|
+
entry, so the hosting IP can take part in the rotation instead of being the
|
|
495
|
+
fallback used only when every proxy is down.
|
|
496
|
+
- **Strategies:** `round-robin` (default; even spread across healthy entries),
|
|
497
|
+
`failover` (sticky on the first healthy entry, which keeps upstream prompt
|
|
498
|
+
caching on one IP), `random` (weighted).
|
|
499
|
+
- **Cooldown:** a refusal on a pooled egress is recorded against *that* entry.
|
|
500
|
+
The backoff doubles per consecutive failure from `cooldownMs` (default 120 s)
|
|
501
|
+
up to `maxCooldownMs` (default 30 min); a success resets it. A `429` earns four
|
|
502
|
+
times the base window because it is an unambiguous allowance answer.
|
|
503
|
+
- **Always sends:** when every entry is cooling, the one that recovers first is
|
|
504
|
+
used anyway. Refusing would turn a throttled pool into a dead bridge, and
|
|
505
|
+
upstream answers a throttled IP with a normal refusal the caller already
|
|
506
|
+
handles.
|
|
507
|
+
- **Failover on a gate rejection:** the bridge drops the entry that was refused
|
|
508
|
+
and retries through a different one, in the same request. An unreachable proxy
|
|
509
|
+
is also failed over, since nothing reached upstream.
|
|
510
|
+
- **One egress per request:** the protocol-lane, endpoint-migration, 5xx and
|
|
511
|
+
gate retries all reuse the entry the request started on, because upstream ties
|
|
512
|
+
a retry to the address that saw the first attempt. Only the two cases above
|
|
513
|
+
move it.
|
|
514
|
+
- **`/health`** reports `egress.mode: "pool"` and `egress.proxy.pool` with each
|
|
515
|
+
entry's host, state, remaining cooldown and failure count. Credentials are
|
|
516
|
+
never included.
|
|
517
|
+
|
|
518
|
+
#### Health across restarts
|
|
519
|
+
|
|
520
|
+
Cooldowns are written to `<state dir>/pool-health.json` (mode `0600`), so a
|
|
521
|
+
service restart or a host suspend/resume does not forget which IPs are spent and
|
|
522
|
+
re-burn all of them.
|
|
523
|
+
|
|
524
|
+
- A refusal is written immediately; a success is coalesced over one second,
|
|
525
|
+
because a success arrives with every request and a rewrite per request would
|
|
526
|
+
turn a memory update into disk churn.
|
|
527
|
+
- The file is written by rename, so a crash mid-write keeps the previous copy
|
|
528
|
+
instead of leaving a truncated one that would cost every cooldown.
|
|
529
|
+
- Records are keyed by a truncated SHA-256 of the egress identity, not by URL,
|
|
530
|
+
so the file is not a second copy of the proxy credentials. A `label` field
|
|
531
|
+
carries `host:port` (or `direct`), which `/health` already exposes.
|
|
532
|
+
- Saving the pool in the panel keeps existing cooldowns; only an entry that
|
|
533
|
+
leaves the pool loses its record.
|
|
534
|
+
- A corrupt or missing health file costs the cooldowns and nothing else: the
|
|
535
|
+
bridge starts, reports the pool from `proxy.json`, and treats every entry as
|
|
536
|
+
ready.
|
|
537
|
+
|
|
538
|
+
#### Turning a host into a pool member
|
|
539
|
+
|
|
540
|
+
`egress-proxy.js` is a dependency-free HTTP proxy for exactly this: it answers
|
|
541
|
+
`CONNECT` and absolute-URI HTTP, and forwards bytes without inspecting them.
|
|
542
|
+
Its TLS is tunneled end to end, so it never sees plaintext.
|
|
543
|
+
|
|
544
|
+
```bash
|
|
545
|
+
# on the node
|
|
546
|
+
PORT=8888 EGRESS_PROXY_TOKEN=secret bun egress-proxy.js
|
|
547
|
+
|
|
548
|
+
# from the bridge host: put it on a loopback port, so the proxy itself is never
|
|
549
|
+
# exposed and the tunnel carries the authentication
|
|
550
|
+
ssh -L 127.0.0.1:20251:127.0.0.1:8888 node
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
`/healthz` is unauthenticated on purpose: it reports a connection count and an
|
|
554
|
+
uptime, nothing about the target or the token, so a monitor does not need the
|
|
555
|
+
credential to ask whether the proxy is up.
|
|
556
|
+
|
|
557
|
+
`/health` reports `egress.mode` (`pool`, `proxy`, `relay`, or `direct`) plus
|
|
558
|
+
`egress.proxy` with the same credential-free description.
|
|
559
|
+
|
|
560
|
+
The proxy applies *in addition to* the relay: with both configured, the request
|
|
561
|
+
is still sent to `UPSTREAM_RELAY_URL`, and that connection is dialled through
|
|
562
|
+
the proxy.
|
|
563
|
+
|
|
564
|
+
## Troubleshooting
|
|
565
|
+
|
|
566
|
+
### `403` with "detected abusive traffic" / `Detect Abuse Traffic` (IP-level block)
|
|
567
|
+
|
|
568
|
+
This is not the shape gate and not a stale bridge. The gateway has flagged the
|
|
569
|
+
network's egress IP for abusive traffic; the message says it is unrelated to the
|
|
570
|
+
account. The bridge treats it as terminal: it does **not** refresh the identity,
|
|
571
|
+
re-mint the session id, or retry, and returns the refusal verbatim with
|
|
572
|
+
`X-OpenCode-Gate: ip-abuse`. `/health` reports `identity.gate.status:
|
|
573
|
+
"ip-blocked"` and `identity.gate.lastRejection.kind: "ip-abuse"`.
|
|
574
|
+
|
|
575
|
+
The official OpenCode CLI gets the same answer from the same network. Only
|
|
576
|
+
upstream can clear the flag (`help@anoma.ly`, with the time it started and the
|
|
577
|
+
`x-opencode-log-id` from the response). Until then, change egress (route through
|
|
578
|
+
another host or SSH tunnel), subscribe to OpenCode Go, or wait. Reinstalling the
|
|
579
|
+
bridge or editing headers will not help.
|
|
580
|
+
|
|
581
|
+
### `403 FreeTierError` persists even after retry
|
|
582
|
+
|
|
583
|
+
When the bridge receives `403 FreeTierError`, it refreshes its recipe from OpenCode's update API and GitHub source tree, re-mints the session ID, escalates to the full tool profile, and retries once. If the message names an explicit IP-level abuse block (`abusive traffic` / `Detect Abuse Traffic`), this path is skipped entirely — see the section above.
|
|
584
|
+
|
|
585
|
+
If that retry also returns `403 FreeTierError` and `/health` shows `"lastError": null` with the current CLI version (for example `opencode/latest/2.0.32/cli`), **your current IP is in a temporary free-tier cooldown upstream**. To prevent extending the penalty, the bridge suppresses duplicate `403` retries and forced GitHub refreshes for `30s` (`GATE_RETRY_COOLDOWN_MS`). Running the official OpenCode CLI binary (`opencode run -m opencode/big-pickle "hi"`) from the same IP at the same moment returns the exact same `403 FreeTierError`. Wait for the per-IP window to reset or route through another host/SSH tunnel.
|
|
586
|
+
|
|
587
|
+
### `429 RateLimitError` / `BUSY` in `verify.js`
|
|
588
|
+
|
|
589
|
+
Upstream is throttling the specific model or current IP due to capacity. Try another model ID from `/v1/models`.
|
|
590
|
+
|
|
591
|
+
### `404 Model '<id>' has been retired upstream`
|
|
592
|
+
|
|
593
|
+
The model was marked `status: "deprecated"` in `models.opencode.ai/api.json` (for example `cat-jam-free`, `muse-spark-1.3-contributor-free`, or `step-5-preview-free`). Query `curl -s http://127.0.0.1:20129/v1/models` and switch your client to one of the active models.
|
|
594
|
+
|
|
595
|
+
### `bun: command not found` in a fresh SSH session
|
|
596
|
+
|
|
597
|
+
The service runs fine, but `curl https://bun.sh/install | bash` only adds
|
|
598
|
+
`~/.bun/bin` to an *interactive* shell's PATH. A new non-login SSH session, or a
|
|
599
|
+
`sh -c` command, does not see it:
|
|
600
|
+
|
|
601
|
+
```bash
|
|
602
|
+
export PATH="$HOME/.bun/bin:$PATH" # or use the absolute path
|
|
603
|
+
/root/.bun/bin/bun transport-fingerprint.js --check
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
The installer prints the absolute path in its closing summary for this reason.
|
|
607
|
+
The service itself is unaffected: its unit file records the full path.
|
|
608
|
+
|
|
609
|
+
### `remote port forwarding failed for listen port 20136`
|
|
610
|
+
|
|
611
|
+
The receiving host still has a **stale** listener from a tunnel whose sender rebooted or lost power. The TCP connection died without a FIN, so `sshd` keeps the forwarded port until keepalive notices — ten minutes with the default `ClientAliveInterval 300` × `ClientAliveCountMax 2`. The replacement tunnel then fails even though its own unit is healthy.
|
|
612
|
+
|
|
613
|
+
```bash
|
|
614
|
+
./tunnel-doctor.sh # which forwards are stale (listener with no SSH transport)
|
|
615
|
+
./tunnel-doctor.sh --reap # kill only the stale sessions
|
|
616
|
+
./tunnel-doctor.sh --check # is the keepalive window short enough?
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
To close the window itself, on the **receiving** host:
|
|
620
|
+
|
|
621
|
+
```bash
|
|
622
|
+
# /etc/ssh/sshd_config.d/00-keepalive.conf — the 00- prefix matters
|
|
623
|
+
ClientAliveInterval 30
|
|
624
|
+
ClientAliveCountMax 4 # dead peer detected in 120s
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
OpenSSH uses the **first** value it reads, so a later-sorting file (for example `99-*.conf`) is silently ignored. Verify with `sshd -T | grep -i clientalive` and `systemctl reload ssh`.
|
|
628
|
+
|
|
629
|
+
### `429 FreeUsageLimitError` — the quota, or a missing `<env>` block?
|
|
630
|
+
|
|
631
|
+
`429 FreeUsageLimitError` has **two** causes, and the gateway uses the same
|
|
632
|
+
message for both:
|
|
633
|
+
|
|
634
|
+
1. the free-tier allowance is spent, or
|
|
635
|
+
2. the request's system prompt is missing the harness environment block.
|
|
636
|
+
|
|
637
|
+
Cause 2 is the one that made the bridge look throttled while the CLI worked.
|
|
638
|
+
Reproduced by capturing the CLI's own request and replaying it byte-for-byte:
|
|
639
|
+
the captured body answers `200`, the same body with only its system prompt
|
|
640
|
+
replaced by a short one answers `429`. Same headers, same session id, same TLS
|
|
641
|
+
stack — the difference is the body.
|
|
642
|
+
|
|
643
|
+
The trigger is precise: an `<env>` block containing a `Working directory:` line.
|
|
644
|
+
|
|
645
|
+
| system prompt | result |
|
|
646
|
+
|---|---|
|
|
647
|
+
| `You are a helpful assistant.` | `429` |
|
|
648
|
+
| 12 KB of generic prompt text | `429` |
|
|
649
|
+
| `<env>Working directory: /tmp</env>` (65 bytes) | `200` |
|
|
650
|
+
| 12 KB generic prompt **plus** the env block | `200` |
|
|
651
|
+
|
|
652
|
+
Length is not the rule, and neither is the model — `big-pickle` and
|
|
653
|
+
`mimo-v2.6-flash-free` split the same way. Sending `tools: []` also produces
|
|
654
|
+
this `429`, which is why an empty tool profile looked like a quota error too.
|
|
655
|
+
|
|
656
|
+
The bridge has appended the block since the fix in this release: it derives the
|
|
657
|
+
working directory, workspace root, git-repo flag and platform from its own
|
|
658
|
+
process. A client that already forwards the CLI's prompt keeps its own block
|
|
659
|
+
and does not get a duplicate. If you still see `429` on an up-to-date bridge,
|
|
660
|
+
the allowance really is spent — check with the interleaving probe:
|
|
661
|
+
|
|
662
|
+
```bash
|
|
663
|
+
for i in 1 2 3; do
|
|
664
|
+
opencode run -m opencode/glm-5.3-flash-free "probe $i" 2>&1 | tail -1
|
|
665
|
+
curl -s http://127.0.0.1:20129/v1/chat/completions \
|
|
666
|
+
-H 'Content-Type: application/json' \
|
|
667
|
+
-d '{"model":"glm-5.3-flash-free","messages":[{"role":"user","content":"probe"}],"max_tokens":8}' \
|
|
668
|
+
| head -c 120; echo
|
|
669
|
+
done
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
If both refuse, the allowance is spent. If the CLI answers while the bridge
|
|
673
|
+
refuses **in the same minute** on a bridge older than this release, upgrade —
|
|
674
|
+
that is the shape bug, not a quota.
|
|
675
|
+
|
|
676
|
+
#### Why the bridge can be charged earlier than the CLI
|
|
677
|
+
|
|
678
|
+
The default `TOOL_PROFILE_SCOPE=core` sends two tools (`read`, `shell`) and a
|
|
679
|
+
short placeholder note, then escalates to `full` only after a rejection. The
|
|
680
|
+
real CLI always sends the full profile. Measured against a captured CLI request
|
|
681
|
+
(`opencode run`, v2.0.32) and this bridge on the same model:
|
|
682
|
+
|
|
683
|
+
| | CLI | bridge, `core` | bridge, `full` |
|
|
684
|
+
|---|---|---|---|
|
|
685
|
+
| tools | 12 | 2 | 13 |
|
|
686
|
+
| tool schema bytes | 13,991 | 2,522 | 13,972 |
|
|
687
|
+
| system prompt chars | 12,259 | ~470 | ~470 |
|
|
688
|
+
| `max_completion_tokens` | 32,000 | 5,120 | 5,120 |
|
|
689
|
+
| total body bytes | 26,296 | ~3,300 | ~14,500 |
|
|
690
|
+
|
|
691
|
+
`TOOL_PROFILE_SCOPE=full` closes the tool gap and is the safer default when a
|
|
692
|
+
deployment is being throttled ahead of the CLI.
|
|
693
|
+
|
|
694
|
+
The system-prompt gap is **not** cosmetic: before the `<env>` fix in this
|
|
695
|
+
release it was the whole `429` cause. What remains after the fix is the CLI's
|
|
696
|
+
harness prose (persona, communication rules, skill catalogue), which the bridge
|
|
697
|
+
does not ship. That part is accepted by the gateway — the env block is the only
|
|
698
|
+
anchor it checks — so the bridge no longer needs to reproduce the full prompt.
|
|
699
|
+
|
|
700
|
+
```bash
|
|
701
|
+
# systemd drop-in for a service that runs as a dedicated user
|
|
702
|
+
sudo mkdir -p /etc/systemd/system/opencode-free-bridge.service.d
|
|
703
|
+
printf '[Service]\nEnvironment=TOOL_PROFILE_SCOPE=full\n' \
|
|
704
|
+
| sudo tee /etc/systemd/system/opencode-free-bridge.service.d/10-tool-profile.conf
|
|
705
|
+
sudo systemctl daemon-reload && sudo systemctl restart opencode-free-bridge.service
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
### Reverse tunnel dies while the process still looks healthy
|
|
709
|
+
|
|
710
|
+
A deployment that reaches the bridge over `ssh -R` can fail in a way systemd
|
|
711
|
+
cannot see: the `ssh` process stays `active`, the TCP connection stays `ESTAB`,
|
|
712
|
+
and the listener on the far end is gone. `Restart=always` never fires because
|
|
713
|
+
the process never exits. This cost two hours of downtime on a five-node
|
|
714
|
+
deployment before it was diagnosed.
|
|
715
|
+
|
|
716
|
+
The only reliable probe is the endpoint itself. Run it on the host that owns the
|
|
717
|
+
forwarded port, not on the node:
|
|
718
|
+
|
|
719
|
+
```bash
|
|
720
|
+
#!/bin/bash
|
|
721
|
+
# /usr/local/bin/opencode-tunnel-watchdog.sh — run as root via a systemd timer
|
|
722
|
+
PORT=20136
|
|
723
|
+
LOG=/var/log/opencode-tunnel-watchdog.log
|
|
724
|
+
log() { printf '%s %s\n' "$(date -u +%FT%TZ)" "$*" >> "$LOG"; }
|
|
725
|
+
|
|
726
|
+
if ! curl -sf --max-time 5 http://127.0.0.1:20129/health >/dev/null; then
|
|
727
|
+
log "bridge DOWN - restarting"; systemctl restart opencode-bridge.service; sleep 4
|
|
728
|
+
fi
|
|
729
|
+
if ! pgrep -f "ssh .*-R 127.0.0.1:${PORT}:" >/dev/null; then
|
|
730
|
+
log "tunnel MISSING - restarting"; systemctl restart opencode-tunnel.service; sleep 6
|
|
731
|
+
fi
|
|
732
|
+
pgrep -f "ssh .*-R 127.0.0.1:${PORT}:" >/dev/null \
|
|
733
|
+
&& log "SUMMARY ok" || log "SUMMARY FAILED - manual attention needed"
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
Pair it with a `oneshot` service and a `OnUnitActiveSec=60` timer. The
|
|
737
|
+
`SUMMARY` line is deliberate: without a heartbeat, an empty log is
|
|
738
|
+
indistinguishable from a watchdog that never ran.
|
|
739
|
+
|
|
740
|
+
Restarting the tunnel from a remote host over SSH needs care. `Defaults use_pty`
|
|
741
|
+
in `/etc/sudoers` makes `sudo -n` fail with "a password is required" even when
|
|
742
|
+
`NOPASSWD` is granted, and a nested `ssh host "python3 -c \"...\""` mangles
|
|
743
|
+
quotes. Run the watchdog **as root on the node itself** through systemd instead
|
|
744
|
+
of driving it remotely.
|
|
745
|
+
|
|
746
|
+
### Checking service logs
|
|
747
|
+
|
|
748
|
+
```bash
|
|
749
|
+
# Linux (systemd user or system service)
|
|
750
|
+
journalctl --user -u opencode-free-bridge -n 50 --no-pager
|
|
751
|
+
journalctl -u opencode-free-bridge -n 50 --no-pager
|
|
752
|
+
|
|
753
|
+
# macOS (launchd)
|
|
754
|
+
tail -50 ~/Library/Logs/opencode-free-bridge.log
|
|
755
|
+
|
|
756
|
+
# Manual foreground run
|
|
757
|
+
bun ~/.opencode-free-bridge/server.js
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
## License and upstream data
|
|
761
|
+
|
|
762
|
+
This project is licensed under [MIT](./LICENSE). `identity.js` derives request identity and tool metadata from the [OpenCode source repository](https://github.com/anomalyco/opencode). Its offline fallback includes OpenCode `read` and `shell` tool descriptions and schema fields; related source-shaped fixtures appear in `test/identity.test.js`. See [`THIRD_PARTY_NOTICES.md`](./THIRD_PARTY_NOTICES.md) for the OpenCode MIT notice and contributor credit.
|