@bitkyc08/opencodex 2.33.0 → 2.34.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/README.md +3 -3
- package/gui/dist/assets/index-C4TMRloX.js +112 -0
- package/gui/dist/assets/index-DQ-Ie18T.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic.ts +79 -2
- package/src/adapters/command-code.ts +40 -3
- package/src/adapters/cursor/call-id.ts +44 -0
- package/src/adapters/cursor/checkpoint-store.ts +15 -10
- package/src/adapters/cursor/discovery.ts +60 -2
- package/src/adapters/cursor/effort-map.ts +79 -1
- package/src/adapters/cursor/envelope-echo.ts +162 -0
- package/src/adapters/cursor/live-models.ts +7 -2
- package/src/adapters/cursor/live-transport.ts +14 -0
- package/src/adapters/cursor/message-mapper.ts +4 -1
- package/src/adapters/cursor/native-exec-desktop.ts +23 -0
- package/src/adapters/cursor/native-exec-fs.ts +2 -2
- package/src/adapters/cursor/native-exec-network.ts +1 -1
- package/src/adapters/cursor/native-exec-shell.ts +5 -2
- package/src/adapters/cursor/native-exec.ts +20 -0
- package/src/adapters/cursor/protobuf-request.ts +68 -14
- package/src/adapters/cursor/request-builder.ts +29 -6
- package/src/adapters/cursor/tool-definitions.ts +11 -2
- package/src/adapters/cursor/tool-result-normalize.ts +32 -1
- package/src/adapters/cursor/types.ts +20 -0
- package/src/adapters/cursor.ts +189 -37
- package/src/adapters/google-antigravity-replay.ts +102 -23
- package/src/adapters/google-errors.ts +44 -12
- package/src/adapters/google.ts +115 -16
- package/src/adapters/image.ts +1 -1
- package/src/adapters/kiro-constants.ts +15 -0
- package/src/adapters/kiro-tools.ts +43 -15
- package/src/adapters/kiro.ts +54 -9
- package/src/adapters/openai-chat.ts +286 -242
- package/src/adapters/openai-responses.ts +334 -24
- package/src/adapters/tool-catalog-nudge.ts +2 -2
- package/src/adapters/xai-tool-schema.ts +436 -0
- package/src/bridge.ts +67 -24
- package/src/chat/inbound.ts +14 -1
- package/src/chat/outbound.ts +15 -7
- package/src/claude/agents-inject.ts +8 -1
- package/src/claude/outbound.ts +10 -8
- package/src/cli/account-api.ts +32 -4
- package/src/cli/account-extended.ts +1 -1
- package/src/cli/account.ts +29 -5
- package/src/cli/alias.ts +66 -0
- package/src/cli/claude.ts +26 -1
- package/src/cli/dispatch.ts +13 -1
- package/src/cli/help.ts +1 -0
- package/src/cli/init.ts +1 -0
- package/src/cli/models-runtime.ts +95 -0
- package/src/cli/models.ts +13 -7
- package/src/cli/provider-runtime.ts +16 -2
- package/src/cli/registry.ts +6 -1
- package/src/cli/v2.ts +34 -10
- package/src/codex/account-pause.ts +2 -1
- package/src/codex/account-priority.ts +3 -2
- package/src/codex/app-server-processes.ts +80 -6
- package/src/codex/auth-api.ts +46 -8
- package/src/codex/auth-context.ts +21 -18
- package/src/codex/catalog/aggregation.ts +6 -0
- package/src/codex/catalog/native-models.ts +5 -2
- package/src/codex/catalog/parsing.ts +16 -0
- package/src/codex/catalog/provider-fetch.ts +20 -3
- package/src/codex/catalog/sync.ts +127 -2
- package/src/codex/catalog.ts +1 -1
- package/src/codex/codex-write-lock.ts +3 -1
- package/src/codex/convergence-types.ts +1 -1
- package/src/codex/convergence.ts +22 -2
- package/src/codex/desired-state.ts +2 -2
- package/src/codex/desktop-app-restart.ts +18 -5
- package/src/codex/inject-coordination.ts +83 -0
- package/src/codex/inject.ts +14 -1
- package/src/codex/log-guard/inspect.ts +22 -4
- package/src/codex/model-entitlements.ts +9 -2
- package/src/codex/prompt-layers.ts +371 -25
- package/src/codex/prompt-text-probe.ts +238 -0
- package/src/codex/quota.ts +91 -10
- package/src/codex/routing.ts +9 -0
- package/src/codex/subagent-model-fallback.ts +104 -25
- package/src/codex/transition-state.ts +107 -8
- package/src/combos/types.ts +10 -0
- package/src/compatibility/openai-responses.ts +33 -1
- package/src/config/rebase-provenance.ts +68 -0
- package/src/config.ts +107 -9
- package/src/generated/compatibility-version.json +191 -139
- package/src/images/loop.ts +5 -4
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +1 -1
- package/src/lab/fabric/producer-child.ts +1 -1
- package/src/lib/config-ownership.ts +20 -0
- package/src/lib/errors.ts +11 -2
- package/src/lib/package-tree-integrity.ts +101 -0
- package/src/oauth/generic-account-failover.ts +231 -0
- package/src/oauth/index.ts +57 -5
- package/src/oauth/key-providers.ts +10 -1
- package/src/oauth/kiro.ts +45 -0
- package/src/oauth/types.ts +15 -0
- package/src/providers/command-code-efforts.ts +38 -6
- package/src/providers/context-cap.ts +4 -3
- package/src/providers/default-aliases.ts +65 -0
- package/src/providers/derive.ts +28 -0
- package/src/providers/fastwire.ts +7 -1
- package/src/providers/model-presets.ts +119 -0
- package/src/providers/new-model-policy.ts +146 -0
- package/src/providers/provider-id-rewrite.ts +2 -1
- package/src/providers/quota.ts +65 -7
- package/src/providers/registry.ts +160 -68
- package/src/providers/slug-codec.ts +52 -0
- package/src/responses/code-mode-helper-compat.ts +50 -0
- package/src/responses/custom-tool-compat.ts +34 -10
- package/src/responses/parser.ts +4 -0
- package/src/responses/schema.ts +5 -1
- package/src/responses/thought-signature-replay.ts +17 -0
- package/src/router.ts +40 -2
- package/src/routing/analytics.ts +1 -0
- package/src/routing/quota.ts +10 -0
- package/src/server/auth-cors.ts +4 -0
- package/src/server/chat-completions.ts +24 -16
- package/src/server/chat-native-sse.ts +3 -3
- package/src/server/chat-native.ts +30 -11
- package/src/server/claude-messages.ts +1 -1
- package/src/server/index.ts +77 -11
- package/src/server/lifecycle.ts +52 -1
- package/src/server/management/agent-settings-routes.ts +31 -15
- package/src/server/management/codex-prompt-routes.ts +570 -0
- package/src/server/management/combo-routes.ts +2 -1
- package/src/server/management/config-routes.ts +27 -9
- package/src/server/management/context.ts +9 -0
- package/src/server/management/model-routes.ts +266 -0
- package/src/server/management/provider-routes.ts +6 -0
- package/src/server/management/routing-profile-routes.ts +2 -2
- package/src/server/management-api.ts +2 -0
- package/src/server/relay-eager.ts +147 -20
- package/src/server/relay.ts +251 -19
- package/src/server/request-log-conversation.ts +21 -0
- package/src/server/request-log.ts +41 -15
- package/src/server/responses/collaboration.ts +37 -3
- package/src/server/responses/combo-stream-preflight.ts +10 -3
- package/src/server/responses/core.ts +494 -114
- package/src/server/responses/empty-completion-guard.ts +35 -0
- package/src/server/responses/fetch-helpers.ts +2 -1
- package/src/server/responses/input-admission.ts +3 -1
- package/src/server/responses/passthrough-error.ts +33 -9
- package/src/server/responses/responses-field-backfill.ts +105 -13
- package/src/server/responses-custom-tool-repair.ts +52 -7
- package/src/server/responses-terminal-repair.ts +25 -4
- package/src/server/sse-frame-buffer.ts +31 -4
- package/src/server/ws-bridge.ts +14 -2
- package/src/types/config.ts +53 -0
- package/src/types/provider.ts +64 -0
- package/src/types/request.ts +11 -3
- package/src/types/tools.ts +7 -5
- package/src/types.ts +1 -0
- package/src/usage/expected-prices.ts +43 -24
- package/src/usage/log.ts +3 -0
- package/src/web-search/exa-executor.ts +40 -9
- package/src/web-search/loop.ts +5 -4
- package/gui/dist/assets/index-23-Lf7jR.js +0 -102
- package/gui/dist/assets/index-DxJMDyOr.css +0 -1
|
@@ -6,6 +6,7 @@ import { COMPACT_PROMPT, compactionItemToText, decodeCompactionSummary, isCompac
|
|
|
6
6
|
import { collectResponsesToolGroups } from "../responses/tool-groups";
|
|
7
7
|
import { isHostedToolUnsupportedForModel } from "../responses/hosted-tool-policy";
|
|
8
8
|
import { decodeServerSentEvents } from "../lib/sse-decoder";
|
|
9
|
+
import { debugProviderDiagnostic } from "../lib/debug";
|
|
9
10
|
import {
|
|
10
11
|
CODEX_FORWARD_BASE_URL,
|
|
11
12
|
destinationDecodesNativeCompactionBlob,
|
|
@@ -20,6 +21,11 @@ import { rewriteRoutedToolSearchForUpstream } from "../responses/tool-search-com
|
|
|
20
21
|
import { rewriteRoutedNamespaceToolsForUpstream } from "../responses/namespace-tool-compat";
|
|
21
22
|
import { openaiResponsesUrl } from "./openai-responses-url";
|
|
22
23
|
import { normalizeXaiResponsesWebSearch } from "./xai-web-search";
|
|
24
|
+
import {
|
|
25
|
+
isXaiSchemaTarget,
|
|
26
|
+
normalizeXaiToolParameters,
|
|
27
|
+
XaiToolSchemaCompatibilityError,
|
|
28
|
+
} from "./xai-tool-schema";
|
|
23
29
|
import {
|
|
24
30
|
createAdapterTierMetadata,
|
|
25
31
|
} from "../providers/fastwire";
|
|
@@ -46,6 +52,15 @@ export const FORWARD_HEADERS = [
|
|
|
46
52
|
"x-responsesapi-include-timing-metrics",
|
|
47
53
|
];
|
|
48
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Sanitize reasoning input by field policy, not by preserving each item's shape. Retaining a
|
|
57
|
+
* native `encrypted_content` guarantees only that blob value: `status` is always removed;
|
|
58
|
+
* proxy-owned `ocxr1:` envelopes are always removed; and native blobs are removed when the caller
|
|
59
|
+
* requests stripping after a route-identity change or opaque-blob recovery. On routed/non-OpenAI
|
|
60
|
+
* destinations, a present non-array `content` field is omitted. Otherwise non-empty array content
|
|
61
|
+
* is blanked unless raw reasoning preservation is enabled; removing an `ocxr1:` envelope selects
|
|
62
|
+
* the same blanking path when non-array omission is not active.
|
|
63
|
+
*/
|
|
49
64
|
export function sanitizeReasoningInputContent(
|
|
50
65
|
body: unknown,
|
|
51
66
|
opts?: {
|
|
@@ -77,12 +92,11 @@ export function sanitizeReasoningInputContent(
|
|
|
77
92
|
// rather than the field it actually refused, which is why this reads as a blob failure. Drop the
|
|
78
93
|
// key so the item matches the shape the upstream issued.
|
|
79
94
|
//
|
|
80
|
-
// Gated to routed destinations. An OpenAI-operated backend
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
// reach this branch. This is independent of the output-only status removal below.
|
|
95
|
+
// Gated to routed destinations. An OpenAI-operated backend rejects a blob-bearing item when its
|
|
96
|
+
// null `content` channel is deleted (`The encrypted content ... could not be verified`); that
|
|
97
|
+
// live result establishes this channel constraint, not whole-item shape preservation. The gate
|
|
98
|
+
// is also why this drop may touch an item that keeps its blob: xAI demonstrably accepts its own
|
|
99
|
+
// blob without the null channel. This is independent of the output-only status removal below.
|
|
86
100
|
const dropNullContentChannel = opts?.dropNullContentChannel === true
|
|
87
101
|
&& "content" in rec && !Array.isArray(rec.content);
|
|
88
102
|
// `status` is output-only. Measured OpenAI reasoning items never contain it, and Grok accepts
|
|
@@ -368,6 +382,27 @@ function stripDisabledReasoningSummaries(
|
|
|
368
382
|
};
|
|
369
383
|
}
|
|
370
384
|
|
|
385
|
+
/**
|
|
386
|
+
* Hide a no-op Responses verbosity control from the wire as well as the catalog. This runs at
|
|
387
|
+
* final serialization so a stale catalog or direct caller cannot bypass the capability. Other
|
|
388
|
+
* `text` settings (notably structured-output `format`) remain untouched.
|
|
389
|
+
*/
|
|
390
|
+
function stripDisabledVerbosity(
|
|
391
|
+
body: unknown,
|
|
392
|
+
provider: OcxProviderConfig,
|
|
393
|
+
modelId: string,
|
|
394
|
+
): unknown {
|
|
395
|
+
if (modelRecordValue(provider.modelSupportsVerbosity, modelId) !== false || !isPlainObject(body)) {
|
|
396
|
+
return body;
|
|
397
|
+
}
|
|
398
|
+
if (!isPlainObject(body.text) || !Object.hasOwn(body.text, "verbosity")) return body;
|
|
399
|
+
const { verbosity: _verbosity, ...rest } = body.text;
|
|
400
|
+
return {
|
|
401
|
+
...body,
|
|
402
|
+
...(Object.keys(rest).length > 0 ? { text: rest } : { text: undefined }),
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
371
406
|
/**
|
|
372
407
|
* Normalize only the delivery enum Codex already emitted. Do not inject a field into callers that
|
|
373
408
|
* did not request summaries, and leave every unconfigured provider/model byte-for-byte unchanged.
|
|
@@ -502,8 +537,12 @@ function isPlainObject(v: unknown): v is Record<string, unknown> {
|
|
|
502
537
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
503
538
|
}
|
|
504
539
|
|
|
505
|
-
function normalizeFunctionToolSchema(tool: unknown): unknown {
|
|
540
|
+
function normalizeFunctionToolSchema(tool: unknown, xaiTarget: boolean): unknown | undefined {
|
|
506
541
|
if (!isPlainObject(tool) || tool.type !== "function") return tool;
|
|
542
|
+
if (xaiTarget) {
|
|
543
|
+
const parameters = normalizeXaiToolParameters(isPlainObject(tool.parameters) ? tool.parameters : {});
|
|
544
|
+
return parameters === undefined ? undefined : { ...tool, parameters };
|
|
545
|
+
}
|
|
507
546
|
if (isPlainObject(tool.parameters) && tool.parameters.type === "object") return tool;
|
|
508
547
|
return {
|
|
509
548
|
...tool,
|
|
@@ -511,16 +550,69 @@ function normalizeFunctionToolSchema(tool: unknown): unknown {
|
|
|
511
550
|
};
|
|
512
551
|
}
|
|
513
552
|
|
|
514
|
-
|
|
553
|
+
/**
|
|
554
|
+
* Re-point `tool_choice` after an incompatible function was dropped from the catalog. Names here
|
|
555
|
+
* are already wire names, because namespace lowering rewrote the declarations and the selector
|
|
556
|
+
* together before this runs. A selector left naming an omitted tool reaches Grok as a dangling
|
|
557
|
+
* reference it rejects, and silently relaxing it to `auto` is worse: the turn would quietly
|
|
558
|
+
* proceed without the tool the caller required. So an `allowed_tools` list drops the omitted
|
|
559
|
+
* entries while any remain, and a selection with nothing left to point at fails locally with the
|
|
560
|
+
* same 400 the caller gets for a tool catalog this proxy cannot lower.
|
|
561
|
+
*/
|
|
562
|
+
function reconcileToolChoiceForOmittedTools(
|
|
563
|
+
body: Record<string, unknown>,
|
|
564
|
+
omittedFunctionNames: ReadonlySet<string>,
|
|
565
|
+
): Record<string, unknown> {
|
|
566
|
+
if (omittedFunctionNames.size === 0) return body;
|
|
567
|
+
const toolChoice = body.tool_choice;
|
|
568
|
+
if (!isPlainObject(toolChoice)) return body;
|
|
569
|
+
|
|
570
|
+
const refuse = (name: string): never => {
|
|
571
|
+
throw new XaiToolSchemaCompatibilityError(
|
|
572
|
+
`tool_choice requires function "${name}", but its parameter schema cannot be represented for this destination; `
|
|
573
|
+
+ "relax tool_choice or simplify the tool's parameter schema",
|
|
574
|
+
);
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
if (toolChoice.type === "function" && typeof toolChoice.name === "string") {
|
|
578
|
+
return omittedFunctionNames.has(toolChoice.name) ? refuse(toolChoice.name) : body;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
if (toolChoice.type === "allowed_tools" && Array.isArray(toolChoice.tools)) {
|
|
582
|
+
const omitted = toolChoice.tools.filter(tool =>
|
|
583
|
+
isPlainObject(tool)
|
|
584
|
+
&& tool.type === "function"
|
|
585
|
+
&& typeof tool.name === "string"
|
|
586
|
+
&& omittedFunctionNames.has(tool.name));
|
|
587
|
+
if (omitted.length === 0) return body;
|
|
588
|
+
const kept = toolChoice.tools.filter(tool => !omitted.includes(tool));
|
|
589
|
+
if (kept.length === 0) {
|
|
590
|
+
const first = omitted[0];
|
|
591
|
+
return refuse(isPlainObject(first) && typeof first.name === "string" ? first.name : "unknown");
|
|
592
|
+
}
|
|
593
|
+
return { ...body, tool_choice: { ...toolChoice, tools: kept } };
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
return body;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function normalizeToolSchemas(body: unknown, xaiTarget: boolean): unknown {
|
|
515
600
|
if (!isPlainObject(body)) return body;
|
|
516
601
|
|
|
602
|
+
const omittedFunctionNames = new Set<string>();
|
|
517
603
|
const normalizeTools = (tools: unknown[]): unknown[] => {
|
|
518
604
|
let changed = false;
|
|
519
|
-
const normalized =
|
|
520
|
-
|
|
605
|
+
const normalized: unknown[] = [];
|
|
606
|
+
for (const tool of tools) {
|
|
607
|
+
const fixed = normalizeFunctionToolSchema(tool, xaiTarget);
|
|
608
|
+
if (fixed === undefined) {
|
|
609
|
+
changed = true;
|
|
610
|
+
if (isPlainObject(tool) && typeof tool.name === "string") omittedFunctionNames.add(tool.name);
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
521
613
|
if (fixed !== tool) changed = true;
|
|
522
|
-
|
|
523
|
-
}
|
|
614
|
+
normalized.push(fixed);
|
|
615
|
+
}
|
|
524
616
|
return changed ? normalized : tools;
|
|
525
617
|
};
|
|
526
618
|
|
|
@@ -540,7 +632,14 @@ function normalizeToolSchemas(body: unknown): unknown {
|
|
|
540
632
|
});
|
|
541
633
|
if (inputChanged) normalizedBody = { ...normalizedBody, input };
|
|
542
634
|
}
|
|
543
|
-
|
|
635
|
+
if (omittedFunctionNames.size > 0) {
|
|
636
|
+
// A dropped tool is a capability the caller declared and will not get, and the only other
|
|
637
|
+
// trace of it is a turn that never makes the call. Name them so the cause is recoverable.
|
|
638
|
+
debugProviderDiagnostic("openai-responses", "tool-schema-omitted", {
|
|
639
|
+
omitted: [...omittedFunctionNames],
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
return reconcileToolChoiceForOmittedTools(normalizedBody, omittedFunctionNames);
|
|
544
643
|
}
|
|
545
644
|
|
|
546
645
|
function activateDeferredTool(tool: Record<string, unknown>): Record<string, unknown> {
|
|
@@ -1075,6 +1174,143 @@ function stripUnsupportedForwardParams(body: unknown): unknown {
|
|
|
1075
1174
|
return rest;
|
|
1076
1175
|
}
|
|
1077
1176
|
|
|
1177
|
+
/** Return the lossless text represented by one system message, or null when it is multimodal. */
|
|
1178
|
+
function canonicalForwardSystemText(item: Record<string, unknown>): string | null {
|
|
1179
|
+
const content = item.content;
|
|
1180
|
+
if (content === undefined) return "";
|
|
1181
|
+
if (typeof content === "string") return content;
|
|
1182
|
+
if (!Array.isArray(content)) return null;
|
|
1183
|
+
let text = "";
|
|
1184
|
+
for (const block of content) {
|
|
1185
|
+
if (!isPlainObject(block)) return null;
|
|
1186
|
+
if (block.type !== "input_text" && block.type !== "text") return null;
|
|
1187
|
+
if (typeof block.text !== "string") return null;
|
|
1188
|
+
text += block.text;
|
|
1189
|
+
}
|
|
1190
|
+
return text;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
/**
|
|
1194
|
+
* The public Responses API accepts input system messages and `truncation`, but the canonical
|
|
1195
|
+
* ChatGPT Codex forward endpoint rejects both. Fold only fully textual system messages into the
|
|
1196
|
+
* existing top-level instructions and remove the unsupported flag at this destination boundary.
|
|
1197
|
+
*
|
|
1198
|
+
* The fold is atomic: if any system message contains a non-text block, keep every message in
|
|
1199
|
+
* place so the proxy never silently drops multimodal content. The backend may still reject that
|
|
1200
|
+
* unsupported shape, but it will not receive a partially rewritten prompt.
|
|
1201
|
+
*/
|
|
1202
|
+
function normalizeCanonicalForwardPromptEnvelope(body: unknown): unknown {
|
|
1203
|
+
if (!isPlainObject(body)) return body;
|
|
1204
|
+
const stripTruncation = Object.hasOwn(body, "truncation");
|
|
1205
|
+
const input = Array.isArray(body.input) ? body.input : undefined;
|
|
1206
|
+
if (!input) {
|
|
1207
|
+
if (!stripTruncation) return body;
|
|
1208
|
+
const { truncation: _truncation, ...rest } = body;
|
|
1209
|
+
return rest;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
const foldedText: string[] = [];
|
|
1213
|
+
let sawSystemMessage = false;
|
|
1214
|
+
let canFoldAllSystemMessages = true;
|
|
1215
|
+
for (const item of input) {
|
|
1216
|
+
if (!isPlainObject(item) || item.role !== "system") continue;
|
|
1217
|
+
sawSystemMessage = true;
|
|
1218
|
+
const text = canonicalForwardSystemText(item);
|
|
1219
|
+
if (text === null) {
|
|
1220
|
+
canFoldAllSystemMessages = false;
|
|
1221
|
+
break;
|
|
1222
|
+
}
|
|
1223
|
+
foldedText.push(text);
|
|
1224
|
+
}
|
|
1225
|
+
if (!stripTruncation && (!sawSystemMessage || !canFoldAllSystemMessages)) return body;
|
|
1226
|
+
|
|
1227
|
+
const next: Record<string, unknown> = { ...body };
|
|
1228
|
+
if (stripTruncation) delete next.truncation;
|
|
1229
|
+
if (sawSystemMessage && canFoldAllSystemMessages) {
|
|
1230
|
+
next.input = input.filter(item => !isPlainObject(item) || item.role !== "system");
|
|
1231
|
+
const folded = foldedText.join("\n\n");
|
|
1232
|
+
if (folded !== "") {
|
|
1233
|
+
const existing = typeof body.instructions === "string" ? body.instructions : "";
|
|
1234
|
+
next.instructions = existing !== "" ? `${existing}\n\n${folded}` : folded;
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
return next;
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
const POSIT_CACHE_MARKER_MAX_DEPTH = 64;
|
|
1241
|
+
const POSIT_CACHE_MARKER_MAX_NODES = 100_000;
|
|
1242
|
+
|
|
1243
|
+
type PromptCacheMarkerRewrite = {
|
|
1244
|
+
value: unknown;
|
|
1245
|
+
changed: boolean;
|
|
1246
|
+
complete: boolean;
|
|
1247
|
+
};
|
|
1248
|
+
|
|
1249
|
+
/**
|
|
1250
|
+
* Remove Posit/Anthropic-style prompt-cache markers without trusting request nesting. The walk
|
|
1251
|
+
* aborts atomically when its depth or node budget is exceeded, so a hostile extension object can
|
|
1252
|
+
* neither overflow the stack nor receive a partially rewritten subtree.
|
|
1253
|
+
*/
|
|
1254
|
+
function stripPromptCacheBreakpoints(
|
|
1255
|
+
value: unknown,
|
|
1256
|
+
state: { nodes: number },
|
|
1257
|
+
depth = 0,
|
|
1258
|
+
): PromptCacheMarkerRewrite {
|
|
1259
|
+
state.nodes += 1;
|
|
1260
|
+
if (depth > POSIT_CACHE_MARKER_MAX_DEPTH || state.nodes > POSIT_CACHE_MARKER_MAX_NODES) {
|
|
1261
|
+
return { value, changed: false, complete: false };
|
|
1262
|
+
}
|
|
1263
|
+
if (Array.isArray(value)) {
|
|
1264
|
+
let changed = false;
|
|
1265
|
+
const next: unknown[] = [];
|
|
1266
|
+
for (const entry of value) {
|
|
1267
|
+
const rewritten = stripPromptCacheBreakpoints(entry, state, depth + 1);
|
|
1268
|
+
if (!rewritten.complete) return { value, changed: false, complete: false };
|
|
1269
|
+
changed ||= rewritten.changed;
|
|
1270
|
+
next.push(rewritten.value);
|
|
1271
|
+
}
|
|
1272
|
+
return { value: changed ? next : value, changed, complete: true };
|
|
1273
|
+
}
|
|
1274
|
+
if (!isPlainObject(value)) return { value, changed: false, complete: true };
|
|
1275
|
+
|
|
1276
|
+
let changed = Object.hasOwn(value, "prompt_cache_breakpoint");
|
|
1277
|
+
const next: Record<string, unknown> = {};
|
|
1278
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
1279
|
+
if (key === "prompt_cache_breakpoint") continue;
|
|
1280
|
+
const rewritten = stripPromptCacheBreakpoints(entry, state, depth + 1);
|
|
1281
|
+
if (!rewritten.complete) return { value, changed: false, complete: false };
|
|
1282
|
+
changed ||= rewritten.changed;
|
|
1283
|
+
next[key] = rewritten.value;
|
|
1284
|
+
}
|
|
1285
|
+
return { value: changed ? next : value, changed, complete: true };
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
/**
|
|
1289
|
+
* Posit Assistant can replay client-only cache markers and stored-item references on a
|
|
1290
|
+
* `store: false` continuation. The canonical ChatGPT Codex backend rejects both. Remove the
|
|
1291
|
+
* markers recursively and drop only `item_reference` rows that cannot name persisted state;
|
|
1292
|
+
* ordinary item ids are handled later by stripItemIdsWhenUnstored and tool call_id pairs remain.
|
|
1293
|
+
*/
|
|
1294
|
+
function normalizeCanonicalForwardContinuationEnvelope(body: unknown): unknown {
|
|
1295
|
+
if (!isPlainObject(body) || !Array.isArray(body.input)) return body;
|
|
1296
|
+
let input: unknown[] = body.input;
|
|
1297
|
+
let changed = false;
|
|
1298
|
+
if (body.store === false) {
|
|
1299
|
+
const withoutReferences = input.filter(item => !isPlainObject(item) || item.type !== "item_reference");
|
|
1300
|
+
if (withoutReferences.length !== input.length) {
|
|
1301
|
+
input = withoutReferences;
|
|
1302
|
+
changed = true;
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
const markerRewrite = stripPromptCacheBreakpoints(input, { nodes: 0 });
|
|
1307
|
+
if (markerRewrite.complete && markerRewrite.changed) {
|
|
1308
|
+
input = markerRewrite.value as unknown[];
|
|
1309
|
+
changed = true;
|
|
1310
|
+
}
|
|
1311
|
+
return changed ? { ...body, input } : body;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1078
1314
|
const IMAGE_GEN_NAMESPACE = "image_gen";
|
|
1079
1315
|
const HOSTED_IMAGE_GENERATION_TOOL = "image_generation";
|
|
1080
1316
|
const IMAGE_GEN_DOTTED_PREFIX = `${IMAGE_GEN_NAMESPACE}.`;
|
|
@@ -1555,6 +1791,57 @@ export function stripOpenAiOnlyWebSearchFields(body: unknown): unknown {
|
|
|
1555
1791
|
return changed ? next : body;
|
|
1556
1792
|
}
|
|
1557
1793
|
|
|
1794
|
+
/**
|
|
1795
|
+
* OpenCode Zen / Go Muse Spark Responses gateway refuses `search_content_types`
|
|
1796
|
+
* on a plain `web_search` tool (400) but accepts it on `web_search_preview`; a
|
|
1797
|
+
* plain `web_search` is also accepted. Probed directly against the gateway on
|
|
1798
|
+
* 2026-08-26: `web_search` + `search_content_types` -> 400, `web_search_preview`
|
|
1799
|
+
* + `search_content_types` -> 200, plain `web_search` -> 200. Luna accepts every
|
|
1800
|
+
* shape, so this is Muse-only. Drop only the field the gateway refuses while
|
|
1801
|
+
* keeping the tool type and every other accepted option intact.
|
|
1802
|
+
*/
|
|
1803
|
+
function stripMuseSparkUnsupportedWebSearchFields(body: unknown, modelId: unknown): unknown {
|
|
1804
|
+
if (!isPlainObject(body)) return body;
|
|
1805
|
+
if (typeof modelId !== "string" || modelId.trim().toLowerCase() !== "muse-spark-1.2-contributor") return body;
|
|
1806
|
+
|
|
1807
|
+
const rewriteTools = (tools: unknown[]): { tools: unknown[]; changed: boolean } => {
|
|
1808
|
+
let changed = false;
|
|
1809
|
+
const rewritten = tools.map(tool => {
|
|
1810
|
+
if (!isPlainObject(tool) || tool.type !== "web_search") return tool;
|
|
1811
|
+
if (!Object.hasOwn(tool, "search_content_types")) return tool;
|
|
1812
|
+
const { search_content_types: _dropped, ...rest } = tool;
|
|
1813
|
+
changed = true;
|
|
1814
|
+
return rest;
|
|
1815
|
+
});
|
|
1816
|
+
return { tools: changed ? rewritten : tools, changed };
|
|
1817
|
+
};
|
|
1818
|
+
|
|
1819
|
+
let next: Record<string, unknown> = body;
|
|
1820
|
+
let changed = false;
|
|
1821
|
+
if (Array.isArray(body.tools)) {
|
|
1822
|
+
const rewritten = rewriteTools(body.tools);
|
|
1823
|
+
if (rewritten.changed) {
|
|
1824
|
+
next = { ...next, tools: rewritten.tools };
|
|
1825
|
+
changed = true;
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
if (Array.isArray(next.input)) {
|
|
1829
|
+
let inputChanged = false;
|
|
1830
|
+
const input = next.input.map(item => {
|
|
1831
|
+
if (!isPlainObject(item) || item.type !== "additional_tools" || !Array.isArray(item.tools)) return item;
|
|
1832
|
+
const rewritten = rewriteTools(item.tools);
|
|
1833
|
+
if (!rewritten.changed) return item;
|
|
1834
|
+
inputChanged = true;
|
|
1835
|
+
return { ...item, tools: rewritten.tools };
|
|
1836
|
+
});
|
|
1837
|
+
if (inputChanged) {
|
|
1838
|
+
next = { ...next, input };
|
|
1839
|
+
changed = true;
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
return changed ? next : body;
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1558
1845
|
/** Replace every `input_image` part under a routed-compaction body with a short marker. */
|
|
1559
1846
|
function stripInputImagesDeep(value: unknown): unknown {
|
|
1560
1847
|
if (Array.isArray(value)) return value.map(stripInputImagesDeep);
|
|
@@ -1714,6 +2001,8 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
1714
2001
|
// third-party forward gateway may still accept it, so this must not be widened.
|
|
1715
2002
|
if (isCanonicalOpenAiForwardProvider(provider)) {
|
|
1716
2003
|
outBody = stripDeprecatedPromptCacheRetention(outBody, parsed.modelId);
|
|
2004
|
+
outBody = normalizeCanonicalForwardPromptEnvelope(outBody);
|
|
2005
|
+
outBody = normalizeCanonicalForwardContinuationEnvelope(outBody);
|
|
1717
2006
|
}
|
|
1718
2007
|
} else {
|
|
1719
2008
|
outBody = preferConfiguredHostedTools(
|
|
@@ -1766,6 +2055,7 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
1766
2055
|
if (provider.supportsOpenAiWebSearchToolFields === false) {
|
|
1767
2056
|
outBody = stripOpenAiOnlyWebSearchFields(outBody);
|
|
1768
2057
|
}
|
|
2058
|
+
outBody = stripMuseSparkUnsupportedWebSearchFields(outBody, parsed.modelId);
|
|
1769
2059
|
// Last, so promoted namespace children are also cleared of Codex-private fields.
|
|
1770
2060
|
outBody = stripCanonicalOnlyToolFields(outBody, provider.supportsOpenAiWebSearchToolFields === false);
|
|
1771
2061
|
}
|
|
@@ -1778,17 +2068,37 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
1778
2068
|
outBody = buildRoutedCompactionBody(outBody);
|
|
1779
2069
|
}
|
|
1780
2070
|
const threadServingIdentityChanged = parsed._stripReasoningEncryptedContent === true;
|
|
1781
|
-
const sanitizedBody = normalizeToolSchemas(
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
2071
|
+
const sanitizedBody = normalizeToolSchemas(
|
|
2072
|
+
stripSparkCompatibility(
|
|
2073
|
+
stripUnsupportedReasoningParams(
|
|
2074
|
+
stripItemIdsWhenUnstored(
|
|
2075
|
+
stripInvalidItemIds(
|
|
2076
|
+
stripUnsupportedHostedTools(
|
|
2077
|
+
sanitizeReasoningInputContent(
|
|
2078
|
+
scrubOcxCompactionItems(
|
|
2079
|
+
outBody,
|
|
2080
|
+
destinationDecodesNativeCompactionBlob(provider),
|
|
2081
|
+
threadServingIdentityChanged,
|
|
2082
|
+
),
|
|
2083
|
+
{
|
|
2084
|
+
preserveRawReasoningContent: provider.preserveResponsesReasoningContent === true,
|
|
2085
|
+
dropNullContentChannel: !isOpenAiOperatedResponsesDestination(provider),
|
|
2086
|
+
stripEncryptedContent: threadServingIdentityChanged,
|
|
2087
|
+
},
|
|
2088
|
+
),
|
|
2089
|
+
),
|
|
2090
|
+
),
|
|
2091
|
+
),
|
|
2092
|
+
),
|
|
2093
|
+
),
|
|
2094
|
+
isXaiSchemaTarget(provider),
|
|
2095
|
+
);
|
|
2096
|
+
const finalBody = stripDisabledVerbosity(
|
|
2097
|
+
stripDisabledReasoningSummaries(
|
|
2098
|
+
normalizeConfiguredReasoningSummaryDelivery(sanitizedBody, provider, parsed.modelId),
|
|
2099
|
+
provider,
|
|
2100
|
+
parsed.modelId,
|
|
2101
|
+
),
|
|
1792
2102
|
provider,
|
|
1793
2103
|
parsed.modelId,
|
|
1794
2104
|
);
|
|
@@ -28,7 +28,7 @@ const NEIGHBOR_AGENT_TOOL_NAMES = ["Read", "Grep", "Glob", "Bash", "LS"] as cons
|
|
|
28
28
|
const CODEX_UNIFIED_EXEC_TOOL_NAME = "exec";
|
|
29
29
|
const CODEX_SHELL_BRIDGE_TOOL_NAMES = ["exec_command", "shell_command"] as const;
|
|
30
30
|
|
|
31
|
-
function isCodexCodeModeExecTool(tool: Pick<OcxTool, "namespace" | "name" | "freeform">): boolean {
|
|
31
|
+
export function isCodexCodeModeExecTool(tool: Pick<OcxTool, "namespace" | "name" | "freeform">): boolean {
|
|
32
32
|
return !tool.namespace && tool.name === CODEX_UNIFIED_EXEC_TOOL_NAME && tool.freeform === true;
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -45,7 +45,7 @@ function isCodexCodeModeExecTool(tool: Pick<OcxTool, "namespace" | "name" | "fre
|
|
|
45
45
|
* `!tool.namespace` requirement; dropping it here made the name assert a check the body did not
|
|
46
46
|
* perform.
|
|
47
47
|
*/
|
|
48
|
-
function isBareShellBridgeTool(tool: Pick<OcxTool, "namespace" | "name">): boolean {
|
|
48
|
+
export function isBareShellBridgeTool(tool: Pick<OcxTool, "namespace" | "name">): boolean {
|
|
49
49
|
return !tool.namespace && (CODEX_SHELL_BRIDGE_TOOL_NAMES as readonly string[]).includes(tool.name);
|
|
50
50
|
}
|
|
51
51
|
|