@bitkyc08/opencodex 2.87.0 → 2.88.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/gui/dist/assets/{App-ZfYbLHkB.js → App-CHC89EXQ.js} +3 -3
- package/gui/dist/assets/{Tray-DCd-A1az.js → Tray-B5bC7AWL.js} +1 -1
- package/gui/dist/assets/index-B5bsvxor.js +94 -0
- package/gui/dist/assets/{quota-summary-ChvmKGOk.js → quota-summary-D8y2wTsB.js} +1 -1
- package/gui/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/adapters/anthropic-rejection-memo.ts +71 -0
- package/src/adapters/anthropic-replay.ts +135 -0
- package/src/adapters/anthropic-tool-catalog.ts +11 -0
- package/src/adapters/anthropic.ts +76 -45
- package/src/adapters/google-progress-history.ts +53 -0
- package/src/adapters/google.ts +17 -1
- package/src/bridge/internal.ts +6 -2
- package/src/bridge/response-json.ts +2 -2
- package/src/bridge/sse.ts +7 -6
- package/src/claude/inbound-content-options.ts +66 -0
- package/src/claude/outbound.ts +13 -5
- package/src/cli/cross-home-owner.ts +16 -2
- package/src/cli/loopback-family.ts +31 -0
- package/src/codex/catalog/routed-gather.ts +3 -1
- package/src/codex/websocket-activity.ts +161 -0
- package/src/codex/websocket-registry.ts +25 -1
- package/src/config/opencode-zen-upgrade.ts +9 -8
- package/src/generated/compatibility-version.json +88 -52
- package/src/images/loop.ts +72 -21
- package/src/lib/request-failure-model.ts +2 -0
- package/src/oauth/generic-account-failover.ts +7 -4
- package/src/protocols/encoders/adapter-events.ts +5 -5
- package/src/protocols/encoders/messages.ts +2 -2
- package/src/providers/opencode-zen-rate-limit.ts +7 -2
- package/src/responses/reasoning-replay-cache.ts +50 -4
- package/src/routing/analytics.ts +7 -15
- package/src/server/claude-messages.ts +2 -1
- package/src/server/index/live-sideband.ts +3 -2
- package/src/server/index/serve-options.ts +11 -5
- package/src/server/index/websocket-handler.ts +18 -4
- package/src/server/index.ts +7 -7
- package/src/server/lifecycle.ts +13 -1
- package/src/server/messages-native.ts +16 -3
- package/src/server/responses/adapter-continuation.ts +34 -18
- package/src/server/responses/adapter-delivery.ts +53 -6
- package/src/server/responses/adapter-dispatch.ts +27 -1
- package/src/server/responses/anthropic-thinking-recovery.ts +153 -0
- package/src/server/responses/combo-stream-preflight.ts +3 -2
- package/src/server/responses/compaction-recovery.ts +2 -0
- package/src/server/responses/core-codex-account.ts +2 -2
- package/src/server/responses/core-combo.ts +3 -2
- package/src/server/responses/core-lifetime.ts +3 -2
- package/src/server/responses/core-options.ts +2 -0
- package/src/server/responses/core-replay.ts +5 -1
- package/src/server/responses/passthrough-execution.ts +3 -2
- package/src/server/responses/request-prepare.ts +1 -0
- package/src/server/responses/response-effects.ts +1 -0
- package/src/server/responses/response-tool-ownership.ts +21 -0
- package/src/server/responses/sidecar-execution.ts +43 -7
- package/src/server/responses/sidecar-send-budget.ts +2 -2
- package/src/server/responses-undeclared-tool-guard.ts +1 -1
- package/src/server/ws-bridge.ts +2 -0
- package/src/types/opencode-zen.ts +9 -3
- package/src/types/request.ts +10 -2
- package/src/types/wire.ts +4 -2
- package/src/types.ts +1 -0
- package/src/usage/summary.ts +9 -3
- package/src/usage/telemetry-contract.ts +1 -0
- package/src/web-search/anthropic-history.ts +59 -0
- package/src/web-search/loop.ts +181 -68
- package/src/web-search/progress-stream.ts +11 -1
- package/gui/dist/assets/index-CfYDMj34.js +0 -94
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** A failed connect cannot distinguish an absent loopback address from a slow owner. */
|
|
2
|
+
export type LoopbackFamilyAvailability = "available" | "unavailable" | "unknown";
|
|
3
|
+
|
|
4
|
+
function bindLoopback(hostname: string): { stop(): void } {
|
|
5
|
+
return Bun.listen({ hostname, port: 0, socket: { data(socket) { socket.end(); } } });
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Ask the kernel whether the concrete local address can be bound, independently of
|
|
10
|
+
* routing/connect timeouts. Only address/family absence permits skipping discovery;
|
|
11
|
+
* permissions, resource exhaustion and other failures leave owner evidence unknown.
|
|
12
|
+
* The ephemeral listener is closed synchronously and never accepts client traffic.
|
|
13
|
+
*/
|
|
14
|
+
export function loopbackFamilyAvailability(
|
|
15
|
+
hostname: string,
|
|
16
|
+
bindFn: (hostname: string) => { stop(): void } = bindLoopback,
|
|
17
|
+
): LoopbackFamilyAvailability {
|
|
18
|
+
const address = hostname === "[::1]" ? "::1" : hostname;
|
|
19
|
+
if (address !== "::1" && address !== "127.0.0.1") return "unknown";
|
|
20
|
+
let listener: { stop(): void };
|
|
21
|
+
try {
|
|
22
|
+
listener = bindFn(address);
|
|
23
|
+
} catch (error) {
|
|
24
|
+
const code = (error as { code?: unknown } | null)?.code;
|
|
25
|
+
return code === "EADDRNOTAVAIL" || code === "EAFNOSUPPORT"
|
|
26
|
+
|| code === "EPROTONOSUPPORT"
|
|
27
|
+
? "unavailable" : "unknown";
|
|
28
|
+
}
|
|
29
|
+
listener.stop();
|
|
30
|
+
return "available";
|
|
31
|
+
}
|
|
@@ -102,7 +102,7 @@ import type {
|
|
|
102
102
|
CatalogTrustedOpenAiApiPolicySnapshot,
|
|
103
103
|
} from "../convergence-types";
|
|
104
104
|
import type { CatalogGatherProviderAuthOutcome, CatalogGatherProviderModelOutcome, GatherFlightCapture, ModelsAuthResolverFactory } from "./gather-capture";
|
|
105
|
-
import { applyProviderConfigHints, configuredAutoCompactTokenLimit, configuredMaxInputTokens, configuredReasoningSummarySupport, modelInputModalities, routedMaxOutputTokens, withVisionSidecarInput } from "./model-hints";
|
|
105
|
+
import { applyProviderConfigHints, configuredAutoCompactTokenLimit, configuredMaxInputTokens, configuredModelDisplayName, configuredReasoningSummarySupport, modelInputModalities, routedMaxOutputTokens, withVisionSidecarInput } from "./model-hints";
|
|
106
106
|
import { resolveComboCatalogMember } from "./combo-member";
|
|
107
107
|
import { captureGatherFlight, captureTrustedOpenAiApiPolicy, gatherFlightKey, keyedGatherBytesIdentity, withCanonicalOpenAiForwardAuthDefault } from "./gather-capture";
|
|
108
108
|
import { fetchProviderModelsWithAuth, observedModelsAuthResolver, refreshingModelsAuthResolver } from "./provider-models";
|
|
@@ -886,10 +886,12 @@ function augmentRoutedModelsWithCapturedOpenAiApiRows(
|
|
|
886
886
|
undefined,
|
|
887
887
|
metadataConfigDir,
|
|
888
888
|
);
|
|
889
|
+
const displayName = configuredModelDisplayName(configured, id);
|
|
889
890
|
return {
|
|
890
891
|
provider: OPENAI_API_PROVIDER_ID,
|
|
891
892
|
id,
|
|
892
893
|
owned_by: OPENAI_API_PROVIDER_ID,
|
|
894
|
+
...(displayName ? { displayName } : {}),
|
|
893
895
|
...(contextWindow ? { contextWindow } : {}),
|
|
894
896
|
...(maxInputTokens ? { maxInputTokens } : {}),
|
|
895
897
|
...(maxOutputTokens !== undefined ? { maxOutputTokens } : {}),
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { isAuthorizedProviderExecutedCall, type ProviderExecutedCallType } from "../server/responses-undeclared-tool-guard";
|
|
3
|
+
|
|
4
|
+
// Logical recency avoids clock adjustments and ties between messages in one millisecond.
|
|
5
|
+
let activityOrder = 0;
|
|
6
|
+
const MAX_PENDING_RESULTS = 1024;
|
|
7
|
+
const MAX_RESULT_ID_LENGTH = 1024;
|
|
8
|
+
const resultTypes: Readonly<Record<string, string>> = {
|
|
9
|
+
function_call: "function_call_output",
|
|
10
|
+
custom_tool_call: "custom_tool_call_output",
|
|
11
|
+
local_shell_call: "function_call_output",
|
|
12
|
+
shell_call: "shell_call_output",
|
|
13
|
+
computer_call: "computer_call_output",
|
|
14
|
+
apply_patch_call: "apply_patch_call_output",
|
|
15
|
+
tool_search_call: "tool_search_output",
|
|
16
|
+
mcp_approval_request: "mcp_approval_response",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function record(value: unknown): value is Record<string, unknown> {
|
|
20
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function resultKey(type: string, id: string): string {
|
|
24
|
+
return createHash("sha256").update(JSON.stringify([type, id])).digest("hex");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Content-free activity owned by one admitted incoming Responses socket. */
|
|
28
|
+
export class CodexWebSocketActivity {
|
|
29
|
+
lastUsed = ++activityOrder;
|
|
30
|
+
closing = false;
|
|
31
|
+
private turns = 0;
|
|
32
|
+
private pendingResults = new Set<string>();
|
|
33
|
+
private returnedResults = new Set<string>();
|
|
34
|
+
// Hashed identities only; null marks a route-authorized provider-owned call.
|
|
35
|
+
private argumentCalls = new Map<string, { type: string; result: string | null }>();
|
|
36
|
+
// Unknown/oversized identities cannot safely prove that client work finished.
|
|
37
|
+
private untrackedResults = false;
|
|
38
|
+
|
|
39
|
+
get idle(): boolean {
|
|
40
|
+
return !this.closing && this.turns === 0 && this.pendingResults.size === 0 && !this.untrackedResults;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
touch(): void {
|
|
44
|
+
this.lastUsed = ++activityOrder;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Each turn retains its own busy reference, including superseded async cleanup. */
|
|
48
|
+
beginTurn(input: unknown): () => void {
|
|
49
|
+
this.turns += 1;
|
|
50
|
+
this.touch();
|
|
51
|
+
this.acceptResults(input);
|
|
52
|
+
// Ordinary creates start a new response even when its upstream omits response.created.
|
|
53
|
+
// Prior results settle old waits, but cannot suppress new calls reusing their IDs.
|
|
54
|
+
this.returnedResults.clear();
|
|
55
|
+
this.argumentCalls.clear();
|
|
56
|
+
let finished = false;
|
|
57
|
+
return () => {
|
|
58
|
+
if (finished) return;
|
|
59
|
+
finished = true;
|
|
60
|
+
this.turns -= 1;
|
|
61
|
+
if (this.turns === 0) this.touch();
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Called only after admission or native control accepts the client's results. */
|
|
66
|
+
acceptResults(input: unknown): void {
|
|
67
|
+
if (this.untrackedResults) return;
|
|
68
|
+
if (Array.isArray(input)) {
|
|
69
|
+
for (const item of input) {
|
|
70
|
+
if (!record(item) || typeof item.type !== "string") continue;
|
|
71
|
+
const id = item.type === "mcp_approval_response" ? item.approval_request_id : item.call_id;
|
|
72
|
+
if (typeof id === "string" && id.length <= MAX_RESULT_ID_LENGTH) {
|
|
73
|
+
// Ordinary Codex shell results use function_call_output; native steering
|
|
74
|
+
// also accepts its explicit local_shell_call_output continuation stub.
|
|
75
|
+
const type = item.type === "local_shell_call_output" ? "function_call_output" : item.type;
|
|
76
|
+
const key = resultKey(type, id);
|
|
77
|
+
if (this.pendingResults.delete(key)) {
|
|
78
|
+
if (this.returnedResults.size >= MAX_PENDING_RESULTS) { this.protectUntracked(); return; }
|
|
79
|
+
else this.returnedResults.add(key);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Preserve tool waits even when the response terminal omits its output array. */
|
|
87
|
+
observePayload(payload: string, providerExecuted?: ReadonlySet<ProviderExecutedCallType>): void {
|
|
88
|
+
if (this.untrackedResults) return;
|
|
89
|
+
let frame: unknown;
|
|
90
|
+
try { frame = JSON.parse(payload); } catch { return; }
|
|
91
|
+
if (!record(frame)) return;
|
|
92
|
+
// Native injection can return a result before the same terminal repeats its call.
|
|
93
|
+
if (frame.type === "response.created") {
|
|
94
|
+
this.returnedResults.clear();
|
|
95
|
+
this.argumentCalls.clear();
|
|
96
|
+
}
|
|
97
|
+
if (frame.type === "response.output_item.added") this.rememberArgumentCall(frame.item, providerExecuted);
|
|
98
|
+
if (frame.type === "response.function_call_arguments.done" || frame.type === "response.custom_tool_call_input.done") {
|
|
99
|
+
const type = frame.type === "response.function_call_arguments.done" ? "function_call" : "custom_tool_call";
|
|
100
|
+
const call = typeof frame.item_id === "string" && frame.item_id.length <= MAX_RESULT_ID_LENGTH
|
|
101
|
+
? this.argumentCalls.get(resultKey("item", frame.item_id)) : undefined;
|
|
102
|
+
if (!call || call.type !== type) this.protectUntracked();
|
|
103
|
+
else if (call.result !== null) this.observeResultKey(call.result);
|
|
104
|
+
}
|
|
105
|
+
if (frame.type === "response.output_item.done") this.observeOutput(frame.item, providerExecuted);
|
|
106
|
+
if (["response.completed", "response.incomplete", "response.failed"].includes(String(frame.type))
|
|
107
|
+
&& record(frame.response) && Array.isArray(frame.response.output)) {
|
|
108
|
+
for (const item of frame.response.output) this.observeOutput(item, providerExecuted);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private rememberArgumentCall(item: unknown, providerExecuted?: ReadonlySet<ProviderExecutedCallType>): void {
|
|
113
|
+
if (!record(item) || (item.type !== "function_call" && item.type !== "custom_tool_call")) return;
|
|
114
|
+
if (typeof item.id !== "string" || !item.id.length || item.id.length > MAX_RESULT_ID_LENGTH) return;
|
|
115
|
+
const key = resultKey("item", item.id);
|
|
116
|
+
const hosted = providerExecuted && isAuthorizedProviderExecutedCall(item, providerExecuted);
|
|
117
|
+
if (!hosted && (typeof item.call_id !== "string" || !item.call_id.length || item.call_id.length > MAX_RESULT_ID_LENGTH)) {
|
|
118
|
+
if (this.argumentCalls.has(key)) this.protectUntracked();
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const result = hosted ? null : resultKey(resultTypes[item.type]!, item.call_id as string);
|
|
122
|
+
const previous = this.argumentCalls.get(key);
|
|
123
|
+
if (previous) {
|
|
124
|
+
if (previous.type !== item.type || previous.result !== result) this.protectUntracked();
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (this.argumentCalls.size >= MAX_PENDING_RESULTS) { this.protectUntracked(); return; }
|
|
128
|
+
// An unfinished argument stream alone does not prove the client started work.
|
|
129
|
+
this.argumentCalls.set(key, { type: item.type, result });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private observeOutput(item: unknown, providerExecuted?: ReadonlySet<ProviderExecutedCallType>): void {
|
|
133
|
+
if (this.untrackedResults) return;
|
|
134
|
+
if (!record(item) || typeof item.type !== "string") return;
|
|
135
|
+
// Hosted execution may publish both the call and its result in the output stream.
|
|
136
|
+
this.acceptResults([item]);
|
|
137
|
+
if (!Object.hasOwn(resultTypes, item.type)) return;
|
|
138
|
+
if (providerExecuted && isAuthorizedProviderExecutedCall(item, providerExecuted)) return;
|
|
139
|
+
if (item.type === "tool_search_call" && item.execution !== "client") return;
|
|
140
|
+
const id = item.type === "mcp_approval_request" ? item.id
|
|
141
|
+
: item.type === "local_shell_call" || item.type === "tool_search_call" ? item.call_id ?? item.id : item.call_id;
|
|
142
|
+
if (typeof id !== "string" || !id.length || id.length > MAX_RESULT_ID_LENGTH) {
|
|
143
|
+
this.protectUntracked();
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
this.observeResultKey(resultKey(resultTypes[item.type]!, id));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private observeResultKey(key: string): void {
|
|
150
|
+
if (this.pendingResults.has(key) || this.returnedResults.has(key)) return;
|
|
151
|
+
if (this.pendingResults.size >= MAX_PENDING_RESULTS) this.protectUntracked();
|
|
152
|
+
else this.pendingResults.add(key);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private protectUntracked(): void {
|
|
156
|
+
this.untrackedResults = true;
|
|
157
|
+
this.pendingResults.clear();
|
|
158
|
+
this.returnedResults.clear();
|
|
159
|
+
this.argumentCalls.clear();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -1,23 +1,47 @@
|
|
|
1
1
|
import type { ServerWebSocket } from "bun";
|
|
2
2
|
import type { WsData } from "../server/ws-bridge";
|
|
3
3
|
import { createAdmissionGate, type AdmissionMetrics, type AdmissionReservation } from "../lib/admission";
|
|
4
|
+
import { CodexWebSocketActivity } from "./websocket-activity";
|
|
4
5
|
|
|
5
6
|
export const MAX_TRACKED_CODEX_WEBSOCKETS = 128;
|
|
6
7
|
const websocketGate = createAdmissionGate("codex_websockets", MAX_TRACKED_CODEX_WEBSOCKETS);
|
|
8
|
+
const admittedSockets = new Set<ServerWebSocket<WsData>>();
|
|
9
|
+
|
|
10
|
+
function reclaimOldestIdleSocket(): void {
|
|
11
|
+
let oldest: ServerWebSocket<WsData> | undefined;
|
|
12
|
+
for (const ws of admittedSockets) {
|
|
13
|
+
const activity = ws.data.responsesActivity;
|
|
14
|
+
if (!activity?.idle || ws.data.nativeControl || ws.readyState !== 1 || ws.getBufferedAmount() !== 0) continue;
|
|
15
|
+
if (!oldest || activity.lastUsed < oldest.data.responsesActivity!.lastUsed) oldest = ws;
|
|
16
|
+
}
|
|
17
|
+
if (!oldest) return;
|
|
18
|
+
// Fence late frames before closing. Only the actual close callback releases admission.
|
|
19
|
+
oldest.data.responsesActivity!.closing = true;
|
|
20
|
+
try {
|
|
21
|
+
oldest.close(1001, "Idle Responses connection reclaimed");
|
|
22
|
+
} catch {
|
|
23
|
+
// A failed/delayed close never manufactures a free slot or retries another victim.
|
|
24
|
+
if (oldest.readyState === 1) oldest.data.responsesActivity!.closing = false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
7
27
|
|
|
8
28
|
export function tryReserveCodexWebSocket(): AdmissionReservation<ServerWebSocket<WsData>> | null {
|
|
29
|
+
if (websocketGate.metrics().active >= MAX_TRACKED_CODEX_WEBSOCKETS) reclaimOldestIdleSocket();
|
|
9
30
|
const gateLease = websocketGate.tryAcquire();
|
|
10
31
|
if (!gateLease) return null;
|
|
11
32
|
let socket: ServerWebSocket<WsData> | undefined;
|
|
12
33
|
let active = true;
|
|
13
34
|
return {
|
|
14
35
|
bind(value) {
|
|
15
|
-
if (!active) return;
|
|
36
|
+
if (!active || socket) return;
|
|
16
37
|
socket = value;
|
|
38
|
+
socket.data.responsesActivity = new CodexWebSocketActivity();
|
|
39
|
+
admittedSockets.add(socket);
|
|
17
40
|
},
|
|
18
41
|
release() {
|
|
19
42
|
if (!active) return;
|
|
20
43
|
active = false;
|
|
44
|
+
if (socket) admittedSockets.delete(socket);
|
|
21
45
|
socket = undefined;
|
|
22
46
|
gateLease.release();
|
|
23
47
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { openCodeZenBaseUrl } from "../types/opencode-zen";
|
|
1
|
+
import { openCodeZenBaseUrl, openCodeZenKeyBaseUrl, type OpenCodeZenDestination } from "../types/opencode-zen";
|
|
2
2
|
import { declaredUnsupportedHostedTools, isHostedToolUnsupportedForModel } from "../responses/hosted-tool-policy";
|
|
3
3
|
|
|
4
4
|
function record(value: unknown): Record<string, unknown> | undefined {
|
|
@@ -6,8 +6,9 @@ function record(value: unknown): Record<string, unknown> | undefined {
|
|
|
6
6
|
? value as Record<string, unknown> : undefined;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
function
|
|
10
|
-
|
|
9
|
+
function isNativeZenModel(model: string, provider: OpenCodeZenDestination): boolean {
|
|
10
|
+
const folded = model.trim().toLowerCase();
|
|
11
|
+
return folded.startsWith("claude-") || (folded.startsWith("gemini-") && openCodeZenBaseUrl(provider) !== undefined);
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
/** Repair legacy Zen wire overrides and dependent preferences in file-backed reads only. */
|
|
@@ -15,7 +16,7 @@ export function sanitizeOpenCodeZenOverridesForLoad(parsed: unknown): void {
|
|
|
15
16
|
const providers = record(record(parsed)?.providers);
|
|
16
17
|
const provider = providers && Object.hasOwn(providers, "opencode-zen")
|
|
17
18
|
? record(providers["opencode-zen"]) : undefined;
|
|
18
|
-
if (!provider || !
|
|
19
|
+
if (!provider || !openCodeZenKeyBaseUrl(provider)) return;
|
|
19
20
|
const overrides = record(provider.modelAdapters) ?? {};
|
|
20
21
|
const preferences = record(provider.modelPreferHostedTools);
|
|
21
22
|
let preferencesChanged = false;
|
|
@@ -25,7 +26,7 @@ export function sanitizeOpenCodeZenOverridesForLoad(parsed: unknown): void {
|
|
|
25
26
|
// Capture the old wire before deleting overrides. An explicit non-Responses entry
|
|
26
27
|
// must not inherit a provider-wide Responses default for this migration.
|
|
27
28
|
const oldAdapter = Object.hasOwn(overrides, model) ? overrides[model] : provider.adapter;
|
|
28
|
-
if (!
|
|
29
|
+
if (!isNativeZenModel(model, provider) || oldAdapter !== "openai-responses"
|
|
29
30
|
|| !Array.isArray(tools) || tools.length === 0 || tools.some(tool => tool !== "image_generation")
|
|
30
31
|
|| isHostedToolUnsupportedForModel(model, "image_generation", undefined, unsupported)) continue;
|
|
31
32
|
delete preferences[model];
|
|
@@ -37,16 +38,16 @@ export function sanitizeOpenCodeZenOverridesForLoad(parsed: unknown): void {
|
|
|
37
38
|
for (const [model, adapter] of Object.entries(overrides)) {
|
|
38
39
|
// Retain invalid preference/wire pairs so repeated normalization cannot admit them.
|
|
39
40
|
if (preferences && Object.hasOwn(preferences, model)) continue;
|
|
40
|
-
if (
|
|
41
|
+
if (isNativeZenModel(model, provider) && (adapter === "openai-chat" || adapter === "openai-responses")) {
|
|
41
42
|
delete overrides[model];
|
|
42
43
|
overridesChanged = true;
|
|
43
44
|
}
|
|
44
45
|
}
|
|
45
46
|
if (overridesChanged) {
|
|
46
47
|
if (Object.keys(overrides).length === 0) delete provider.modelAdapters;
|
|
47
|
-
console.warn("OpenCode Zen Gemini models now use
|
|
48
|
+
console.warn("OpenCode Zen Gemini and Claude models now use their native APIs; obsolete Chat/Responses overrides were ignored while loading config.json.");
|
|
48
49
|
}
|
|
49
50
|
if (preferencesChanged) {
|
|
50
|
-
console.warn("OpenCode Zen Gemini models now use
|
|
51
|
+
console.warn("OpenCode Zen Gemini and Claude models now use their native APIs; legacy Responses-only hosted-tool preferences were ignored while loading config.json.");
|
|
51
52
|
}
|
|
52
53
|
}
|