@bitkyc08/opencodex 2.56.0 → 2.58.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/bin/ocx.mjs +10 -0
- package/gui/dist/assets/{index-D4zuyIxQ.js → index-BbrHOIY0.js} +21 -21
- package/gui/dist/assets/{index-BBOZWGB6.css → index-C5-RdDmD.css} +1 -1
- package/gui/dist/index.html +2 -2
- package/package.json +4 -4
- package/src/adapters/codebuddy/adapter.ts +2 -1
- package/src/adapters/codebuddy/scaffold-guard.ts +249 -0
- package/src/adapters/command-code.ts +12 -3
- package/src/adapters/cursor/cursor-errors.ts +15 -0
- package/src/adapters/cursor/discovery.ts +65 -1
- package/src/adapters/cursor/envelope-echo.ts +8 -2
- package/src/adapters/cursor/live-transport.ts +5 -1
- package/src/adapters/cursor/protobuf-events.ts +110 -11
- package/src/adapters/cursor/protobuf-request.ts +19 -1
- package/src/adapters/cursor/text-toolcall.ts +230 -0
- package/src/adapters/cursor/thread-continuity.ts +67 -0
- package/src/adapters/cursor/types.ts +5 -0
- package/src/adapters/cursor.ts +55 -5
- package/src/adapters/google-http.ts +38 -13
- package/src/adapters/google.ts +7 -7
- package/src/adapters/kiro/payload.ts +17 -3
- package/src/adapters/kiro/reasoning.ts +70 -7
- package/src/adapters/kiro/stream.ts +8 -2
- package/src/adapters/kiro/wire.ts +2 -1
- package/src/adapters/kiro-events.ts +21 -13
- package/src/adapters/mimo-free.ts +32 -17
- package/src/adapters/ollama-native.ts +42 -8
- package/src/adapters/openai-chat/tool-name-registry.ts +166 -0
- package/src/adapters/openai-chat/tool-schema.ts +25 -7
- package/src/adapters/openai-chat.ts +8 -8
- package/src/adapters/openai-responses/passthrough.ts +62 -5
- package/src/adapters/openai-responses/request-strips.ts +43 -0
- package/src/adapters/physical-send.ts +50 -0
- package/src/bridge/errors.ts +26 -2
- package/src/bridge/response-json.ts +8 -2
- package/src/bridge/sse.ts +20 -2
- package/src/claude/desktop-profile.ts +66 -9
- package/src/claude/outbound.ts +32 -4
- package/src/cli/account-main.ts +1 -1
- package/src/cli/capabilities.ts +2 -2
- package/src/cli/combo.ts +10 -1
- package/src/cli/config-command.ts +35 -18
- package/src/cli/dispatch.ts +17 -4
- package/src/cli/index.ts +92 -7
- package/src/cli/registry.ts +2 -1
- package/src/cli/system-command.ts +74 -5
- package/src/cli/uninstall-client-state.ts +12 -0
- package/src/clients/config-export.ts +7 -3
- package/src/codex/account-label.ts +14 -3
- package/src/codex/account-store.ts +113 -26
- package/src/codex/account-usability.ts +21 -0
- package/src/codex/auth-api/login-flow.ts +14 -2
- package/src/codex/auth-api/reset-credit-service.ts +11 -2
- package/src/codex/auth-context.ts +199 -15
- package/src/codex/catalog/aggregation.ts +80 -1
- package/src/codex/catalog/model-visibility.ts +1 -0
- package/src/codex/catalog/remote.ts +30 -0
- package/src/codex/catalog/retained-sync.ts +9 -1
- package/src/codex/catalog/routed-gather.ts +38 -1
- package/src/codex/cli-install-provenance.ts +7 -1
- package/src/codex/convergence.ts +7 -2
- package/src/codex/desktop-app/types.ts +11 -2
- package/src/codex/desktop-app/windows.ts +5 -5
- package/src/codex/desktop-switches.ts +145 -0
- package/src/codex/history-job.ts +5 -1
- package/src/codex/history-provider.ts +33 -4
- package/src/codex/history-worker.ts +14 -1
- package/src/codex/inject/remove.ts +145 -7
- package/src/codex/inject/restore.ts +231 -32
- package/src/codex/inject.ts +12 -16
- package/src/codex/loopback-target.ts +9 -0
- package/src/codex/model-entitlements.ts +152 -15
- package/src/codex/native-profile-startup.ts +64 -20
- package/src/codex/pool-refresh-backoff.ts +12 -3
- package/src/codex/quota-rejection.ts +104 -15
- package/src/codex/routing/cache-affinity.ts +70 -0
- package/src/codex/routing/cooldown-math.ts +10 -0
- package/src/codex/routing/selection.ts +79 -2
- package/src/codex/routing/thread-affinity.ts +50 -2
- package/src/codex/routing/transient-hold-dispatch.ts +141 -0
- package/src/codex/routing.ts +29 -49
- package/src/codex/warmup.ts +1 -1
- package/src/combos/failover.ts +85 -0
- package/src/combos/request.ts +17 -10
- package/src/combos/types.ts +23 -2
- package/src/config/atomic-write.ts +83 -8
- package/src/config/pending-teardown.ts +31 -0
- package/src/config/schema/config-schema.ts +2 -0
- package/src/config/schema/leaf-validators.ts +1 -0
- package/src/generated/compatibility-version.json +272 -180
- package/src/images/loop.ts +1 -1
- package/src/lib/bounded-subprocess.ts +62 -10
- package/src/lib/errors.ts +17 -0
- package/src/lib/request-execution-budget.ts +147 -21
- package/src/lib/spend-reservation-ledger.ts +18 -0
- package/src/lib/state-store-registrations.ts +6 -2
- package/src/lib/test-home-guard.ts +85 -1
- package/src/lib/upstream-retry.ts +77 -10
- package/src/lib/windows-elevation.ts +76 -14
- package/src/lib/windows-secret-acl.ts +151 -15
- package/src/lib/windows-user-principal.ts +5 -1
- package/src/oauth/index.ts +2 -2
- package/src/oauth/key-providers.ts +2 -2
- package/src/providers/derive.ts +6 -0
- package/src/providers/kiro-models.ts +4 -3
- package/src/providers/label.ts +19 -1
- package/src/providers/model-discovery.ts +35 -7
- package/src/providers/registry/entries-core.ts +18 -0
- package/src/providers/registry/entries-extended.ts +59 -28
- package/src/providers/registry/model-seeds.ts +71 -17
- package/src/providers/registry/types.ts +9 -0
- package/src/responses/reasoning-envelope.ts +6 -3
- package/src/responses/spill-store.ts +17 -0
- package/src/responses/state/body-policy.ts +25 -0
- package/src/responses/state/spill-queue.ts +8 -6
- package/src/responses/state.ts +3 -22
- package/src/router.ts +4 -0
- package/src/routing/identity-domains.ts +21 -14
- package/src/routing/probe-lease.ts +103 -1
- package/src/server/auth-cors.ts +1 -0
- package/src/server/chat-completions.ts +3 -1
- package/src/server/chat-native.ts +37 -9
- package/src/server/index/live-sideband.ts +37 -1
- package/src/server/index/websocket-handler.ts +54 -3
- package/src/server/index.ts +5 -5
- package/src/server/inspection-tee.ts +107 -0
- package/src/server/live.ts +46 -1
- package/src/server/management/combo-routes.ts +10 -1
- package/src/server/management/config-routes.ts +27 -5
- package/src/server/models-capabilities.ts +24 -3
- package/src/server/relay-eager.ts +2 -0
- package/src/server/relay.ts +14 -19
- package/src/server/request-log.ts +127 -3
- package/src/server/response-log-body.ts +153 -0
- package/src/server/responses/account-change-state.ts +74 -0
- package/src/server/responses/adapter-continuation.ts +33 -7
- package/src/server/responses/adapter-delivery.ts +5 -11
- package/src/server/responses/adapter-dispatch.ts +84 -13
- package/src/server/responses/codex-ws-exchange.ts +65 -4
- package/src/server/responses/codex-ws-wire.ts +5 -0
- package/src/server/responses/collaboration.ts +74 -4
- package/src/server/responses/combo-session-recall.ts +68 -8
- package/src/server/responses/combo-stream-preflight.ts +68 -5
- package/src/server/responses/compact.ts +54 -13
- package/src/server/responses/core-auth.ts +2 -0
- package/src/server/responses/core-codex-account.ts +51 -3
- package/src/server/responses/core-combo.ts +129 -23
- package/src/server/responses/core-errors.ts +18 -0
- package/src/server/responses/core-options.ts +3 -0
- package/src/server/responses/core-replay.ts +105 -32
- package/src/server/responses/core.ts +3 -3
- package/src/server/responses/encrypted-payload.ts +0 -1
- package/src/server/responses/fetch-helpers.ts +4 -1
- package/src/server/responses/input-admission.ts +126 -6
- package/src/server/responses/native-injection-protocol.ts +42 -0
- package/src/server/responses/native-injection-replay.ts +105 -0
- package/src/server/responses/native-injection.ts +242 -0
- package/src/server/responses/native-response-control.ts +56 -0
- package/src/server/responses/native-response-json.ts +14 -0
- package/src/server/responses/native-response-output.ts +37 -0
- package/src/server/responses/native-steering-log.ts +44 -0
- package/src/server/responses/native-steering-policy.ts +49 -0
- package/src/server/responses/native-steering-replay.ts +126 -0
- package/src/server/responses/native-steering-settings.ts +76 -0
- package/src/server/responses/native-steering.ts +400 -0
- package/src/server/responses/native-tool-results.ts +130 -0
- package/src/server/responses/passthrough-delivery.ts +30 -6
- package/src/server/responses/passthrough-dispatch.ts +61 -11
- package/src/server/responses/passthrough-error.ts +38 -2
- package/src/server/responses/request-prepare.ts +173 -22
- package/src/server/responses/request-send-budget.ts +97 -2
- package/src/server/responses/request-spend.ts +147 -0
- package/src/server/responses/request-transport.ts +62 -3
- package/src/server/responses/run-turn-execution.ts +59 -31
- package/src/server/responses/sidecar-execution.ts +7 -13
- package/src/server/responses/terminal-guard.ts +65 -4
- package/src/server/responses/ws-upstream.ts +21 -1
- package/src/server/responses-undeclared-tool-guard.ts +9 -5
- package/src/server/stop-teardown.ts +8 -1
- package/src/server/ws-bridge.ts +16 -1
- package/src/service/cli.ts +13 -1
- package/src/service/windows-ops.ts +210 -16
- package/src/service/windows-scheduler.ts +28 -21
- package/src/service.ts +1 -1
- package/src/types/config.ts +8 -1
- package/src/types/provider.ts +13 -0
- package/src/types/request.ts +8 -5
- package/src/types/tools.ts +24 -0
- package/src/types.ts +2 -0
- package/src/update/index.ts +10 -0
- package/src/update/stop-contract.d.mts +1 -0
- package/src/update/stop-contract.mjs +19 -0
- package/src/update/stop-decision.d.mts +1 -1
- package/src/update/stop-decision.mjs +12 -3
- package/src/usage/log.ts +1 -1
- package/src/vision/anthropic-describe.ts +1 -1
- package/src/vision/describe.ts +5 -5
- package/src/web-search/anthropic-executor.ts +1 -1
- package/src/web-search/exa-executor.ts +1 -1
- package/src/web-search/executor.ts +1 -1
- package/src/web-search/gemini-executor.ts +1 -1
- package/src/web-search/loop.ts +1 -1
- package/src/web-search/ollama-executor.ts +1 -1
- package/src/web-search/parse.ts +67 -14
- package/src/web-search/passthrough-bridge.ts +64 -31
- package/src/web-search/xai-executor.ts +1 -1
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-BbrHOIY0.js"></script>
|
|
20
|
+
<link rel="stylesheet" crossorigin href="/assets/index-C5-RdDmD.css">
|
|
21
21
|
</head>
|
|
22
22
|
<body>
|
|
23
23
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bitkyc08/opencodex",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.58.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",
|
|
@@ -76,17 +76,17 @@
|
|
|
76
76
|
"@bufbuild/protobuf": "^2.14.0",
|
|
77
77
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
78
78
|
"@napi-rs/keyring": "1.3.0",
|
|
79
|
-
"bun": "1.4.
|
|
79
|
+
"bun": "1.4.0",
|
|
80
80
|
"zod": "4.4.3"
|
|
81
81
|
},
|
|
82
82
|
"devDependencies": {
|
|
83
|
-
"@types/bun": "1.4.
|
|
83
|
+
"@types/bun": "1.4.0",
|
|
84
84
|
"typescript": "7.0.2"
|
|
85
85
|
},
|
|
86
86
|
"overrides": {
|
|
87
87
|
"@hono/node-server": "2.1.0",
|
|
88
88
|
"fast-uri": "^3.1.7",
|
|
89
|
-
"hono": "4.13.
|
|
89
|
+
"hono": "4.13.8",
|
|
90
90
|
"ip-address": "^10.4.0",
|
|
91
91
|
"qs": "^6.16.0"
|
|
92
92
|
},
|
|
@@ -4,6 +4,7 @@ import { mapReasoningEffort } from "../../reasoning-effort";
|
|
|
4
4
|
import { buildSystemPrompt } from "../coding-agent/protocol";
|
|
5
5
|
import { baseScopedEnv, runCodingAgentTurn, type CodingAgentDeps, type SpawnFn } from "../coding-agent/turn";
|
|
6
6
|
import { CODEBUDDY_PROFILES, type CodeBuddyProfile } from "./profiles";
|
|
7
|
+
import { guardCodeBuddyScaffolding } from "./scaffold-guard";
|
|
7
8
|
|
|
8
9
|
export type { SpawnFn } from "../coding-agent/turn";
|
|
9
10
|
export type CodeBuddyAdapterDeps = CodingAgentDeps;
|
|
@@ -75,7 +76,7 @@ export function createCodeBuddyAdapter(provider: OcxProviderConfig, deps: CodeBu
|
|
|
75
76
|
provider,
|
|
76
77
|
parsed,
|
|
77
78
|
incoming,
|
|
78
|
-
emit,
|
|
79
|
+
emit: guardCodeBuddyScaffolding(emit),
|
|
79
80
|
buildArgs: (resolved, req, prov) => buildArgs(resolved as CodeBuddyProfile, req, prov),
|
|
80
81
|
buildEnv: (resolved, apiKey) => buildChildEnv(resolved as CodeBuddyProfile, apiKey),
|
|
81
82
|
deps,
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import type { AdapterEvent } from "../../types";
|
|
2
|
+
|
|
3
|
+
/** Error code for a CodeBuddy turn whose output contains vendor agent scaffolding. */
|
|
4
|
+
export const CODEBUDDY_SCAFFOLD_ERROR_CODE = "vendor_scaffold_detected";
|
|
5
|
+
|
|
6
|
+
// The observed control protocol uses FULLWIDTH VERTICAL LINE (U+FF5C). Detection stays
|
|
7
|
+
// deliberately narrower than the marker spelling: a calls control line must be followed by an
|
|
8
|
+
// invoke line with a non-empty tool name. That distinguishes an agent scaffold from prose quoting or
|
|
9
|
+
// discussing one tag.
|
|
10
|
+
const DSML_CALLS_LINE = "<||dsml|| calls>";
|
|
11
|
+
const DSML_INVOKE_PREFIX = "<||dsml|| invoke name=\"";
|
|
12
|
+
|
|
13
|
+
export interface CodeBuddyScaffoldFilterResult {
|
|
14
|
+
/** Bytes released from a suffix withheld by an earlier event on this channel. */
|
|
15
|
+
releasedPending: string;
|
|
16
|
+
/** Safe bytes belonging to the event currently being processed. */
|
|
17
|
+
text: string;
|
|
18
|
+
/** The earlier pending event still owns the extended candidate. */
|
|
19
|
+
pendingContinues: boolean;
|
|
20
|
+
fail: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface ScanResult {
|
|
24
|
+
safe: string;
|
|
25
|
+
held: string;
|
|
26
|
+
fail: boolean;
|
|
27
|
+
fence: "`" | "~" | null;
|
|
28
|
+
lineStart: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function prefixAtEnd(text: string, at: number, expected: string): boolean {
|
|
32
|
+
const rest = text.slice(at).toLowerCase();
|
|
33
|
+
return rest.length < expected.length && expected.startsWith(rest);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Scan complete bytes and retain only a bounded suffix that can still become a control sequence.
|
|
38
|
+
*
|
|
39
|
+
* Control tags are recognized only at column zero and outside fenced Markdown. Inline code,
|
|
40
|
+
* quoted strings, blockquotes, indented source, and prose all add syntax before the tag and are
|
|
41
|
+
* therefore forwarded unchanged. A calls line alone is harmless; refusal requires the observed
|
|
42
|
+
* two-line calls-plus-named-invoke grammar.
|
|
43
|
+
*/
|
|
44
|
+
function scan(
|
|
45
|
+
text: string,
|
|
46
|
+
initialFence: "`" | "~" | null,
|
|
47
|
+
initialLineStart: boolean,
|
|
48
|
+
): ScanResult {
|
|
49
|
+
let fence = initialFence;
|
|
50
|
+
let lineStart = initialLineStart;
|
|
51
|
+
let index = 0;
|
|
52
|
+
|
|
53
|
+
while (index < text.length) {
|
|
54
|
+
if (lineStart) {
|
|
55
|
+
const fenceMarkers = fence ? [fence.repeat(3)] : ["```", "~~~"];
|
|
56
|
+
const completeFence = fenceMarkers.find(marker => text.startsWith(marker, index));
|
|
57
|
+
if (completeFence) {
|
|
58
|
+
fence = fence ? null : (completeFence[0] as "`" | "~");
|
|
59
|
+
index += completeFence.length;
|
|
60
|
+
lineStart = false;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (fenceMarkers.some(marker => prefixAtEnd(text, index, marker))) {
|
|
64
|
+
return { safe: text.slice(0, index), held: text.slice(index), fail: false, fence, lineStart };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!fence) {
|
|
68
|
+
const lowered = text.slice(index).toLowerCase();
|
|
69
|
+
if (lowered.startsWith(DSML_CALLS_LINE)) {
|
|
70
|
+
const afterCalls = index + DSML_CALLS_LINE.length;
|
|
71
|
+
let invokeAt = -1;
|
|
72
|
+
if (text[afterCalls] === "\n") invokeAt = afterCalls + 1;
|
|
73
|
+
else if (text[afterCalls] === "\r" && text[afterCalls + 1] === "\n") invokeAt = afterCalls + 2;
|
|
74
|
+
else if (afterCalls === text.length || (text[afterCalls] === "\r" && afterCalls + 1 === text.length)) {
|
|
75
|
+
return { safe: text.slice(0, index), held: text.slice(index), fail: false, fence, lineStart };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (invokeAt >= 0) {
|
|
79
|
+
const invokeRest = text.slice(invokeAt).toLowerCase();
|
|
80
|
+
const invokeNameStart = invokeRest[DSML_INVOKE_PREFIX.length];
|
|
81
|
+
if (invokeRest.startsWith(DSML_INVOKE_PREFIX) && invokeNameStart && !/[\s"]/.test(invokeNameStart)) {
|
|
82
|
+
return { safe: text.slice(0, index), held: "", fail: true, fence, lineStart };
|
|
83
|
+
}
|
|
84
|
+
if (invokeRest.length === 0 || DSML_INVOKE_PREFIX.startsWith(invokeRest)) {
|
|
85
|
+
return { safe: text.slice(0, index), held: text.slice(index), fail: false, fence, lineStart };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
} else if (prefixAtEnd(text, index, DSML_CALLS_LINE)) {
|
|
89
|
+
return { safe: text.slice(0, index), held: text.slice(index), fail: false, fence, lineStart };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const char = text[index]!;
|
|
95
|
+
index += 1;
|
|
96
|
+
lineStart = char === "\n";
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return { safe: text, held: "", fail: false, fence, lineStart };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Streaming DSML control-sequence filter for one text or reasoning channel. */
|
|
103
|
+
export class CodeBuddyScaffoldFilter {
|
|
104
|
+
private pending = "";
|
|
105
|
+
private failed = false;
|
|
106
|
+
private fence: "`" | "~" | null = null;
|
|
107
|
+
private lineStart = true;
|
|
108
|
+
|
|
109
|
+
/** True while an earlier event owns an unresolved marker or fence prefix. */
|
|
110
|
+
hasPending(): boolean {
|
|
111
|
+
return this.pending.length > 0;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
push(chunk: string): CodeBuddyScaffoldFilterResult {
|
|
115
|
+
if (this.failed) {
|
|
116
|
+
return { releasedPending: "", text: "", pendingContinues: false, fail: false };
|
|
117
|
+
}
|
|
118
|
+
if (!chunk) {
|
|
119
|
+
return {
|
|
120
|
+
releasedPending: "",
|
|
121
|
+
text: "",
|
|
122
|
+
pendingContinues: this.hasPending(),
|
|
123
|
+
fail: false,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const priorPending = this.pending;
|
|
128
|
+
const result = scan(priorPending + chunk, this.fence, this.lineStart);
|
|
129
|
+
this.pending = result.held;
|
|
130
|
+
this.fence = result.fence;
|
|
131
|
+
this.lineStart = result.lineStart;
|
|
132
|
+
this.failed = result.fail;
|
|
133
|
+
|
|
134
|
+
const releasedLength = Math.min(priorPending.length, result.safe.length);
|
|
135
|
+
return {
|
|
136
|
+
releasedPending: result.safe.slice(0, releasedLength),
|
|
137
|
+
text: result.safe.slice(releasedLength),
|
|
138
|
+
pendingContinues: priorPending.length > 0 && result.safe.length === 0 && result.held.length > 0,
|
|
139
|
+
fail: result.fail,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Release a suffix that never completed the two-line control grammar. */
|
|
144
|
+
flush(): CodeBuddyScaffoldFilterResult {
|
|
145
|
+
if (this.failed) {
|
|
146
|
+
return { releasedPending: "", text: "", pendingContinues: false, fail: false };
|
|
147
|
+
}
|
|
148
|
+
const text = this.pending;
|
|
149
|
+
this.pending = "";
|
|
150
|
+
return { releasedPending: text, text: "", pendingContinues: false, fail: false };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function codeBuddyScaffoldErrorMessage(): string {
|
|
155
|
+
return "CodeBuddy CLI emitted vendor tool-call markup in an assistant output channel. This route"
|
|
156
|
+
+ " runs the CLI with its own tools and MCP servers disabled and Codex owns tool control, so"
|
|
157
|
+
+ " the turn was refused rather than forwarding or executing vendor agent scaffolding.";
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Guard both streamed channels while preserving event order around withheld marker prefixes. */
|
|
161
|
+
export function guardCodeBuddyScaffolding(emit: (event: AdapterEvent) => void): (event: AdapterEvent) => void {
|
|
162
|
+
const textFilter = new CodeBuddyScaffoldFilter();
|
|
163
|
+
const thinkingFilter = new CodeBuddyScaffoldFilter();
|
|
164
|
+
type PendingChannel = "text" | "thinking";
|
|
165
|
+
type EventSlot = { resolved: boolean; event?: AdapterEvent };
|
|
166
|
+
const eventQueue: EventSlot[] = [];
|
|
167
|
+
const pendingSlots = new Map<PendingChannel, EventSlot>();
|
|
168
|
+
let closed = false;
|
|
169
|
+
|
|
170
|
+
const channelEvent = (channel: PendingChannel, text: string): AdapterEvent => channel === "text"
|
|
171
|
+
? { type: "text_delta", text }
|
|
172
|
+
: { type: "thinking_delta", thinking: text };
|
|
173
|
+
|
|
174
|
+
const drainResolved = (): void => {
|
|
175
|
+
while (eventQueue[0]?.resolved) {
|
|
176
|
+
const slot = eventQueue.shift()!;
|
|
177
|
+
if (slot.event) emit(slot.event);
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const enqueueResolved = (event: AdapterEvent): void => {
|
|
182
|
+
eventQueue.push({ resolved: true, event });
|
|
183
|
+
drainResolved();
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const resolvePendingSlot = (channel: PendingChannel, text: string): void => {
|
|
187
|
+
const slot = pendingSlots.get(channel);
|
|
188
|
+
if (!slot) return;
|
|
189
|
+
slot.resolved = true;
|
|
190
|
+
if (text) slot.event = channelEvent(channel, text);
|
|
191
|
+
pendingSlots.delete(channel);
|
|
192
|
+
drainResolved();
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const enqueuePendingSlot = (channel: PendingChannel): void => {
|
|
196
|
+
const slot: EventSlot = { resolved: false };
|
|
197
|
+
eventQueue.push(slot);
|
|
198
|
+
pendingSlots.set(channel, slot);
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const flushAllPending = (): void => {
|
|
202
|
+
for (const channel of ["text", "thinking"] as const) {
|
|
203
|
+
if (!pendingSlots.has(channel)) continue;
|
|
204
|
+
const filter = channel === "text" ? textFilter : thinkingFilter;
|
|
205
|
+
resolvePendingSlot(channel, filter.flush().releasedPending);
|
|
206
|
+
}
|
|
207
|
+
drainResolved();
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const refuse = (): void => {
|
|
211
|
+
if (closed) return;
|
|
212
|
+
flushAllPending();
|
|
213
|
+
closed = true;
|
|
214
|
+
emit({
|
|
215
|
+
type: "error",
|
|
216
|
+
message: codeBuddyScaffoldErrorMessage(),
|
|
217
|
+
status: 502,
|
|
218
|
+
errorType: "upstream_error",
|
|
219
|
+
code: CODEBUDDY_SCAFFOLD_ERROR_CODE,
|
|
220
|
+
retryable: false,
|
|
221
|
+
});
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
return (event: AdapterEvent): void => {
|
|
225
|
+
if (closed) return;
|
|
226
|
+
if (event.type === "text_delta" || event.type === "thinking_delta") {
|
|
227
|
+
const channel: PendingChannel = event.type === "text_delta" ? "text" : "thinking";
|
|
228
|
+
const filter = channel === "text" ? textFilter : thinkingFilter;
|
|
229
|
+
const hadPending = filter.hasPending();
|
|
230
|
+
const cleaned = filter.push(event.type === "text_delta" ? event.text : event.thinking);
|
|
231
|
+
if (hadPending && !cleaned.pendingContinues) resolvePendingSlot(channel, cleaned.releasedPending);
|
|
232
|
+
if (cleaned.text) {
|
|
233
|
+
enqueueResolved(event.type === "text_delta"
|
|
234
|
+
? { ...event, text: cleaned.text }
|
|
235
|
+
: { ...event, thinking: cleaned.text });
|
|
236
|
+
}
|
|
237
|
+
if (filter.hasPending() && !cleaned.pendingContinues) enqueuePendingSlot(channel);
|
|
238
|
+
if (cleaned.fail) refuse();
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (event.type === "done" || event.type === "error" || event.type === "incomplete") {
|
|
242
|
+
flushAllPending();
|
|
243
|
+
closed = true;
|
|
244
|
+
emit(event);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
enqueueResolved(event);
|
|
248
|
+
};
|
|
249
|
+
}
|
|
@@ -13,6 +13,8 @@ import { commandCodeReasoningEfforts, refreshCommandCodeReasoningEfforts } from
|
|
|
13
13
|
import { identifyRoutedModel } from "./identity";
|
|
14
14
|
import { buildNonOpenAIToolCatalogNudgeForTools } from "./tool-catalog-nudge";
|
|
15
15
|
import { parseDataUrl } from "./image";
|
|
16
|
+
import { createAdapterPhysicalSend } from "./physical-send";
|
|
17
|
+
import { SendBudgetExhaustedError } from "../lib/upstream-retry";
|
|
16
18
|
|
|
17
19
|
// Retain the short ids emitted by the first local integration. New requests use the live catalog's
|
|
18
20
|
// provider-native IDs directly; this map is compatibility-only and is not a model fallback list.
|
|
@@ -556,7 +558,8 @@ export function createCommandCodeAdapter(provider: OcxProviderConfig): ProviderA
|
|
|
556
558
|
};
|
|
557
559
|
},
|
|
558
560
|
async fetchResponse(request: AdapterRequest, ctx?: AdapterFetchContext): Promise<Response> {
|
|
559
|
-
const
|
|
561
|
+
const send = createAdapterPhysicalSend(ctx, executor);
|
|
562
|
+
const response = await send({ url: request.url, dispatch: physical => fetchCommandCode(request, ctx, physical) });
|
|
560
563
|
if (response.ok) return response;
|
|
561
564
|
const currentEffort = (() => {
|
|
562
565
|
try { return (JSON.parse(request.body) as { params?: { reasoning_effort?: unknown } }).params?.reasoning_effort; } catch { return undefined; }
|
|
@@ -577,8 +580,14 @@ export function createCommandCodeAdapter(provider: OcxProviderConfig): ProviderA
|
|
|
577
580
|
if (!refreshed || refreshed.includes(currentEffort)) return response;
|
|
578
581
|
const retry = requestWithoutReasoningEffort(request);
|
|
579
582
|
if (!retry) return response;
|
|
580
|
-
try {
|
|
581
|
-
|
|
583
|
+
try {
|
|
584
|
+
return await send({ url: retry.url, sendClass: "repair", recovery: "reasoning-effort-downgrade",
|
|
585
|
+
beforeDispatch: () => { try { void response.body?.cancel().catch(() => {}); } catch { /* already closed */ } },
|
|
586
|
+
dispatch: physical => fetchCommandCode(retry, ctx, physical) });
|
|
587
|
+
} catch (error) {
|
|
588
|
+
if (error instanceof SendBudgetExhaustedError) return response;
|
|
589
|
+
throw error;
|
|
590
|
+
}
|
|
582
591
|
},
|
|
583
592
|
async *parseStream(response: Response, budget: TranslatorBudget): AsyncGenerator<AdapterEvent> {
|
|
584
593
|
let sawFinish = false;
|
|
@@ -46,6 +46,21 @@ export class CursorStreamTruncatedError extends Error {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
export const CURSOR_INCOMPLETE_TOOL_CALL_MESSAGE_PREFIX =
|
|
50
|
+
"Cursor stream ended with incomplete tool call(s):";
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* True when Cursor ended the stream with a client tool still open. The adapter fail-closes
|
|
54
|
+
* the current turn (no partial `tool_call_start`) and remints the conversation afterwards
|
|
55
|
+
* so the next turn does not resume a session left waiting for `mcpResult`.
|
|
56
|
+
*/
|
|
57
|
+
export function isCursorIncompleteToolCallMessage(value: unknown): boolean {
|
|
58
|
+
const message = typeof value === "string" ? value : errorMessage(value);
|
|
59
|
+
const lower = message.toLowerCase();
|
|
60
|
+
return lower.includes(CURSOR_INCOMPLETE_TOOL_CALL_MESSAGE_PREFIX.toLowerCase())
|
|
61
|
+
|| lower.includes("tool call(s) left incomplete");
|
|
62
|
+
}
|
|
63
|
+
|
|
49
64
|
/**
|
|
50
65
|
* A cancel-shaped stream failure that WE did not request. `cancelCursorRun` is the only place
|
|
51
66
|
* that cancels our own stream, and it sets `expectedClose` first, so a cancel arriving without it
|
|
@@ -24,8 +24,54 @@ const CONTEXT_272K = 272_000;
|
|
|
24
24
|
const CONTEXT_262K = 262_144;
|
|
25
25
|
const CONTEXT_256K = 256_000;
|
|
26
26
|
const CONTEXT_200K = 200_000;
|
|
27
|
+
export const CURSOR_OBSERVED_CONTEXT_WINDOW_MAX_ENTRIES = 2_048;
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Process-local ceilings from `ConversationTokenDetails.maxTokens` on live
|
|
31
|
+
* checkpoints. Each observation belongs to the Cursor identity scope that
|
|
32
|
+
* produced it; plan-gated accounts sharing one proxy must not overwrite each
|
|
33
|
+
* other's overflow prior (senpi `cursor-context-limit`).
|
|
34
|
+
*/
|
|
35
|
+
const observedCursorContextWindows = new Map<string, number>();
|
|
36
|
+
|
|
37
|
+
interface CursorContextWindowOptions {
|
|
38
|
+
identityScope?: string;
|
|
39
|
+
observed?: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function normalizeObservedWindowKey(modelId: string, identityScope?: string): string {
|
|
43
|
+
return `${identityScope?.trim() || "local"}\0${modelId.trim().toLowerCase()}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function recordObservedCursorContextWindow(
|
|
47
|
+
modelId: string,
|
|
48
|
+
maxTokens: number | undefined,
|
|
49
|
+
options: Pick<CursorContextWindowOptions, "identityScope"> = {},
|
|
50
|
+
): void {
|
|
51
|
+
if (!modelId.trim()) return;
|
|
52
|
+
if (typeof maxTokens !== "number" || !Number.isFinite(maxTokens) || maxTokens <= 0) return;
|
|
53
|
+
const key = normalizeObservedWindowKey(modelId, options.identityScope);
|
|
54
|
+
observedCursorContextWindows.delete(key);
|
|
55
|
+
observedCursorContextWindows.set(key, Math.floor(maxTokens));
|
|
56
|
+
while (observedCursorContextWindows.size > CURSOR_OBSERVED_CONTEXT_WINDOW_MAX_ENTRIES) {
|
|
57
|
+
const oldest = observedCursorContextWindows.keys().next().value;
|
|
58
|
+
if (oldest === undefined) break;
|
|
59
|
+
observedCursorContextWindows.delete(oldest);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function observedCursorContextWindow(
|
|
64
|
+
modelId: string,
|
|
65
|
+
options: Pick<CursorContextWindowOptions, "identityScope"> = {},
|
|
66
|
+
): number | undefined {
|
|
67
|
+
return observedCursorContextWindows.get(normalizeObservedWindowKey(modelId, options.identityScope));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function resetObservedCursorContextWindowsForTests(): void {
|
|
71
|
+
observedCursorContextWindows.clear();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function inferCursorContextWindowHeuristic(modelId: string): number {
|
|
29
75
|
const id = modelId.trim().toLowerCase();
|
|
30
76
|
if (id.includes("1m")) return CONTEXT_1M;
|
|
31
77
|
if (id.startsWith("gemini-")) return CONTEXT_1M;
|
|
@@ -40,6 +86,24 @@ export function inferCursorContextWindow(modelId: string): number {
|
|
|
40
86
|
return CURSOR_DEFAULT_CONTEXT_WINDOW;
|
|
41
87
|
}
|
|
42
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Infer a conservative context window for a Cursor model id.
|
|
91
|
+
*
|
|
92
|
+
* A positive explicit observation wins, then an identity-scoped process-local
|
|
93
|
+
* checkpoint `maxTokens`, then the id heuristic. Cursor's `AvailableModelsResponse`
|
|
94
|
+
* does not currently include per-model context window metadata.
|
|
95
|
+
*/
|
|
96
|
+
export function inferCursorContextWindow(
|
|
97
|
+
modelId: string,
|
|
98
|
+
options: CursorContextWindowOptions = {},
|
|
99
|
+
): number {
|
|
100
|
+
const { observed } = options;
|
|
101
|
+
if (typeof observed === "number" && Number.isFinite(observed) && observed > 0) {
|
|
102
|
+
return Math.floor(observed);
|
|
103
|
+
}
|
|
104
|
+
return observedCursorContextWindow(modelId, options) ?? inferCursorContextWindowHeuristic(modelId);
|
|
105
|
+
}
|
|
106
|
+
|
|
43
107
|
function normalizeInputModalities(input: string[] | undefined): string[] {
|
|
44
108
|
const values = (input ?? [...CURSOR_DEFAULT_INPUT_MODALITIES])
|
|
45
109
|
.map(item => item.trim())
|
|
@@ -217,7 +217,13 @@ export type RoutingCommentaryDecision =
|
|
|
217
217
|
| { kind: "flush" }
|
|
218
218
|
| { kind: "hallucination" };
|
|
219
219
|
|
|
220
|
-
|
|
220
|
+
// The Korean alternative is deliberately asymmetric: it has a left boundary and no right one.
|
|
221
|
+
// Korean attaches particles directly to the noun, so the real sentences this detector exists to
|
|
222
|
+
// catch read "네이티브 셸이 차단되어..." and "네이티브 셸과 Read가...". A mirrored
|
|
223
|
+
// (?![\p{L}\p{M}\p{N}_]) lookahead would see the 이/과 particle as a letter and stop matching
|
|
224
|
+
// every one of them, which is why the negative cases below only probe the left side. Adding the
|
|
225
|
+
// right boundary looks like an obvious fix and disables the check; do not.
|
|
226
|
+
const ROUTING_NATIVE_TOOL_NAME = /\b(shell|read|grep|list|bash)\b|(?<![\p{L}\p{M}\p{N}_])네이티브\s*(?:셸|쉘)/giu;
|
|
221
227
|
const ROUTING_TOOL_HINT =
|
|
222
228
|
/(?:\b(?:shell|read|grep|list|bash)\b|exec_command|shell_command|브리지|네이티브\s*(?:셸|쉘))/iu;
|
|
223
229
|
const ROUTING_FAILURE_CLAIM =
|
|
@@ -276,7 +282,7 @@ export class CursorRoutingCommentarySniffer {
|
|
|
276
282
|
private matchesHallucination(): boolean {
|
|
277
283
|
if (!ROUTING_FAILURE_CLAIM.test(this.buffered)) return false;
|
|
278
284
|
const nativeTools = new Set(
|
|
279
|
-
[...this.buffered.matchAll(ROUTING_NATIVE_TOOL_NAME)].map(match => match[1]?.toLowerCase()),
|
|
285
|
+
[...this.buffered.matchAll(ROUTING_NATIVE_TOOL_NAME)].map(match => match[1]?.toLowerCase() ?? "shell"),
|
|
280
286
|
);
|
|
281
287
|
if (nativeTools.size === 0) return false;
|
|
282
288
|
return ROUTING_REDIRECT_CLAIM.test(this.buffered) || nativeTools.size >= 2;
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
createCursorContextUsageTracker,
|
|
21
21
|
createCursorProtobufEventState,
|
|
22
22
|
finalizeTurnEvents,
|
|
23
|
+
hasBufferedTextToolCalls,
|
|
23
24
|
mapCursorProtobufServerMessage,
|
|
24
25
|
mapSyntheticMcpExecToToolEvents,
|
|
25
26
|
reportableContextTokens,
|
|
@@ -732,6 +733,8 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
732
733
|
syntheticStructuredEditToolNames,
|
|
733
734
|
translatorBudget: this.translatorBudget,
|
|
734
735
|
contextUsage,
|
|
736
|
+
wireModelId: request.modelId,
|
|
737
|
+
identityScope: request._cursorIdentityScope,
|
|
735
738
|
...(prepared.estimatedInputTokens !== undefined
|
|
736
739
|
? { estimatedInputTokens: prepared.estimatedInputTokens }
|
|
737
740
|
: {}),
|
|
@@ -1265,6 +1268,7 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
1265
1268
|
state.openToolCalls.size > 0
|
|
1266
1269
|
|| this.sawAssistantText
|
|
1267
1270
|
|| hasPendingClientToolFinalization
|
|
1271
|
+
|| hasBufferedTextToolCalls(state)
|
|
1268
1272
|
)
|
|
1269
1273
|
) {
|
|
1270
1274
|
const terminal = hasPendingClientToolFinalization && state.openToolCalls.size === 0
|
|
@@ -1431,7 +1435,7 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
1431
1435
|
settler.settleFinish();
|
|
1432
1436
|
return;
|
|
1433
1437
|
}
|
|
1434
|
-
if (this.framesReceived > 0 && this.sawAssistantText) {
|
|
1438
|
+
if (this.framesReceived > 0 && (this.sawAssistantText || hasBufferedTextToolCalls(state))) {
|
|
1435
1439
|
for (const event of finalizeTurnEvents(state)) push(event);
|
|
1436
1440
|
releaseBacklogLease();
|
|
1437
1441
|
settler.settleFinish();
|