@bitkyc08/opencodex 2.8.0 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -0
- package/bin/ocx.mjs +32 -4
- package/gui/dist/assets/index-CHwf3tTD.css +1 -0
- package/gui/dist/assets/index-u5eFOv2y.js +67 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic-image-normalize.ts +114 -20
- package/src/adapters/anthropic.ts +126 -10
- package/src/adapters/azure.ts +3 -3
- package/src/adapters/base.ts +7 -3
- package/src/adapters/cursor/discovery.ts +14 -4
- package/src/adapters/cursor/effort-map.ts +18 -6
- package/src/adapters/cursor/framing.ts +102 -27
- package/src/adapters/cursor/kv-store.ts +30 -3
- package/src/adapters/cursor/live-models.ts +22 -2
- package/src/adapters/cursor/live-transport.ts +245 -49
- package/src/adapters/cursor/mcp-manager.ts +105 -8
- package/src/adapters/cursor/native-exec-mcp.ts +5 -3
- package/src/adapters/cursor/native-exec-shell.ts +296 -14
- package/src/adapters/cursor/native-exec.ts +381 -33
- package/src/adapters/cursor/protobuf-events.ts +28 -1
- package/src/adapters/cursor/protobuf-request.ts +71 -39
- package/src/adapters/cursor/request-builder.ts +2 -2
- package/src/adapters/cursor/transport.ts +2 -0
- package/src/adapters/cursor.ts +13 -2
- package/src/adapters/google-antigravity-replay.ts +184 -17
- package/src/adapters/google.ts +58 -8
- package/src/adapters/kiro-thinking.ts +23 -9
- package/src/adapters/kiro-tools.ts +49 -18
- package/src/adapters/kiro.ts +377 -133
- package/src/adapters/mimo-free.ts +36 -4
- package/src/adapters/openai-chat.ts +143 -17
- package/src/adapters/openai-responses.ts +130 -14
- package/src/adapters/run-turn-queue.ts +7 -1
- package/src/bridge.ts +466 -69
- package/src/chat/outbound.ts +144 -38
- package/src/claude/inbound-debug.ts +53 -8
- package/src/claude/outbound.ts +224 -38
- package/src/cli/agent-driven.ts +34 -1
- package/src/cli/catalog-prewarm.ts +5 -2
- package/src/cli/claude-desktop.ts +2 -2
- package/src/cli/doctor.ts +12 -0
- package/src/cli/export-command.ts +187 -0
- package/src/cli/help.ts +11 -0
- package/src/cli/index.ts +13 -3
- package/src/cli/init.ts +129 -102
- package/src/cli/opencode.ts +36 -151
- package/src/cli/star-prompt.ts +13 -4
- package/src/cli/status-oauth.ts +12 -2
- package/src/clients/config-export.ts +377 -0
- package/src/codex/account-runtime-state.ts +19 -1
- package/src/codex/account-store.ts +162 -82
- package/src/codex/auth-api.ts +467 -159
- package/src/codex/auth-context.ts +15 -2
- package/src/codex/catalog/aggregation.ts +15 -0
- package/src/codex/catalog/effort.ts +16 -6
- package/src/codex/catalog/metadata.ts +6 -0
- package/src/codex/catalog/parsing.ts +3 -1
- package/src/codex/catalog/provider-fetch.ts +29 -0
- package/src/codex/catalog/sync.ts +64 -7
- package/src/codex/catalog.ts +2 -2
- package/src/codex/inject.ts +5 -5
- package/src/codex/main-account-cache.ts +8 -1
- package/src/codex/model-cache.ts +81 -2
- package/src/codex/pool-rotation.ts +39 -0
- package/src/codex/project-config-warnings.ts +12 -1
- package/src/codex/quota.ts +35 -3
- package/src/codex/routing.ts +46 -1
- package/src/codex/shim.ts +10 -4
- package/src/codex/subagent-model-fallback.ts +12 -0
- package/src/codex/websocket-registry.ts +27 -0
- package/src/combos/failover.ts +31 -1
- package/src/combos/request.ts +9 -0
- package/src/combos/resolve.ts +60 -4
- package/src/combos/types.ts +12 -0
- package/src/config.ts +510 -55
- package/src/github/star-state.ts +13 -1
- package/src/images/fulfill.ts +39 -1
- package/src/images/loop.ts +52 -12
- package/src/lib/admission.ts +83 -0
- package/src/lib/app-owned-memory-stores.ts +173 -0
- package/src/lib/app-owned-memory.ts +265 -0
- package/src/lib/bun-stream-caps.ts +31 -7
- package/src/lib/config-ownership.ts +33 -0
- package/src/lib/crash-guard.ts +65 -5
- package/src/lib/debug-log-buffer.ts +47 -6
- package/src/lib/destination-policy.ts +12 -1
- package/src/lib/errors.ts +3 -0
- package/src/lib/gcp-adc.ts +40 -2
- package/src/lib/injection-debug-log.ts +26 -2
- package/src/lib/provider-outbound.ts +3 -0
- package/src/lib/sidecar-tracker.ts +5 -2
- package/src/lib/sse-decoder.ts +257 -37
- package/src/lib/state-store-registrations.ts +109 -0
- package/src/lib/state-store-sweeper.ts +184 -0
- package/src/lib/translator-budget.ts +356 -0
- package/src/lib/windows-secret-acl.ts +33 -12
- package/src/lib/winsw.ts +14 -1
- package/src/oauth/anthropic-routing.ts +31 -7
- package/src/oauth/google-antigravity.ts +2 -1
- package/src/oauth/health.ts +30 -12
- package/src/oauth/index.ts +127 -23
- package/src/oauth/kiro-credentials.ts +72 -1
- package/src/oauth/kiro.ts +23 -4
- package/src/oauth/store.ts +165 -18
- package/src/oauth/token-guardian.ts +43 -4
- package/src/oauth/types.ts +2 -1
- package/src/providers/base-url-choices.ts +10 -0
- package/src/providers/derive.ts +12 -0
- package/src/providers/free-directory.ts +4 -1
- package/src/providers/key-failover.ts +12 -0
- package/src/providers/openai-sidecar.ts +4 -1
- package/src/providers/quota.ts +68 -7
- package/src/providers/registry.ts +279 -3
- package/src/responses/parser.ts +5 -1
- package/src/responses/spill-store.ts +394 -0
- package/src/responses/state.ts +520 -102
- package/src/router.ts +18 -1
- package/src/server/adapter-resolve.ts +20 -3
- package/src/server/auth-cors.ts +121 -28
- package/src/server/chat-completions.ts +57 -12
- package/src/server/claude-messages.ts +85 -13
- package/src/server/index.ts +242 -100
- package/src/server/lifecycle.ts +155 -25
- package/src/server/management/agent-settings-routes.ts +79 -36
- package/src/server/management/api-key-usage.ts +167 -0
- package/src/server/management/body.ts +35 -0
- package/src/server/management/combo-routes.ts +5 -1
- package/src/server/management/config-routes.ts +42 -12
- package/src/server/management/logs-usage-routes.ts +41 -21
- package/src/server/management/model-routes.ts +188 -54
- package/src/server/management/oauth-account-routes.ts +115 -26
- package/src/server/management/provider-routes.ts +56 -6
- package/src/server/management/shared.ts +16 -3
- package/src/server/management/sidebar-routes.ts +50 -1
- package/src/server/management/system-restart.ts +13 -6
- package/src/server/management/system-routes.ts +15 -3
- package/src/server/management/usage-summary-cache.ts +86 -0
- package/src/server/management-api.ts +39 -5
- package/src/server/management-auth.ts +65 -14
- package/src/server/port-reclaim.ts +58 -12
- package/src/server/ports.ts +2 -0
- package/src/server/proxy-liveness.ts +60 -14
- package/src/server/relay-eager.ts +20 -4
- package/src/server/relay.ts +548 -154
- package/src/server/request-decompress.ts +51 -4
- package/src/server/request-log.ts +134 -15
- package/src/server/responses/collaboration.ts +15 -4
- package/src/server/responses/compact.ts +3 -0
- package/src/server/responses/core.ts +241 -66
- package/src/server/responses-image-gen-repair.ts +19 -5
- package/src/server/responses-item-id-repair.ts +23 -5
- package/src/server/sse-payload-rewrite.ts +71 -12
- package/src/server/startup-health-cache.ts +14 -1
- package/src/server/system-env.ts +8 -1
- package/src/server/windows-tcp-drop.ts +15 -5
- package/src/server/ws-bridge.ts +25 -0
- package/src/service.ts +179 -13
- package/src/storage/policy-job.ts +93 -23
- package/src/storage/policy-worker.ts +6 -0
- package/src/storage/restore-job.ts +62 -16
- package/src/storage/restore-worker.ts +6 -0
- package/src/storage/storage-mutation-coordinator.ts +36 -6
- package/src/storage/worker-lifecycle.ts +181 -47
- package/src/tray/windows.ts +97 -25
- package/src/types.ts +39 -6
- package/src/update/index.ts +24 -5
- package/src/update/job.ts +598 -73
- package/src/usage/log.ts +115 -17
- package/src/usage/summary.ts +67 -2
- package/src/vision/index.ts +112 -22
- package/src/web-search/loop.ts +38 -6
- package/src/web-search/progress-stream.ts +14 -3
- package/gui/dist/assets/index-BDjpkcRN.js +0 -67
- package/gui/dist/assets/index-BHsKRFh9.css +0 -1
package/src/providers/quota.ts
CHANGED
|
@@ -7,6 +7,11 @@ import { antigravityUserAgent } from "../adapters/client-fingerprint";
|
|
|
7
7
|
import { getProviderRegistryEntry, providerCodexAccountMode } from "./registry";
|
|
8
8
|
import type { OcxConfig, OcxProviderConfig } from "../types";
|
|
9
9
|
import { isCanonicalOpenAiForwardProvider, OPENAI_CODEX_PROVIDER_ID } from "./openai-tiers";
|
|
10
|
+
import {
|
|
11
|
+
captureConfigGeneration,
|
|
12
|
+
sweepExpiredOnWrite,
|
|
13
|
+
type GenerationContext,
|
|
14
|
+
} from "../lib/state-store-sweeper";
|
|
10
15
|
|
|
11
16
|
/** Match oauth/index REFRESH_SKEW_MS — use stored access without refresh when still fresh. */
|
|
12
17
|
const ACCOUNT_TOKEN_SKEW_MS = 60_000;
|
|
@@ -243,6 +248,8 @@ async function fetchAnthropicQuota(provider: string): Promise<ProviderQuotaRepor
|
|
|
243
248
|
// Capture the account we intend to probe before awaiting — a mid-flight active
|
|
244
249
|
// switch must not seed the wrong account's cache with this response.
|
|
245
250
|
const probedAccountId = getAccountSet("anthropic")?.activeAccountId;
|
|
251
|
+
const probedAccountKey = probedAccountId ? accountCacheKey("anthropic", probedAccountId) : null;
|
|
252
|
+
const writerGeneration = captureConfigGeneration();
|
|
246
253
|
let accessToken: string;
|
|
247
254
|
try {
|
|
248
255
|
accessToken = await getValidAccessToken("anthropic");
|
|
@@ -253,10 +260,10 @@ async function fetchAnthropicQuota(provider: string): Promise<ProviderQuotaRepor
|
|
|
253
260
|
if (!quota) return null;
|
|
254
261
|
// Share the active-account probe with the per-account cache so Providers-page
|
|
255
262
|
// loads do not double-hit Anthropic's rate-limited usage endpoint.
|
|
256
|
-
if (probedAccountId) {
|
|
263
|
+
if (probedAccountId && probedAccountKey) {
|
|
257
264
|
const stillOwnsToken = getAccountCredential("anthropic", probedAccountId)?.access === accessToken;
|
|
258
|
-
if (stillOwnsToken) {
|
|
259
|
-
accountQuotaCache.set(
|
|
265
|
+
if (stillOwnsToken && mayCommitAccountQuotaKey(probedAccountKey, writerGeneration)) {
|
|
266
|
+
accountQuotaCache.set(probedAccountKey, { ts: Date.now(), quota });
|
|
260
267
|
}
|
|
261
268
|
}
|
|
262
269
|
return report(provider, "anthropic:oauth-usage", quota);
|
|
@@ -284,6 +291,17 @@ type AccountQuotaCacheEntry = {
|
|
|
284
291
|
};
|
|
285
292
|
const accountQuotaCache = new Map<string, AccountQuotaCacheEntry>();
|
|
286
293
|
const accountQuotaInflight = new Map<string, Promise<AccountQuotaCacheEntry>>();
|
|
294
|
+
let lastReconciledGeneration = 0;
|
|
295
|
+
let liveAccountQuotaKeys = new Set<string>();
|
|
296
|
+
let liveProviderQuotaKeys = new Set<string>();
|
|
297
|
+
|
|
298
|
+
function mayCommitAccountQuotaKey(key: string, writerGeneration: number): boolean {
|
|
299
|
+
return writerGeneration >= lastReconciledGeneration || liveAccountQuotaKeys.has(key);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function mayCommitProviderQuotaKey(key: string, writerGeneration: number): boolean {
|
|
303
|
+
return writerGeneration >= lastReconciledGeneration || liveProviderQuotaKeys.has(key);
|
|
304
|
+
}
|
|
287
305
|
|
|
288
306
|
export interface ProviderAccountQuota {
|
|
289
307
|
accountId: string;
|
|
@@ -324,6 +342,35 @@ export function setCachedProviderAccountQuotaForTests(
|
|
|
324
342
|
accountQuotaCache.set(key, { ts: Date.now(), quota });
|
|
325
343
|
}
|
|
326
344
|
|
|
345
|
+
export function sweepExpiredProviderAccountQuotaRows(now = Date.now()): number {
|
|
346
|
+
let removed = 0;
|
|
347
|
+
for (const [key, entry] of accountQuotaCache) {
|
|
348
|
+
if (entry.ts + ACCOUNT_QUOTA_TTL_MS > now) continue;
|
|
349
|
+
accountQuotaCache.delete(key);
|
|
350
|
+
removed += 1;
|
|
351
|
+
}
|
|
352
|
+
return removed;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export function reconcileProviderAccountQuotaRows(context: GenerationContext): number {
|
|
356
|
+
if (context.generation <= lastReconciledGeneration) return 0;
|
|
357
|
+
let removed = 0;
|
|
358
|
+
for (const key of accountQuotaCache.keys()) {
|
|
359
|
+
if (context.oauthAccountKeys.has(key)) continue;
|
|
360
|
+
accountQuotaCache.delete(key);
|
|
361
|
+
removed += 1;
|
|
362
|
+
}
|
|
363
|
+
if (cache) {
|
|
364
|
+
const reports = cache.response.reports.filter(report => context.providerNames.has(report.provider));
|
|
365
|
+
removed += cache.response.reports.length - reports.length;
|
|
366
|
+
cache = { ...cache, response: { ...cache.response, reports } };
|
|
367
|
+
}
|
|
368
|
+
liveAccountQuotaKeys = new Set(context.oauthAccountKeys);
|
|
369
|
+
liveProviderQuotaKeys = new Set(context.providerNames);
|
|
370
|
+
lastReconciledGeneration = context.generation;
|
|
371
|
+
return removed;
|
|
372
|
+
}
|
|
373
|
+
|
|
327
374
|
/** Drop cached per-account rows (all, or just one provider's). */
|
|
328
375
|
export function clearAccountQuotaCache(provider?: string): void {
|
|
329
376
|
if (!provider) {
|
|
@@ -369,6 +416,7 @@ async function fetchAccountQuota(
|
|
|
369
416
|
forceRefresh: boolean,
|
|
370
417
|
): Promise<AccountQuotaCacheEntry> {
|
|
371
418
|
const key = accountCacheKey(provider, accountId);
|
|
419
|
+
const writerGeneration = captureConfigGeneration();
|
|
372
420
|
const cached = accountQuotaCache.get(key);
|
|
373
421
|
if (!forceRefresh && cached && Date.now() - cached.ts < ACCOUNT_QUOTA_TTL_MS) return cached;
|
|
374
422
|
const joinable = accountQuotaInflight.get(key);
|
|
@@ -386,11 +434,17 @@ async function fetchAccountQuota(
|
|
|
386
434
|
quota: cached?.quota ?? null,
|
|
387
435
|
unavailable: true,
|
|
388
436
|
};
|
|
389
|
-
|
|
437
|
+
if (mayCommitAccountQuotaKey(key, writerGeneration)) {
|
|
438
|
+
accountQuotaCache.set(key, entry);
|
|
439
|
+
sweepExpiredOnWrite(entry.ts);
|
|
440
|
+
}
|
|
390
441
|
return entry;
|
|
391
442
|
}
|
|
392
443
|
const entry: AccountQuotaCacheEntry = { ts: Date.now(), quota };
|
|
393
|
-
|
|
444
|
+
if (mayCommitAccountQuotaKey(key, writerGeneration)) {
|
|
445
|
+
accountQuotaCache.set(key, entry);
|
|
446
|
+
sweepExpiredOnWrite(entry.ts);
|
|
447
|
+
}
|
|
394
448
|
return entry;
|
|
395
449
|
} catch {
|
|
396
450
|
const entry: AccountQuotaCacheEntry = {
|
|
@@ -398,7 +452,10 @@ async function fetchAccountQuota(
|
|
|
398
452
|
quota: cached?.quota ?? null,
|
|
399
453
|
unavailable: true,
|
|
400
454
|
};
|
|
401
|
-
|
|
455
|
+
if (mayCommitAccountQuotaKey(key, writerGeneration)) {
|
|
456
|
+
accountQuotaCache.set(key, entry);
|
|
457
|
+
sweepExpiredOnWrite(entry.ts);
|
|
458
|
+
}
|
|
402
459
|
return entry;
|
|
403
460
|
}
|
|
404
461
|
})().finally(() => {
|
|
@@ -861,6 +918,7 @@ async function maybeFetchProviderQuota(
|
|
|
861
918
|
|
|
862
919
|
export async function fetchProviderQuotaReports(config: OcxConfig, forceRefresh = false): Promise<ProviderQuotaResponse> {
|
|
863
920
|
const key = cacheKey(config);
|
|
921
|
+
const writerGeneration = captureConfigGeneration();
|
|
864
922
|
const now = Date.now();
|
|
865
923
|
// The cache fast path must not extend a preserved last-good row past its 30-minute bound:
|
|
866
924
|
// a row preserved at age 29:59 plus a full 5-minute TTL would otherwise serve until ~35min.
|
|
@@ -893,7 +951,10 @@ export async function fetchProviderQuotaReports(config: OcxConfig, forceRefresh
|
|
|
893
951
|
|
|
894
952
|
const response = { generatedAt: Date.now(), reports: [...byProvider.values()] };
|
|
895
953
|
// Commit only when this probe still holds authority (no clear/force superseded it).
|
|
896
|
-
if (epoch === invalidationEpoch)
|
|
954
|
+
if (epoch === invalidationEpoch) {
|
|
955
|
+
const reports = response.reports.filter(item => mayCommitProviderQuotaKey(item.provider, writerGeneration));
|
|
956
|
+
cache = { key, ts: Date.now(), response: { ...response, reports } };
|
|
957
|
+
}
|
|
897
958
|
return response;
|
|
898
959
|
})();
|
|
899
960
|
|
|
@@ -5,6 +5,7 @@ import type { ProviderBaseUrlChoice } from "./base-url-choices";
|
|
|
5
5
|
import {
|
|
6
6
|
QWEN_CLOUD_BASE_URL_CHOICES, QWEN_CLOUD_TOKEN_PLAN_BASE_URL,
|
|
7
7
|
ALIBABA_INTL_BASE_URL_CHOICES, ALIBABA_INTL_TOKEN_PLAN_BASE_URL,
|
|
8
|
+
ALIBABA_CODING_BASE_URL_CHOICES, ALIBABA_CODING_INTL_BASE_URL,
|
|
8
9
|
} from "./base-url-choices";
|
|
9
10
|
import {
|
|
10
11
|
CURSOR_STATIC_MODELS,
|
|
@@ -17,6 +18,19 @@ import {
|
|
|
17
18
|
export type ProviderAuthKind = "forward" | "oauth" | "key" | "local";
|
|
18
19
|
export type MetadataModelIdNormalize = "case-insensitive";
|
|
19
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Wire protocol a client spoke when it reached the proxy. Chat and Anthropic surfaces
|
|
23
|
+
* translate into a Responses-shaped body and replay through `handleResponses`, so the
|
|
24
|
+
* original inbound has to travel with the request or the replay looks native.
|
|
25
|
+
*/
|
|
26
|
+
export type InboundWire = "responses" | "chat" | "anthropic";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A per-model wire default: a bare string applies to every inbound, while the object
|
|
30
|
+
* form applies only to the listed inbound protocols.
|
|
31
|
+
*/
|
|
32
|
+
export type ModelWireDefault = string | { wire: string; inbound: readonly InboundWire[] };
|
|
33
|
+
|
|
20
34
|
export type ProviderModelDiscoveryScalar = string | number | boolean;
|
|
21
35
|
|
|
22
36
|
export type ProviderModelDiscoveryPredicate =
|
|
@@ -123,6 +137,30 @@ export interface ProviderRegistryEntry {
|
|
|
123
137
|
defaultModel?: string;
|
|
124
138
|
models?: string[];
|
|
125
139
|
liveModels?: boolean;
|
|
140
|
+
/**
|
|
141
|
+
* Registry-only per-model wire defaults for mixed OpenAI-compatible gateways.
|
|
142
|
+
* These are intentionally not seeded into saved config: an explicit `modelAdapters`
|
|
143
|
+
* entry must remain distinguishable and must always win over a default.
|
|
144
|
+
*
|
|
145
|
+
* A bare string applies to every inbound protocol. The object form scopes the
|
|
146
|
+
* default to the inbound surfaces named in `inbound`, which is how a model that is
|
|
147
|
+
* native on two wires can serve each client on the wire it already speaks instead
|
|
148
|
+
* of paying a translation hop.
|
|
149
|
+
*/
|
|
150
|
+
modelWireDefaults?: Record<string, ModelWireDefault>;
|
|
151
|
+
/**
|
|
152
|
+
* Responses-API resource path for providers whose route is not `/v1/responses`.
|
|
153
|
+
* Unlike `modelWireDefaults` above, this IS seeded into saved config: it describes
|
|
154
|
+
* the provider's fixed endpoint rather than a default a user might want to override
|
|
155
|
+
* per model. DeepSeek documents `POST /responses` with no `/v1` segment.
|
|
156
|
+
*/
|
|
157
|
+
responsesPath?: string;
|
|
158
|
+
/**
|
|
159
|
+
* Responses upstream that stores nothing server-side. Stateful request parameters
|
|
160
|
+
* are dropped and `store` is pinned false, and orphaned tool results left by a
|
|
161
|
+
* replay miss are repaired rather than forwarded.
|
|
162
|
+
*/
|
|
163
|
+
statelessResponses?: boolean;
|
|
126
164
|
modelDiscovery?: ProviderModelDiscoverySpec;
|
|
127
165
|
contextWindow?: number;
|
|
128
166
|
modelContextWindows?: Record<string, number>;
|
|
@@ -162,7 +200,7 @@ export interface ProviderRegistryEntry {
|
|
|
162
200
|
|
|
163
201
|
export type ProviderConfigSeed = Pick<
|
|
164
202
|
OcxProviderConfig,
|
|
165
|
-
"adapter" | "baseUrl" | "apiKeyTransport" | "authMode" | "keyOptional" | "freeTier" | "modelSuffixBracketStrip" | "defaultModel" | "models"
|
|
203
|
+
"adapter" | "baseUrl" | "apiKeyTransport" | "responsesPath" | "authMode" | "keyOptional" | "freeTier" | "modelSuffixBracketStrip" | "defaultModel" | "models"
|
|
166
204
|
| "liveModels" | "contextWindow" | "modelContextWindows" | "modelInputModalities"
|
|
167
205
|
| "modelMaxInputTokens" | "defaultMaxOutputTokens" | "modelMaxOutputTokens"
|
|
168
206
|
| "reasoningEfforts" | "modelReasoningEfforts" | "modelDefaultReasoningEfforts" | "reasoningEffortMap" | "modelReasoningEffortMap"
|
|
@@ -326,6 +364,67 @@ const ALIBABA_INTL_TOKEN_PLAN_QWEN_MODELS = [
|
|
|
326
364
|
// coding tools (not custom application backends or non-interactive batch automation).
|
|
327
365
|
// Evidence: https://cloud.tencent.cn/document/product/1823/130092
|
|
328
366
|
const TENCENT_CODING_PLAN_MODELS = ["tc-code-latest", "glm-5", "kimi-k2.5", "minimax-m2.5"];
|
|
367
|
+
// Volcengine's authenticated /api/v3/models catalog mixes chat models with embedding,
|
|
368
|
+
// image, video, and 3D generation resources. Keep the Codex-facing presets scoped to
|
|
369
|
+
// models documented for text/agent or Coding Plan use.
|
|
370
|
+
//
|
|
371
|
+
// Maintenance owner: @lidge-jun. Verified 2026-08-01 against the vendor's own docs —
|
|
372
|
+
// endpoints https://docs.volcengine.com/docs/82379/1528783 (Coding Plan) and
|
|
373
|
+
// https://docs.volcengine.com/docs/82379/2165245 (Agent Plan); Codex CLI integration
|
|
374
|
+
// https://www.volcengine.com/docs/82379/2556056; supported clients
|
|
375
|
+
// https://www.volcengine.com/docs/82379/2188957; terms https://www.volcengine.com/docs/6256/64903
|
|
376
|
+
// (北京火山引擎科技有限公司). Plan quota is restricted to supported AI coding tools and misuse
|
|
377
|
+
// is documented as grounds for suspension — see the `note` on both Plan entries.
|
|
378
|
+
// Report a break by opening an issue tagging the owner; the three things that rot first are the
|
|
379
|
+
// static catalogs (liveModels:false cannot self-heal), the base URLs, and those Plan terms.
|
|
380
|
+
// Full evidence ledger: devlog/_plan/260801_pr611_volcengine_evidence/000_evidence_ledger.md
|
|
381
|
+
const VOLCENGINE_ARK_MODELS = [
|
|
382
|
+
"doubao-seed-2-1-pro-260628",
|
|
383
|
+
"doubao-seed-2-1-turbo-260628",
|
|
384
|
+
"doubao-seed-evolving",
|
|
385
|
+
"deepseek-v4-pro-260425",
|
|
386
|
+
"deepseek-v4-flash-260425",
|
|
387
|
+
"deepseek-v3-2-251201",
|
|
388
|
+
"glm-5-2-260617",
|
|
389
|
+
"glm-4-7-251222",
|
|
390
|
+
];
|
|
391
|
+
const VOLCENGINE_DOUBAO_THINKING_MODELS = [
|
|
392
|
+
"doubao-seed-2-1-pro-260628",
|
|
393
|
+
"doubao-seed-2-1-turbo-260628",
|
|
394
|
+
"doubao-seed-evolving",
|
|
395
|
+
];
|
|
396
|
+
const VOLCENGINE_CODING_PLAN_MODELS = [
|
|
397
|
+
"ark-code-latest",
|
|
398
|
+
"doubao-seed-2.0-code",
|
|
399
|
+
"deepseek-v4-pro",
|
|
400
|
+
"deepseek-v4-flash",
|
|
401
|
+
"glm-5.2",
|
|
402
|
+
"kimi-k2.6",
|
|
403
|
+
"minimax-m3",
|
|
404
|
+
];
|
|
405
|
+
const VOLCENGINE_AGENT_PLAN_MODELS = [
|
|
406
|
+
"deepseek-v4-pro",
|
|
407
|
+
"deepseek-v4-flash",
|
|
408
|
+
"glm-5.2",
|
|
409
|
+
"kimi-k2.6",
|
|
410
|
+
"minimax-m3",
|
|
411
|
+
"doubao-seed-2.0-pro",
|
|
412
|
+
];
|
|
413
|
+
const VOLCENGINE_PLAN_INPUT_MODALITIES: Record<string, string[]> = {
|
|
414
|
+
"kimi-k2.6": ["text", "image"],
|
|
415
|
+
"minimax-m3": ["text", "image"],
|
|
416
|
+
};
|
|
417
|
+
// Every other Plan model is text-only. Declaring this explicitly keeps the vision
|
|
418
|
+
// sidecar from advertising image input for models that cannot accept it — the same
|
|
419
|
+
// treatment tencent-coding-plan gives its (entirely text-only) plan catalog.
|
|
420
|
+
const VOLCENGINE_PLAN_TEXT_ONLY_MODELS = [
|
|
421
|
+
"ark-code-latest",
|
|
422
|
+
"doubao-seed-2.0-code",
|
|
423
|
+
"deepseek-v4-pro",
|
|
424
|
+
"deepseek-v4-flash",
|
|
425
|
+
"glm-5.2",
|
|
426
|
+
"doubao-seed-2.0-pro",
|
|
427
|
+
];
|
|
329
428
|
const ALIBABA_INTL_TOKEN_PLAN_INPUT_MODALITIES: Record<string, string[]> = {
|
|
330
429
|
"qwen3.8-max-preview": ["text", "image"],
|
|
331
430
|
"qwen3.7-max": ["text", "image"],
|
|
@@ -796,7 +895,7 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
796
895
|
// 2026-07-10: defaultModel is frozen pending Vertex-specific Tier-2 evidence; Gemini API
|
|
797
896
|
// evidence from ai.google.dev does not establish Vertex publisher availability.
|
|
798
897
|
{ id: "google-vertex", label: "Google Vertex AI", adapter: "google", baseUrl: "https://aiplatform.googleapis.com", authKind: "key", dashboardUrl: "https://console.cloud.google.com/vertex-ai", defaultModel: "gemini-3-pro", googleMode: "vertex", jawcodeBundle: "google", extraMetadataAliases: ["gemini-vertex"] },
|
|
799
|
-
{ id: "google-antigravity", label: "Google Antigravity", adapter: "google", baseUrl: "https://daily-cloudcode-pa.googleapis.com", authKind: "oauth", dashboardUrl: "https://antigravity.google", models: ANTIGRAVITY_MODELS, defaultModel: "gemini-3.6-flash", modelContextWindows: ANTIGRAVITY_MODEL_CONTEXT_WINDOWS, modelReasoningEfforts: ANTIGRAVITY_MODEL_EFFORTS, googleMode: "cloud-code-assist", jawcodeBundle: "google", extraMetadataAliases: ["antigravity", "gemini-antigravity"] },
|
|
898
|
+
{ id: "google-antigravity", label: "Google Antigravity", adapter: "google", baseUrl: "https://daily-cloudcode-pa.googleapis.com", authKind: "oauth", dashboardUrl: "https://antigravity.google", models: ANTIGRAVITY_MODELS, liveModels: false, defaultModel: "gemini-3.6-flash", modelContextWindows: ANTIGRAVITY_MODEL_CONTEXT_WINDOWS, modelReasoningEfforts: ANTIGRAVITY_MODEL_EFFORTS, googleMode: "cloud-code-assist", jawcodeBundle: "google", extraMetadataAliases: ["antigravity", "gemini-antigravity"] },
|
|
800
899
|
{ id: "azure-openai", label: "Azure OpenAI", adapter: "azure-openai", baseUrl: "https://{resource}.openai.azure.com/openai", authKind: "key", featured: true, dashboardUrl: "https://portal.azure.com" },
|
|
801
900
|
{ id: "ollama", label: "Ollama (local)", adapter: "openai-chat", baseUrl: "http://localhost:11434/v1", authKind: "local", allowPrivateNetworkByDefault: true, allowBaseUrlOverride: true, featured: true, note: "Local — key usually blank" },
|
|
802
901
|
{ id: "vllm", label: "vLLM (local)", adapter: "openai-chat", baseUrl: "http://localhost:8000/v1", authKind: "local", allowPrivateNetworkByDefault: true, allowBaseUrlOverride: true, featured: true, note: "Local — key usually blank" },
|
|
@@ -813,6 +912,25 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
813
912
|
models: ["deepseek-chat", "deepseek-reasoner", ...DEEPSEEK_THINKING_MODELS],
|
|
814
913
|
defaultModel: "deepseek-v4-flash",
|
|
815
914
|
modelContextWindows: { "deepseek-v4-flash": 1_000_000, "deepseek-v4-pro": 1_000_000 },
|
|
915
|
+
// DeepSeek documents V4-Flash as a native Responses API model adapted for Codex. The
|
|
916
|
+
// API id is `deepseek-v4-flash`; `DeepSeek-V4-Flash-0731` is a release/version label.
|
|
917
|
+
modelWireDefaults: {
|
|
918
|
+
// Codex speaks Responses natively and DeepSeek ships a Codex-compatible
|
|
919
|
+
// apply_patch tool on that wire, so a Responses inbound goes straight out with
|
|
920
|
+
// no translation. Claude Code and OpenAI-compatible clients keep the
|
|
921
|
+
// provider-wide Chat wire: DeepSeek serves Chat Completions natively too, so
|
|
922
|
+
// translating them into Responses would add a hop onto our newest upstream path
|
|
923
|
+
// for no gain.
|
|
924
|
+
"deepseek-v4-flash": { wire: "openai-responses", inbound: ["responses"] },
|
|
925
|
+
},
|
|
926
|
+
// DeepSeek's Responses route is `POST /responses` with no `/v1` segment. Without
|
|
927
|
+
// this the passthrough adapter falls back to its legacy `/v1/responses`
|
|
928
|
+
// construction and the wire above can never route.
|
|
929
|
+
// Evidence: https://api-docs.deepseek.com/api/create-response/
|
|
930
|
+
responsesPath: "/responses",
|
|
931
|
+
// "The API is stateless: responses and conversations are not stored on the
|
|
932
|
+
// server." https://api-docs.deepseek.com/api/create-response/
|
|
933
|
+
statelessResponses: true,
|
|
816
934
|
/* [Decision Log]
|
|
817
935
|
- 목적: DeepSeek V4 thinking mode multi-turn/tool-call requests must replay prior assistant reasoning_content.
|
|
818
936
|
- 대안 분석: Globally preserve reasoning_content for all OpenAI-compatible models; preserve it for legacy deepseek-reasoner too; mark only V4 thinking models in registry metadata.
|
|
@@ -828,6 +946,43 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
828
946
|
},
|
|
829
947
|
// llama-3.3-70b was deprecated by Cerebras on 2026-02-16. Evidence: devlog/_plan/260710_provider_hardening/003_research_aggregators.md.
|
|
830
948
|
{ id: "cerebras", label: "Cerebras", baseUrl: "https://api.cerebras.ai/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://cloud.cerebras.ai/platform/apikeys", defaultModel: "gpt-oss-120b" },
|
|
949
|
+
{
|
|
950
|
+
id: "deepinfra",
|
|
951
|
+
label: "DeepInfra",
|
|
952
|
+
baseUrl: "https://api.deepinfra.com/v1/openai",
|
|
953
|
+
adapter: "openai-chat",
|
|
954
|
+
authKind: "key",
|
|
955
|
+
dashboardUrl: "https://deepinfra.com/dash/api_keys",
|
|
956
|
+
liveModels: true,
|
|
957
|
+
preserveCustomDestination: true,
|
|
958
|
+
modelDiscovery: {
|
|
959
|
+
// DeepInfra documents the OpenAI model catalog outside the chat-compatible `/v1/openai`
|
|
960
|
+
// namespace, so keep this destination registry-owned instead of deriving it from baseUrl.
|
|
961
|
+
url: "https://api.deepinfra.com/v1/models",
|
|
962
|
+
maxResponseBytes: 512 * 1024,
|
|
963
|
+
maxModels: 512,
|
|
964
|
+
filter: {
|
|
965
|
+
allOf: [{ path: ["metadata", "tags"], containsAny: ["chat"] }],
|
|
966
|
+
},
|
|
967
|
+
},
|
|
968
|
+
note: "OpenAI-compatible chat models only; live discovery excludes non-chat rows from DeepInfra's mixed model catalog.",
|
|
969
|
+
},
|
|
970
|
+
{
|
|
971
|
+
id: "hyperbolic",
|
|
972
|
+
label: "Hyperbolic",
|
|
973
|
+
baseUrl: "https://api.hyperbolic.xyz/v1",
|
|
974
|
+
adapter: "openai-chat",
|
|
975
|
+
authKind: "key",
|
|
976
|
+
dashboardUrl: "https://app.hyperbolic.ai",
|
|
977
|
+
liveModels: true,
|
|
978
|
+
preserveCustomDestination: true,
|
|
979
|
+
modelDiscovery: {
|
|
980
|
+
path: "models",
|
|
981
|
+
maxResponseBytes: 256 * 1024,
|
|
982
|
+
maxModels: 256,
|
|
983
|
+
},
|
|
984
|
+
note: "Serverless text and vision-language chat models only; Hyperbolic's separate image, audio, and GPU endpoints are out of scope.",
|
|
985
|
+
},
|
|
831
986
|
// FREEZE 2026-07-10: exact serverless ids remain auth-gated/unverified. Evidence: devlog/_plan/260710_provider_hardening/003_research_aggregators.md.
|
|
832
987
|
{ id: "together", label: "Together", baseUrl: "https://api.together.xyz/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://api.together.xyz/settings/api-keys" },
|
|
833
988
|
{ id: "fireworks", label: "Fireworks", baseUrl: "https://api.fireworks.ai/inference/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://fireworks.ai/account/api-keys" },
|
|
@@ -966,10 +1121,81 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
966
1121
|
noVisionModels: TENCENT_CODING_PLAN_MODELS,
|
|
967
1122
|
note: "Coding tools only. Tencent forbids general API automation, custom backends, and non-interactive batch use.",
|
|
968
1123
|
},
|
|
1124
|
+
{
|
|
1125
|
+
id: "volcengine",
|
|
1126
|
+
label: "Volcengine Ark",
|
|
1127
|
+
baseUrl: "https://ark.cn-beijing.volces.com/api/v3",
|
|
1128
|
+
adapter: "openai-chat",
|
|
1129
|
+
authKind: "key",
|
|
1130
|
+
preserveCustomDestination: true,
|
|
1131
|
+
dashboardUrl: "https://console.volcengine.com/ark/region:ark+cn-beijing/apikey",
|
|
1132
|
+
defaultModel: "doubao-seed-2-1-pro-260628",
|
|
1133
|
+
models: VOLCENGINE_ARK_MODELS,
|
|
1134
|
+
liveModels: false,
|
|
1135
|
+
modelReasoningEfforts: Object.fromEntries(
|
|
1136
|
+
VOLCENGINE_DOUBAO_THINKING_MODELS.map(id => [id, THINKING_TOGGLE_EFFORTS]),
|
|
1137
|
+
),
|
|
1138
|
+
modelReasoningEffortMap: Object.fromEntries(
|
|
1139
|
+
VOLCENGINE_DOUBAO_THINKING_MODELS.map(id => [id, THINKING_TOGGLE_MAP]),
|
|
1140
|
+
),
|
|
1141
|
+
thinkingToggleModels: VOLCENGINE_DOUBAO_THINKING_MODELS,
|
|
1142
|
+
preserveReasoningContentModels: [
|
|
1143
|
+
"deepseek-v4-pro-260425",
|
|
1144
|
+
"deepseek-v4-flash-260425",
|
|
1145
|
+
"glm-5-2-260617",
|
|
1146
|
+
"glm-4-7-251222",
|
|
1147
|
+
],
|
|
1148
|
+
noVisionModels: [
|
|
1149
|
+
"deepseek-v4-pro-260425",
|
|
1150
|
+
"deepseek-v4-flash-260425",
|
|
1151
|
+
"deepseek-v3-2-251201",
|
|
1152
|
+
"glm-5-2-260617",
|
|
1153
|
+
"glm-4-7-251222",
|
|
1154
|
+
],
|
|
1155
|
+
note: "Pay-as-you-go Ark API with a curated text/agent catalog. Calls on this endpoint do not consume Coding Plan or Agent Plan quota.",
|
|
1156
|
+
},
|
|
1157
|
+
{
|
|
1158
|
+
id: "volcengine-coding-plan",
|
|
1159
|
+
label: "Volcengine Ark Coding Plan",
|
|
1160
|
+
baseUrl: "https://ark.cn-beijing.volces.com/api/coding/v3",
|
|
1161
|
+
adapter: "openai-chat",
|
|
1162
|
+
authKind: "key",
|
|
1163
|
+
preserveCustomDestination: true,
|
|
1164
|
+
dashboardUrl: "https://console.volcengine.com/ark/region:ark+cn-beijing/overview",
|
|
1165
|
+
defaultModel: "ark-code-latest",
|
|
1166
|
+
models: VOLCENGINE_CODING_PLAN_MODELS,
|
|
1167
|
+
liveModels: false,
|
|
1168
|
+
modelInputModalities: VOLCENGINE_PLAN_INPUT_MODALITIES,
|
|
1169
|
+
noVisionModels: VOLCENGINE_PLAN_TEXT_ONLY_MODELS,
|
|
1170
|
+
modelReasoningEfforts: Object.fromEntries(
|
|
1171
|
+
DEEPSEEK_THINKING_MODELS.map(id => [id, DEEPSEEK_THINKING_EFFORTS]),
|
|
1172
|
+
),
|
|
1173
|
+
modelReasoningEffortMap: Object.fromEntries(
|
|
1174
|
+
DEEPSEEK_THINKING_MODELS.map(id => [id, DEEPSEEK_THINKING_REASONING_MAP]),
|
|
1175
|
+
),
|
|
1176
|
+
preserveReasoningContentModels: DEEPSEEK_THINKING_MODELS,
|
|
1177
|
+
note: "Coding tools only. Volcengine restricts Coding Plan quota to supported AI coding tools and warns that using this key for general API calls may suspend the subscription or ban the account. Use the plan key issued by the Ark console.",
|
|
1178
|
+
},
|
|
1179
|
+
{
|
|
1180
|
+
id: "volcengine-agent-plan",
|
|
1181
|
+
label: "Volcengine Ark Agent Plan",
|
|
1182
|
+
baseUrl: "https://ark.cn-beijing.volces.com/api/plan/v3",
|
|
1183
|
+
responsesPath: "/responses",
|
|
1184
|
+
adapter: "openai-responses",
|
|
1185
|
+
authKind: "key",
|
|
1186
|
+
preserveCustomDestination: true,
|
|
1187
|
+
dashboardUrl: "https://console.volcengine.com/ark/region:ark+cn-beijing/overview",
|
|
1188
|
+
defaultModel: "deepseek-v4-pro",
|
|
1189
|
+
models: VOLCENGINE_AGENT_PLAN_MODELS,
|
|
1190
|
+
liveModels: false,
|
|
1191
|
+
modelInputModalities: VOLCENGINE_PLAN_INPUT_MODALITIES,
|
|
1192
|
+
noVisionModels: VOLCENGINE_PLAN_TEXT_ONLY_MODELS,
|
|
1193
|
+
note: "Coding tools only. Agent Plan is a subscription endpoint over the native Responses API with a static fallback catalog; Ark plan quota is intended for supported AI coding and agent tools, so avoid using this key as a general-purpose API key.",
|
|
1194
|
+
},
|
|
969
1195
|
// 2026-07-10: docs unverified; model data frozen. Evidence: devlog/_plan/260710_provider_hardening/002_research_cn.md.
|
|
970
1196
|
{ id: "qianfan", label: "Qianfan (Baidu)", baseUrl: "https://qianfan.baidubce.com/v2", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://console.bce.baidu.com/iam/#/iam/apikey/list" },
|
|
971
1197
|
// 2026-07-10: docs unverified; model data frozen. Evidence: devlog/_plan/260710_provider_hardening/002_research_cn.md.
|
|
972
|
-
{ id: "alibaba", label: "Alibaba Coding Plan", baseUrl:
|
|
1198
|
+
{ id: "alibaba", label: "Alibaba Coding Plan", baseUrl: ALIBABA_CODING_INTL_BASE_URL, adapter: "openai-chat", authKind: "key", allowBaseUrlOverride: true, baseUrlChoices: ALIBABA_CODING_BASE_URL_CHOICES, dashboardUrl: "https://dashscope.console.aliyun.com/apiKey" },
|
|
973
1199
|
{
|
|
974
1200
|
id: "alibaba-token-plan",
|
|
975
1201
|
label: "Alibaba Token Plan (Beijing)",
|
|
@@ -1235,6 +1461,56 @@ export function providerMatchesRegistryTransport(
|
|
|
1235
1461
|
return normalizedProviderEndpoint(provider.baseUrl) === normalizedProviderEndpoint(entry.baseUrl);
|
|
1236
1462
|
}
|
|
1237
1463
|
|
|
1464
|
+
/**
|
|
1465
|
+
* Resolve the registry entry a configured provider actually points at, by TRANSPORT
|
|
1466
|
+
* rather than by name.
|
|
1467
|
+
*
|
|
1468
|
+
* `providerMatchesRegistryTransport` answers "does the row named X still point at X's
|
|
1469
|
+
* documented destination", which is the right question for routing but the wrong one
|
|
1470
|
+
* for user-facing metadata: the GUI lets a preset be saved under any name, and a
|
|
1471
|
+
* renamed row would silently lose a usage restriction it still needs to display.
|
|
1472
|
+
*
|
|
1473
|
+
* Only fixed key destinations are matched. Entries with an overridable or templated
|
|
1474
|
+
* base URL are skipped, because their configured URL cannot identify one vendor route.
|
|
1475
|
+
*/
|
|
1476
|
+
export function registryEntryForProviderDestination(
|
|
1477
|
+
provider: Pick<OcxProviderConfig, "baseUrl" | "adapter"> & Partial<Pick<OcxProviderConfig, "authMode">>,
|
|
1478
|
+
): ProviderRegistryEntry | undefined {
|
|
1479
|
+
if (typeof provider.baseUrl !== "string" || !provider.baseUrl) return undefined;
|
|
1480
|
+
if (provider.authMode !== undefined && provider.authMode !== "key") return undefined;
|
|
1481
|
+
const endpoint = normalizedProviderEndpoint(provider.baseUrl);
|
|
1482
|
+
return PROVIDER_REGISTRY.find(entry =>
|
|
1483
|
+
entry.authKind === "key"
|
|
1484
|
+
&& !entry.allowBaseUrlOverride
|
|
1485
|
+
&& !/\{[^}]*\}/.test(entry.baseUrl)
|
|
1486
|
+
&& entry.adapter === provider.adapter
|
|
1487
|
+
&& normalizedProviderEndpoint(entry.baseUrl) === endpoint);
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
/**
|
|
1491
|
+
* Resolve a registry-only default for a mixed-wire provider. Defaults only move a provider
|
|
1492
|
+
* between the two OpenAI-shaped adapters and never override a provider configured on another
|
|
1493
|
+
* wire. The resolver receives the allow-list so this helper cannot accidentally widen the
|
|
1494
|
+
* adapter-selection boundary when a new registry entry is added.
|
|
1495
|
+
*/
|
|
1496
|
+
export function providerModelWireDefault(
|
|
1497
|
+
id: string,
|
|
1498
|
+
provider: Pick<OcxProviderConfig, "baseUrl" | "adapter"> & Partial<Pick<OcxProviderConfig, "authMode">>,
|
|
1499
|
+
modelId: string,
|
|
1500
|
+
allowedWires: ReadonlySet<string>,
|
|
1501
|
+
inbound: InboundWire,
|
|
1502
|
+
): string | undefined {
|
|
1503
|
+
if (!allowedWires.has(provider.adapter)) return undefined;
|
|
1504
|
+
const entry = getProviderRegistryEntry(id);
|
|
1505
|
+
if (!entry?.modelWireDefaults || !providerMatchesRegistryTransport(id, provider)) return undefined;
|
|
1506
|
+
const declared = entry.modelWireDefaults[modelId.trim().toLowerCase()];
|
|
1507
|
+
if (declared === undefined) return undefined;
|
|
1508
|
+
// A bare string applies to every inbound; the object form only to the listed ones.
|
|
1509
|
+
if (typeof declared !== "string" && !declared.inbound.includes(inbound)) return undefined;
|
|
1510
|
+
const wire = typeof declared === "string" ? declared : declared.wire;
|
|
1511
|
+
return wire !== undefined && allowedWires.has(wire) ? wire : undefined;
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1238
1514
|
/**
|
|
1239
1515
|
* Effective Codex account mode for a provider. For canonical `openai`, a valid persisted
|
|
1240
1516
|
* `codexAccountMode` on the provider config wins and a missing/invalid value defaults to
|
package/src/responses/parser.ts
CHANGED
|
@@ -137,11 +137,15 @@ function allowedToolName(tool: unknown): string | undefined {
|
|
|
137
137
|
function buildTools(tools: unknown[] | undefined): OcxTool[] | undefined {
|
|
138
138
|
if (!tools) return undefined;
|
|
139
139
|
const out: OcxTool[] = [];
|
|
140
|
+
const normalizeParameters = (raw: unknown): Record<string, unknown> => {
|
|
141
|
+
if (isObj(raw) && raw.type === "object") return raw;
|
|
142
|
+
return { ...(isObj(raw) ? raw : {}), type: "object" };
|
|
143
|
+
};
|
|
140
144
|
const pushFn = (t: Record<string, unknown>, namespace?: string) => {
|
|
141
145
|
const tool: OcxTool = {
|
|
142
146
|
name: t.name as string,
|
|
143
147
|
description: (t.description as string) ?? "",
|
|
144
|
-
parameters: (t.parameters
|
|
148
|
+
parameters: normalizeParameters(t.parameters),
|
|
145
149
|
};
|
|
146
150
|
if (t.strict !== undefined) tool.strict = t.strict as boolean;
|
|
147
151
|
if (namespace) tool.namespace = namespace;
|