@remodex/rmx 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS_INSTALL.md +91 -0
- package/LICENSE +21 -0
- package/README.md +242 -0
- package/assets/architecture.png +0 -0
- package/assets/banner.png +0 -0
- package/assets/claude-code-models.gif +0 -0
- package/assets/codex-app-picker.png +0 -0
- package/bin/ocx.mjs +584 -0
- package/bin/package-main.mjs +9 -0
- package/gui/dist/assets/index-CZqebSPQ.css +1 -0
- package/gui/dist/assets/index-CkETtt7P.js +71 -0
- package/gui/dist/favicon.png +0 -0
- package/gui/dist/fonts/google-sans-cyrillic.woff2 +0 -0
- package/gui/dist/fonts/google-sans-latin.woff2 +0 -0
- package/gui/dist/icons.svg +24 -0
- package/gui/dist/index.html +25 -0
- package/gui/dist/logo.png +0 -0
- package/gui/dist/provider-icons/alibaba-color.svg +1 -0
- package/gui/dist/provider-icons/antigravity-color.svg +1 -0
- package/gui/dist/provider-icons/claude-color.svg +1 -0
- package/gui/dist/provider-icons/cline-color.svg +16 -0
- package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -0
- package/gui/dist/provider-icons/commandcode-color.svg +1 -0
- package/gui/dist/provider-icons/copilot-color.svg +1 -0
- package/gui/dist/provider-icons/cursor-color.svg +2 -0
- package/gui/dist/provider-icons/deepseek-color.svg +1 -0
- package/gui/dist/provider-icons/discord.svg +1 -0
- package/gui/dist/provider-icons/firepass-color.svg +1 -0
- package/gui/dist/provider-icons/fireworks-color.svg +1 -0
- package/gui/dist/provider-icons/gemini-color.svg +1 -0
- package/gui/dist/provider-icons/github-copilot-color.svg +1 -0
- package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -0
- package/gui/dist/provider-icons/grok.svg +1 -0
- package/gui/dist/provider-icons/groq-color.svg +1 -0
- package/gui/dist/provider-icons/huggingface-color.svg +1 -0
- package/gui/dist/provider-icons/kimi-color.svg +1 -0
- package/gui/dist/provider-icons/kiro-color.svg +15 -0
- package/gui/dist/provider-icons/lm-studio-color.svg +1 -0
- package/gui/dist/provider-icons/mistral-color.svg +1 -0
- package/gui/dist/provider-icons/moonshot-color.svg +1 -0
- package/gui/dist/provider-icons/nvidia-color.svg +1 -0
- package/gui/dist/provider-icons/ollama-color.svg +1 -0
- package/gui/dist/provider-icons/openai.svg +1 -0
- package/gui/dist/provider-icons/opencode.svg +2 -0
- package/gui/dist/provider-icons/openrouter-color.svg +1 -0
- package/gui/dist/provider-icons/pi.svg +21 -0
- package/gui/dist/provider-icons/qianfan-color.svg +1 -0
- package/gui/dist/provider-icons/qwen-portal-color.svg +1 -0
- package/gui/dist/provider-icons/telegram.svg +1 -0
- package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -0
- package/gui/dist/provider-icons/vllm-color.svg +1 -0
- package/gui/dist/provider-icons/xiaomi-color.svg +1 -0
- package/package.json +118 -0
- package/src/AGENTS.md +28 -0
- package/src/adapters/anthropic-image-guard.ts +251 -0
- package/src/adapters/anthropic-image-normalize.ts +518 -0
- package/src/adapters/anthropic.ts +1205 -0
- package/src/adapters/azure.ts +36 -0
- package/src/adapters/base.ts +83 -0
- package/src/adapters/client-fingerprint.ts +59 -0
- package/src/adapters/command-code.ts +453 -0
- package/src/adapters/cursor/arg-codec.ts +38 -0
- package/src/adapters/cursor/arg-normalize.ts +104 -0
- package/src/adapters/cursor/cursor-errors.ts +165 -0
- package/src/adapters/cursor/discovery.ts +276 -0
- package/src/adapters/cursor/effort-map.ts +139 -0
- package/src/adapters/cursor/exec-policy.ts +88 -0
- package/src/adapters/cursor/framing.ts +250 -0
- package/src/adapters/cursor/gen/agent_pb.ts +15274 -0
- package/src/adapters/cursor/kv-store.ts +52 -0
- package/src/adapters/cursor/live-models.ts +153 -0
- package/src/adapters/cursor/live-smoke-gate.ts +41 -0
- package/src/adapters/cursor/live-transport.ts +1235 -0
- package/src/adapters/cursor/mcp-config.ts +42 -0
- package/src/adapters/cursor/mcp-manager.ts +333 -0
- package/src/adapters/cursor/message-mapper.ts +49 -0
- package/src/adapters/cursor/native-exec-common.ts +59 -0
- package/src/adapters/cursor/native-exec-desktop.ts +184 -0
- package/src/adapters/cursor/native-exec-fs.ts +332 -0
- package/src/adapters/cursor/native-exec-mcp.ts +153 -0
- package/src/adapters/cursor/native-exec-network.ts +43 -0
- package/src/adapters/cursor/native-exec-shell.ts +548 -0
- package/src/adapters/cursor/native-exec-tools.ts +118 -0
- package/src/adapters/cursor/native-exec.ts +604 -0
- package/src/adapters/cursor/protobuf-events.ts +735 -0
- package/src/adapters/cursor/protobuf-request.ts +719 -0
- package/src/adapters/cursor/request-builder.ts +280 -0
- package/src/adapters/cursor/thread-continuity.ts +67 -0
- package/src/adapters/cursor/tool-definitions.ts +621 -0
- package/src/adapters/cursor/transport-retry.ts +132 -0
- package/src/adapters/cursor/transport.ts +57 -0
- package/src/adapters/cursor/types.ts +59 -0
- package/src/adapters/cursor.ts +196 -0
- package/src/adapters/google-antigravity-replay.ts +520 -0
- package/src/adapters/google-antigravity-wire.ts +140 -0
- package/src/adapters/google-errors.ts +85 -0
- package/src/adapters/google-http.ts +100 -0
- package/src/adapters/google-tool-schema.ts +173 -0
- package/src/adapters/google-truncation.ts +24 -0
- package/src/adapters/google-wire-compiler.ts +232 -0
- package/src/adapters/google.ts +859 -0
- package/src/adapters/identity.ts +77 -0
- package/src/adapters/image.ts +23 -0
- package/src/adapters/kiro-constants.ts +16 -0
- package/src/adapters/kiro-errors.ts +208 -0
- package/src/adapters/kiro-events.ts +197 -0
- package/src/adapters/kiro-images.ts +129 -0
- package/src/adapters/kiro-retry.ts +312 -0
- package/src/adapters/kiro-thinking.ts +104 -0
- package/src/adapters/kiro-tool-fallback.ts +36 -0
- package/src/adapters/kiro-tools.ts +224 -0
- package/src/adapters/kiro-truncation.ts +33 -0
- package/src/adapters/kiro-wire.ts +129 -0
- package/src/adapters/kiro.ts +1924 -0
- package/src/adapters/mimo-free.ts +263 -0
- package/src/adapters/openai-chat.ts +1265 -0
- package/src/adapters/openai-responses.ts +1309 -0
- package/src/adapters/run-turn-queue.ts +114 -0
- package/src/adapters/tool-catalog-nudge.ts +71 -0
- package/src/adapters/upstream-http-error.ts +48 -0
- package/src/android-remote/assets.ts +218 -0
- package/src/android-remote/attachments.ts +168 -0
- package/src/android-remote/auth.ts +237 -0
- package/src/android-remote/cloudflare-provisioning.ts +409 -0
- package/src/android-remote/cloudflare-secret.ts +106 -0
- package/src/android-remote/cloudflare-tunnel.ts +488 -0
- package/src/android-remote/cloudflared.ts +286 -0
- package/src/android-remote/codex-app-server.ts +565 -0
- package/src/android-remote/desktop-history-page.ts +936 -0
- package/src/android-remote/desktop-ipc.ts +3643 -0
- package/src/android-remote/desktop-ownership-store.ts +98 -0
- package/src/android-remote/desktop-project-registration.ts +129 -0
- package/src/android-remote/desktop-session-stream.ts +1110 -0
- package/src/android-remote/desktop-workspace-state.ts +443 -0
- package/src/android-remote/file-change-parser.ts +112 -0
- package/src/android-remote/gateway.ts +9108 -0
- package/src/android-remote/mutation-store.ts +299 -0
- package/src/android-remote/projection.ts +1780 -0
- package/src/android-remote/queued-turn-store.ts +248 -0
- package/src/android-remote/session-command-recovery.ts +1384 -0
- package/src/android-remote/store.ts +466 -0
- package/src/android-remote/thread-reconciliation.ts +133 -0
- package/src/android-remote/thread-source-paths.ts +307 -0
- package/src/android-remote/thread-stream.ts +546 -0
- package/src/android-remote/turn-activity.ts +235 -0
- package/src/android-remote/user-message-identity.ts +94 -0
- package/src/bridge.ts +1793 -0
- package/src/chat/inbound.ts +295 -0
- package/src/chat/outbound.ts +821 -0
- package/src/claude/agents-inject.ts +267 -0
- package/src/claude/alias.ts +149 -0
- package/src/claude/auth-detect.ts +229 -0
- package/src/claude/auth-mode-migration.ts +32 -0
- package/src/claude/auth-mode.ts +62 -0
- package/src/claude/context-windows.ts +189 -0
- package/src/claude/desktop-3p-guard.ts +35 -0
- package/src/claude/desktop-3p-paths.ts +84 -0
- package/src/claude/desktop-3p.ts +601 -0
- package/src/claude/desktop-health.ts +26 -0
- package/src/claude/desktop-profile.ts +263 -0
- package/src/claude/gateway-cache.ts +70 -0
- package/src/claude/inbound-debug.ts +163 -0
- package/src/claude/inbound.ts +519 -0
- package/src/claude/model-info.ts +154 -0
- package/src/claude/outbound.ts +898 -0
- package/src/cli/access.ts +108 -0
- package/src/cli/account-api.ts +296 -0
- package/src/cli/account-auth.ts +250 -0
- package/src/cli/account-catalog-refresh.ts +14 -0
- package/src/cli/account-extended.ts +476 -0
- package/src/cli/account-main.ts +317 -0
- package/src/cli/account.ts +297 -0
- package/src/cli/agent-driven.ts +70 -0
- package/src/cli/agent.ts +184 -0
- package/src/cli/catalog-prewarm.ts +27 -0
- package/src/cli/claude-desktop.ts +211 -0
- package/src/cli/claude.ts +302 -0
- package/src/cli/codex-shim-autorestore.ts +45 -0
- package/src/cli/codex-shim-readiness.ts +69 -0
- package/src/cli/combo.ts +124 -0
- package/src/cli/config-command.ts +183 -0
- package/src/cli/debug.ts +228 -0
- package/src/cli/desktop-first-run.ts +25 -0
- package/src/cli/doctor.ts +1022 -0
- package/src/cli/export-command.ts +201 -0
- package/src/cli/help.ts +370 -0
- package/src/cli/index.ts +1565 -0
- package/src/cli/init.ts +224 -0
- package/src/cli/integrations.ts +225 -0
- package/src/cli/interactive-confirm.ts +133 -0
- package/src/cli/internal-dispatch.ts +35 -0
- package/src/cli/launcher-context.ts +77 -0
- package/src/cli/models-runtime.ts +224 -0
- package/src/cli/models.ts +340 -0
- package/src/cli/observe.ts +170 -0
- package/src/cli/opencode.ts +587 -0
- package/src/cli/provider-runtime.ts +179 -0
- package/src/cli/provider.ts +476 -0
- package/src/cli/ready.ts +301 -0
- package/src/cli/route-policy.ts +92 -0
- package/src/cli/runtime-api.ts +328 -0
- package/src/cli/star-prompt.ts +211 -0
- package/src/cli/status-oauth.ts +78 -0
- package/src/cli/status.ts +321 -0
- package/src/cli/system-command.ts +196 -0
- package/src/cli/system-restart-client.ts +146 -0
- package/src/cli/tray-proxy.ts +205 -0
- package/src/cli/v2.ts +200 -0
- package/src/cli.ts +10 -0
- package/src/clients/config-export.ts +1109 -0
- package/src/codex/account-id.ts +34 -0
- package/src/codex/account-label.ts +34 -0
- package/src/codex/account-lifecycle.ts +172 -0
- package/src/codex/account-namespace-match.ts +63 -0
- package/src/codex/account-namespaces.ts +195 -0
- package/src/codex/account-pause.ts +20 -0
- package/src/codex/account-priority.ts +83 -0
- package/src/codex/account-runtime-state.ts +31 -0
- package/src/codex/account-store.ts +517 -0
- package/src/codex/account-usability.ts +40 -0
- package/src/codex/admission.ts +263 -0
- package/src/codex/app-server-processes.ts +799 -0
- package/src/codex/auth-api.ts +2098 -0
- package/src/codex/auth-collision.ts +107 -0
- package/src/codex/auth-context.ts +480 -0
- package/src/codex/autostart-health.ts +156 -0
- package/src/codex/catalog/account-models.ts +67 -0
- package/src/codex/catalog/aggregation.ts +471 -0
- package/src/codex/catalog/bundled.ts +533 -0
- package/src/codex/catalog/effort.ts +432 -0
- package/src/codex/catalog/filesystem-evidence.ts +302 -0
- package/src/codex/catalog/kinds.ts +2 -0
- package/src/codex/catalog/metadata.ts +287 -0
- package/src/codex/catalog/native-models.ts +7 -0
- package/src/codex/catalog/parsing.ts +503 -0
- package/src/codex/catalog/provider-fetch.ts +2267 -0
- package/src/codex/catalog/sync.ts +1606 -0
- package/src/codex/catalog-admission.ts +197 -0
- package/src/codex/catalog-refresh-status.ts +87 -0
- package/src/codex/catalog-write-serialization.ts +242 -0
- package/src/codex/catalog.ts +15 -0
- package/src/codex/codex-write-lock.ts +384 -0
- package/src/codex/convergence-types.ts +593 -0
- package/src/codex/convergence.ts +580 -0
- package/src/codex/custom-model-catalog-migration.ts +176 -0
- package/src/codex/data/upstream-models.json +830 -0
- package/src/codex/desired-state.ts +230 -0
- package/src/codex/desktop-client-processes.ts +521 -0
- package/src/codex/exec-invocation.ts +22 -0
- package/src/codex/features.ts +1091 -0
- package/src/codex/generation.ts +202 -0
- package/src/codex/history-job.ts +347 -0
- package/src/codex/history-lock.ts +242 -0
- package/src/codex/history-migration-guardian.ts +115 -0
- package/src/codex/history-provider.ts +1075 -0
- package/src/codex/history-transition.ts +105 -0
- package/src/codex/history-worker.ts +204 -0
- package/src/codex/home.ts +206 -0
- package/src/codex/inject-coordination.ts +257 -0
- package/src/codex/inject.ts +1857 -0
- package/src/codex/injected-marker.ts +79 -0
- package/src/codex/integration-record.ts +266 -0
- package/src/codex/internal/catalog-writer.ts +203 -0
- package/src/codex/internal/history-writer.ts +105 -0
- package/src/codex/journal.ts +172 -0
- package/src/codex/main-account-cache.ts +56 -0
- package/src/codex/main-account.ts +40 -0
- package/src/codex/management-convergence.ts +114 -0
- package/src/codex/model-cache.ts +267 -0
- package/src/codex/native-main-admission.ts +47 -0
- package/src/codex/native-main-auth-temp.ts +187 -0
- package/src/codex/native-main-claim.ts +167 -0
- package/src/codex/native-main-lock-file.ts +162 -0
- package/src/codex/native-main-owner.ts +329 -0
- package/src/codex/native-profile-api.ts +247 -0
- package/src/codex/native-profile-manager.ts +1531 -0
- package/src/codex/native-profile-processes.ts +121 -0
- package/src/codex/native-profile-recovery.ts +99 -0
- package/src/codex/native-profile-stage-store.ts +387 -0
- package/src/codex/native-profile-startup.ts +348 -0
- package/src/codex/native-profile-store.ts +855 -0
- package/src/codex/native-profile-types.ts +120 -0
- package/src/codex/native-residue.ts +691 -0
- package/src/codex/paths.ts +78 -0
- package/src/codex/plugins-doctor.ts +242 -0
- package/src/codex/pool-rotation.ts +295 -0
- package/src/codex/project-config-warnings.ts +426 -0
- package/src/codex/prompt-journal.ts +311 -0
- package/src/codex/prompt-layers.ts +967 -0
- package/src/codex/prompt-lock.ts +143 -0
- package/src/codex/provider-adoption.ts +242 -0
- package/src/codex/quota-rejection.ts +224 -0
- package/src/codex/quota.ts +494 -0
- package/src/codex/refresh.ts +60 -0
- package/src/codex/routing.ts +1855 -0
- package/src/codex/runtime.ts +659 -0
- package/src/codex/shim.ts +1215 -0
- package/src/codex/subagent-defaults.ts +557 -0
- package/src/codex/subagent-model-fallback.ts +560 -0
- package/src/codex/sync.ts +238 -0
- package/src/codex/transition-state.ts +612 -0
- package/src/codex/upstream-host-health.ts +368 -0
- package/src/codex/user-identity.ts +374 -0
- package/src/codex/warmup.ts +192 -0
- package/src/codex/websocket-registry.ts +100 -0
- package/src/codex/write-coordination.ts +114 -0
- package/src/combos/failover.ts +140 -0
- package/src/combos/index.ts +44 -0
- package/src/combos/request.ts +64 -0
- package/src/combos/resolve.ts +232 -0
- package/src/combos/types.ts +392 -0
- package/src/config.ts +3270 -0
- package/src/generated/model-metadata.ts +144 -0
- package/src/github/star-state.ts +203 -0
- package/src/grok/inject.ts +540 -0
- package/src/grok/inspect.ts +45 -0
- package/src/grok/status.ts +127 -0
- package/src/grok/sync.ts +66 -0
- package/src/images/artifacts.ts +516 -0
- package/src/images/fulfill-video.ts +163 -0
- package/src/images/fulfill.ts +149 -0
- package/src/images/index.ts +4 -0
- package/src/images/loop.ts +922 -0
- package/src/images/plan.ts +133 -0
- package/src/images/synthetic-tool.ts +133 -0
- package/src/images/types.ts +41 -0
- package/src/images/xai-client.ts +141 -0
- package/src/images/xai-video-client.ts +163 -0
- package/src/index.ts +22 -0
- package/src/integrations/config-io.ts +151 -0
- package/src/integrations/journal.ts +315 -0
- package/src/integrations/merge.ts +135 -0
- package/src/integrations/native/ownership-preflight.ts +202 -0
- package/src/integrations/ownership.ts +111 -0
- package/src/integrations/registry.ts +108 -0
- package/src/integrations/serialize.ts +235 -0
- package/src/integrations/state.ts +290 -0
- package/src/integrations/store.ts +103 -0
- package/src/integrations/writer.ts +492 -0
- package/src/lib/abort.ts +146 -0
- package/src/lib/admin-secrets.ts +25 -0
- package/src/lib/admission.ts +83 -0
- package/src/lib/app-owned-memory-stores.ts +173 -0
- package/src/lib/app-owned-memory.ts +265 -0
- package/src/lib/bounded-body.ts +242 -0
- package/src/lib/bun-binary-validator.d.mts +3 -0
- package/src/lib/bun-binary-validator.mjs +18 -0
- package/src/lib/bun-runtime.ts +184 -0
- package/src/lib/bun-stream-caps.ts +127 -0
- package/src/lib/config-ownership.ts +438 -0
- package/src/lib/crash-guard.ts +344 -0
- package/src/lib/debug-log-buffer.ts +83 -0
- package/src/lib/debug-settings.ts +108 -0
- package/src/lib/debug.ts +31 -0
- package/src/lib/destination-policy.ts +316 -0
- package/src/lib/errors.ts +364 -0
- package/src/lib/eventstream-decoder.ts +253 -0
- package/src/lib/gcp-adc.ts +341 -0
- package/src/lib/injection-debug-log.ts +58 -0
- package/src/lib/local-management-attestation.ts +51 -0
- package/src/lib/open-url.ts +25 -0
- package/src/lib/pinned-http.ts +182 -0
- package/src/lib/privacy.ts +20 -0
- package/src/lib/process-control.ts +168 -0
- package/src/lib/provider-environment.ts +470 -0
- package/src/lib/provider-outbound.ts +203 -0
- package/src/lib/provider-url.ts +14 -0
- package/src/lib/proxy-env.ts +18 -0
- package/src/lib/redact.ts +510 -0
- package/src/lib/remodex-home.ts +616 -0
- package/src/lib/retry-after.ts +55 -0
- package/src/lib/service-secrets.ts +178 -0
- package/src/lib/shadow-call.ts +54 -0
- package/src/lib/sidecar-tracker.ts +52 -0
- package/src/lib/sse-decoder.ts +364 -0
- package/src/lib/state-store-registrations.ts +109 -0
- package/src/lib/state-store-sweeper.ts +184 -0
- package/src/lib/system-restart-contract.ts +73 -0
- package/src/lib/test-home-guard.ts +98 -0
- package/src/lib/token-estimate.ts +69 -0
- package/src/lib/translator-budget.ts +366 -0
- package/src/lib/upstream-reachability.ts +91 -0
- package/src/lib/upstream-retry.ts +508 -0
- package/src/lib/win-exec.ts +115 -0
- package/src/lib/win-paths.ts +68 -0
- package/src/lib/windows-elevation.ts +705 -0
- package/src/lib/windows-secret-acl.ts +817 -0
- package/src/lib/windows-user-principal.ts +283 -0
- package/src/lib/winsw.ts +402 -0
- package/src/model-sources.ts +73 -0
- package/src/oauth/anthropic-routing.ts +594 -0
- package/src/oauth/anthropic.ts +177 -0
- package/src/oauth/callback-server.ts +294 -0
- package/src/oauth/chatgpt.ts +150 -0
- package/src/oauth/command-code.ts +239 -0
- package/src/oauth/cursor.ts +231 -0
- package/src/oauth/github-copilot.ts +428 -0
- package/src/oauth/google-antigravity.ts +230 -0
- package/src/oauth/health.ts +443 -0
- package/src/oauth/index.ts +1280 -0
- package/src/oauth/key-providers.ts +128 -0
- package/src/oauth/kimi.ts +213 -0
- package/src/oauth/kiro-credentials.ts +726 -0
- package/src/oauth/kiro.ts +621 -0
- package/src/oauth/local-token-detect.ts +121 -0
- package/src/oauth/log.ts +48 -0
- package/src/oauth/login-cli.ts +163 -0
- package/src/oauth/pkce.ts +15 -0
- package/src/oauth/store.ts +655 -0
- package/src/oauth/token-guardian.ts +309 -0
- package/src/oauth/types.ts +62 -0
- package/src/oauth/xai.ts +241 -0
- package/src/providers/alibaba-region-backup.ts +75 -0
- package/src/providers/alibaba-region-migration.ts +156 -0
- package/src/providers/alibaba-region-startup.ts +36 -0
- package/src/providers/antigravity-models.ts +317 -0
- package/src/providers/api-keys.ts +140 -0
- package/src/providers/base-url-choices.ts +64 -0
- package/src/providers/codex-capacity.ts +288 -0
- package/src/providers/command-code-efforts.ts +85 -0
- package/src/providers/context-cap.ts +73 -0
- package/src/providers/derive.ts +451 -0
- package/src/providers/free-directory.ts +187 -0
- package/src/providers/github-copilot-transport.ts +56 -0
- package/src/providers/google-vertex-location.ts +14 -0
- package/src/providers/key-failover.ts +271 -0
- package/src/providers/kiro-models.ts +67 -0
- package/src/providers/label.ts +19 -0
- package/src/providers/model-discovery-limits.ts +16 -0
- package/src/providers/model-discovery.ts +361 -0
- package/src/providers/openai-sidecar.ts +235 -0
- package/src/providers/openai-tier-startup.ts +27 -0
- package/src/providers/openai-tiers.ts +301 -0
- package/src/providers/openai-virtual-models.ts +83 -0
- package/src/providers/openrouter-routing.ts +102 -0
- package/src/providers/provider-id-rewrite.ts +179 -0
- package/src/providers/quota.ts +1942 -0
- package/src/providers/reasoning-capabilities.ts +336 -0
- package/src/providers/registry.ts +2375 -0
- package/src/providers/slug-codec.ts +74 -0
- package/src/providers/xai-transport.ts +149 -0
- package/src/reasoning-effort.ts +243 -0
- package/src/responses/compaction.ts +124 -0
- package/src/responses/hosted-tool-policy.ts +9 -0
- package/src/responses/parser.ts +714 -0
- package/src/responses/reasoning-envelope.ts +60 -0
- package/src/responses/reasoning-replay-cache.ts +106 -0
- package/src/responses/schema.ts +159 -0
- package/src/responses/spill-store.ts +431 -0
- package/src/responses/state.ts +1039 -0
- package/src/responses/tool-groups.ts +19 -0
- package/src/router.ts +802 -0
- package/src/routing/analytics.ts +377 -0
- package/src/routing/capability.ts +205 -0
- package/src/routing/cost.ts +77 -0
- package/src/routing/evaluator.ts +444 -0
- package/src/routing/health.ts +401 -0
- package/src/routing/history/cursor.ts +43 -0
- package/src/routing/history/indexer.ts +605 -0
- package/src/routing/history/schema.ts +72 -0
- package/src/routing/profile-namespace.ts +15 -0
- package/src/routing/profile.ts +424 -0
- package/src/routing/quota.ts +145 -0
- package/src/routing/request-evidence.ts +45 -0
- package/src/routing/trace.ts +686 -0
- package/src/server/adapter-resolve.ts +83 -0
- package/src/server/auth-cors.ts +606 -0
- package/src/server/chat-completions.ts +379 -0
- package/src/server/claude-messages.ts +980 -0
- package/src/server/effort-policy.ts +251 -0
- package/src/server/github-copilot-responses-repair.ts +338 -0
- package/src/server/gui-static.ts +152 -0
- package/src/server/image-retry.ts +42 -0
- package/src/server/images.ts +485 -0
- package/src/server/index.ts +1633 -0
- package/src/server/lifecycle.ts +482 -0
- package/src/server/live.ts +609 -0
- package/src/server/management/agent-settings-routes.ts +1180 -0
- package/src/server/management/android-remote-routes.ts +390 -0
- package/src/server/management/api-access.ts +141 -0
- package/src/server/management/api-key-usage.ts +167 -0
- package/src/server/management/body.ts +35 -0
- package/src/server/management/combo-routes.ts +244 -0
- package/src/server/management/config-routes.ts +602 -0
- package/src/server/management/context.ts +88 -0
- package/src/server/management/integration-routes.ts +538 -0
- package/src/server/management/logs-usage-routes.ts +516 -0
- package/src/server/management/model-routes.ts +519 -0
- package/src/server/management/model-rows.ts +143 -0
- package/src/server/management/native-integration-routes.ts +781 -0
- package/src/server/management/oauth-account-routes.ts +573 -0
- package/src/server/management/provider-routes.ts +781 -0
- package/src/server/management/request-history-routes.ts +191 -0
- package/src/server/management/routing-analytics-routes.ts +74 -0
- package/src/server/management/routing-profile-routes.ts +384 -0
- package/src/server/management/shared.ts +277 -0
- package/src/server/management/sidebar-routes.ts +106 -0
- package/src/server/management/sync-response.ts +69 -0
- package/src/server/management/system-restart.ts +433 -0
- package/src/server/management/system-routes.ts +141 -0
- package/src/server/management/usage-summary-cache.ts +86 -0
- package/src/server/management-api.ts +269 -0
- package/src/server/management-auth.ts +353 -0
- package/src/server/memory-watchdog.ts +156 -0
- package/src/server/port-reclaim.ts +307 -0
- package/src/server/ports.ts +156 -0
- package/src/server/proxy-liveness.ts +326 -0
- package/src/server/proxy-stop.ts +92 -0
- package/src/server/readiness.ts +99 -0
- package/src/server/relay-eager.ts +353 -0
- package/src/server/relay.ts +1179 -0
- package/src/server/request-decompress.ts +132 -0
- package/src/server/request-log-conversation.ts +168 -0
- package/src/server/request-log.ts +1072 -0
- package/src/server/responses/collaboration.ts +409 -0
- package/src/server/responses/compact.ts +710 -0
- package/src/server/responses/core.ts +3561 -0
- package/src/server/responses/encrypted-payload.ts +308 -0
- package/src/server/responses/fetch-helpers.ts +171 -0
- package/src/server/responses/passthrough-error.ts +78 -0
- package/src/server/responses/policy-fallback.ts +152 -0
- package/src/server/responses/terminal-guard.ts +230 -0
- package/src/server/responses/upstream-error.ts +48 -0
- package/src/server/responses-image-gen-repair.ts +132 -0
- package/src/server/responses-item-id-repair.ts +272 -0
- package/src/server/responses-json-events.ts +52 -0
- package/src/server/responses-model-rewrite.ts +29 -0
- package/src/server/responses-snapshot-repair.ts +621 -0
- package/src/server/responses.ts +10 -0
- package/src/server/search.ts +181 -0
- package/src/server/sse-frame-buffer.ts +292 -0
- package/src/server/sse-payload-rewrite.ts +263 -0
- package/src/server/startup-action-control.ts +308 -0
- package/src/server/startup-health-cache.ts +119 -0
- package/src/server/system-env.ts +418 -0
- package/src/server/windows-tcp-drop.ts +184 -0
- package/src/server/windows-tray-control.ts +41 -0
- package/src/server/ws-bridge.ts +470 -0
- package/src/service-manager-probe.ts +824 -0
- package/src/service.ts +3011 -0
- package/src/stall-timeout.ts +20 -0
- package/src/storage/cleanup-job.ts +57 -0
- package/src/storage/cleanup.ts +3085 -0
- package/src/storage/policy-job.ts +457 -0
- package/src/storage/policy-scheduler.ts +40 -0
- package/src/storage/policy-worker.ts +59 -0
- package/src/storage/policy.ts +527 -0
- package/src/storage/restore-job.ts +299 -0
- package/src/storage/restore-worker.ts +58 -0
- package/src/storage/scanner.ts +238 -0
- package/src/storage/storage-mutation-coordinator.ts +139 -0
- package/src/storage/worker-lifecycle.ts +215 -0
- package/src/tray/assets/opencodex-tray-offline.ico +0 -0
- package/src/tray/assets/opencodex-tray-online.ico +0 -0
- package/src/tray/assets/opencodex-tray-warning.ico +0 -0
- package/src/tray/assets/opencodex-tray.png +0 -0
- package/src/tray/windows-tray.ps1 +364 -0
- package/src/tray/windows.ts +738 -0
- package/src/types.ts +1531 -0
- package/src/update/badge.ts +72 -0
- package/src/update/desktop-release.ts +1620 -0
- package/src/update/index.ts +402 -0
- package/src/update/job.ts +1906 -0
- package/src/update/notify.ts +261 -0
- package/src/update/npm-cache-preflight.d.mts +47 -0
- package/src/update/npm-cache-preflight.mjs +201 -0
- package/src/update/npm-invocation.d.mts +23 -0
- package/src/update/npm-invocation.mjs +94 -0
- package/src/update/tray-update-plan.d.mts +18 -0
- package/src/update/tray-update-plan.mjs +38 -0
- package/src/usage/cost.ts +0 -0
- package/src/usage/debug.ts +97 -0
- package/src/usage/expected-prices.ts +283 -0
- package/src/usage/log.ts +695 -0
- package/src/usage/summary.ts +585 -0
- package/src/usage/totals.ts +14 -0
- package/src/vision/anthropic-describe.ts +185 -0
- package/src/vision/describe.ts +127 -0
- package/src/vision/index.ts +558 -0
- package/src/vision/reasoning.ts +55 -0
- package/src/web-search/anthropic-executor.ts +189 -0
- package/src/web-search/executor.ts +105 -0
- package/src/web-search/format-result.ts +89 -0
- package/src/web-search/index.ts +196 -0
- package/src/web-search/loop.ts +791 -0
- package/src/web-search/parse.ts +235 -0
- package/src/web-search/progress-stream.ts +342 -0
- package/src/web-search/synthetic-tool.ts +47 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { join, resolve } from "node:path";
|
|
2
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, truncateSync, unlinkSync } from "node:fs";
|
|
3
|
+
import { atomicWriteFile, getConfigDir } from "../config";
|
|
4
|
+
import type { OcxConfig } from "../types";
|
|
5
|
+
import { providerEnvironmentReferences } from "./provider-environment";
|
|
6
|
+
|
|
7
|
+
export function serviceApiTokenFilePath(): string {
|
|
8
|
+
return join(getConfigDir(), "service-api-token");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Pointer carried by every durable service wrapper; the file itself is owner-only. */
|
|
12
|
+
export const SERVICE_PROVIDER_ENV_FILE_ENV = "OCX_PROVIDER_ENV_FILE";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The durable provider snapshot is safe to hydrate for both supervised service
|
|
16
|
+
* children and the installed desktop shell's short-lived CLI actions. Keeping
|
|
17
|
+
* this predicate here makes the trust boundary explicit: ordinary interactive
|
|
18
|
+
* commands do not implicitly load a persisted secret file.
|
|
19
|
+
*/
|
|
20
|
+
export function shouldLoadServiceProviderEnvironment(
|
|
21
|
+
env: Record<string, string | undefined> = process.env,
|
|
22
|
+
): boolean {
|
|
23
|
+
return env.OCX_SERVICE === "1" || env.OCX_DESKTOP === "1";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const SERVICE_PROVIDER_ENV_FILE = "service-provider-env.json";
|
|
27
|
+
const SERVICE_PROVIDER_ENV_VERSION = 1;
|
|
28
|
+
const MAX_SERVICE_PROVIDER_ENV_BYTES = 256 * 1024;
|
|
29
|
+
const MAX_SERVICE_PROVIDER_ENV_VALUES = 128;
|
|
30
|
+
const MAX_SERVICE_PROVIDER_ENV_VALUE_BYTES = 64 * 1024;
|
|
31
|
+
|
|
32
|
+
type ServiceProviderEnvFile = {
|
|
33
|
+
version: 1;
|
|
34
|
+
variables: Record<string, string>;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
function validEnvironmentName(value: string): boolean {
|
|
38
|
+
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(value);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function nonEmpty(value: string | undefined): string | undefined {
|
|
42
|
+
return value?.trim() ? value : undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Environment names which must survive an interactive shell → durable-service handoff.
|
|
47
|
+
*
|
|
48
|
+
* The config deliberately keeps these as `${NAME}` references rather than writing provider
|
|
49
|
+
* credentials to config.json. A launchd/systemd/Task Scheduler service does not inherit the
|
|
50
|
+
* user's shell, so the service installer snapshots only the referenced values into an owner-only
|
|
51
|
+
* file and gives the child a pointer to it. OAuth/forward/local providers do not use apiKey.
|
|
52
|
+
*/
|
|
53
|
+
export function serviceProviderEnvironmentReferences(
|
|
54
|
+
config: Pick<OcxConfig, "proxy" | "providers">,
|
|
55
|
+
): string[] {
|
|
56
|
+
return providerEnvironmentReferences(config);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function serviceProviderEnvFilePath(): string {
|
|
60
|
+
return join(getConfigDir(), SERVICE_PROVIDER_ENV_FILE);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function safeReadServiceProviderEnvFile(path: string): Record<string, string> {
|
|
64
|
+
try {
|
|
65
|
+
const stat = lstatSync(path);
|
|
66
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_SERVICE_PROVIDER_ENV_BYTES) return {};
|
|
67
|
+
if (process.platform !== "win32" && (stat.mode & 0o077) !== 0) return {};
|
|
68
|
+
const parsed = JSON.parse(readFileSync(path, "utf8")) as Partial<ServiceProviderEnvFile>;
|
|
69
|
+
if (parsed.version !== SERVICE_PROVIDER_ENV_VERSION || !parsed.variables || typeof parsed.variables !== "object" || Array.isArray(parsed.variables)) return {};
|
|
70
|
+
const values: Record<string, string> = Object.create(null) as Record<string, string>;
|
|
71
|
+
for (const [name, value] of Object.entries(parsed.variables)) {
|
|
72
|
+
if (Object.keys(values).length >= MAX_SERVICE_PROVIDER_ENV_VALUES) break;
|
|
73
|
+
if (!validEnvironmentName(name) || typeof value !== "string" || value.includes("\0")) continue;
|
|
74
|
+
if (Buffer.byteLength(value, "utf8") > MAX_SERVICE_PROVIDER_ENV_VALUE_BYTES) continue;
|
|
75
|
+
values[name] = value;
|
|
76
|
+
}
|
|
77
|
+
return values;
|
|
78
|
+
} catch {
|
|
79
|
+
return {};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Values that a service can use now, without overriding explicitly supplied manager variables. */
|
|
84
|
+
export function unresolvedServiceProviderEnvironment(
|
|
85
|
+
config: Pick<OcxConfig, "proxy" | "providers">,
|
|
86
|
+
env: Record<string, string | undefined> = process.env,
|
|
87
|
+
): string[] {
|
|
88
|
+
const saved = safeReadServiceProviderEnvFile(serviceProviderEnvFilePath());
|
|
89
|
+
return serviceProviderEnvironmentReferences(config).filter(name => !nonEmpty(env[name]) && !nonEmpty(saved[name]));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function removeServiceProviderEnvFile(): void {
|
|
93
|
+
const path = serviceProviderEnvFilePath();
|
|
94
|
+
try {
|
|
95
|
+
const stat = lstatSync(path);
|
|
96
|
+
if (!stat.isFile() || stat.isSymbolicLink()) return;
|
|
97
|
+
try { truncateSync(path, 0); } catch { /* unlink may still succeed */ }
|
|
98
|
+
unlinkSync(path);
|
|
99
|
+
} catch {
|
|
100
|
+
/* no owner-only snapshot to remove */
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Persist the effective values for referenced provider/config env vars. The supplied process
|
|
106
|
+
* environment wins; a previous owner-only snapshot is retained only when a repair runs from a
|
|
107
|
+
* shell that no longer exports the value. Missing references are reported, never serialized.
|
|
108
|
+
*/
|
|
109
|
+
export function writeServiceProviderEnvironment(
|
|
110
|
+
config: Pick<OcxConfig, "proxy" | "providers">,
|
|
111
|
+
env: Record<string, string | undefined> = process.env,
|
|
112
|
+
): { missing: string[]; written: number } {
|
|
113
|
+
const names = serviceProviderEnvironmentReferences(config);
|
|
114
|
+
if (names.length === 0) {
|
|
115
|
+
removeServiceProviderEnvFile();
|
|
116
|
+
return { missing: [], written: 0 };
|
|
117
|
+
}
|
|
118
|
+
const previous = safeReadServiceProviderEnvFile(serviceProviderEnvFilePath());
|
|
119
|
+
const variables: Record<string, string> = {};
|
|
120
|
+
const missing: string[] = [];
|
|
121
|
+
for (const name of names) {
|
|
122
|
+
const value = nonEmpty(env[name]) ?? nonEmpty(previous[name]);
|
|
123
|
+
if (!value) {
|
|
124
|
+
missing.push(name);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
variables[name] = value;
|
|
128
|
+
}
|
|
129
|
+
if (missing.length > 0) return { missing, written: 0 };
|
|
130
|
+
|
|
131
|
+
const dir = getConfigDir();
|
|
132
|
+
const path = serviceProviderEnvFilePath();
|
|
133
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
134
|
+
atomicWriteFile(
|
|
135
|
+
path,
|
|
136
|
+
`${JSON.stringify({ version: SERVICE_PROVIDER_ENV_VERSION, variables } satisfies ServiceProviderEnvFile)}\n`,
|
|
137
|
+
);
|
|
138
|
+
return { missing: [], written: Object.keys(variables).length };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Hydrate an Remodex service process from its exact owner-only snapshot. A pointer outside the
|
|
143
|
+
* active OPENCODEX_HOME is ignored so project-controlled dotenv files cannot make `rmx` read an
|
|
144
|
+
* arbitrary path. Existing manager-provided variables always win over the snapshot.
|
|
145
|
+
*/
|
|
146
|
+
export function loadServiceProviderEnvironmentFromFile(
|
|
147
|
+
env: Record<string, string | undefined> = process.env,
|
|
148
|
+
): number {
|
|
149
|
+
const requested = env[SERVICE_PROVIDER_ENV_FILE_ENV]?.trim();
|
|
150
|
+
if (!requested) return 0;
|
|
151
|
+
const expected = serviceProviderEnvFilePath();
|
|
152
|
+
if (resolve(requested) !== resolve(expected)) return 0;
|
|
153
|
+
let loaded = 0;
|
|
154
|
+
for (const [name, value] of Object.entries(safeReadServiceProviderEnvFile(expected))) {
|
|
155
|
+
if (nonEmpty(env[name])) continue;
|
|
156
|
+
env[name] = value;
|
|
157
|
+
loaded += 1;
|
|
158
|
+
}
|
|
159
|
+
return loaded;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* App-side service token loading (WinSW native mode has no batch wrapper to read the
|
|
164
|
+
* token file into the environment). Pure: returns the token or null — the CALLER
|
|
165
|
+
* assigns it to process.env.OPENCODEX_API_AUTH_TOKEN. Loads only when the env token
|
|
166
|
+
* is empty and OCX_API_TOKEN_FILE names a readable file.
|
|
167
|
+
*/
|
|
168
|
+
export function loadServiceTokenFromFile(env: Record<string, string | undefined>): string | null {
|
|
169
|
+
if (env.OPENCODEX_API_AUTH_TOKEN?.trim()) return null;
|
|
170
|
+
const file = env.OCX_API_TOKEN_FILE?.trim();
|
|
171
|
+
if (!file) return null;
|
|
172
|
+
try {
|
|
173
|
+
const token = readFileSync(file, "utf8").trim();
|
|
174
|
+
return token || null;
|
|
175
|
+
} catch {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shadow-call intercept source models.
|
|
3
|
+
*
|
|
4
|
+
* Codex 0.145.0+ uses `gpt-5.6-luna` for helper calls. Older clients through
|
|
5
|
+
* 0.144.x used `gpt-5.4-mini`; operators supporting them can restore that
|
|
6
|
+
* prefix with the `sourceModels` override. Every surface that names the
|
|
7
|
+
* intercepted model (management API, GUI badges/tooltips, CLI) reads it from
|
|
8
|
+
* here instead of hard-coding a slug that goes stale on the next client bump.
|
|
9
|
+
*/
|
|
10
|
+
export const DEFAULT_SHADOW_SOURCE_MODELS = ["gpt-5.6-luna"] as const;
|
|
11
|
+
|
|
12
|
+
/** Normalize a persisted `sourceModels` override; falls back to the defaults. */
|
|
13
|
+
export function shadowSourceModels(configured?: unknown): string[] {
|
|
14
|
+
const configuredStrings = Array.isArray(configured)
|
|
15
|
+
? configured
|
|
16
|
+
.filter((v): v is string => typeof v === "string" && v.trim() !== "")
|
|
17
|
+
.map(v => v.trim())
|
|
18
|
+
: [];
|
|
19
|
+
return configuredStrings.length > 0 ? configuredStrings : [...DEFAULT_SHADOW_SOURCE_MODELS];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* True when `modelId` is one of Codex's helper/shadow source models.
|
|
24
|
+
* Routed ids (`provider/model`) are hard-excluded: a shadow call is always a
|
|
25
|
+
* bare native slug, and an explicit routed selection must never be hijacked.
|
|
26
|
+
*/
|
|
27
|
+
export function isShadowSourceModel(modelId: string, configured?: unknown): boolean {
|
|
28
|
+
if (modelId.includes("/")) return false;
|
|
29
|
+
return shadowSourceModels(configured).some(prefix => modelId.startsWith(prefix));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Decide whether a matching source model should use the opt-in intercept.
|
|
34
|
+
*
|
|
35
|
+
* Codex 0.145.0+ identifies normal user turns and maintenance requests in
|
|
36
|
+
* x-codex-turn-metadata. Only an explicit normal turn bypasses interception;
|
|
37
|
+
* missing or unrecognized metadata retains the legacy opt-in prefix behavior.
|
|
38
|
+
*/
|
|
39
|
+
export function shouldInterceptShadowCall(
|
|
40
|
+
modelId: string,
|
|
41
|
+
configured: unknown,
|
|
42
|
+
headers: Headers,
|
|
43
|
+
): boolean {
|
|
44
|
+
if (!isShadowSourceModel(modelId, configured)) return false;
|
|
45
|
+
const rawMetadata = headers.get("x-codex-turn-metadata");
|
|
46
|
+
if (rawMetadata === null) return true;
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const parsed = JSON.parse(rawMetadata) as { request_kind?: unknown };
|
|
50
|
+
return parsed?.request_kind !== "turn";
|
|
51
|
+
} catch {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight in-flight breadcrumb for sidecar work (web-search / vision), read by crash-guard.
|
|
3
|
+
*
|
|
4
|
+
* The proxy's unhandled rejections arrive with a native-only stack, so the throw site is invisible.
|
|
5
|
+
* To confirm or rule out the strong correlation with sidecar activity (the gpt-mini web-search /
|
|
6
|
+
* vision passes that run an EXTRA upstream fetch alongside the main turn), each sidecar call brackets
|
|
7
|
+
* itself with enter()/exit(). crash-guard then records whether any sidecar was in flight when a
|
|
8
|
+
* rejection fired — turning a guess into evidence without a debugger.
|
|
9
|
+
*/
|
|
10
|
+
let inFlight = 0;
|
|
11
|
+
let lastLabel = "";
|
|
12
|
+
let lastEnterAt = 0;
|
|
13
|
+
|
|
14
|
+
export function sidecarEnter(label: string): () => void {
|
|
15
|
+
inFlight++;
|
|
16
|
+
lastLabel = truncateRetainedUtf8(label, MAX_DIAGNOSTIC_VALUE_BYTES);
|
|
17
|
+
lastEnterAt = Date.now();
|
|
18
|
+
let exited = false;
|
|
19
|
+
return () => {
|
|
20
|
+
if (exited) return;
|
|
21
|
+
exited = true;
|
|
22
|
+
if (inFlight > 0) inFlight--;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function sidecarBreadcrumb(): { inFlight: number; lastLabel: string; sinceMs: number } {
|
|
27
|
+
return {
|
|
28
|
+
inFlight,
|
|
29
|
+
lastLabel,
|
|
30
|
+
sinceMs: lastEnterAt ? Date.now() - lastEnterAt : 0,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Generic last-activity breadcrumb (any code path, not just sidecars). The proxy's native-only
|
|
36
|
+
* rejections carry no source location, so we record the most recent meaningful activity —
|
|
37
|
+
* request path + a short note — to correlate the next fault with what the daemon was doing.
|
|
38
|
+
*/
|
|
39
|
+
let lastActivity = "";
|
|
40
|
+
let lastActivityAt = 0;
|
|
41
|
+
|
|
42
|
+
export function markActivity(note: string): void {
|
|
43
|
+
lastActivity = truncateRetainedUtf8(note, MAX_DIAGNOSTIC_VALUE_BYTES);
|
|
44
|
+
lastActivityAt = Date.now();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function activityBreadcrumb(): { note: string; sinceMs: number } {
|
|
48
|
+
return { note: lastActivity, sinceMs: lastActivityAt ? Date.now() - lastActivityAt : 0 };
|
|
49
|
+
}
|
|
50
|
+
import { truncateRetainedUtf8 } from "./admission";
|
|
51
|
+
|
|
52
|
+
const MAX_DIAGNOSTIC_VALUE_BYTES = 8 * 1024;
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TRANSLATOR_MAX_SSE_EVENT_BYTES,
|
|
3
|
+
TranslatorBudgetExceededError,
|
|
4
|
+
type TranslatorBudget,
|
|
5
|
+
} from "./translator-budget";
|
|
6
|
+
|
|
7
|
+
export interface ServerSentEvent {
|
|
8
|
+
event?: string;
|
|
9
|
+
data: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type SseRecord =
|
|
13
|
+
| { kind: "event"; event?: string; data: string }
|
|
14
|
+
| { kind: "comment"; comment: string };
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Extract one SSE field value from a single line, or null when the line is a different field.
|
|
18
|
+
*
|
|
19
|
+
* The space after the colon is OPTIONAL in text/event-stream: `data:{"a":1}` is as valid as
|
|
20
|
+
* `data: {"a":1}`. Parsers that hardcoded `startsWith("data: ")` silently dropped every frame
|
|
21
|
+
* from a producer that omits it, which surfaced as a completed turn with no content (#1170).
|
|
22
|
+
*
|
|
23
|
+
* Strips at most ONE leading space — the same rule `decodeServerSentEvents` applies below — so a
|
|
24
|
+
* payload that legitimately begins with whitespace keeps the rest of it. Does not trim the value:
|
|
25
|
+
* callers own that choice, and some of them intentionally keep trailing bytes.
|
|
26
|
+
*/
|
|
27
|
+
export function sseFieldValue(line: string, field: string): string | null {
|
|
28
|
+
if (!line.startsWith(field)) return null;
|
|
29
|
+
const rest = line.slice(field.length);
|
|
30
|
+
// A colonless field line is the field with an empty value per the SSE rules, and
|
|
31
|
+
// `decodeServerSentEvents` below treats it that way (`colon < 0` -> valueStart = line.length).
|
|
32
|
+
// These helpers must not disagree with the decoder they mirror.
|
|
33
|
+
if (rest.length === 0) return "";
|
|
34
|
+
if (!rest.startsWith(":")) return null;
|
|
35
|
+
return rest.startsWith(": ") ? rest.slice(2) : rest.slice(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Offset-only variant of {@link sseFieldValue} for parsers that index into a larger buffer.
|
|
40
|
+
*
|
|
41
|
+
* Returns the index where the field's value begins within `text`, or -1 when the line at
|
|
42
|
+
* `[lineStart, lineEnd)` is a different field. Slicing nothing matters for the live Claude relay,
|
|
43
|
+
* whose translator-budget accounting reserves bytes by offset — materializing the line first would
|
|
44
|
+
* allocate the very string the budget exists to bound.
|
|
45
|
+
*/
|
|
46
|
+
export function sseFieldOffset(text: string, lineStart: number, lineEnd: number, field: string): number {
|
|
47
|
+
if (!text.startsWith(field, lineStart)) return -1;
|
|
48
|
+
let valueStart = lineStart + field.length;
|
|
49
|
+
// Colonless field line: empty value, positioned at end-of-line (matches the decoder).
|
|
50
|
+
if (valueStart >= lineEnd) return lineEnd;
|
|
51
|
+
if (text[valueStart] !== ":") return -1;
|
|
52
|
+
valueStart += 1;
|
|
53
|
+
if (valueStart < lineEnd && text[valueStart] === " ") valueStart += 1;
|
|
54
|
+
return valueStart;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Decode text/event-stream records across arbitrary fetch chunk boundaries.
|
|
59
|
+
*
|
|
60
|
+
* The final record is dispatched at EOF even when the upstream omits the trailing blank line or
|
|
61
|
+
* final newline. That matters for compatible APIs that place a terminal event in the last bytes of
|
|
62
|
+
* the body: dropping that record turns a successful response into an adapter_eof failure.
|
|
63
|
+
*/
|
|
64
|
+
export function decodeServerSentEvents(
|
|
65
|
+
source: ReadableStream<Uint8Array>,
|
|
66
|
+
options: { includeComments: true; signal?: AbortSignal; translatorBudget: TranslatorBudget },
|
|
67
|
+
): AsyncGenerator<SseRecord>;
|
|
68
|
+
export function decodeServerSentEvents(
|
|
69
|
+
source: ReadableStream<Uint8Array>,
|
|
70
|
+
options: { includeComments?: false; signal?: AbortSignal; translatorBudget: TranslatorBudget },
|
|
71
|
+
): AsyncGenerator<ServerSentEvent>;
|
|
72
|
+
export async function* decodeServerSentEvents(
|
|
73
|
+
source: ReadableStream<Uint8Array>,
|
|
74
|
+
options: { includeComments?: boolean; signal?: AbortSignal; translatorBudget: TranslatorBudget },
|
|
75
|
+
): AsyncGenerator<ServerSentEvent | SseRecord> {
|
|
76
|
+
const translatorBudget = options.translatorBudget;
|
|
77
|
+
const reader = source.getReader();
|
|
78
|
+
const decoder = new TextDecoder();
|
|
79
|
+
let lineBuffer = "";
|
|
80
|
+
let lineRawBytes = 0;
|
|
81
|
+
let lineRetainedBytes = 0;
|
|
82
|
+
let event: string | undefined;
|
|
83
|
+
let eventBytes = 0;
|
|
84
|
+
let dataLines: string[] = [];
|
|
85
|
+
let dataLinesBytes = 0;
|
|
86
|
+
const scope = { kind: "live_transient" as const };
|
|
87
|
+
|
|
88
|
+
const utf8SliceBytes = (value: string, start: number, end: number): number => {
|
|
89
|
+
let bytes = 0;
|
|
90
|
+
for (let index = start; index < end; index++) {
|
|
91
|
+
const codePoint = value.codePointAt(index)!;
|
|
92
|
+
if (codePoint <= 0x7f) bytes += 1;
|
|
93
|
+
else if (codePoint <= 0x7ff) bytes += 2;
|
|
94
|
+
else if (codePoint <= 0xffff) bytes += 3;
|
|
95
|
+
else {
|
|
96
|
+
bytes += 4;
|
|
97
|
+
index += 1;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return bytes;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const linePrefixAfter = (sourceValue: string, start: number, end: number): string => {
|
|
104
|
+
if (lineBuffer.length >= 6) return lineBuffer.slice(0, 6);
|
|
105
|
+
return (lineBuffer + sourceValue.slice(start, Math.min(end, start + 6 - lineBuffer.length))).slice(0, 6);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const accountedLineBytes = (rawBytes: number, prefix: string): number => {
|
|
109
|
+
if (prefix.length < 5 && "data:".startsWith(prefix)) return 0;
|
|
110
|
+
if (!prefix.startsWith("data:")) return rawBytes;
|
|
111
|
+
return Math.max(0, rawBytes - (prefix[5] === " " ? 6 : 5));
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const appendLine = (sourceValue: string, start: number, end: number): void => {
|
|
115
|
+
if (start >= end) return;
|
|
116
|
+
const nextRawBytes = lineRawBytes + utf8SliceBytes(sourceValue, start, end);
|
|
117
|
+
const prefix = linePrefixAfter(sourceValue, start, end);
|
|
118
|
+
const nextRetainedBytes = accountedLineBytes(nextRawBytes, prefix);
|
|
119
|
+
if (prefix.startsWith("data:")) {
|
|
120
|
+
const logicalEventBytes = dataLinesBytes + dataLines.length + nextRetainedBytes;
|
|
121
|
+
if (logicalEventBytes > TRANSLATOR_MAX_SSE_EVENT_BYTES) {
|
|
122
|
+
throw new TranslatorBudgetExceededError("live_transient", TRANSLATOR_MAX_SSE_EVENT_BYTES);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const reservation = translatorBudget.reserveTransient(nextRawBytes, scope);
|
|
126
|
+
try {
|
|
127
|
+
const fragment = sourceValue.slice(start, end);
|
|
128
|
+
lineBuffer += fragment;
|
|
129
|
+
reservation.commitRetained();
|
|
130
|
+
translatorBudget.releaseRetained(lineRetainedBytes, scope);
|
|
131
|
+
lineRawBytes = nextRawBytes;
|
|
132
|
+
lineRetainedBytes = nextRawBytes;
|
|
133
|
+
} catch (error) {
|
|
134
|
+
reservation.release();
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
// Prompt cancellation channel: an abort cancels the underlying reader directly, which
|
|
139
|
+
// settles any in-flight read() so a consumer's iterator.return() cannot hang behind an
|
|
140
|
+
// idle upstream (a plain generator return waits for the pending await first).
|
|
141
|
+
const signal = options?.signal;
|
|
142
|
+
const onAbort = () => { reader.cancel(signal?.reason).catch(() => { /* already closed */ }); };
|
|
143
|
+
if (signal?.aborted) onAbort();
|
|
144
|
+
else signal?.addEventListener("abort", onAbort, { once: true });
|
|
145
|
+
|
|
146
|
+
const includeComments = options?.includeComments === true;
|
|
147
|
+
|
|
148
|
+
const dispatch = (): { record: ServerSentEvent | SseRecord; bytes: number } | undefined => {
|
|
149
|
+
if (dataLines.length === 0) {
|
|
150
|
+
translatorBudget.releaseRetained(eventBytes, scope);
|
|
151
|
+
event = undefined;
|
|
152
|
+
eventBytes = 0;
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
const payloadBytes = dataLinesBytes + Math.max(0, dataLines.length - 1);
|
|
156
|
+
if (payloadBytes > TRANSLATOR_MAX_SSE_EVENT_BYTES) {
|
|
157
|
+
throw new TranslatorBudgetExceededError("live_transient", TRANSLATOR_MAX_SSE_EVENT_BYTES);
|
|
158
|
+
}
|
|
159
|
+
let data: string;
|
|
160
|
+
if (dataLines.length === 1) {
|
|
161
|
+
data = dataLines[0]!;
|
|
162
|
+
} else {
|
|
163
|
+
const reservation = translatorBudget.reserveTransient(payloadBytes, scope);
|
|
164
|
+
try {
|
|
165
|
+
data = dataLines.join("\n");
|
|
166
|
+
reservation.commitRetained();
|
|
167
|
+
translatorBudget.releaseRetained(dataLinesBytes, scope);
|
|
168
|
+
} catch (error) {
|
|
169
|
+
reservation.release();
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const record = { ...(event ? { event } : {}), data };
|
|
174
|
+
const retainedRecordBytes = payloadBytes + eventBytes;
|
|
175
|
+
event = undefined;
|
|
176
|
+
eventBytes = 0;
|
|
177
|
+
dataLines = [];
|
|
178
|
+
dataLinesBytes = 0;
|
|
179
|
+
return {
|
|
180
|
+
record: includeComments ? { kind: "event", ...record } : record,
|
|
181
|
+
bytes: retainedRecordBytes,
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const acceptLine = (): { record: ServerSentEvent | SseRecord; bytes: number } | undefined => {
|
|
186
|
+
let line = lineBuffer;
|
|
187
|
+
let retainedLineBytes = lineRetainedBytes;
|
|
188
|
+
let lineOwned = true;
|
|
189
|
+
const releaseLine = (): void => {
|
|
190
|
+
if (!lineOwned) return;
|
|
191
|
+
lineOwned = false;
|
|
192
|
+
translatorBudget.releaseRetained(retainedLineBytes, scope);
|
|
193
|
+
};
|
|
194
|
+
lineBuffer = "";
|
|
195
|
+
lineRawBytes = 0;
|
|
196
|
+
lineRetainedBytes = 0;
|
|
197
|
+
try {
|
|
198
|
+
if (line.endsWith("\r")) {
|
|
199
|
+
const nextLineBytes = retainedLineBytes - 1;
|
|
200
|
+
const reservation = translatorBudget.reserveTransient(nextLineBytes, scope);
|
|
201
|
+
try {
|
|
202
|
+
const nextLine = line.slice(0, -1);
|
|
203
|
+
reservation.commitRetained();
|
|
204
|
+
translatorBudget.releaseRetained(retainedLineBytes, scope);
|
|
205
|
+
line = nextLine;
|
|
206
|
+
retainedLineBytes = nextLineBytes;
|
|
207
|
+
} catch (error) {
|
|
208
|
+
reservation.release();
|
|
209
|
+
throw error;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (line === "") {
|
|
213
|
+
releaseLine();
|
|
214
|
+
return dispatch();
|
|
215
|
+
}
|
|
216
|
+
if (line.startsWith(":")) {
|
|
217
|
+
if (!includeComments) {
|
|
218
|
+
releaseLine();
|
|
219
|
+
return undefined;
|
|
220
|
+
}
|
|
221
|
+
const commentStart = line[1] === " " ? 2 : 1;
|
|
222
|
+
const commentBytes = utf8SliceBytes(line, commentStart, line.length);
|
|
223
|
+
translatorBudget.chargeRetained(commentBytes, scope);
|
|
224
|
+
try {
|
|
225
|
+
const comment = line.slice(commentStart);
|
|
226
|
+
releaseLine();
|
|
227
|
+
return { record: { kind: "comment", comment }, bytes: commentBytes };
|
|
228
|
+
} catch (error) {
|
|
229
|
+
translatorBudget.releaseRetained(commentBytes, scope);
|
|
230
|
+
throw error;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const colon = line.indexOf(":");
|
|
235
|
+
const fieldEnd = colon < 0 ? line.length : colon;
|
|
236
|
+
const fieldBytes = utf8SliceBytes(line, 0, fieldEnd);
|
|
237
|
+
const fieldReservation = translatorBudget.reserveTransient(fieldBytes, scope);
|
|
238
|
+
let field = "";
|
|
239
|
+
try {
|
|
240
|
+
field = line.slice(0, fieldEnd);
|
|
241
|
+
fieldReservation.commitRetained();
|
|
242
|
+
} catch (error) {
|
|
243
|
+
fieldReservation.release();
|
|
244
|
+
throw error;
|
|
245
|
+
}
|
|
246
|
+
try {
|
|
247
|
+
let valueStart = colon < 0 ? line.length : colon + 1;
|
|
248
|
+
if (line[valueStart] === " ") valueStart += 1;
|
|
249
|
+
const valueBytes = utf8SliceBytes(line, valueStart, line.length);
|
|
250
|
+
|
|
251
|
+
if (field === "event") {
|
|
252
|
+
const reservation = translatorBudget.reserveTransient(valueBytes, scope);
|
|
253
|
+
try {
|
|
254
|
+
const value = line.slice(valueStart);
|
|
255
|
+
reservation.commitRetained();
|
|
256
|
+
translatorBudget.releaseRetained(eventBytes, scope);
|
|
257
|
+
event = value;
|
|
258
|
+
eventBytes = valueBytes;
|
|
259
|
+
releaseLine();
|
|
260
|
+
} catch (error) {
|
|
261
|
+
reservation.release();
|
|
262
|
+
throw error;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
else if (field === "data") {
|
|
266
|
+
const nextEventBytes = dataLinesBytes + valueBytes + dataLines.length;
|
|
267
|
+
if (nextEventBytes > TRANSLATOR_MAX_SSE_EVENT_BYTES) {
|
|
268
|
+
throw new TranslatorBudgetExceededError("live_transient", TRANSLATOR_MAX_SSE_EVENT_BYTES);
|
|
269
|
+
}
|
|
270
|
+
// The extracted value is a distinct retained allocation while the source line is still
|
|
271
|
+
// live. Admit that insertion before slicing/pushing, then release the source owner.
|
|
272
|
+
translatorBudget.chargeRetained(valueBytes, scope);
|
|
273
|
+
try {
|
|
274
|
+
const value = line.slice(valueStart);
|
|
275
|
+
dataLines.push(value);
|
|
276
|
+
dataLinesBytes += valueBytes;
|
|
277
|
+
releaseLine();
|
|
278
|
+
} catch (error) {
|
|
279
|
+
translatorBudget.releaseRetained(valueBytes, scope);
|
|
280
|
+
throw error;
|
|
281
|
+
}
|
|
282
|
+
} else {
|
|
283
|
+
releaseLine();
|
|
284
|
+
}
|
|
285
|
+
} finally {
|
|
286
|
+
translatorBudget.releaseRetained(fieldBytes, scope);
|
|
287
|
+
}
|
|
288
|
+
return undefined;
|
|
289
|
+
} catch (error) {
|
|
290
|
+
releaseLine();
|
|
291
|
+
throw error;
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const consumeDecoded = async function* (
|
|
296
|
+
decoded: string,
|
|
297
|
+
): AsyncGenerator<ServerSentEvent | SseRecord> {
|
|
298
|
+
let offset = 0;
|
|
299
|
+
while (offset < decoded.length) {
|
|
300
|
+
const newline = decoded.indexOf("\n", offset);
|
|
301
|
+
if (newline < 0) {
|
|
302
|
+
appendLine(decoded, offset, decoded.length);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
appendLine(decoded, offset, newline);
|
|
306
|
+
const accepted = acceptLine();
|
|
307
|
+
if (accepted) {
|
|
308
|
+
try {
|
|
309
|
+
yield accepted.record;
|
|
310
|
+
} finally {
|
|
311
|
+
translatorBudget.releaseRetained(accepted.bytes, scope);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
offset = newline + 1;
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
try {
|
|
319
|
+
while (true) {
|
|
320
|
+
const { done, value } = await reader.read();
|
|
321
|
+
if (value) {
|
|
322
|
+
const sourceBytes = value.byteLength;
|
|
323
|
+
const sourceReservation = translatorBudget.reserveTransient(sourceBytes, scope);
|
|
324
|
+
let sourceCommitted = false;
|
|
325
|
+
try {
|
|
326
|
+
const decoded = decoder.decode(value, { stream: !done });
|
|
327
|
+
sourceReservation.commitRetained();
|
|
328
|
+
sourceCommitted = true;
|
|
329
|
+
yield* consumeDecoded(decoded);
|
|
330
|
+
} finally {
|
|
331
|
+
if (sourceCommitted) translatorBudget.releaseRetained(sourceBytes, scope);
|
|
332
|
+
else sourceReservation.release();
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (!done) continue;
|
|
336
|
+
const finalDecoded = decoder.decode();
|
|
337
|
+
yield* consumeDecoded(finalDecoded);
|
|
338
|
+
if (lineBuffer.length > 0) {
|
|
339
|
+
const accepted = acceptLine();
|
|
340
|
+
if (accepted) {
|
|
341
|
+
try {
|
|
342
|
+
yield accepted.record;
|
|
343
|
+
} finally {
|
|
344
|
+
translatorBudget.releaseRetained(accepted.bytes, scope);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
const finalRecord = dispatch();
|
|
349
|
+
if (finalRecord) {
|
|
350
|
+
try {
|
|
351
|
+
yield finalRecord.record;
|
|
352
|
+
} finally {
|
|
353
|
+
translatorBudget.releaseRetained(finalRecord.bytes, scope);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
} finally {
|
|
359
|
+
translatorBudget.releaseRetained(lineRetainedBytes + dataLinesBytes + eventBytes, scope);
|
|
360
|
+
signal?.removeEventListener("abort", onAbort);
|
|
361
|
+
try { await reader.cancel(); } catch { /* already closed/errored */ }
|
|
362
|
+
try { reader.releaseLock(); } catch { /* already released */ }
|
|
363
|
+
}
|
|
364
|
+
}
|