@bitkyc08/opencodex 2.52.0 → 2.53.0-preview.20260913
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/index-BBOZWGB6.css +1 -0
- package/gui/dist/assets/index-D7ynYo2K.js +128 -0
- package/gui/dist/index.html +2 -2
- package/native/remote-workspace-helper/Cargo.lock +130 -0
- package/native/remote-workspace-helper/Cargo.toml +24 -0
- package/native/remote-workspace-helper/src/main.rs +49 -0
- package/native/remote-workspace-helper/src/protocol.rs +246 -0
- package/native/remote-workspace-helper/src/sandbox/macos.rs +19 -0
- package/native/remote-workspace-helper/src/sandbox/mod.rs +77 -0
- package/native/remote-workspace-helper/src/sandbox/windows.rs +15 -0
- package/package.json +6 -1
- package/src/adapters/anthropic-image-normalize.ts +30 -2
- package/src/adapters/anthropic.ts +1 -1
- package/src/adapters/base.ts +8 -2
- package/src/adapters/cursor/cursor-errors.ts +12 -0
- package/src/adapters/cursor/thread-continuity.ts +93 -0
- package/src/adapters/cursor.ts +104 -73
- package/src/adapters/devin/cloud-direct/chat.ts +312 -23
- package/src/adapters/devin/cloud-direct/metadata.ts +31 -2
- package/src/adapters/devin/live-models.ts +70 -3
- package/src/adapters/devin.ts +281 -21
- package/src/adapters/google-wire-compiler.ts +14 -6
- package/src/adapters/google.ts +22 -8
- package/src/adapters/kiro/adapter.ts +316 -0
- package/src/adapters/kiro/conversation.ts +136 -0
- package/src/adapters/kiro/payload.ts +432 -0
- package/src/adapters/kiro/reasoning.ts +56 -0
- package/src/adapters/kiro/stream.ts +1153 -0
- package/src/adapters/kiro/usage.ts +223 -0
- package/src/adapters/kiro/wire.ts +76 -0
- package/src/adapters/kiro.ts +8 -2319
- package/src/adapters/mimo-free.ts +1 -1
- package/src/adapters/openai-chat-images.ts +101 -0
- package/src/adapters/openai-chat.ts +201 -181
- package/src/adapters/openai-responses.ts +92 -224
- package/src/adapters/registry.ts +0 -7
- package/src/adapters/run-turn-queue.ts +13 -6
- package/src/bridge.ts +14 -15
- package/src/chat/inbound.ts +29 -4
- package/src/chat/outbound.ts +145 -107
- package/src/claude/desktop-profile.ts +4 -6
- package/src/cli/account-api.ts +14 -0
- package/src/cli/account-extended.ts +1 -1
- package/src/cli/account-history.ts +60 -0
- package/src/cli/account-main.ts +80 -0
- package/src/cli/account.ts +11 -3
- package/src/cli/capabilities.ts +113 -0
- package/src/cli/catalog.ts +109 -0
- package/src/cli/dispatch.ts +9 -0
- package/src/cli/help.ts +2 -0
- package/src/cli/index.ts +2 -2
- package/src/cli/observe.ts +28 -1
- package/src/cli/opencode.ts +42 -8
- package/src/cli/provider-runtime.ts +11 -1
- package/src/cli/provider.ts +22 -2
- package/src/cli/registry.ts +21 -0
- package/src/cli/remote-workspace.ts +154 -0
- package/src/cli/status.ts +39 -7
- package/src/cli/usage-report.ts +14 -2
- package/src/client/hub-client.ts +34 -0
- package/src/client/hub-state.ts +9 -1
- package/src/codex/account-store.ts +78 -0
- package/src/codex/auth-api.ts +81 -54
- package/src/codex/auth-context.ts +45 -16
- package/src/codex/catalog/effort.ts +1 -1
- package/src/codex/catalog/metadata.ts +3 -6
- package/src/codex/catalog/native-models.ts +4 -4
- package/src/codex/catalog/parsing.ts +2 -20
- package/src/codex/catalog/provider-fetch.ts +10 -1
- package/src/codex/catalog/remote.ts +233 -0
- package/src/codex/catalog/sync.ts +403 -35
- package/src/codex/convergence.ts +1 -1
- package/src/codex/history-manifest.ts +36 -0
- package/src/codex/history-provider.ts +32 -5
- package/src/codex/inject.ts +9 -0
- package/src/codex/main-account.ts +113 -0
- package/src/codex/main-device-reauth-api.ts +89 -0
- package/src/codex/main-device-reauth.ts +217 -0
- package/src/codex/native-residue.ts +9 -2
- package/src/codex/quota-auto-refresh.ts +3 -2
- package/src/codex/quota-capacity.ts +98 -0
- package/src/codex/quota-history.ts +160 -0
- package/src/codex/quota-types.ts +8 -0
- package/src/codex/quota.ts +118 -91
- package/src/codex/refresh.ts +2 -1
- package/src/codex/routing.ts +90 -17
- package/src/codex/sync.ts +33 -4
- package/src/combos/request.ts +19 -1
- package/src/config/multi-agent-surface.ts +61 -0
- package/src/config/provider-validation.ts +176 -0
- package/src/config.ts +213 -11
- package/src/generated/compatibility-version.json +436 -168
- package/src/images/loop.ts +119 -36
- package/src/lib/admission.ts +12 -6
- package/src/lib/redact.ts +7 -0
- package/src/lib/translator-budget.ts +4 -3
- package/src/lib/windows-atomic-replace.ts +1 -0
- package/src/lib/windows-elevation.ts +1 -1
- package/src/oauth/chatgpt-device.ts +62 -5
- package/src/oauth/devin/cli-import.ts +130 -0
- package/src/oauth/devin.ts +63 -8
- package/src/oauth/index.ts +29 -14
- package/src/oauth/kiro.ts +18 -6
- package/src/oauth/login-cli.ts +9 -1
- package/src/oauth/meta-muse-device.ts +464 -0
- package/src/oauth/meta-muse.ts +123 -32
- package/src/oauth/pool-kernel.ts +9 -0
- package/src/oauth/pool-settings-capability.ts +2 -2
- package/src/oauth/store.ts +57 -0
- package/src/oauth/types.ts +31 -0
- package/src/providers/derive.ts +13 -3
- package/src/providers/devin-cli-authmode-migration.ts +57 -35
- package/src/providers/devin-provider-merge-migration.ts +240 -0
- package/src/providers/muse-key-quota.ts +117 -0
- package/src/providers/muse-subscription-usage.ts +14 -2
- package/src/providers/openai-sidecar.ts +25 -3
- package/src/providers/opencode-zen-rate-limit.ts +58 -0
- package/src/providers/provider-id-rewrite.ts +20 -5
- package/src/providers/quota-types.ts +12 -0
- package/src/providers/quota.ts +143 -102
- package/src/providers/reasoning-metadata.ts +543 -0
- package/src/providers/registry.ts +80 -49
- package/src/reasoning-effort.ts +26 -2
- package/src/remote/hub-usage.ts +32 -0
- package/src/remote-control/index.ts +192 -41
- package/src/remote-control/workspace-activation.ts +9 -0
- package/src/remote-control/workspace-agent-connection.ts +366 -0
- package/src/remote-control/workspace-claude-runtime.ts +243 -0
- package/src/remote-control/workspace-codex-runtime.ts +531 -0
- package/src/remote-control/workspace-codex-sandbox.ts +115 -0
- package/src/remote-control/workspace-command-runner.ts +748 -0
- package/src/remote-control/workspace-coordinator.ts +231 -0
- package/src/remote-control/workspace-device.ts +585 -0
- package/src/remote-control/workspace-executable.ts +43 -0
- package/src/remote-control/workspace-executor.ts +397 -0
- package/src/remote-control/workspace-hub.ts +519 -0
- package/src/remote-control/workspace-pi-runtime.ts +382 -0
- package/src/remote-control/workspace-process.ts +129 -0
- package/src/remote-control/workspace-rpc.ts +304 -0
- package/src/remote-control/workspace-runtime.ts +60 -0
- package/src/remote-control/workspace-secret-store.ts +39 -0
- package/src/remote-control/workspace-sessions.ts +799 -0
- package/src/remote-control/workspace-tool-bridge.ts +192 -0
- package/src/responses/code-mode-helper-compat.ts +22 -3
- package/src/responses/hosted-tool-policy.ts +0 -1
- package/src/responses/muse-tool-name-alias.ts +379 -0
- package/src/responses/plaintext-v2-agent-messages.ts +902 -0
- package/src/router.ts +7 -0
- package/src/routing/compatibility/behavior.ts +0 -1
- package/src/server/audio-client.ts +64 -0
- package/src/server/audio-dictation.ts +91 -0
- package/src/server/audio-live.ts +185 -0
- package/src/server/audio-transcriptions.ts +183 -0
- package/src/server/audio-upstream.ts +153 -0
- package/src/server/auth-cors.ts +61 -2
- package/src/server/chat-completions.ts +1 -1
- package/src/server/chat-native-sse.ts +92 -48
- package/src/server/chat-native.ts +37 -15
- package/src/server/hub-usage.ts +57 -0
- package/src/server/images.ts +4 -0
- package/src/server/index.ts +722 -57
- package/src/server/lifecycle.ts +5 -6
- package/src/server/live-call-bindings.ts +60 -0
- package/src/server/live.ts +12 -1
- package/src/server/management/agent-settings-routes.ts +25 -4
- package/src/server/management/api-access.ts +37 -0
- package/src/server/management/api-key-usage.ts +7 -2
- package/src/server/management/config-routes.ts +1 -18
- package/src/server/management/context.ts +15 -0
- package/src/server/management/logs-usage-routes.ts +2 -0
- package/src/server/management/oauth-account-routes.ts +39 -12
- package/src/server/management/provider-routes.ts +125 -2
- package/src/server/management/remote-workspace-routes.ts +140 -0
- package/src/server/management/route-registry.ts +15 -0
- package/src/server/management/usage-aggregate-cache.ts +14 -15
- package/src/server/management/usage-summary-cache.ts +2 -0
- package/src/server/management-api.ts +23 -0
- package/src/server/ports.ts +17 -0
- package/src/server/relay-eager.ts +4 -1
- package/src/server/relay.ts +70 -10
- package/src/server/request-decompress.ts +6 -3
- package/src/server/responses/agent-task-recovery.ts +25 -32
- package/src/server/responses/codex-auth-error.ts +11 -0
- package/src/server/responses/codex-ws-exchange.ts +52 -3
- package/src/server/responses/codex-ws-wire.ts +55 -0
- package/src/server/responses/compact.ts +9 -1
- package/src/server/responses/core.ts +337 -73
- package/src/server/responses/encrypted-payload.ts +45 -2
- package/src/server/responses/ws-upstream.ts +4 -1
- package/src/server/responses-self-named-namespace-scrub.ts +1 -3
- package/src/server/responses-undeclared-tool-guard.ts +1 -1
- package/src/server/search.ts +3 -0
- package/src/server/sse-payload-rewrite.ts +136 -51
- package/src/server/ws-bridge.ts +35 -1
- package/src/service/cli.ts +372 -0
- package/src/service/diagnostics.ts +340 -0
- package/src/service/guards.ts +303 -0
- package/src/service/health.ts +222 -0
- package/src/service/launchd.ts +853 -0
- package/src/service/orchestration.ts +617 -0
- package/src/service/repair.ts +334 -0
- package/src/service/state.ts +363 -0
- package/src/service/systemd.ts +229 -0
- package/src/service/windows-ops.ts +690 -0
- package/src/service/windows-scheduler.ts +769 -0
- package/src/service/windows-taskxml.ts +613 -0
- package/src/service.ts +22 -5550
- package/src/storage/cleanup/db.ts +258 -0
- package/src/storage/cleanup/execute.ts +358 -0
- package/src/storage/cleanup/paths.ts +189 -0
- package/src/storage/cleanup/pending.ts +140 -0
- package/src/storage/cleanup/preview.ts +292 -0
- package/src/storage/cleanup/reconcile.ts +347 -0
- package/src/storage/cleanup/restore.ts +932 -0
- package/src/storage/cleanup/satellite.ts +474 -0
- package/src/storage/cleanup/staging.ts +129 -0
- package/src/storage/cleanup/types.ts +98 -0
- package/src/storage/cleanup.ts +49 -3127
- package/src/types/accounts.ts +2 -0
- package/src/types/config.ts +13 -12
- package/src/types/provider.ts +37 -0
- package/src/types/request.ts +2 -0
- package/src/types/tools.ts +17 -5
- package/src/types.ts +1 -0
- package/src/usage/expected-prices.ts +127 -0
- package/src/usage/log.ts +58 -1
- package/src/vision/eligibility.ts +13 -2
- package/src/web-search/loop.ts +56 -3
- package/gui/dist/assets/index-CWXut3rG.js +0 -115
- package/gui/dist/assets/index-EdoPnm9_.css +0 -1
- package/src/adapters/devin-cli/acp.ts +0 -204
- package/src/adapters/devin-cli/adapter.ts +0 -345
- package/src/adapters/devin-cli/binary.ts +0 -69
- package/src/adapters/devin-cli/models.ts +0 -57
- package/src/oauth/devin-cli.ts +0 -149
- package/src/server/responses-reasoning-summary-rewrite.ts +0 -178
|
@@ -225,7 +225,7 @@ export function createMimoFreeAdapter(provider: OcxProviderConfig): ProviderAdap
|
|
|
225
225
|
|
|
226
226
|
// Let the base adapter build the wire body (handles reasoning, tools, etc.)
|
|
227
227
|
// but override the URL and headers after.
|
|
228
|
-
const baseReq = base.buildRequest(parsed, incoming)
|
|
228
|
+
const baseReq = await base.buildRequest(parsed, incoming);
|
|
229
229
|
const baseBody = JSON.parse(baseReq.body as string) as unknown;
|
|
230
230
|
const markedBody = injectMimoSystemMarker(baseBody);
|
|
231
231
|
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { parseDataUrl } from "./image";
|
|
2
|
+
import {
|
|
3
|
+
normalizeImageTargets,
|
|
4
|
+
type NormalizeOptions,
|
|
5
|
+
type NormalizeTarget,
|
|
6
|
+
} from "./anthropic-image-normalize";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Best-effort base64 image budget for translated Chat requests. This leaves room for
|
|
10
|
+
* other request fields but is not a guarantee that the complete body fits an upstream
|
|
11
|
+
* limit. Remote URLs are never fetched by request construction.
|
|
12
|
+
*/
|
|
13
|
+
export const OPENAI_CHAT_IMAGE_BASE64_BUDGET = 3_670_016; // 3.5MiB
|
|
14
|
+
|
|
15
|
+
export interface NormalizeOpenAIChatImagesOptions
|
|
16
|
+
extends Pick<NormalizeOptions, "encode" | "tierBias" | "validate"> {}
|
|
17
|
+
|
|
18
|
+
/** Whether `value` is a plain object, so message and part shapes can be walked safely. */
|
|
19
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
20
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Walk every well-formed `image_url` part in a Chat Completions message array, ignoring
|
|
25
|
+
* malformed shapes rather than throwing on them. Returning false from `visit` stops the walk.
|
|
26
|
+
*/
|
|
27
|
+
function forEachImagePart(
|
|
28
|
+
messages: unknown,
|
|
29
|
+
visit: (imageUrl: Record<string, unknown>, url: string) => boolean | void,
|
|
30
|
+
): void {
|
|
31
|
+
if (!Array.isArray(messages)) return;
|
|
32
|
+
for (const message of messages) {
|
|
33
|
+
if (!isRecord(message) || !Array.isArray(message.content)) continue;
|
|
34
|
+
for (const part of message.content) {
|
|
35
|
+
if (!isRecord(part) || part.type !== "image_url" || !isRecord(part.image_url)) continue;
|
|
36
|
+
const imageUrl = part.image_url;
|
|
37
|
+
if (typeof imageUrl.url !== "string") continue;
|
|
38
|
+
if (visit(imageUrl, imageUrl.url) === false) return;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Whether this turn carries inline image bytes worth normalizing. The adapter uses this
|
|
45
|
+
* to stay synchronous for text-only turns, which is every turn on most providers.
|
|
46
|
+
*/
|
|
47
|
+
export function hasShrinkableOpenAIChatImages(messages: unknown): boolean {
|
|
48
|
+
let total = 0;
|
|
49
|
+
let found = false;
|
|
50
|
+
forEachImagePart(messages, (_imageUrl, url) => {
|
|
51
|
+
const source = parseDataUrl(url);
|
|
52
|
+
if (!source) return;
|
|
53
|
+
total += source.base64.length;
|
|
54
|
+
if (total > OPENAI_CHAT_IMAGE_BASE64_BUDGET) {
|
|
55
|
+
found = true;
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
return found;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Normalize image_url parts in already-built Chat Completions messages, in place.
|
|
64
|
+
*
|
|
65
|
+
* The drop callback deliberately keeps the original URL. The shared normalizer calls
|
|
66
|
+
* drop for corrupt or decode-bomb inputs, and this wire has no downstream guard that
|
|
67
|
+
* would re-attach a dropped image, so dropping here would silently lose a user's
|
|
68
|
+
* screenshot. Terminal-size overflow uses overflowAction "none" for the same reason:
|
|
69
|
+
* an image floored at 320px stays attached rather than being removed.
|
|
70
|
+
*/
|
|
71
|
+
export async function normalizeOpenAIChatImages(
|
|
72
|
+
messages: unknown,
|
|
73
|
+
options: NormalizeOpenAIChatImagesOptions = {},
|
|
74
|
+
): Promise<void> {
|
|
75
|
+
const targets: NormalizeTarget[] = [];
|
|
76
|
+
forEachImagePart(messages, (imageUrl, url) => {
|
|
77
|
+
const source = parseDataUrl(url);
|
|
78
|
+
if (!source) return;
|
|
79
|
+
targets.push({
|
|
80
|
+
base64: source.base64,
|
|
81
|
+
mediaType: source.mediaType,
|
|
82
|
+
replace: (data: string, mediaType: string) => {
|
|
83
|
+
imageUrl.url = `data:${mediaType};base64,${data}`;
|
|
84
|
+
},
|
|
85
|
+
drop: () => {
|
|
86
|
+
// Preserve the original image URL when it cannot be normalized.
|
|
87
|
+
},
|
|
88
|
+
// The drop above is a no-op, so these bytes are still on the wire and must keep
|
|
89
|
+
// counting against the budget. Without this the core would stop counting them and
|
|
90
|
+
// the demotion loop could stop early, shipping a body that is still oversized.
|
|
91
|
+
retainsBytesOnDrop: true,
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
if (targets.length === 0) return;
|
|
95
|
+
|
|
96
|
+
await normalizeImageTargets(targets, {
|
|
97
|
+
budget: OPENAI_CHAT_IMAGE_BASE64_BUDGET,
|
|
98
|
+
overflowAction: "none",
|
|
99
|
+
...options,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { hasShrinkableOpenAIChatImages, normalizeOpenAIChatImages } from "./openai-chat-images";
|
|
2
|
+
import type { AdapterRequest, IncomingMeta, ProviderAdapter } from "./base";
|
|
2
3
|
import type { AdapterEvent, OcxAssistantMessage, OcxContentPart, OcxMessage, OcxParsedRequest, OcxProviderConfig, OcxTextContent, OcxThinkingContent, OcxToolCall, OcxUsage } from "../types";
|
|
3
4
|
import { isAllowedToolChoice, modelInList, namespacedToolName, resolveToolChoiceWireName, toolChoiceToolPredicate } from "../types";
|
|
4
5
|
import { mapReasoningEffort, modelRecordValue } from "../reasoning-effort";
|
|
6
|
+
import { registryEntryForProviderDestination } from "../providers/registry";
|
|
5
7
|
import { debugProviderDiagnostic } from "../lib/debug";
|
|
6
8
|
import { sseFieldValue } from "../lib/sse-decoder";
|
|
7
9
|
import { isDebugEnabled } from "../lib/debug-settings";
|
|
@@ -130,6 +132,10 @@ export function buildOpenAIChatPassthroughRequest(
|
|
|
130
132
|
for (const field of CHAT_PASSTHROUGH_FIELDS) {
|
|
131
133
|
if (rawBody[field] !== undefined) body[field] = rawBody[field];
|
|
132
134
|
}
|
|
135
|
+
const rawEfforts = modelRecordValue(provider.modelReasoningEfforts, modelId) ?? provider.reasoningEfforts;
|
|
136
|
+
if (modelInList(provider.noReasoningModels, modelId) || rawEfforts?.length === 0) {
|
|
137
|
+
delete body.reasoning_effort;
|
|
138
|
+
}
|
|
133
139
|
|
|
134
140
|
const openRouterRouting = resolveOpenRouterRouting(provider, modelId);
|
|
135
141
|
if (openRouterRouting) body.provider = openRouterProviderPayload(openRouterRouting);
|
|
@@ -204,7 +210,7 @@ export function buildOpenAIChatPassthroughRequest(
|
|
|
204
210
|
messageCount: Array.isArray(body.messages) ? body.messages.length : 0,
|
|
205
211
|
toolCount: Array.isArray(body.tools) ? body.tools.length : 0,
|
|
206
212
|
hasCredential,
|
|
207
|
-
bodyBytes:
|
|
213
|
+
bodyBytes: Buffer.byteLength(bodyJson, "utf8"),
|
|
208
214
|
});
|
|
209
215
|
}
|
|
210
216
|
|
|
@@ -733,10 +739,14 @@ function messagesToChatFormat(parsed: OcxParsedRequest, provider: OcxProviderCon
|
|
|
733
739
|
};
|
|
734
740
|
|
|
735
741
|
const nativeOpenAI = isNativeOpenAIChatTarget(provider);
|
|
742
|
+
// Hoisting a newly appended reminder rewrites the reusable prompt prefix.
|
|
743
|
+
// Keep this compatibility exception on the destination/model tested with OCG.
|
|
744
|
+
const chronologicalSystem = parsed.modelId === "deepseek-v4.1-flash"
|
|
745
|
+
&& registryEntryForProviderDestination(provider)?.id === "opencode-go";
|
|
736
746
|
const toolCatalogNudge = shouldInjectNonOpenAIToolCatalogNudge(provider)
|
|
737
747
|
? buildNonOpenAIToolCatalogNudgeForTools(context.tools, options.toolChoice)
|
|
738
748
|
: undefined;
|
|
739
|
-
const developerSystemParts = nativeOpenAI
|
|
749
|
+
const developerSystemParts = nativeOpenAI || chronologicalSystem
|
|
740
750
|
? []
|
|
741
751
|
: context.messages
|
|
742
752
|
.map(developerSystemText)
|
|
@@ -762,11 +772,15 @@ function messagesToChatFormat(parsed: OcxParsedRequest, provider: OcxProviderCon
|
|
|
762
772
|
const hasImages = parts?.some(p => p.type === "image") ?? false;
|
|
763
773
|
let chatMsg: Record<string, unknown>;
|
|
764
774
|
if (msg.role === "developer" && !hasImages) {
|
|
765
|
-
if (!nativeOpenAI) break;
|
|
775
|
+
if (!nativeOpenAI && !chronologicalSystem) break;
|
|
766
776
|
const text = typeof msg.content === "string"
|
|
767
777
|
? msg.content
|
|
768
778
|
: parts!.map(p => (p as OcxTextContent).text).join("");
|
|
769
|
-
|
|
779
|
+
// A non-text timeline part (video, for example) serializes to nothing here.
|
|
780
|
+
// The generic path drops such a message; the chronological exception must not
|
|
781
|
+
// turn it into an empty system message that some upstreams reject.
|
|
782
|
+
if (!nativeOpenAI && text.length === 0) break;
|
|
783
|
+
chatMsg = { role: nativeOpenAI ? "developer" : "system", content: text };
|
|
770
784
|
} else if (typeof msg.content === "string") {
|
|
771
785
|
chatMsg = { role: "user", content: msg.content };
|
|
772
786
|
} else if (!hasImages) {
|
|
@@ -1462,206 +1476,212 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
1462
1476
|
|
|
1463
1477
|
formatErrorBody: formatOpenAIChatErrorBody,
|
|
1464
1478
|
|
|
1465
|
-
buildRequest(parsed: OcxParsedRequest) {
|
|
1479
|
+
buildRequest(parsed: OcxParsedRequest, incoming?: IncomingMeta) {
|
|
1466
1480
|
lastRequestedModelId = parsed.modelId;
|
|
1467
1481
|
const { url, headers, hasCredential } = openAIChatTransport(provider);
|
|
1468
1482
|
const messages = frameAgentRouterMessages(provider.baseUrl, messagesToChatFormat(parsed, provider));
|
|
1469
|
-
const
|
|
1470
|
-
|
|
1483
|
+
const finish = (): AdapterRequest => {
|
|
1484
|
+
const tools = toolsToChatFormatForProvider(parsed, provider);
|
|
1485
|
+
const toolChoice = toolChoiceToChatFormat(parsed.options.toolChoice, parsed.context.tools, provider);
|
|
1471
1486
|
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
}
|
|
1507
|
-
if (parsed.options.topP !== undefined && !modelInList(provider.noTopPModels, parsed.modelId)) {
|
|
1508
|
-
body.top_p = parsed.options.topP;
|
|
1509
|
-
}
|
|
1510
|
-
if (parsed.options.stopSequences !== undefined) body.stop = parsed.options.stopSequences;
|
|
1511
|
-
const reasoningDisabled = modelInList(provider.noReasoningModels, parsed.modelId);
|
|
1512
|
-
// Some gateways accept a reasoning-effort field on a plain turn but reject the
|
|
1513
|
-
// effort + tools combination. `noReasoningModels` would fix that only by
|
|
1514
|
-
// stripping reasoning everywhere, costing the model its whole picker. This keeps
|
|
1515
|
-
// the ladder advertised and drops the wire field for tool-bearing requests only.
|
|
1516
|
-
const omitReasoningEffortWithTools = !!tools
|
|
1517
|
-
&& modelInList(provider.omitReasoningEffortWithToolsModels, parsed.modelId);
|
|
1518
|
-
const reasoningEffort = omitReasoningEffortWithTools
|
|
1519
|
-
? undefined
|
|
1520
|
-
: mapReasoningEffort(provider, parsed.modelId, parsed.options.reasoning);
|
|
1521
|
-
const nativeOpenAI = isNativeOpenAIChatTarget(provider);
|
|
1522
|
-
let reasoningLog: AdapterRequest["reasoningLog"];
|
|
1523
|
-
if (!reasoningDisabled && !omitReasoningEffortWithTools && provider.reasoningWireFormat === "gateway-object" && parsed.options.reasoning === "none") {
|
|
1524
|
-
if (nativeOpenAI) {
|
|
1525
|
-
body.reasoning_effort = "none";
|
|
1526
|
-
reasoningLog = {
|
|
1527
|
-
effectiveEffort: "none",
|
|
1528
|
-
wireField: "reasoning_effort",
|
|
1529
|
-
wireValue: "none",
|
|
1530
|
-
};
|
|
1531
|
-
} else {
|
|
1532
|
-
body.reasoning = { enabled: false };
|
|
1533
|
-
reasoningLog = {
|
|
1534
|
-
effectiveEffort: "none",
|
|
1535
|
-
wireField: "reasoning.enabled",
|
|
1536
|
-
wireValue: false,
|
|
1537
|
-
};
|
|
1487
|
+
const body: Record<string, unknown> = {
|
|
1488
|
+
model: provider.modelSuffixBracketStrip ? stripBracketedModelSuffix(parsed.modelId) : parsed.modelId,
|
|
1489
|
+
messages,
|
|
1490
|
+
stream: parsed.stream,
|
|
1491
|
+
};
|
|
1492
|
+
// A policy-produced canonical decision has already passed capability validation. Without
|
|
1493
|
+
// that decision, a canonical caller value still requires an explicit true capability;
|
|
1494
|
+
// unclassified Chat routes remain behind the caller-forwarding opt-in.
|
|
1495
|
+
const serviceTier = parsed.options.serviceTier;
|
|
1496
|
+
const tierDecision = parsed.options.tierDecision;
|
|
1497
|
+
const canSerializeServiceTier = canSerializeOpenAIChatServiceTier(
|
|
1498
|
+
provider,
|
|
1499
|
+
parsed.modelId,
|
|
1500
|
+
serviceTier,
|
|
1501
|
+
tierDecision,
|
|
1502
|
+
);
|
|
1503
|
+
if (canSerializeServiceTier && serviceTier !== undefined) {
|
|
1504
|
+
body.service_tier = serviceTier;
|
|
1505
|
+
}
|
|
1506
|
+
if (modelInList(provider.reasoningSplitModels, parsed.modelId)) body.reasoning_split = true;
|
|
1507
|
+
const maxTokens = resolveMaxTokens(provider, parsed);
|
|
1508
|
+
const openRouterRouting = resolveOpenRouterRouting(provider, parsed.modelId);
|
|
1509
|
+
if (openRouterRouting) body.provider = openRouterProviderPayload(openRouterRouting);
|
|
1510
|
+
const vercelRouting = resolveVercelGatewayRouting(provider, parsed.modelId);
|
|
1511
|
+
if (vercelRouting) body.provider = vercelGatewayProviderPayload(vercelRouting);
|
|
1512
|
+
if (tools) body.tools = tools;
|
|
1513
|
+
if (tools && toolChoice !== undefined) {
|
|
1514
|
+
body.tool_choice = modelInList(provider.autoToolChoiceOnlyModels, parsed.modelId)
|
|
1515
|
+
? (toolChoice === "none" ? "none" : "auto")
|
|
1516
|
+
: toolChoice;
|
|
1517
|
+
}
|
|
1518
|
+
if (maxTokens !== undefined) body.max_tokens = maxTokens;
|
|
1519
|
+
if (parsed.options.temperature !== undefined && !modelInList(provider.noTemperatureModels, parsed.modelId)) {
|
|
1520
|
+
body.temperature = parsed.options.temperature;
|
|
1538
1521
|
}
|
|
1539
|
-
|
|
1540
|
-
|
|
1522
|
+
if (parsed.options.topP !== undefined && !modelInList(provider.noTopPModels, parsed.modelId)) {
|
|
1523
|
+
body.top_p = parsed.options.topP;
|
|
1524
|
+
}
|
|
1525
|
+
if (parsed.options.stopSequences !== undefined) body.stop = parsed.options.stopSequences;
|
|
1526
|
+
const reasoningDisabled = modelInList(provider.noReasoningModels, parsed.modelId);
|
|
1527
|
+
// Some gateways accept a reasoning-effort field on a plain turn but reject the
|
|
1528
|
+
// effort + tools combination. `noReasoningModels` would fix that only by
|
|
1529
|
+
// stripping reasoning everywhere, costing the model its whole picker. This keeps
|
|
1530
|
+
// the ladder advertised and drops the wire field for tool-bearing requests only.
|
|
1531
|
+
const omitReasoningEffortWithTools = !!tools
|
|
1532
|
+
&& modelInList(provider.omitReasoningEffortWithToolsModels, parsed.modelId);
|
|
1533
|
+
const reasoningEffort = omitReasoningEffortWithTools
|
|
1534
|
+
? undefined
|
|
1535
|
+
: mapReasoningEffort(provider, parsed.modelId, parsed.options.reasoning);
|
|
1536
|
+
const nativeOpenAI = isNativeOpenAIChatTarget(provider);
|
|
1537
|
+
let reasoningLog: AdapterRequest["reasoningLog"];
|
|
1538
|
+
if (!reasoningDisabled && !omitReasoningEffortWithTools && provider.reasoningWireFormat === "gateway-object" && parsed.options.reasoning === "none") {
|
|
1541
1539
|
if (nativeOpenAI) {
|
|
1542
|
-
body.reasoning_effort =
|
|
1540
|
+
body.reasoning_effort = "none";
|
|
1543
1541
|
reasoningLog = {
|
|
1544
|
-
effectiveEffort:
|
|
1542
|
+
effectiveEffort: "none",
|
|
1545
1543
|
wireField: "reasoning_effort",
|
|
1546
|
-
wireValue:
|
|
1544
|
+
wireValue: "none",
|
|
1547
1545
|
};
|
|
1548
1546
|
} else {
|
|
1549
|
-
body.reasoning = { enabled:
|
|
1550
|
-
reasoningLog = {
|
|
1551
|
-
effectiveEffort: reasoningEffort,
|
|
1552
|
-
wireField: "reasoning.effort",
|
|
1553
|
-
wireValue: reasoningEffort,
|
|
1554
|
-
};
|
|
1555
|
-
}
|
|
1556
|
-
} else if (modelInList(provider.thinkingBudgetModels, parsed.modelId)) {
|
|
1557
|
-
const budget = thinkingBudgetForEffort(parsed, reasoningEffort, maxTokens);
|
|
1558
|
-
if (budget !== undefined) {
|
|
1559
|
-
body.thinking_budget = budget;
|
|
1547
|
+
body.reasoning = { enabled: false };
|
|
1560
1548
|
reasoningLog = {
|
|
1561
|
-
effectiveEffort:
|
|
1562
|
-
wireField: "
|
|
1563
|
-
wireValue:
|
|
1549
|
+
effectiveEffort: "none",
|
|
1550
|
+
wireField: "reasoning.enabled",
|
|
1551
|
+
wireValue: false,
|
|
1564
1552
|
};
|
|
1565
1553
|
}
|
|
1566
|
-
} else if (
|
|
1567
|
-
if (
|
|
1568
|
-
|
|
1554
|
+
} else if (reasoningEffort !== undefined) {
|
|
1555
|
+
if (provider.reasoningWireFormat === "gateway-object") {
|
|
1556
|
+
if (nativeOpenAI) {
|
|
1557
|
+
body.reasoning_effort = reasoningEffort;
|
|
1558
|
+
reasoningLog = {
|
|
1559
|
+
effectiveEffort: reasoningEffort,
|
|
1560
|
+
wireField: "reasoning_effort",
|
|
1561
|
+
wireValue: reasoningEffort,
|
|
1562
|
+
};
|
|
1563
|
+
} else {
|
|
1564
|
+
body.reasoning = { enabled: true, effort: reasoningEffort };
|
|
1565
|
+
reasoningLog = {
|
|
1566
|
+
effectiveEffort: reasoningEffort,
|
|
1567
|
+
wireField: "reasoning.effort",
|
|
1568
|
+
wireValue: reasoningEffort,
|
|
1569
|
+
};
|
|
1570
|
+
}
|
|
1571
|
+
} else if (modelInList(provider.thinkingBudgetModels, parsed.modelId)) {
|
|
1572
|
+
const budget = thinkingBudgetForEffort(parsed, reasoningEffort, maxTokens);
|
|
1573
|
+
if (budget !== undefined) {
|
|
1574
|
+
body.thinking_budget = budget;
|
|
1575
|
+
reasoningLog = {
|
|
1576
|
+
effectiveEffort: parsed.options.reasoning === "minimal" ? "minimal" : reasoningEffort,
|
|
1577
|
+
wireField: "thinking_budget",
|
|
1578
|
+
wireValue: budget,
|
|
1579
|
+
};
|
|
1580
|
+
}
|
|
1581
|
+
} else if (modelInList(provider.thinkingToggleModels, parsed.modelId)) {
|
|
1582
|
+
if (reasoningEffort === "enabled" || reasoningEffort === "disabled" || reasoningEffort === "adaptive") {
|
|
1583
|
+
body.thinking = { type: reasoningEffort };
|
|
1584
|
+
reasoningLog = {
|
|
1585
|
+
effectiveEffort: reasoningEffort,
|
|
1586
|
+
wireField: "thinking.type",
|
|
1587
|
+
wireValue: reasoningEffort,
|
|
1588
|
+
};
|
|
1589
|
+
}
|
|
1590
|
+
} else {
|
|
1591
|
+
body.reasoning_effort = reasoningEffort;
|
|
1569
1592
|
reasoningLog = {
|
|
1570
1593
|
effectiveEffort: reasoningEffort,
|
|
1571
|
-
wireField: "
|
|
1594
|
+
wireField: "reasoning_effort",
|
|
1572
1595
|
wireValue: reasoningEffort,
|
|
1573
1596
|
};
|
|
1574
1597
|
}
|
|
1575
|
-
} else {
|
|
1576
|
-
body.reasoning_effort = reasoningEffort;
|
|
1577
|
-
reasoningLog = {
|
|
1578
|
-
effectiveEffort: reasoningEffort,
|
|
1579
|
-
wireField: "reasoning_effort",
|
|
1580
|
-
wireValue: reasoningEffort,
|
|
1581
|
-
};
|
|
1582
1598
|
}
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
}
|
|
1614
|
-
|
|
1599
|
+
if (parsed.options.presencePenalty !== undefined && !modelInList(provider.noPenaltyModels, parsed.modelId)) {
|
|
1600
|
+
body.presence_penalty = parsed.options.presencePenalty;
|
|
1601
|
+
}
|
|
1602
|
+
if (parsed.options.frequencyPenalty !== undefined && !modelInList(provider.noPenaltyModels, parsed.modelId)) {
|
|
1603
|
+
body.frequency_penalty = parsed.options.frequencyPenalty;
|
|
1604
|
+
}
|
|
1605
|
+
if (provider.promptCacheKey && parsed.options.promptCacheKey !== undefined) {
|
|
1606
|
+
body.prompt_cache_key = parsed.options.promptCacheKey;
|
|
1607
|
+
}
|
|
1608
|
+
// Structured-output support varies by the physical upstream model even when one
|
|
1609
|
+
// gateway exposes a uniform OpenAI-compatible endpoint. Keep the #1137 translation
|
|
1610
|
+
// as the default, but let an exact model opt out instead of forcing a provider-wide
|
|
1611
|
+
// rollback that would silently return prose for siblings that support JSON Schema.
|
|
1612
|
+
if (!provider.noStructuredOutputModels?.includes(parsed.modelId)) {
|
|
1613
|
+
const textFormat = parsed.options.textFormat;
|
|
1614
|
+
if (textFormat?.type === "json_object") {
|
|
1615
|
+
body.response_format = { type: "json_object" };
|
|
1616
|
+
} else if (textFormat?.type === "json_schema") {
|
|
1617
|
+
// Same downgrade as the passthrough path: the schema is dropped because the
|
|
1618
|
+
// upstream rejects it, but the JSON-mode request itself survives.
|
|
1619
|
+
body.response_format = provider.noJsonSchemaModels?.includes(parsed.modelId)
|
|
1620
|
+
? { type: "json_object" }
|
|
1621
|
+
: {
|
|
1622
|
+
type: "json_schema",
|
|
1623
|
+
json_schema: {
|
|
1624
|
+
name: textFormat.name ?? "response",
|
|
1625
|
+
...(textFormat.description !== undefined ? { description: textFormat.description } : {}),
|
|
1626
|
+
...(textFormat.schema !== undefined ? { schema: textFormat.schema } : {}),
|
|
1627
|
+
...(textFormat.strict !== undefined ? { strict: textFormat.strict } : {}),
|
|
1628
|
+
},
|
|
1629
|
+
};
|
|
1630
|
+
}
|
|
1615
1631
|
}
|
|
1616
|
-
}
|
|
1617
1632
|
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1633
|
+
if (tools) {
|
|
1634
|
+
if (provider.parallelToolCalls === false) {
|
|
1635
|
+
// NIM documents the Boolean defaulting to false and kimi rejects true; pin the
|
|
1636
|
+
// wire bit so Codex cannot opt in via request.options. Other opted-out providers
|
|
1637
|
+
// omit the field by default so strict OpenAI-compatible hosts never see an
|
|
1638
|
+
// unsupported knob, but a self-hosted gateway that DOES honor the field and keeps
|
|
1639
|
+
// emitting parallel calls without it can opt in via pinParallelToolCallsFalse.
|
|
1640
|
+
if (provider.baseUrl === "https://integrate.api.nvidia.com/v1"
|
|
1641
|
+
|| provider.pinParallelToolCallsFalse === true) {
|
|
1642
|
+
body.parallel_tool_calls = false;
|
|
1643
|
+
}
|
|
1644
|
+
} else if (provider.parallelToolCalls === true) {
|
|
1645
|
+
body.parallel_tool_calls = parsed.options.parallelToolCalls !== false;
|
|
1628
1646
|
}
|
|
1629
|
-
} else if (provider.parallelToolCalls === true) {
|
|
1630
|
-
body.parallel_tool_calls = parsed.options.parallelToolCalls !== false;
|
|
1631
1647
|
}
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
}
|
|
1655
|
-
}
|
|
1648
|
+
if (parsed.stream) body.stream_options = { include_usage: true };
|
|
1649
|
+
|
|
1650
|
+
const bodyJson = JSON.stringify(body);
|
|
1651
|
+
const actualServiceTier = typeof body.service_tier === "string" ? body.service_tier : null;
|
|
1652
|
+
const tierLog = createAdapterTierMetadata(
|
|
1653
|
+
parsed.options.tierObservation,
|
|
1654
|
+
parsed.options.tierDecision,
|
|
1655
|
+
actualServiceTier === null ? null : "service-tier",
|
|
1656
|
+
actualServiceTier,
|
|
1657
|
+
);
|
|
1658
|
+
if (isDebugEnabled()) {
|
|
1659
|
+
let host = "upstream";
|
|
1660
|
+
try { host = new URL(url).host; } catch { /* keep fallback */ }
|
|
1661
|
+
debugProviderDiagnostic("openai-chat", "request", {
|
|
1662
|
+
host,
|
|
1663
|
+
model: body.model,
|
|
1664
|
+
stream: parsed.stream,
|
|
1665
|
+
messageCount: Array.isArray(messages) ? messages.length : 0,
|
|
1666
|
+
toolCount: Array.isArray(tools) ? tools.length : 0,
|
|
1667
|
+
hasCredential,
|
|
1668
|
+
bodyBytes: Buffer.byteLength(bodyJson, "utf8"),
|
|
1669
|
+
});
|
|
1670
|
+
}
|
|
1656
1671
|
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1672
|
+
return {
|
|
1673
|
+
url,
|
|
1674
|
+
method: "POST",
|
|
1675
|
+
headers,
|
|
1676
|
+
body: bodyJson,
|
|
1677
|
+
...(reasoningLog ? { reasoningLog } : {}),
|
|
1678
|
+
...(tierLog ? { tierLog } : {}),
|
|
1679
|
+
};
|
|
1664
1680
|
};
|
|
1681
|
+
if (hasShrinkableOpenAIChatImages(messages)) {
|
|
1682
|
+
return normalizeOpenAIChatImages(messages, { tierBias: incoming?.imageTierBias }).then(finish, finish);
|
|
1683
|
+
}
|
|
1684
|
+
return finish();
|
|
1665
1685
|
},
|
|
1666
1686
|
|
|
1667
1687
|
async *parseStream(
|
|
@@ -2080,7 +2100,7 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
2080
2100
|
if (Object.hasOwn(json, "service_tier")) {
|
|
2081
2101
|
tierMetadata?.observeResponseServiceTier(json.service_tier);
|
|
2082
2102
|
}
|
|
2083
|
-
const responseBytes =
|
|
2103
|
+
const responseBytes = Buffer.byteLength(JSON.stringify(json), "utf8");
|
|
2084
2104
|
budget.chargeRetained(responseBytes, { kind: "retained_collectors" });
|
|
2085
2105
|
try {
|
|
2086
2106
|
const payload = unwrapChatCompletionPayload(json);
|