@bitkyc08/opencodex 2.63.0 → 2.64.0-preview.20260923
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-D5eN1TID.js +50 -0
- package/gui/dist/assets/App-I5AnaSLh.css +1 -0
- package/gui/dist/assets/{Tray-CncKDBTp.js → Tray-Bh_ErQDh.js} +1 -1
- package/gui/dist/assets/index-BEq4OCOz.js +86 -0
- package/gui/dist/assets/index-DiBRuK-d.css +1 -0
- package/gui/dist/assets/usage-companion-chart-DBG_37kQ.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
|
@@ -146,9 +146,12 @@ const DEEPSEEK_PRICING = "https://api-docs.deepseek.com/quick_start/pricing-deta
|
|
|
146
146
|
* cache-write charge.
|
|
147
147
|
*/
|
|
148
148
|
const DEEPSEEK_V41_FLASH: Cost4 = { input: 0.3, output: 1.2, cacheRead: 0.006, cacheWrite: 0 };
|
|
149
|
-
//
|
|
150
|
-
|
|
151
|
-
|
|
149
|
+
// Historical K2 rows retain their 2026-07-20 verification; they do not price retargeted aliases.
|
|
150
|
+
const KIMI_PRICING = "https://platform.kimi.ai/docs/pricing (historical official table; cacheWrite derived from cache-miss input)";
|
|
151
|
+
// K3 publishes a separate cache-write price: $3/1M for the default 5-minute TTL.
|
|
152
|
+
// Coding presets share an endpoint/product, but these API reference estimates are not plan
|
|
153
|
+
// billing or quota: K3's 1M model consumes about twice the quota of k3-256k.
|
|
154
|
+
const KIMI_K3_PRICING = "https://platform.kimi.ai/docs/pricing/chat (API reference estimate; default 5-minute cache-write rate, not Code Plan billing/quota); https://www.kimi.com/code/docs/en/kimi-code/models.html";
|
|
152
155
|
// Z.AI publishes one USD table for the international surface; the Coding Plan
|
|
153
156
|
// subscription and the domestic bigmodel.cn endpoints bill differently
|
|
154
157
|
// (subscription quota / CNY tiers), so every GLM row below is verified-derived:
|
|
@@ -306,25 +309,31 @@ export const EXPECTED_PRICE_OVERLAYS: readonly ExpectedPriceOverlay[] = [
|
|
|
306
309
|
// Kimi / Moonshot — official price tables are now published (2026-07-20 re-check;
|
|
307
310
|
// previously empty). kimi = Kimi Code OAuth surface, moonshot = CN key surface,
|
|
308
311
|
// kimi-code = API key surface (expected list price, not actual billing).
|
|
309
|
-
{ provider: "kimi", modelId: "k3", cost4: KIMI_K3, source:
|
|
310
|
-
{ provider: "kimi", modelId: "k3[1m]", cost4: KIMI_K3, source: `derived:
|
|
312
|
+
{ provider: "kimi", modelId: "k3", cost4: KIMI_K3, source: KIMI_K3_PRICING, verifiedAt: "2026-09-23", status: "verified-derived" },
|
|
313
|
+
{ provider: "kimi", modelId: "k3[1m]", cost4: KIMI_K3, source: `derived: local alias sent as k3; ${KIMI_K3_PRICING}`, verifiedAt: "2026-09-23", status: "verified-derived" },
|
|
314
|
+
{ provider: "kimi", modelId: "k3-256k", cost4: KIMI_K3, source: `derived: K3 with a fixed 256K ceiling; ${KIMI_K3_PRICING}`, verifiedAt: "2026-09-23", status: "verified-derived" },
|
|
311
315
|
{ provider: "kimi", modelId: "kimi-k2.7-code", cost4: KIMI_K27_CODE, source: KIMI_PRICING, verifiedAt: "2026-07-20", status: "verified-derived" },
|
|
312
316
|
{ provider: "kimi", modelId: "kimi-k2.7-code-highspeed", cost4: KIMI_K27_CODE_HIGHSPEED, source: KIMI_PRICING, verifiedAt: "2026-07-20", status: "verified-derived" },
|
|
313
317
|
{ provider: "kimi", modelId: "kimi-k2.6", cost4: KIMI_K26, source: KIMI_PRICING, verifiedAt: "2026-07-20", status: "verified-derived" },
|
|
314
318
|
{ provider: "kimi", modelId: "kimi-k2.5", cost4: KIMI_K25, source: KIMI_PRICING, verifiedAt: "2026-07-20", status: "verified-derived" },
|
|
315
|
-
|
|
316
|
-
|
|
319
|
+
// kimi-for-coding now serves K2.8 Preview. No verified K2.8 price: keep all three
|
|
320
|
+
// Coding presets unknown rather than reusing the alias's retired K2.7 price.
|
|
321
|
+
{ provider: "moonshot", modelId: "kimi-k3", cost4: KIMI_K3, source: KIMI_K3_PRICING, verifiedAt: "2026-09-23", status: "verified-derived" },
|
|
317
322
|
{ provider: "moonshot", modelId: "kimi-k2.7-code", cost4: KIMI_K27_CODE, source: KIMI_PRICING, verifiedAt: "2026-07-20", status: "verified-derived" },
|
|
318
323
|
{ provider: "moonshot", modelId: "kimi-k2.7-code-highspeed", cost4: KIMI_K27_CODE_HIGHSPEED, source: KIMI_PRICING, verifiedAt: "2026-07-20", status: "verified-derived" },
|
|
319
324
|
{ provider: "moonshot", modelId: "kimi-k2.6", cost4: KIMI_K26, source: KIMI_PRICING, verifiedAt: "2026-07-20", status: "verified-derived" },
|
|
320
325
|
{ provider: "moonshot", modelId: "kimi-k2.5", cost4: KIMI_K25, source: KIMI_PRICING, verifiedAt: "2026-07-20", status: "verified-derived" },
|
|
321
|
-
{ provider: "kimi-code", modelId: "k3", cost4: KIMI_K3, source:
|
|
322
|
-
{ provider: "kimi-code", modelId: "k3[1m]", cost4: KIMI_K3, source: `derived: k3 ${
|
|
326
|
+
{ provider: "kimi-code", modelId: "k3", cost4: KIMI_K3, source: KIMI_K3_PRICING, verifiedAt: "2026-09-23", status: "verified-derived" },
|
|
327
|
+
{ provider: "kimi-code", modelId: "k3[1m]", cost4: KIMI_K3, source: `derived: local alias sent as k3; ${KIMI_K3_PRICING}`, verifiedAt: "2026-09-23", status: "verified-derived" },
|
|
328
|
+
{ provider: "kimi-code", modelId: "k3-256k", cost4: KIMI_K3, source: `derived: K3 with a fixed 256K ceiling; ${KIMI_K3_PRICING}`, verifiedAt: "2026-09-23", status: "verified-derived" },
|
|
323
329
|
{ provider: "kimi-code", modelId: "kimi-k2.7-code", cost4: KIMI_K27_CODE, source: KIMI_PRICING, verifiedAt: "2026-07-20", status: "verified-derived" },
|
|
324
330
|
{ provider: "kimi-code", modelId: "kimi-k2.7-code-highspeed", cost4: KIMI_K27_CODE_HIGHSPEED, source: KIMI_PRICING, verifiedAt: "2026-07-20", status: "verified-derived" },
|
|
325
331
|
{ provider: "kimi-code", modelId: "kimi-k2.6", cost4: KIMI_K26, source: KIMI_PRICING, verifiedAt: "2026-07-20", status: "verified-derived" },
|
|
326
332
|
{ provider: "kimi-code", modelId: "kimi-k2.5", cost4: KIMI_K25, source: KIMI_PRICING, verifiedAt: "2026-07-20", status: "verified-derived" },
|
|
327
|
-
|
|
333
|
+
// Responses reuses the kimi OAuth account and the same Coding endpoint/model seeds.
|
|
334
|
+
{ provider: "kimi-responses", modelId: "k3", cost4: KIMI_K3, source: KIMI_K3_PRICING, verifiedAt: "2026-09-23", status: "verified-derived" },
|
|
335
|
+
{ provider: "kimi-responses", modelId: "k3[1m]", cost4: KIMI_K3, source: `derived: local alias sent as k3; ${KIMI_K3_PRICING}`, verifiedAt: "2026-09-23", status: "verified-derived" },
|
|
336
|
+
{ provider: "kimi-responses", modelId: "k3-256k", cost4: KIMI_K3, source: `derived: K3 with a fixed 256K ceiling; ${KIMI_K3_PRICING}`, verifiedAt: "2026-09-23", status: "verified-derived" },
|
|
328
337
|
// Qwen3.8-Max: vendor-published input/output rate (verified). See QWEN38_MAX_PRICING
|
|
329
338
|
// for what that source does and does not cover.
|
|
330
339
|
{ provider: "alibaba-token-plan", modelId: "qwen3.8-max", cost4: QWEN38_MAX, source: QWEN38_MAX_PRICING, verifiedAt: "2026-08-04", status: "verified" },
|
|
@@ -440,6 +449,14 @@ export const VERIFIED_PRICE_OVERRIDES: readonly ExpectedPriceOverlay[] = [
|
|
|
440
449
|
verifiedAt: "2026-08-18",
|
|
441
450
|
status: "verified",
|
|
442
451
|
},
|
|
452
|
+
{
|
|
453
|
+
provider: "xai",
|
|
454
|
+
modelId: "grok-4.7",
|
|
455
|
+
cost4: { input: 2, output: 6, cacheRead: 0.5, cacheWrite: 0 },
|
|
456
|
+
source: "https://docs.x.ai/developers/models/grok-4.7",
|
|
457
|
+
verifiedAt: "2026-09-23",
|
|
458
|
+
status: "verified",
|
|
459
|
+
},
|
|
443
460
|
];
|
|
444
461
|
|
|
445
462
|
export function findVerifiedPriceOverride(
|
|
@@ -503,6 +520,7 @@ export interface PriorityPricingRule {
|
|
|
503
520
|
}
|
|
504
521
|
|
|
505
522
|
const XAI_PRIORITY_PRICING = "https://docs.x.ai/developers/advanced-api-usage/priority-processing";
|
|
523
|
+
const ANTHROPIC_FAST_PRICING = "https://platform.claude.com/docs/en/about-claude/pricing";
|
|
506
524
|
|
|
507
525
|
/**
|
|
508
526
|
* Exact provider/model priority premiums. Routed resellers never inherit a vendor rule merely
|
|
@@ -529,14 +547,25 @@ export const PRIORITY_PRICING_RULES: readonly PriorityPricingRule[] = [
|
|
|
529
547
|
source: "https://developers.openai.com/api/docs/pricing (derived from the virtual selection's base wire model)",
|
|
530
548
|
verifiedAt: "2026-09-05",
|
|
531
549
|
})),
|
|
532
|
-
...["grok-4.5", "grok-4.6"].map((modelId): PriorityPricingRule => ({
|
|
550
|
+
...["grok-4.5", "grok-4.6", "grok-4.7"].map((modelId): PriorityPricingRule => ({
|
|
533
551
|
provider: "xai",
|
|
534
552
|
modelId,
|
|
535
553
|
multiplier: 2,
|
|
536
554
|
requiresResponseConfirmation: true,
|
|
537
555
|
source: XAI_PRIORITY_PRICING,
|
|
538
|
-
verifiedAt: "2026-08-18",
|
|
556
|
+
verifiedAt: modelId === "grok-4.7" ? "2026-09-23" : "2026-08-18",
|
|
539
557
|
})),
|
|
558
|
+
// Claude Code subscription fast draws usage credits at API fast rates, so OAuth is included.
|
|
559
|
+
...["anthropic", "anthropic-apikey"].flatMap(provider =>
|
|
560
|
+
["claude-opus-5-5", "claude-opus-5", "claude-opus-4-8"].map((modelId): PriorityPricingRule => ({
|
|
561
|
+
provider,
|
|
562
|
+
modelId,
|
|
563
|
+
multiplier: 2,
|
|
564
|
+
requiresResponseConfirmation: true,
|
|
565
|
+
source: ANTHROPIC_FAST_PRICING,
|
|
566
|
+
verifiedAt: "2026-09-23",
|
|
567
|
+
})),
|
|
568
|
+
),
|
|
540
569
|
];
|
|
541
570
|
|
|
542
571
|
/** Exact provider/model priority-pricing lookup. */
|
|
@@ -662,6 +691,19 @@ export const CONTEXT_TIERS: readonly ContextTier[] = [
|
|
|
662
691
|
source: "https://docs.x.ai/developers/pricing",
|
|
663
692
|
verifiedAt: "2026-08-18",
|
|
664
693
|
},
|
|
694
|
+
{
|
|
695
|
+
// xAI documents the same whole-request >=200k band for grok-4.7;
|
|
696
|
+
// priority stacking remains a lower bound. See
|
|
697
|
+
// devlog/_plan/260923_grok47_parity/010_probe-evidence.md.
|
|
698
|
+
provider: "xai",
|
|
699
|
+
modelId: "grok-4.7",
|
|
700
|
+
thresholdInputTokens: 200_000,
|
|
701
|
+
inclusive: true,
|
|
702
|
+
multiplier: UNIFORM_DOUBLE,
|
|
703
|
+
confirmedPriorityRelation: "lower-bound",
|
|
704
|
+
source: "https://docs.x.ai/developers/models/grok-4.7",
|
|
705
|
+
verifiedAt: "2026-09-23",
|
|
706
|
+
},
|
|
665
707
|
...["minimax", "minimax-cn"].map((provider): ContextTier => ({
|
|
666
708
|
provider,
|
|
667
709
|
modelId: "MiniMax-M3",
|
package/src/usage/log.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { getConfigDir } from "../config";
|
|
|
5
5
|
import type { CodexAffinityMove, CodexAffinityReason } from "../codex/routing";
|
|
6
6
|
import { enforceAppOwnedMemoryBudget } from "../lib/app-owned-memory";
|
|
7
7
|
import { recordOwnedConfigPath } from "../lib/config-ownership";
|
|
8
|
-
import { sanitizeLogMetadataString } from "../lib/redact";
|
|
8
|
+
import { redactSecretString, sanitizeLogMetadataString } from "../lib/redact";
|
|
9
9
|
import { usageDisplayTotalTokens } from "./totals";
|
|
10
10
|
import { normalizeAttemptDeliverySummary } from "./attempt-delivery";
|
|
11
11
|
import {
|
|
@@ -222,8 +222,39 @@ export interface PersistedRequestSpend extends RequestSpendTotals {
|
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
const MAX_PERSISTED_MOVE_REASONS = 8;
|
|
225
|
+
// Model selectors are NOT length-bound at admission: configured and discovered
|
|
226
|
+
// model ids reach MODEL_DISCOVERY_MAX_MODEL_ID_LENGTH, and the wire `model`
|
|
227
|
+
// field is raw client input. Persisting a plain prefix would merge selectors
|
|
228
|
+
// that share it, so over-long selectors persist as prefix + a digest of the
|
|
229
|
+
// FULL selector — bounded, deterministic, and still exact-matchable.
|
|
230
|
+
const MAX_PERSISTED_REQUESTED_MODEL_LEN = 130;
|
|
231
|
+
const REQUESTED_MODEL_DIGEST_HEX_LEN = 16;
|
|
225
232
|
const LOGICAL_REQUEST_ID_RE = /^[A-Za-z0-9_.:-]{1,64}$/;
|
|
226
233
|
|
|
234
|
+
/**
|
|
235
|
+
* Persisted form of the wire model selector. Selectors within the bound persist
|
|
236
|
+
* verbatim; longer selectors persist as a prefix plus a short digest of the full
|
|
237
|
+
* value, so two distinct selectors that share the prefix never collapse into one
|
|
238
|
+
* persisted identity. Exact-match readers (`requested_model = ?`) must encode
|
|
239
|
+
* lookup input through this same function. Idempotent — encoded forms fit the
|
|
240
|
+
* bound — which matters because rows are normalized again on read.
|
|
241
|
+
*
|
|
242
|
+
* Idempotence has one cost: a literal selector that equals another selector's
|
|
243
|
+
* persisted form is indistinguishable from it, so both rows share one display
|
|
244
|
+
* value and one exact-match filter. Reaching that needs the caller to send the
|
|
245
|
+
* exact prefix-and-digest string; keeping them apart would need a separate
|
|
246
|
+
* full-selector digest column.
|
|
247
|
+
*/
|
|
248
|
+
export function encodePersistedRequestedModel(selector: string): string {
|
|
249
|
+
if (selector.length <= MAX_PERSISTED_REQUESTED_MODEL_LEN) return selector;
|
|
250
|
+
const digest = createHash("sha256")
|
|
251
|
+
.update(selector)
|
|
252
|
+
.digest("hex")
|
|
253
|
+
.slice(0, REQUESTED_MODEL_DIGEST_HEX_LEN);
|
|
254
|
+
const prefixLen = MAX_PERSISTED_REQUESTED_MODEL_LEN - REQUESTED_MODEL_DIGEST_HEX_LEN - 1;
|
|
255
|
+
return `${selector.slice(0, prefixLen)}~${digest}`;
|
|
256
|
+
}
|
|
257
|
+
|
|
227
258
|
export function isLogicalRequestId(value: unknown): value is string {
|
|
228
259
|
return typeof value === "string" && LOGICAL_REQUEST_ID_RE.test(value);
|
|
229
260
|
}
|
|
@@ -284,6 +315,10 @@ export interface PersistedUsageEntry {
|
|
|
284
315
|
/** Best-effort chat/session correlation for Logs grouping (#330). */
|
|
285
316
|
conversationId?: string;
|
|
286
317
|
resolvedModel?: string;
|
|
318
|
+
/** Model the upstream actually served (openai-model header or response body). */
|
|
319
|
+
servedModel?: string;
|
|
320
|
+
/** The exact model id sent upstream when it differs from the client-facing `model`. */
|
|
321
|
+
wireModel?: string;
|
|
287
322
|
requestedModel?: string;
|
|
288
323
|
/** Original bare helper model when the opt-in shadow-call route rewrote this request. */
|
|
289
324
|
shadowCallRewrittenFrom?: string;
|
|
@@ -806,6 +841,80 @@ function capMetadataString(s: string): string {
|
|
|
806
841
|
return s.length > MAX_METADATA_STRING_LEN ? s.slice(0, MAX_METADATA_STRING_LEN) : s;
|
|
807
842
|
}
|
|
808
843
|
|
|
844
|
+
const MAX_SERVED_MODEL_LENGTH = 200;
|
|
845
|
+
/**
|
|
846
|
+
* An upstream model is an identifier, never free-form text to truncate into one. A value the
|
|
847
|
+
* secret redactor would change is dropped rather than logged, because credential-shaped text can
|
|
848
|
+
* fit the identifier alphabet.
|
|
849
|
+
*/
|
|
850
|
+
export function sanitizeServedModel(value: unknown): string | undefined {
|
|
851
|
+
return typeof value === "string"
|
|
852
|
+
&& value.length <= MAX_SERVED_MODEL_LENGTH
|
|
853
|
+
&& /^[A-Za-z0-9._:/@+-]+$/.test(value)
|
|
854
|
+
&& redactSecretString(value) === value
|
|
855
|
+
? value
|
|
856
|
+
: undefined;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
/** The identity fields that decide whether a response model is ocx's own echo. */
|
|
860
|
+
export interface ServedModelEchoSource {
|
|
861
|
+
provider?: string;
|
|
862
|
+
model?: string;
|
|
863
|
+
wireModel?: string;
|
|
864
|
+
requestedAlias?: string;
|
|
865
|
+
requestedModel?: string;
|
|
866
|
+
/** Client-facing selector this proxy wrote into `response.model` (Anthropic routes keep it). */
|
|
867
|
+
responseModelEcho?: string;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
/**
|
|
871
|
+
* True when `served` is the client's own selector echoed back rather than a model the upstream
|
|
872
|
+
* reported. Anthropic routes answer with the Codex-facing selector (`anthropic/claude-opus-5-5`),
|
|
873
|
+
* and recording that as the served model painted every such row as rerouted. A value equal to
|
|
874
|
+
* the wire model is never an echo. On adapter paths the upstream's real model is not observable
|
|
875
|
+
* at all, so this only removes a false signal; passthrough `openai-model` observations are kept.
|
|
876
|
+
*/
|
|
877
|
+
export function isClientSelectorEcho(source: ServedModelEchoSource, served: string | undefined): boolean {
|
|
878
|
+
if (served === undefined) return false;
|
|
879
|
+
const wire = source.wireModel ?? source.model;
|
|
880
|
+
if (served === wire) return false;
|
|
881
|
+
return served === source.responseModelEcho
|
|
882
|
+
|| served === source.requestedAlias
|
|
883
|
+
|| served === source.requestedModel
|
|
884
|
+
|| (source.provider !== undefined && wire !== undefined && served === `${source.provider}/${wire}`);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
/** Record a response-body model as the served model when it is a real upstream observation. */
|
|
888
|
+
export function recordObservedServedModel(
|
|
889
|
+
target: ServedModelEchoSource & { servedModel?: string; resolvedModel?: string; preserveResolvedModelFromRoute?: boolean },
|
|
890
|
+
value: unknown,
|
|
891
|
+
): void {
|
|
892
|
+
const servedModel = sanitizeServedModel(value);
|
|
893
|
+
if (!servedModel || isClientSelectorEcho(target, servedModel)) return;
|
|
894
|
+
target.servedModel = servedModel;
|
|
895
|
+
if (!target.preserveResolvedModelFromRoute) target.resolvedModel = servedModel;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
/**
|
|
899
|
+
* Model identity fields for a log row. The served model is sanitized, and a resolvedModel that
|
|
900
|
+
* only echoed a dropped served model is dropped with it, so the rejected value cannot survive
|
|
901
|
+
* under the other name. A client-selector echo is dropped the same way, which also repairs
|
|
902
|
+
* rows persisted before the echo was filtered at capture.
|
|
903
|
+
*/
|
|
904
|
+
export function modelIdentityLogFields(source: ServedModelEchoSource & { resolvedModel?: string; servedModel?: unknown }): {
|
|
905
|
+
resolvedModel?: string; servedModel?: string; wireModel?: string;
|
|
906
|
+
} {
|
|
907
|
+
const sanitized = sanitizeServedModel(source.servedModel);
|
|
908
|
+
const servedModel = isClientSelectorEcho(source, sanitized) ? undefined : sanitized;
|
|
909
|
+
const resolvedModel = source.servedModel !== undefined && source.resolvedModel === source.servedModel && !servedModel
|
|
910
|
+
? undefined : source.resolvedModel;
|
|
911
|
+
return {
|
|
912
|
+
...(resolvedModel ? { resolvedModel } : {}),
|
|
913
|
+
...(servedModel ? { servedModel } : {}),
|
|
914
|
+
...(source.wireModel ? { wireModel: source.wireModel } : {}),
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
|
|
809
918
|
/** Test seam: the normalization branch old rows take is worth asserting directly. */
|
|
810
919
|
export function normalizeUsageEntryForTest(entry: PersistedUsageEntry): PersistedUsageEntry {
|
|
811
920
|
return normalizeUsageEntry(entry);
|
|
@@ -817,6 +926,7 @@ function normalizeUsageEntry(entry: PersistedUsageEntry): PersistedUsageEntry {
|
|
|
817
926
|
const callerServiceTier = sanitizeLogMetadataString(entry.callerServiceTier);
|
|
818
927
|
const responseServiceTier = sanitizeLogMetadataString(entry.responseServiceTier);
|
|
819
928
|
const shadowCallRewrittenFrom = sanitizeLogMetadataString(entry.shadowCallRewrittenFrom);
|
|
929
|
+
const { servedModel, resolvedModel } = modelIdentityLogFields(entry);
|
|
820
930
|
const claudeCompatibility = normalizeClaudeCompatibilityUsageLog(entry.claudeCompatibility);
|
|
821
931
|
const transportPhase = isKnownTransportPhase(entry.transportPhase) ? entry.transportPhase : undefined;
|
|
822
932
|
const terminalSource = isKnownTerminalSource(entry.terminalSource) ? entry.terminalSource : undefined;
|
|
@@ -855,8 +965,12 @@ function normalizeUsageEntry(entry: PersistedUsageEntry): PersistedUsageEntry {
|
|
|
855
965
|
...(typeof entry.conversationId === "string" && entry.conversationId.trim()
|
|
856
966
|
? { conversationId: entry.conversationId.trim().slice(0, 128) }
|
|
857
967
|
: {}),
|
|
858
|
-
...(
|
|
859
|
-
...(
|
|
968
|
+
...(resolvedModel ? { resolvedModel } : {}),
|
|
969
|
+
...(servedModel ? { servedModel } : {}),
|
|
970
|
+
...(entry.wireModel ? { wireModel: entry.wireModel } : {}),
|
|
971
|
+
...(typeof entry.requestedModel === "string" && entry.requestedModel
|
|
972
|
+
? { requestedModel: encodePersistedRequestedModel(entry.requestedModel) }
|
|
973
|
+
: {}),
|
|
860
974
|
...(shadowCallRewrittenFrom ? { shadowCallRewrittenFrom } : {}),
|
|
861
975
|
...(typeof entry.requestedEffort === "string" && entry.requestedEffort
|
|
862
976
|
? { requestedEffort: capMetadataString(entry.requestedEffort) }
|
|
@@ -36,6 +36,7 @@ export const ATTEMPT_RECOVERY_KIND_ROSTER = Object.freeze([
|
|
|
36
36
|
"opaque-blob-rejection",
|
|
37
37
|
"empty-completion",
|
|
38
38
|
"reasoning-effort-downgrade",
|
|
39
|
+
"anthropic-fast-downgrade",
|
|
39
40
|
] as const);
|
|
40
41
|
|
|
41
42
|
export type AttemptRecoveryKind = typeof ATTEMPT_RECOVERY_KIND_ROSTER[number];
|
package/src/usage/timeline.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { baseProviderLabel } from "../providers/label";
|
|
1
2
|
import { cacheTokensFromUsage, usageAttributions } from "./summary";
|
|
2
3
|
import type { PersistedUsageEntry } from "./log";
|
|
3
4
|
import { usageDisplayTotalTokens } from "./totals";
|
|
@@ -55,6 +56,30 @@ export function isTimelineModelId(value: unknown): value is string {
|
|
|
55
56
|
return typeof value === "string" && /^[^/\s]+\/\S+$/.test(value);
|
|
56
57
|
}
|
|
57
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Pool accounts log as `openai-p<hex6>` (and older rows as `openai-main`/`chatgpt`), so the raw
|
|
61
|
+
* provider would draw one line per account for the same model. The usage summary already folds these
|
|
62
|
+
* through `baseProviderLabel`; the timeline keys on the same label so both views agree.
|
|
63
|
+
*/
|
|
64
|
+
function timelineModelId(provider: string, model: string): string {
|
|
65
|
+
return `${baseProviderLabel(provider)}/${model}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** A saved selection may still name a pool account (`openai-p6bc633/gpt-5`); it selects the merged row. */
|
|
69
|
+
export function normalizeTimelineModelId(id: string): string {
|
|
70
|
+
const cut = id.indexOf("/");
|
|
71
|
+
return timelineModelId(id.slice(0, cut), id.slice(cut + 1));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* In account grouping the pool suffix is the account when no explicit label was stamped. Codex pool
|
|
76
|
+
* accounts and Anthropic OAuth accounts (`formatAnthropicProviderForLog`) both log that way.
|
|
77
|
+
*/
|
|
78
|
+
function poolAccountLabel(provider: string): string | undefined {
|
|
79
|
+
if (baseProviderLabel(provider) === provider) return undefined;
|
|
80
|
+
return provider.match(/-(main|p[a-f0-9]{6})$/)?.[1];
|
|
81
|
+
}
|
|
82
|
+
|
|
58
83
|
function parseModels(raw: string | null): string[] | null | { error: string } {
|
|
59
84
|
if (raw === null || raw.trim() === "") return null;
|
|
60
85
|
const models = raw.split(",").map(model => model.trim());
|
|
@@ -127,6 +152,7 @@ export function createTimelineAccumulator(query: TimelineQuery): { add(entry: Pe
|
|
|
127
152
|
const series = new Map<string, SeriesState>();
|
|
128
153
|
const availableModels = new Set<string>();
|
|
129
154
|
const hiddenProviders = new Set(query.hiddenProviders);
|
|
155
|
+
const selectedModels = query.models === null ? null : new Set(query.models.map(normalizeTimelineModelId));
|
|
130
156
|
let missingMeasurements = 0;
|
|
131
157
|
|
|
132
158
|
function add(entry: PersistedUsageEntry): void {
|
|
@@ -134,19 +160,21 @@ export function createTimelineAccumulator(query: TimelineQuery): { add(entry: Pe
|
|
|
134
160
|
const bucket = Math.floor((entry.timestamp - startMs) / (bucketSeconds * 1000));
|
|
135
161
|
if (bucket < 0 || bucket >= buckets) return;
|
|
136
162
|
for (const attribution of usageAttributions(entry)) {
|
|
137
|
-
|
|
138
|
-
|
|
163
|
+
const provider = baseProviderLabel(attribution.provider);
|
|
164
|
+
if (hiddenProviders.has(attribution.provider) || hiddenProviders.has(provider)) continue;
|
|
165
|
+
const modelId = timelineModelId(attribution.provider, attribution.model);
|
|
139
166
|
availableModels.add(modelId);
|
|
140
|
-
if (
|
|
167
|
+
if (selectedModels && !selectedModels.has(modelId)) continue;
|
|
168
|
+
const accountLogLabel = attribution.accountLogLabel ?? poolAccountLabel(attribution.provider) ?? "unknown";
|
|
141
169
|
const id = query.grouping === "model"
|
|
142
170
|
? modelId
|
|
143
|
-
: `${modelId} · ${
|
|
171
|
+
: `${modelId} · ${accountLogLabel}`;
|
|
144
172
|
let state = series.get(id);
|
|
145
173
|
if (!state) {
|
|
146
174
|
state = {
|
|
147
|
-
provider
|
|
175
|
+
provider,
|
|
148
176
|
model: attribution.model,
|
|
149
|
-
...(query.grouping === "modelAccount" ? { accountLogLabel
|
|
177
|
+
...(query.grouping === "modelAccount" ? { accountLogLabel } : {}),
|
|
150
178
|
points: Array<number>(buckets).fill(0),
|
|
151
179
|
requests: new Map(),
|
|
152
180
|
};
|
|
@@ -22,9 +22,9 @@ import { statSync } from "node:fs";
|
|
|
22
22
|
|
|
23
23
|
import { getConfigPath, readConfigDiagnostics } from "../config";
|
|
24
24
|
import type { OcxConfig, OcxProviderConfig } from "../types";
|
|
25
|
+
import { refreshConfigDerivedRegistries } from "../config/derived-registries";
|
|
25
26
|
import {
|
|
26
27
|
refreshPreservedProviderOwner,
|
|
27
|
-
refreshUserCostOverlays,
|
|
28
28
|
registerPreservedProviderOwner,
|
|
29
29
|
setPreservedDiskOnlyProviders,
|
|
30
30
|
unregisterPreservedProviderOwner,
|
|
@@ -169,7 +169,7 @@ export function reconcileUserCostOverlaysFromDisk(liveConfig?: OcxConfig | null)
|
|
|
169
169
|
}
|
|
170
170
|
// Refresh from the DISK config: overlays for providers only added by the
|
|
171
171
|
// external edit are display-only and must still resolve for historical rows.
|
|
172
|
-
|
|
172
|
+
refreshConfigDerivedRegistries(disk);
|
|
173
173
|
return true;
|
|
174
174
|
}
|
|
175
175
|
|
|
@@ -190,7 +190,7 @@ function reconcileForOwners(): void {
|
|
|
190
190
|
// cache resurrect providers that were intentionally removed.
|
|
191
191
|
setPreservedDiskOnlyProviders(null);
|
|
192
192
|
}
|
|
193
|
-
|
|
193
|
+
refreshConfigDerivedRegistries(disk);
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
/** Test-only observation that proves the timer actually read an invalid config fallback. */
|
package/src/vision/reasoning.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SUPPORTED_NATIVE_OPENAI_SLUGS, nativeReasoningEfforts } from "../codex/catalog/metadata";
|
|
2
2
|
import {
|
|
3
3
|
VISION_REASONING_EFFORTS,
|
|
4
4
|
sanitizeVisionReasoning,
|
|
@@ -13,15 +13,13 @@ declare module "../types" {
|
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
const NATIVE_VISION_MODELS = new Set(NATIVE_OPENAI_MODELS);
|
|
17
|
-
|
|
18
16
|
/**
|
|
19
17
|
* Return the known vision reasoning ladder for a native OpenAI model.
|
|
20
18
|
* Unknown/custom models deliberately return undefined so callers stay permissive when reliable
|
|
21
19
|
* capability metadata is unavailable.
|
|
22
20
|
*/
|
|
23
21
|
export function nativeVisionReasoningEfforts(modelId: string): VisionReasoningEffort[] | undefined {
|
|
24
|
-
if (!
|
|
22
|
+
if (!SUPPORTED_NATIVE_OPENAI_SLUGS.has(modelId)) return undefined;
|
|
25
23
|
const advertised = new Set(nativeReasoningEfforts(modelId));
|
|
26
24
|
const supported = VISION_REASONING_EFFORTS.filter(effort => advertised.has(effort));
|
|
27
25
|
return supported.length > 0 ? [...supported] : undefined;
|
|
@@ -14,6 +14,7 @@ import type { OcxProviderConfig } from "../types";
|
|
|
14
14
|
import { getValidAccessToken, publicOAuthAuthenticationErrorMessage } from "../oauth";
|
|
15
15
|
import { applyUpstreamRecoveryInit, fetchWithResetRetry } from "../lib/upstream-retry";
|
|
16
16
|
import { cancelBodyOnAbort, signalWithTimeout } from "../lib/abort";
|
|
17
|
+
import { readBoundedResponseBytes } from "../lib/bounded-body";
|
|
17
18
|
import { sidecarEnter } from "../lib/sidecar-tracker";
|
|
18
19
|
import { redactSecretString } from "../lib/redact";
|
|
19
20
|
import { MAX_SIDECAR_RESPONSE_BYTES, type WebSearchSource } from "./parse";
|
|
@@ -114,10 +115,19 @@ export async function runXaiWebSearch(
|
|
|
114
115
|
);
|
|
115
116
|
const detachBodyGuard = cancelBodyOnAbort(res.body, linkedSignal.signal);
|
|
116
117
|
if (!res.ok) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
try {
|
|
119
|
+
const bounded = await readBoundedResponseBytes(res, {
|
|
120
|
+
maxBytes: MAX_SIDECAR_RESPONSE_BYTES,
|
|
121
|
+
signal: linkedSignal.signal,
|
|
122
|
+
});
|
|
123
|
+
const detail = bounded.oversized
|
|
124
|
+
? "response body exceeded byte bound"
|
|
125
|
+
: redactSecretString(new TextDecoder().decode(bounded.bytes).slice(0, 200));
|
|
126
|
+
const entitlement = res.status === 401 || res.status === 403 ? " (Grok OAuth entitlement — re-run ocx login xai?)" : "";
|
|
127
|
+
return { text: "", sources: [], error: `xai sidecar HTTP ${res.status}${entitlement}: ${detail}` };
|
|
128
|
+
} finally {
|
|
129
|
+
detachBodyGuard();
|
|
130
|
+
}
|
|
121
131
|
}
|
|
122
132
|
try {
|
|
123
133
|
return await parseXaiResponsesSSE(res);
|