@bitkyc08/opencodex 2.10.0 → 2.10.1
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 +77 -0
- package/README.md +4 -10
- package/bin/ocx.mjs +71 -18
- package/gui/dist/assets/index-Cd6_PBKn.css +1 -0
- package/gui/dist/assets/index-ChZQsmBY.js +70 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/alibaba-color.svg +1 -1
- package/gui/dist/provider-icons/antigravity-color.svg +1 -1
- package/gui/dist/provider-icons/claude-color.svg +1 -1
- package/gui/dist/provider-icons/cline-color.svg +16 -0
- package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/copilot-color.svg +1 -1
- package/gui/dist/provider-icons/cursor-color.svg +1 -1
- package/gui/dist/provider-icons/deepseek-color.svg +1 -1
- package/gui/dist/provider-icons/firepass-color.svg +1 -1
- package/gui/dist/provider-icons/fireworks-color.svg +1 -1
- package/gui/dist/provider-icons/gemini-color.svg +1 -1
- package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
- package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
- package/gui/dist/provider-icons/grok.svg +1 -1
- package/gui/dist/provider-icons/groq-color.svg +1 -1
- package/gui/dist/provider-icons/huggingface-color.svg +1 -1
- package/gui/dist/provider-icons/kimi-color.svg +1 -1
- package/gui/dist/provider-icons/kiro-color.svg +2 -2
- package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
- package/gui/dist/provider-icons/mistral-color.svg +1 -1
- package/gui/dist/provider-icons/moonshot-color.svg +1 -1
- package/gui/dist/provider-icons/nvidia-color.svg +1 -1
- package/gui/dist/provider-icons/ollama-color.svg +1 -1
- package/gui/dist/provider-icons/openai.svg +1 -1
- package/gui/dist/provider-icons/opencode.svg +2 -1
- package/gui/dist/provider-icons/openrouter-color.svg +1 -1
- package/gui/dist/provider-icons/pi.svg +2 -2
- package/gui/dist/provider-icons/qianfan-color.svg +1 -1
- package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
- package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/vllm-color.svg +1 -1
- package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
- package/package.json +8 -4
- package/src/adapters/anthropic.ts +208 -14
- package/src/adapters/base.ts +16 -5
- package/src/adapters/cursor/effort-map.ts +3 -2
- package/src/adapters/cursor/framing.ts +39 -0
- package/src/adapters/cursor/live-transport.ts +105 -95
- package/src/adapters/cursor/native-exec.ts +32 -6
- package/src/adapters/cursor/protobuf-request.ts +20 -15
- package/src/adapters/cursor/request-builder.ts +21 -7
- package/src/adapters/cursor/types.ts +7 -0
- package/src/adapters/google-antigravity-replay.ts +237 -21
- package/src/adapters/google-truncation.ts +11 -0
- package/src/adapters/google.ts +50 -9
- package/src/adapters/identity.ts +39 -6
- package/src/adapters/kiro-errors.ts +11 -0
- package/src/adapters/kiro-events.ts +19 -1
- package/src/adapters/kiro-thinking.ts +10 -2
- package/src/adapters/kiro-tools.ts +10 -1
- package/src/adapters/kiro.ts +37 -11
- package/src/adapters/openai-chat.ts +284 -83
- package/src/adapters/openai-responses.ts +182 -24
- package/src/bridge.ts +177 -7
- package/src/chat/outbound.ts +78 -23
- package/src/claude/agents-inject.ts +27 -5
- package/src/claude/inbound.ts +11 -1
- package/src/claude/model-info.ts +13 -10
- package/src/claude/outbound.ts +17 -0
- package/src/cli/account-api.ts +24 -0
- package/src/cli/account-auth.ts +31 -6
- package/src/cli/account-main.ts +317 -0
- package/src/cli/account.ts +5 -0
- package/src/cli/claude.ts +2 -1
- package/src/cli/doctor.ts +93 -22
- package/src/cli/export-command.ts +26 -12
- package/src/cli/help.ts +8 -6
- package/src/cli/index.ts +56 -22
- package/src/cli/integrations.ts +84 -1
- package/src/cli/observe.ts +54 -1
- package/src/cli/opencode.ts +2 -1
- package/src/cli/provider-runtime.ts +18 -1
- package/src/cli/route-policy.ts +92 -0
- package/src/cli/runtime-api.ts +6 -3
- package/src/cli/star-prompt.ts +71 -15
- package/src/cli/status.ts +1 -1
- package/src/cli/v2.ts +36 -9
- package/src/clients/config-export.ts +687 -10
- package/src/codex/account-lifecycle.ts +30 -5
- package/src/codex/account-usability.ts +22 -2
- package/src/codex/admission.ts +255 -0
- package/src/codex/auth-api.ts +427 -140
- package/src/codex/auth-context.ts +155 -30
- package/src/codex/autostart-health.ts +8 -1
- package/src/codex/catalog/account-models.ts +62 -0
- package/src/codex/catalog/aggregation.ts +14 -1
- package/src/codex/catalog/bundled.ts +282 -32
- package/src/codex/catalog/filesystem-evidence.ts +302 -0
- package/src/codex/catalog/metadata.ts +51 -6
- package/src/codex/catalog/parsing.ts +6 -3
- package/src/codex/catalog/provider-fetch.ts +576 -41
- package/src/codex/catalog/sync.ts +505 -66
- package/src/codex/catalog-admission.ts +197 -0
- package/src/codex/catalog-write-serialization.ts +241 -0
- package/src/codex/catalog.ts +2 -1
- package/src/codex/codex-write-lock.ts +372 -0
- package/src/codex/convergence-types.ts +593 -0
- package/src/codex/convergence.ts +441 -0
- package/src/codex/desired-state.ts +177 -0
- package/src/codex/features.ts +52 -8
- package/src/codex/generation.ts +202 -0
- package/src/codex/history-job.ts +257 -0
- package/src/codex/history-lock.ts +241 -0
- package/src/codex/history-migration-guardian.ts +18 -5
- package/src/codex/history-provider.ts +9 -2
- package/src/codex/history-transition.ts +105 -0
- package/src/codex/history-worker.ts +176 -0
- package/src/codex/inject-coordination.ts +245 -0
- package/src/codex/inject.ts +605 -124
- package/src/codex/integration-record.ts +266 -0
- package/src/codex/internal/catalog-writer.ts +203 -0
- package/src/codex/internal/history-writer.ts +80 -0
- package/src/codex/journal.ts +10 -1
- package/src/codex/main-account-cache.ts +24 -0
- package/src/codex/management-convergence.ts +114 -0
- package/src/codex/native-main-admission.ts +47 -0
- package/src/codex/native-main-auth-temp.ts +187 -0
- package/src/codex/native-main-claim.ts +167 -0
- package/src/codex/native-main-lock-file.ts +158 -0
- package/src/codex/native-main-owner.ts +315 -0
- package/src/codex/native-profile-api.ts +247 -0
- package/src/codex/native-profile-manager.ts +1512 -0
- package/src/codex/native-profile-processes.ts +121 -0
- package/src/codex/native-profile-recovery.ts +99 -0
- package/src/codex/native-profile-stage-store.ts +387 -0
- package/src/codex/native-profile-startup.ts +340 -0
- package/src/codex/native-profile-store.ts +855 -0
- package/src/codex/native-profile-types.ts +120 -0
- package/src/codex/native-residue.ts +557 -0
- package/src/codex/project-config-warnings.ts +18 -4
- package/src/codex/prompt-journal.ts +311 -0
- package/src/codex/prompt-layers.ts +967 -0
- package/src/codex/prompt-lock.ts +143 -0
- package/src/codex/quota-rejection.ts +224 -0
- package/src/codex/quota.ts +86 -3
- package/src/codex/routing.ts +299 -62
- package/src/codex/runtime.ts +159 -38
- package/src/codex/shim.ts +39 -13
- package/src/codex/subagent-model-fallback.ts +73 -12
- package/src/codex/transition-state.ts +604 -0
- package/src/codex/upstream-host-health.ts +70 -0
- package/src/codex/user-identity.ts +266 -0
- package/src/codex/write-coordination.ts +114 -0
- package/src/config.ts +562 -26
- package/src/generated/jawcode-model-metadata.ts +2 -2
- package/src/grok/inject.ts +15 -4
- package/src/grok/inspect.ts +45 -0
- package/src/images/loop.ts +113 -20
- package/src/integrations/config-io.ts +151 -0
- package/src/integrations/journal.ts +315 -0
- package/src/integrations/merge.ts +135 -0
- package/src/integrations/native/ownership-preflight.ts +165 -0
- package/src/integrations/ownership.ts +111 -0
- package/src/integrations/registry.ts +101 -0
- package/src/integrations/serialize.ts +235 -0
- package/src/integrations/state.ts +290 -0
- package/src/integrations/store.ts +103 -0
- package/src/integrations/writer.ts +492 -0
- package/src/lib/bounded-body.ts +46 -8
- package/src/lib/bun-runtime.ts +110 -1
- package/src/lib/bun-stream-caps.ts +2 -1
- package/src/lib/redact.ts +407 -2
- package/src/lib/shadow-call.ts +24 -0
- package/src/lib/translator-budget.ts +10 -0
- package/src/lib/upstream-reachability.ts +91 -0
- package/src/lib/upstream-retry.ts +154 -2
- package/src/lib/windows-secret-acl.ts +212 -11
- package/src/lib/winsw.ts +9 -3
- package/src/oauth/index.ts +61 -3
- package/src/oauth/key-providers.ts +4 -0
- package/src/oauth/kiro.ts +50 -6
- package/src/oauth/store.ts +31 -6
- package/src/oauth/token-guardian.ts +9 -3
- package/src/providers/codex-capacity.ts +288 -0
- package/src/providers/derive.ts +33 -1
- package/src/providers/free-directory.ts +3 -1
- package/src/providers/key-failover.ts +71 -3
- package/src/providers/openai-sidecar.ts +64 -4
- package/src/providers/openai-virtual-models.ts +1 -0
- package/src/providers/quota.ts +334 -26
- package/src/providers/registry.ts +284 -16
- package/src/providers/xai-transport.ts +11 -4
- package/src/responses/compaction.ts +8 -1
- package/src/responses/hosted-tool-policy.ts +9 -0
- package/src/responses/parser.ts +44 -2
- package/src/responses/reasoning-envelope.ts +9 -1
- package/src/responses/reasoning-replay-cache.ts +105 -0
- package/src/responses/spill-store.ts +45 -8
- package/src/responses/state.ts +161 -17
- package/src/router.ts +302 -16
- package/src/routing/analytics.ts +377 -0
- package/src/routing/capability.ts +204 -0
- package/src/routing/cost.ts +77 -0
- package/src/routing/evaluator.ts +444 -0
- package/src/routing/health.ts +401 -0
- package/src/routing/history/cursor.ts +43 -0
- package/src/routing/history/indexer.ts +590 -0
- package/src/routing/history/schema.ts +72 -0
- package/src/routing/profile.ts +423 -0
- package/src/routing/quota.ts +124 -0
- package/src/routing/request-evidence.ts +45 -0
- package/src/routing/trace.ts +686 -0
- package/src/server/auth-cors.ts +46 -6
- package/src/server/chat-completions.ts +28 -13
- package/src/server/claude-messages.ts +23 -15
- package/src/server/gui-static.ts +39 -10
- package/src/server/images.ts +10 -1
- package/src/server/index.ts +238 -52
- package/src/server/lifecycle.ts +228 -9
- package/src/server/live.ts +14 -3
- package/src/server/management/agent-settings-routes.ts +64 -14
- package/src/server/management/combo-routes.ts +5 -5
- package/src/server/management/config-routes.ts +1 -1
- package/src/server/management/context.ts +42 -2
- package/src/server/management/integration-routes.ts +538 -0
- package/src/server/management/logs-usage-routes.ts +1 -1
- package/src/server/management/model-routes.ts +32 -113
- package/src/server/management/model-rows.ts +117 -0
- package/src/server/management/native-integration-routes.ts +587 -0
- package/src/server/management/oauth-account-routes.ts +1 -1
- package/src/server/management/provider-routes.ts +218 -117
- package/src/server/management/request-history-routes.ts +191 -0
- package/src/server/management/routing-analytics-routes.ts +74 -0
- package/src/server/management/routing-profile-routes.ts +177 -0
- package/src/server/management/shared.ts +2 -2
- package/src/server/management/sidebar-routes.ts +47 -31
- package/src/server/management/sync-response.ts +69 -0
- package/src/server/management/system-restart.ts +276 -24
- package/src/server/management/system-routes.ts +4 -0
- package/src/server/management-api.ts +84 -9
- package/src/server/management-auth.ts +43 -5
- package/src/server/relay-eager.ts +82 -42
- package/src/server/relay.ts +120 -6
- package/src/server/request-log.ts +26 -6
- package/src/server/responses/collaboration.ts +63 -8
- package/src/server/responses/compact.ts +272 -41
- package/src/server/responses/core.ts +730 -132
- package/src/server/responses/fetch-helpers.ts +15 -1
- package/src/server/responses-item-id-repair.ts +32 -3
- package/src/server/responses-json-events.ts +52 -0
- package/src/server/responses-snapshot-repair.ts +621 -0
- package/src/server/search.ts +51 -6
- package/src/server/sse-payload-rewrite.ts +89 -12
- package/src/server/startup-health-cache.ts +7 -1
- package/src/server/ws-bridge.ts +11 -17
- package/src/service-manager-probe.ts +297 -0
- package/src/service.ts +222 -32
- package/src/tray/windows-tray.ps1 +9 -0
- package/src/tray/windows.ts +15 -7
- package/src/types.ts +194 -14
- package/src/update/index.ts +13 -13
- package/src/update/job.ts +24 -21
- package/src/update/notify.ts +7 -3
- package/src/usage/cost.ts +0 -0
- package/src/usage/expected-prices.ts +129 -10
- package/src/usage/log.ts +50 -15
- package/src/usage/summary.ts +4 -4
- package/src/vision/index.ts +6 -1
- package/src/web-search/loop.ts +161 -34
- package/gui/dist/assets/index-OY43ubAq.css +0 -1
- package/gui/dist/assets/index-YwNnKZcL.js +0 -67
- package/gui/dist/provider-icons/antigravity.svg +0 -1
- package/gui/dist/provider-icons/claude.svg +0 -1
- package/gui/dist/provider-icons/copilot.svg +0 -1
- package/gui/dist/provider-icons/cursor.svg +0 -2
- package/gui/dist/provider-icons/gemini.svg +0 -1
- package/gui/dist/provider-icons/grok-color.svg +0 -1
- package/gui/dist/provider-icons/kiro.svg +0 -14
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import http2 from "node:http2";
|
|
2
2
|
import { create, fromBinary, toBinary } from "@bufbuild/protobuf";
|
|
3
3
|
import { namespacedToolName, type OcxProviderConfig, type OcxUsage } from "../../types";
|
|
4
|
-
import { CONNECT_FLAG_END_STREAM,
|
|
4
|
+
import { CONNECT_FLAG_END_STREAM, ConnectFrameError, consumeConnectFrames, encodeConnectFrame } from "./framing";
|
|
5
5
|
import {
|
|
6
6
|
CURSOR_MAX_EFFECTIVE_CONNECT_PAYLOAD_BYTES,
|
|
7
7
|
CURSOR_MAX_CONNECT_FRAME_BYTES,
|
|
@@ -787,6 +787,7 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
787
787
|
clearTimer: () => this.clearFirstFrameTimer(),
|
|
788
788
|
});
|
|
789
789
|
const failAndClear = (error: Error) => {
|
|
790
|
+
releaseBacklogLease();
|
|
790
791
|
if (this.expectedClose) {
|
|
791
792
|
// We already emitted a terminal `done` and cancelled the run (client-tool suspension). The
|
|
792
793
|
// RST_STREAM CANCEL surfaces here as a stream error/abort; it is expected, not a failure.
|
|
@@ -822,28 +823,53 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
822
823
|
// close() waits for in-flight frames; a dead socket can ignore it — force-destroy shortly
|
|
823
824
|
// after so a stalled TLS session cannot linger past the timeout.
|
|
824
825
|
armTimeoutDestroyFallback(stream, session, this.input.timeoutDestroyGraceMs ?? CURSOR_TIMEOUT_DESTROY_GRACE_MS);
|
|
826
|
+
releaseBacklogLease();
|
|
825
827
|
settler.settleFail(new Error("Cursor transport timed out before first response"));
|
|
826
828
|
}, this.input.firstFrameTimeoutMs ?? CURSOR_FIRST_FRAME_TIMEOUT_MS);
|
|
827
829
|
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
830
|
+
// Raw Connect backlog with a parse cursor: appends copy only the incoming
|
|
831
|
+
// chunk (amortized capacity growth), the consumed prefix is reclaimed
|
|
832
|
+
// lazily, and the RAW used length (headers included) is what the 32 MiB
|
|
833
|
+
// transport cap bounds — payload-only accounting let tiny-frame/header
|
|
834
|
+
// floods slip through.
|
|
835
|
+
let backlog = new Uint8Array();
|
|
836
|
+
let backlogStart = 0;
|
|
837
|
+
let backlogEnd = 0;
|
|
838
|
+
const BACKLOG_COMPACT_MIN_SAVINGS = 64 * 1024;
|
|
839
|
+
const appendBacklog = (chunk: Uint8Array): void => {
|
|
840
|
+
const used = backlogEnd - backlogStart;
|
|
841
|
+
let start = backlogStart;
|
|
842
|
+
let end = backlogEnd;
|
|
843
|
+
// Reclaim the consumed prefix when it is large or needed for capacity.
|
|
844
|
+
if (start > 0 && (start >= BACKLOG_COMPACT_MIN_SAVINGS || end + chunk.byteLength > backlog.byteLength)) {
|
|
845
|
+
backlog = backlog.slice(start, end);
|
|
846
|
+
start = 0;
|
|
847
|
+
end = used;
|
|
848
|
+
}
|
|
849
|
+
if (end + chunk.byteLength > backlog.byteLength) {
|
|
850
|
+
const capacity = Math.max(8192, backlog.byteLength * 2, end + chunk.byteLength);
|
|
851
|
+
const next = new Uint8Array(Math.min(CURSOR_TRANSPORT_MAX_BUFFERED_BYTES, capacity));
|
|
852
|
+
next.set(backlog.subarray(start, end), 0);
|
|
853
|
+
backlog = next;
|
|
833
854
|
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
855
|
+
backlog.set(chunk, end);
|
|
856
|
+
backlogStart = start;
|
|
857
|
+
backlogEnd = end + chunk.byteLength;
|
|
858
|
+
};
|
|
859
|
+
let frameWork: Promise<void> = Promise.resolve();
|
|
860
|
+
// Idempotent terminal owner for the backlog lease: every settle/close path
|
|
861
|
+
// must leave the raw charge at zero instead of relying on budget disposal.
|
|
862
|
+
let backlogLeaseReleased = false;
|
|
863
|
+
const releaseBacklogLease = () => {
|
|
864
|
+
if (backlogLeaseReleased) return;
|
|
865
|
+
backlogLeaseReleased = true;
|
|
866
|
+
const leftover = backlogEnd - backlogStart;
|
|
867
|
+
if (leftover > 0) this.releaseTransportBytes(leftover);
|
|
868
|
+
backlog = new Uint8Array();
|
|
869
|
+
backlogStart = 0;
|
|
870
|
+
backlogEnd = 0;
|
|
845
871
|
};
|
|
846
|
-
const handleFrame = async (frame: ReturnType<typeof
|
|
872
|
+
const handleFrame = async (frame: ReturnType<typeof consumeConnectFrames>["frames"][number]) => {
|
|
847
873
|
this.framesReceived++;
|
|
848
874
|
if ((frame.flags & CONNECT_FLAG_END_STREAM) === CONNECT_FLAG_END_STREAM) {
|
|
849
875
|
const endError = parseConnectEndStreamError(frame.payload);
|
|
@@ -861,22 +887,25 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
861
887
|
};
|
|
862
888
|
const drainPendingFrames = () => {
|
|
863
889
|
const availableSlots = CURSOR_MAX_PENDING_FRAMES - this.pendingTransportFrames;
|
|
864
|
-
|
|
890
|
+
const used = backlogEnd - backlogStart;
|
|
891
|
+
if (availableSlots <= 0 || used === 0) {
|
|
865
892
|
this.updateTransportFlowControl();
|
|
866
893
|
return;
|
|
867
894
|
}
|
|
868
|
-
|
|
869
|
-
//
|
|
870
|
-
//
|
|
871
|
-
//
|
|
872
|
-
|
|
873
|
-
|
|
895
|
+
// Cursor decode, zero-copy: frame payloads are views into the backlog, so
|
|
896
|
+
// the charge TRANSFERS — only consumed header bytes leave the counter
|
|
897
|
+
// (payload bytes stay charged and are released when each frame's work
|
|
898
|
+
// finishes). An exact 16 MiB payload therefore peaks at 16 MiB + 5, not
|
|
899
|
+
// at double its size.
|
|
900
|
+
const decoded = consumeConnectFrames(
|
|
901
|
+
backlog.subarray(backlogStart, backlogEnd),
|
|
874
902
|
CURSOR_MAX_EFFECTIVE_CONNECT_PAYLOAD_BYTES,
|
|
875
903
|
availableSlots,
|
|
876
|
-
reservePayloadCopy,
|
|
877
904
|
);
|
|
878
|
-
|
|
879
|
-
|
|
905
|
+
if (decoded.frames.length > 0) {
|
|
906
|
+
backlogStart += decoded.consumedBytes;
|
|
907
|
+
this.releaseTransportBytes(decoded.consumedBytes - decoded.frames.reduce((n, frame) => n + frame.payload.byteLength, 0));
|
|
908
|
+
}
|
|
880
909
|
for (const frame of decoded.frames) {
|
|
881
910
|
this.pendingTransportFrames += 1;
|
|
882
911
|
this.updateTransportFlowControl();
|
|
@@ -893,32 +922,31 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
893
922
|
};
|
|
894
923
|
this.stream.on("data", chunk => {
|
|
895
924
|
this.clearFirstFrameTimer();
|
|
925
|
+
// Once the turn has settled, late network bytes must never be charged —
|
|
926
|
+
// the backlog lease is already released and nobody would own these.
|
|
927
|
+
if (settler.settled()) return;
|
|
896
928
|
if (!this.firstFrameLogged) {
|
|
897
929
|
this.firstFrameLogged = true;
|
|
898
930
|
this.firstFrameAt = Date.now();
|
|
899
931
|
debugProviderDiagnostic("cursor", "first-frame", { latencyMs: this.firstFrameAt - this.turnStartedAt });
|
|
900
932
|
}
|
|
901
933
|
const bytes = typeof chunk === "string" ? new TextEncoder().encode(chunk) : new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
|
|
902
|
-
let
|
|
903
|
-
let
|
|
904
|
-
let replacement: ReturnType<typeof reservePayloadCopy> | undefined;
|
|
934
|
+
let charged = false;
|
|
935
|
+
let appended = false;
|
|
905
936
|
try {
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
const nextPending = concatBytes(pending, bytes);
|
|
913
|
-
pending = nextPending;
|
|
914
|
-
replacement.commitRetained();
|
|
915
|
-
replacement = undefined;
|
|
916
|
-
this.releaseTransportBytes(previousPendingPayloadBytes + incomingPayloadBytes);
|
|
917
|
-
incomingCharged = false;
|
|
937
|
+
// RAW chunk bytes (headers included) join the backlog charge; consumed
|
|
938
|
+
// bytes leave it at drain. No whole-backlog replacement copy anymore.
|
|
939
|
+
this.reserveTransportBytes(bytes.byteLength);
|
|
940
|
+
charged = true;
|
|
941
|
+
appendBacklog(bytes);
|
|
942
|
+
appended = true;
|
|
918
943
|
drainPendingFrames();
|
|
919
944
|
} catch (err) {
|
|
920
|
-
|
|
921
|
-
|
|
945
|
+
// Release ONLY when the reservation succeeded but the append never
|
|
946
|
+
// happened. A failed reservation charged nothing — releasing here
|
|
947
|
+
// would debit unrelated existing ownership; an appended chunk is owned
|
|
948
|
+
// by the terminal backlog cleanup.
|
|
949
|
+
if (charged && !appended) this.releaseTransportBytes(bytes.byteLength);
|
|
922
950
|
failAndClear(err instanceof Error ? err : new Error(String(err)));
|
|
923
951
|
}
|
|
924
952
|
});
|
|
@@ -954,15 +982,38 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
954
982
|
expectedClose: this.expectedClose,
|
|
955
983
|
elapsedMs: Date.now() - this.turnStartedAt,
|
|
956
984
|
});
|
|
957
|
-
//
|
|
958
|
-
//
|
|
959
|
-
//
|
|
960
|
-
//
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
985
|
+
// Settle only after queued frame work drains to quiescence (the chain can
|
|
986
|
+
// extend itself while draining), then classify the terminal state:
|
|
987
|
+
// a trailing incomplete frame is a typed failure, and a zero-frame,
|
|
988
|
+
// zero-byte end without an expected close stays the existing unexpected
|
|
989
|
+
// EOF — both beat the old silent success.
|
|
990
|
+
void (async () => {
|
|
991
|
+
let previous: Promise<void>;
|
|
992
|
+
do {
|
|
993
|
+
previous = frameWork;
|
|
994
|
+
await previous;
|
|
995
|
+
} while (previous !== frameWork);
|
|
996
|
+
})().then(() => {
|
|
997
|
+
if (settler.settled()) return;
|
|
998
|
+
const leftover = backlogEnd - backlogStart;
|
|
999
|
+
if (leftover > 0 && !this.expectedClose) {
|
|
1000
|
+
releaseBacklogLease();
|
|
1001
|
+
settler.settleFail(new ConnectFrameError(
|
|
1002
|
+
"frame_incomplete",
|
|
1003
|
+
`Cursor Connect stream ended with ${leftover} unconsumed bytes (incomplete frame)`,
|
|
1004
|
+
));
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
if (this.framesReceived === 0 && !this.expectedClose) {
|
|
1008
|
+
releaseBacklogLease();
|
|
1009
|
+
settler.settleFail(new Error("Cursor stream ended before any response frame (unexpected EOF)"));
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
releaseBacklogLease();
|
|
1013
|
+
settler.settleFinish();
|
|
1014
|
+
}, (err) => {
|
|
1015
|
+
failAndClear(err instanceof Error ? err : new Error(String(err)));
|
|
1016
|
+
});
|
|
966
1017
|
});
|
|
967
1018
|
|
|
968
1019
|
signal?.addEventListener("abort", () => {
|
|
@@ -1140,47 +1191,6 @@ export function isClientToolFrame(message: AgentServerMessage): boolean {
|
|
|
1140
1191
|
}
|
|
1141
1192
|
}
|
|
1142
1193
|
|
|
1143
|
-
function concatBytes(a: Uint8Array, b: Uint8Array): Uint8Array {
|
|
1144
|
-
const out = new Uint8Array(a.length + b.length);
|
|
1145
|
-
out.set(a);
|
|
1146
|
-
out.set(b, a.length);
|
|
1147
|
-
return out;
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
function connectBufferedPayloadBytes(input: Uint8Array): number {
|
|
1151
|
-
let offset = 0;
|
|
1152
|
-
let payloadBytes = 0;
|
|
1153
|
-
while (input.byteLength - offset >= 5) {
|
|
1154
|
-
const length = new DataView(input.buffer, input.byteOffset + offset, input.byteLength - offset).getUint32(1, false);
|
|
1155
|
-
const available = Math.min(length, input.byteLength - offset - 5);
|
|
1156
|
-
payloadBytes += available;
|
|
1157
|
-
if (available < length) break;
|
|
1158
|
-
offset += 5 + length;
|
|
1159
|
-
}
|
|
1160
|
-
return payloadBytes;
|
|
1161
|
-
}
|
|
1162
|
-
|
|
1163
|
-
/** Payload-byte count for a virtual concatenation, without allocating the concatenated buffer. */
|
|
1164
|
-
function connectBufferedPayloadBytesAcross(first: Uint8Array, second: Uint8Array): number {
|
|
1165
|
-
const totalBytes = first.byteLength + second.byteLength;
|
|
1166
|
-
const byteAt = (index: number): number => index < first.byteLength
|
|
1167
|
-
? first[index]!
|
|
1168
|
-
: second[index - first.byteLength]!;
|
|
1169
|
-
let offset = 0;
|
|
1170
|
-
let payloadBytes = 0;
|
|
1171
|
-
while (totalBytes - offset >= 5) {
|
|
1172
|
-
const length = (byteAt(offset + 1) * 0x1000000)
|
|
1173
|
-
+ (byteAt(offset + 2) << 16)
|
|
1174
|
-
+ (byteAt(offset + 3) << 8)
|
|
1175
|
-
+ byteAt(offset + 4);
|
|
1176
|
-
const available = Math.min(length, totalBytes - offset - 5);
|
|
1177
|
-
payloadBytes += available;
|
|
1178
|
-
if (available < length) break;
|
|
1179
|
-
offset += 5 + length;
|
|
1180
|
-
}
|
|
1181
|
-
return payloadBytes;
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
1194
|
/** Host-only label for Cursor transport diagnostics — never leaks path/query/credentials. */
|
|
1185
1195
|
function cursorHostLabel(baseUrl: string): string {
|
|
1186
1196
|
try {
|
|
@@ -128,6 +128,8 @@ const blobs = new Map<string, CursorBlobEntry>();
|
|
|
128
128
|
const blobRequestScopes = new Map<CursorBlobRequestScopeToken, CursorBlobRequestScopeState>();
|
|
129
129
|
let blobLimits = { ...DEFAULT_BLOB_LIMITS };
|
|
130
130
|
let blobBytes = 0;
|
|
131
|
+
/** Retained key-string bytes (separate from the payload cap — see key()). */
|
|
132
|
+
let blobKeyBytes = 0;
|
|
131
133
|
let blobLocalBytes = 0;
|
|
132
134
|
let blobPinnedBytes = 0;
|
|
133
135
|
let blobEvictableBytes = 0;
|
|
@@ -152,13 +154,16 @@ function recomputeBlobClassAccounting(): void {
|
|
|
152
154
|
let pinnedBytes = 0;
|
|
153
155
|
let evictableBytes = 0;
|
|
154
156
|
let oldestAt: number | null = null;
|
|
155
|
-
for (const entry of blobs
|
|
157
|
+
for (const [k, entry] of blobs) {
|
|
156
158
|
const requestPinned = entry.requestPins.size > 0;
|
|
157
159
|
const provenancePinned = entry.provenance === "remote-setBlobArgs" && !isExpired(entry, now);
|
|
158
160
|
if (entry.provenance === "local-regenerated") localBytes += entry.sizeBytes;
|
|
159
|
-
|
|
161
|
+
// Key strings classify WITH their entry: a zero-payload blob must stay
|
|
162
|
+
// evictable/pinned exactly as its payload would be, or the budget cannot
|
|
163
|
+
// select it even though the total snapshot counts its key.
|
|
164
|
+
if (requestPinned || provenancePinned) pinnedBytes += entry.sizeBytes + k.length;
|
|
160
165
|
if (!requestPinned && (entry.provenance === "local-regenerated" || isExpired(entry, now))) {
|
|
161
|
-
evictableBytes += entry.sizeBytes;
|
|
166
|
+
evictableBytes += entry.sizeBytes + k.length;
|
|
162
167
|
oldestAt = oldestAt === null ? entry.storedAt : Math.min(oldestAt, entry.storedAt);
|
|
163
168
|
}
|
|
164
169
|
}
|
|
@@ -196,9 +201,12 @@ function deleteBlob(k: string, recompute = true): number {
|
|
|
196
201
|
if (!entry) return 0;
|
|
197
202
|
blobs.delete(k);
|
|
198
203
|
blobBytes -= entry.sizeBytes;
|
|
204
|
+
blobKeyBytes -= k.length;
|
|
199
205
|
for (const scope of entry.requestPins) blobRequestScopes.get(scope)?.keys.delete(k);
|
|
200
206
|
if (recompute) recomputeBlobClassAccounting();
|
|
201
|
-
|
|
207
|
+
// Full logical release (payload + key): the retained-store snapshot counts
|
|
208
|
+
// both, so budget enforcement must see both leave.
|
|
209
|
+
return entry.sizeBytes + k.length;
|
|
202
210
|
}
|
|
203
211
|
|
|
204
212
|
function releaseHydratedBlob(k: string, requestScope?: CursorBlobRequestScopeToken): void {
|
|
@@ -313,6 +321,7 @@ function setBlob(
|
|
|
313
321
|
if (blobs.has(k)) deleteBlob(k, false);
|
|
314
322
|
blobs.set(k, entry);
|
|
315
323
|
blobBytes += entry.sizeBytes;
|
|
324
|
+
blobKeyBytes += k.length;
|
|
316
325
|
for (const scope of entry.requestPins) blobRequestScopes.get(scope)?.keys.add(k);
|
|
317
326
|
reconcileBlobClassAccountingAndEnforce();
|
|
318
327
|
return { admitted: true, replaced: existing !== undefined };
|
|
@@ -328,8 +337,20 @@ function getBlob(k: string): Uint8Array | undefined {
|
|
|
328
337
|
return entry.data;
|
|
329
338
|
}
|
|
330
339
|
|
|
340
|
+
/**
|
|
341
|
+
* Raw blob IDs up to this size keep their hex passthrough (every ID the live
|
|
342
|
+
* protocol carries is a 32-byte digest). Anything larger maps to a fixed-size
|
|
343
|
+
* SHA-256 hex of the raw bytes — the derivation is symmetric across
|
|
344
|
+
* setBlobArgs/getBlobArgs, so the round-trip still works, but a hostile or
|
|
345
|
+
* malformed multi-MiB ID can never become an unbounded hex Map key.
|
|
346
|
+
* The `h:`/`d:` prefix domain-separates the two namespaces: a digested ID's
|
|
347
|
+
* key can never collide with a raw 32-byte ID that happens to BE that digest.
|
|
348
|
+
*/
|
|
349
|
+
const MAX_BLOB_ID_PASSTHROUGH_BYTES = 64;
|
|
350
|
+
|
|
331
351
|
function key(bytes: Uint8Array): string {
|
|
332
|
-
return Buffer.from(bytes).toString("hex")
|
|
352
|
+
if (bytes.byteLength <= MAX_BLOB_ID_PASSTHROUGH_BYTES) return `h:${Buffer.from(bytes).toString("hex")}`;
|
|
353
|
+
return `d:${createHash("sha256").update(bytes).digest("hex")}`;
|
|
333
354
|
}
|
|
334
355
|
|
|
335
356
|
/**
|
|
@@ -382,6 +403,7 @@ export function storeCursorBlob(data: Uint8Array, requestScope?: CursorBlobReque
|
|
|
382
403
|
export interface CursorBlobMetrics {
|
|
383
404
|
count: number;
|
|
384
405
|
totalBytes: number;
|
|
406
|
+
keyBytes: number;
|
|
385
407
|
localBytes: number;
|
|
386
408
|
pinnedBytes: number;
|
|
387
409
|
rejectedEntryTooLarge: number;
|
|
@@ -393,6 +415,7 @@ export function cursorBlobMetrics(): CursorBlobMetrics {
|
|
|
393
415
|
return {
|
|
394
416
|
count: blobs.size,
|
|
395
417
|
totalBytes: blobBytes,
|
|
418
|
+
keyBytes: blobKeyBytes,
|
|
396
419
|
localBytes: blobLocalBytes,
|
|
397
420
|
pinnedBytes: blobPinnedBytes,
|
|
398
421
|
rejectedEntryTooLarge,
|
|
@@ -410,7 +433,9 @@ export function cursorBlobRetainedStoreSnapshot(): {
|
|
|
410
433
|
} {
|
|
411
434
|
return {
|
|
412
435
|
count: blobs.size,
|
|
413
|
-
|
|
436
|
+
// Payload + retained key strings: the framework must see everything the
|
|
437
|
+
// store retains. The 64 MiB admission cap stays payload-only by design.
|
|
438
|
+
bytes: blobBytes + blobKeyBytes,
|
|
414
439
|
evictableBytes: blobEvictableBytes,
|
|
415
440
|
pinnedBytes: blobPinnedBytes,
|
|
416
441
|
oldestAt: blobOldestEvictableAt,
|
|
@@ -440,6 +465,7 @@ export function resetCursorBlobStateForTests(): void {
|
|
|
440
465
|
blobs.clear();
|
|
441
466
|
blobRequestScopes.clear();
|
|
442
467
|
blobBytes = 0;
|
|
468
|
+
blobKeyBytes = 0;
|
|
443
469
|
rejectedEntryTooLarge = 0;
|
|
444
470
|
rejectedPinnedSaturation = 0;
|
|
445
471
|
recomputeBlobClassAccounting();
|
|
@@ -621,6 +621,11 @@ function buildPreparedCursorRunRequest(
|
|
|
621
621
|
tools: request.tools?.length ?? 0,
|
|
622
622
|
});
|
|
623
623
|
|
|
624
|
+
const requestedModelParameters = [
|
|
625
|
+
...(request.requestedModelParameters ?? []),
|
|
626
|
+
...(request.routingLevel ? [{ id: CURSOR_ROUTING_LEVEL_PARAMETER_ID, value: request.routingLevel }] : []),
|
|
627
|
+
];
|
|
628
|
+
const hasExplicitModelParameters = (request.requestedModelParameters?.length ?? 0) > 0;
|
|
624
629
|
const runRequest = create(AgentRunRequestSchema, {
|
|
625
630
|
conversationId: request.conversationId,
|
|
626
631
|
conversationState: create(ConversationStateStructureSchema, {
|
|
@@ -637,24 +642,24 @@ function buildPreparedCursorRunRequest(
|
|
|
637
642
|
readPaths: [],
|
|
638
643
|
}),
|
|
639
644
|
action,
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
645
|
+
// Explicit model-picker parameters follow current Cursor clients and use requested_model alone.
|
|
646
|
+
// Keep legacy model_details for flat model ids and the already-live Router path; sending both for
|
|
647
|
+
// a parameterized external model can resolve conflicting selections and end in invalid_argument.
|
|
648
|
+
...(!hasExplicitModelParameters ? {
|
|
649
|
+
modelDetails: create(ModelDetailsSchema, {
|
|
650
|
+
modelId: request.modelId,
|
|
651
|
+
displayModelId: request.modelId,
|
|
652
|
+
displayName: request.modelId,
|
|
653
|
+
displayNameShort: request.modelId,
|
|
654
|
+
aliases: [],
|
|
655
|
+
}),
|
|
656
|
+
} : {}),
|
|
657
|
+
...(requestedModelParameters.length > 0 ? {
|
|
651
658
|
requestedModel: create(RequestedModelSchema, {
|
|
652
659
|
modelId: request.modelId,
|
|
653
660
|
maxMode: false,
|
|
654
|
-
parameters:
|
|
655
|
-
|
|
656
|
-
value: request.routingLevel,
|
|
657
|
-
})],
|
|
661
|
+
parameters: requestedModelParameters.map(parameter =>
|
|
662
|
+
create(RequestedModel_ModelParameterbytesSchema, parameter)),
|
|
658
663
|
}),
|
|
659
664
|
} : {}),
|
|
660
665
|
// Mirror the client (Responses) tool definitions into the top-level AgentRunRequest.mcp_tools
|
|
@@ -8,7 +8,7 @@ import type {
|
|
|
8
8
|
OcxToolResultMessage,
|
|
9
9
|
} from "../../types";
|
|
10
10
|
import { isAllowedToolChoice, namespacedToolName, toolChoiceAliases, type OcxTool, type OcxToolChoice } from "../../types";
|
|
11
|
-
import type { CursorRequestMessage, CursorRunRequest } from "./types";
|
|
11
|
+
import type { CursorRequestMessage, CursorRequestedModelParameter, CursorRunRequest } from "./types";
|
|
12
12
|
import { cursorWireModelSelection, type CursorRoutingLevel } from "./discovery";
|
|
13
13
|
import { cursorEffortSuffix, cursorWireModelIdWithEffort } from "./effort-map";
|
|
14
14
|
import {
|
|
@@ -117,16 +117,29 @@ function catalogLimitNote(kept: readonly OcxTool[], omitted: readonly OcxTool[])
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
/**
|
|
120
|
-
* Resolve a `cursor/<model>` selection + Codex reasoning effort to
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
* known effort base) passes through unchanged.
|
|
120
|
+
* Resolve a `cursor/<model>` selection + Codex reasoning effort to Cursor's requested model shape.
|
|
121
|
+
* Most models encode effort in a flat id (`claude-4.6-opus-high`). Grok 4.5 Fast is parameterized
|
|
122
|
+
* instead: current Cursor clients send the `grok-4.5` base id plus `effort` and `fast` parameters.
|
|
123
|
+
* A fully-qualified id (one that is not a known effort base) passes through unchanged.
|
|
125
124
|
*/
|
|
126
|
-
function normalizeCursorModelId(modelId: string, reasoning?: string): {
|
|
125
|
+
function normalizeCursorModelId(modelId: string, reasoning?: string): {
|
|
126
|
+
modelId: string;
|
|
127
|
+
requestedModelParameters?: readonly CursorRequestedModelParameter[];
|
|
128
|
+
routingLevel?: CursorRoutingLevel;
|
|
129
|
+
} {
|
|
127
130
|
const selection = cursorWireModelSelection(modelId);
|
|
128
131
|
const id = selection.modelId;
|
|
129
132
|
const suffix = cursorEffortSuffix(id, reasoning);
|
|
133
|
+
if (id === "grok-4.5-fast" && suffix) {
|
|
134
|
+
return {
|
|
135
|
+
...selection,
|
|
136
|
+
modelId: "grok-4.5",
|
|
137
|
+
requestedModelParameters: [
|
|
138
|
+
{ id: "effort", value: suffix },
|
|
139
|
+
{ id: "fast", value: "true" },
|
|
140
|
+
],
|
|
141
|
+
};
|
|
142
|
+
}
|
|
130
143
|
return { ...selection, modelId: suffix ? cursorWireModelIdWithEffort(id, suffix) : id };
|
|
131
144
|
}
|
|
132
145
|
|
|
@@ -241,6 +254,7 @@ export function createCursorRequest(
|
|
|
241
254
|
const model = normalizeCursorModelId(parsed.modelId, parsed.options.reasoning);
|
|
242
255
|
return {
|
|
243
256
|
modelId: model.modelId,
|
|
257
|
+
...(model.requestedModelParameters ? { requestedModelParameters: model.requestedModelParameters } : {}),
|
|
244
258
|
...(model.routingLevel ? { routingLevel: model.routingLevel } : {}),
|
|
245
259
|
conversationId: resolveCursorConversationId(parsed, model.modelId, options),
|
|
246
260
|
system: [...(parsed.context.systemPrompt ?? []), ...(limitNote ? [limitNote] : [])],
|
|
@@ -2,8 +2,15 @@ import type { OcxUsage } from "../../types";
|
|
|
2
2
|
import type { OcxMessage, OcxRequestOptions, OcxTool } from "../../types";
|
|
3
3
|
import type { CursorRoutingLevel } from "./discovery";
|
|
4
4
|
|
|
5
|
+
export interface CursorRequestedModelParameter {
|
|
6
|
+
id: string;
|
|
7
|
+
value: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
5
10
|
export interface CursorRunRequest {
|
|
6
11
|
modelId: string;
|
|
12
|
+
/** Cursor model-picker parameters encoded through AgentRunRequest.requested_model. */
|
|
13
|
+
requestedModelParameters?: readonly CursorRequestedModelParameter[];
|
|
7
14
|
/** Cursor Router optimization parameter; valid only while modelId is the `default` wire model. */
|
|
8
15
|
routingLevel?: CursorRoutingLevel;
|
|
9
16
|
conversationId: string;
|