@bitkyc08/opencodex 2.78.0 → 2.80.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/gui/dist/assets/App-CSJvmjNs.css +1 -0
- package/gui/dist/assets/App-MbDVfUeC.js +54 -0
- package/gui/dist/assets/{Tray-DiJpSub0.js → Tray-CzkxK82A.js} +1 -1
- package/gui/dist/assets/{index-DmQB8D_R.css → index-Brk6F3fn.css} +1 -1
- package/gui/dist/assets/index-UJt6npXk.js +94 -0
- package/gui/dist/assets/{quota-summary-B_a96nXj.js → quota-summary-DU3sD6Z-.js} +1 -1
- package/gui/dist/index.html +2 -2
- package/package.json +3 -2
- package/src/adapters/cursor/live-transport.ts +5 -3
- package/src/adapters/cursor/native-exec-fs.ts +12 -9
- package/src/adapters/cursor/native-exec.ts +13 -5
- package/src/adapters/cursor/protobuf-request.ts +13 -7
- package/src/adapters/cursor/request-builder.ts +23 -10
- package/src/adapters/cursor/tool-definitions.ts +9 -6
- package/src/adapters/cursor/tool-guidance.ts +29 -8
- package/src/adapters/cursor/tool-naming.ts +40 -7
- package/src/adapters/cursor/tool-wording.ts +46 -0
- package/src/adapters/cursor.ts +3 -0
- package/src/adapters/devin.ts +37 -2
- package/src/adapters/openai-chat/messages.ts +3 -1
- package/src/claude/desktop-picker.ts +11 -9
- package/src/claude/inbound-content-options.ts +29 -2
- package/src/claude/inbound.ts +9 -0
- package/src/claude/intercept/local-ca-files.ts +265 -0
- package/src/claude/intercept/local-ca-windows.ts +73 -0
- package/src/claude/intercept/local-ca.ts +12 -24
- package/src/claude/intercept/picker-ca-persistence.ts +106 -0
- package/src/claude/intercept/picker-ca-startup.ts +16 -0
- package/src/claude/intercept/picker-ca-store.ts +70 -0
- package/src/claude/intercept/picker-ca.ts +43 -9
- package/src/claude/intercept/picker-runtime.ts +14 -4
- package/src/claude/intercept/runtime.ts +32 -23
- package/src/cli/ensure-readiness.ts +48 -0
- package/src/cli/gui-pair-client.ts +15 -7
- package/src/cli/gui.ts +4 -1
- package/src/cli/hub.ts +1 -0
- package/src/cli/index.ts +10 -11
- package/src/cli/status.ts +2 -2
- package/src/cli/update-restart-child.ts +4 -1
- package/src/codex/account-credit-use.ts +8 -2
- package/src/codex/auth-api/account-pause-group.ts +113 -0
- package/src/codex/auth-api/routes.ts +18 -12
- package/src/codex/auth-context.ts +45 -3
- package/src/codex/autostart-health.ts +13 -3
- package/src/codex/journal.ts +36 -5
- package/src/codex/pool-credit-policy.ts +67 -0
- package/src/codex/shim-overlay.ts +9 -1
- package/src/combos/jev.ts +3 -1
- package/src/generated/compatibility-version.json +135 -63
- package/src/lib/gui-pair-capability.ts +1 -1
- package/src/lib/gui-pair-intent.ts +170 -0
- package/src/lib/windows-owner-acl.ts +143 -0
- package/src/providers/openai-sidecar-credit.ts +26 -0
- package/src/providers/openai-sidecar.ts +35 -0
- package/src/server/claude-messages.ts +1 -0
- package/src/server/gui-pair-delivery.ts +23 -0
- package/src/server/index/serve-options.ts +5 -8
- package/src/server/index.ts +12 -14
- package/src/server/live-native-calls.ts +94 -0
- package/src/server/live.ts +28 -5
- package/src/server/local-management-read-client.ts +15 -11
- package/src/server/management/oauth-account-routes.ts +15 -0
- package/src/server/management/route-registry.ts +1 -0
- package/src/server/management/shared.ts +9 -3
- package/src/server/messages-native-scope.ts +60 -0
- package/src/server/messages-native.ts +9 -0
- package/src/server/responses/adapter-dispatch.ts +31 -2
- package/src/server/responses/combo-requested-effort.ts +14 -0
- package/src/server/responses/compact.ts +17 -2
- package/src/server/responses/core-auth.ts +18 -1
- package/src/server/responses/core-codex-account.ts +18 -8
- package/src/server/responses/core-combo.ts +5 -7
- package/src/server/responses/non-replayable-error.ts +48 -0
- package/src/server/responses/passthrough-delivery.ts +5 -1
- package/src/server/responses/passthrough-dispatch.ts +21 -19
- package/src/server/responses/request-prepare.ts +11 -1
- package/src/server/responses/sidecar-execution.ts +1 -0
- package/src/server/search.ts +4 -0
- package/src/server/startup-health-cache.ts +2 -6
- package/src/service/desktop-startup.ts +100 -3
- package/src/vision/describe.ts +17 -10
- package/src/vision/index.ts +1 -0
- package/src/web-search/executor.ts +17 -10
- package/src/web-search/format-result.ts +304 -20
- package/src/web-search/header-deadline.ts +67 -0
- package/src/web-search/loop.ts +7 -6
- package/src/web-search/passthrough-bridge.ts +1 -0
- package/src/web-search/run-turn-loop.ts +1 -1
- package/gui/dist/assets/App-BtfG5id-.js +0 -54
- package/gui/dist/assets/App-lCIslZ1L.css +0 -1
- package/gui/dist/assets/index-Btc3Ogw4.js +0 -94
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { OcxRequestOptions, OcxTool } from "../../types";
|
|
2
|
+
import { CURSOR_TOOL_CALL_CONTINUATION, cursorUsesPlainToolWording } from "./tool-wording";
|
|
2
3
|
import { CODE_MODE_HOST_CONTRACT_SENTENCE, CODE_MODE_RESULT_ECHO_SENTENCE } from "../exec-tool-result-normalize";
|
|
3
|
-
import { CODEX_SHELL_BRIDGE_TOOL_NAMES, CODEX_TOOL_SEARCH_TOOL, CODEX_UNIFIED_EXEC_TOOL, clientSemanticToolNameFromCursorWire, cursorRequestAdvertisesApplyPatch, cursorRequestHasExecutionPath, cursorRequestHasShellAlias, cursorRequestUsesCodeMode, cursorToolAllowedByChoice, cursorToolWireName, isCodexShellBridgeToolName, isCursorExecutionPathTool, isCursorStructuredEditToolName } from "./tool-naming";
|
|
4
|
+
import { CODEX_SHELL_BRIDGE_TOOL_NAMES, CODEX_TOOL_SEARCH_TOOL, CODEX_UNIFIED_EXEC_TOOL, clientSemanticToolNameFromCursorWire, cursorRequestAdvertisesApplyPatch, cursorRequestHasExecutionPath, cursorRequestHasShellAlias, cursorRequestUsesCodeMode, cursorToolAllowedByChoice, cursorToolWireName, isBareCodexShellBridgeTool, isClaudeClientBareToolName, isCodexShellBridgeToolName, isCursorExecutionPathTool, isCursorStructuredEditToolName } from "./tool-naming";
|
|
4
5
|
|
|
5
6
|
export const CURSOR_SHELL_ALIAS_SYSTEM_NOTE =
|
|
6
7
|
'Shell commands use the Codex shell bridge tool shown in this turn\'s catalog (`shell_command` or `exec_command`) with JSON arguments like {"cmd":"..."}. The long `mcp_opencodex-responses_*` display name is the same tool. Prefer it over Cursor-native Shell.';
|
|
@@ -129,7 +130,15 @@ function advertisedCoversNeighbor(wireNames: readonly string[], neighbor: (typeo
|
|
|
129
130
|
return NEIGHBOR_AGENT_TOOL_ALIASES[neighbor].some(alias => advertised.has(alias.toLowerCase()));
|
|
130
131
|
}
|
|
131
132
|
|
|
132
|
-
function unavailableNeighborAgentToolNames(
|
|
133
|
+
function unavailableNeighborAgentToolNames(
|
|
134
|
+
wireNames: readonly string[],
|
|
135
|
+
catalog?: readonly Pick<OcxTool, "namespace" | "name">[],
|
|
136
|
+
): string[] {
|
|
137
|
+
if (
|
|
138
|
+
catalog
|
|
139
|
+
&& !catalog.some(isBareCodexShellBridgeTool)
|
|
140
|
+
&& catalog.some(tool => !tool.namespace && isClaudeClientBareToolName(tool.name))
|
|
141
|
+
) return [];
|
|
133
142
|
return NEIGHBOR_AGENT_TOOL_NAMES.filter(name => !advertisedCoversNeighbor(wireNames, name));
|
|
134
143
|
}
|
|
135
144
|
|
|
@@ -144,12 +153,14 @@ function discoveryToolLabel(wireNames: readonly string[]): string | undefined {
|
|
|
144
153
|
export function buildCursorToolGuidanceSystemNote(
|
|
145
154
|
tools: readonly Pick<OcxTool, "namespace" | "name" | "freeform">[] | undefined,
|
|
146
155
|
toolChoice?: OcxRequestOptions["toolChoice"],
|
|
156
|
+
modelId?: string,
|
|
147
157
|
): string | undefined {
|
|
148
158
|
if (!tools?.length) return undefined;
|
|
159
|
+
const plainToolWording = cursorUsesPlainToolWording(modelId);
|
|
149
160
|
const wireNames = [...new Set(
|
|
150
161
|
tools
|
|
151
162
|
.filter(tool => cursorToolAllowedByChoice(tool, toolChoice, tools))
|
|
152
|
-
.map(tool => cursorToolWireName(tool)),
|
|
163
|
+
.map(tool => cursorToolWireName(tool, tools)),
|
|
153
164
|
)];
|
|
154
165
|
if (wireNames.length === 0) return undefined;
|
|
155
166
|
|
|
@@ -170,7 +181,7 @@ export function buildCursorToolGuidanceSystemNote(
|
|
|
170
181
|
?.filter(tool => !tool.namespace && isCursorStructuredEditToolName(tool.name))
|
|
171
182
|
.map(tool => tool.name) ?? [];
|
|
172
183
|
const discoveryTools = discoveryToolLabel(wireNames);
|
|
173
|
-
const unavailableNeighborNames = unavailableNeighborAgentToolNames(wireNames);
|
|
184
|
+
const unavailableNeighborNames = unavailableNeighborAgentToolNames(wireNames, tools);
|
|
174
185
|
// Host-shell-neutral: the Codex client executes bridge commands, and may differ from
|
|
175
186
|
// the OpenCodex proxy OS (LAN/SSH remote-proxy). Always cover PowerShell 5.1 pitfalls.
|
|
176
187
|
const hostShellNote = hasBareExec
|
|
@@ -179,6 +190,9 @@ export function buildCursorToolGuidanceSystemNote(
|
|
|
179
190
|
const notes = [
|
|
180
191
|
`Cursor tool calls: available tool names are exactly ${listedNames}.`,
|
|
181
192
|
"Use the current tool catalog as ground truth and call only those exact names with their listed argument keys.",
|
|
193
|
+
plainToolWording && !codeMode && !hasBareExec
|
|
194
|
+
? "Cursor-native Read/Glob/Grep/LS/Shell/Write/Fetch are not available in this request. Use the listed tool that fits the operation."
|
|
195
|
+
: undefined,
|
|
182
196
|
unavailableNeighborNames.length > 0
|
|
183
197
|
? `This turn does not expose neighboring-agent tool names ${quotedNames(unavailableNeighborNames)}; do not call or suggest them unless the catalog lists them.`
|
|
184
198
|
: undefined,
|
|
@@ -191,18 +205,24 @@ export function buildCursorToolGuidanceSystemNote(
|
|
|
191
205
|
? CODE_MODE_RESULT_ECHO_SENTENCE + " There is no `require`, no `module`, and no filesystem or network globals; reach the host only through the nested helpers. " + CODE_MODE_HOST_CONTRACT_SENTENCE
|
|
192
206
|
: undefined,
|
|
193
207
|
codeMode
|
|
194
|
-
?
|
|
208
|
+
? plainToolWording
|
|
209
|
+
? "Cursor-native Shell, Read, Grep, List, and Write are not available in this request. The exec code cell and its nested helpers provide the execution path; separately listed tools remain callable at the top level."
|
|
210
|
+
: "NEVER attempt Cursor-native Shell, Read, Grep, List, Write, or any tool absent from the catalog — they are not executed in this environment and every probe wastes a turn. The exec code cell (with its nested helpers) is the ONLY execution surface; go to it directly on the FIRST attempt and do not narrate switching surfaces."
|
|
195
211
|
: undefined,
|
|
196
212
|
hasBareExec
|
|
197
213
|
? `${shellBridgeLabel} is the Codex Responses shell bridge for this turn, exposed through Cursor's tool protocol; it is not an external MCP server tool. \`shell_command\` and \`exec_command\` are aliases of the same bridge.`
|
|
198
214
|
: undefined,
|
|
199
215
|
hasBareExec
|
|
200
|
-
?
|
|
216
|
+
? plainToolWording
|
|
217
|
+
? "Your tool list may display the same shell bridge under a longer `mcp_opencodex-responses_shell_command` / `mcp_opencodex-responses_exec_command` name; call the name listed in the catalog."
|
|
218
|
+
: "Your tool list may display it under a longer `mcp_opencodex-responses_shell_command` / `mcp_opencodex-responses_exec_command` name; those are the SAME tool — call whichever your list shows, and do not comment on the naming difference to the user."
|
|
201
219
|
: undefined,
|
|
202
220
|
hasBareExec
|
|
203
|
-
?
|
|
221
|
+
? plainToolWording
|
|
222
|
+
? `Cursor-native Shell, Read, Grep, List, and Write are not available in this request. ${shellBridgeLabel} provides shell execution through the Codex client host.`
|
|
223
|
+
: `NEVER attempt Cursor-native Shell, Read, Grep, List, Write, or any tool not in the catalog above — they are not executed locally in this environment and every attempt wastes a turn and can stall the session. ${shellBridgeLabel} is the ONLY shell surface; go to it directly on the FIRST attempt, never as a fallback after probing a native tool. Do not narrate switching surfaces ("native is blocked, using the bridge instead") — there is exactly one surface.`
|
|
204
224
|
: undefined,
|
|
205
|
-
hasBareExec
|
|
225
|
+
hasBareExec && !plainToolWording
|
|
206
226
|
? "Tool-selection commentary is forbidden: for any shell, read, grep, list, or file operation, your FIRST visible action is the bridge call itself — never a sentence about which tool you will use, which tool was redirected, or switching surfaces. Words like 차단/전환/blocked/switching must not appear in your output for tool-routing reasons."
|
|
207
227
|
: undefined,
|
|
208
228
|
hostShellNote,
|
|
@@ -233,5 +253,6 @@ export function buildCursorToolGuidanceSystemNote(
|
|
|
233
253
|
? `For every file read, directory listing, grep, or shell operation use ${shellBridgeLabel} directly with host-shell-safe commands (POSIX: \`cat\`/\`ls\`/\`rg\`; Windows PowerShell: \`Get-Content\`/\`Get-ChildItem\`/\`Select-String\`). For file edits, use ${structuredEditNames.length > 0 ? `the structured edit tools (${quotedNames(structuredEditNames)}) or ` : ""}\`apply_patch\` when available.`
|
|
234
254
|
: undefined,
|
|
235
255
|
].filter((note): note is string => typeof note === "string");
|
|
256
|
+
if (plainToolWording) notes.push(CURSOR_TOOL_CALL_CONTINUATION);
|
|
236
257
|
return notes.join(" ");
|
|
237
258
|
}
|
|
@@ -67,11 +67,11 @@ function cursorToolChoiceMatches(
|
|
|
67
67
|
if (catalogHasBareCodexShellBridge(catalog)) {
|
|
68
68
|
return isBareCodexShellBridgeTool(tool);
|
|
69
69
|
}
|
|
70
|
-
return tool.name === choiceName || cursorToolWireName(tool) === choiceName;
|
|
70
|
+
return tool.name === choiceName || cursorToolWireName(tool, catalog) === choiceName;
|
|
71
71
|
}
|
|
72
72
|
if (tool.name === choiceName) return true;
|
|
73
73
|
if (cursorToolChoiceAliases(tool).includes(choiceName)) return true;
|
|
74
|
-
return cursorToolWireName(tool) === choiceName
|
|
74
|
+
return cursorToolWireName(tool, catalog) === choiceName
|
|
75
75
|
&& !catalog.some(candidate => candidate.name === choiceName);
|
|
76
76
|
}
|
|
77
77
|
|
|
@@ -178,16 +178,49 @@ const CURSOR_PROXY_OWNED_BARE_TOOL_NAMES = new Set([
|
|
|
178
178
|
CODEX_TOOL_SEARCH_TOOL,
|
|
179
179
|
]);
|
|
180
180
|
|
|
181
|
+
export const CLAUDE_CLIENT_BARE_TOOL_NAMES = new Set([
|
|
182
|
+
"Bash",
|
|
183
|
+
"Read",
|
|
184
|
+
"Write",
|
|
185
|
+
"Edit",
|
|
186
|
+
"Grep",
|
|
187
|
+
"Glob",
|
|
188
|
+
"Task",
|
|
189
|
+
"GetDynamicTools",
|
|
190
|
+
"EnterMode",
|
|
191
|
+
"ExitMode",
|
|
192
|
+
"Todo",
|
|
193
|
+
"AddTodo",
|
|
194
|
+
"UpdateTodo",
|
|
195
|
+
"ViewTodo",
|
|
196
|
+
"WebSearch",
|
|
197
|
+
"FetchUrl",
|
|
198
|
+
"KillProcess",
|
|
199
|
+
"ListProcesses",
|
|
200
|
+
"ViewProcessOutput",
|
|
201
|
+
]);
|
|
202
|
+
|
|
203
|
+
export function isClaudeClientBareToolName(name: string): boolean {
|
|
204
|
+
return CLAUDE_CLIENT_BARE_TOOL_NAMES.has(name);
|
|
205
|
+
}
|
|
206
|
+
|
|
181
207
|
/** Avoid collisions with Cursor's private bare-tool namespace. */
|
|
182
|
-
function isCursorBareClientToolWireAliased(
|
|
208
|
+
export function isCursorBareClientToolWireAliased(
|
|
183
209
|
tool: Pick<OcxTool, "namespace" | "name">,
|
|
210
|
+
catalog?: readonly Pick<OcxTool, "namespace" | "name">[],
|
|
184
211
|
): boolean {
|
|
185
|
-
|
|
186
|
-
|
|
212
|
+
if (tool.namespace || CURSOR_PROXY_OWNED_BARE_TOOL_NAMES.has(tool.name)) return false;
|
|
213
|
+
if (isClaudeClientBareToolName(tool.name)) {
|
|
214
|
+
return catalog ? catalogHasBareCodexShellBridge(catalog) : false;
|
|
215
|
+
}
|
|
216
|
+
return true;
|
|
187
217
|
}
|
|
188
218
|
|
|
189
|
-
export function cursorToolWireName(
|
|
190
|
-
|
|
219
|
+
export function cursorToolWireName(
|
|
220
|
+
tool: Pick<OcxTool, "namespace" | "name">,
|
|
221
|
+
catalog?: readonly Pick<OcxTool, "namespace" | "name">[],
|
|
222
|
+
): string {
|
|
223
|
+
if (isCursorBareClientToolWireAliased(tool, catalog)) {
|
|
191
224
|
return `${CURSOR_CLIENT_TOOL_WIRE_PREFIX}${tool.name}`;
|
|
192
225
|
}
|
|
193
226
|
return namespacedToolName(tool.namespace, tool.name);
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { OcxTool } from "../../types";
|
|
2
|
+
import type { McpToolDefinition } from "./gen/agent_pb";
|
|
3
|
+
import { CODE_MODE_RESULT_ECHO_SENTENCE } from "../exec-tool-result-normalize";
|
|
4
|
+
import { OCX_RESPONSES_TOOL_PROVIDER, cursorRequestHasExecutionPath, cursorRequestHasShellAlias, cursorRequestUsesCodeMode, cursorToolWireName } from "./tool-naming";
|
|
5
|
+
|
|
6
|
+
/** Cursor may expose Claude with version-first, family-first, or effort-suffixed ids. */
|
|
7
|
+
export function cursorUsesPlainToolWording(modelId?: string): boolean {
|
|
8
|
+
return /^(?:cursor\/)?claude-/i.test(modelId?.trim() ?? "");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const CURSOR_TOOL_CALL_CONTINUATION = "Continue the task with that tool call.";
|
|
12
|
+
const CURSOR_NO_CLIENT_TOOLS = "Cursor-native filesystem, shell, and fetch tools are not available through this execution channel. No client tools are available in this request. Answer without tools using the information already provided, or report that the requested operation cannot be completed without tools.";
|
|
13
|
+
|
|
14
|
+
/** Factual catalog wording; legacy model wording is owned by native-exec.ts unchanged. */
|
|
15
|
+
export function cursorPlainNativeExecRedirectHint(
|
|
16
|
+
tools: readonly Pick<OcxTool, "namespace" | "name" | "freeform">[] | undefined,
|
|
17
|
+
mcpToolDefs: readonly Pick<McpToolDefinition, "name" | "providerIdentifier">[],
|
|
18
|
+
): string | undefined {
|
|
19
|
+
const clientTools = tools ?? [];
|
|
20
|
+
const names = [...new Set([
|
|
21
|
+
...clientTools.map(tool => cursorToolWireName(tool, clientTools)),
|
|
22
|
+
...mcpToolDefs.map(def => `mcp_${def.providerIdentifier}_${def.name}`),
|
|
23
|
+
])];
|
|
24
|
+
if (names.length === 0) return CURSOR_NO_CLIENT_TOOLS;
|
|
25
|
+
const shown = names.slice(0, 16).map(name => `\`${name}\``).join(", ");
|
|
26
|
+
const more = names.length > 16 ? ` (+${names.length - 16} more)` : "";
|
|
27
|
+
const catalog = `Available tools in this request's catalog: ${shown}${more}. `
|
|
28
|
+
+ `The harness displays a \`${OCX_RESPONSES_TOOL_PROVIDER}\` entry as \`mcp_${OCX_RESPONSES_TOOL_PROVIDER}_<name>\`; that is the same tool. `
|
|
29
|
+
+ "Cursor-native Read/Glob/Grep/LS/Shell/Write/Fetch are not available in this request. ";
|
|
30
|
+
if (!cursorRequestHasShellAlias(clientTools) && cursorRequestUsesCodeMode(clientTools)) {
|
|
31
|
+
return catalog + "`exec` takes a JavaScript body evaluated in a V8 isolate, not a shell command. Shell, file, search, and fetch are nested helpers called inside it as `await tools.<name>(...)`, for example `text(await tools.exec_command({cmd: \"ls\"}))`. "
|
|
32
|
+
+ "`shell_command` and `exec_command` are nested helpers, not top-level tools here. Every other listed tool remains callable at the top level. "
|
|
33
|
+
+ CODE_MODE_RESULT_ECHO_SENTENCE + " " + CURSOR_TOOL_CALL_CONTINUATION;
|
|
34
|
+
}
|
|
35
|
+
const operation = cursorRequestHasExecutionPath(clientTools)
|
|
36
|
+
? "Use the listed execution tool with its advertised argument schema and the Codex client host's shell syntax; use a listed edit tool for file edits. "
|
|
37
|
+
: "Use the listed tool that fits the operation: a file, search, or fetch tool when provided, or a listed worker-agent delegation tool. ";
|
|
38
|
+
return catalog + operation + CURSOR_TOOL_CALL_CONTINUATION;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Fallback uses advertised dispatcher definitions, never assumed shell/edit tools. */
|
|
42
|
+
export function cursorPlainNativeExecFallback(
|
|
43
|
+
toolDefs: readonly Pick<McpToolDefinition, "name" | "providerIdentifier">[] = [],
|
|
44
|
+
): string {
|
|
45
|
+
return cursorPlainNativeExecRedirectHint([], toolDefs)!;
|
|
46
|
+
}
|
package/src/adapters/cursor.ts
CHANGED
|
@@ -65,6 +65,7 @@ import {
|
|
|
65
65
|
type CursorTransportFactory,
|
|
66
66
|
} from "./cursor/transport";
|
|
67
67
|
import { cursorLiveRosterScope } from "./cursor/catalog";
|
|
68
|
+
import { cursorUsesPlainToolWording } from "./cursor/tool-wording";
|
|
68
69
|
|
|
69
70
|
export const CURSOR_API_URL = "https://api2.cursor.sh";
|
|
70
71
|
|
|
@@ -313,8 +314,10 @@ export function createCursorAdapter(provider: OcxProviderConfig, deps: CursorAda
|
|
|
313
314
|
const armMidstreamEcho = cursorNeedsExternalToolContinuation(activeRequest.modelId) && replaysToolResult;
|
|
314
315
|
const midstreamObserver = armMidstreamEcho ? new CursorMidstreamEchoObserver() : undefined;
|
|
315
316
|
const midstreamFilter = armMidstreamEcho ? new ToolEnvelopeEchoFilter() : undefined;
|
|
317
|
+
// Claude's factual redirects allow truthful routing prose; it is not a failure claim.
|
|
316
318
|
const armRoutingCommentarySniffer =
|
|
317
319
|
isCursorExternalWireModel(activeRequest.modelId)
|
|
320
|
+
&& !cursorUsesPlainToolWording(activeRequest.modelId)
|
|
318
321
|
&& (
|
|
319
322
|
cursorRequestUsesCodeMode(activeRequest.tools, activeRequest.toolChoice)
|
|
320
323
|
|| cursorRequestHasShellAlias(activeRequest.tools)
|
package/src/adapters/devin.ts
CHANGED
|
@@ -523,10 +523,45 @@ export function mapOcxMessagesToDevin(
|
|
|
523
523
|
.join("\n\n");
|
|
524
524
|
if (system) items.push({ role: "system", content: system });
|
|
525
525
|
|
|
526
|
+
let previousToolResult: ChatHistoryItem | undefined;
|
|
527
|
+
let textChunks: string[] | undefined;
|
|
528
|
+
const flushText = () => {
|
|
529
|
+
if (previousToolResult && textChunks) previousToolResult.content = textChunks.join("\n\n");
|
|
530
|
+
textChunks = undefined;
|
|
531
|
+
};
|
|
526
532
|
for (const message of parsed.context.messages) {
|
|
533
|
+
// Original-message adjacency matters even when an intervening message maps to nothing.
|
|
534
|
+
if (message.role !== "toolResult") {
|
|
535
|
+
flushText();
|
|
536
|
+
previousToolResult = undefined;
|
|
537
|
+
}
|
|
527
538
|
const mapped = mapOneMessage(message, parsed.modelId, options);
|
|
528
|
-
if (mapped)
|
|
539
|
+
if (!mapped) continue;
|
|
540
|
+
if (mapped.role === "tool") {
|
|
541
|
+
if (previousToolResult && previousToolResult.tool_call_id === mapped.tool_call_id) {
|
|
542
|
+
const previous = previousToolResult;
|
|
543
|
+
if (textChunks && typeof mapped.content === "string") {
|
|
544
|
+
textChunks.push(mapped.content);
|
|
545
|
+
} else {
|
|
546
|
+
flushText();
|
|
547
|
+
// mapOneMessage owns these wire arrays; append without recopying their prefix.
|
|
548
|
+
const parts: ContentPart[] = typeof previous.content === "string"
|
|
549
|
+
? [{ type: "text", text: previous.content }] : previous.content;
|
|
550
|
+
parts.push({ type: "text", text: "\n\n" });
|
|
551
|
+
if (typeof mapped.content === "string") parts.push({ type: "text", text: mapped.content });
|
|
552
|
+
else for (const part of mapped.content) parts.push(part);
|
|
553
|
+
previous.content = parts;
|
|
554
|
+
}
|
|
555
|
+
if (mapped.is_error) previous.is_error = true;
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
flushText();
|
|
559
|
+
previousToolResult = mapped;
|
|
560
|
+
textChunks = typeof mapped.content === "string" ? [mapped.content] : undefined;
|
|
561
|
+
}
|
|
562
|
+
items.push(mapped);
|
|
529
563
|
}
|
|
564
|
+
flushText();
|
|
530
565
|
return items;
|
|
531
566
|
}
|
|
532
567
|
|
|
@@ -774,7 +809,7 @@ export function createDevinAdapter(
|
|
|
774
809
|
// An admitted HTTP turn owns globally shared capacity until this call
|
|
775
810
|
// emits. Without an explicit wait allowance, preserve the typed reset
|
|
776
811
|
// delay in generated diagnostic wording and return immediately.
|
|
777
|
-
const signedMessages =
|
|
812
|
+
const signedMessages = messages; // Reuse the history already mapped above.
|
|
778
813
|
// A Claude signature is replayed because it is what carries the reasoning into this
|
|
779
814
|
// turn, but Cognition streams Claude's thinking as a summary the signature does not
|
|
780
815
|
// cover, and some replays are refused with invalid_argument before any output. That
|
|
@@ -144,7 +144,9 @@ export function messagesToChatFormat(parsed: OcxParsedRequest, provider: OcxProv
|
|
|
144
144
|
const nativeOpenAI = isNativeOpenAIChatTarget(provider);
|
|
145
145
|
// Qwen3.8-27B's pinned chat template rejects non-leading system and every developer role;
|
|
146
146
|
// a later user is rendered in place. Keep this exception specific to that template family.
|
|
147
|
-
|
|
147
|
+
// Gateways serve it under namespaced ids (LiteLLM's openai/Qwen3.8-27B, #6674), so any number
|
|
148
|
+
// of leading path segments is accepted; the final segment still has to name the checkpoint.
|
|
149
|
+
const qwen38LeadingSystemTemplate = !nativeOpenAI && /^(?:[^/]+\/)*(?:Qwen3\.8-27B|qwen3-8-27b(?:-(?:fp8|lora))?)$/i.test(parsed.modelId);
|
|
148
150
|
// Which role a developer message carries, and why the unrecorded state folds, is stated once
|
|
149
151
|
// in ./developer-role.ts and read from there by the native passthrough as well. Either way the
|
|
150
152
|
// message keeps the slot it arrived in — only the role changes, never the position.
|
|
@@ -10,6 +10,8 @@ import { getConfigDir } from "../config/paths";
|
|
|
10
10
|
import { readFileSync, existsSync } from "node:fs";
|
|
11
11
|
import { acceptsPickerAuthority, pickerCaCertPath, pickerCaFingerprints, ensurePickerCa, issuePickerLeaf, pickerLeafCertPath, publishedPickerCaSha256 } from "./intercept/picker-ca";
|
|
12
12
|
import { inspectPickerTrust, trustPickerCa, untrustPickerCa } from "./intercept/picker-trust";
|
|
13
|
+
import { preparePersistentPickerAuthority } from "./intercept/picker-ca-startup";
|
|
14
|
+
import type { PickerCaStore } from "./intercept/picker-ca-store";
|
|
13
15
|
import type { PickerRuntime } from "./intercept/picker-runtime";
|
|
14
16
|
import type { PickerTrustState, SecurityRunner } from "./intercept/picker-trust";
|
|
15
17
|
import {
|
|
@@ -52,6 +54,8 @@ export interface DesktopPickerEnableOptions {
|
|
|
52
54
|
persist: boolean;
|
|
53
55
|
context: "cli-trusted" | "server";
|
|
54
56
|
callerAddedTrust?: boolean;
|
|
57
|
+
/** Normal restart only inspects trust; explicit enable keeps the consent flow. */
|
|
58
|
+
allowTrustPrompt?: boolean;
|
|
55
59
|
}
|
|
56
60
|
|
|
57
61
|
export interface DesktopPickerOps {
|
|
@@ -74,6 +78,7 @@ export interface DesktopPickerControllerDeps {
|
|
|
74
78
|
/** Bound picker CONNECT proxy port (Desktop's egress), or null when it is not running. */
|
|
75
79
|
proxyPort: () => number | null;
|
|
76
80
|
configDir: string;
|
|
81
|
+
persistentAuthority?: { store?: PickerCaStore };
|
|
77
82
|
security?: SecurityRunner;
|
|
78
83
|
platform?: NodeJS.Platform;
|
|
79
84
|
applyProfile?: typeof applyDesktopPickerProfile;
|
|
@@ -185,13 +190,8 @@ export function createDesktopPickerController(deps: DesktopPickerControllerDeps)
|
|
|
185
190
|
// An already selected owned profile proves that another successful enable still relies on
|
|
186
191
|
// this CA. Keep its trust even when the current request is refused.
|
|
187
192
|
if (inspect().kind === "applied") return true;
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const result = await untrustPickerCa(
|
|
191
|
-
pickerCaCertPath(deps.configDir), pickerCaFingerprints(ca.certPem).sha1, deps.security, platform,
|
|
192
|
-
);
|
|
193
|
-
return result.ok;
|
|
194
|
-
} catch { return false; }
|
|
193
|
+
// Compensation needs only the published public root, never a signing identity.
|
|
194
|
+
return untrustCurrentCa();
|
|
195
195
|
}
|
|
196
196
|
|
|
197
197
|
function withTrustFailure(status: DesktopPickerStatus, trustFailed: boolean): DesktopPickerStatus {
|
|
@@ -228,7 +228,9 @@ export function createDesktopPickerController(deps: DesktopPickerControllerDeps)
|
|
|
228
228
|
let caPath: string;
|
|
229
229
|
let caSha1: string;
|
|
230
230
|
try {
|
|
231
|
-
const ca =
|
|
231
|
+
const ca = deps.persistentAuthority
|
|
232
|
+
? await preparePersistentPickerAuthority({ configDir: deps.configDir, store: deps.persistentAuthority.store, security: deps.security, platform })
|
|
233
|
+
: ensurePickerCa(deps.configDir);
|
|
232
234
|
// The CLI path gates trust on the same profile check; a published root whose bytes do not
|
|
233
235
|
// match the profile this process mints must not reach the keychain through the server
|
|
234
236
|
// path either, even when a live peer published it.
|
|
@@ -238,7 +240,7 @@ export function createDesktopPickerController(deps: DesktopPickerControllerDeps)
|
|
|
238
240
|
caSha1 = pickerCaFingerprints(ca.certPem).sha1;
|
|
239
241
|
let trust = await inspectPickerTrust(pickerLeafCertPath(deps.configDir), caSha1, deps.security, platform);
|
|
240
242
|
observedTrust = trust;
|
|
241
|
-
if (trust !== "trusted" && options.context === "server") {
|
|
243
|
+
if (trust !== "trusted" && options.context === "server" && options.allowTrustPrompt !== false) {
|
|
242
244
|
const added = await trustPickerCa(caPath, deps.security, platform, { pem: ca.certPem });
|
|
243
245
|
trustedByAttempt = added.ok;
|
|
244
246
|
trust = await inspectPickerTrust(pickerLeafCertPath(deps.configDir), caSha1, deps.security, platform);
|
|
@@ -1,12 +1,39 @@
|
|
|
1
1
|
import { isClaudeWebSearchToolName } from "./outbound";
|
|
2
2
|
import { AnthropicRequestError, isRec, type Rec } from "./inbound-records";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Claude Code opens its system prompt with a per-request billing line
|
|
6
|
+
* (`x-anthropic-billing-header: cc_version=…; cc_entrypoint=…; cch=…;`) whose `cch` value
|
|
7
|
+
* changes on every request. Anthropic excludes it from caching; a translated Responses
|
|
8
|
+
* destination does not, so the first bytes of `instructions` — and the system-derived
|
|
9
|
+
* fallback `prompt_cache_key` — rotated every turn and prefix caching never hit (#6627).
|
|
10
|
+
* Native Anthropic passthrough never reaches this translation and keeps the line.
|
|
11
|
+
*
|
|
12
|
+
* Anchored at the prompt start without the multiline flag, like the Antigravity strip in
|
|
13
|
+
* `src/adapters/google.ts`, so a mention later in the prompt is never touched.
|
|
14
|
+
*/
|
|
15
|
+
function stripClaudeBillingHeader(text: string): string {
|
|
16
|
+
return text.replace(/^x-anthropic-billing-header:[^\n]*\n*/, "");
|
|
17
|
+
}
|
|
18
|
+
|
|
4
19
|
export function systemToInstructions(system: unknown): string | undefined {
|
|
5
|
-
if (typeof system === "string")
|
|
20
|
+
if (typeof system === "string") {
|
|
21
|
+
const text = stripClaudeBillingHeader(system);
|
|
22
|
+
return text.length > 0 ? text : undefined;
|
|
23
|
+
}
|
|
6
24
|
if (Array.isArray(system)) {
|
|
7
25
|
const parts: string[] = [];
|
|
26
|
+
let first = true;
|
|
8
27
|
for (const block of system) {
|
|
9
|
-
if (isRec(block)
|
|
28
|
+
if (!isRec(block) || block.type !== "text" || typeof block.text !== "string") continue;
|
|
29
|
+
if (first) {
|
|
30
|
+
first = false;
|
|
31
|
+
// Only the first text block can carry the billing line; drop it if nothing else remains.
|
|
32
|
+
const text = stripClaudeBillingHeader(block.text);
|
|
33
|
+
if (text.length > 0) parts.push(text);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
parts.push(block.text);
|
|
10
37
|
}
|
|
11
38
|
return parts.length > 0 ? parts.join("\n\n") : undefined;
|
|
12
39
|
}
|
package/src/claude/inbound.ts
CHANGED
|
@@ -72,6 +72,15 @@ function toolResultOutput(block: Rec): string | Rec[] {
|
|
|
72
72
|
if (!isRec(item)) continue;
|
|
73
73
|
if (item.type === "text" && typeof item.text === "string") {
|
|
74
74
|
out.push({ type: "input_text", text: item.text });
|
|
75
|
+
} else if (item.type === "tool_reference" && typeof item.tool_name === "string" && item.tool_name.length > 0) {
|
|
76
|
+
// Responses has no tool_reference result carrier. Preserve ToolSearch's loaded-name
|
|
77
|
+
// evidence as result text so a reference-only reply does not become empty (#6647).
|
|
78
|
+
// This describes client output; it must not create tools or widen authorization.
|
|
79
|
+
// Text-only tool output is later joined without separators, so the marker carries its
|
|
80
|
+
// own line breaks to keep each loaded name distinct from its neighbours.
|
|
81
|
+
const prev = out[out.length - 1];
|
|
82
|
+
const lead = prev?.type === "input_text" && typeof prev.text === "string" && !prev.text.endsWith("\n") ? "\n" : "";
|
|
83
|
+
out.push({ type: "input_text", text: `${lead}Tool loaded: ${item.tool_name}\n` });
|
|
75
84
|
} else if (item.type === "image") {
|
|
76
85
|
const img = imageBlockToInputImage(item);
|
|
77
86
|
if (img) out.push(img);
|