@iislee/opencodex 2.11.0
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/LICENSE +21 -0
- package/README.md +225 -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 +451 -0
- package/bin/package-main.mjs +9 -0
- package/gui/dist/assets/index-DTpMHS4F.js +67 -0
- package/gui/dist/assets/index-ZNVDE3C7.css +1 -0
- package/gui/dist/favicon.png +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/antigravity.svg +1 -0
- package/gui/dist/provider-icons/claude-color.svg +1 -0
- package/gui/dist/provider-icons/claude.svg +1 -0
- package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -0
- package/gui/dist/provider-icons/copilot-color.svg +1 -0
- package/gui/dist/provider-icons/copilot.svg +1 -0
- package/gui/dist/provider-icons/cursor-color.svg +2 -0
- package/gui/dist/provider-icons/cursor.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/gemini.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-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/kiro.svg +14 -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 +1 -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 +102 -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 +1003 -0
- package/src/adapters/azure.ts +36 -0
- package/src/adapters/base.ts +72 -0
- package/src/adapters/client-fingerprint.ts +59 -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 +127 -0
- package/src/adapters/cursor/exec-policy.ts +88 -0
- package/src/adapters/cursor/framing.ts +211 -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 +1214 -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 +55 -0
- package/src/adapters/cursor/native-exec-desktop.ts +184 -0
- package/src/adapters/cursor/native-exec-fs.ts +329 -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 +576 -0
- package/src/adapters/cursor/protobuf-events.ts +563 -0
- package/src/adapters/cursor/protobuf-request.ts +714 -0
- package/src/adapters/cursor/request-builder.ts +255 -0
- package/src/adapters/cursor/thread-continuity.ts +67 -0
- package/src/adapters/cursor/tool-definitions.ts +505 -0
- package/src/adapters/cursor/transport-retry.ts +132 -0
- package/src/adapters/cursor/transport.ts +57 -0
- package/src/adapters/cursor/types.ts +52 -0
- package/src/adapters/cursor.ts +196 -0
- package/src/adapters/google-antigravity-replay.ts +303 -0
- package/src/adapters/google-antigravity-wire.ts +108 -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 +13 -0
- package/src/adapters/google-wire-compiler.ts +232 -0
- package/src/adapters/google.ts +758 -0
- package/src/adapters/identity.ts +44 -0
- package/src/adapters/image.ts +23 -0
- package/src/adapters/kiro-constants.ts +16 -0
- package/src/adapters/kiro-errors.ts +197 -0
- package/src/adapters/kiro-events.ts +179 -0
- package/src/adapters/kiro-images.ts +129 -0
- package/src/adapters/kiro-retry.ts +312 -0
- package/src/adapters/kiro-thinking.ts +96 -0
- package/src/adapters/kiro-tool-fallback.ts +36 -0
- package/src/adapters/kiro-tools.ts +215 -0
- package/src/adapters/kiro-truncation.ts +33 -0
- package/src/adapters/kiro-wire.ts +129 -0
- package/src/adapters/kiro.ts +1898 -0
- package/src/adapters/mimo-free.ts +263 -0
- package/src/adapters/openai-chat.ts +1005 -0
- package/src/adapters/openai-responses.ts +1137 -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/bridge.ts +1619 -0
- package/src/chat/inbound.ts +295 -0
- package/src/chat/outbound.ts +765 -0
- package/src/claude/agents-inject.ts +243 -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 +381 -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 +509 -0
- package/src/claude/model-info.ts +151 -0
- package/src/claude/outbound.ts +872 -0
- package/src/cli/access.ts +108 -0
- package/src/cli/account-api.ts +268 -0
- package/src/cli/account-auth.ts +223 -0
- package/src/cli/account-extended.ts +350 -0
- package/src/cli/account.ts +275 -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 +188 -0
- package/src/cli/claude.ts +286 -0
- package/src/cli/codex-shim-autorestore.ts +45 -0
- package/src/cli/combo.ts +119 -0
- package/src/cli/config-command.ts +145 -0
- package/src/cli/debug.ts +228 -0
- package/src/cli/doctor.ts +930 -0
- package/src/cli/export-command.ts +187 -0
- package/src/cli/help.ts +354 -0
- package/src/cli/index.ts +1113 -0
- package/src/cli/init.ts +224 -0
- package/src/cli/integrations.ts +142 -0
- package/src/cli/interactive-confirm.ts +133 -0
- package/src/cli/internal-dispatch.ts +20 -0
- package/src/cli/models-runtime.ts +212 -0
- package/src/cli/models.ts +336 -0
- package/src/cli/observe.ts +117 -0
- package/src/cli/opencode.ts +586 -0
- package/src/cli/pi.ts +188 -0
- package/src/cli/provider-runtime.ts +162 -0
- package/src/cli/provider.ts +463 -0
- package/src/cli/runtime-api.ts +325 -0
- package/src/cli/star-prompt.ts +155 -0
- package/src/cli/status-oauth.ts +78 -0
- package/src/cli/status.ts +321 -0
- package/src/cli/sync-cloud.ts +283 -0
- package/src/cli/system-command.ts +112 -0
- package/src/cli/tray-proxy.ts +52 -0
- package/src/cli/v2.ts +173 -0
- package/src/cli.ts +10 -0
- package/src/clients/config-export.ts +377 -0
- package/src/clients/effective-status.ts +385 -0
- package/src/clients/probes/agy.ts +55 -0
- package/src/clients/probes/cc-switch.ts +110 -0
- package/src/clients/probes/claude.ts +90 -0
- package/src/clients/probes/codex.ts +125 -0
- package/src/clients/probes/grok.ts +29 -0
- package/src/clients/probes/opencode.ts +109 -0
- package/src/clients/probes/paseo.ts +55 -0
- package/src/clients/probes/pi.ts +55 -0
- package/src/cloud/onedrive-auth.ts +666 -0
- package/src/cloud/onedrive-graph.ts +108 -0
- package/src/cloud/settings.ts +75 -0
- package/src/cloud/sync.ts +212 -0
- package/src/cloud/types.ts +56 -0
- package/src/cloud/vault.ts +89 -0
- package/src/codex/account-id.ts +34 -0
- package/src/codex/account-label.ts +34 -0
- package/src/codex/account-lifecycle.ts +55 -0
- package/src/codex/account-namespace-match.ts +63 -0
- package/src/codex/account-namespaces.ts +149 -0
- package/src/codex/account-pause.ts +20 -0
- package/src/codex/account-runtime-state.ts +31 -0
- package/src/codex/account-store.ts +517 -0
- package/src/codex/account-usability.ts +20 -0
- package/src/codex/app-server-processes.ts +756 -0
- package/src/codex/auth-api.ts +1540 -0
- package/src/codex/auth-collision.ts +107 -0
- package/src/codex/auth-context.ts +352 -0
- package/src/codex/autostart-health.ts +149 -0
- package/src/codex/catalog/aggregation.ts +378 -0
- package/src/codex/catalog/bundled.ts +251 -0
- package/src/codex/catalog/effort.ts +355 -0
- package/src/codex/catalog/metadata.ts +180 -0
- package/src/codex/catalog/parsing.ts +456 -0
- package/src/codex/catalog/provider-fetch.ts +902 -0
- package/src/codex/catalog/sync.ts +620 -0
- package/src/codex/catalog.ts +12 -0
- package/src/codex/data/upstream-models.json +830 -0
- package/src/codex/exec-invocation.ts +22 -0
- package/src/codex/features.ts +969 -0
- package/src/codex/history-migration-guardian.ts +102 -0
- package/src/codex/history-provider.ts +776 -0
- package/src/codex/home.ts +206 -0
- package/src/codex/inject.ts +799 -0
- package/src/codex/injected-marker.ts +72 -0
- package/src/codex/journal.ts +163 -0
- package/src/codex/main-account-cache.ts +32 -0
- package/src/codex/main-account.ts +40 -0
- package/src/codex/model-cache.ts +227 -0
- package/src/codex/paths.ts +65 -0
- package/src/codex/plugins-doctor.ts +242 -0
- package/src/codex/pool-rotation.ts +225 -0
- package/src/codex/project-config-warnings.ts +411 -0
- package/src/codex/quota.ts +411 -0
- package/src/codex/refresh.ts +53 -0
- package/src/codex/routing.ts +1477 -0
- package/src/codex/runtime.ts +538 -0
- package/src/codex/shim.ts +1189 -0
- package/src/codex/subagent-defaults.ts +550 -0
- package/src/codex/subagent-model-fallback.ts +469 -0
- package/src/codex/sync.ts +130 -0
- package/src/codex/warmup.ts +192 -0
- package/src/codex/websocket-registry.ts +100 -0
- package/src/combos/failover.ts +140 -0
- package/src/combos/index.ts +41 -0
- package/src/combos/request.ts +62 -0
- package/src/combos/resolve.ts +232 -0
- package/src/combos/types.ts +326 -0
- package/src/config.ts +2356 -0
- package/src/generated/jawcode-model-metadata.ts +104 -0
- package/src/github/star-state.ts +203 -0
- package/src/grok/inject.ts +545 -0
- package/src/grok/status.ts +121 -0
- package/src/grok/sync.ts +103 -0
- package/src/grok/usage-hook/report.mjs +348 -0
- package/src/grok/usage-hook.ts +278 -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 +829 -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/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 +202 -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 +71 -0
- package/src/lib/bun-stream-caps.ts +126 -0
- package/src/lib/config-ownership.ts +360 -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/open-url.ts +25 -0
- package/src/lib/pinned-http.ts +151 -0
- package/src/lib/privacy.ts +20 -0
- package/src/lib/process-control.ts +165 -0
- package/src/lib/provider-outbound.ts +170 -0
- package/src/lib/provider-url.ts +14 -0
- package/src/lib/proxy-env.ts +18 -0
- package/src/lib/redact.ts +105 -0
- package/src/lib/retry-after.ts +55 -0
- package/src/lib/service-secrets.ts +25 -0
- package/src/lib/shadow-call.ts +30 -0
- package/src/lib/sidecar-tracker.ts +52 -0
- package/src/lib/sse-decoder.ts +323 -0
- package/src/lib/state-store-registrations.ts +109 -0
- package/src/lib/state-store-sweeper.ts +184 -0
- package/src/lib/test-home-guard.ts +90 -0
- package/src/lib/token-estimate.ts +69 -0
- package/src/lib/translator-budget.ts +356 -0
- package/src/lib/upstream-retry.ts +239 -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 +514 -0
- package/src/lib/winsw.ts +375 -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/cursor.ts +211 -0
- package/src/oauth/github-copilot.ts +428 -0
- package/src/oauth/google-antigravity.ts +230 -0
- package/src/oauth/health.ts +399 -0
- package/src/oauth/index.ts +1174 -0
- package/src/oauth/key-providers.ts +108 -0
- package/src/oauth/kimi.ts +213 -0
- package/src/oauth/kiro-credentials.ts +726 -0
- package/src/oauth/kiro.ts +577 -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 +630 -0
- package/src/oauth/token-guardian.ts +303 -0
- package/src/oauth/types.ts +62 -0
- package/src/oauth/xai.ts +241 -0
- package/src/pi/extensions.ts +72 -0
- package/src/pi/home.ts +42 -0
- package/src/pi/index.ts +40 -0
- package/src/pi/models.ts +278 -0
- package/src/pi/packages.ts +219 -0
- package/src/pi/settings.ts +365 -0
- package/src/pi/status.ts +68 -0
- package/src/pi/sync.ts +75 -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 +205 -0
- package/src/providers/api-keys.ts +140 -0
- package/src/providers/base-url-choices.ts +64 -0
- package/src/providers/context-cap.ts +65 -0
- package/src/providers/derive.ts +339 -0
- package/src/providers/free-directory.ts +184 -0
- package/src/providers/github-copilot-transport.ts +56 -0
- package/src/providers/key-failover.ts +203 -0
- package/src/providers/kiro-models.ts +67 -0
- package/src/providers/label.ts +19 -0
- package/src/providers/model-discovery.ts +356 -0
- package/src/providers/openai-sidecar.ts +175 -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 +82 -0
- package/src/providers/openrouter-routing.ts +102 -0
- package/src/providers/provider-id-rewrite.ts +150 -0
- package/src/providers/quota.ts +1260 -0
- package/src/providers/registry.ts +1600 -0
- package/src/providers/slug-codec.ts +67 -0
- package/src/providers/xai-transport.ts +141 -0
- package/src/reasoning-effort.ts +135 -0
- package/src/responses/compaction.ts +117 -0
- package/src/responses/parser.ts +656 -0
- package/src/responses/reasoning-envelope.ts +52 -0
- package/src/responses/schema.ts +159 -0
- package/src/responses/spill-store.ts +394 -0
- package/src/responses/state.ts +895 -0
- package/src/responses/tool-groups.ts +19 -0
- package/src/router.ts +425 -0
- package/src/server/adapter-resolve.ts +80 -0
- package/src/server/auth-cors.ts +530 -0
- package/src/server/chat-completions.ts +368 -0
- package/src/server/claude-messages.ts +914 -0
- package/src/server/effort-policy.ts +172 -0
- package/src/server/gui-static.ts +123 -0
- package/src/server/image-retry.ts +42 -0
- package/src/server/images.ts +476 -0
- package/src/server/index.ts +1126 -0
- package/src/server/lifecycle.ts +227 -0
- package/src/server/live.ts +598 -0
- package/src/server/management/agent-settings-routes.ts +1169 -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/clients-routes.ts +63 -0
- package/src/server/management/cloud-sync-routes.ts +266 -0
- package/src/server/management/combo-routes.ts +220 -0
- package/src/server/management/config-routes.ts +422 -0
- package/src/server/management/context.ts +31 -0
- package/src/server/management/logs-usage-routes.ts +707 -0
- package/src/server/management/model-routes.ts +525 -0
- package/src/server/management/oauth-account-routes.ts +563 -0
- package/src/server/management/provider-routes.ts +556 -0
- package/src/server/management/shared.ts +277 -0
- package/src/server/management/sidebar-routes.ts +90 -0
- package/src/server/management/system-restart.ts +179 -0
- package/src/server/management/system-routes.ts +117 -0
- package/src/server/management/usage-summary-cache.ts +86 -0
- package/src/server/management-api.ts +215 -0
- package/src/server/management-auth.ts +267 -0
- package/src/server/memory-watchdog.ts +156 -0
- package/src/server/port-reclaim.ts +307 -0
- package/src/server/ports.ts +116 -0
- package/src/server/proxy-liveness.ts +201 -0
- package/src/server/relay-eager.ts +313 -0
- package/src/server/relay.ts +1049 -0
- package/src/server/request-decompress.ts +132 -0
- package/src/server/request-log-conversation.ts +168 -0
- package/src/server/request-log.ts +1046 -0
- package/src/server/responses/collaboration.ts +354 -0
- package/src/server/responses/compact.ts +384 -0
- package/src/server/responses/core.ts +2758 -0
- package/src/server/responses/encrypted-payload.ts +308 -0
- package/src/server/responses/fetch-helpers.ts +157 -0
- package/src/server/responses/passthrough-error.ts +78 -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 +224 -0
- package/src/server/responses.ts +9 -0
- package/src/server/search.ts +136 -0
- package/src/server/sse-payload-rewrite.ts +175 -0
- package/src/server/startup-action-control.ts +308 -0
- package/src/server/startup-health-cache.ts +113 -0
- package/src/server/system-env.ts +413 -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 +471 -0
- package/src/service.ts +2554 -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 +290 -0
- package/src/tray/windows.ts +730 -0
- package/src/types.ts +1237 -0
- package/src/update/badge.ts +72 -0
- package/src/update/index.ts +407 -0
- package/src/update/job.ts +1520 -0
- package/src/update/notify.ts +257 -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 +164 -0
- package/src/usage/log.ts +658 -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 +125 -0
- package/src/vision/index.ts +467 -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 +664 -0
- package/src/web-search/parse.ts +220 -0
- package/src/web-search/progress-stream.ts +342 -0
- package/src/web-search/synthetic-tool.ts +47 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CliUsageError,
|
|
3
|
+
csv,
|
|
4
|
+
printData,
|
|
5
|
+
rejectArgs,
|
|
6
|
+
runCliAction,
|
|
7
|
+
runtimeRequest,
|
|
8
|
+
summaryLines,
|
|
9
|
+
takeBooleanOption,
|
|
10
|
+
takeFlag,
|
|
11
|
+
takeIntegerOption,
|
|
12
|
+
takeOption,
|
|
13
|
+
type RuntimeApiDeps,
|
|
14
|
+
} from "./runtime-api";
|
|
15
|
+
|
|
16
|
+
const USAGE = `Usage:
|
|
17
|
+
ocx models live [--provider <name>] [--json]
|
|
18
|
+
ocx models edit <custom-id> [--model-id <id>] [--display-name <name|->]
|
|
19
|
+
[--context-window <tokens|0>] [--modalities <text,image,audio|->] [--json]
|
|
20
|
+
ocx models <enable|disable> <provider/model|native-model> [--native] [--json]
|
|
21
|
+
ocx models provider <name> <on|off> [--json]
|
|
22
|
+
ocx models selected <provider> [--set <id,id...>|--clear] [--json]
|
|
23
|
+
ocx models context <status|value <tokens>|provider <name> <on|off>|all <on|off>> [--json]
|
|
24
|
+
ocx models shadow <status|set> [model|-] [--enabled <on|off>] [--json]`;
|
|
25
|
+
|
|
26
|
+
type ModelRow = {
|
|
27
|
+
provider?: string;
|
|
28
|
+
id?: string;
|
|
29
|
+
namespaced?: string;
|
|
30
|
+
native?: boolean;
|
|
31
|
+
disabled?: boolean;
|
|
32
|
+
custom?: boolean;
|
|
33
|
+
customId?: string;
|
|
34
|
+
displayName?: string;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
async function live(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
38
|
+
const args = [...argv];
|
|
39
|
+
const wantsJson = takeFlag(args, "--json");
|
|
40
|
+
const provider = takeOption(args, "--provider");
|
|
41
|
+
rejectArgs(args, USAGE);
|
|
42
|
+
const rows = await runtimeRequest<ModelRow[]>("/api/models", {}, deps);
|
|
43
|
+
const filtered = provider ? rows.filter(row => row.provider === provider) : rows;
|
|
44
|
+
printData(filtered, wantsJson, filtered.map(row => {
|
|
45
|
+
const flags = [row.native ? "native" : "routed", row.custom ? "custom" : "", row.disabled ? "disabled" : "enabled"].filter(Boolean);
|
|
46
|
+
return `${row.namespaced ?? `${row.provider}/${row.id}`} [${flags.join(", ")}]`;
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function edit(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
51
|
+
const args = [...argv];
|
|
52
|
+
const id = args.shift()?.trim();
|
|
53
|
+
const wantsJson = takeFlag(args, "--json");
|
|
54
|
+
if (!id) throw new CliUsageError("custom model id is required", USAGE);
|
|
55
|
+
const patch: Record<string, unknown> = {};
|
|
56
|
+
const modelId = takeOption(args, "--model-id");
|
|
57
|
+
const displayName = takeOption(args, "--display-name");
|
|
58
|
+
const contextRaw = takeOption(args, "--context-window");
|
|
59
|
+
const modalitiesRaw = takeOption(args, "--modalities");
|
|
60
|
+
rejectArgs(args, USAGE);
|
|
61
|
+
if (modelId !== undefined) patch.modelId = modelId;
|
|
62
|
+
if (displayName !== undefined) patch.displayName = displayName === "-" ? "" : displayName;
|
|
63
|
+
if (contextRaw !== undefined) {
|
|
64
|
+
const value = Number(contextRaw.replace(/[_,]/g, ""));
|
|
65
|
+
if (!Number.isInteger(value) || value < 0) throw new CliUsageError("--context-window must be an integer >= 0", USAGE);
|
|
66
|
+
patch.contextWindow = value === 0 ? null : value;
|
|
67
|
+
}
|
|
68
|
+
if (modalitiesRaw !== undefined) patch.inputModalities = modalitiesRaw === "-" ? [] : csv(modalitiesRaw);
|
|
69
|
+
if (Object.keys(patch).length === 0) throw new CliUsageError("at least one edit option is required", USAGE);
|
|
70
|
+
const result = await runtimeRequest(`/api/custom-models/${encodeURIComponent(id)}`, {
|
|
71
|
+
method: "PUT",
|
|
72
|
+
body: JSON.stringify(patch),
|
|
73
|
+
}, deps);
|
|
74
|
+
printData(result, wantsJson, [`Updated custom model ${id}.`]);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function parseSelector(selector: string, forceNative: boolean): { provider: string; id: string; native: boolean } {
|
|
78
|
+
if (forceNative || !selector.includes("/")) return { provider: "openai", id: selector, native: true };
|
|
79
|
+
const slash = selector.indexOf("/");
|
|
80
|
+
const provider = selector.slice(0, slash);
|
|
81
|
+
const id = selector.slice(slash + 1);
|
|
82
|
+
if (!provider || !id) throw new CliUsageError("model selector must be provider/model or a native model id", USAGE);
|
|
83
|
+
return { provider, id, native: false };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function visibility(enabled: boolean, argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
87
|
+
const args = [...argv];
|
|
88
|
+
const selector = args.shift()?.trim();
|
|
89
|
+
const wantsJson = takeFlag(args, "--json");
|
|
90
|
+
const native = takeFlag(args, "--native");
|
|
91
|
+
if (!selector) throw new CliUsageError("model selector is required", USAGE);
|
|
92
|
+
rejectArgs(args, USAGE);
|
|
93
|
+
const target = parseSelector(selector, native);
|
|
94
|
+
const result = await runtimeRequest("/api/model-visibility", {
|
|
95
|
+
method: "PUT",
|
|
96
|
+
body: JSON.stringify({ scope: "models", provider: target.provider, enabled, targets: [{ id: target.id, native: target.native }] }),
|
|
97
|
+
}, deps);
|
|
98
|
+
printData(result, wantsJson, [`${enabled ? "Enabled" : "Disabled"} ${selector}.`]);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function providerVisibility(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
102
|
+
const args = [...argv];
|
|
103
|
+
const provider = args.shift()?.trim();
|
|
104
|
+
const state = args.shift()?.toLowerCase();
|
|
105
|
+
const wantsJson = takeFlag(args, "--json");
|
|
106
|
+
if (!provider || (state !== "on" && state !== "off")) throw new CliUsageError("provider and on|off are required", USAGE);
|
|
107
|
+
rejectArgs(args, USAGE);
|
|
108
|
+
const rows = await runtimeRequest<ModelRow[]>("/api/models", {}, deps);
|
|
109
|
+
const targets = rows.filter(row => row.provider === provider && typeof row.id === "string")
|
|
110
|
+
.map(row => ({ id: row.id!, native: row.native === true }));
|
|
111
|
+
if (targets.length === 0) throw new CliUsageError(`no models are available for provider ${provider}`);
|
|
112
|
+
const result = await runtimeRequest("/api/model-visibility", {
|
|
113
|
+
method: "PUT",
|
|
114
|
+
body: JSON.stringify({ scope: "provider", provider, enabled: state === "on", targets }),
|
|
115
|
+
}, deps);
|
|
116
|
+
printData(result, wantsJson, [`${provider}: ${state}`]);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function selected(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
120
|
+
const args = [...argv];
|
|
121
|
+
const provider = args.shift()?.trim();
|
|
122
|
+
const wantsJson = takeFlag(args, "--json");
|
|
123
|
+
const selectedModels = csv(takeOption(args, "--set"));
|
|
124
|
+
const clear = takeFlag(args, "--clear");
|
|
125
|
+
if (!provider) throw new CliUsageError("provider is required", USAGE);
|
|
126
|
+
if (selectedModels !== undefined && clear) throw new CliUsageError("--set and --clear cannot be combined", USAGE);
|
|
127
|
+
rejectArgs(args, USAGE);
|
|
128
|
+
if (selectedModels === undefined && !clear) {
|
|
129
|
+
const result = await runtimeRequest<Record<string, unknown>>("/api/selected-models", {}, deps);
|
|
130
|
+
const map = result.selected as Record<string, string[]> | undefined;
|
|
131
|
+
const available = result.available as Record<string, string[]> | undefined;
|
|
132
|
+
const view = { provider, selected: map?.[provider] ?? [], available: available?.[provider] ?? [] };
|
|
133
|
+
printData(view, wantsJson, [`${provider}: ${view.selected.join(", ") || "all models"}`]);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const models = clear ? [] : selectedModels!;
|
|
137
|
+
const result = await runtimeRequest("/api/selected-models", {
|
|
138
|
+
method: "PUT",
|
|
139
|
+
body: JSON.stringify({ provider, models }),
|
|
140
|
+
}, deps);
|
|
141
|
+
printData(result, wantsJson, [`${provider}: ${models.length ? models.join(", ") : "all models"}`]);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function context(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
145
|
+
const args = [...argv];
|
|
146
|
+
const action = (args.shift() ?? "status").toLowerCase();
|
|
147
|
+
const wantsJson = takeFlag(args, "--json");
|
|
148
|
+
if (action === "status") {
|
|
149
|
+
rejectArgs(args, USAGE);
|
|
150
|
+
const result = await runtimeRequest("/api/provider-context-caps", {}, deps);
|
|
151
|
+
printData(result, wantsJson, summaryLines(result));
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
let body: Record<string, unknown>;
|
|
155
|
+
if (action === "value") {
|
|
156
|
+
const raw = args.shift();
|
|
157
|
+
if (!raw) throw new CliUsageError("context value is required", USAGE);
|
|
158
|
+
const value = Number(raw.replace(/[_,]/g, ""));
|
|
159
|
+
if (!Number.isInteger(value) || value <= 0) throw new CliUsageError("context value must be a positive integer", USAGE);
|
|
160
|
+
body = { value };
|
|
161
|
+
} else if (action === "provider") {
|
|
162
|
+
const provider = args.shift()?.trim();
|
|
163
|
+
const state = args.shift()?.toLowerCase();
|
|
164
|
+
if (!provider || (state !== "on" && state !== "off")) throw new CliUsageError("provider and on|off are required", USAGE);
|
|
165
|
+
body = { provider, enabled: state === "on" };
|
|
166
|
+
} else if (action === "all") {
|
|
167
|
+
const state = args.shift()?.toLowerCase();
|
|
168
|
+
if (state !== "on" && state !== "off") throw new CliUsageError("all requires on|off", USAGE);
|
|
169
|
+
body = { setAll: state === "on" };
|
|
170
|
+
} else throw new CliUsageError(`unknown context action ${action}`, USAGE);
|
|
171
|
+
rejectArgs(args, USAGE);
|
|
172
|
+
const result = await runtimeRequest("/api/provider-context-caps", { method: "PUT", body: JSON.stringify(body) }, deps);
|
|
173
|
+
printData(result, wantsJson, ["Context cap settings updated."]);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function shadow(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
177
|
+
const args = [...argv];
|
|
178
|
+
const action = (args.shift() ?? "status").toLowerCase();
|
|
179
|
+
const wantsJson = takeFlag(args, "--json");
|
|
180
|
+
if (action === "status") {
|
|
181
|
+
rejectArgs(args, USAGE);
|
|
182
|
+
const result = await runtimeRequest("/api/shadow-call-settings", {}, deps);
|
|
183
|
+
printData(result, wantsJson);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (action !== "set") throw new CliUsageError(`unknown shadow action ${action}`, USAGE);
|
|
187
|
+
const modelRaw = args[0] && !args[0].startsWith("--") ? args.shift() : undefined;
|
|
188
|
+
const enabled = takeBooleanOption(args, "--enabled");
|
|
189
|
+
rejectArgs(args, USAGE);
|
|
190
|
+
const body: Record<string, unknown> = {};
|
|
191
|
+
if (modelRaw !== undefined) body.model = modelRaw === "-" ? "" : modelRaw;
|
|
192
|
+
if (enabled !== undefined) body.enabled = enabled;
|
|
193
|
+
if (Object.keys(body).length === 0) throw new CliUsageError("model and/or --enabled is required", USAGE);
|
|
194
|
+
const result = await runtimeRequest("/api/shadow-call-settings", { method: "PUT", body: JSON.stringify(body) }, deps);
|
|
195
|
+
printData(result, wantsJson, ["Shadow-call settings updated."]);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export async function handleModelsRuntimeCommand(sub: string, argv: string[], deps: RuntimeApiDeps = {}): Promise<number | null> {
|
|
199
|
+
let action: (() => Promise<void>) | undefined;
|
|
200
|
+
if (sub === "live") action = () => live(argv, deps);
|
|
201
|
+
else if (sub === "edit") action = () => edit(argv, deps);
|
|
202
|
+
else if (sub === "enable") action = () => visibility(true, argv, deps);
|
|
203
|
+
else if (sub === "disable") action = () => visibility(false, argv, deps);
|
|
204
|
+
else if (sub === "provider") action = () => providerVisibility(argv, deps);
|
|
205
|
+
else if (sub === "selected") action = () => selected(argv, deps);
|
|
206
|
+
else if (sub === "context") action = () => context(argv, deps);
|
|
207
|
+
else if (sub === "shadow") action = () => shadow(argv, deps);
|
|
208
|
+
if (!action) return null;
|
|
209
|
+
return runCliAction(action);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export const MODELS_RUNTIME_USAGE = USAGE;
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ocx models` subcommand — list configured models and manage custom models.
|
|
3
|
+
*/
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { createInterface } from "node:readline/promises";
|
|
6
|
+
import { syncModelsToCodex } from "../codex/sync";
|
|
7
|
+
import { hasOwnProvider, isValidProviderName, loadConfig, saveConfig } from "../config";
|
|
8
|
+
import { routedSlug } from "../providers/slug-codec";
|
|
9
|
+
import { findLiveProxy } from "../server/proxy-liveness";
|
|
10
|
+
import type { OcxConfig, OcxCustomModel } from "../types";
|
|
11
|
+
|
|
12
|
+
const ADD_USAGE = "Usage: ocx models add <provider> <modelId> [--display-name <name>] [--context-window <tokens>] [--modalities text,image,audio]";
|
|
13
|
+
const REMOVE_USAGE = "Usage: ocx models remove <customId|provider/modelId> [--yes]";
|
|
14
|
+
const LIST_CUSTOM_USAGE = "Usage: ocx models list-custom [--json]";
|
|
15
|
+
const ALLOWED_MODALITIES = new Set(["text", "image", "audio"]);
|
|
16
|
+
|
|
17
|
+
interface ModelEntry {
|
|
18
|
+
provider: string;
|
|
19
|
+
model: string;
|
|
20
|
+
isDefault: boolean;
|
|
21
|
+
contextWindow: number | null;
|
|
22
|
+
inputModalities: string[] | null;
|
|
23
|
+
reasoningEfforts: string[] | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function collectModels(config: OcxConfig, providerFilter?: string): ModelEntry[] {
|
|
27
|
+
const entries: ModelEntry[] = [];
|
|
28
|
+
const providers = providerFilter
|
|
29
|
+
? { [providerFilter]: config.providers[providerFilter] }
|
|
30
|
+
: config.providers;
|
|
31
|
+
|
|
32
|
+
for (const [provName, prov] of Object.entries(providers)) {
|
|
33
|
+
if (!prov) continue;
|
|
34
|
+
const seen = new Set<string>();
|
|
35
|
+
const contextWindows = prov.modelContextWindows ?? {};
|
|
36
|
+
const inputModalities = prov.modelInputModalities ?? {};
|
|
37
|
+
const reasoningEfforts = prov.modelReasoningEfforts ?? {};
|
|
38
|
+
const globalContext = prov.contextWindow ?? null;
|
|
39
|
+
|
|
40
|
+
const addModel = (model: string, isDefault: boolean) => {
|
|
41
|
+
if (seen.has(model)) return;
|
|
42
|
+
seen.add(model);
|
|
43
|
+
|
|
44
|
+
const noVision = prov.noVisionModels?.includes(model);
|
|
45
|
+
const modalities = inputModalities[model] ?? (noVision ? ["text"] : null);
|
|
46
|
+
const efforts = reasoningEfforts[model] ?? prov.reasoningEfforts ?? null;
|
|
47
|
+
|
|
48
|
+
entries.push({
|
|
49
|
+
provider: provName,
|
|
50
|
+
model,
|
|
51
|
+
isDefault,
|
|
52
|
+
contextWindow: contextWindows[model] ?? globalContext,
|
|
53
|
+
inputModalities: modalities,
|
|
54
|
+
reasoningEfforts: efforts,
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// defaultModel first
|
|
59
|
+
if (prov.defaultModel) addModel(prov.defaultModel, true);
|
|
60
|
+
|
|
61
|
+
// models array
|
|
62
|
+
if (prov.models) {
|
|
63
|
+
for (const m of prov.models) addModel(m, m === prov.defaultModel);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return entries;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function consumeFlag(args: string[], flag: string): boolean {
|
|
71
|
+
const idx = args.indexOf(flag);
|
|
72
|
+
if (idx === -1) return false;
|
|
73
|
+
args.splice(idx, 1);
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function consumeFlagValue(args: string[], flag: string): string | undefined {
|
|
78
|
+
const idx = args.indexOf(flag);
|
|
79
|
+
if (idx === -1 || idx + 1 >= args.length) return undefined;
|
|
80
|
+
const value = args[idx + 1];
|
|
81
|
+
args.splice(idx, 2);
|
|
82
|
+
return value;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function fail(message: string, usage?: string): never {
|
|
86
|
+
console.error(`Error: ${message}`);
|
|
87
|
+
if (usage) console.error(usage);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function rejectUnexpectedArgs(args: string[], usage: string): void {
|
|
92
|
+
if (args.length === 0) return;
|
|
93
|
+
const unknown = args.filter(arg => arg.startsWith("-"));
|
|
94
|
+
fail(
|
|
95
|
+
unknown.length > 0
|
|
96
|
+
? `Unknown flag(s): ${unknown.join(", ")}`
|
|
97
|
+
: `Unexpected argument(s): ${args.join(", ")}`,
|
|
98
|
+
usage,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function syncCustomModelsIfLive(): Promise<void> {
|
|
103
|
+
const live = await findLiveProxy();
|
|
104
|
+
if (!live) return;
|
|
105
|
+
await syncModelsToCodex(live.port).catch(error => {
|
|
106
|
+
console.error(`Warning: custom model saved, but catalog sync failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function handleCustomAdd(args: string[]): Promise<void> {
|
|
111
|
+
const rest = [...args];
|
|
112
|
+
const provider = rest.shift()?.trim() ?? "";
|
|
113
|
+
const modelId = rest.shift()?.trim() ?? "";
|
|
114
|
+
const displayNameValue = consumeFlagValue(rest, "--display-name");
|
|
115
|
+
const contextWindowValue = consumeFlagValue(rest, "--context-window");
|
|
116
|
+
const modalitiesValue = consumeFlagValue(rest, "--modalities");
|
|
117
|
+
rejectUnexpectedArgs(rest, ADD_USAGE);
|
|
118
|
+
|
|
119
|
+
if (!provider || !modelId) fail("provider and modelId are required", ADD_USAGE);
|
|
120
|
+
if (!isValidProviderName(provider)) fail(`invalid provider name "${provider}"`);
|
|
121
|
+
if (modelId.includes("/")) fail("modelId must not contain /");
|
|
122
|
+
|
|
123
|
+
const config = loadConfig();
|
|
124
|
+
if (!hasOwnProvider(config.providers, provider)) {
|
|
125
|
+
fail(`provider "${provider}" is not configured. See: ocx provider list`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const displayName = displayNameValue?.trim() || undefined;
|
|
129
|
+
if (displayName?.includes("/")) fail("displayName must not contain /");
|
|
130
|
+
|
|
131
|
+
let contextWindow: number | undefined;
|
|
132
|
+
if (contextWindowValue !== undefined) {
|
|
133
|
+
contextWindow = Number(contextWindowValue);
|
|
134
|
+
if (!Number.isInteger(contextWindow) || contextWindow <= 0) {
|
|
135
|
+
fail("context window must be a positive integer");
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let inputModalities: string[] | undefined;
|
|
140
|
+
if (modalitiesValue !== undefined) {
|
|
141
|
+
inputModalities = modalitiesValue.split(",").map(value => value.trim());
|
|
142
|
+
const invalid = inputModalities.filter(value => !ALLOWED_MODALITIES.has(value));
|
|
143
|
+
if (inputModalities.length === 0 || invalid.length > 0) {
|
|
144
|
+
fail("modalities must be comma-separated values from text|image|audio");
|
|
145
|
+
}
|
|
146
|
+
inputModalities = [...new Set(inputModalities)];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const existing = config.customModels ?? [];
|
|
150
|
+
const slug = routedSlug(provider, modelId);
|
|
151
|
+
if (existing.some(model => routedSlug(model.provider, model.modelId) === slug)) {
|
|
152
|
+
fail(`custom model "${slug}" already exists`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const entry: OcxCustomModel = {
|
|
156
|
+
id: randomUUID(),
|
|
157
|
+
provider,
|
|
158
|
+
modelId,
|
|
159
|
+
...(displayName ? { displayName } : {}),
|
|
160
|
+
...(contextWindow ? { contextWindow } : {}),
|
|
161
|
+
...(inputModalities ? { inputModalities } : {}),
|
|
162
|
+
addedAt: new Date().toISOString(),
|
|
163
|
+
};
|
|
164
|
+
config.customModels = [...existing, entry];
|
|
165
|
+
saveConfig(config);
|
|
166
|
+
await syncCustomModelsIfLive();
|
|
167
|
+
console.log(`Added custom model ${slug} (${entry.id}).`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function confirmCustomRemoval(model: OcxCustomModel): Promise<boolean> {
|
|
171
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
172
|
+
fail("remove requires --yes in non-interactive mode");
|
|
173
|
+
}
|
|
174
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
175
|
+
try {
|
|
176
|
+
const answer = (await rl.question(`Remove custom model ${routedSlug(model.provider, model.modelId)}? [y/N] `)).trim().toLowerCase();
|
|
177
|
+
return answer === "y" || answer === "yes";
|
|
178
|
+
} finally {
|
|
179
|
+
rl.close();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function handleCustomRemove(args: string[]): Promise<void> {
|
|
184
|
+
const rest = [...args];
|
|
185
|
+
const confirmed = consumeFlag(rest, "--yes");
|
|
186
|
+
const target = rest.shift()?.trim() ?? "";
|
|
187
|
+
rejectUnexpectedArgs(rest, REMOVE_USAGE);
|
|
188
|
+
if (!target) fail("custom model id or provider/modelId is required", REMOVE_USAGE);
|
|
189
|
+
|
|
190
|
+
const config = loadConfig();
|
|
191
|
+
const existing = config.customModels ?? [];
|
|
192
|
+
const index = target.includes("/")
|
|
193
|
+
? existing.findIndex(model => routedSlug(model.provider, model.modelId) === target)
|
|
194
|
+
: existing.findIndex(model => model.id === target);
|
|
195
|
+
if (index === -1) fail(`custom model "${target}" not found`);
|
|
196
|
+
|
|
197
|
+
const model = existing[index];
|
|
198
|
+
if (!confirmed && !(await confirmCustomRemoval(model))) {
|
|
199
|
+
console.log("Cancelled.");
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const next = existing.filter((_, modelIndex) => modelIndex !== index);
|
|
204
|
+
config.customModels = next.length > 0 ? next : undefined;
|
|
205
|
+
saveConfig(config);
|
|
206
|
+
await syncCustomModelsIfLive();
|
|
207
|
+
console.log(`Removed custom model ${routedSlug(model.provider, model.modelId)}.`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function customModelCells(model: OcxCustomModel): string[] {
|
|
211
|
+
return [
|
|
212
|
+
model.id.slice(0, 8),
|
|
213
|
+
model.modelId,
|
|
214
|
+
model.displayName ?? "-",
|
|
215
|
+
model.contextWindow ? `${Math.round(model.contextWindow / 1000)}k` : "-",
|
|
216
|
+
model.inputModalities?.join(",") ?? "-",
|
|
217
|
+
];
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function printCustomModelGroup(provider: string, models: OcxCustomModel[]): void {
|
|
221
|
+
const rows = models.map(customModelCells);
|
|
222
|
+
const headers = ["ID", "MODEL", "DISPLAY NAME", "CONTEXT", "MODALITIES"];
|
|
223
|
+
const widths = headers.map((header, column) => Math.max(header.length, ...rows.map(row => row[column].length)));
|
|
224
|
+
const line = (cells: string[]) => cells.map((cell, column) => cell.padEnd(widths[column])).join(" ");
|
|
225
|
+
console.log(`${provider}:`);
|
|
226
|
+
console.log(` ${line(headers)}`);
|
|
227
|
+
for (const row of rows) console.log(` ${line(row)}`);
|
|
228
|
+
console.log();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function handleCustomList(args: string[]): void {
|
|
232
|
+
const rest = [...args];
|
|
233
|
+
const wantsJson = consumeFlag(rest, "--json");
|
|
234
|
+
rejectUnexpectedArgs(rest, LIST_CUSTOM_USAGE);
|
|
235
|
+
const models = loadConfig().customModels ?? [];
|
|
236
|
+
if (wantsJson) {
|
|
237
|
+
console.log(JSON.stringify(models, null, 2));
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
if (models.length === 0) {
|
|
241
|
+
console.log("No custom models registered.");
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const byProvider = new Map<string, OcxCustomModel[]>();
|
|
245
|
+
for (const model of models) {
|
|
246
|
+
const group = byProvider.get(model.provider) ?? [];
|
|
247
|
+
group.push(model);
|
|
248
|
+
byProvider.set(model.provider, group);
|
|
249
|
+
}
|
|
250
|
+
for (const [provider, providerModels] of byProvider) printCustomModelGroup(provider, providerModels);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function handleConfiguredModels(args: string[]): void {
|
|
254
|
+
const restArgs = [...args];
|
|
255
|
+
const wantsJson = consumeFlag(restArgs, "--json");
|
|
256
|
+
const providerFilter = consumeFlagValue(restArgs, "--provider");
|
|
257
|
+
|
|
258
|
+
if (restArgs.length > 0) {
|
|
259
|
+
const unknown = restArgs.filter(a => a.startsWith("-"));
|
|
260
|
+
if (unknown.length > 0) {
|
|
261
|
+
console.error(`Unknown flag(s): ${unknown.join(", ")}`);
|
|
262
|
+
} else {
|
|
263
|
+
console.error(`Unexpected argument(s): ${restArgs.join(", ")}`);
|
|
264
|
+
}
|
|
265
|
+
console.error("Usage: ocx models [--provider <name>] [--json]");
|
|
266
|
+
process.exit(1);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const config = loadConfig();
|
|
270
|
+
|
|
271
|
+
if (providerFilter && !hasOwnProvider(config.providers, providerFilter)) {
|
|
272
|
+
console.error(`Provider "${providerFilter}" is not configured. See: ocx provider list`);
|
|
273
|
+
process.exit(1);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const models = collectModels(config, providerFilter ?? undefined);
|
|
277
|
+
|
|
278
|
+
if (wantsJson) {
|
|
279
|
+
console.log(JSON.stringify({
|
|
280
|
+
models,
|
|
281
|
+
note: "Static config models only. Providers with liveModels=true may have additional models at runtime.",
|
|
282
|
+
}, null, 2));
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (models.length === 0) {
|
|
287
|
+
console.log("No models found in configured providers.");
|
|
288
|
+
if (!providerFilter) console.log("Providers may discover models dynamically at runtime (liveModels).");
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Group by provider
|
|
293
|
+
const byProvider = new Map<string, ModelEntry[]>();
|
|
294
|
+
for (const entry of models) {
|
|
295
|
+
const list = byProvider.get(entry.provider) ?? [];
|
|
296
|
+
list.push(entry);
|
|
297
|
+
byProvider.set(entry.provider, list);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
for (const [provName, provModels] of byProvider) {
|
|
301
|
+
const isDefaultProv = provName === config.defaultProvider ? " (default provider)" : "";
|
|
302
|
+
console.log(`${provName}${isDefaultProv}:`);
|
|
303
|
+
for (const m of provModels) {
|
|
304
|
+
const marker = m.isDefault ? " *" : "";
|
|
305
|
+
const ctx = m.contextWindow ? ` (${Math.round(m.contextWindow / 1000)}k)` : "";
|
|
306
|
+
console.log(` ${m.model}${marker}${ctx}`);
|
|
307
|
+
}
|
|
308
|
+
console.log();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
console.log("* = default model for provider");
|
|
312
|
+
console.log("Note: providers with liveModels may have additional models at runtime.");
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export async function handleModels(args: string[]): Promise<void> {
|
|
316
|
+
const [subcommand, ...rest] = args;
|
|
317
|
+
if (subcommand === "add") {
|
|
318
|
+
await handleCustomAdd(rest);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
if (subcommand === "remove") {
|
|
322
|
+
await handleCustomRemove(rest);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
if (subcommand === "list-custom") {
|
|
326
|
+
handleCustomList(rest);
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
if (["live", "edit", "enable", "disable", "provider", "selected", "context", "shadow"].includes(subcommand ?? "")) {
|
|
330
|
+
const { handleModelsRuntimeCommand } = await import("./models-runtime");
|
|
331
|
+
const code = await handleModelsRuntimeCommand(subcommand!, rest);
|
|
332
|
+
if (code !== null) process.exitCode = code;
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
handleConfiguredModels(subcommand === "list" ? rest : args);
|
|
336
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CliUsageError,
|
|
3
|
+
printData,
|
|
4
|
+
rejectArgs,
|
|
5
|
+
runCliAction,
|
|
6
|
+
runtimeRequest,
|
|
7
|
+
summaryLines,
|
|
8
|
+
takeFlag,
|
|
9
|
+
takeIntegerOption,
|
|
10
|
+
takeOption,
|
|
11
|
+
type RuntimeApiDeps,
|
|
12
|
+
} from "./runtime-api";
|
|
13
|
+
|
|
14
|
+
const USAGE = `Usage:
|
|
15
|
+
ocx observe logs [--provider <name>] [--model <id>] [--status <code>]
|
|
16
|
+
[--limit <n>] [--follow] [--json|--jsonl]
|
|
17
|
+
ocx observe usage [--range <7d|30d|all>] [--surface <all|codex|claude|grok>] [--json]
|
|
18
|
+
ocx observe storage [--json]
|
|
19
|
+
ocx observe memory [--json]
|
|
20
|
+
ocx observe debug [--json]
|
|
21
|
+
ocx observe claude-inbound [--limit <n>] [--json]
|
|
22
|
+
ocx observe injection [--limit <n>] [--json]`;
|
|
23
|
+
|
|
24
|
+
type LogEntry = Record<string, unknown> & { id?: string | number; timestamp?: string; provider?: string; model?: string; status?: number };
|
|
25
|
+
|
|
26
|
+
function query(params: Record<string, string | number | undefined>): string {
|
|
27
|
+
const search = new URLSearchParams();
|
|
28
|
+
for (const [key, value] of Object.entries(params)) if (value !== undefined) search.set(key, String(value));
|
|
29
|
+
const encoded = search.toString();
|
|
30
|
+
return encoded ? `?${encoded}` : "";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function logRows(data: unknown): LogEntry[] {
|
|
34
|
+
if (Array.isArray(data)) return data as LogEntry[];
|
|
35
|
+
if (data && typeof data === "object") {
|
|
36
|
+
const record = data as Record<string, unknown>;
|
|
37
|
+
for (const key of ["logs", "entries", "requests"]) if (Array.isArray(record[key])) return record[key] as LogEntry[];
|
|
38
|
+
}
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function formatLog(row: LogEntry): string {
|
|
43
|
+
const time = String(row.timestamp ?? row.createdAt ?? "");
|
|
44
|
+
const route = [row.provider, row.model].filter(Boolean).join("/");
|
|
45
|
+
const status = row.status ?? row.statusCode ?? "?";
|
|
46
|
+
const duration = row.durationMs !== undefined ? `${String(row.durationMs)}ms` : "";
|
|
47
|
+
return [time, String(status), route, duration].filter(Boolean).join(" ");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function logs(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
51
|
+
const args = [...argv];
|
|
52
|
+
const wantsJson = takeFlag(args, "--json");
|
|
53
|
+
const wantsJsonl = takeFlag(args, "--jsonl");
|
|
54
|
+
const follow = takeFlag(args, "--follow") || takeFlag(args, "-f");
|
|
55
|
+
const provider = takeOption(args, "--provider");
|
|
56
|
+
const model = takeOption(args, "--model");
|
|
57
|
+
const status = takeOption(args, "--status");
|
|
58
|
+
const limit = takeIntegerOption(args, "--limit", { min: 1 }) ?? 200;
|
|
59
|
+
rejectArgs(args, USAGE);
|
|
60
|
+
if (wantsJson && wantsJsonl) throw new CliUsageError("--json and --jsonl cannot be combined", USAGE);
|
|
61
|
+
if (follow && wantsJson) throw new CliUsageError("--follow uses --jsonl, not --json", USAGE);
|
|
62
|
+
let seen = new Set<string>();
|
|
63
|
+
do {
|
|
64
|
+
const data = await runtimeRequest(`/api/logs${query({ provider, model, status, limit })}`, {}, deps);
|
|
65
|
+
const rows = logRows(data);
|
|
66
|
+
if (!follow && wantsJson) printData(data, true);
|
|
67
|
+
else {
|
|
68
|
+
for (const row of rows) {
|
|
69
|
+
const key = String(row.id ?? `${row.timestamp}:${row.provider}:${row.model}:${row.status}`);
|
|
70
|
+
if (follow && seen.has(key)) continue;
|
|
71
|
+
if (wantsJsonl) console.log(JSON.stringify(row));
|
|
72
|
+
else console.log(formatLog(row));
|
|
73
|
+
seen.add(key);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (!follow) return;
|
|
77
|
+
if (seen.size > 5_000) seen = new Set([...seen].slice(-2_500));
|
|
78
|
+
await Bun.sleep(1_000);
|
|
79
|
+
} while (true);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function usage(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
83
|
+
const args = [...argv];
|
|
84
|
+
const wantsJson = takeFlag(args, "--json");
|
|
85
|
+
const range = takeOption(args, "--range") ?? "30d";
|
|
86
|
+
const surface = takeOption(args, "--surface") ?? "all";
|
|
87
|
+
if (!["7d", "30d", "all"].includes(range)) throw new CliUsageError("--range must be 7d, 30d, or all", USAGE);
|
|
88
|
+
if (!["all", "codex", "claude", "grok"].includes(surface)) throw new CliUsageError("--surface must be all, codex, claude, or grok", USAGE);
|
|
89
|
+
rejectArgs(args, USAGE);
|
|
90
|
+
const result = await runtimeRequest(`/api/usage${query({ range, surface })}`, {}, deps);
|
|
91
|
+
printData(result, wantsJson, summaryLines(result));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function simple(path: string, argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
95
|
+
const args = [...argv];
|
|
96
|
+
const wantsJson = takeFlag(args, "--json");
|
|
97
|
+
const limit = takeIntegerOption(args, "--limit", { min: 1 });
|
|
98
|
+
rejectArgs(args, USAGE);
|
|
99
|
+
const result = await runtimeRequest(`${path}${query({ limit })}`, {}, deps);
|
|
100
|
+
printData(result, wantsJson, summaryLines(result));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function handleObserveCommand(argv: string[], deps: RuntimeApiDeps = {}): Promise<number> {
|
|
104
|
+
return runCliAction(async () => {
|
|
105
|
+
const [sub = "logs", ...rest] = argv;
|
|
106
|
+
if (sub === "logs") await logs(rest, deps);
|
|
107
|
+
else if (sub === "usage") await usage(rest, deps);
|
|
108
|
+
else if (sub === "storage") await simple("/api/storage", rest, deps);
|
|
109
|
+
else if (sub === "memory") await simple("/api/system/memory", rest, deps);
|
|
110
|
+
else if (sub === "debug") await simple("/api/debug", rest, deps);
|
|
111
|
+
else if (sub === "claude-inbound") await simple("/api/claude/inbound-debug", rest, deps);
|
|
112
|
+
else if (sub === "injection") await simple("/api/debug/injection-logs", rest, deps);
|
|
113
|
+
else throw new CliUsageError(`unknown observe command ${sub}`, USAGE);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export const OBSERVE_USAGE = USAGE;
|