@bitkyc08/opencodex 2.63.0 → 2.64.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/AGENTS_INSTALL.md +4 -3
- package/README.md +46 -33
- package/assets/download-linux.svg +10 -0
- package/assets/download-macos.svg +10 -0
- package/assets/download-windows.svg +10 -0
- package/bin/ocx.mjs +12 -4
- package/gui/dist/assets/App-CpuDF3ci.js +50 -0
- package/gui/dist/assets/App-I5AnaSLh.css +1 -0
- package/gui/dist/assets/{Tray-CncKDBTp.js → Tray-B4uEIa1O.js} +1 -1
- package/gui/dist/assets/index-DiBRuK-d.css +1 -0
- package/gui/dist/assets/index-SggB6t3z.js +86 -0
- package/gui/dist/assets/usage-companion-chart-DLbKOJml.js +1 -0
- package/gui/dist/index.html +2 -2
- package/native/remote-workspace-helper/src/protocol.rs +5 -0
- package/package.json +4 -1
- package/src/adapters/anthropic.ts +66 -7
- package/src/adapters/codebuddy/adapter.ts +112 -16
- package/src/adapters/codebuddy/mcp-server.ts +180 -0
- package/src/adapters/codebuddy/scaffold-guard.ts +25 -8
- package/src/adapters/codebuddy/tool-bridge.ts +597 -0
- package/src/adapters/coding-agent/protocol.ts +123 -10
- package/src/adapters/coding-agent/turn.ts +324 -2
- package/src/adapters/command-code-restored-schema.ts +112 -0
- package/src/adapters/command-code-tool-text.ts +589 -0
- package/src/adapters/command-code.ts +80 -6
- package/src/adapters/cursor/catalog.ts +12 -0
- package/src/adapters/cursor/current-request.ts +46 -0
- package/src/adapters/cursor/discovery.ts +1 -1
- package/src/adapters/cursor/effort-map.ts +4 -0
- package/src/adapters/cursor/native-exec.ts +15 -0
- package/src/adapters/cursor/protobuf-events.ts +40 -12
- package/src/adapters/cursor/protobuf-request.ts +83 -18
- package/src/adapters/cursor/request-builder.ts +5 -4
- package/src/adapters/cursor/tool-guidance.ts +1 -1
- package/src/adapters/devin/live-models.ts +7 -0
- package/src/adapters/inline-think-tags.ts +251 -0
- package/src/adapters/kiro/adapter.ts +1 -0
- package/src/adapters/kiro/stream.ts +5 -3
- package/src/adapters/kiro/usage.ts +4 -3
- package/src/adapters/mimo-free.ts +37 -18
- package/src/adapters/openai-chat/messages.ts +5 -5
- package/src/adapters/openai-chat/tool-schema.ts +124 -2
- package/src/adapters/openai-chat.ts +13 -3
- package/src/adapters/openai-responses/passthrough.ts +15 -2
- package/src/adapters/openai-responses/reasoning.ts +14 -1
- package/src/adapters/openai-responses/request-strips.ts +29 -3
- package/src/adapters/openai-responses/tool-output-recovery.ts +9 -3
- package/src/adapters/qoder/adapter.ts +15 -12
- package/src/adapters/qoder/scaffold-guard.ts +45 -13
- package/src/bridge/internal.ts +20 -0
- package/src/bridge/sse.ts +14 -5
- package/src/claude/agents-inject.ts +2 -1
- package/src/claude/desktop-applied-marker.ts +44 -0
- package/src/claude/desktop-profile.ts +22 -12
- package/src/claude/inbound.ts +8 -3
- package/src/cli/access.ts +22 -5
- package/src/cli/account-api.ts +6 -0
- package/src/cli/account-auth.ts +5 -2
- package/src/cli/account.ts +5 -6
- package/src/cli/aside-profiles.ts +61 -1
- package/src/cli/claude-desktop.ts +3 -2
- package/src/cli/claude.ts +3 -1
- package/src/cli/codex-cli-update.ts +2 -2
- package/src/cli/codex-shim-autorestore.ts +3 -0
- package/src/cli/dispatch.ts +12 -4
- package/src/cli/index.ts +98 -10
- package/src/cli/registry.ts +1 -1
- package/src/cli/resolve.ts +113 -1
- package/src/cli/root.ts +5 -0
- package/src/cli/stop-approval.ts +186 -0
- package/src/cli/stop-report.ts +1 -1
- package/src/cli/system-restart-client.ts +19 -4
- package/src/client/hub-client.ts +25 -18
- package/src/clients/config-export.ts +12 -4
- package/src/codex/account-auto-switch.ts +59 -0
- package/src/codex/account-lifecycle.ts +7 -0
- package/src/codex/account-priority.ts +10 -0
- package/src/codex/account-usability.ts +9 -0
- package/src/codex/auth-api/account-list.ts +5 -0
- package/src/codex/auth-api/login-flow.ts +15 -5
- package/src/codex/auth-api/login-state.ts +5 -16
- package/src/codex/auth-api/pool-mode-gate.ts +30 -7
- package/src/codex/auth-api/routes.ts +39 -3
- package/src/codex/auth-context.ts +145 -17
- package/src/codex/catalog/build-entries.ts +2 -1
- package/src/codex/catalog/effort.ts +8 -2
- package/src/codex/catalog/metadata.ts +33 -7
- package/src/codex/catalog/native-models.ts +102 -5
- package/src/codex/catalog/parsing.ts +2 -0
- package/src/codex/catalog/provider-models.ts +19 -6
- package/src/codex/cli-installation-targets.ts +44 -40
- package/src/codex/codex-write-lock.ts +32 -2
- package/src/codex/inject/multi-agent-v2.ts +90 -0
- package/src/codex/inject/plan.ts +365 -0
- package/src/codex/inject.ts +237 -362
- package/src/codex/log-guard/maintenance.ts +30 -13
- package/src/codex/project-config-warnings.ts +47 -7
- package/src/codex/prompt-layers/encoding.ts +41 -0
- package/src/codex/prompt-layers/toml-read.ts +1 -1
- package/src/codex/prompt-layers.ts +17 -13
- package/src/codex/prompt-text-probe.ts +20 -10
- package/src/codex/quota-observation-freshness.ts +34 -0
- package/src/codex/quota-rejection.ts +8 -4
- package/src/codex/quota.ts +4 -2
- package/src/codex/routing/selection.ts +32 -9
- package/src/codex/routing.ts +53 -11
- package/src/codex/subagent-model-fallback.ts +4 -3
- package/src/codex/sync.ts +11 -0
- package/src/codex/windows-installation-files.ts +33 -11
- package/src/codex/write-coordination.ts +4 -0
- package/src/combos/request.ts +6 -2
- package/src/companion/settings.ts +6 -1
- package/src/config/atomic-write.ts +12 -1
- package/src/config/derived-registries.ts +29 -0
- package/src/config/diagnostics.ts +17 -0
- package/src/config/live-reconcile.ts +11 -2
- package/src/config/load-degrade.ts +9 -2
- package/src/config/persist-unlocked.ts +4 -3
- package/src/config/persisted-mutation.ts +94 -0
- package/src/config/provider-validation.ts +1 -1
- package/src/config/proxy-env.ts +62 -12
- package/src/config/rebase-provenance.ts +80 -1
- package/src/config/schema/config-schema.ts +5 -0
- package/src/config/schema/leaf-validators.ts +50 -1
- package/src/config/subagent-models.ts +41 -15
- package/src/config.ts +18 -95
- package/src/generated/compatibility-version.json +330 -218
- package/src/generated/model-metadata.ts +8 -5
- package/src/github/star-state.ts +46 -6
- package/src/grok/inject.ts +74 -40
- package/src/grok/status.ts +11 -4
- package/src/integrations/cursor-effort-table.ts +50 -13
- package/src/integrations/raycast-detect.ts +19 -4
- package/src/integrations/serialize.ts +11 -1
- package/src/lib/bounded-body.ts +30 -0
- package/src/lib/crash-guard.ts +52 -4
- package/src/lib/local-aside-sync-contract.ts +41 -0
- package/src/lib/package-tree-integrity.ts +181 -9
- package/src/lib/proxy-env.ts +18 -1
- package/src/lib/request-failure-attribution.ts +1 -0
- package/src/lib/request-failure-model.ts +3 -0
- package/src/lib/service-secrets.ts +99 -2
- package/src/lib/socks5-fetch.ts +43 -14
- package/src/lib/token-estimate.ts +17 -2
- package/src/oauth/command-code.ts +3 -2
- package/src/oauth/generic-account-failover.ts +29 -1
- package/src/oauth/index.ts +24 -16
- package/src/oauth/login-flow-state.ts +5 -5
- package/src/oauth/meta-muse-device.ts +49 -7
- package/src/oauth/store.ts +74 -9
- package/src/providers/alibaba-region-backup.ts +16 -1
- package/src/providers/anthropic-fast.ts +89 -0
- package/src/providers/anthropic-reset-grant-ledger.ts +288 -0
- package/src/providers/anthropic-reset-grants.ts +329 -0
- package/src/providers/claude-cli-identity.ts +12 -0
- package/src/providers/command-code-efforts.ts +184 -102
- package/src/providers/derive.ts +11 -3
- package/src/providers/fastwire.ts +16 -3
- package/src/providers/label.ts +8 -3
- package/src/providers/model-rename-fields.ts +1 -0
- package/src/providers/openai-virtual-models.ts +1 -0
- package/src/providers/quota/vendor-probes-oauth.ts +2 -1
- package/src/providers/reasoning-metadata.ts +38 -15
- package/src/providers/registry/entries-core.ts +95 -7
- package/src/providers/registry/entries-extended.ts +21 -10
- package/src/providers/registry/model-ids.ts +1 -0
- package/src/providers/registry/model-seeds.ts +31 -3
- package/src/providers/registry/types.ts +3 -1
- package/src/providers/resolved-model-policy-merge.ts +38 -8
- package/src/providers/resolved-model-policy.ts +4 -2
- package/src/providers/service-tier.ts +3 -1
- package/src/reasoning-effort.ts +6 -5
- package/src/responses/code-mode-helper-compat.ts +14 -4
- package/src/responses/code-mode-shell-input.ts +54 -0
- package/src/responses/custom-tool-compat.ts +173 -5
- package/src/responses/parser.ts +38 -2
- package/src/responses/reasoning-replay-cache.ts +26 -0
- package/src/router.ts +11 -1
- package/src/routing/history/indexer.ts +7 -2
- package/src/routing/history/schema.ts +3 -1
- package/src/server/adapter-resolve.ts +2 -2
- package/src/server/auth-cors.ts +3 -0
- package/src/server/chat-completions.ts +14 -1
- package/src/server/chat-native.ts +17 -0
- package/src/server/claude-messages.ts +4 -3
- package/src/server/direct-local-http.ts +45 -19
- package/src/server/gui-session.ts +6 -15
- package/src/server/index/package-tree-guard.ts +53 -0
- package/src/server/index/serve-options.ts +17 -3
- package/src/server/index/startup-warnings.ts +17 -0
- package/src/server/index.ts +15 -17
- package/src/server/lifecycle.ts +8 -0
- package/src/server/live.ts +54 -4
- package/src/server/management/agent-settings-routes.ts +44 -8
- package/src/server/management/anthropic-reset-grant-routes.ts +252 -0
- package/src/server/management/codex-prompt-routes.ts +5 -1
- package/src/server/management/config-routes.ts +14 -4
- package/src/server/management/oauth-account-routes.ts +17 -1
- package/src/server/management/provider-overwrite-carry.ts +164 -0
- package/src/server/management/provider-routes.ts +36 -6
- package/src/server/management/remote-workspace-routes.ts +2 -2
- package/src/server/management/route-registry.ts +2 -0
- package/src/server/management/shadow-call-validation.ts +39 -0
- package/src/server/management/system-restart.ts +72 -6
- package/src/server/management-api.ts +16 -2
- package/src/server/management-auth.ts +52 -8
- package/src/server/proxy-liveness.ts +110 -4
- package/src/server/request-log.ts +26 -9
- package/src/server/request-metrics.ts +5 -0
- package/src/server/responses/adapter-continuation.ts +6 -3
- package/src/server/responses/adapter-dispatch.ts +65 -1
- package/src/server/responses/compact.ts +31 -1
- package/src/server/responses/compaction-routing.ts +28 -3
- package/src/server/responses/core-codex-account.ts +85 -26
- package/src/server/responses/core-combo-failure.ts +16 -15
- package/src/server/responses/core-normalize.ts +5 -1
- package/src/server/responses/core-opaque-recovery.ts +50 -2
- package/src/server/responses/core-options.ts +2 -0
- package/src/server/responses/core-replay.ts +7 -0
- package/src/server/responses/core.ts +1 -1
- package/src/server/responses/encrypted-payload.ts +23 -9
- package/src/server/responses/passthrough-delivery.ts +55 -35
- package/src/server/responses/passthrough-dispatch.ts +12 -13
- package/src/server/responses/policy-fallback.ts +12 -1
- package/src/server/responses/request-prepare.ts +46 -7
- package/src/server/responses/request-spend.ts +31 -15
- package/src/server/responses/run-turn-execution.ts +6 -5
- package/src/server/responses/shadow-target-availability.ts +61 -0
- package/src/server/responses-custom-tool-repair.ts +2 -0
- package/src/service/claim.ts +185 -0
- package/src/service/cli.ts +10 -1
- package/src/service/guarded-manager-target.ts +151 -0
- package/src/service/guards.ts +47 -48
- package/src/service/managing-cli.ts +170 -0
- package/src/service/orchestration.ts +3 -1
- package/src/service/state.ts +4 -0
- package/src/service/systemd.ts +16 -1
- package/src/service.ts +1 -1
- package/src/types/config.ts +8 -0
- package/src/types/provider.ts +16 -0
- package/src/types/request.ts +10 -0
- package/src/types/tools.ts +9 -1
- package/src/types/wire.ts +68 -4
- package/src/types.ts +1 -0
- package/src/update/job.ts +10 -16
- package/src/update/npm-invocation.mjs +17 -16
- package/src/update/transactional-install.d.mts +22 -1
- package/src/update/transactional-install.mjs +176 -19
- package/src/update/update-failure-guidance.d.mts +8 -0
- package/src/update/update-failure-guidance.mjs +47 -0
- package/src/usage/expected-prices.ts +54 -12
- package/src/usage/log.ts +117 -3
- package/src/usage/telemetry-contract.ts +1 -0
- package/src/usage/timeline.ts +34 -6
- package/src/usage/user-cost-overlay-reconciler.ts +3 -3
- package/src/vision/reasoning.ts +2 -4
- package/src/web-search/xai-executor.ts +14 -4
- package/gui/dist/assets/App-BqrsSrIR.js +0 -50
- package/gui/dist/assets/index-C6SJrh0N.js +0 -86
- package/gui/dist/assets/index-DdDunwDb.css +0 -1
- package/gui/dist/assets/usage-companion-chart-CzAAAB1o.js +0 -1
- package/src/adapters/kiro-thinking.ts +0 -112
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{n as e}from"./index-SggB6t3z.js";var t={anthropic:`claude-color.svg`,"anthropic-apikey":`claude-color.svg`,"azure-openai":`openai.svg`,chatgpt:`openai.svg`,"cloudflare-ai-gateway":`cloudflare-ai-gateway-color.svg`,"cloudflare-workers-ai":`cloudflare-ai-gateway-color.svg`,cline:`cline-color.svg`,"cline-pass":`cline-color.svg`,"command-code":`commandcode-color.svg`,commandcode:`commandcode-color.svg`,cursor:`cursor-color.svg`,deepseek:`deepseek-color.svg`,devin:`devin.svg`,firepass:`firepass-color.svg`,fireworks:`fireworks-color.svg`,github:`github-copilot-color.svg`,"github-copilot":`copilot-color.svg`,"gitlab-duo":`gitlab-duo-color.svg`,google:`gemini-color.svg`,"google-antigravity":`antigravity-color.svg`,"google-vertex":`gemini-color.svg`,groq:`groq-color.svg`,huggingface:`huggingface-color.svg`,kimi:`kimi-color.svg`,"kimi-code":`kimi-color.svg`,"kimi-responses":`kimi-color.svg`,kiro:`kiro-color.svg`,"lm-studio":`lm-studio-color.svg`,"meta-model":`meta.svg`,"meta-muse":`meta.svg`,mistral:`mistral-color.svg`,minimax:`minimax.svg`,"minimax-cn":`minimax.svg`,moonshot:`moonshot-color.svg`,nvidia:`nvidia-color.svg`,ollama:`ollama-color.svg`,"ollama-cloud":`ollama-color.svg`,openai:`openai.svg`,"openai-apikey":`openai.svg`,"opencode-free":`opencode.svg`,"opencode-go":`opencode.svg`,"opencode-zen":`opencode.svg`,openrouter:`openrouter-color.svg`,opper:`opper.svg`,qianfan:`qianfan-color.svg`,qoder:`qoder.svg`,"qoder-cn":`qoder.svg`,alibaba:`alibaba-color.svg`,"alibaba-token-plan":`alibaba-color.svg`,"alibaba-token-plan-intl":`alibaba-color.svg`,baseten:`baseten.svg`,bizrouter:`bizrouter.svg`,cerebras:`cerebras.svg`,crusoe:`crusoe.svg`,deepinfra:`deepinfra.svg`,digitalocean:`digitalocean.svg`,featherless:`featherless.svg`,hyperbolic:`hyperbolic.svg`,kilo:`kilo.svg`,nanogpt:`nanogpt.svg`,nebius:`nebius.svg`,neuralwatt:`neuralwatt.svg`,nous:`nous.svg`,novita:`novita.svg`,orcarouter:`orcarouter.svg`,"orcarouter-oauth":`orcarouter.svg`,packycode:`packycode.svg`,parallel:`parallel.svg`,sambanova:`sambanova.svg`,scaleway:`scaleway.svg`,stepfun:`stepfun-color.svg`,siliconflow:`siliconflow.svg`,synthetic:`synthetic.svg`,together:`together.svg`,umans:`umans.svg`,venice:`venice.svg`,vultr:`vultr.svg`,litellm:`litellm.svg`,zenmux:`zenmux.svg`,zai:`zai.svg`,"zhipu-bigmodel":`zai.svg`,"zhipu-bigmodel-coding":`zai.svg`,"qwen-cloud":`qwen-portal-color.svg`,"vercel-ai-gateway":`vercel-ai-gateway-color.svg`,vllm:`vllm-color.svg`,xai:`grok.svg`,"mimo-free":`xiaomi-color.svg`,mimo:`xiaomi-color.svg`,xiaomi:`xiaomi-color.svg`,"xiaomi-mimo":`xiaomi-color.svg`},n={anthropic:`Anthropic Claude`,"anthropic-apikey":`Anthropic Claude`,chatgpt:`ChatGPT`,openai:`OpenAI (Codex login)`,"openai-apikey":`OpenAI API`,"azure-openai":`Azure OpenAI`,"cloudflare-ai-gateway":`Cloudflare AI Gateway`,"cloudflare-workers-ai":`Cloudflare Workers AI`,cline:`Cline`,"cline-pass":`ClinePass`,nvidia:`NVIDIA NIM`,ollama:`Ollama`,"ollama-cloud":`Ollama Cloud`,xai:`xAI Grok`,"mimo-free":`MiMo Free`,xiaomi:`Xiaomi`,cursor:`Cursor`,deepseek:`DeepSeek`,devin:`Devin`,github:`GitHub`,"github-copilot":`GitHub Copilot`,"gitlab-duo":`GitLab Duo`,openrouter:`OpenRouter`,opper:`Opper`,"opencode-go":`OpenCode Go`,"opencode-free":`OpenCode Free`,"opencode-zen":`OpenCode Zen`,orcarouter:`OrcaRouter - API`,"orcarouter-oauth":`OrcaRouter - Auth`,packycode:`PackyCode`,mistral:`Mistral`,groq:`Groq`,"meta-model":`Meta Model API`,"meta-muse":`Muse Code`,alibaba:`Alibaba Coding Plan`,"alibaba-token-plan":`Alibaba Token Plan`,"alibaba-token-plan-intl":`Alibaba Token Plan (Intl)`,kimi:`Kimi`,"kimi-code":`Kimi`,"kimi-responses":`Kimi`,moonshot:`Moonshot`,google:`Google`,"google-vertex":`Google Vertex`,"lm-studio":`LM Studio`,huggingface:`Hugging Face`,"qwen-cloud":`Qwen Cloud`,siliconflow:`SiliconFlow`,stepfun:`StepFun`,"tencent-coding-plan":`Tencent Cloud Coding Plan`,codebuddy:`CodeBuddy`,"codebuddy-cn":`CodeBuddy CN`,qoder:`Qoder`,"qoder-cn":`Qoder CN`,"vercel-ai-gateway":`Vercel AI Gateway`,vllm:`vLLM`,litellm:`LiteLLM`},r={"command-code":`provider.name.commandCodeAuth`,commandcode:`provider.name.commandCodeApi`,orcarouter:`provider.name.orcaRouterApi`,"orcarouter-oauth":`provider.name.orcaRouterAuth`,volcengine:`provider.name.volcengine`,"volcengine-coding-plan":`provider.name.volcengineCodingPlan`,"volcengine-agent-plan":`provider.name.volcengineAgentPlan`},i=new Set([...Object.keys(n),...Object.keys(r)]);function a(e){let n=e.toLowerCase();return Object.hasOwn(t,n)?t[n]:void 0}function o(e,t){let n=a(e);return n?`/provider-icons/${n}`:void 0}var s=new Set([`cerebras.svg`,`deepinfra.svg`,`neuralwatt.svg`,`nous.svg`,`novita.svg`,`packycode.svg`,`opper.svg`,`siliconflow.svg`,`synthetic.svg`,`zenmux.svg`,`grok.svg`,`kimi-color.svg`,`ollama-color.svg`,`opencode.svg`,`vercel-ai-gateway-color.svg`]),c=new Set([`baseten.svg`,`kilo.svg`,`sambanova.svg`,`venice.svg`,`zai.svg`]),l=new Set([`bizrouter.svg`,`featherless.svg`,`hyperbolic.svg`,`nebius.svg`,`parallel.svg`,`umans.svg`]);function u(e){if(!e)return`image`;let t=e.split(`/`).pop()??``;return s.has(t)?`mask`:c.has(t)?`plate`:l.has(t)?`dark-plate`:`image`}function d(e,t){let i=e.toLowerCase(),a=Object.hasOwn(r,i)?r[i]:void 0;return a?t(a):(Object.hasOwn(n,i)?n[i]:void 0)||(e===i&&/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(e)?e.split(`-`).map(e=>e&&e[0].toUpperCase()+e.slice(1)).join(` `):e)}function f(e){return i.has(e.toLowerCase())}function p(e){return e===`command-code`?`commandcode-auth`:e===`commandcode`?`commandcode-api`:e}function m(e,t){let n=e.indexOf(`/`);if(n<=0)return e;let r=e.slice(0,n),i=e.slice(n+1);if(r===`command-code`||r===`commandcode`){let e=i.match(/^([a-z0-9]+)-([a-z0-9]+(?:-[a-z0-9]+)+)$/i);return e&&i.startsWith(`${e[1]}-${e[1]}-`)&&(i=i.slice(e[1].length+1)),`${r===`command-code`?`commandcode-auth`:`commandcode-api`}/${i}`}return e}var h={ko:[{v:0x2386f26fc10000,s:`경`},{v:0xe8d4a51000,s:`조`},{v:1e8,s:`억`},{v:1e4,s:`만`}],zh:[{v:0x2386f26fc10000,s:`京`},{v:0xe8d4a51000,s:`兆`},{v:1e8,s:`亿`},{v:1e4,s:`万`}],"zh-TW":[{v:0x2386f26fc10000,s:`京`},{v:0xe8d4a51000,s:`兆`},{v:1e8,s:`億`},{v:1e4,s:`萬`}]};function g(e){return e.replace(/\.0+$/,``).replace(/(\.\d*?)0+$/,`$1`)}function _(e,t){let n=h[t];if(n){for(let t of n)if(e>=t.v)return`${g((e/t.v).toFixed(1))}${t.s}`;return String(e)}return e<1e4?String(e):e<1e6?`${g((e/1e3).toFixed(1))}K`:e<1e9?`${g((e/1e6).toFixed(1))}M`:e<0xe8d4a51000?`${g((e/1e9).toFixed(1))}B`:`${g((e/0xe8d4a51000).toFixed(1))}T`}var v=1e10;function y(e){return e<v?e*1e3:e}function b(e,t){let n=e=>typeof e==`number`&&Number.isFinite(e);if(!n(e.shortPercent)||e.shortPercent<100)return!1;let r=e.shortResetAt;if(!n(r)||r<=0){let r=e.shortObservedAt;if(!n(r))return!1;let i=t-r;return i>=0&&i<=3e5}return y(r)>t}function x(e){return{fiveHourAvailable:e?.shortWindowSeconds===18e3&&typeof e.shortResetAt==`number`,weeklyAvailable:typeof e?.weeklyResetAt==`number`}}function S(e){let t=e?.trim().toLowerCase();return t===`go`||t===`free`}function C(e,t){if(!e)return null;let n=e.shortPercent===void 0&&e.shortResetAt===void 0?e:{...e,fiveHourPercent:e.fiveHourPercent??e.shortPercent,fiveHourResetAt:e.fiveHourResetAt??e.shortResetAt};return S(t)?{...n.monthlyPercent===void 0?{}:{monthlyPercent:n.monthlyPercent},...n.monthlyResetAt===void 0?{}:{monthlyResetAt:n.monthlyResetAt},...n.creditsUsd===void 0?{}:{creditsUsd:n.creditsUsd},...n.resetCredits===void 0?{}:{resetCredits:n.resetCredits},updatedAt:n.updatedAt}:n}function w(e,t,n=Date.now()){if(!e)return null;let r=e=>typeof e==`number`&&Number.isFinite(e),i=r(e.fiveHourPercent)?e.fiveHourPercent:r(e.shortPercent)?e.shortPercent:void 0,a=(S(t)?[e.monthlyPercent]:[e.weeklyPercent,e.monthlyPercent]).filter(r);if(a.length===0)return b({...r(i)?{shortPercent:i}:{},...r(e.fiveHourResetAt??e.shortResetAt)?{shortResetAt:e.fiveHourResetAt??e.shortResetAt}:{},...r(e.shortObservedAt)?{shortObservedAt:e.shortObservedAt}:{}},n)?100:null;let o=r(i)?[...a,i]:a;return o.length?Math.max(...o):null}function T(e){let t=new URLSearchParams({hours:String(e.chartHours),bucketMinutes:String(e.bucketMinutes),metric:e.tokenMetric,aggregation:e.aggregation,grouping:e.chartGrouping});e.models?.length&&t.set(`models`,e.models.join(`,`));for(let n of e.hiddenProviders)t.append(`hiddenProvider`,n);return t}function E(e){return Array.isArray(e)&&e.length<=100&&e.every(e=>typeof e==`string`)?JSON.stringify([...new Set(e)].sort()):void 0}function D(e,t){let n=new Set(t.hiddenProviders),r=e.availableModels.filter(e=>!n.has(e.slice(0,e.indexOf(`/`))));if(t.models?.length===0)return{...e,series:[],availableModels:r};let i=t.models===null?null:new Set(t.models),a=i!==null||n.size>0,o=e.appliedFilters,s=!!o&&E(o.hiddenProviders)===E(t.hiddenProviders)&&(t.models===null?o.models===null:E(o.models)===E(t.models)),c=e.series.filter(e=>e.id===`other`&&e.provider===``?!a||s:!n.has(e.provider)&&(i===null||i.has(`${e.provider}/${e.model}`)||i.has(e.model)));return{...e,series:c,availableModels:r,truncated:e.truncated||(a||o!==void 0)&&!s}}var O={6:15,24:60,72:180,168:360};function k(e){return O[e]}function A(e){if(e<1e3)return String(Math.round(e));let t=[[0xe8d4a51000,`T`],[1e9,`B`],[1e6,`M`],[1e3,`K`]];for(let n=0;n<t.length;n+=1){let[r,i]=t[n];if(e>=r){let a=Math.round(e/r);if(a>=1e3&&n>0){let[r,i]=t[n-1];return`${Math.round(e/r)}${i}`}return`${a}${i}`}}return String(Math.round(e))}function j(e,t){let n=new Map;for(let r of e){let e=r.includes(`/`)?r.slice(0,r.indexOf(`/`)):r,i=n.get(e)??{provider:e,models:[],total:0},a=t.get(r)??0;i.models.push({id:r,total:a}),i.total+=a,n.set(e,i)}return Array.from(n.values()).map(e=>({...e,models:e.models.toSorted((e,t)=>t.total-e.total||e.id.localeCompare(t.id))})).toSorted((e,t)=>t.total-e.total||e.provider.localeCompare(t.provider))}function M(e,t,n,r){let i=new Set(t),a=new Set((e??t).filter(e=>i.has(e)));for(let e of n)r?a.add(e):a.delete(e);return t.length>0&&t.every(e=>a.has(e))?null:t.filter(e=>a.has(e))}function N(e,t=[]){let n={...e};if(typeof n.menuBarTemplate==`string`&&n.menuBarTemplate.trim()===``&&(n.menuBarTemplate=null),n.models!==void 0&&t.length>0){let e=n.models??[],r=new Set(e);e.length===t.length&&t.every(e=>r.has(e))&&(n.models=null)}return n}function P(e,t,n,r,i=8){let a=Math.max(0,t-i*2),o=Math.max(0,n-i*2),s=Math.max(r,1),c=Math.max(e.length-1,1);return e.map((e,t)=>`${i+a*t/c},${i+o*(1-Math.max(0,e)/s)}`).join(` `)}function F(e,t,n,r,i=8){let a=e[0]?.points.length??0;if(a===0)return[];let o=Math.max(0,t-i*2),s=Math.max(0,n-i*2),c=Math.max(r,1),l=Math.min(3,o/Math.max(a*8,1)),u=Math.max(0,o/a-l),d=[];for(let t=0;t<a;t+=1){let n=0;for(let r=0;r<e.length;r+=1){let f=s*Math.max(0,e[r]?.points[t]??0)/c;f>0&&d.push({x:i+o/a*t+l/2,y:i+s-n-f,width:u,height:f,seriesIndex:r,bucketIndex:t}),n+=f}}return d}var I=e(),L=[`#0A84FF`,`#FF9F0A`,`#30D158`,`#BF5AF2`,`#FF453A`,`#64D2FF`],R=640,z=160,B=28;function V(e,t){return Math.max(...t===`stackedBar`?Array.from({length:e.buckets},(t,n)=>e.series.reduce((e,t)=>e+(t.points[n]??0),0)):e.series.flatMap(e=>e.points),0)}function H(e,t){let n=new Intl.DateTimeFormat(t,{month:`short`,day:`numeric`}),r=Math.max(1,Math.floor((e.buckets-1)/3));return[0,1,2,3].map(t=>{let i=Math.min(e.buckets-1,t*r);return n.format(new Date((e.start+i*e.bucketSeconds)*1e3))})}function U({timeline:e,chartStyle:t,hours:n,loading:r,error:i,onRetry:a,locale:o,t:s}){if(r)return(0,I.jsx)(`div`,{className:`usage-companion-chart-skeleton`,"aria-busy":`true`,"aria-label":s(`usage.companion.loading`)});if(i)return(0,I.jsxs)(`div`,{className:`usage-companion-chart-state`,role:`alert`,children:[(0,I.jsx)(`span`,{children:s(`usage.companion.timelineUnavailable`)}),(0,I.jsx)(`button`,{type:`button`,className:`btn btn-ghost btn-sm`,onClick:a,children:s(`common.retry`)})]});if(!e||e.series.length===0)return(0,I.jsx)(`div`,{className:`usage-companion-chart-state`,children:s(`usage.companion.empty`,{hours:e?.buckets?Math.round(e.buckets*e.bucketSeconds/3600):n})});let c=V(e,t),l=H(e,o),u=B,d=l.map((e,t)=>(0,I.jsx)(`text`,{x:B+584*t/3,y:156,textAnchor:t===0?`start`:t===3?`end`:`middle`,className:`usage-companion-axis-label`,children:e},`${e}-${t}`)),f=t===`line`?e.series.map((e,t)=>(0,I.jsx)(`polyline`,{points:P(e.points,584,104,c,0),transform:`translate(${B},${B})`,fill:`none`,stroke:L[t%L.length],strokeWidth:`2`,strokeLinejoin:`round`,strokeLinecap:`round`},e.id)):F(e.series,584,104,c,0).map(e=>(0,I.jsx)(`rect`,{x:B+e.x,y:B+e.y,width:e.width,height:e.height,fill:L[e.seriesIndex%L.length],rx:`1`},`${e.bucketIndex}-${e.seriesIndex}`));return(0,I.jsxs)(`div`,{className:`usage-companion-chart`,children:[(0,I.jsxs)(`svg`,{viewBox:`0 0 ${R} ${z}`,role:`img`,"aria-label":s(`usage.companion.chartLabel`),children:[(0,I.jsx)(`line`,{x1:B,y1:u,x2:B,y2:132,className:`usage-companion-axis`}),(0,I.jsx)(`line`,{x1:B,y1:132,x2:612,y2:132,className:`usage-companion-axis`}),(0,I.jsx)(`text`,{x:24,y:32,textAnchor:`end`,className:`usage-companion-axis-label`,children:A(c)}),f,d]}),(0,I.jsx)(`div`,{className:`usage-companion-legend`,children:e.series.map((e,t)=>(0,I.jsxs)(`span`,{className:`usage-companion-legend-item`,children:[(0,I.jsx)(`span`,{className:`usage-companion-swatch`,style:{background:L[t%L.length]},"aria-hidden":`true`}),(0,I.jsx)(`span`,{children:e.id})]},e.id))}),e.truncated&&(0,I.jsx)(`p`,{className:`muted text-caption`,children:s(`usage.companion.olderRecordsSkipped`)})]})}export{u as _,T as a,M as c,x as d,_ as f,p as g,f as h,D as i,w as l,d as m,k as n,A as o,m as p,N as r,j as s,U as t,C as u,o as v};
|
package/gui/dist/index.html
CHANGED
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
} catch (e) {}
|
|
17
17
|
})();
|
|
18
18
|
</script>
|
|
19
|
-
<script type="module" crossorigin src="/assets/index-
|
|
20
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
19
|
+
<script type="module" crossorigin src="/assets/index-SggB6t3z.js"></script>
|
|
20
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DiBRuK-d.css">
|
|
21
21
|
</head>
|
|
22
22
|
<body>
|
|
23
23
|
<div id="root"></div>
|
|
@@ -91,7 +91,10 @@ impl HelperRequest {
|
|
|
91
91
|
Ok(())
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
// Staged for the Windows confinement backend, which still reports unavailable; nothing calls
|
|
95
|
+
// it yet, so the lint gate would otherwise fail every Windows build.
|
|
94
96
|
#[cfg(target_os = "windows")]
|
|
97
|
+
#[allow(dead_code)]
|
|
95
98
|
pub fn canonical_paths(&self) -> Result<CanonicalPaths, String> {
|
|
96
99
|
let root = canonical_directory(&self.root, "workspace root")?;
|
|
97
100
|
let cwd = canonical_directory(&self.cwd, "command cwd")?;
|
|
@@ -121,6 +124,7 @@ fn validate_path(value: &str, label: &str) -> Result<(), String> {
|
|
|
121
124
|
}
|
|
122
125
|
|
|
123
126
|
#[cfg(target_os = "windows")]
|
|
127
|
+
#[allow(dead_code)]
|
|
124
128
|
fn canonical_directory(value: &str, label: &str) -> Result<PathBuf, String> {
|
|
125
129
|
let original = Path::new(value);
|
|
126
130
|
let metadata =
|
|
@@ -134,6 +138,7 @@ fn canonical_directory(value: &str, label: &str) -> Result<PathBuf, String> {
|
|
|
134
138
|
}
|
|
135
139
|
|
|
136
140
|
#[cfg(target_os = "windows")]
|
|
141
|
+
#[allow(dead_code)]
|
|
137
142
|
#[derive(Debug)]
|
|
138
143
|
pub struct CanonicalPaths {
|
|
139
144
|
pub root: PathBuf,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bitkyc08/opencodex",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.64.0",
|
|
4
4
|
"description": "Universal provider proxy for OpenAI Codex & Claude Code — use any LLM with Codex CLI/App/SDK and Claude Code",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./bin/package-main.mjs",
|
|
@@ -20,6 +20,9 @@
|
|
|
20
20
|
"src",
|
|
21
21
|
"gui/dist",
|
|
22
22
|
"assets/banner.png",
|
|
23
|
+
"assets/download-macos.svg",
|
|
24
|
+
"assets/download-windows.svg",
|
|
25
|
+
"assets/download-linux.svg",
|
|
23
26
|
"assets/architecture.png",
|
|
24
27
|
"assets/claude-code-models.gif",
|
|
25
28
|
"assets/codex-app-picker.png",
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { IncomingMeta, ProviderAdapter } from "./base";
|
|
2
|
+
import { createAdapterTierMetadata, type AdapterTierMetadata } from "../providers/fastwire";
|
|
3
|
+
import { ANTHROPIC_FAST_MODE_BETA, mergeAnthropicBetaHeader } from "../providers/anthropic-fast";
|
|
2
4
|
import { createToolCallIdAllocator, type ToolCallIdAllocator } from "./tool-call-id";
|
|
3
5
|
import { debugDroppedFrame } from "../lib/debug";
|
|
4
6
|
import type {
|
|
@@ -175,6 +177,31 @@ function applyPromptCaching(
|
|
|
175
177
|
}
|
|
176
178
|
}
|
|
177
179
|
|
|
180
|
+
/**
|
|
181
|
+
* The fast-mode wire this request should carry, if any. Only a settled `set` decision on a
|
|
182
|
+
* declared `anthropic-speed` wire emits it, and only with the value that wire maps canonical
|
|
183
|
+
* Fast to; everything else (drop, forwarded caller tiers, a declaration for another wire)
|
|
184
|
+
* sends no `speed`, which is the API's standard speed.
|
|
185
|
+
*/
|
|
186
|
+
function anthropicFastSpeed(
|
|
187
|
+
parsed: OcxParsedRequest,
|
|
188
|
+
provider: OcxProviderConfig,
|
|
189
|
+
): { value: string; betas: readonly string[] } | undefined {
|
|
190
|
+
const decision = parsed.options.tierDecision;
|
|
191
|
+
if (decision?.kind !== "set") return undefined;
|
|
192
|
+
const wire = parsed.options.tierObservation?.fastWire ?? provider.fastWire ?? null;
|
|
193
|
+
if (wire?.kind !== "anthropic-speed") return undefined;
|
|
194
|
+
if (decision.value !== wire.canonicalToWire.priority) return undefined;
|
|
195
|
+
return { value: decision.value, betas: wire.betas?.length ? wire.betas : [ANTHROPIC_FAST_MODE_BETA] };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Feed a `usage.speed` echo ("fast" | "standard") to the attempt's tier observer. */
|
|
199
|
+
function observeAnthropicSpeed(usage: unknown, tierMetadata: AdapterTierMetadata | undefined): void {
|
|
200
|
+
if (!tierMetadata || !usage || typeof usage !== "object" || Array.isArray(usage)) return;
|
|
201
|
+
const speed = (usage as { speed?: unknown }).speed;
|
|
202
|
+
if (speed !== undefined) tierMetadata.observeResponseServiceTier(speed);
|
|
203
|
+
}
|
|
204
|
+
|
|
178
205
|
// ---------------------------------------------------------------------------
|
|
179
206
|
// Breakpoint cap enforcement — strip excess beyond the 4-breakpoint limit
|
|
180
207
|
// ---------------------------------------------------------------------------
|
|
@@ -1088,6 +1115,11 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
|
|
|
1088
1115
|
if (unresolvedPlaceholder) {
|
|
1089
1116
|
throw new Error(`anthropic baseUrl contains unresolved ${unresolvedPlaceholder}`);
|
|
1090
1117
|
}
|
|
1118
|
+
// Anthropic fast mode: `speed` is only accepted beside its beta; without it the API
|
|
1119
|
+
// answers 400 "speed: Extra inputs are not permitted". The beta is merged below, after
|
|
1120
|
+
// any header override, so a request never carries one without the other.
|
|
1121
|
+
const fastSpeed = anthropicFastSpeed(parsed, provider);
|
|
1122
|
+
if (fastSpeed) body.speed = fastSpeed.value;
|
|
1091
1123
|
const headers: Record<string, string> = {
|
|
1092
1124
|
"Content-Type": "application/json",
|
|
1093
1125
|
"anthropic-version": "2023-06-01",
|
|
@@ -1108,6 +1140,7 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
|
|
|
1108
1140
|
else headers["x-api-key"] = provider.apiKey;
|
|
1109
1141
|
}
|
|
1110
1142
|
if (provider.headers) Object.assign(headers, provider.headers);
|
|
1143
|
+
mergeAnthropicBetaHeader(headers, fastSpeed?.betas ?? []);
|
|
1111
1144
|
|
|
1112
1145
|
// Prompt caching: native Anthropic supports top-level automatic caching, which
|
|
1113
1146
|
// follows the moving final block across turns. Keep one breakpoint slot free for it.
|
|
@@ -1122,10 +1155,27 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
|
|
|
1122
1155
|
enforceCacheControlLimit(body, explicitLimit);
|
|
1123
1156
|
normalizeTtlOrdering(body);
|
|
1124
1157
|
|
|
1125
|
-
return {
|
|
1158
|
+
return {
|
|
1159
|
+
url,
|
|
1160
|
+
method: "POST",
|
|
1161
|
+
headers,
|
|
1162
|
+
body: JSON.stringify(body),
|
|
1163
|
+
// Adapter-owned: the registry wrapper would otherwise report the exact absence of a
|
|
1164
|
+
// tier field and mislabel every fast turn as downgraded.
|
|
1165
|
+
tierLog: createAdapterTierMetadata(
|
|
1166
|
+
parsed.options.tierObservation,
|
|
1167
|
+
parsed.options.tierDecision,
|
|
1168
|
+
fastSpeed ? "anthropic-speed" : null,
|
|
1169
|
+
fastSpeed?.value ?? null,
|
|
1170
|
+
),
|
|
1171
|
+
};
|
|
1126
1172
|
},
|
|
1127
1173
|
|
|
1128
|
-
async *parseStream(
|
|
1174
|
+
async *parseStream(
|
|
1175
|
+
response: Response,
|
|
1176
|
+
budget: TranslatorBudget,
|
|
1177
|
+
tierMetadata?: AdapterTierMetadata,
|
|
1178
|
+
): AsyncGenerator<AdapterEvent> {
|
|
1129
1179
|
if (!response.body) {
|
|
1130
1180
|
yield { type: "error", message: "No response body" };
|
|
1131
1181
|
return;
|
|
@@ -1199,10 +1249,12 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
|
|
|
1199
1249
|
case "message_start": {
|
|
1200
1250
|
const message = data.message as { usage?: unknown } | undefined;
|
|
1201
1251
|
pendingUsage = mergeAnthropicUsage(pendingUsage, message?.usage);
|
|
1252
|
+
// Fast mode reports the speed actually served here (`usage.speed`).
|
|
1253
|
+
observeAnthropicSpeed(message?.usage, tierMetadata);
|
|
1202
1254
|
break;
|
|
1203
1255
|
}
|
|
1204
1256
|
case "content_block_start": {
|
|
1205
|
-
const block = data.content_block as { type: string; id?: string; name?:
|
|
1257
|
+
const block = data.content_block as { type: string; id?: string; name?: unknown; data?: string; thinking?: string } | undefined;
|
|
1206
1258
|
if (!block) break;
|
|
1207
1259
|
currentBlockType = block.type;
|
|
1208
1260
|
if (block.type === "thinking") {
|
|
@@ -1212,7 +1264,7 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
|
|
|
1212
1264
|
}
|
|
1213
1265
|
if (block.type === "tool_use") {
|
|
1214
1266
|
currentToolCallId = usableToolUseId(block.id);
|
|
1215
|
-
currentToolCallName = toolNames.fromWire(block.name
|
|
1267
|
+
currentToolCallName = toolNames.fromWire(typeof block.name === "string" ? block.name : "");
|
|
1216
1268
|
currentToolCallJson = "";
|
|
1217
1269
|
budget.openCall(currentToolCallId);
|
|
1218
1270
|
yield { type: "tool_call_start", id: currentToolCallId, name: currentToolCallName };
|
|
@@ -1288,6 +1340,8 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
|
|
|
1288
1340
|
case "message_delta": {
|
|
1289
1341
|
const usage = data.usage;
|
|
1290
1342
|
pendingUsage = mergeAnthropicUsage(pendingUsage, usage);
|
|
1343
|
+
// Later frames win: a speed here (not seen live, but usage is cumulative) supersedes message_start.
|
|
1344
|
+
observeAnthropicSpeed(usage, tierMetadata);
|
|
1291
1345
|
const delta = data.delta as { stop_reason?: unknown } | undefined;
|
|
1292
1346
|
if (typeof delta?.stop_reason === "string") pendingStopReason = delta.stop_reason;
|
|
1293
1347
|
break;
|
|
@@ -1377,7 +1431,11 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
|
|
|
1377
1431
|
}
|
|
1378
1432
|
},
|
|
1379
1433
|
|
|
1380
|
-
async parseResponse(
|
|
1434
|
+
async parseResponse(
|
|
1435
|
+
response: Response,
|
|
1436
|
+
budget: TranslatorBudget,
|
|
1437
|
+
tierMetadata?: AdapterTierMetadata,
|
|
1438
|
+
): Promise<AdapterEvent[]> {
|
|
1381
1439
|
const parsed: unknown = await response.json();
|
|
1382
1440
|
// `response.json()` resolves a body of `null` to `null` without throwing, so the cast below
|
|
1383
1441
|
// used to reach `json.content` on it — the #1219 defect at the buffered body root. The
|
|
@@ -1426,7 +1484,7 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
|
|
|
1426
1484
|
}
|
|
1427
1485
|
}
|
|
1428
1486
|
}
|
|
1429
|
-
const content = rawContent as { type: string; text?: string; id?: string; name?:
|
|
1487
|
+
const content = rawContent as { type: string; text?: string; id?: string; name?: unknown; input?: unknown; thinking?: string; reasoning?: string; signature?: string; data?: string }[] | undefined;
|
|
1430
1488
|
if (content) {
|
|
1431
1489
|
for (const block of content) {
|
|
1432
1490
|
if (block.type === "text" && block.text) {
|
|
@@ -1442,13 +1500,14 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
|
|
|
1442
1500
|
events.push({ type: "redacted_thinking", data: block.data });
|
|
1443
1501
|
} else if (block.type === "tool_use") {
|
|
1444
1502
|
const id = usableToolUseId(block.id);
|
|
1445
|
-
events.push({ type: "tool_call_start", id, name: toolNames.fromWire(block.name
|
|
1503
|
+
events.push({ type: "tool_call_start", id, name: toolNames.fromWire(typeof block.name === "string" ? block.name : "") });
|
|
1446
1504
|
events.push({ type: "tool_call_delta", arguments: toolUseArguments(block.input, provider.anthropicEofTolerance === true) });
|
|
1447
1505
|
events.push({ type: "tool_call_end" });
|
|
1448
1506
|
}
|
|
1449
1507
|
}
|
|
1450
1508
|
}
|
|
1451
1509
|
const usage = json.usage as Record<string, number> | undefined;
|
|
1510
|
+
observeAnthropicSpeed(json.usage, tierMetadata);
|
|
1452
1511
|
const stopReason = typeof json.stop_reason === "string" ? json.stop_reason : undefined;
|
|
1453
1512
|
// An Anthropic-compatible upstream can forward an `error` stop reason verbatim. As a
|
|
1454
1513
|
// `done` it reads as a clean completion, so the turn reports success and — on a compaction
|
|
@@ -1,14 +1,45 @@
|
|
|
1
1
|
import type { AdapterEvent, OcxParsedRequest, OcxProviderConfig } from "../../types";
|
|
2
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
2
6
|
import type { AdapterRequest, ProviderAdapter } from "../base";
|
|
3
7
|
import { mapReasoningEffort } from "../../reasoning-effort";
|
|
4
8
|
import { buildSystemPrompt } from "../coding-agent/protocol";
|
|
5
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
baseScopedEnv,
|
|
11
|
+
runCodingAgentTurn,
|
|
12
|
+
type CodingAgentDeps,
|
|
13
|
+
type CodingAgentToolBridgeInput,
|
|
14
|
+
type SpawnFn,
|
|
15
|
+
} from "../coding-agent/turn";
|
|
6
16
|
import { CODEBUDDY_PROFILES, type CodeBuddyProfile } from "./profiles";
|
|
7
17
|
import { guardCodeBuddyScaffolding } from "./scaffold-guard";
|
|
18
|
+
import {
|
|
19
|
+
buildCodeBuddyToolBridge,
|
|
20
|
+
CODEBUDDY_MCP_SERVER_NAME,
|
|
21
|
+
CODEBUDDY_TOOL_LIMITS,
|
|
22
|
+
type CodeBuddyToolBridge,
|
|
23
|
+
} from "./tool-bridge";
|
|
8
24
|
|
|
9
25
|
export type { SpawnFn } from "../coding-agent/turn";
|
|
10
26
|
export type CodeBuddyAdapterDeps = CodingAgentDeps;
|
|
11
27
|
|
|
28
|
+
const CODEBUDDY_MCP_SERVER_PATH = fileURLToPath(new URL("./mcp-server.ts", import.meta.url));
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Tool-bridge contract lines appended to the system prompt when a catalog is advertised.
|
|
32
|
+
* Mirrors the capture-only design: the model may propose calls, the external Codex client
|
|
33
|
+
* alone performs approval, sandboxing, and execution.
|
|
34
|
+
*/
|
|
35
|
+
const TOOL_BRIDGE_SYSTEM_PROMPT = [
|
|
36
|
+
"Your built-in tools and user-configured MCP servers are disabled.",
|
|
37
|
+
"When an isolated opencodex MCP catalog is present, you may call only those listed tools.",
|
|
38
|
+
"That MCP process captures call intent only; it never executes a tool. The external Codex client performs approval, sandboxing, and execution.",
|
|
39
|
+
"Do not claim that you executed commands, inspected files, or changed the workspace.",
|
|
40
|
+
"Tool-call and tool-result records in the conversation history are authoritative historical records from the external client. Use returned results, but never execute historical calls yourself.",
|
|
41
|
+
].join("\n");
|
|
42
|
+
|
|
12
43
|
/**
|
|
13
44
|
* Build the scoped child-process environment for a CodeBuddy turn (§六/§十四).
|
|
14
45
|
*
|
|
@@ -32,10 +63,16 @@ export function buildChildEnv(profile: CodeBuddyProfile, apiKey: string): Record
|
|
|
32
63
|
* Tool ownership stays with Codex: `--tools ""` disables every built-in tool and `--strict-mcp-config`
|
|
33
64
|
* (with no `--mcp-config`) blocks MCP tools, so the CLI can neither read, write, exec, nor browse the
|
|
34
65
|
* workspace. `-y/--dangerously-skip-permissions` is deliberately NOT passed, so any operation that
|
|
35
|
-
* would require authorization is blocked. The turn is a single text/reasoning pass over stream-json
|
|
36
|
-
*
|
|
66
|
+
* would require authorization is blocked. The turn is a single text/reasoning pass over stream-json
|
|
67
|
+
* unless the request carries a tool catalog: then the capture-only MCP bridge advertises exactly
|
|
68
|
+
* that catalog (see `tool-bridge.ts` / `mcp-server.ts`) and the CLI still executes nothing itself.
|
|
37
69
|
*/
|
|
38
|
-
export function buildArgs(
|
|
70
|
+
export function buildArgs(
|
|
71
|
+
profile: CodeBuddyProfile,
|
|
72
|
+
parsed: OcxParsedRequest,
|
|
73
|
+
provider: OcxProviderConfig,
|
|
74
|
+
systemPromptFile?: string,
|
|
75
|
+
): string[] {
|
|
39
76
|
const args: string[] = [
|
|
40
77
|
"-p",
|
|
41
78
|
"--output-format", "stream-json",
|
|
@@ -50,8 +87,9 @@ export function buildArgs(profile: CodeBuddyProfile, parsed: OcxParsedRequest, p
|
|
|
50
87
|
];
|
|
51
88
|
const effort = mapReasoningEffort(provider, parsed.modelId, parsed.options.reasoning);
|
|
52
89
|
if (effort) args.push("--effort", effort);
|
|
53
|
-
|
|
54
|
-
|
|
90
|
+
// The vendor CLI documents no file-backed append flag, so the staged prompt replaces the default.
|
|
91
|
+
// That default targets interactive tool use, which this adapter disables end to end.
|
|
92
|
+
if (systemPromptFile) args.push("--system-prompt-file", systemPromptFile);
|
|
55
93
|
// profile is retained for symmetry with the region-isolated design and future per-region flags.
|
|
56
94
|
void profile;
|
|
57
95
|
return args;
|
|
@@ -71,16 +109,74 @@ export function createCodeBuddyAdapter(provider: OcxProviderConfig, deps: CodeBu
|
|
|
71
109
|
},
|
|
72
110
|
|
|
73
111
|
async runTurn(parsed, incoming, emit): Promise<void> {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
112
|
+
let toolBridge: CodeBuddyToolBridge;
|
|
113
|
+
try {
|
|
114
|
+
toolBridge = buildCodeBuddyToolBridge(parsed);
|
|
115
|
+
} catch (err) {
|
|
116
|
+
emit({
|
|
117
|
+
type: "error",
|
|
118
|
+
message: `Invalid CodeBuddy tool catalog: ${err instanceof Error ? err.message : String(err)}`,
|
|
119
|
+
status: 400,
|
|
120
|
+
errorType: "invalid_request_error",
|
|
121
|
+
code: "tool_catalog_invalid",
|
|
122
|
+
retryable: false,
|
|
123
|
+
});
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const bridgeInput: CodingAgentToolBridgeInput | undefined = toolBridge.tools.length > 0
|
|
127
|
+
? {
|
|
128
|
+
serverName: CODEBUDDY_MCP_SERVER_NAME,
|
|
129
|
+
serverModulePath: CODEBUDDY_MCP_SERVER_PATH,
|
|
130
|
+
tools: toolBridge.tools,
|
|
131
|
+
emittedNameMap: toolBridge.emittedNameMap,
|
|
132
|
+
maxTurnToolCalls: CODEBUDDY_TOOL_LIMITS.maxTurnToolCalls,
|
|
133
|
+
requireToolCall: toolBridge.requireToolCall,
|
|
134
|
+
}
|
|
135
|
+
: undefined;
|
|
136
|
+
// argv is world-readable via process listing, so the folded system+developer prompt —
|
|
137
|
+
// plus the tool-bridge directive when a catalog is advertised — is staged in a
|
|
138
|
+
// private temp file and passed by path instead of embedded in the arguments.
|
|
139
|
+
const system = buildSystemPrompt(parsed);
|
|
140
|
+
const systemParts: string[] = [];
|
|
141
|
+
if (system) systemParts.push(system);
|
|
142
|
+
if (toolBridge.tools.length > 0) systemParts.push(TOOL_BRIDGE_SYSTEM_PROMPT);
|
|
143
|
+
const staged = systemParts.length > 0 ? systemParts.join("\n\n") : undefined;
|
|
144
|
+
let promptDir: string | undefined;
|
|
145
|
+
let promptFile: string | undefined;
|
|
146
|
+
if (staged) {
|
|
147
|
+
try {
|
|
148
|
+
promptDir = await mkdtemp(join(tmpdir(), "ocx-codebuddy-prompt-"));
|
|
149
|
+
promptFile = join(promptDir, "system-prompt.txt");
|
|
150
|
+
await writeFile(promptFile, staged, { encoding: "utf8", mode: 0o600, flag: "wx" });
|
|
151
|
+
} catch {
|
|
152
|
+
if (promptDir) await rm(promptDir, { recursive: true, force: true }).catch(() => {});
|
|
153
|
+
emit({
|
|
154
|
+
type: "error",
|
|
155
|
+
message: "CodeBuddy system prompt could not be staged securely.",
|
|
156
|
+
status: 500,
|
|
157
|
+
errorType: "upstream_error",
|
|
158
|
+
code: "system_prompt_staging_failed",
|
|
159
|
+
retryable: false,
|
|
160
|
+
});
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
await runCodingAgentTurn({
|
|
167
|
+
profiles: CODEBUDDY_PROFILES,
|
|
168
|
+
provider,
|
|
169
|
+
parsed,
|
|
170
|
+
incoming,
|
|
171
|
+
emit: guardCodeBuddyScaffolding(emit),
|
|
172
|
+
...(bridgeInput ? { toolBridge: bridgeInput } : {}),
|
|
173
|
+
buildArgs: (resolved, req, prov) => buildArgs(resolved as CodeBuddyProfile, req, prov, promptFile),
|
|
174
|
+
buildEnv: (resolved, apiKey) => buildChildEnv(resolved as CodeBuddyProfile, apiKey),
|
|
175
|
+
deps,
|
|
176
|
+
});
|
|
177
|
+
} finally {
|
|
178
|
+
if (promptDir) await rm(promptDir, { recursive: true, force: true }).catch(() => {});
|
|
179
|
+
}
|
|
84
180
|
},
|
|
85
181
|
};
|
|
86
182
|
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Isolated MCP catalog used by the CodeBuddy adapter.
|
|
3
|
+
*
|
|
4
|
+
* This process advertises the current Codex tool schemas but deliberately never
|
|
5
|
+
* executes a call. The parent adapter captures CodeBuddy's completed `tool_use`
|
|
6
|
+
* frame, terminates this process tree, and returns the call to the Codex host,
|
|
7
|
+
* where the normal approval and sandbox boundary remains authoritative.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { open } from "node:fs/promises";
|
|
11
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
12
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
13
|
+
import {
|
|
14
|
+
CallToolRequestSchema,
|
|
15
|
+
ListToolsRequestSchema,
|
|
16
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
17
|
+
import { CODEBUDDY_TOOL_LIMITS } from "./tool-bridge";
|
|
18
|
+
|
|
19
|
+
interface ToolDefinition {
|
|
20
|
+
name: string;
|
|
21
|
+
description: string;
|
|
22
|
+
inputSchema: Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const MCP_TOOL_NAME_PATTERN = /^[A-Za-z0-9_-]{1,40}$/;
|
|
26
|
+
const INVALID_DESCRIPTION_CONTROL_PATTERN = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/u;
|
|
27
|
+
const textEncoder = new TextEncoder();
|
|
28
|
+
|
|
29
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
30
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function utf8Bytes(value: string): number {
|
|
34
|
+
return textEncoder.encode(value).byteLength;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function hasUnpairedSurrogate(value: string): boolean {
|
|
38
|
+
for (let index = 0; index < value.length; index++) {
|
|
39
|
+
const unit = value.charCodeAt(index);
|
|
40
|
+
if (unit >= 0xd800 && unit <= 0xdbff) {
|
|
41
|
+
const next = value.charCodeAt(index + 1);
|
|
42
|
+
if (next < 0xdc00 || next > 0xdfff) return true;
|
|
43
|
+
index += 1;
|
|
44
|
+
} else if (unit >= 0xdc00 && unit <= 0xdfff) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function readCatalogBounded(path: string): Promise<Buffer> {
|
|
52
|
+
const handle = await open(path, "r");
|
|
53
|
+
try {
|
|
54
|
+
const before = await handle.stat();
|
|
55
|
+
if (!before.isFile()) throw new Error("tool catalog must be a regular file");
|
|
56
|
+
if (before.size > CODEBUDDY_TOOL_LIMITS.maxCatalogBytes) {
|
|
57
|
+
throw new Error("tool catalog is too large");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Read at most limit + 1 from the already-open descriptor. The extra byte
|
|
61
|
+
// distinguishes an exact-limit file from a file that grew after fstat,
|
|
62
|
+
// without ever allocating or retaining an attacker-sized input.
|
|
63
|
+
const bytes = Buffer.allocUnsafe(CODEBUDDY_TOOL_LIMITS.maxCatalogBytes + 1);
|
|
64
|
+
let offset = 0;
|
|
65
|
+
while (offset < bytes.length) {
|
|
66
|
+
const result = await handle.read(bytes, offset, bytes.length - offset, offset);
|
|
67
|
+
if (result.bytesRead === 0) break;
|
|
68
|
+
offset += result.bytesRead;
|
|
69
|
+
}
|
|
70
|
+
if (offset > CODEBUDDY_TOOL_LIMITS.maxCatalogBytes) {
|
|
71
|
+
throw new Error("tool catalog is too large");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const after = await handle.stat();
|
|
75
|
+
if (
|
|
76
|
+
before.dev !== after.dev
|
|
77
|
+
|| before.ino !== after.ino
|
|
78
|
+
|| before.size !== after.size
|
|
79
|
+
|| before.mtimeMs !== after.mtimeMs
|
|
80
|
+
|| before.ctimeMs !== after.ctimeMs
|
|
81
|
+
|| after.size !== offset
|
|
82
|
+
) {
|
|
83
|
+
throw new Error("tool catalog changed while being read");
|
|
84
|
+
}
|
|
85
|
+
return bytes.subarray(0, offset);
|
|
86
|
+
} finally {
|
|
87
|
+
await handle.close();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function assertBoundedSchema(schema: Record<string, unknown>): void {
|
|
92
|
+
if (schema.type !== "object") throw new Error("tool input schema must have object type");
|
|
93
|
+
if (utf8Bytes(JSON.stringify(schema)) > CODEBUDDY_TOOL_LIMITS.maxSchemaBytes) {
|
|
94
|
+
throw new Error("tool input schema is too large");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
let nodes = 0;
|
|
98
|
+
const pending: Array<{ depth: number; value: unknown }> = [{ depth: 0, value: schema }];
|
|
99
|
+
while (pending.length > 0) {
|
|
100
|
+
const current = pending.pop()!;
|
|
101
|
+
nodes += 1;
|
|
102
|
+
if (nodes > CODEBUDDY_TOOL_LIMITS.maxSchemaNodes) {
|
|
103
|
+
throw new Error("tool input schema has too many nodes");
|
|
104
|
+
}
|
|
105
|
+
if (current.depth > CODEBUDDY_TOOL_LIMITS.maxSchemaDepth) {
|
|
106
|
+
throw new Error("tool input schema is too deeply nested");
|
|
107
|
+
}
|
|
108
|
+
if (Array.isArray(current.value)) {
|
|
109
|
+
for (const child of current.value) pending.push({ depth: current.depth + 1, value: child });
|
|
110
|
+
} else if (isRecord(current.value)) {
|
|
111
|
+
for (const child of Object.values(current.value)) {
|
|
112
|
+
pending.push({ depth: current.depth + 1, value: child });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function loadTools(path: string): Promise<ToolDefinition[]> {
|
|
119
|
+
const bytes = await readCatalogBounded(path);
|
|
120
|
+
const parsed: unknown = JSON.parse(bytes.toString("utf8"));
|
|
121
|
+
if (!Array.isArray(parsed)) throw new Error("tool catalog must be an array");
|
|
122
|
+
if (parsed.length > CODEBUDDY_TOOL_LIMITS.maxTools) {
|
|
123
|
+
throw new Error("tool catalog contains too many definitions");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const names = new Set<string>();
|
|
127
|
+
return parsed.map((value): ToolDefinition => {
|
|
128
|
+
if (
|
|
129
|
+
!isRecord(value)
|
|
130
|
+
|| typeof value.name !== "string"
|
|
131
|
+
|| !MCP_TOOL_NAME_PATTERN.test(value.name)
|
|
132
|
+
|| utf8Bytes(value.name) > CODEBUDDY_TOOL_LIMITS.maxNameBytes
|
|
133
|
+
|| typeof value.description !== "string"
|
|
134
|
+
|| value.description.length < 1
|
|
135
|
+
|| hasUnpairedSurrogate(value.description)
|
|
136
|
+
|| INVALID_DESCRIPTION_CONTROL_PATTERN.test(value.description)
|
|
137
|
+
|| utf8Bytes(value.description) > CODEBUDDY_TOOL_LIMITS.maxDescriptionBytes
|
|
138
|
+
|| !isRecord(value.inputSchema)
|
|
139
|
+
) {
|
|
140
|
+
throw new Error("tool catalog contains an invalid definition");
|
|
141
|
+
}
|
|
142
|
+
if (names.has(value.name)) throw new Error("tool catalog contains duplicate names");
|
|
143
|
+
names.add(value.name);
|
|
144
|
+
assertBoundedSchema(value.inputSchema);
|
|
145
|
+
const definition = {
|
|
146
|
+
name: value.name,
|
|
147
|
+
description: value.description,
|
|
148
|
+
inputSchema: value.inputSchema,
|
|
149
|
+
};
|
|
150
|
+
if (utf8Bytes(JSON.stringify(definition)) > CODEBUDDY_TOOL_LIMITS.maxToolBytes) {
|
|
151
|
+
throw new Error("tool catalog contains an oversized definition");
|
|
152
|
+
}
|
|
153
|
+
return definition;
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export async function runCodeBuddyMcpServer(catalogPath: string): Promise<void> {
|
|
158
|
+
if (!catalogPath) throw new Error("missing tool catalog");
|
|
159
|
+
// The pinned SDK does not detect stdin EOF itself. The capture server must exit when
|
|
160
|
+
// the parent terminates its CLI, including after a captured message_stop.
|
|
161
|
+
const exitOnStdinClose = (): void => process.exit(0);
|
|
162
|
+
process.stdin.on("end", exitOnStdinClose);
|
|
163
|
+
process.stdin.on("close", exitOnStdinClose);
|
|
164
|
+
|
|
165
|
+
const tools = await loadTools(catalogPath);
|
|
166
|
+
const advertisedNames = new Set(tools.map(tool => tool.name));
|
|
167
|
+
const server = new Server(
|
|
168
|
+
{ name: "opencodex-codebuddy-capture", version: "1.0.0" },
|
|
169
|
+
{ capabilities: { tools: {} } },
|
|
170
|
+
);
|
|
171
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
|
|
172
|
+
server.setRequestHandler(CallToolRequestSchema, async request => {
|
|
173
|
+
if (!advertisedNames.has(request.params.name)) throw new Error("unknown isolated tool");
|
|
174
|
+
// The external Codex client retains approval and execution ownership.
|
|
175
|
+
return await new Promise<never>(() => {});
|
|
176
|
+
});
|
|
177
|
+
await server.connect(new StdioServerTransport());
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (import.meta.main) await runCodeBuddyMcpServer(process.argv[2] ?? "");
|
|
@@ -28,9 +28,28 @@ interface ScanResult {
|
|
|
28
28
|
lineStart: boolean;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
// Probe with ASCII-only case folding rather than toLowerCase(): Unicode lowercasing can expand
|
|
32
|
+
// a code point (İ, ʼn, ligatures), shifting folded-text offsets away from `text` positions and
|
|
33
|
+
// silently disabling detection. `expected` must already be lowercase.
|
|
34
|
+
function asciiFold(code: number): number {
|
|
35
|
+
return code >= 0x41 && code <= 0x5a ? code + 0x20 : code;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function startsWithFolded(text: string, index: number, expected: string): boolean {
|
|
39
|
+
if (index + expected.length > text.length) return false;
|
|
40
|
+
for (let i = 0; i < expected.length; i++) {
|
|
41
|
+
if (asciiFold(text.charCodeAt(index + i)) !== expected.charCodeAt(i)) return false;
|
|
42
|
+
}
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
|
|
31
46
|
function prefixAtEnd(text: string, at: number, expected: string): boolean {
|
|
32
|
-
const
|
|
33
|
-
|
|
47
|
+
const remaining = text.length - at;
|
|
48
|
+
if (remaining > expected.length) return false;
|
|
49
|
+
for (let i = 0; i < remaining; i++) {
|
|
50
|
+
if (asciiFold(text.charCodeAt(at + i)) !== expected.charCodeAt(i)) return false;
|
|
51
|
+
}
|
|
52
|
+
return true;
|
|
34
53
|
}
|
|
35
54
|
|
|
36
55
|
/**
|
|
@@ -65,8 +84,7 @@ function scan(
|
|
|
65
84
|
}
|
|
66
85
|
|
|
67
86
|
if (!fence) {
|
|
68
|
-
|
|
69
|
-
if (lowered.startsWith(DSML_CALLS_LINE)) {
|
|
87
|
+
if (startsWithFolded(text, index, DSML_CALLS_LINE)) {
|
|
70
88
|
const afterCalls = index + DSML_CALLS_LINE.length;
|
|
71
89
|
let invokeAt = -1;
|
|
72
90
|
if (text[afterCalls] === "\n") invokeAt = afterCalls + 1;
|
|
@@ -76,12 +94,11 @@ function scan(
|
|
|
76
94
|
}
|
|
77
95
|
|
|
78
96
|
if (invokeAt >= 0) {
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
if (invokeRest.startsWith(DSML_INVOKE_PREFIX) && invokeNameStart && !/[\s"]/.test(invokeNameStart)) {
|
|
97
|
+
const invokeNameStart = text[invokeAt + DSML_INVOKE_PREFIX.length];
|
|
98
|
+
if (startsWithFolded(text, invokeAt, DSML_INVOKE_PREFIX) && invokeNameStart && !/[\s"]/.test(invokeNameStart)) {
|
|
82
99
|
return { safe: text.slice(0, index), held: "", fail: true, fence, lineStart };
|
|
83
100
|
}
|
|
84
|
-
if (
|
|
101
|
+
if (invokeAt === text.length || prefixAtEnd(text, invokeAt, DSML_INVOKE_PREFIX)) {
|
|
85
102
|
return { safe: text.slice(0, index), held: text.slice(index), fail: false, fence, lineStart };
|
|
86
103
|
}
|
|
87
104
|
}
|