@bitkyc08/opencodex 2.29.0 → 2.31.0-preview.20260822
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 +5 -5
- package/gui/dist/assets/index-DyWYnr-t.js +102 -0
- package/gui/dist/index.html +1 -1
- package/package.json +3 -3
- package/src/adapters/cursor/cursor-errors.ts +65 -6
- package/src/adapters/cursor/discovery.ts +29 -2
- package/src/adapters/cursor/effort-map.ts +6 -0
- package/src/adapters/cursor/h2-pool.ts +123 -0
- package/src/adapters/cursor/images.ts +704 -0
- package/src/adapters/cursor/live-models.ts +21 -26
- package/src/adapters/cursor/live-transport.ts +239 -8
- package/src/adapters/cursor/native-exec-common.ts +17 -0
- package/src/adapters/cursor/native-exec.ts +9 -4
- package/src/adapters/cursor/protobuf-events.ts +5 -1
- package/src/adapters/cursor/protobuf-request.ts +46 -9
- package/src/adapters/cursor/request-builder.ts +29 -14
- package/src/adapters/cursor/tool-definitions.ts +20 -0
- package/src/adapters/cursor/transport.ts +10 -0
- package/src/adapters/cursor/types.ts +8 -1
- package/src/adapters/cursor.ts +23 -5
- package/src/adapters/google.ts +16 -3
- package/src/adapters/openai-responses.ts +66 -20
- package/src/adapters/xai-web-search.ts +185 -0
- package/src/cli/agent.ts +2 -1
- package/src/cli/dispatch.ts +2 -2
- package/src/cli/doctor.ts +89 -0
- package/src/cli/help.ts +2 -0
- package/src/cli/registry.ts +7 -2
- package/src/codex/auth-context.ts +41 -2
- package/src/codex/catalog/effort.ts +1 -1
- package/src/codex/catalog/parsing.ts +2 -0
- package/src/codex/catalog/provider-fetch.ts +20 -5
- package/src/codex/coordinator-doctor.ts +332 -0
- package/src/codex/features.ts +58 -0
- package/src/codex/inject-coordination.ts +39 -6
- package/src/codex/transition-state.ts +12 -12
- package/src/generated/compatibility-version.json +86 -58
- package/src/lib/bun-stream-caps.ts +7 -4
- package/src/lib/errors.ts +8 -2
- package/src/oauth/cursor.ts +21 -0
- package/src/providers/command-code-efforts.ts +7 -0
- package/src/providers/cursor-pool.ts +72 -0
- package/src/providers/derive.ts +3 -0
- package/src/providers/fastwire.ts +12 -1
- package/src/providers/openai-sidecar.ts +1 -0
- package/src/providers/quota.ts +98 -25
- package/src/providers/registry.ts +115 -10
- package/src/providers/service-tier.ts +22 -7
- package/src/responses/custom-tool-compat.ts +24 -8
- package/src/responses/namespace-tool-compat.ts +2 -3
- package/src/router.ts +3 -0
- package/src/server/chat-completions.ts +4 -0
- package/src/server/chat-native.ts +20 -0
- package/src/server/management/agent-settings-routes.ts +16 -5
- package/src/server/management/config-routes.ts +25 -5
- package/src/server/management/vision-sidecar-options.ts +54 -19
- package/src/server/responses/compact.ts +1 -2
- package/src/server/responses/core.ts +54 -13
- package/src/service.ts +122 -14
- package/src/types/config.ts +9 -3
- package/src/types/provider.ts +6 -0
- package/src/usage/cost.ts +52 -38
- package/src/usage/expected-prices.ts +79 -9
- package/src/vision/backends.ts +97 -0
- package/src/vision/eligibility.ts +43 -22
- package/src/vision/index.ts +73 -5
- package/src/vision/routed-describe.ts +175 -0
- package/gui/dist/assets/index-BNESwCzn.js +0 -102
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Weighted credential routing for Cursor accounts.
|
|
3
|
+
*
|
|
4
|
+
* Transfer from yelixir-dev/cursor-ai-proxy-bridge credentials.ts:
|
|
5
|
+
* weighted round-robin selection with per-credential auth-failure cooldown
|
|
6
|
+
* and one-retry failover on a different account before surfacing the error.
|
|
7
|
+
*
|
|
8
|
+
* OpenCodex already has JWT-based multi-account identification (src/oauth/cursor.ts)
|
|
9
|
+
* and Anthropic-specific 429 rotation; this module adds Cursor-aware weighted
|
|
10
|
+
* routing on top of those primitives.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export interface CursorCredential {
|
|
14
|
+
readonly id: string;
|
|
15
|
+
weight: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface CredentialState {
|
|
19
|
+
readonly credential: CursorCredential;
|
|
20
|
+
currentWeight: number;
|
|
21
|
+
disabledUntil: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class NoAvailableCursorCredentialError extends Error {
|
|
25
|
+
constructor(message = "No available Cursor credentials") { super(message); }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class CursorCredentialRouter {
|
|
29
|
+
private states: CredentialState[] = [];
|
|
30
|
+
private readonly cooldownMs: number;
|
|
31
|
+
|
|
32
|
+
constructor(credentials: ReadonlyArray<CursorCredential>, cooldownMs = 300_000) {
|
|
33
|
+
this.cooldownMs = cooldownMs;
|
|
34
|
+
this.replace(credentials);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
replace(credentials: ReadonlyArray<CursorCredential>): void {
|
|
38
|
+
this.states = credentials.map(c => ({
|
|
39
|
+
credential: { ...c, weight: Math.max(1, c.weight || 1) },
|
|
40
|
+
currentWeight: 0,
|
|
41
|
+
disabledUntil: 0,
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
pick(excludeIds: ReadonlySet<string> = new Set()): CursorCredential {
|
|
46
|
+
const now = Date.now();
|
|
47
|
+
const candidates = this.states.filter(s =>
|
|
48
|
+
!excludeIds.has(s.credential.id) && s.disabledUntil <= now,
|
|
49
|
+
);
|
|
50
|
+
if (candidates.length === 0) throw new NoAvailableCursorCredentialError();
|
|
51
|
+
let selected: CredentialState | undefined;
|
|
52
|
+
let totalWeight = 0;
|
|
53
|
+
for (const state of candidates) {
|
|
54
|
+
state.currentWeight += state.credential.weight;
|
|
55
|
+
totalWeight += state.credential.weight;
|
|
56
|
+
if (!selected || state.currentWeight > selected.currentWeight) selected = state;
|
|
57
|
+
}
|
|
58
|
+
if (!selected) throw new NoAvailableCursorCredentialError();
|
|
59
|
+
selected.currentWeight -= totalWeight;
|
|
60
|
+
return { ...selected.credential };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
disable(id: string): void {
|
|
64
|
+
const state = this.states.find(s => s.credential.id === id);
|
|
65
|
+
if (state) state.disabledUntil = Date.now() + this.cooldownMs;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
get snapshot(): ReadonlyArray<{ id: string; disabled: boolean }> {
|
|
69
|
+
const now = Date.now();
|
|
70
|
+
return this.states.map(s => ({ id: s.credential.id, disabled: s.disabledUntil > now }));
|
|
71
|
+
}
|
|
72
|
+
}
|
package/src/providers/derive.ts
CHANGED
|
@@ -483,6 +483,9 @@ export function enrichProviderFromRegistry(name: string, prov: OcxProviderConfig
|
|
|
483
483
|
if (prov.supportsOpenAiWebSearchToolFields === undefined && entry.supportsOpenAiWebSearchToolFields !== undefined) {
|
|
484
484
|
prov.supportsOpenAiWebSearchToolFields = entry.supportsOpenAiWebSearchToolFields;
|
|
485
485
|
}
|
|
486
|
+
if (prov.supportsResponsesCustomTools === undefined && entry.supportsResponsesCustomTools !== undefined) {
|
|
487
|
+
prov.supportsResponsesCustomTools = entry.supportsResponsesCustomTools;
|
|
488
|
+
}
|
|
486
489
|
if (prov.preserveResponsesReasoningContent === undefined && entry.preserveResponsesReasoningContent !== undefined) prov.preserveResponsesReasoningContent = entry.preserveResponsesReasoningContent;
|
|
487
490
|
applyReasoningSummaryDefaults(prov, entry.modelSupportsReasoningSummaries);
|
|
488
491
|
applyServiceTierModelDefaults(prov, serviceTierModelDefaultsFor(entry, prov));
|
|
@@ -33,6 +33,7 @@ export interface FastPolicyAuthority {
|
|
|
33
33
|
readonly providerAdapter: string;
|
|
34
34
|
readonly providerAuthMode?: ProviderAuthKind;
|
|
35
35
|
readonly fastWireDeclaration: FastWire | null | undefined;
|
|
36
|
+
readonly fastTierDescription?: string;
|
|
36
37
|
readonly modelWireOverrideAllowed: boolean;
|
|
37
38
|
readonly authTransport: FastPolicyAuthTransport;
|
|
38
39
|
readonly capability: {
|
|
@@ -55,6 +56,7 @@ export interface ResolvedFastPolicy {
|
|
|
55
56
|
| "pin-unavailable";
|
|
56
57
|
readonly adapter: string;
|
|
57
58
|
readonly fastWire: FastWire | null;
|
|
59
|
+
readonly fastTierDescription?: string;
|
|
58
60
|
readonly forwardCallerTier: boolean;
|
|
59
61
|
}
|
|
60
62
|
|
|
@@ -222,7 +224,16 @@ export function resolveFastPolicy(
|
|
|
222
224
|
else if (capability === undefined) eligibility = "unclassified";
|
|
223
225
|
else eligibility = "eligible";
|
|
224
226
|
|
|
225
|
-
return {
|
|
227
|
+
return {
|
|
228
|
+
capability,
|
|
229
|
+
eligibility,
|
|
230
|
+
adapter,
|
|
231
|
+
fastWire,
|
|
232
|
+
...(authority.fastTierDescription !== undefined
|
|
233
|
+
? { fastTierDescription: authority.fastTierDescription }
|
|
234
|
+
: {}),
|
|
235
|
+
forwardCallerTier,
|
|
236
|
+
};
|
|
226
237
|
}
|
|
227
238
|
|
|
228
239
|
export function canonicalFastTierMarker(callerTier: string | undefined): "priority" | undefined {
|
package/src/providers/quota.ts
CHANGED
|
@@ -50,6 +50,7 @@ const OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
|
|
|
50
50
|
const DEEPSEEK_BASE_URL = "https://api.deepseek.com";
|
|
51
51
|
const CLINE_BASE_URL = "https://api.cline.bot";
|
|
52
52
|
const ZAI_BASE_URL = "https://api.z.ai";
|
|
53
|
+
const ZAI_CN_BASE_URL = "https://open.bigmodel.cn";
|
|
53
54
|
const MINIMAX_REMAINS_URL = "https://www.minimax.io/v1/token_plan/remains";
|
|
54
55
|
const MOONSHOT_BASE_URL = "https://api.moonshot.ai/v1";
|
|
55
56
|
const VENICE_BASE_URL = "https://api.venice.ai/api/v1";
|
|
@@ -343,7 +344,12 @@ function isCanonicalClineBaseUrl(baseUrl: string): boolean {
|
|
|
343
344
|
|
|
344
345
|
function isCanonicalZaiBaseUrl(baseUrl: string): boolean {
|
|
345
346
|
const normalized = normalizedBaseUrl(baseUrl);
|
|
346
|
-
return normalized === ZAI_BASE_URL
|
|
347
|
+
return normalized === ZAI_BASE_URL
|
|
348
|
+
|| normalized === `${ZAI_BASE_URL}/api/coding/paas/v4`
|
|
349
|
+
|| normalized === ZAI_CN_BASE_URL
|
|
350
|
+
|| normalized === `${ZAI_CN_BASE_URL}/api/coding/paas/v4`
|
|
351
|
+
// BigModel serves the same GLM Coding Plan on the OpenAI Responses wire at /api/v1.
|
|
352
|
+
|| normalized === `${ZAI_CN_BASE_URL}/api/v1`;
|
|
347
353
|
}
|
|
348
354
|
|
|
349
355
|
function isCanonicalMinimaxBaseUrl(baseUrl: string): boolean {
|
|
@@ -669,34 +675,67 @@ async function fetchClineQuota(provider: string, config: OcxProviderConfig): Pro
|
|
|
669
675
|
|
|
670
676
|
/**
|
|
671
677
|
* Z.AI GLM Coding Plan `GET /api/monitor/usage/quota/limit` — the coding-plan
|
|
672
|
-
*
|
|
673
|
-
*
|
|
678
|
+
* limits arrive as a `limits` array of `TOKENS_LIMIT` (newer plans call the
|
|
679
|
+
* same rows `CREDIT_LIMIT`) and `TIME_LIMIT` rows. `TOKENS_LIMIT`/`CREDIT_LIMIT`
|
|
680
|
+
* rows carry the window length as `unit`/`number`: unit 3 is hours (number 5 →
|
|
681
|
+
* the rolling five-hour window), unit 6 is weeks (number 1 → the weekly
|
|
682
|
+
* window). `TIME_LIMIT` rows are the monthly MCP tool budget (Web Search / Web
|
|
683
|
+
* Reader / Zread). Every row's `percentage` is the consumed share (falling
|
|
684
|
+
* back to `currentValue`/`usage` when absent) and `nextResetTime` (unix ms)
|
|
685
|
+
* the window reset.
|
|
674
686
|
*/
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
687
|
+
export function parseZaiQuotaLimits(data: Record<string, unknown> | null): ProviderQuota | null {
|
|
688
|
+
const limits = Array.isArray(data?.limits) ? data.limits as unknown[] : null;
|
|
689
|
+
if (!limits) return null;
|
|
690
|
+
const quota: ProviderQuota = { updatedAt: Date.now() };
|
|
691
|
+
let windows = 0;
|
|
692
|
+
for (const raw of limits) {
|
|
693
|
+
const row = asRecord(raw);
|
|
694
|
+
if (!row) continue;
|
|
695
|
+
const resetAt = normalizeResetAt(row.nextResetTime);
|
|
696
|
+
let percent = normalizePercent(row.percentage);
|
|
697
|
+
if (percent === undefined) {
|
|
698
|
+
const used = toFiniteNumber(row.currentValue);
|
|
699
|
+
const total = toFiniteNumber(row.usage);
|
|
700
|
+
if (used !== undefined && total !== undefined && total > 0) {
|
|
701
|
+
percent = normalizePercent((used / total) * 100);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
if (percent === undefined) continue;
|
|
705
|
+
if (row.type === "TOKENS_LIMIT" || row.type === "CREDIT_LIMIT") {
|
|
706
|
+
const unit = toFiniteNumber(row.unit);
|
|
707
|
+
const number = toFiniteNumber(row.number);
|
|
708
|
+
if (unit === 3 && number === 5) {
|
|
709
|
+
quota.fiveHourPercent = percent;
|
|
710
|
+
if (resetAt !== undefined) quota.fiveHourResetAt = resetAt;
|
|
711
|
+
windows += 1;
|
|
712
|
+
} else if (unit === 6 && number === 1) {
|
|
713
|
+
quota.weeklyPercent = percent;
|
|
714
|
+
if (resetAt !== undefined) quota.weeklyResetAt = resetAt;
|
|
715
|
+
windows += 1;
|
|
716
|
+
}
|
|
717
|
+
} else if (row.type === "TIME_LIMIT") {
|
|
718
|
+
quota.monthlyPercent = percent;
|
|
719
|
+
if (resetAt !== undefined) quota.monthlyResetAt = resetAt;
|
|
720
|
+
windows += 1;
|
|
721
|
+
}
|
|
688
722
|
}
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
723
|
+
return windows > 0 ? quota : null;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/**
|
|
727
|
+
* Legacy Z.AI payload shape: percent fields with window identifiers directly on
|
|
728
|
+
* the data object (optionally nested under `quota`). Kept as a fallback so
|
|
729
|
+
* older responses keep rendering when the `limits` array is absent.
|
|
730
|
+
*/
|
|
731
|
+
function parseZaiQuotaLegacyFields(data: Record<string, unknown> | null): ProviderQuota | null {
|
|
732
|
+
if (!data) return null;
|
|
694
733
|
const quota: ProviderQuota = { updatedAt: Date.now() };
|
|
695
734
|
let windows = 0;
|
|
696
735
|
const percentAt = (key: string): number | undefined => {
|
|
697
|
-
const value = normalizePercent(data
|
|
736
|
+
const value = normalizePercent(data[key]);
|
|
698
737
|
if (value !== undefined) return value;
|
|
699
|
-
const nested = asRecord(data
|
|
738
|
+
const nested = asRecord(data.quota);
|
|
700
739
|
return nested ? normalizePercent(nested[key]) : undefined;
|
|
701
740
|
};
|
|
702
741
|
const fiveHour = percentAt("fiveHourPercent") ?? percentAt("fiveHourUsage") ?? percentAt("fiveHourUsed");
|
|
@@ -714,7 +753,40 @@ async function fetchZaiQuota(provider: string, config: OcxProviderConfig): Promi
|
|
|
714
753
|
quota.monthlyPercent = monthly;
|
|
715
754
|
windows += 1;
|
|
716
755
|
}
|
|
717
|
-
return windows > 0 ?
|
|
756
|
+
return windows > 0 ? quota : null;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* Fetches the Z.AI GLM Coding Plan quota — on whichever region the provider
|
|
761
|
+
* points at (api.z.ai or open.bigmodel.cn). Authenticates with the API key as
|
|
762
|
+
* a Bearer token per Z.AI's API reference. The `limits` array shape is
|
|
763
|
+
* preferred; older field-name payloads fall back to the legacy parser.
|
|
764
|
+
*/
|
|
765
|
+
async function fetchZaiQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaProbeResult> {
|
|
766
|
+
if (!isCanonicalZaiBaseUrl(config.baseUrl)) return null;
|
|
767
|
+
const apiKey = resolveEnvValue(config.apiKey)?.trim();
|
|
768
|
+
if (!apiKey) return null;
|
|
769
|
+
const normalized = normalizedBaseUrl(config.baseUrl);
|
|
770
|
+
const monitorHost = normalized === ZAI_BASE_URL || normalized === `${ZAI_BASE_URL}/api/coding/paas/v4`
|
|
771
|
+
? ZAI_BASE_URL
|
|
772
|
+
: ZAI_CN_BASE_URL;
|
|
773
|
+
const response = await fetch(`${monitorHost}/api/monitor/usage/quota/limit`, {
|
|
774
|
+
headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
|
|
775
|
+
redirect: "error",
|
|
776
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
777
|
+
});
|
|
778
|
+
if (!response.ok) {
|
|
779
|
+
return response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429
|
|
780
|
+
? TERMINAL_QUOTA_FAILURE
|
|
781
|
+
: null;
|
|
782
|
+
}
|
|
783
|
+
const body = asRecord(await readQuotaJson(response));
|
|
784
|
+
if (!body || body.success === false) return null;
|
|
785
|
+
const data = asRecord(body.data) ?? body;
|
|
786
|
+
const quota = Array.isArray(data?.limits)
|
|
787
|
+
? parseZaiQuotaLimits(data)
|
|
788
|
+
: parseZaiQuotaLegacyFields(data);
|
|
789
|
+
return quota ? report(provider, "zai:quota-limit", quota) : null;
|
|
718
790
|
}
|
|
719
791
|
|
|
720
792
|
/**
|
|
@@ -2106,7 +2178,8 @@ async function maybeFetchProviderQuota(
|
|
|
2106
2178
|
if ((provider.authMode ?? "key") === "key" && name === "cline-pass") {
|
|
2107
2179
|
return fetchClineQuota(name, provider);
|
|
2108
2180
|
}
|
|
2109
|
-
if ((provider.authMode ?? "key") === "key"
|
|
2181
|
+
if ((provider.authMode ?? "key") === "key"
|
|
2182
|
+
&& (name === "zai" || name === "glm" || name === "glm-cn" || name === "zhipu-bigmodel-coding")) {
|
|
2110
2183
|
return fetchZaiQuota(name, provider);
|
|
2111
2184
|
}
|
|
2112
2185
|
if ((provider.authMode ?? "key") === "key" && (name === "minimax" || name === "minimax-cn")) {
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
MOONSHOT_BASE_URL_CHOICES, MOONSHOT_INTL_BASE_URL,
|
|
11
11
|
} from "./base-url-choices";
|
|
12
12
|
import {
|
|
13
|
+
CURSOR_NO_VISION_MODELS,
|
|
13
14
|
CURSOR_STATIC_MODELS,
|
|
14
15
|
cursorModelContextWindows,
|
|
15
16
|
cursorModelIds,
|
|
@@ -224,8 +225,22 @@ export interface ProviderRegistryEntry {
|
|
|
224
225
|
supportsServiceTier?: boolean;
|
|
225
226
|
/** Registry default for OpenAI extended hosted web_search field support. */
|
|
226
227
|
supportsOpenAiWebSearchToolFields?: boolean;
|
|
228
|
+
/** Registry default for native Responses custom-tool support. */
|
|
229
|
+
supportsResponsesCustomTools?: boolean;
|
|
227
230
|
/** Registry default for exact model service-tier capability; explicit config keys win. */
|
|
228
231
|
modelSupportsServiceTier?: Record<string, boolean>;
|
|
232
|
+
/**
|
|
233
|
+
* Registry-only service-tier defaults for an OAuth preset's explicit API-key transport.
|
|
234
|
+
* Applied only when `allowKeyAuthOverride` is true and the captured effective auth transport
|
|
235
|
+
* is key-based. Explicit provider config still wins field-by-field, including `false`.
|
|
236
|
+
*/
|
|
237
|
+
keyAuthServiceTier?: {
|
|
238
|
+
supportsServiceTier?: boolean;
|
|
239
|
+
modelSupportsServiceTier?: Record<string, boolean>;
|
|
240
|
+
chatServiceTier?: boolean;
|
|
241
|
+
};
|
|
242
|
+
/** Provider-specific copy for the Codex catalog's Fast tier. */
|
|
243
|
+
fastTierDescription?: string;
|
|
229
244
|
/**
|
|
230
245
|
* Registry-only destination guard for `modelSupportsServiceTier`. This scopes vendor evidence
|
|
231
246
|
* without changing provider ownership, routing, authentication, or config validation.
|
|
@@ -452,7 +467,23 @@ const THINKING_BUDGET_MODELS = [
|
|
|
452
467
|
];
|
|
453
468
|
const OPENCODE_GO_THINKING_BUDGET_MODELS = ["qwen3.5-plus", "qwen3.6-plus", "qwen3.7-max", "qwen3.7-plus"];
|
|
454
469
|
const DEEPSEEK_THINKING_MODELS = ["deepseek-v4-pro", "deepseek-v4-flash"];
|
|
470
|
+
/*
|
|
471
|
+
* DeepSeek's experimental vision preview (released 2026-08-21, api-docs.deepseek.com):
|
|
472
|
+
* text+image input on the V4 Flash base. DeepSeek positions it as a preview id;
|
|
473
|
+
* the expectation is that vision merges into `deepseek-v4-flash` proper later,
|
|
474
|
+
* at which point this id retires the same way deepseek-chat/reasoner did.
|
|
475
|
+
*/
|
|
476
|
+
const DEEPSEEK_VISION_PREVIEW_MODEL = "deepseek-v4-flash-vision-exp";
|
|
455
477
|
const OPENCODE_FREE_DEEPSEEK_MODELS = ["deepseek-v4-flash-free"];
|
|
478
|
+
/*
|
|
479
|
+
* OpenCode Zen's free slug for the OpenRouter stealth model "Ox Alpha"
|
|
480
|
+
* (openrouter.ai/stealth/ox-alpha): 1,048,576-token context, multimodal
|
|
481
|
+
* (text+image+video upstream; Zen serves text+image), mandatory reasoning,
|
|
482
|
+
* free during the stealth window. Zen displays it as "Ox Alpha Free" under
|
|
483
|
+
* this exact id (opencode.ai/docs/zen, verified 2026-08-21).
|
|
484
|
+
*/
|
|
485
|
+
const OPENCODE_OX_ALPHA_FREE_MODEL = "x-preview-f-free";
|
|
486
|
+
const OX_ALPHA_CONTEXT_WINDOW = 1_048_576;
|
|
456
487
|
/*
|
|
457
488
|
* Zen free models that reject `image_url` upstream (#1043, and the reproducible
|
|
458
489
|
* half of #1024).
|
|
@@ -994,11 +1025,10 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
994
1025
|
// no-effort fallback to `kimi-k3-max` would never be reached. Mirrors the other K3
|
|
995
1026
|
// routes (kimi, kimi-code, opencode-go).
|
|
996
1027
|
modelDefaultReasoningEfforts: { "kimi-k3": "max" },
|
|
997
|
-
// Cursor
|
|
998
|
-
//
|
|
999
|
-
//
|
|
1000
|
-
|
|
1001
|
-
noVisionModels: cursorModelIds(CURSOR_STATIC_MODELS),
|
|
1028
|
+
// Blind Cursor models (Auto routers, Composer, GLM-5.2, GLM-5.3) go through the vision sidecar;
|
|
1029
|
+
// multimodal hosts (Claude/Gemini/GPT/Kimi/Grok) take native SelectedImage. The catalog
|
|
1030
|
+
// still advertises image for noVision members so Codex can attach (sidecar option B).
|
|
1031
|
+
noVisionModels: [...CURSOR_NO_VISION_MODELS],
|
|
1002
1032
|
},
|
|
1003
1033
|
{
|
|
1004
1034
|
id: "xai",
|
|
@@ -1007,10 +1037,21 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
1007
1037
|
baseUrl: "https://api.x.ai/v1",
|
|
1008
1038
|
authKind: "oauth",
|
|
1009
1039
|
allowKeyAuthOverride: true,
|
|
1040
|
+
// Priority Processing is documented for xAI's public API-key Chat Completions and
|
|
1041
|
+
// Responses endpoints. OAuth is a separate Grok CLI subscription gateway and remains
|
|
1042
|
+
// unclassified; do not turn this into a provider-wide supportsServiceTier declaration.
|
|
1043
|
+
keyAuthServiceTier: {
|
|
1044
|
+
supportsServiceTier: true,
|
|
1045
|
+
chatServiceTier: true,
|
|
1046
|
+
},
|
|
1047
|
+
fastTierDescription: "Priority processing, 2x token price",
|
|
1010
1048
|
featured: true,
|
|
1011
1049
|
oauthId: "xai",
|
|
1012
1050
|
jawcodeBundle: "xai",
|
|
1013
1051
|
supportsOpenAiWebSearchToolFields: false,
|
|
1052
|
+
// Live A/B on 2026-08-20: xAI rejects native custom/custom_tool_call shapes while accepting
|
|
1053
|
+
// the otherwise-identical request after the custom tool is lowered to a function.
|
|
1054
|
+
supportsResponsesCustomTools: false,
|
|
1014
1055
|
note: "Log in with your Grok account",
|
|
1015
1056
|
// Parallel tool calls: officially supported and default-on per docs.x.ai function-calling
|
|
1016
1057
|
// (verified 260709, devlog/_plan/260709_parallel_tool_calls). Streamed calls arrive whole
|
|
@@ -1101,6 +1142,17 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
1101
1142
|
// Unknown/new live models deliberately do not advertise a reasoning picker.
|
|
1102
1143
|
reasoningEfforts: [],
|
|
1103
1144
|
modelReasoningEfforts: COMMAND_CODE_MODEL_REASONING_EFFORTS,
|
|
1145
|
+
// Ox Alpha (stealth preview, changelog v1.31.0): free 1M multimodal reasoning
|
|
1146
|
+
// model on every plan. DeepSeek vision preview id is preemptive metadata —
|
|
1147
|
+
// it is expected to merge into deepseek-v4-flash later.
|
|
1148
|
+
modelContextWindows: {
|
|
1149
|
+
"stealth/ox-alpha": OX_ALPHA_CONTEXT_WINDOW,
|
|
1150
|
+
[`deepseek/${DEEPSEEK_VISION_PREVIEW_MODEL}`]: 1_048_576,
|
|
1151
|
+
},
|
|
1152
|
+
modelInputModalities: {
|
|
1153
|
+
"stealth/ox-alpha": ["text", "image"],
|
|
1154
|
+
[`deepseek/${DEEPSEEK_VISION_PREVIEW_MODEL}`]: ["text", "image"],
|
|
1155
|
+
},
|
|
1104
1156
|
defaultMaxOutputTokens: 64_000,
|
|
1105
1157
|
// The proprietary generate wire has no verified per-request serialization flag.
|
|
1106
1158
|
parallelToolCalls: false,
|
|
@@ -1292,8 +1344,20 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
1292
1344
|
- 장점, 단점 및 영향: Luna reaches `/responses` from every inbound surface without changing siblings; a future upstream endpoint change requires an evidence-backed registry update.
|
|
1293
1345
|
*/
|
|
1294
1346
|
modelWireDefaults: { "gpt-5.6-luna": "openai-responses" },
|
|
1295
|
-
modelContextWindows: {
|
|
1296
|
-
|
|
1347
|
+
modelContextWindows: {
|
|
1348
|
+
"kimi-k3": KIMI_K3_STANDARD_CONTEXT_WINDOW,
|
|
1349
|
+
// Ox Alpha (stealth 1M multimodal) and the DeepSeek vision preview are
|
|
1350
|
+
// metadata-only here: the Go roster is discovered live, so these apply
|
|
1351
|
+
// the moment the gateway starts serving the ids.
|
|
1352
|
+
[OPENCODE_OX_ALPHA_FREE_MODEL]: OX_ALPHA_CONTEXT_WINDOW,
|
|
1353
|
+
[DEEPSEEK_VISION_PREVIEW_MODEL]: 1_048_576,
|
|
1354
|
+
},
|
|
1355
|
+
modelInputModalities: {
|
|
1356
|
+
"kimi-k3": ["text", "image"],
|
|
1357
|
+
[OPENCODE_OX_ALPHA_FREE_MODEL]: ["text", "image"],
|
|
1358
|
+
// Experimental DeepSeek vision preview — expected to merge into deepseek-v4-flash later.
|
|
1359
|
+
[DEEPSEEK_VISION_PREVIEW_MODEL]: ["text", "image"],
|
|
1360
|
+
},
|
|
1297
1361
|
modelReasoningEfforts: {
|
|
1298
1362
|
"glm-5.3": ZAI_GLM_53_REASONING_EFFORTS,
|
|
1299
1363
|
"glm-5.2": ZAI_GLM_52_REASONING_EFFORTS,
|
|
@@ -1396,11 +1460,16 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
1396
1460
|
featured: true,
|
|
1397
1461
|
dashboardUrl: "https://openrouter.ai/keys",
|
|
1398
1462
|
jawcodeBundle: "openrouter",
|
|
1399
|
-
|
|
1463
|
+
// stealth/ox-alpha: free stealth-window frontier model (launched 2026-08-20).
|
|
1464
|
+
// /api/v1/models reports 1,048,576 context, 131,072 max output, text+image+video
|
|
1465
|
+
// input, $0 pricing, mandatory reasoning. Single provider slug: `stealth`.
|
|
1466
|
+
models: ["anthropic/claude-sonnet-5", "stealth/ox-alpha", ...OPENROUTER_GPT56_MODELS],
|
|
1400
1467
|
modelContextWindows: {
|
|
1401
1468
|
"anthropic/claude-sonnet-5": 1_000_000,
|
|
1469
|
+
"stealth/ox-alpha": OX_ALPHA_CONTEXT_WINDOW,
|
|
1402
1470
|
...OPENROUTER_GPT56_CONTEXT_WINDOWS,
|
|
1403
1471
|
},
|
|
1472
|
+
modelInputModalities: { "stealth/ox-alpha": ["text", "image"] },
|
|
1404
1473
|
// OpenRouter documents priority support for OpenAI endpoints, but not Anthropic. Keep the
|
|
1405
1474
|
// provider unclassified and opt in only the exact OpenAI-backed slugs we ship. These facts
|
|
1406
1475
|
// belong only to the canonical destination; a same-named custom gateway is unknown to us.
|
|
@@ -1551,11 +1620,14 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
1551
1620
|
// keep validating and routing (they previously mapped to v4-flash; devlog
|
|
1552
1621
|
// _fin/260710_provider_hardening/002_research_cn.md). The current offerings are
|
|
1553
1622
|
// the V4 ids — defaultModel and the model-specific wiring above use them.
|
|
1554
|
-
|
|
1623
|
+
// deepseek-v4-flash-vision-exp: experimental vision preview (2026-08-21) —
|
|
1624
|
+
// expected to merge into deepseek-v4-flash later; see DEEPSEEK_VISION_PREVIEW_MODEL.
|
|
1625
|
+
models: ["deepseek-chat", "deepseek-reasoner", ...DEEPSEEK_THINKING_MODELS, DEEPSEEK_VISION_PREVIEW_MODEL],
|
|
1555
1626
|
defaultModel: "deepseek-v4-flash",
|
|
1556
1627
|
// Official DeepSeek Codex setup (codex-deepseek-setup.sh) advertises 1,048,576
|
|
1557
1628
|
// for both V4 models; the older 1,000,000 figure was a rounded approximation.
|
|
1558
|
-
modelContextWindows: { "deepseek-v4-flash": 1_048_576, "deepseek-v4-pro": 1_048_576 },
|
|
1629
|
+
modelContextWindows: { "deepseek-v4-flash": 1_048_576, "deepseek-v4-pro": 1_048_576, [DEEPSEEK_VISION_PREVIEW_MODEL]: 1_048_576 },
|
|
1630
|
+
modelInputModalities: { [DEEPSEEK_VISION_PREVIEW_MODEL]: ["text", "image"] },
|
|
1559
1631
|
// DeepSeek documents both V4 models as native Responses API models adapted for Codex
|
|
1560
1632
|
// (model table marks Responses API ✓ for flash and pro; the /responses reference lists
|
|
1561
1633
|
// both ids as accepted `model` values — verified 2026-08-13 with the V4 Pro GA,
|
|
@@ -1808,6 +1880,17 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
1808
1880
|
// slash ids — so a Codex-facing slug like `commandcode/deepseek-deepseek-v4-pro`
|
|
1809
1881
|
// is sent upstream verbatim and rejected with `unsupported_model`.
|
|
1810
1882
|
modelReasoningEfforts: COMMAND_CODE_MODEL_REASONING_EFFORTS,
|
|
1883
|
+
// Ox Alpha (stealth preview, Command Code changelog v1.31.0) ships with a
|
|
1884
|
+
// 1.05M-token multimodal context; the DeepSeek vision preview id is
|
|
1885
|
+
// preemptive for when the catalog serves it (merges into v4-flash later).
|
|
1886
|
+
modelContextWindows: {
|
|
1887
|
+
"stealth/ox-alpha": OX_ALPHA_CONTEXT_WINDOW,
|
|
1888
|
+
[`deepseek/${DEEPSEEK_VISION_PREVIEW_MODEL}`]: 1_048_576,
|
|
1889
|
+
},
|
|
1890
|
+
modelInputModalities: {
|
|
1891
|
+
"stealth/ox-alpha": ["text", "image"],
|
|
1892
|
+
[`deepseek/${DEEPSEEK_VISION_PREVIEW_MODEL}`]: ["text", "image"],
|
|
1893
|
+
},
|
|
1811
1894
|
modelDiscovery: {
|
|
1812
1895
|
path: "models",
|
|
1813
1896
|
maxResponseBytes: 256 * 1024,
|
|
@@ -2452,6 +2535,16 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
2452
2535
|
[...DEEPSEEK_THINKING_MODELS, ...OPENCODE_FREE_DEEPSEEK_MODELS].map(id => [id, deepseekReasoningMapFor(id)]),
|
|
2453
2536
|
),
|
|
2454
2537
|
preserveReasoningContentModels: [...DEEPSEEK_THINKING_MODELS, ...OPENCODE_FREE_DEEPSEEK_MODELS],
|
|
2538
|
+
// Same Zen gateway as opencode-free: Ox Alpha Free (1M multimodal stealth model)
|
|
2539
|
+
// and the DeepSeek vision preview (merges into deepseek-v4-flash later).
|
|
2540
|
+
modelContextWindows: {
|
|
2541
|
+
[OPENCODE_OX_ALPHA_FREE_MODEL]: OX_ALPHA_CONTEXT_WINDOW,
|
|
2542
|
+
[DEEPSEEK_VISION_PREVIEW_MODEL]: 1_048_576,
|
|
2543
|
+
},
|
|
2544
|
+
modelInputModalities: {
|
|
2545
|
+
[OPENCODE_OX_ALPHA_FREE_MODEL]: ["text", "image"],
|
|
2546
|
+
[DEEPSEEK_VISION_PREVIEW_MODEL]: ["text", "image"],
|
|
2547
|
+
},
|
|
2455
2548
|
noVisionModels: [...OPENCODE_ZEN_TEXT_ONLY_MODELS, ...DEEPSEEK_THINKING_MODELS],
|
|
2456
2549
|
},
|
|
2457
2550
|
{ id: "vercel-ai-gateway", label: "Vercel AI Gateway", baseUrl: "https://ai-gateway.vercel.sh/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://vercel.com/dashboard" },
|
|
@@ -2482,6 +2575,18 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
2482
2575
|
modelReasoningEfforts: Object.fromEntries(OPENCODE_FREE_DEEPSEEK_MODELS.map(id => [id, deepseekThinkingEffortsFor(id)])),
|
|
2483
2576
|
modelReasoningEffortMap: Object.fromEntries(OPENCODE_FREE_DEEPSEEK_MODELS.map(id => [id, deepseekReasoningMapFor(id)])),
|
|
2484
2577
|
preserveReasoningContentModels: OPENCODE_FREE_DEEPSEEK_MODELS,
|
|
2578
|
+
// Ox Alpha Free (`x-preview-f-free`): the OpenRouter stealth model on Zen's
|
|
2579
|
+
// free tier — 1,048,576 context, text+image input. Deliberately NOT in the
|
|
2580
|
+
// text-only list below. The DeepSeek vision preview id is preemptive
|
|
2581
|
+
// metadata for when Zen starts serving it (merges into v4-flash later).
|
|
2582
|
+
modelContextWindows: {
|
|
2583
|
+
[OPENCODE_OX_ALPHA_FREE_MODEL]: OX_ALPHA_CONTEXT_WINDOW,
|
|
2584
|
+
[DEEPSEEK_VISION_PREVIEW_MODEL]: 1_048_576,
|
|
2585
|
+
},
|
|
2586
|
+
modelInputModalities: {
|
|
2587
|
+
[OPENCODE_OX_ALPHA_FREE_MODEL]: ["text", "image"],
|
|
2588
|
+
[DEEPSEEK_VISION_PREVIEW_MODEL]: ["text", "image"],
|
|
2589
|
+
},
|
|
2485
2590
|
// Same Zen roster behind the same base URL, so it carries the same measured
|
|
2486
2591
|
// text-only list rather than only its DeepSeek member (#1043).
|
|
2487
2592
|
noVisionModels: OPENCODE_ZEN_TEXT_ONLY_MODELS,
|
|
@@ -70,11 +70,22 @@ function buildFastPolicyAuthority(
|
|
|
70
70
|
capabilityProvider: ServiceTierCapabilityProvider = provider,
|
|
71
71
|
): FastPolicyAuthority {
|
|
72
72
|
const registry = registryTransportMatch ? getProviderRegistryEntry(providerName) : undefined;
|
|
73
|
+
const authTransport = resolveProviderAuthTransport(
|
|
74
|
+
provider.adapter,
|
|
75
|
+
provider.authMode ?? registry?.authKind ?? "key",
|
|
76
|
+
provider.apiKeyTransport,
|
|
77
|
+
);
|
|
78
|
+
const keyAuthDefaults = registry?.allowKeyAuthOverride === true
|
|
79
|
+
&& (authTransport === "authorization_bearer" || authTransport === "x_api_key")
|
|
80
|
+
? registry.keyAuthServiceTier
|
|
81
|
+
: undefined;
|
|
73
82
|
const registryModelCapabilities = registry
|
|
74
83
|
&& registryModelServiceTierCapabilityApplies(registry, capabilityProvider)
|
|
75
84
|
? registry.modelSupportsServiceTier
|
|
76
85
|
: undefined;
|
|
77
|
-
const providerCapability = capabilityProvider.supportsServiceTier
|
|
86
|
+
const providerCapability = capabilityProvider.supportsServiceTier
|
|
87
|
+
?? keyAuthDefaults?.supportsServiceTier
|
|
88
|
+
?? registry?.supportsServiceTier;
|
|
78
89
|
const authority: FastPolicyAuthority = Object.freeze({
|
|
79
90
|
providerAdapter: provider.adapter,
|
|
80
91
|
providerAuthMode: provider.authMode ?? registry?.authKind ?? "key",
|
|
@@ -82,19 +93,23 @@ function buildFastPolicyAuthority(
|
|
|
82
93
|
provider.fastWire !== undefined ? provider.fastWire : registry?.fastWire,
|
|
83
94
|
{ freeze: true },
|
|
84
95
|
),
|
|
96
|
+
...(registry?.fastTierDescription !== undefined
|
|
97
|
+
? { fastTierDescription: registry.fastTierDescription }
|
|
98
|
+
: {}),
|
|
85
99
|
modelWireOverrideAllowed: !isCanonicalOpenAiForwardProvider(provider as OcxProviderConfig),
|
|
86
|
-
authTransport
|
|
87
|
-
provider.adapter,
|
|
88
|
-
provider.authMode ?? registry?.authKind ?? "key",
|
|
89
|
-
provider.apiKeyTransport,
|
|
90
|
-
),
|
|
100
|
+
authTransport,
|
|
91
101
|
capability: Object.freeze({
|
|
92
102
|
...(providerCapability !== undefined ? { provider: providerCapability } : {}),
|
|
93
103
|
models: Object.freeze({
|
|
94
104
|
...(registryModelCapabilities ?? {}),
|
|
105
|
+
...(keyAuthDefaults?.modelSupportsServiceTier ?? {}),
|
|
95
106
|
...(capabilityProvider.modelSupportsServiceTier ?? {}),
|
|
96
107
|
}),
|
|
97
|
-
...(provider.chatServiceTier !== undefined
|
|
108
|
+
...(provider.chatServiceTier !== undefined
|
|
109
|
+
? { chatServiceTier: provider.chatServiceTier }
|
|
110
|
+
: keyAuthDefaults?.chatServiceTier !== undefined
|
|
111
|
+
? { chatServiceTier: keyAuthDefaults.chatServiceTier }
|
|
112
|
+
: {}),
|
|
98
113
|
}),
|
|
99
114
|
modelAdapters: Object.freeze({ ...(provider.modelAdapters ?? {}) }),
|
|
100
115
|
hardPins: captureWireAdapterHardPins(providerName),
|
|
@@ -4,6 +4,13 @@ import { collectResponsesToolGroups } from "./tool-groups";
|
|
|
4
4
|
const ROUTED_CUSTOM_TOOL_PASSTHROUGH = new Set(["apply_patch"]);
|
|
5
5
|
const BUILTIN_FUNCTIONS_NAMESPACE = "functions";
|
|
6
6
|
|
|
7
|
+
function routedCustomToolPassesThrough(
|
|
8
|
+
name: string,
|
|
9
|
+
supportsResponsesCustomTools: boolean | undefined,
|
|
10
|
+
): boolean {
|
|
11
|
+
return supportsResponsesCustomTools !== false && ROUTED_CUSTOM_TOOL_PASSTHROUGH.has(name);
|
|
12
|
+
}
|
|
13
|
+
|
|
7
14
|
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
8
15
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
9
16
|
}
|
|
@@ -34,7 +41,10 @@ export function routedCustomToolWireName(value: unknown): string | undefined {
|
|
|
34
41
|
* Names of converted custom declarations after namespace lowering. Restoration uses these exact
|
|
35
42
|
* wire identities so same-named function and custom children in different namespaces stay distinct.
|
|
36
43
|
*/
|
|
37
|
-
function collectRoutedCustomToolWireNames(
|
|
44
|
+
function collectRoutedCustomToolWireNames(
|
|
45
|
+
body: unknown,
|
|
46
|
+
supportsResponsesCustomTools?: boolean,
|
|
47
|
+
): Set<string> {
|
|
38
48
|
const names = new Set<string>();
|
|
39
49
|
const groups = collectResponsesToolGroups(body);
|
|
40
50
|
const bareWireNames = new Set<string>();
|
|
@@ -54,7 +64,7 @@ function collectRoutedCustomToolWireNames(body: unknown): Set<string> {
|
|
|
54
64
|
if (
|
|
55
65
|
tool.type === "custom"
|
|
56
66
|
&& typeof tool.name === "string"
|
|
57
|
-
&& !
|
|
67
|
+
&& !routedCustomToolPassesThrough(tool.name, supportsResponsesCustomTools)
|
|
58
68
|
) {
|
|
59
69
|
names.add(tool.name);
|
|
60
70
|
continue;
|
|
@@ -67,7 +77,7 @@ function collectRoutedCustomToolWireNames(body: unknown): Set<string> {
|
|
|
67
77
|
isPlainObject(child)
|
|
68
78
|
&& child.type === "custom"
|
|
69
79
|
&& typeof child.name === "string"
|
|
70
|
-
&& !
|
|
80
|
+
&& !routedCustomToolPassesThrough(child.name, supportsResponsesCustomTools)
|
|
71
81
|
&& !(tool.name === BUILTIN_FUNCTIONS_NAMESPACE && bareWireNames.has(child.name))
|
|
72
82
|
) names.add(customToolWireName(tool.name, child.name));
|
|
73
83
|
}
|
|
@@ -81,7 +91,10 @@ export function customToolItemId(id: unknown): unknown {
|
|
|
81
91
|
return id.startsWith("fc_") ? `ctc_${id.slice(3)}` : id;
|
|
82
92
|
}
|
|
83
93
|
|
|
84
|
-
export function collectRoutedCustomToolNames(
|
|
94
|
+
export function collectRoutedCustomToolNames(
|
|
95
|
+
body: unknown,
|
|
96
|
+
supportsResponsesCustomTools?: boolean,
|
|
97
|
+
): Set<string> {
|
|
85
98
|
const names = new Set<string>();
|
|
86
99
|
const visit = (value: unknown): void => {
|
|
87
100
|
if (Array.isArray(value)) {
|
|
@@ -92,7 +105,7 @@ export function collectRoutedCustomToolNames(body: unknown): Set<string> {
|
|
|
92
105
|
if (
|
|
93
106
|
value.type === "custom"
|
|
94
107
|
&& typeof value.name === "string"
|
|
95
|
-
&& !
|
|
108
|
+
&& !routedCustomToolPassesThrough(value.name, supportsResponsesCustomTools)
|
|
96
109
|
) {
|
|
97
110
|
names.add(value.name);
|
|
98
111
|
}
|
|
@@ -184,12 +197,15 @@ function rewriteForUpstream(
|
|
|
184
197
|
return changed ? next : value;
|
|
185
198
|
}
|
|
186
199
|
|
|
187
|
-
export function rewriteRoutedCustomToolsForUpstream(
|
|
200
|
+
export function rewriteRoutedCustomToolsForUpstream(
|
|
201
|
+
body: unknown,
|
|
202
|
+
supportsResponsesCustomTools?: boolean,
|
|
203
|
+
): {
|
|
188
204
|
body: unknown;
|
|
189
205
|
names: Set<string>;
|
|
190
206
|
} {
|
|
191
|
-
const conversionNames = collectRoutedCustomToolNames(body);
|
|
192
|
-
const names = collectRoutedCustomToolWireNames(body);
|
|
207
|
+
const conversionNames = collectRoutedCustomToolNames(body, supportsResponsesCustomTools);
|
|
208
|
+
const names = collectRoutedCustomToolWireNames(body, supportsResponsesCustomTools);
|
|
193
209
|
if (conversionNames.size === 0) return { body, names };
|
|
194
210
|
const callIds = new Set<string>();
|
|
195
211
|
collectConvertedCallIds(body, conversionNames, callIds);
|