@bitkyc08/opencodex 2.7.42 → 2.7.43
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/README.md +8 -1
- package/bin/ocx.mjs +13 -14
- package/gui/dist/assets/index-Czw-jpTU.css +1 -0
- package/gui/dist/assets/index-cmds12BG.js +67 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/AGENTS.md +28 -0
- package/src/adapters/anthropic.ts +15 -6
- package/src/adapters/cursor/native-exec-shell.ts +18 -6
- package/src/adapters/cursor/protobuf-events.ts +24 -2
- package/src/adapters/cursor/protobuf-request.ts +1 -2
- package/src/adapters/cursor/tool-definitions.ts +68 -29
- package/src/adapters/google-wire-compiler.ts +4 -0
- package/src/adapters/google.ts +128 -2
- package/src/adapters/identity.ts +12 -2
- package/src/adapters/kiro.ts +49 -6
- package/src/adapters/mimo-free.ts +2 -0
- package/src/adapters/openai-responses.ts +246 -59
- package/src/claude/agents-inject.ts +5 -0
- package/src/claude/inbound.ts +26 -9
- package/src/cli/account-auth.ts +1 -1
- package/src/cli/agent-driven.ts +37 -0
- package/src/cli/claude.ts +3 -3
- package/src/cli/doctor.ts +23 -18
- package/src/cli/help.ts +42 -6
- package/src/cli/index.ts +88 -19
- package/src/cli/interactive-confirm.ts +129 -0
- package/src/cli/opencode.ts +701 -0
- package/src/cli/provider-runtime.ts +3 -0
- package/src/cli/provider.ts +31 -10
- package/src/cli/star-prompt.ts +55 -16
- package/src/cli/status.ts +47 -13
- package/src/codex/account-id.ts +34 -0
- package/src/codex/account-lifecycle.ts +4 -1
- package/src/codex/account-namespace-match.ts +63 -0
- package/src/codex/account-namespaces.ts +149 -0
- package/src/codex/account-pause.ts +20 -0
- package/src/codex/account-usability.ts +6 -1
- package/src/codex/app-server-processes.ts +511 -0
- package/src/codex/auth-api.ts +293 -34
- package/src/codex/auth-collision.ts +2 -1
- package/src/codex/auth-context.ts +60 -17
- package/src/codex/catalog/parsing.ts +16 -1
- package/src/codex/catalog/provider-fetch.ts +48 -12
- package/src/codex/catalog/sync.ts +18 -3
- package/src/codex/catalog.ts +1 -1
- package/src/codex/history-provider.ts +145 -1
- package/src/codex/inject.ts +114 -14
- package/src/codex/main-account.ts +2 -8
- package/src/codex/pool-rotation.ts +186 -0
- package/src/codex/quota.ts +15 -0
- package/src/codex/routing.ts +695 -106
- package/src/codex/shim.ts +4 -1
- package/src/codex/subagent-defaults.ts +550 -0
- package/src/codex/subagent-model-fallback.ts +2 -0
- package/src/codex/sync.ts +3 -0
- package/src/config.ts +566 -25
- package/src/images/artifacts.ts +516 -0
- package/src/images/fulfill-video.ts +163 -0
- package/src/images/fulfill.ts +111 -0
- package/src/images/index.ts +4 -0
- package/src/images/loop.ts +789 -0
- package/src/images/plan.ts +133 -0
- package/src/images/synthetic-tool.ts +133 -0
- package/src/images/types.ts +41 -0
- package/src/images/xai-client.ts +141 -0
- package/src/images/xai-video-client.ts +163 -0
- package/src/lib/admin-secrets.ts +25 -0
- package/src/lib/config-ownership.ts +327 -0
- package/src/lib/crash-guard.ts +2 -0
- package/src/lib/destination-policy.ts +122 -4
- package/src/lib/pinned-http.ts +151 -0
- package/src/lib/process-control.ts +2 -2
- package/src/lib/provider-outbound.ts +164 -0
- package/src/lib/provider-url.ts +14 -0
- package/src/lib/proxy-env.ts +18 -0
- package/src/lib/windows-elevation.ts +81 -3
- package/src/lib/windows-secret-acl.ts +189 -12
- package/src/lib/winsw.ts +2 -0
- package/src/oauth/anthropic-routing.ts +570 -0
- package/src/oauth/health.ts +6 -0
- package/src/oauth/index.ts +283 -72
- package/src/oauth/key-providers.ts +17 -6
- package/src/oauth/kimi.ts +2 -0
- package/src/oauth/kiro-credentials.ts +322 -10
- package/src/oauth/kiro.ts +423 -43
- package/src/oauth/login-cli.ts +32 -5
- package/src/oauth/store.ts +54 -4
- package/src/oauth/types.ts +11 -0
- package/src/providers/alibaba-region-migration.ts +16 -3
- package/src/providers/antigravity-models.ts +3 -0
- package/src/providers/api-keys.ts +13 -6
- package/src/providers/derive.ts +6 -0
- package/src/providers/key-failover.ts +24 -4
- package/src/providers/quota.ts +233 -29
- package/src/providers/registry.ts +11 -3
- package/src/responses/parser.ts +11 -0
- package/src/responses/state.ts +22 -8
- package/src/responses/tool-groups.ts +19 -0
- package/src/router.ts +14 -4
- package/src/server/auth-cors.ts +110 -22
- package/src/server/claude-messages.ts +8 -1
- package/src/server/gui-static.ts +30 -6
- package/src/server/images.ts +303 -9
- package/src/server/index.ts +77 -9
- package/src/server/lifecycle.ts +25 -1
- package/src/server/management/agent-settings-routes.ts +28 -4
- package/src/server/management/combo-routes.ts +7 -0
- package/src/server/management/config-routes.ts +5 -2
- package/src/server/management/logs-usage-routes.ts +167 -3
- package/src/server/management/oauth-account-routes.ts +163 -17
- package/src/server/management/provider-routes.ts +35 -7
- package/src/server/management/system-restart.ts +172 -0
- package/src/server/management/system-routes.ts +33 -10
- package/src/server/management-api.ts +2 -2
- package/src/server/management-auth.ts +216 -0
- package/src/server/proxy-liveness.ts +14 -3
- package/src/server/responses/compact.ts +21 -13
- package/src/server/responses/core.ts +583 -152
- package/src/server/responses-image-gen-repair.ts +118 -0
- package/src/server/responses-item-id-repair.ts +10 -85
- package/src/server/sse-payload-rewrite.ts +116 -0
- package/src/server/system-env.ts +28 -10
- package/src/service.ts +49 -2
- package/src/storage/cleanup-job.ts +57 -0
- package/src/storage/cleanup.ts +1504 -28
- package/src/storage/policy-job.ts +366 -0
- package/src/storage/policy-scheduler.ts +40 -0
- package/src/storage/policy-worker.ts +53 -0
- package/src/storage/policy.ts +522 -0
- package/src/storage/restore-job.ts +242 -0
- package/src/storage/restore-worker.ts +52 -0
- package/src/storage/storage-mutation-coordinator.ts +109 -0
- package/src/tray/windows.ts +2 -0
- package/src/types.ts +96 -1
- package/src/update/index.ts +36 -18
- package/src/update/job.ts +103 -12
- package/src/update/npm-invocation.d.mts +23 -0
- package/src/update/npm-invocation.mjs +94 -0
- package/src/usage/debug.ts +2 -0
- package/src/usage/log.ts +4 -0
- package/gui/dist/assets/index-Bl_VBGoI.js +0 -65
- package/gui/dist/assets/index-DfVGuN88.css +0 -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-cmds12BG.js"></script>
|
|
20
|
+
<link rel="stylesheet" crossorigin href="/assets/index-Czw-jpTU.css">
|
|
21
21
|
</head>
|
|
22
22
|
<body>
|
|
23
23
|
<div id="root"></div>
|
package/package.json
CHANGED
package/src/AGENTS.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Source runtime instructions
|
|
2
|
+
|
|
3
|
+
This file applies to `src/` and inherits the repository-wide rules in `/AGENTS.md`.
|
|
4
|
+
|
|
5
|
+
## Runtime and module rules
|
|
6
|
+
|
|
7
|
+
- `src/` is Bun-native TypeScript in strict mode and uses ES modules only.
|
|
8
|
+
- Do not assume a separate server compilation step.
|
|
9
|
+
- Prefer Bun and Web-platform APIs. Introduce a Node-only runtime dependency only when the task explicitly requires compatibility code and the owning module already has that role.
|
|
10
|
+
- Preserve existing public exports and configuration compatibility unless the task explicitly changes them.
|
|
11
|
+
- Read the applicable documents in `structure/` before changing shared routing, adapters, transports, sidecars, authentication, configuration, or server architecture.
|
|
12
|
+
|
|
13
|
+
## Implementation rules
|
|
14
|
+
|
|
15
|
+
- Follow the existing subsystem boundaries and naming patterns.
|
|
16
|
+
- Do not combine unrelated responsibilities to avoid creating another large shared module.
|
|
17
|
+
- Handle asynchronous failures at request, transport, and sidecar boundaries. Optional integrations must degrade through the existing failure representation rather than crash the request path.
|
|
18
|
+
- Provider catalog metadata belongs in the canonical provider registry and derivation flow. Do not duplicate provider facts across independent pickers or seeds.
|
|
19
|
+
- Adapter changes must preserve the internal event contract, streaming behavior, tool calls, cancellation, error mapping, and image handling relevant to that adapter.
|
|
20
|
+
- Authentication, OAuth, token, credential, management API, and CORS changes are security-boundary changes.
|
|
21
|
+
|
|
22
|
+
## Tests and validation
|
|
23
|
+
|
|
24
|
+
- Place focused regression coverage near the existing tests for the affected subsystem.
|
|
25
|
+
- For focused behavior, run the relevant `bun test tests/<name>.test.ts` and `bun run typecheck`.
|
|
26
|
+
- For shared routing, adapters, config, OAuth, or server behavior, also run `bun run test`.
|
|
27
|
+
- For logging, requests, credentials, account data, or fixtures, also run `bun run privacy:scan`.
|
|
28
|
+
- Update `docs-site/` when the change affects user-visible behavior or configuration.
|
|
@@ -250,6 +250,10 @@ function usesNativeAnthropicEndpoint(provider: OcxProviderConfig): boolean {
|
|
|
250
250
|
}
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
+
function anthropicKeyUsesBearer(provider: OcxProviderConfig): boolean {
|
|
254
|
+
return provider.apiKeyTransport === "bearer";
|
|
255
|
+
}
|
|
256
|
+
|
|
253
257
|
/** Map a Responses reasoning effort to an Anthropic extended-thinking budget (tokens, >= 1024). */
|
|
254
258
|
function reasoningBudget(effort: string): number {
|
|
255
259
|
switch (effort) {
|
|
@@ -399,29 +403,33 @@ function messagesToAnthropicFormat(
|
|
|
399
403
|
}
|
|
400
404
|
case "assistant": {
|
|
401
405
|
const aMsg = msg as OcxAssistantMessage;
|
|
402
|
-
const
|
|
406
|
+
const preface: unknown[] = [];
|
|
407
|
+
const toolUses: unknown[] = [];
|
|
403
408
|
const toolUseIds: string[] = [];
|
|
404
409
|
for (const part of aMsg.content) {
|
|
405
410
|
if (part.type === "text") {
|
|
406
411
|
const text = (part as OcxTextContent).text;
|
|
407
|
-
if (text)
|
|
412
|
+
if (text) preface.push({ type: "text", text });
|
|
408
413
|
} else if (part.type === "thinking") {
|
|
409
414
|
const t = part as OcxThinkingContent;
|
|
410
415
|
// Redacted blocks replay verbatim FIRST (they preceded the visible thinking block
|
|
411
416
|
// in the original stream order preserved by the bridge envelope).
|
|
412
417
|
for (const data of t.redacted ?? []) {
|
|
413
|
-
|
|
418
|
+
preface.push({ type: "redacted_thinking", data });
|
|
414
419
|
}
|
|
415
420
|
if (isLikelyRealAnthropicThinkingSignature(t.signature)) {
|
|
416
|
-
|
|
421
|
+
preface.push({ type: "thinking", thinking: t.thinking, signature: t.signature });
|
|
417
422
|
}
|
|
418
423
|
} else if (part.type === "toolCall") {
|
|
419
424
|
const tc = part as OcxToolCall;
|
|
420
425
|
const flatName = namespacedToolName(tc.namespace, tc.name);
|
|
421
426
|
toolUseIds.push(tc.id);
|
|
422
|
-
|
|
427
|
+
toolUses.push({ type: "tool_use", id: tc.id, name: toolNames.toWire(flatName), input: tc.arguments });
|
|
423
428
|
}
|
|
424
429
|
}
|
|
430
|
+
// Anthropic treats text/thinking after tool_use as ending the tool turn, which makes
|
|
431
|
+
// earlier tool_use ids look unpaired (#620 / common multi-step history shape).
|
|
432
|
+
const content = [...preface, ...toolUses];
|
|
425
433
|
if (content.length === 0) break;
|
|
426
434
|
messages.push({ role: "assistant", content });
|
|
427
435
|
if (toolUseIds.length > 0) {
|
|
@@ -694,7 +702,8 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
|
|
|
694
702
|
headers["X-Claude-Code-Session-Id"] = claudeCodeSessionId(provider.apiKey);
|
|
695
703
|
headers["x-client-request-id"] = crypto.randomUUID();
|
|
696
704
|
} else {
|
|
697
|
-
headers["
|
|
705
|
+
if (anthropicKeyUsesBearer(provider)) headers["Authorization"] = `Bearer ${provider.apiKey}`;
|
|
706
|
+
else headers["x-api-key"] = provider.apiKey;
|
|
698
707
|
}
|
|
699
708
|
if (provider.headers) Object.assign(headers, provider.headers);
|
|
700
709
|
|
|
@@ -23,8 +23,20 @@ import { errorText, execBytes, execStreamCloseBytes } from "./native-exec-common
|
|
|
23
23
|
const backgroundShells = new Map<number, { child: ChildProcessWithoutNullStreams; outputLength: number }>();
|
|
24
24
|
let nextShellId = 1;
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
/** Rejection text when Cursor-native shell is denied by policy (issue #604). */
|
|
27
|
+
export function nativeShellDisabledMessage(): string {
|
|
28
|
+
// Do not insist on "the same command" — that steers models into replaying bash/CMD
|
|
29
|
+
// idioms through the Codex bridge on Windows PowerShell 5.1 and looping (#604).
|
|
30
|
+
// Keep this host-shell-neutral: OpenCodex may run on a different OS than the Codex
|
|
31
|
+
// client that executes the bridge (LAN/SSH remote-proxy).
|
|
32
|
+
return (
|
|
33
|
+
"Route this through the Codex bridge shell tool from the current catalog (`shell_command` or `exec_command`, including the long `mcp_opencodex-responses_*` display name if listed). "
|
|
34
|
+
+ "Do not tell the user shell access is blocked, disabled, or denied — silently call that bridge tool. "
|
|
35
|
+
+ "Adapt the command for the Codex client host shell before calling the bridge "
|
|
36
|
+
+ "(Windows PowerShell 5.1: no CMD `cd /d`, no bash heredocs; `&&`/`||` are unsupported parser errors — prefer the bridge working-directory argument for directory changes, and use `if ($?) { ... }` for success-gated follow-up steps; do not treat `;` as a substitute for `&&`). "
|
|
37
|
+
+ "Make at most one corrected bridge attempt after a failure, then report the error and stop — do not repeat equivalent failing commands."
|
|
38
|
+
);
|
|
39
|
+
}
|
|
28
40
|
|
|
29
41
|
function rejectedShellResult(command: string, cwd: string, started: number) {
|
|
30
42
|
return create(ShellResultSchema, {
|
|
@@ -36,7 +48,7 @@ function rejectedShellResult(command: string, cwd: string, started: number) {
|
|
|
36
48
|
exitCode: 1,
|
|
37
49
|
signal: "",
|
|
38
50
|
stdout: "",
|
|
39
|
-
stderr:
|
|
51
|
+
stderr: nativeShellDisabledMessage(),
|
|
40
52
|
executionTime: Date.now() - started,
|
|
41
53
|
aborted: true,
|
|
42
54
|
}),
|
|
@@ -95,7 +107,7 @@ export function rejectShellStreamExecForPolicy(execMsg: ExecServerMessage): Uint
|
|
|
95
107
|
event: { case: "start", value: create(ShellStreamStartSchema, { sandboxPolicy: args.requestedSandboxPolicy }) },
|
|
96
108
|
})),
|
|
97
109
|
execBytes(execMsg, "shellStream", create(ShellStreamSchema, {
|
|
98
|
-
event: { case: "stderr", value: create(ShellStreamStderrSchema, { data:
|
|
110
|
+
event: { case: "stderr", value: create(ShellStreamStderrSchema, { data: nativeShellDisabledMessage() }) },
|
|
99
111
|
})),
|
|
100
112
|
execBytes(execMsg, "shellStream", create(ShellStreamSchema, {
|
|
101
113
|
event: { case: "exit", value: create(ShellStreamExitSchema, { code: 1, cwd, aborted: true }) },
|
|
@@ -196,7 +208,7 @@ export function rejectBackgroundShellSpawnExecForPolicy(execMsg: ExecServerMessa
|
|
|
196
208
|
const args = execMsg.message.value;
|
|
197
209
|
const cwd = resolve(args.workingDirectory || process.cwd());
|
|
198
210
|
return execBytes(execMsg, "backgroundShellSpawnResult", create(BackgroundShellSpawnResultSchema, {
|
|
199
|
-
result: { case: "error", value: create(BackgroundShellSpawnErrorSchema, { command: args.command, workingDirectory: cwd, error:
|
|
211
|
+
result: { case: "error", value: create(BackgroundShellSpawnErrorSchema, { command: args.command, workingDirectory: cwd, error: nativeShellDisabledMessage() }) },
|
|
200
212
|
}));
|
|
201
213
|
}
|
|
202
214
|
|
|
@@ -233,7 +245,7 @@ export function backgroundShellSpawnExec(execMsg: ExecServerMessage): Uint8Array
|
|
|
233
245
|
export function rejectWriteShellStdinExecForPolicy(execMsg: ExecServerMessage): Uint8Array {
|
|
234
246
|
if (execMsg.message.case !== "writeShellStdinArgs") throw new Error("invalid shell stdin exec");
|
|
235
247
|
return execBytes(execMsg, "writeShellStdinResult", create(WriteShellStdinResultSchema, {
|
|
236
|
-
result: { case: "error", value: create(WriteShellStdinErrorSchema, { error:
|
|
248
|
+
result: { case: "error", value: create(WriteShellStdinErrorSchema, { error: nativeShellDisabledMessage() }) },
|
|
237
249
|
}));
|
|
238
250
|
}
|
|
239
251
|
|
|
@@ -3,6 +3,10 @@ import type { AgentServerMessage, McpArgs, ToolCall } from "./gen/agent_pb";
|
|
|
3
3
|
import { decodeCursorArgsMap } from "./arg-codec";
|
|
4
4
|
import { normalizeArgKeys } from "./arg-normalize";
|
|
5
5
|
import {
|
|
6
|
+
cursorShellBridgeArgsValid,
|
|
7
|
+
cursorShellBridgeDropError,
|
|
8
|
+
defaultShellBridgeArgNormalizeSchema,
|
|
9
|
+
isCodexShellBridgeToolName,
|
|
6
10
|
normalizeCursorWireName,
|
|
7
11
|
OCX_RESPONSES_TOOL_PROVIDER,
|
|
8
12
|
resolveShellBridgeAliasKey,
|
|
@@ -324,10 +328,18 @@ export function mapSyntheticMcpExecToToolEvents(
|
|
|
324
328
|
out.push(...commitToolCall(options.state, callId, finalArgs));
|
|
325
329
|
return out;
|
|
326
330
|
}
|
|
331
|
+
const responsesName = responsesToolNameFromCursorWire(cursorWireName);
|
|
332
|
+
const normSchema = defaultShellBridgeArgNormalizeSchema(responsesName);
|
|
333
|
+
const normalizedArgs = JSON.stringify(normalizeArgKeys(decodeCursorArgsMap(args?.args), normSchema));
|
|
334
|
+
if (!cursorShellBridgeArgsValid(normalizedArgs, responsesName, normSchema)) {
|
|
335
|
+
if (isCodexShellBridgeToolName(responsesName)) {
|
|
336
|
+
return [{ type: "error", message: cursorShellBridgeDropError(responsesName) }];
|
|
337
|
+
}
|
|
338
|
+
}
|
|
327
339
|
// Stateless fallback (no shared event state): emit a complete, self-contained tool call.
|
|
328
340
|
return [
|
|
329
|
-
{ type: "tool_call_start", id: callId, name:
|
|
330
|
-
{ type: "tool_call_delta", arguments:
|
|
341
|
+
{ type: "tool_call_start", id: callId, name: responsesName },
|
|
342
|
+
...(normalizedArgs.length > 2 ? [{ type: "tool_call_delta" as const, arguments: normalizedArgs }] : []),
|
|
331
343
|
{ type: "tool_call_end", id: callId },
|
|
332
344
|
];
|
|
333
345
|
}
|
|
@@ -361,9 +373,19 @@ function recordToolCall(state: CursorProtobufEventState, callId: string, cursorW
|
|
|
361
373
|
* recorded in `openToolCalls`. Because each completion emits a whole non-interleaved unit, the bridge
|
|
362
374
|
* (which tracks a single current tool call) serializes parallel Cursor calls correctly.
|
|
363
375
|
*/
|
|
376
|
+
function dropShellBridgeCall(state: CursorProtobufEventState, callId: string, toolName: string): CursorServerMessage[] {
|
|
377
|
+
state.openToolCalls.delete(callId);
|
|
378
|
+
state.completedToolCalls.add(callId);
|
|
379
|
+
return [{ type: "error", message: cursorShellBridgeDropError(toolName) }];
|
|
380
|
+
}
|
|
381
|
+
|
|
364
382
|
function commitToolCall(state: CursorProtobufEventState, callId: string, finalArgs: string): CursorServerMessage[] {
|
|
365
383
|
const open = state.openToolCalls.get(callId);
|
|
366
384
|
if (!open) return [];
|
|
385
|
+
const schema = toolSchemaForWireName(state, open.name);
|
|
386
|
+
if (!cursorShellBridgeArgsValid(finalArgs, open.name, schema)) {
|
|
387
|
+
if (isCodexShellBridgeToolName(open.name)) return dropShellBridgeCall(state, callId, open.name);
|
|
388
|
+
}
|
|
367
389
|
const out: CursorServerMessage[] = [{ type: "tool_call_start", id: callId, name: open.name }];
|
|
368
390
|
if (finalArgs.length > 0) out.push({ type: "tool_call_delta", arguments: finalArgs });
|
|
369
391
|
out.push(...endToolCall(state, callId));
|
|
@@ -38,7 +38,6 @@ import {
|
|
|
38
38
|
} from "./gen/agent_pb";
|
|
39
39
|
import {
|
|
40
40
|
appendCursorGenericToolUseHint,
|
|
41
|
-
appendCursorShellAliasHint,
|
|
42
41
|
cursorToolsForActivePrompt,
|
|
43
42
|
buildCursorToolGuidanceSystemNote,
|
|
44
43
|
buildCursorToolDefinitions,
|
|
@@ -560,7 +559,7 @@ export function prepareCursorRunRequest(
|
|
|
560
559
|
const rawText = activePromptText(request);
|
|
561
560
|
const lastRole = request.messages.at(-1)?.role;
|
|
562
561
|
const text = lastRole === "user" || lastRole === "developer"
|
|
563
|
-
?
|
|
562
|
+
? appendCursorGenericToolUseHint(request.tools, rawText)
|
|
564
563
|
: rawText;
|
|
565
564
|
// Tool-result-only turns resume the remembered Cursor conversation with results in history.
|
|
566
565
|
const lastRawIsToolResult = request.rawMessages?.at(-1)?.role === "toolResult";
|
|
@@ -12,8 +12,6 @@ export const CURSOR_EXEC_COMMAND_TOOL = CODEX_EXEC_COMMAND_TOOL;
|
|
|
12
12
|
export const CODEX_SHELL_BRIDGE_TOOL_NAMES = [CODEX_EXEC_COMMAND_TOOL, CODEX_SHELL_COMMAND_TOOL] as const;
|
|
13
13
|
export const CURSOR_SHELL_ALIAS_SYSTEM_NOTE =
|
|
14
14
|
'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; never say native shell is blocked.';
|
|
15
|
-
export const CURSOR_SHELL_ALIAS_USER_HINT =
|
|
16
|
-
"Use the Codex shell bridge tool listed this turn (`shell_command` or `exec_command`, including the long `mcp_opencodex-responses_*` name). Prefer that bridge over Cursor-native Shell/Read. Never say phrases like \"Native shell access is blocked\" or \"Shell access is blocked\" — silently use the bridge unless that bridge tool itself fails.";
|
|
17
15
|
const NEIGHBOR_AGENT_TOOL_NAMES = ["Read", "Grep", "Glob", "Bash", "LS"] as const;
|
|
18
16
|
|
|
19
17
|
export const CURSOR_GENERIC_TOOL_USE_USER_HINT = [
|
|
@@ -213,17 +211,6 @@ function shellBridgeArgNormalizeSchema(tool: OcxTool): unknown {
|
|
|
213
211
|
};
|
|
214
212
|
}
|
|
215
213
|
|
|
216
|
-
function activeTextMentionsExecCommand(text: string): boolean {
|
|
217
|
-
return /\b(?:exec_command|shell_command)\b/i.test(text);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
function looksLikeShellCommandRequest(text: string): boolean {
|
|
221
|
-
const hasKnownCommand = /(?:^|[\s`$])(?:echo|pwd|ls|cat|grep|rg|find|python3?|node|bun|npm|pnpm|yarn|git|curl|wget|chmod|mkdir|rm|cp|mv|touch|docker|kubectl|make|cargo|go|pytest)(?=\s|$|[`:;|&])/i.test(text);
|
|
222
|
-
const hasRunIntent = /\b(?:run|execute|exec)\b/i.test(text) || /\b(?:stdout|stderr|exit\s+code)\b/i.test(text);
|
|
223
|
-
const hasShellTarget = /\b(?:shell|terminal|command|cmd)\b/i.test(text);
|
|
224
|
-
return /\b(?:run|execute|exec)\s*:/i.test(text) || hasKnownCommand || (hasRunIntent && hasShellTarget);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
214
|
export function isGenericToolUseCountDemoPrompt(text: string): boolean {
|
|
228
215
|
const trimmed = text.trim();
|
|
229
216
|
if (trimmed.length === 0) return false;
|
|
@@ -316,23 +303,69 @@ export function cursorToolsForActivePrompt<T extends Pick<OcxTool, "namespace" |
|
|
|
316
303
|
return execTools && execTools.length > 0 ? execTools : tools;
|
|
317
304
|
}
|
|
318
305
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
306
|
+
/**
|
|
307
|
+
* Required command payload keys for a shell bridge tool, derived from the advertised schema when present.
|
|
308
|
+
*/
|
|
309
|
+
export function shellBridgeRequiredCommandKeys(
|
|
310
|
+
toolName: string,
|
|
311
|
+
schema?: unknown,
|
|
312
|
+
): readonly ("cmd" | "command")[] {
|
|
313
|
+
if (schema && typeof schema === "object") {
|
|
314
|
+
const required = (schema as Record<string, unknown>).required;
|
|
315
|
+
if (Array.isArray(required)) {
|
|
316
|
+
const keys = required.filter((key): key is "cmd" | "command" => key === "cmd" || key === "command");
|
|
317
|
+
if (keys.length > 0) return keys;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return toolName === CODEX_SHELL_COMMAND_TOOL ? ["command"] : ["cmd"];
|
|
328
321
|
}
|
|
329
322
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
323
|
+
/** Normalize-schema defaults used when validating stateless synthetic shell-bridge calls. */
|
|
324
|
+
export function defaultShellBridgeArgNormalizeSchema(toolName: string): unknown {
|
|
325
|
+
return toolName === CODEX_SHELL_COMMAND_TOOL
|
|
326
|
+
? CODEX_SHELL_BRIDGE_ARG_NORMALIZE_SCHEMA
|
|
327
|
+
: {
|
|
328
|
+
type: "object",
|
|
329
|
+
properties: CURSOR_EXEC_COMMAND_INPUT_SCHEMA.properties,
|
|
330
|
+
required: ["cmd"],
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export function cursorShellBridgeDropError(toolName: string): string {
|
|
335
|
+
return `Cursor emitted ${toolName} without a non-empty command; the tool call was dropped.`;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Extract a non-empty shell command from completed Cursor bridge args using the schema's required
|
|
340
|
+
* command key (`cmd` for bare exec_command, `command` for shell_command).
|
|
341
|
+
*/
|
|
342
|
+
export function nonEmptyShellBridgeCommandFromArgs(
|
|
343
|
+
finalArgs: string,
|
|
344
|
+
toolName: string,
|
|
345
|
+
schema?: unknown,
|
|
346
|
+
): string | undefined {
|
|
347
|
+
let parsed: unknown;
|
|
348
|
+
try {
|
|
349
|
+
parsed = finalArgs.length > 0 ? JSON.parse(finalArgs) : {};
|
|
350
|
+
} catch {
|
|
351
|
+
return undefined;
|
|
352
|
+
}
|
|
353
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return undefined;
|
|
354
|
+
const record = parsed as Record<string, unknown>;
|
|
355
|
+
for (const key of shellBridgeRequiredCommandKeys(toolName, schema)) {
|
|
356
|
+
const value = record[key];
|
|
357
|
+
if (typeof value === "string" && value.trim().length > 0) return value.trim();
|
|
358
|
+
}
|
|
359
|
+
return undefined;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export function cursorShellBridgeArgsValid(
|
|
363
|
+
finalArgs: string,
|
|
364
|
+
toolName: string,
|
|
365
|
+
schema?: unknown,
|
|
366
|
+
): boolean {
|
|
367
|
+
return !isCodexShellBridgeToolName(toolName)
|
|
368
|
+
|| nonEmptyShellBridgeCommandFromArgs(finalArgs, toolName, schema) !== undefined;
|
|
336
369
|
}
|
|
337
370
|
|
|
338
371
|
export function cursorToolAllowedByChoice(
|
|
@@ -384,6 +417,11 @@ export function buildCursorToolGuidanceSystemNote(
|
|
|
384
417
|
const hasApplyPatch = cursorRequestAdvertisesApplyPatch(tools, toolChoice);
|
|
385
418
|
const discoveryTools = discoveryToolLabel(wireNames);
|
|
386
419
|
const unavailableNeighborNames = unavailableNeighborAgentToolNames(wireNames);
|
|
420
|
+
// Host-shell-neutral: the Codex client executes bridge commands, and may differ from
|
|
421
|
+
// the OpenCodex proxy OS (LAN/SSH remote-proxy). Always cover PowerShell 5.1 pitfalls.
|
|
422
|
+
const hostShellNote = hasBareExec
|
|
423
|
+
? "Match shell syntax to the Codex client host that runs the bridge (not only the proxy OS). Windows PowerShell 5.1: no CMD `cd /d`, no bash heredocs (`<<EOF`); `&&`/`||` are unsupported parser errors — prefer the bridge working-directory argument for directory changes, and use `if ($?) { ... }` for success-gated follow-up steps; do not treat `;` as a substitute for `&&`. POSIX: use portable commands. After a shell failure, make at most one corrected bridge attempt, then report the error and stop — do not repeat equivalent failing commands."
|
|
424
|
+
: undefined;
|
|
387
425
|
const notes = [
|
|
388
426
|
`Cursor tool calls: available tool names are exactly ${listedNames}.`,
|
|
389
427
|
"Use the current tool catalog as ground truth and call only those exact names with their listed argument keys.",
|
|
@@ -399,6 +437,7 @@ export function buildCursorToolGuidanceSystemNote(
|
|
|
399
437
|
hasBareExec
|
|
400
438
|
? "Never tell the user that shell or read access is blocked, disabled, or denied unless the Codex shell bridge tool itself fails. Prefer the bridge over Cursor-native Shell/Read; do not narrate phrases like \"Native shell access is blocked\" — silently call `shell_command` / `exec_command`."
|
|
401
439
|
: undefined,
|
|
440
|
+
hostShellNote,
|
|
402
441
|
"Cursor product features (Chronicle, screen recording, Notes, Plans, background agents) are available only if this turn's catalog lists a matching tool; do not offer or promise them otherwise.",
|
|
403
442
|
hasBareExec
|
|
404
443
|
? `For file read/search/listing, use ${shellBridgeLabel} when no more specific listed tool is available.`
|
|
@@ -418,7 +457,7 @@ export function buildCursorToolGuidanceSystemNote(
|
|
|
418
457
|
: undefined,
|
|
419
458
|
"Do not count or report a tool call unless a tool result was actually returned.",
|
|
420
459
|
hasBareExec
|
|
421
|
-
? `If a Cursor-native file read, directory listing, grep, or shell operation is rejected by the runtime, silently use ${shellBridgeLabel} with
|
|
460
|
+
? `If a Cursor-native file read, directory listing, grep, or shell operation is rejected by the runtime, silently use ${shellBridgeLabel} with an equivalent host-shell-safe command (POSIX: \`cat\`/\`ls\`/\`rg\`; Windows PowerShell: \`Get-Content\`/\`Get-ChildItem\`/\`Select-String\`). Do not tell the user access is blocked. For file edits, use \`apply_patch\` when available.`
|
|
422
461
|
: undefined,
|
|
423
462
|
].filter((note): note is string => typeof note === "string");
|
|
424
463
|
return notes.join(" ");
|
|
@@ -137,6 +137,10 @@ function compileGenerationConfig(value: unknown): JsonObject | undefined {
|
|
|
137
137
|
: (["xhigh", "max", "ultra"].includes(raw) ? "high" : undefined);
|
|
138
138
|
if (thinkingLevel) out.thinkingConfig = { thinkingLevel };
|
|
139
139
|
}
|
|
140
|
+
if (Array.isArray(value.responseModalities)) {
|
|
141
|
+
const valid = value.responseModalities.filter((m): m is string => typeof m === "string" && ["TEXT", "IMAGE", "AUDIO"].includes(m));
|
|
142
|
+
if (valid.length > 0) out.responseModalities = valid;
|
|
143
|
+
}
|
|
140
144
|
return Object.keys(out).length > 0 ? out : undefined;
|
|
141
145
|
}
|
|
142
146
|
|
package/src/adapters/google.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AdapterFetchContext, AdapterRequest, ProviderAdapter } from "./base";
|
|
2
2
|
import { debugDroppedFrame } from "../lib/debug";
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
|
+
import { createImageBudget, materializeInlineImage, MAX_ENCODED_BYTES_PER_IMAGE, artifactHttpUrl } from "../images/artifacts";
|
|
4
5
|
import type {
|
|
5
6
|
AdapterEvent,
|
|
6
7
|
OcxAssistantMessage,
|
|
@@ -233,6 +234,38 @@ function usageFromGemini(usage: Record<string, number> | undefined): OcxUsage |
|
|
|
233
234
|
};
|
|
234
235
|
}
|
|
235
236
|
|
|
237
|
+
/**
|
|
238
|
+
* Cap on the buffered non-streaming response body (100 MiB), matching
|
|
239
|
+
* IMAGES_RESPONSE_MAX_BYTES in src/server/images.ts. Enforced by streaming the
|
|
240
|
+
* body with a hard byte cap before JSON.parse — Content-Length alone is not
|
|
241
|
+
* trusted (missing/lying headers must still reject oversized payloads).
|
|
242
|
+
* Streaming SSE responses also cap each data frame before JSON.parse.
|
|
243
|
+
*/
|
|
244
|
+
const MAX_RESPONSE_BYTES = 100 * 1024 * 1024;
|
|
245
|
+
const MAX_SSE_FRAME_BYTES = MAX_RESPONSE_BYTES;
|
|
246
|
+
|
|
247
|
+
// Note: imagen-* models use a different API surface (prediction/image-generation
|
|
248
|
+
// schema) and must NOT be treated as responseModalities-capable Gemini models.
|
|
249
|
+
// Explicit allowlist only — never `/gemini/ && /image/` (resurrects media-gen IDs).
|
|
250
|
+
const IMAGE_CAPABLE_MODELS = new Set([
|
|
251
|
+
"gemini-3.1-flash-image",
|
|
252
|
+
"gemini-2.0-flash-preview-image-generation",
|
|
253
|
+
"gemini-3-pro-image-preview",
|
|
254
|
+
]);
|
|
255
|
+
|
|
256
|
+
function isImageCapableModel(modelId: string): boolean {
|
|
257
|
+
return IMAGE_CAPABLE_MODELS.has(modelId);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Model-visible markdown link for a materialized artifact. Uses the authenticated
|
|
262
|
+
* opaque HTTP route so remote/container clients can fetch the image without host
|
|
263
|
+
* filesystem paths leaking into the transcript.
|
|
264
|
+
*/
|
|
265
|
+
function artifactMarkdownUrl(filePath: string): string {
|
|
266
|
+
return artifactHttpUrl(filePath).replace(/([()])/g, "\\$1");
|
|
267
|
+
}
|
|
268
|
+
|
|
236
269
|
export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapter {
|
|
237
270
|
// Per-request closure: resolveAdapter builds a fresh adapter per request (server.ts), so buildRequest
|
|
238
271
|
// can stash the CCA model/session for parseStream's reasoning-replay observation.
|
|
@@ -272,6 +305,9 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
272
305
|
? mapReasoningEffort(provider, parsed.modelId, parsed.options.reasoning)
|
|
273
306
|
: undefined;
|
|
274
307
|
if (directFlashThinking) generationConfig.thinkingConfig = { thinkingLevel: directFlashThinking };
|
|
308
|
+
if (!generationConfig.thinkingConfig && isImageCapableModel(parsed.modelId)) {
|
|
309
|
+
generationConfig.responseModalities = ["TEXT", "IMAGE"];
|
|
310
|
+
}
|
|
275
311
|
if (Object.keys(generationConfig).length > 0) body.generationConfig = generationConfig;
|
|
276
312
|
|
|
277
313
|
const method = parsed.stream ? "streamGenerateContent" : "generateContent";
|
|
@@ -377,6 +413,10 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
377
413
|
yield { type: "error", message: "No response body" };
|
|
378
414
|
return;
|
|
379
415
|
}
|
|
416
|
+
// Streaming responses are processed incrementally (SSE chunks), so the full body
|
|
417
|
+
// is never buffered — no Content-Length pre-check is needed here. Per-image size
|
|
418
|
+
// protection is enforced on each chunk via MAX_ENCODED_BYTES_PER_IMAGE before
|
|
419
|
+
// materializeInlineImage is called (see the inline.data check below).
|
|
380
420
|
|
|
381
421
|
const reader = response.body.getReader();
|
|
382
422
|
const decoder = new TextDecoder();
|
|
@@ -387,9 +427,13 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
387
427
|
let sawAnyFrame = false;
|
|
388
428
|
let sawTerminalSignal = false;
|
|
389
429
|
|
|
390
|
-
const handleDataLine = function* (line: string):
|
|
430
|
+
const handleDataLine = async function* (line: string): AsyncGenerator<AdapterEvent, "continue" | "content" | "terminate"> {
|
|
391
431
|
const payload = line.slice(5).trim();
|
|
392
432
|
if (!payload) return "continue";
|
|
433
|
+
if (payload.length > MAX_SSE_FRAME_BYTES) {
|
|
434
|
+
yield { type: "error", message: `upstream SSE data frame exceeds ${MAX_SSE_FRAME_BYTES} bytes` };
|
|
435
|
+
return "terminate";
|
|
436
|
+
}
|
|
393
437
|
let emittedContentEvent = false;
|
|
394
438
|
|
|
395
439
|
let chunk: Record<string, unknown>;
|
|
@@ -452,6 +496,21 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
452
496
|
emittedContentEvent = true;
|
|
453
497
|
yield { type: "text_delta", text: part.text };
|
|
454
498
|
}
|
|
499
|
+
const inline = (part as { inlineData?: { mimeType?: string; data?: string } }).inlineData;
|
|
500
|
+
if (inline && typeof inline.data === "string") {
|
|
501
|
+
if (inline.data.length > MAX_ENCODED_BYTES_PER_IMAGE) {
|
|
502
|
+
yield { type: "error", message: "inline image exceeds per-image size cap" };
|
|
503
|
+
} else {
|
|
504
|
+
try {
|
|
505
|
+
const filePath = await materializeInlineImage(inline.data, imageBudget);
|
|
506
|
+
const escapedPath = artifactMarkdownUrl(filePath);
|
|
507
|
+
emittedContentEvent = true;
|
|
508
|
+
yield { type: "text_delta", text: `\n\n` };
|
|
509
|
+
} catch {
|
|
510
|
+
yield { type: "error", message: "failed to materialize inline image" };
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
455
514
|
if (part.functionCall) {
|
|
456
515
|
const id = `call_${crypto.randomUUID().slice(0, 8)}`;
|
|
457
516
|
toolCallsStarted++;
|
|
@@ -464,12 +523,20 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
464
523
|
}
|
|
465
524
|
return emittedContentEvent ? "content" : "continue";
|
|
466
525
|
};
|
|
526
|
+
const imageBudget = createImageBudget();
|
|
467
527
|
|
|
468
528
|
try {
|
|
469
529
|
while (true) {
|
|
470
530
|
const { done, value } = await reader.read();
|
|
471
531
|
if (done) break;
|
|
472
532
|
buffer += decoder.decode(value, { stream: true });
|
|
533
|
+
// Cap incomplete frames before waiting for a newline — otherwise a single
|
|
534
|
+
// unterminated data: payload can grow without bound.
|
|
535
|
+
if (buffer.length > MAX_SSE_FRAME_BYTES) {
|
|
536
|
+
yield { type: "error", message: `upstream SSE data frame exceeds ${MAX_SSE_FRAME_BYTES} bytes` };
|
|
537
|
+
try { await reader.cancel(); } catch { /* ignore */ }
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
473
540
|
|
|
474
541
|
const lines = buffer.split("\n");
|
|
475
542
|
buffer = lines.pop() ?? "";
|
|
@@ -526,7 +593,51 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
526
593
|
},
|
|
527
594
|
|
|
528
595
|
async parseResponse(response: Response): Promise<AdapterEvent[]> {
|
|
529
|
-
|
|
596
|
+
// Reject oversized responses before JSON parse. Prefer Content-Length when
|
|
597
|
+
// present and truthful; always stream-read with a hard byte cap so a missing
|
|
598
|
+
// or lying Content-Length cannot force a full in-memory buffer + parse.
|
|
599
|
+
const contentLength = Number(response.headers.get("content-length"));
|
|
600
|
+
if (Number.isFinite(contentLength) && contentLength > MAX_RESPONSE_BYTES) {
|
|
601
|
+
try { await response.body?.cancel(); } catch { /* ignore */ }
|
|
602
|
+
return [{ type: "error", message: `google response too large (content-length ${contentLength} exceeds ${MAX_RESPONSE_BYTES} bytes)` }];
|
|
603
|
+
}
|
|
604
|
+
let rawText: string;
|
|
605
|
+
try {
|
|
606
|
+
const reader = response.body?.getReader();
|
|
607
|
+
if (!reader) return [{ type: "error", message: "google response had no body" }];
|
|
608
|
+
const chunks: Uint8Array[] = [];
|
|
609
|
+
let total = 0;
|
|
610
|
+
try {
|
|
611
|
+
for (;;) {
|
|
612
|
+
const { done, value } = await reader.read();
|
|
613
|
+
if (done) break;
|
|
614
|
+
total += value.byteLength;
|
|
615
|
+
if (total > MAX_RESPONSE_BYTES) {
|
|
616
|
+
await reader.cancel().catch(() => {});
|
|
617
|
+
return [{ type: "error", message: `google response too large (exceeded ${MAX_RESPONSE_BYTES} bytes)` }];
|
|
618
|
+
}
|
|
619
|
+
chunks.push(value);
|
|
620
|
+
}
|
|
621
|
+
} finally {
|
|
622
|
+
try { await reader.cancel(); } catch { /* ignore */ }
|
|
623
|
+
reader.releaseLock();
|
|
624
|
+
}
|
|
625
|
+
const bytes = new Uint8Array(total);
|
|
626
|
+
let offset = 0;
|
|
627
|
+
for (const chunk of chunks) {
|
|
628
|
+
bytes.set(chunk, offset);
|
|
629
|
+
offset += chunk.byteLength;
|
|
630
|
+
}
|
|
631
|
+
rawText = new TextDecoder().decode(bytes);
|
|
632
|
+
} catch (err) {
|
|
633
|
+
return [{ type: "error", message: err instanceof Error ? err.message : "failed to read google response body" }];
|
|
634
|
+
}
|
|
635
|
+
let raw: Record<string, unknown>;
|
|
636
|
+
try {
|
|
637
|
+
raw = JSON.parse(rawText) as Record<string, unknown>;
|
|
638
|
+
} catch {
|
|
639
|
+
return [{ type: "error", message: "google response was not valid JSON" }];
|
|
640
|
+
}
|
|
530
641
|
if (raw.error) {
|
|
531
642
|
const err = raw.error as { message?: string };
|
|
532
643
|
return [{ type: "error", message: err.message ?? "upstream error" }];
|
|
@@ -547,6 +658,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
547
658
|
return [{ type: "error", message: "google response contained no candidates" }];
|
|
548
659
|
}
|
|
549
660
|
let toolCallsStarted = 0;
|
|
661
|
+
const imageBudget = createImageBudget();
|
|
550
662
|
if (candidates?.[0]?.content?.parts) {
|
|
551
663
|
// Non-streaming CCA: observe thoughtSignatures for the next turn, same as the stream path.
|
|
552
664
|
if (provider.googleMode === "cloud-code-assist" && antigravityModel && antigravitySession) {
|
|
@@ -554,6 +666,20 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
554
666
|
}
|
|
555
667
|
for (const part of candidates[0].content.parts) {
|
|
556
668
|
if (part.text) events.push({ type: "text_delta", text: part.text });
|
|
669
|
+
const inline = (part as { inlineData?: { mimeType?: string; data?: string } }).inlineData;
|
|
670
|
+
if (inline && typeof inline.data === "string") {
|
|
671
|
+
if (inline.data.length > MAX_ENCODED_BYTES_PER_IMAGE) {
|
|
672
|
+
events.push({ type: "error", message: "inline image exceeds per-image size cap" });
|
|
673
|
+
} else {
|
|
674
|
+
try {
|
|
675
|
+
const filePath = await materializeInlineImage(inline.data, imageBudget);
|
|
676
|
+
const escapedPath = artifactMarkdownUrl(filePath);
|
|
677
|
+
events.push({ type: "text_delta", text: `\n\n` });
|
|
678
|
+
} catch {
|
|
679
|
+
events.push({ type: "error", message: "failed to materialize inline image" });
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}
|
|
557
683
|
if (part.functionCall) {
|
|
558
684
|
const id = `call_${crypto.randomUUID().slice(0, 8)}`;
|
|
559
685
|
toolCallsStarted++;
|
package/src/adapters/identity.ts
CHANGED
|
@@ -14,9 +14,19 @@
|
|
|
14
14
|
* to be a specific first-party client.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
/**
|
|
17
|
+
/** Historical exact identity line Codex injected for every model. */
|
|
18
18
|
export const CODEX_GPT5_IDENTITY_LINE = "You are Codex, a coding agent based on GPT-5.";
|
|
19
19
|
|
|
20
|
+
/** Codex CLI 0.145.0+ wording (#622) — still GPT-5 identity, slightly different phrasing. */
|
|
21
|
+
export const CODEX_GPT5_IDENTITY_LINE_AGENT = "You are Codex, an agent based on GPT-5.";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Known Codex GPT-5 identity sentences. Narrow: only "coding agent" / "an agent" + GPT-5(.x)?
|
|
25
|
+
* Avoid a broad `You are Codex.*` rewrite that could touch unrelated content.
|
|
26
|
+
*/
|
|
27
|
+
const CODEX_GPT5_IDENTITY_RE =
|
|
28
|
+
/You are Codex, (?:a coding agent|an agent) based on GPT-5(?:\.[0-9]+)*\./g;
|
|
29
|
+
|
|
20
30
|
/** Proxy-neutral replacement: no "opencodex proxy" mention, just the GPT-5/OpenAI disclaimer. */
|
|
21
31
|
export const NEUTRAL_IDENTITY_LINE = "You are a coding agent. Do not claim to be GPT-5 or to be made by OpenAI.";
|
|
22
32
|
|
|
@@ -27,7 +37,7 @@ export const NEUTRAL_IDENTITY_LINE = "You are a coding agent. Do not claim to be
|
|
|
27
37
|
* the leak can't reappear in one adapter while being fixed in another.
|
|
28
38
|
*/
|
|
29
39
|
export function neutralizeIdentity(systemText: string): string {
|
|
30
|
-
return systemText.replace(
|
|
40
|
+
return systemText.replace(CODEX_GPT5_IDENTITY_RE, NEUTRAL_IDENTITY_LINE);
|
|
31
41
|
}
|
|
32
42
|
|
|
33
43
|
/** The catalog (static, on-disk) replacement for `base_instructions`. Same neutral wording. */
|