@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,133 @@
|
|
|
1
|
+
import type { OcxConfig, OcxParsedRequest, OcxProviderConfig } from "../types";
|
|
2
|
+
import type { ImageBridgePlan, VideoBridgePlan } from "./types";
|
|
3
|
+
import { resolveEnvValue } from "../config";
|
|
4
|
+
import { getProviderRegistryEntry } from "../providers/registry";
|
|
5
|
+
import { IMAGE_GEN_TOOL_NAME, VIDEO_GEN_TOOL_NAME, isVideoGenName } from "./synthetic-tool";
|
|
6
|
+
|
|
7
|
+
const DEFAULT_MODEL = "grok-imagine-image-quality";
|
|
8
|
+
/** Absolute ceiling for `images.timeoutMs` (matches /v1/images relay budget). */
|
|
9
|
+
export const MAX_IMAGE_TIMEOUT_MS = 300_000;
|
|
10
|
+
|
|
11
|
+
function clampImageTimeoutMs(value: unknown): number | undefined {
|
|
12
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return undefined;
|
|
13
|
+
return Math.max(1, Math.min(MAX_IMAGE_TIMEOUT_MS, Math.floor(value)));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function findXaiProvider(config: OcxConfig): { name: string; provider: OcxProviderConfig } | undefined {
|
|
17
|
+
// Primary: well-known name "xai"
|
|
18
|
+
const xai = config.providers["xai"];
|
|
19
|
+
if (xai && xai.disabled !== true) return { name: "xai", provider: xai };
|
|
20
|
+
// Fallback: hostname match for custom-named xAI configs
|
|
21
|
+
for (const [name, p] of Object.entries(config.providers)) {
|
|
22
|
+
if (p.disabled) continue;
|
|
23
|
+
try {
|
|
24
|
+
const host = new URL(p.baseUrl).hostname;
|
|
25
|
+
if (host === "api.x.ai" || host === "cli-chat-proxy.grok.com") return { name, provider: p };
|
|
26
|
+
} catch { /* invalid baseUrl */ }
|
|
27
|
+
}
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Image Bridge fulfillment talks to the public Images API on api.x.ai with a Bearer API key.
|
|
33
|
+
* OAuth / Grok CLI proxy transport is not used here (that path is chat-oriented and not a
|
|
34
|
+
* supported Images transport), so oauth-only configs deliberately do not arm the bridge.
|
|
35
|
+
*/
|
|
36
|
+
export function resolveXaiImageApiKey(provider: OcxProviderConfig): string | undefined {
|
|
37
|
+
if (provider.authMode === "oauth") return undefined;
|
|
38
|
+
const apiKey = resolveEnvValue(provider.apiKey)?.trim();
|
|
39
|
+
return apiKey || undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function planImageBridge(
|
|
43
|
+
config: OcxConfig,
|
|
44
|
+
parsed: OcxParsedRequest,
|
|
45
|
+
routedProvider: OcxProviderConfig,
|
|
46
|
+
): Promise<ImageBridgePlan | undefined> {
|
|
47
|
+
if (config.images?.bridgeEnabled !== true) return undefined;
|
|
48
|
+
if (!parsed._imageGeneration) return undefined;
|
|
49
|
+
// Don't intercept for OpenAI native passthrough
|
|
50
|
+
const host = (() => { try { return new URL(routedProvider.baseUrl).hostname; } catch { return ""; } })();
|
|
51
|
+
if (host === "api.openai.com") return undefined;
|
|
52
|
+
const found = findXaiProvider(config);
|
|
53
|
+
if (!found) return undefined;
|
|
54
|
+
const token = resolveXaiImageApiKey(found.provider);
|
|
55
|
+
if (!token) return undefined;
|
|
56
|
+
// Pin the baseUrl to the registry entry, ignoring any config-level baseUrl override.
|
|
57
|
+
const registryEntry = getProviderRegistryEntry("xai");
|
|
58
|
+
const pinnedBaseUrl = (registryEntry?.baseUrl ?? "https://api.x.ai/v1").replace(/\/+$/, "");
|
|
59
|
+
// The synthetic tool injected into the conversation is named IMAGE_GEN_TOOL_NAME,
|
|
60
|
+
// which is what the model will actually call. Merge it with any original hosted tool names.
|
|
61
|
+
const toolNames = new Set(parsed._imageGeneration.toolNames);
|
|
62
|
+
toolNames.add(IMAGE_GEN_TOOL_NAME);
|
|
63
|
+
const original = parsed._imageGeneration.originalTool;
|
|
64
|
+
const hostedSize = typeof original?.size === "string" ? original.size : undefined;
|
|
65
|
+
const hostedQuality = typeof original?.quality === "string" ? original.quality : undefined;
|
|
66
|
+
const timeoutMs = clampImageTimeoutMs(config.images?.timeoutMs);
|
|
67
|
+
const keepRaw = config.images?.artifactsKeepCount;
|
|
68
|
+
const artifactsKeepCount =
|
|
69
|
+
typeof keepRaw === "number" && Number.isFinite(keepRaw) ? Math.floor(keepRaw) : undefined;
|
|
70
|
+
return {
|
|
71
|
+
provider: found.provider,
|
|
72
|
+
auth: { baseUrl: pinnedBaseUrl, token },
|
|
73
|
+
model: config.images?.bridgeModel ?? DEFAULT_MODEL,
|
|
74
|
+
toolNames,
|
|
75
|
+
...(hostedSize ? { defaultSize: hostedSize } : {}),
|
|
76
|
+
...(hostedQuality ? { defaultQuality: hostedQuality } : {}),
|
|
77
|
+
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
78
|
+
...(artifactsKeepCount !== undefined ? { artifactsKeepCount } : {}),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const DEFAULT_VIDEO_MODEL = "grok-imagine-video";
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Decide whether the video bridge should activate for this request. Unlike images, video
|
|
86
|
+
* generation has no hosted OpenAI tool type — the synthetic `video_gen` tool is unconditionally
|
|
87
|
+
* injected when `videoBridgeEnabled` is true. The bridge activates only when:
|
|
88
|
+
* 1. videoBridgeEnabled is explicitly true (opt-in)
|
|
89
|
+
* 2. the routed provider is NOT api.openai.com (native passthrough)
|
|
90
|
+
* 3. an xAI provider with a valid API key is available
|
|
91
|
+
*/
|
|
92
|
+
export async function planVideoBridge(
|
|
93
|
+
config: OcxConfig,
|
|
94
|
+
parsed: OcxParsedRequest,
|
|
95
|
+
routedProvider: OcxProviderConfig,
|
|
96
|
+
): Promise<VideoBridgePlan | undefined> {
|
|
97
|
+
if (config.images?.videoBridgeEnabled !== true) return undefined;
|
|
98
|
+
// Don't intercept for OpenAI native passthrough
|
|
99
|
+
const host = (() => { try { return new URL(routedProvider.baseUrl).hostname; } catch { return ""; } })();
|
|
100
|
+
if (host === "api.openai.com") return undefined;
|
|
101
|
+
const found = findXaiProvider(config);
|
|
102
|
+
if (!found) return undefined;
|
|
103
|
+
const token = resolveXaiImageApiKey(found.provider);
|
|
104
|
+
if (!token) return undefined;
|
|
105
|
+
// Pin the baseUrl to the registry entry, ignoring any config-level baseUrl override.
|
|
106
|
+
const registryEntry = getProviderRegistryEntry("xai");
|
|
107
|
+
const pinnedBaseUrl = (registryEntry?.baseUrl ?? "https://api.x.ai/v1").replace(/\/+$/, "");
|
|
108
|
+
const toolNames = new Set<string>();
|
|
109
|
+
toolNames.add(VIDEO_GEN_TOOL_NAME);
|
|
110
|
+
// Collect any existing function tools whose name matches a video_gen alias
|
|
111
|
+
// so the loop can intercept and replace them (image-bridge parity).
|
|
112
|
+
for (const t of parsed.context?.tools ?? []) {
|
|
113
|
+
// Skip namespaced tools — a namespaced MCP video_gen must not be intercepted.
|
|
114
|
+
if (t.namespace) continue;
|
|
115
|
+
const fnName = typeof t.name === "string" ? t.name
|
|
116
|
+
: (t as unknown as { function?: { name?: string } }).function?.name;
|
|
117
|
+
if (typeof fnName === "string" && isVideoGenName(fnName)) {
|
|
118
|
+
toolNames.add(fnName);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const timeoutMs = clampImageTimeoutMs(config.images?.videoTimeoutMs);
|
|
122
|
+
const keepRaw = config.images?.artifactsKeepCount;
|
|
123
|
+
const artifactsKeepCount =
|
|
124
|
+
typeof keepRaw === "number" && Number.isFinite(keepRaw) ? Math.floor(keepRaw) : undefined;
|
|
125
|
+
return {
|
|
126
|
+
provider: found.provider,
|
|
127
|
+
auth: { baseUrl: pinnedBaseUrl, token },
|
|
128
|
+
model: config.images?.videoBridgeModel ?? DEFAULT_VIDEO_MODEL,
|
|
129
|
+
toolNames,
|
|
130
|
+
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
131
|
+
...(artifactsKeepCount !== undefined ? { artifactsKeepCount } : {}),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import type { OcxTool } from "../types";
|
|
2
|
+
|
|
3
|
+
/** The function name the chat model sees + the name the loop intercepts. */
|
|
4
|
+
export const IMAGE_GEN_TOOL_NAME = "image_gen";
|
|
5
|
+
|
|
6
|
+
/** Client function aliases collected only when a hosted image tool is also present. */
|
|
7
|
+
const IMAGE_GEN_ALIASES = new Set([
|
|
8
|
+
"imagegen",
|
|
9
|
+
"generate_image",
|
|
10
|
+
"generateimage",
|
|
11
|
+
IMAGE_GEN_TOOL_NAME,
|
|
12
|
+
"image_generation",
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
export function isImageGenName(name: string): boolean {
|
|
16
|
+
const lower = name.toLowerCase();
|
|
17
|
+
return lower === IMAGE_GEN_TOOL_NAME || lower === "image_generation";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function isImageGenClientAlias(name: string): boolean {
|
|
21
|
+
return IMAGE_GEN_ALIASES.has(name.toLowerCase());
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Scan a Responses request's `tools[]` for hosted image-generation entries
|
|
26
|
+
* (`{type:"image_generation"}` or `{type:"image_gen"}`). Ordinary `type:"function"`
|
|
27
|
+
* tools never activate the bridge by name alone — they are only collected as conflicting
|
|
28
|
+
* aliases to strip/replace when a hosted entry is present.
|
|
29
|
+
*/
|
|
30
|
+
export function extractHostedImageGeneration(
|
|
31
|
+
tools: unknown[] | undefined,
|
|
32
|
+
): { toolNames: Set<string>; originalTool?: Record<string, unknown> } | undefined {
|
|
33
|
+
if (!Array.isArray(tools)) return undefined;
|
|
34
|
+
let hasHostedEntry = false;
|
|
35
|
+
let originalTool: Record<string, unknown> | undefined;
|
|
36
|
+
for (const t of tools) {
|
|
37
|
+
if (!t || typeof t !== "object") continue;
|
|
38
|
+
const obj = t as Record<string, unknown>;
|
|
39
|
+
if (obj.type === "image_generation" || obj.type === "image_gen") {
|
|
40
|
+
hasHostedEntry = true;
|
|
41
|
+
if (!originalTool) originalTool = obj;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (!hasHostedEntry) return undefined;
|
|
45
|
+
|
|
46
|
+
const toolNames = new Set<string>();
|
|
47
|
+
for (const t of tools) {
|
|
48
|
+
if (!t || typeof t !== "object") continue;
|
|
49
|
+
const obj = t as Record<string, unknown>;
|
|
50
|
+
if (obj.type === "image_generation" || obj.type === "image_gen") {
|
|
51
|
+
const name = typeof obj.name === "string" ? obj.name : (obj.type as string);
|
|
52
|
+
toolNames.add(name);
|
|
53
|
+
} else if (obj.type === "function") {
|
|
54
|
+
// Responses API function tools have a flat shape: {type:"function", name:"...", parameters:{...}}.
|
|
55
|
+
// Also handle the nested Chat Completions shape {type:"function", function:{name:"..."}} for safety.
|
|
56
|
+
const fnName =
|
|
57
|
+
typeof obj.name === "string" ? obj.name : (obj as { function?: { name?: string } }).function?.name;
|
|
58
|
+
if (fnName && isImageGenClientAlias(fnName)) {
|
|
59
|
+
toolNames.add(fnName);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (toolNames.size === 0) return undefined;
|
|
64
|
+
return { toolNames, originalTool };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The synthetic function tool exposed to a chat/anthropic model in place of the dropped hosted
|
|
69
|
+
* image_generation. The model calls it like any function; the proxy intercepts the call and runs the
|
|
70
|
+
* real generation via the sidecar (the call is never relayed to Codex). `imageGeneration:true` flags it.
|
|
71
|
+
*/
|
|
72
|
+
export function buildImageTool(): OcxTool {
|
|
73
|
+
return {
|
|
74
|
+
name: IMAGE_GEN_TOOL_NAME,
|
|
75
|
+
description:
|
|
76
|
+
"Generate an image from a text prompt. Returns absolute local filesystem path(s). " +
|
|
77
|
+
"Use when the user asks to create or draw an image.",
|
|
78
|
+
parameters: {
|
|
79
|
+
type: "object",
|
|
80
|
+
properties: {
|
|
81
|
+
prompt: { type: "string", description: "Detailed image generation prompt. Required." },
|
|
82
|
+
n: { type: "integer", minimum: 1, maximum: 4 },
|
|
83
|
+
},
|
|
84
|
+
required: ["prompt"],
|
|
85
|
+
},
|
|
86
|
+
imageGeneration: true,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** The function name the chat model sees for video generation. */
|
|
91
|
+
export const VIDEO_GEN_TOOL_NAME = "video_gen";
|
|
92
|
+
|
|
93
|
+
const VIDEO_GEN_NAMES = new Set([
|
|
94
|
+
"video_gen",
|
|
95
|
+
"video_generation",
|
|
96
|
+
"videogen",
|
|
97
|
+
"generate_video",
|
|
98
|
+
"generatevideo",
|
|
99
|
+
]);
|
|
100
|
+
|
|
101
|
+
export function isVideoGenName(name: string): boolean {
|
|
102
|
+
return VIDEO_GEN_NAMES.has(name.toLowerCase());
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* The synthetic function tool exposed to a chat/anthropic model for video generation.
|
|
107
|
+
* Unlike image_generation, there is no hosted OpenAI tool type for video — this tool is
|
|
108
|
+
* unconditionally injected when the video bridge is enabled. `videoGeneration:true` flags it
|
|
109
|
+
* so the forced-final pass can drop it.
|
|
110
|
+
*/
|
|
111
|
+
export function buildVideoTool(): OcxTool {
|
|
112
|
+
return {
|
|
113
|
+
name: VIDEO_GEN_TOOL_NAME,
|
|
114
|
+
description:
|
|
115
|
+
"Generate a short video (1-15 seconds) from a text prompt. Returns an absolute local filesystem path. " +
|
|
116
|
+
"Use when the user asks to create, animate, or generate a video.",
|
|
117
|
+
parameters: {
|
|
118
|
+
type: "object",
|
|
119
|
+
properties: {
|
|
120
|
+
prompt: { type: "string", description: "Detailed video generation prompt. Required." },
|
|
121
|
+
duration: { type: "integer", minimum: 1, maximum: 15, description: "Video length in seconds. Default 6." },
|
|
122
|
+
resolution: { type: "string", enum: ["480p", "720p"], description: "Video resolution. Default 720p." },
|
|
123
|
+
aspect_ratio: {
|
|
124
|
+
type: "string",
|
|
125
|
+
enum: ["16:9", "9:16", "1:1", "4:3", "3:4", "3:2", "2:3"],
|
|
126
|
+
description: "Aspect ratio. Default 16:9.",
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
required: ["prompt"],
|
|
130
|
+
},
|
|
131
|
+
videoGeneration: true,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { OcxProviderConfig } from "../types";
|
|
2
|
+
|
|
3
|
+
export interface ImageBridgePlan {
|
|
4
|
+
provider: OcxProviderConfig;
|
|
5
|
+
auth: { baseUrl: string; token: string };
|
|
6
|
+
model: string;
|
|
7
|
+
toolNames: Set<string>;
|
|
8
|
+
/** Per-call xAI deadline (ms). Defaults inside callXaiImages when omitted. */
|
|
9
|
+
timeoutMs?: number;
|
|
10
|
+
/** Defaults from the hosted image_generation tool when the model omits size/quality. */
|
|
11
|
+
defaultSize?: string;
|
|
12
|
+
defaultQuality?: string;
|
|
13
|
+
/** Max artifact files to retain (from config.images.artifactsKeepCount). Default 200. ≤0 disables prune. */
|
|
14
|
+
artifactsKeepCount?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ImageCallResult {
|
|
18
|
+
ok: boolean;
|
|
19
|
+
model: string;
|
|
20
|
+
prompt: string;
|
|
21
|
+
path?: string;
|
|
22
|
+
files: string[];
|
|
23
|
+
count: number;
|
|
24
|
+
markdown?: string;
|
|
25
|
+
error?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Plan for the video bridge. Same auth/provider shape as image, without image-specific defaults. */
|
|
29
|
+
export interface VideoBridgePlan {
|
|
30
|
+
provider: OcxProviderConfig;
|
|
31
|
+
auth: { baseUrl: string; token: string };
|
|
32
|
+
model: string;
|
|
33
|
+
toolNames: Set<string>;
|
|
34
|
+
/** Per-call xAI deadline (ms) for submit + poll. */
|
|
35
|
+
timeoutMs?: number;
|
|
36
|
+
/** Max artifact files to retain (from config.images.artifactsKeepCount). Default 200. ≤0 disables prune. */
|
|
37
|
+
artifactsKeepCount?: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Result shape for video fulfillment — same as image. */
|
|
41
|
+
export type VideoCallResult = ImageCallResult;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* xAI image generation/editing client.
|
|
3
|
+
*
|
|
4
|
+
* Calls xAI's `/images/generations` or `/images/edits` endpoint, composing a
|
|
5
|
+
* 60 s timeout with the caller's abort signal so the deadline covers the entire
|
|
6
|
+
* response body read. Non-2xx responses throw with the original status code —
|
|
7
|
+
* no 502 compression — so callers can distinguish rate-limit / auth failures
|
|
8
|
+
* from transient errors.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export interface XaiImageRequest {
|
|
12
|
+
prompt: string;
|
|
13
|
+
model?: string; // default "grok-imagine-image-quality"
|
|
14
|
+
n?: number; // 1-4
|
|
15
|
+
size?: string;
|
|
16
|
+
quality?: string;
|
|
17
|
+
imageUrl?: string; // if set → /images/edits
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface XaiImageResult {
|
|
21
|
+
images: Array<{ b64_json?: string; url?: string }>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const XAI_IMAGES_TIMEOUT_MS = 60_000;
|
|
25
|
+
const XAI_DEFAULT_MODEL = "grok-imagine-image-quality";
|
|
26
|
+
|
|
27
|
+
// xAI only accepts aspect_ratio + resolution, not OpenAI's size/quality. Map
|
|
28
|
+
// OpenAI's "WxH" size to the closest standard ratio (log-space distance) and
|
|
29
|
+
// OpenAI quality buckets onto xAI's 1k/2k resolution. Unknown values are
|
|
30
|
+
// dropped rather than forwarded, to avoid sending parameters xAI rejects.
|
|
31
|
+
const XAI_ASPECT_RATIOS: ReadonlyArray<readonly [string, number]> = [
|
|
32
|
+
["1:1", 1],
|
|
33
|
+
["3:4", 0.75],
|
|
34
|
+
["4:3", 4 / 3],
|
|
35
|
+
["9:16", 0.5625],
|
|
36
|
+
["16:9", 16 / 9],
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
function mapSizeToAspectRatio(size?: string): string | undefined {
|
|
40
|
+
if (!size) return undefined;
|
|
41
|
+
const m = /^(\d+)x(\d+)$/.exec(size);
|
|
42
|
+
if (!m) return undefined;
|
|
43
|
+
const ratio = parseInt(m[1], 10) / parseInt(m[2], 10);
|
|
44
|
+
let best = XAI_ASPECT_RATIOS[0]!;
|
|
45
|
+
let bestDiff = Infinity;
|
|
46
|
+
for (const [label, r] of XAI_ASPECT_RATIOS) {
|
|
47
|
+
const diff = Math.abs(Math.log(ratio / r));
|
|
48
|
+
if (diff < bestDiff) {
|
|
49
|
+
bestDiff = diff;
|
|
50
|
+
best = [label, r] as const;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return best[0];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function mapQualityToResolution(quality?: string): string | undefined {
|
|
57
|
+
if (!quality) return undefined;
|
|
58
|
+
const q = quality.toLowerCase();
|
|
59
|
+
if (q === "hd" || q === "high") return "2k";
|
|
60
|
+
if (q === "standard" || q === "low" || q === "auto") return "1k";
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function callXaiImages(
|
|
65
|
+
req: XaiImageRequest,
|
|
66
|
+
auth: { baseUrl: string; token: string },
|
|
67
|
+
signal?: AbortSignal,
|
|
68
|
+
timeoutMs: number = XAI_IMAGES_TIMEOUT_MS,
|
|
69
|
+
): Promise<XaiImageResult> {
|
|
70
|
+
const isEdit = typeof req.imageUrl === "string" && req.imageUrl.length > 0;
|
|
71
|
+
const endpoint = isEdit ? "/images/edits" : "/images/generations";
|
|
72
|
+
|
|
73
|
+
const body: Record<string, unknown> = {
|
|
74
|
+
model: req.model ?? XAI_DEFAULT_MODEL,
|
|
75
|
+
prompt: req.prompt,
|
|
76
|
+
n: req.n ?? 1,
|
|
77
|
+
};
|
|
78
|
+
const aspectRatio = mapSizeToAspectRatio(req.size);
|
|
79
|
+
const resolution = mapQualityToResolution(req.quality);
|
|
80
|
+
if (aspectRatio) body.aspect_ratio = aspectRatio;
|
|
81
|
+
if (resolution) body.resolution = resolution;
|
|
82
|
+
if (isEdit) {
|
|
83
|
+
body.image = { url: req.imageUrl as string, type: "image_url" };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const deadlineMs = Number.isFinite(timeoutMs) && timeoutMs > 0
|
|
87
|
+
? Math.floor(timeoutMs)
|
|
88
|
+
: XAI_IMAGES_TIMEOUT_MS;
|
|
89
|
+
const timeout = AbortSignal.timeout(deadlineMs);
|
|
90
|
+
const linkedSignal = signal ? AbortSignal.any([signal, timeout]) : timeout;
|
|
91
|
+
|
|
92
|
+
const baseUrl = auth.baseUrl.replace(/\/+$/, "");
|
|
93
|
+
const resp = await fetch(`${baseUrl}${endpoint}`, {
|
|
94
|
+
method: "POST",
|
|
95
|
+
headers: {
|
|
96
|
+
"Authorization": `Bearer ${auth.token}`,
|
|
97
|
+
"Content-Type": "application/json",
|
|
98
|
+
},
|
|
99
|
+
body: JSON.stringify(body),
|
|
100
|
+
signal: linkedSignal,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
if (!resp.ok) {
|
|
104
|
+
try { await resp.body?.cancel(); } catch { /* ignore */ }
|
|
105
|
+
const err = new Error("xAI images API returned " + resp.status) as Error & { status: number };
|
|
106
|
+
err.status = resp.status;
|
|
107
|
+
throw err;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Read the body as text under the linked signal, then parse. The 60 s timeout
|
|
111
|
+
// and caller abort cover the read. A 200 MiB hard cap on the text prevents a
|
|
112
|
+
// runaway response from exhausting memory before materialization caps apply.
|
|
113
|
+
const MAX_RESPONSE_BYTES = 200 * 1024 * 1024;
|
|
114
|
+
const reader = resp.body?.getReader();
|
|
115
|
+
if (!reader) throw new Error("xAI images API returned no body");
|
|
116
|
+
const decoder = new TextDecoder();
|
|
117
|
+
let text = "";
|
|
118
|
+
let totalBytes = 0;
|
|
119
|
+
try {
|
|
120
|
+
for (;;) {
|
|
121
|
+
const { value, done } = await reader.read();
|
|
122
|
+
if (done) break;
|
|
123
|
+
totalBytes += value.byteLength;
|
|
124
|
+
if (totalBytes > MAX_RESPONSE_BYTES) throw new Error("xAI images API response exceeds size cap");
|
|
125
|
+
text += decoder.decode(value, { stream: true });
|
|
126
|
+
}
|
|
127
|
+
text += decoder.decode();
|
|
128
|
+
} finally {
|
|
129
|
+
try { await reader.cancel(); } catch { /* ignore cancel errors */ }
|
|
130
|
+
reader.releaseLock();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const json = JSON.parse(text) as { data?: Array<{ b64_json?: string; url?: string }> };
|
|
134
|
+
|
|
135
|
+
const images = (json.data ?? []).map((entry) => ({
|
|
136
|
+
b64_json: entry.b64_json,
|
|
137
|
+
url: entry.url,
|
|
138
|
+
}));
|
|
139
|
+
|
|
140
|
+
return { images };
|
|
141
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* xAI video generation client.
|
|
3
|
+
*
|
|
4
|
+
* Video generation is asynchronous: a POST to `/videos/generations` returns a `request_id`,
|
|
5
|
+
* which is then polled via GET `/videos/{request_id}` until the status is terminal.
|
|
6
|
+
* The submit call uses a 60 s timeout (it only returns an ID); the poll call uses 30 s.
|
|
7
|
+
* Non-2xx responses throw with the original status code so callers can distinguish
|
|
8
|
+
* rate-limit / auth failures from transient errors.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export interface XaiVideoSubmitRequest {
|
|
12
|
+
prompt: string;
|
|
13
|
+
model?: string; // default "grok-imagine-video"
|
|
14
|
+
duration?: number; // 1-15 seconds
|
|
15
|
+
resolution?: string; // "480p" | "720p"
|
|
16
|
+
aspectRatio?: string; // "16:9" | "9:16" | "1:1" | "4:3" | "3:4" | "3:2" | "2:3"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface XaiVideoSubmitResult {
|
|
20
|
+
requestId: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface XaiVideoPollResult {
|
|
24
|
+
status: "processing" | "done" | "failed" | "expired";
|
|
25
|
+
videoUrl?: string;
|
|
26
|
+
progress?: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const SUBMIT_TIMEOUT_MS = 60_000;
|
|
30
|
+
const POLL_TIMEOUT_MS = 30_000;
|
|
31
|
+
const DEFAULT_VIDEO_MODEL = "grok-imagine-video";
|
|
32
|
+
|
|
33
|
+
/** Maximum response body size for the submit and poll calls (responses are small JSON). */
|
|
34
|
+
const MAX_RESPONSE_BYTES = 1024 * 1024; // 1 MiB — these endpoints return small JSON
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Read an HTTP response body as text with a hard byte cap. Used for the non-streaming
|
|
38
|
+
* video submit/poll endpoints.
|
|
39
|
+
*/
|
|
40
|
+
async function readBoundedText(resp: Response): Promise<string> {
|
|
41
|
+
const reader = resp.body?.getReader();
|
|
42
|
+
if (!reader) throw new Error("xAI video API returned no body");
|
|
43
|
+
const decoder = new TextDecoder();
|
|
44
|
+
let text = "";
|
|
45
|
+
let totalBytes = 0;
|
|
46
|
+
try {
|
|
47
|
+
for (;;) {
|
|
48
|
+
const { value, done } = await reader.read();
|
|
49
|
+
if (done) break;
|
|
50
|
+
totalBytes += value.byteLength;
|
|
51
|
+
if (totalBytes > MAX_RESPONSE_BYTES) throw new Error("xAI video API response exceeds size cap");
|
|
52
|
+
text += decoder.decode(value, { stream: true });
|
|
53
|
+
}
|
|
54
|
+
text += decoder.decode();
|
|
55
|
+
} finally {
|
|
56
|
+
try { await reader.cancel(); } catch { /* ignore */ }
|
|
57
|
+
reader.releaseLock();
|
|
58
|
+
}
|
|
59
|
+
return text;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Submit a video generation job to xAI. Returns the `request_id` used for polling.
|
|
64
|
+
*/
|
|
65
|
+
export async function submitVideoJob(
|
|
66
|
+
req: XaiVideoSubmitRequest,
|
|
67
|
+
auth: { baseUrl: string; token: string },
|
|
68
|
+
signal?: AbortSignal,
|
|
69
|
+
): Promise<XaiVideoSubmitResult> {
|
|
70
|
+
const body: Record<string, unknown> = {
|
|
71
|
+
model: req.model ?? DEFAULT_VIDEO_MODEL,
|
|
72
|
+
prompt: req.prompt,
|
|
73
|
+
};
|
|
74
|
+
if (typeof req.duration === "number") body.duration = req.duration;
|
|
75
|
+
if (typeof req.resolution === "string") body.resolution = req.resolution;
|
|
76
|
+
if (typeof req.aspectRatio === "string") body.aspect_ratio = req.aspectRatio;
|
|
77
|
+
|
|
78
|
+
const timeout = AbortSignal.timeout(SUBMIT_TIMEOUT_MS);
|
|
79
|
+
const linkedSignal = signal ? AbortSignal.any([signal, timeout]) : timeout;
|
|
80
|
+
|
|
81
|
+
const resp = await fetch(`${auth.baseUrl}/videos/generations`, {
|
|
82
|
+
method: "POST",
|
|
83
|
+
headers: {
|
|
84
|
+
"Authorization": `Bearer ${auth.token}`,
|
|
85
|
+
"Content-Type": "application/json",
|
|
86
|
+
},
|
|
87
|
+
body: JSON.stringify(body),
|
|
88
|
+
signal: linkedSignal,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
if (!resp.ok) {
|
|
92
|
+
try { await resp.body?.cancel(); } catch { /* ignore */ }
|
|
93
|
+
const err = new Error("xAI videos API returned " + resp.status) as Error & { status: number };
|
|
94
|
+
err.status = resp.status;
|
|
95
|
+
throw err;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const text = await readBoundedText(resp);
|
|
99
|
+
const json = JSON.parse(text) as { request_id?: string; id?: string };
|
|
100
|
+
const requestId = json.request_id ?? json.id;
|
|
101
|
+
if (typeof requestId !== "string") {
|
|
102
|
+
throw new Error("xAI videos API did not return a request_id");
|
|
103
|
+
}
|
|
104
|
+
return { requestId };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Poll the status of a video generation job. Call this repeatedly with backoff until
|
|
109
|
+
* `status` is `"done"` (video ready) or a terminal failure state.
|
|
110
|
+
*/
|
|
111
|
+
export async function pollVideoJob(
|
|
112
|
+
requestId: string,
|
|
113
|
+
auth: { baseUrl: string; token: string },
|
|
114
|
+
signal?: AbortSignal,
|
|
115
|
+
): Promise<XaiVideoPollResult> {
|
|
116
|
+
const timeout = AbortSignal.timeout(POLL_TIMEOUT_MS);
|
|
117
|
+
const linkedSignal = signal ? AbortSignal.any([signal, timeout]) : timeout;
|
|
118
|
+
|
|
119
|
+
const resp = await fetch(`${auth.baseUrl}/videos/${encodeURIComponent(requestId)}`, {
|
|
120
|
+
method: "GET",
|
|
121
|
+
headers: {
|
|
122
|
+
"Authorization": `Bearer ${auth.token}`,
|
|
123
|
+
},
|
|
124
|
+
signal: linkedSignal,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
if (!resp.ok) {
|
|
128
|
+
try { await resp.body?.cancel(); } catch { /* ignore */ }
|
|
129
|
+
const err = new Error("xAI videos poll API returned " + resp.status) as Error & { status: number };
|
|
130
|
+
err.status = resp.status;
|
|
131
|
+
throw err;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const text = await readBoundedText(resp);
|
|
135
|
+
const json = JSON.parse(text) as {
|
|
136
|
+
status?: string;
|
|
137
|
+
state?: string;
|
|
138
|
+
video?: { url?: string };
|
|
139
|
+
videos?: Array<{ url?: string }>;
|
|
140
|
+
progress?: number;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// Normalize status — xAI uses "done"/"processing"/"failed"/"expired" but be lenient.
|
|
144
|
+
const rawStatus = (json.status ?? json.state ?? "").toLowerCase();
|
|
145
|
+
let status: XaiVideoPollResult["status"];
|
|
146
|
+
if (rawStatus === "done" || rawStatus === "completed" || rawStatus === "succeeded") {
|
|
147
|
+
status = "done";
|
|
148
|
+
} else if (rawStatus === "failed" || rawStatus === "error") {
|
|
149
|
+
status = "failed";
|
|
150
|
+
} else if (rawStatus === "expired" || rawStatus === "timeout") {
|
|
151
|
+
status = "expired";
|
|
152
|
+
} else {
|
|
153
|
+
status = "processing";
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const videoUrl = json.video?.url ?? json.videos?.[0]?.url;
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
status,
|
|
160
|
+
...(typeof videoUrl === "string" ? { videoUrl } : {}),
|
|
161
|
+
...(typeof json.progress === "number" ? { progress: json.progress } : {}),
|
|
162
|
+
};
|
|
163
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export { startServer } from "./server";
|
|
2
|
+
export { parseRequest } from "./responses/parser";
|
|
3
|
+
export { bridgeToResponsesSSE, buildResponseJSON, formatErrorResponse } from "./bridge";
|
|
4
|
+
export { createAnthropicAdapter } from "./adapters/anthropic";
|
|
5
|
+
export { createAzureAdapter } from "./adapters/azure";
|
|
6
|
+
export { createCursorAdapter } from "./adapters/cursor";
|
|
7
|
+
export { createGoogleAdapter } from "./adapters/google";
|
|
8
|
+
export { createOpenAIChatAdapter } from "./adapters/openai-chat";
|
|
9
|
+
export { createResponsesPassthroughAdapter } from "./adapters/openai-responses";
|
|
10
|
+
export { loadConfig, saveConfig } from "./config";
|
|
11
|
+
export type { ProviderAdapter } from "./adapters/base";
|
|
12
|
+
export type {
|
|
13
|
+
OcxConfig,
|
|
14
|
+
OcxContext,
|
|
15
|
+
OcxMessage,
|
|
16
|
+
OcxParsedRequest,
|
|
17
|
+
OcxProviderConfig,
|
|
18
|
+
OcxRequestOptions,
|
|
19
|
+
OcxTool,
|
|
20
|
+
AdapterEvent,
|
|
21
|
+
} from "./types";
|
|
22
|
+
// release-train: preview publish gate for v2.7.35-preview.20260723
|