@bitkyc08/opencodex 2.10.1 → 2.10.2
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/bin/ocx.mjs +18 -9
- package/gui/dist/assets/index-BKVqyYqT.js +70 -0
- package/gui/dist/assets/{index-Cd6_PBKn.css → index-Ca_3269W.css} +1 -1
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/commandcode-color.svg +1 -0
- package/gui/dist/provider-icons/openai.svg +1 -1
- package/package.json +1 -1
- package/src/adapters/command-code.ts +453 -0
- package/src/adapters/google.ts +3 -0
- package/src/cli/claude.ts +37 -22
- package/src/cli/index.ts +16 -3
- package/src/cli/launcher-context.ts +77 -0
- package/src/codex/admission.ts +1 -1
- package/src/codex/app-server-processes.ts +44 -1
- package/src/codex/catalog/sync.ts +22 -3
- package/src/codex/catalog-write-serialization.ts +1 -1
- package/src/codex/codex-write-lock.ts +1 -1
- package/src/codex/convergence-types.ts +1 -1
- package/src/codex/desired-state.ts +27 -7
- package/src/codex/history-job.ts +1 -1
- package/src/codex/history-lock.ts +1 -1
- package/src/codex/history-worker.ts +1 -1
- package/src/codex/internal/history-writer.ts +1 -1
- package/src/codex/transition-state.ts +1 -1
- package/src/codex/user-identity.ts +1 -1
- package/src/config.ts +6 -1
- package/src/integrations/config-io.ts +1 -1
- package/src/integrations/journal.ts +1 -1
- package/src/integrations/merge.ts +1 -1
- package/src/integrations/ownership.ts +1 -1
- package/src/integrations/registry.ts +1 -1
- package/src/integrations/serialize.ts +1 -1
- package/src/integrations/state.ts +1 -1
- package/src/integrations/store.ts +1 -1
- package/src/integrations/writer.ts +1 -1
- package/src/lib/bounded-body.ts +3 -1
- package/src/lib/bun-runtime.ts +21 -17
- package/src/lib/bun-stream-caps.ts +1 -1
- package/src/lib/local-management-attestation.ts +51 -0
- package/src/lib/shadow-call.ts +4 -4
- package/src/oauth/command-code.ts +239 -0
- package/src/oauth/health.ts +46 -2
- package/src/oauth/index.ts +38 -3
- package/src/providers/command-code-efforts.ts +85 -0
- package/src/providers/google-vertex-location.ts +14 -0
- package/src/providers/registry.ts +138 -26
- package/src/routing/capability.ts +1 -0
- package/src/server/adapter-resolve.ts +3 -0
- package/src/server/auth-cors.ts +6 -1
- package/src/server/index.ts +44 -2
- package/src/server/management/integration-routes.ts +1 -1
- package/src/server/management/native-integration-routes.ts +2 -2
- package/src/server/responses/core.ts +13 -9
- package/src/storage/scanner.ts +1 -1
- package/src/types.ts +6 -0
- package/src/usage/log.ts +1 -1
- package/gui/dist/assets/index-ChZQsmBY.js +0 -70
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
cursorModelInputModalities,
|
|
15
15
|
cursorModelReasoningEfforts,
|
|
16
16
|
} from "../adapters/cursor/discovery";
|
|
17
|
+
import { COMMAND_CODE_MODEL_REASONING_EFFORTS } from "./command-code-efforts";
|
|
17
18
|
|
|
18
19
|
export type ProviderAuthKind = "forward" | "oauth" | "key" | "local";
|
|
19
20
|
export type MetadataModelIdNormalize = "case-insensitive";
|
|
@@ -348,16 +349,89 @@ const THINKING_BUDGET_MODELS = [
|
|
|
348
349
|
const OPENCODE_GO_THINKING_BUDGET_MODELS = ["qwen3.5-plus", "qwen3.6-plus", "qwen3.7-max", "qwen3.7-plus"];
|
|
349
350
|
const DEEPSEEK_THINKING_MODELS = ["deepseek-v4-pro", "deepseek-v4-flash"];
|
|
350
351
|
const OPENCODE_FREE_DEEPSEEK_MODELS = ["deepseek-v4-flash-free"];
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
352
|
+
/*
|
|
353
|
+
* Zen free models that reject `image_url` upstream (#1043, and the reproducible
|
|
354
|
+
* half of #1024).
|
|
355
|
+
*
|
|
356
|
+
* Zen publishes NO modality metadata — its `/v1/models` returns only id, object,
|
|
357
|
+
* created, owned_by — so this list is measured, not derived. Each id was probed
|
|
358
|
+
* once against https://opencode.ai/zen/v1 on 2026-08-05 with a text control first
|
|
359
|
+
* and then a 1x1 PNG; the six below failed the image request, four of them with
|
|
360
|
+
* `[404] No endpoints found that support image input` and `big-pickle` with the
|
|
361
|
+
* exact deserialize error quoted in #1043.
|
|
362
|
+
*
|
|
363
|
+
* `mimo-v2.5-free` and `longcat-2.0-free` ACCEPT images and are deliberately
|
|
364
|
+
* absent. Adding them would silently replace a working image with a caption,
|
|
365
|
+
* which is worse than the loud 400 this list exists to prevent — see the negative
|
|
366
|
+
* assertion in tests/provider-registry-parity.test.ts.
|
|
367
|
+
*
|
|
368
|
+
* Zen's roster is discovered live while this list is static, so it is a dated
|
|
369
|
+
* exception list, not a capability model. Re-probe before extending it.
|
|
370
|
+
* Evidence: devlog/_plan/260805_bug_fix_stack/002_zen_modality_probe.md
|
|
371
|
+
*/
|
|
372
|
+
const OPENCODE_ZEN_TEXT_ONLY_MODELS = [
|
|
373
|
+
"big-pickle",
|
|
374
|
+
"nemotron-3-ultra-free",
|
|
375
|
+
"ling-3.0-flash-free",
|
|
376
|
+
"north-mini-code-free",
|
|
377
|
+
"laguna-s-2.1-free",
|
|
378
|
+
"deepseek-v4-flash-free",
|
|
379
|
+
];
|
|
380
|
+
/*
|
|
381
|
+
* DeepSeek's Codex ladder is low/high/max, and the two V4 models resolve it
|
|
382
|
+
* DIFFERENTLY. From the official thinking-mode table (api-docs.deepseek.com,
|
|
383
|
+
* EN and zh-cn agree, re-verified 2026-08-06):
|
|
384
|
+
*
|
|
385
|
+
* requested | v4-flash | v4-pro
|
|
386
|
+
* low | low | high
|
|
387
|
+
* high | high | high
|
|
388
|
+
* xhigh | high | max
|
|
389
|
+
* max | max | max
|
|
390
|
+
*
|
|
391
|
+
* Two consequences (#1057):
|
|
392
|
+
*
|
|
393
|
+
* - `xhigh` is a COMPATIBILITY ALIAS, not a native tier. It stays in the wire maps
|
|
394
|
+
* so existing requests and saved configs keep working, but it is not advertised.
|
|
395
|
+
* - Pro does NOT honor `low` — the vendor silently upgrades it to `high`. So Pro
|
|
396
|
+
* advertises only the two levels it actually distinguishes. Advertising `low`
|
|
397
|
+
* there would put a tier in the picker that costs `high`, which is the same
|
|
398
|
+
* defect this fixes wearing a different value.
|
|
399
|
+
*
|
|
400
|
+
* The vendor page footnotes that Pro's mapping updates in early August 2026; as of
|
|
401
|
+
* the re-verification above it had not changed. When it does, Pro gains `low` here.
|
|
402
|
+
*
|
|
403
|
+
* `medium` has no row in the vendor table — mapping it to `high` is OUR
|
|
404
|
+
* compatibility choice for clients that only speak the OpenAI ladder.
|
|
405
|
+
*/
|
|
406
|
+
const DEEPSEEK_FLASH_THINKING_EFFORTS = ["low", "high", "max"];
|
|
407
|
+
const DEEPSEEK_PRO_THINKING_EFFORTS = ["high", "max"];
|
|
408
|
+
const DEEPSEEK_PRO_REASONING_MAP: Record<string, string> = {
|
|
355
409
|
low: "high",
|
|
356
410
|
medium: "high",
|
|
357
411
|
high: "high",
|
|
358
412
|
xhigh: "max",
|
|
359
413
|
max: "max",
|
|
360
414
|
};
|
|
415
|
+
const DEEPSEEK_FLASH_REASONING_MAP: Record<string, string> = {
|
|
416
|
+
low: "low",
|
|
417
|
+
medium: "high",
|
|
418
|
+
high: "high",
|
|
419
|
+
xhigh: "high",
|
|
420
|
+
max: "max",
|
|
421
|
+
};
|
|
422
|
+
/**
|
|
423
|
+
* Flash-versus-Pro classification for DeepSeek V4 model ids, including prefixed
|
|
424
|
+
* (`deepseek/deepseek-v4-pro`) and suffixed (`deepseek-v4-flash-free`) forms.
|
|
425
|
+
* `tests/provider-registry-parity.test.ts` enumerates every id the registry
|
|
426
|
+
* actually passes here, so a future id this substring test would misread cannot
|
|
427
|
+
* land silently.
|
|
428
|
+
*/
|
|
429
|
+
const isDeepseekFlashModel = (modelId: string): boolean =>
|
|
430
|
+
modelId.toLowerCase().includes("flash");
|
|
431
|
+
const deepseekThinkingEffortsFor = (modelId: string): string[] =>
|
|
432
|
+
isDeepseekFlashModel(modelId) ? DEEPSEEK_FLASH_THINKING_EFFORTS : DEEPSEEK_PRO_THINKING_EFFORTS;
|
|
433
|
+
const deepseekReasoningMapFor = (modelId: string): Record<string, string> =>
|
|
434
|
+
isDeepseekFlashModel(modelId) ? DEEPSEEK_FLASH_REASONING_MAP : DEEPSEEK_PRO_REASONING_MAP;
|
|
361
435
|
// 260719 Alibaba Token Plan Personal Edition (China/Beijing). Keep it distinct from
|
|
362
436
|
// Coding Plan: the products use different exact allowlists and different base URLs.
|
|
363
437
|
// Evidence: https://help.aliyun.com/en/model-studio/token-plan-personal-overview
|
|
@@ -412,7 +486,7 @@ const TENCENT_CODING_PLAN_MODELS = ["tc-code-latest", "glm-5", "kimi-k2.5", "min
|
|
|
412
486
|
// is documented as grounds for suspension — see the `note` on both Plan entries.
|
|
413
487
|
// Report a break by opening an issue tagging the owner; the three things that rot first are the
|
|
414
488
|
// static catalogs (liveModels:false cannot self-heal), the base URLs, and those Plan terms.
|
|
415
|
-
// Full evidence ledger: devlog/
|
|
489
|
+
// Full evidence ledger: devlog/_fin/260801_pr611_volcengine_evidence/000_evidence_ledger.md
|
|
416
490
|
const VOLCENGINE_ARK_MODELS = [
|
|
417
491
|
"doubao-seed-2-1-pro-260628",
|
|
418
492
|
"doubao-seed-2-1-turbo-260628",
|
|
@@ -537,7 +611,7 @@ const NVIDIA_NIM_KIMI_MODELS = [
|
|
|
537
611
|
*
|
|
538
612
|
* Two lists, both verified per-model against NVIDIA documentation on 2026-08-04
|
|
539
613
|
* (build.nvidia.com model pages and docs.api.nvidia.com/nim/reference/*). Evidence and
|
|
540
|
-
* the per-id audit: devlog/
|
|
614
|
+
* the per-id audit: devlog/_fin/260804_stack7_service_vision/011_nim_id_audit.md.
|
|
541
615
|
*
|
|
542
616
|
* Read `noVisionModels` carefully — it lists models that CANNOT see images, which is
|
|
543
617
|
* what routes them through the proxy's vision sidecar (src/vision/index.ts) and makes the
|
|
@@ -799,6 +873,32 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
799
873
|
},
|
|
800
874
|
noVisionModels: ["grok-build-0.1", "grok-composer-2.5-fast"],
|
|
801
875
|
},
|
|
876
|
+
{
|
|
877
|
+
id: "command-code",
|
|
878
|
+
label: "Command Code - Auth",
|
|
879
|
+
adapter: "command-code",
|
|
880
|
+
baseUrl: "https://api.commandcode.ai",
|
|
881
|
+
authKind: "oauth",
|
|
882
|
+
oauthId: "command-code",
|
|
883
|
+
featured: true,
|
|
884
|
+
note: "Log in with your Command Code account",
|
|
885
|
+
// OAuth needs one initial selection, but the exposed catalog is always discovered from the
|
|
886
|
+
// signed-in account. Do not add a static model list here.
|
|
887
|
+
defaultModel: "deepseek/deepseek-v4-flash",
|
|
888
|
+
liveModels: true,
|
|
889
|
+
modelDiscovery: {
|
|
890
|
+
url: "https://api.commandcode.ai/provider/v1/models",
|
|
891
|
+
maxResponseBytes: 262_144,
|
|
892
|
+
maxModels: 256,
|
|
893
|
+
},
|
|
894
|
+
// These are capability facts from official Command Code model profiles, not seeded models.
|
|
895
|
+
// Unknown/new live models deliberately do not advertise a reasoning picker.
|
|
896
|
+
reasoningEfforts: [],
|
|
897
|
+
modelReasoningEfforts: COMMAND_CODE_MODEL_REASONING_EFFORTS,
|
|
898
|
+
defaultMaxOutputTokens: 64_000,
|
|
899
|
+
// The proprietary generate wire has no verified per-request serialization flag.
|
|
900
|
+
parallelToolCalls: false,
|
|
901
|
+
},
|
|
802
902
|
{
|
|
803
903
|
id: "anthropic",
|
|
804
904
|
label: "Anthropic Claude",
|
|
@@ -938,7 +1038,7 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
938
1038
|
"kimi-k2.7-code-highspeed": [],
|
|
939
1039
|
...Object.fromEntries(OPENCODE_GO_THINKING_TOGGLE_MODELS.map(id => [id, THINKING_TOGGLE_EFFORTS])),
|
|
940
1040
|
...Object.fromEntries(OPENCODE_GO_THINKING_BUDGET_MODELS.map(id => [id, THINKING_BUDGET_EFFORTS])),
|
|
941
|
-
...Object.fromEntries(DEEPSEEK_THINKING_MODELS.map(id => [id,
|
|
1041
|
+
...Object.fromEntries(DEEPSEEK_THINKING_MODELS.map(id => [id, deepseekThinkingEffortsFor(id)])),
|
|
942
1042
|
},
|
|
943
1043
|
modelDefaultReasoningEfforts: { "kimi-k3": "max" },
|
|
944
1044
|
// glm-5.2 uses identity labels now that `max` is a native Codex level (no alias map);
|
|
@@ -946,7 +1046,7 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
946
1046
|
modelReasoningEffortMap: {
|
|
947
1047
|
"kimi-k3": KIMI_CODING_K3_REASONING_EFFORT_MAP,
|
|
948
1048
|
...Object.fromEntries(OPENCODE_GO_THINKING_TOGGLE_MODELS.map(id => [id, THINKING_TOGGLE_MAP])),
|
|
949
|
-
...Object.fromEntries(DEEPSEEK_THINKING_MODELS.map(id => [id,
|
|
1049
|
+
...Object.fromEntries(DEEPSEEK_THINKING_MODELS.map(id => [id, deepseekReasoningMapFor(id)])),
|
|
950
1050
|
},
|
|
951
1051
|
thinkingToggleModels: OPENCODE_GO_THINKING_TOGGLE_MODELS,
|
|
952
1052
|
thinkingBudgetModels: THINKING_BUDGET_MODELS,
|
|
@@ -1087,9 +1187,9 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
1087
1187
|
// returned 200), so no noTemperatureModels entry is warranted here.
|
|
1088
1188
|
modelReasoningEfforts: {
|
|
1089
1189
|
"openai/gpt-5.5": ["low", "medium", "high", "xhigh"],
|
|
1090
|
-
"deepseek/deepseek-v4-pro":
|
|
1190
|
+
"deepseek/deepseek-v4-pro": deepseekThinkingEffortsFor("deepseek/deepseek-v4-pro"),
|
|
1091
1191
|
},
|
|
1092
|
-
modelReasoningEffortMap: { "deepseek/deepseek-v4-pro":
|
|
1192
|
+
modelReasoningEffortMap: { "deepseek/deepseek-v4-pro": deepseekReasoningMapFor("deepseek/deepseek-v4-pro") },
|
|
1093
1193
|
preserveReasoningContentModels: ["deepseek/deepseek-v4-pro"],
|
|
1094
1194
|
note: "OpenAI-compatible adaptive router. Default is a tool-capable model; orcarouter/auto (adaptive routing) is also selectable. Full catalog: https://www.orcarouter.ai/models",
|
|
1095
1195
|
},
|
|
@@ -1182,8 +1282,8 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
1182
1282
|
- 대안 분석: Globally preserve reasoning_content for all OpenAI-compatible models; preserve it for legacy deepseek-reasoner too; mark only V4 thinking models in registry metadata.
|
|
1183
1283
|
- 선택 근거: DeepSeek V4 thinking mode requires history replay, while older DeepSeek reasoner has different compatibility rules. A model-scoped registry flag fixes built-in and stale saved configs without broad provider regressions.
|
|
1184
1284
|
*/
|
|
1185
|
-
modelReasoningEfforts: Object.fromEntries(DEEPSEEK_THINKING_MODELS.map(id => [id,
|
|
1186
|
-
modelReasoningEffortMap: Object.fromEntries(DEEPSEEK_THINKING_MODELS.map(id => [id,
|
|
1285
|
+
modelReasoningEfforts: Object.fromEntries(DEEPSEEK_THINKING_MODELS.map(id => [id, deepseekThinkingEffortsFor(id)])),
|
|
1286
|
+
modelReasoningEffortMap: Object.fromEntries(DEEPSEEK_THINKING_MODELS.map(id => [id, deepseekReasoningMapFor(id)])),
|
|
1187
1287
|
preserveReasoningContentModels: DEEPSEEK_THINKING_MODELS,
|
|
1188
1288
|
// Issue #88: every DeepSeek API model is text-only input (no image support upstream) — the
|
|
1189
1289
|
// vision sidecar describes attached images for them, and the catalog advertises image input
|
|
@@ -1256,7 +1356,7 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
1256
1356
|
},
|
|
1257
1357
|
{
|
|
1258
1358
|
id: "commandcode",
|
|
1259
|
-
label: "Command Code",
|
|
1359
|
+
label: "Command Code - API",
|
|
1260
1360
|
adapter: "openai-chat",
|
|
1261
1361
|
baseUrl: "https://api.commandcode.ai/provider/v1",
|
|
1262
1362
|
authKind: "key",
|
|
@@ -1278,7 +1378,7 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
1278
1378
|
},
|
|
1279
1379
|
// Verified 2026-08-03: public /provider/v1/models returns 51 rows; /chat/completions returns
|
|
1280
1380
|
// 401 UNAUTHORIZED without a Bearer key. Primary source: https://commandcode.ai/docs/provider.
|
|
1281
|
-
note: "Command Code Provider API (OpenAI-compatible); API access requires the Provider plan.
|
|
1381
|
+
note: "Command Code Provider API (OpenAI-compatible); API access requires the Provider plan. Use `ocx login command-code` for OAuth account login (imports an existing local Command Code CLI credential when present). Docs: https://commandcode.ai/docs/provider.",
|
|
1282
1382
|
},
|
|
1283
1383
|
// FREEZE 2026-07-10: exact serverless ids remain auth-gated/unverified. Evidence: devlog/_plan/260710_provider_hardening/003_research_aggregators.md.
|
|
1284
1384
|
{ id: "together", label: "Together", baseUrl: "https://api.together.xyz/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://api.together.xyz/settings/api-keys" },
|
|
@@ -1470,10 +1570,10 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
1470
1570
|
modelInputModalities: VOLCENGINE_PLAN_INPUT_MODALITIES,
|
|
1471
1571
|
noVisionModels: VOLCENGINE_PLAN_TEXT_ONLY_MODELS,
|
|
1472
1572
|
modelReasoningEfforts: Object.fromEntries(
|
|
1473
|
-
DEEPSEEK_THINKING_MODELS.map(id => [id,
|
|
1573
|
+
DEEPSEEK_THINKING_MODELS.map(id => [id, deepseekThinkingEffortsFor(id)]),
|
|
1474
1574
|
),
|
|
1475
1575
|
modelReasoningEffortMap: Object.fromEntries(
|
|
1476
|
-
DEEPSEEK_THINKING_MODELS.map(id => [id,
|
|
1576
|
+
DEEPSEEK_THINKING_MODELS.map(id => [id, deepseekReasoningMapFor(id)]),
|
|
1477
1577
|
),
|
|
1478
1578
|
preserveReasoningContentModels: DEEPSEEK_THINKING_MODELS,
|
|
1479
1579
|
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.",
|
|
@@ -1519,9 +1619,9 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
1519
1619
|
modelReasoningEfforts: {
|
|
1520
1620
|
...Object.fromEntries(ALIBABA_TOKEN_PLAN_QWEN_MODELS.map(id => [id, THINKING_BUDGET_EFFORTS])),
|
|
1521
1621
|
"glm-5.2": ZAI_GLM_52_REASONING_EFFORTS,
|
|
1522
|
-
"deepseek-v4-pro":
|
|
1622
|
+
"deepseek-v4-pro": deepseekThinkingEffortsFor("deepseek-v4-pro"),
|
|
1523
1623
|
},
|
|
1524
|
-
modelReasoningEffortMap: { "deepseek-v4-pro":
|
|
1624
|
+
modelReasoningEffortMap: { "deepseek-v4-pro": deepseekReasoningMapFor("deepseek-v4-pro") },
|
|
1525
1625
|
thinkingBudgetModels: ALIBABA_TOKEN_PLAN_QWEN_MODELS,
|
|
1526
1626
|
preserveReasoningContentModels: ["glm-5.2", "deepseek-v4-pro", "qwen3.8-max", "qwen3.7-max", "qwen3.7-plus", "qwen3.6-flash"],
|
|
1527
1627
|
noVisionModels: ["glm-5.2", "deepseek-v4-pro"],
|
|
@@ -1553,12 +1653,12 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
1553
1653
|
...Object.fromEntries(ALIBABA_INTL_TOKEN_PLAN_QWEN_MODELS.map(id => [id, THINKING_BUDGET_EFFORTS])),
|
|
1554
1654
|
"qwen3.8-max": ["low", "high", "xhigh"],
|
|
1555
1655
|
"glm-5.2": ZAI_GLM_52_REASONING_EFFORTS,
|
|
1556
|
-
"deepseek-v4-pro":
|
|
1557
|
-
"deepseek-v4-flash":
|
|
1656
|
+
"deepseek-v4-pro": deepseekThinkingEffortsFor("deepseek-v4-pro"),
|
|
1657
|
+
"deepseek-v4-flash": deepseekThinkingEffortsFor("deepseek-v4-flash"),
|
|
1558
1658
|
},
|
|
1559
1659
|
modelReasoningEffortMap: {
|
|
1560
|
-
"deepseek-v4-pro":
|
|
1561
|
-
"deepseek-v4-flash":
|
|
1660
|
+
"deepseek-v4-pro": deepseekReasoningMapFor("deepseek-v4-pro"),
|
|
1661
|
+
"deepseek-v4-flash": deepseekReasoningMapFor("deepseek-v4-flash"),
|
|
1562
1662
|
},
|
|
1563
1663
|
thinkingBudgetModels: ALIBABA_INTL_TOKEN_PLAN_QWEN_MODELS,
|
|
1564
1664
|
preserveReasoningContentModels: ["glm-5.2", "deepseek-v4-pro", "deepseek-v4-flash", "qwen3.8-max", "qwen3.7-max", "qwen3.7-plus", "qwen3.6-plus", "qwen3.6-flash"],
|
|
@@ -1649,7 +1749,17 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
1649
1749
|
autoToolChoiceOnlyModels: KIMI_AUTO_TOOL_CHOICE_ONLY_MODELS,
|
|
1650
1750
|
preserveReasoningContentModels: KIMI_THINKING_MODELS,
|
|
1651
1751
|
},
|
|
1652
|
-
{
|
|
1752
|
+
{
|
|
1753
|
+
id: "opencode-zen",
|
|
1754
|
+
label: "opencode zen",
|
|
1755
|
+
baseUrl: "https://opencode.ai/zen/v1",
|
|
1756
|
+
adapter: "openai-chat",
|
|
1757
|
+
authKind: "key",
|
|
1758
|
+
dashboardUrl: "https://opencode.ai/auth",
|
|
1759
|
+
// #1043: without this the proxy forwards image parts to text-only Zen models and
|
|
1760
|
+
// the upstream rejects the whole request with a 400.
|
|
1761
|
+
noVisionModels: OPENCODE_ZEN_TEXT_ONLY_MODELS,
|
|
1762
|
+
},
|
|
1653
1763
|
{ 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" },
|
|
1654
1764
|
{
|
|
1655
1765
|
id: "opencode-free",
|
|
@@ -1665,10 +1775,12 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
1665
1775
|
staticHeaders: {
|
|
1666
1776
|
"x-opencode-client": "desktop",
|
|
1667
1777
|
},
|
|
1668
|
-
modelReasoningEfforts: Object.fromEntries(OPENCODE_FREE_DEEPSEEK_MODELS.map(id => [id,
|
|
1669
|
-
modelReasoningEffortMap: Object.fromEntries(OPENCODE_FREE_DEEPSEEK_MODELS.map(id => [id,
|
|
1778
|
+
modelReasoningEfforts: Object.fromEntries(OPENCODE_FREE_DEEPSEEK_MODELS.map(id => [id, deepseekThinkingEffortsFor(id)])),
|
|
1779
|
+
modelReasoningEffortMap: Object.fromEntries(OPENCODE_FREE_DEEPSEEK_MODELS.map(id => [id, deepseekReasoningMapFor(id)])),
|
|
1670
1780
|
preserveReasoningContentModels: OPENCODE_FREE_DEEPSEEK_MODELS,
|
|
1671
|
-
|
|
1781
|
+
// Same Zen roster behind the same base URL, so it carries the same measured
|
|
1782
|
+
// text-only list rather than only its DeepSeek member (#1043).
|
|
1783
|
+
noVisionModels: OPENCODE_ZEN_TEXT_ONLY_MODELS,
|
|
1672
1784
|
},
|
|
1673
1785
|
{ id: "xiaomi", label: "Xiaomi MiMo", baseUrl: "https://api.xiaomimimo.com/anthropic", adapter: "anthropic", authKind: "key", dashboardUrl: "https://xiaomimimo.com", defaultModel: "mimo-v2.5-pro" },
|
|
1674
1786
|
{ id: "kilo", label: "Kilo", baseUrl: "https://api.kilo.ai/api/gateway", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://kilo.ai" },
|
|
@@ -5,6 +5,7 @@ import { createGoogleAdapter } from "../adapters/google";
|
|
|
5
5
|
import { createKiroAdapter } from "../adapters/kiro";
|
|
6
6
|
import { createMimoFreeAdapter } from "../adapters/mimo-free";
|
|
7
7
|
import { createOpenAIChatAdapter } from "../adapters/openai-chat";
|
|
8
|
+
import { createCommandCodeAdapter } from "../adapters/command-code";
|
|
8
9
|
import { createResponsesPassthroughAdapter } from "../adapters/openai-responses";
|
|
9
10
|
import type { OcxProviderConfig } from "../types";
|
|
10
11
|
import { isWirePinnedModel, MODEL_ADAPTER_OVERRIDE_ALLOWED, pinnedWireAdapter } from "../types";
|
|
@@ -57,6 +58,8 @@ export function resolveWireProtocolOverride(
|
|
|
57
58
|
/** Build the provider adapter for a resolved provider config. */
|
|
58
59
|
export function resolveAdapter(providerConfig: OcxProviderConfig, cacheRetention?: "none" | "short" | "long") {
|
|
59
60
|
switch (providerConfig.adapter) {
|
|
61
|
+
case "command-code":
|
|
62
|
+
return createCommandCodeAdapter(providerConfig);
|
|
60
63
|
case "openai-chat":
|
|
61
64
|
return createOpenAIChatAdapter(providerConfig);
|
|
62
65
|
case "anthropic":
|
package/src/server/auth-cors.ts
CHANGED
|
@@ -15,10 +15,11 @@ import {
|
|
|
15
15
|
} from "../config";
|
|
16
16
|
import { providerDestinationConfigError } from "../lib/destination-policy";
|
|
17
17
|
import { redactSecretString } from "../lib/redact";
|
|
18
|
-
import { getProviderRegistryEntry, providerCodexAccountMode, providerMatchesRegistryTransport, registryEntryForProviderDestination } from "../providers/registry";
|
|
18
|
+
import { effectiveGoogleMode, getProviderRegistryEntry, providerCodexAccountMode, providerMatchesRegistryTransport, registryEntryForProviderDestination } from "../providers/registry";
|
|
19
19
|
import { providerConfigSeed } from "../providers/derive";
|
|
20
20
|
import type { OcxConfig, OcxProviderConfig } from "../types";
|
|
21
21
|
import { openRouterRoutingConfigError } from "../providers/openrouter-routing";
|
|
22
|
+
import { googleVertexLocationConfigError } from "../providers/google-vertex-location";
|
|
22
23
|
|
|
23
24
|
let _corsOrigin = "http://localhost:10100";
|
|
24
25
|
export function setCorsOrigin(port: number): void { _corsOrigin = `http://localhost:${port}`; }
|
|
@@ -425,6 +426,10 @@ export function providerManagementConfigError(name: unknown, provider: unknown):
|
|
|
425
426
|
const typed = provider as unknown as OcxProviderConfig;
|
|
426
427
|
const baseUrlError = providerBaseUrlConfigError(typed.baseUrl);
|
|
427
428
|
if (baseUrlError) return `provider ${name} ${baseUrlError}`;
|
|
429
|
+
if (effectiveGoogleMode(name, typed) === "vertex" && typed.location !== undefined) {
|
|
430
|
+
const locationError = googleVertexLocationConfigError(typed.location);
|
|
431
|
+
if (locationError) return `provider ${name} ${locationError}`;
|
|
432
|
+
}
|
|
428
433
|
const destinationError = providerDestinationConfigError(name, typed);
|
|
429
434
|
if (destinationError) return `provider ${name} ${destinationError}`;
|
|
430
435
|
const headersError = providerHeadersConfigError(typed.headers);
|
package/src/server/index.ts
CHANGED
|
@@ -174,6 +174,12 @@ import {
|
|
|
174
174
|
requireManagementAuth,
|
|
175
175
|
type ManagementAuthState,
|
|
176
176
|
} from "./management-auth";
|
|
177
|
+
import {
|
|
178
|
+
LOCAL_ATTESTATION_CHALLENGE_HEADER,
|
|
179
|
+
LOCAL_ATTESTATION_PROOF_HEADER,
|
|
180
|
+
createLocalAttestationProof,
|
|
181
|
+
createLocalAttestationSecret,
|
|
182
|
+
} from "../lib/local-management-attestation";
|
|
177
183
|
|
|
178
184
|
const MAX_WS_FRAME_BYTES = 50 * 1024 * 1024;
|
|
179
185
|
const WEBSOCKET_IDLE_TIMEOUT_SECONDS = 0;
|
|
@@ -357,9 +363,33 @@ export interface StartServerDeps {
|
|
|
357
363
|
nativeMainStartup?: NativeMainStartupGateDeps;
|
|
358
364
|
/** Test-only seam for an upstream that cannot complete its WebSocket close handshake. */
|
|
359
365
|
liveSidebandWebSocketFactory?: LiveSidebandWebSocketFactory;
|
|
366
|
+
/** Test-only seam; production derives a fresh local-attestation secret per process. */
|
|
367
|
+
localAttestationSecret?: string;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/*
|
|
371
|
+
* #1046. `startServer` rewrites the Codex models cache during boot, and an
|
|
372
|
+
* app-server that started earlier keeps its own in-memory model list. The stale
|
|
373
|
+
* warning is not emitted here: `handleStart` runs a catalog sync moments later,
|
|
374
|
+
* so warning now would read an mtime that write is about to move, and both sites
|
|
375
|
+
* calling the helper independently would warn twice. This records the fact; the
|
|
376
|
+
* CLI start path owns the single decision.
|
|
377
|
+
*
|
|
378
|
+
* A caller that starts a server without `handleStart` (tests, embedded use)
|
|
379
|
+
* deliberately gets no warning — lifecycle diagnostics belong to whoever owns
|
|
380
|
+
* the lifecycle.
|
|
381
|
+
*/
|
|
382
|
+
let startupCacheInvalidationWrote = false;
|
|
383
|
+
|
|
384
|
+
/** #1046: did this process's startup cache invalidation actually write? */
|
|
385
|
+
export function consumeStartupCacheInvalidationWrite(): boolean {
|
|
386
|
+
const wrote = startupCacheInvalidationWrote;
|
|
387
|
+
startupCacheInvalidationWrote = false;
|
|
388
|
+
return wrote;
|
|
360
389
|
}
|
|
361
390
|
|
|
362
391
|
export function startServer(port?: number, deps: StartServerDeps = {}) {
|
|
392
|
+
const localAttestationSecret = deps.localAttestationSecret ?? createLocalAttestationSecret();
|
|
363
393
|
const config = runAlibabaRegionStartupMigration(runOpenAiTierStartupMigration(loadConfig()));
|
|
364
394
|
setLiveStateStoreConfig(config);
|
|
365
395
|
applyProxyEnv(config);
|
|
@@ -407,8 +437,13 @@ export function startServer(port?: number, deps: StartServerDeps = {}) {
|
|
|
407
437
|
// otherwise turn "no Codex installed" into "proxy will not start".
|
|
408
438
|
try {
|
|
409
439
|
const startupCodexHome = getCodexHome();
|
|
410
|
-
|
|
440
|
+
// #1046: record whether this actually rewrote the cache. `handleStart` ORs this
|
|
441
|
+
// with the later startup sync and warns ONCE about stale app-servers; warning
|
|
442
|
+
// here instead would read a catalog mtime the sync is about to move.
|
|
443
|
+
const outcome = withCatalogWriteSerialization(startupCodexHome, permit =>
|
|
411
444
|
invalidateCodexModelsCacheWithPermit(permit, startupCodexHome));
|
|
445
|
+
// A refused permit is not a write; only a completed run that returned true is.
|
|
446
|
+
startupCacheInvalidationWrote = outcome.kind === "completed" && outcome.value === true;
|
|
412
447
|
} catch { /* no readable Codex home: nothing to invalidate */ }
|
|
413
448
|
// Arm the `claudeCode` hand-edit guard (devlog 260726_claude_auth_auto/040 H1) BEFORE
|
|
414
449
|
// the server can serve a request, and AFTER the startup migrations above — those run
|
|
@@ -545,7 +580,14 @@ export function startServer(port?: number, deps: StartServerDeps = {}) {
|
|
|
545
580
|
|
|
546
581
|
if (url.pathname === "/healthz" && req.method === "GET") {
|
|
547
582
|
// service/pid/port let CLI liveness reject foreign 200s and verify pid identity.
|
|
548
|
-
|
|
583
|
+
const healthPort = server.port ?? listenPort;
|
|
584
|
+
const response = jsonResponse({ status: "ok", service: "opencodex", version: VERSION, uptime: process.uptime(), pid: process.pid, port: healthPort }, 200, req, config);
|
|
585
|
+
const challenge = req.headers.get(LOCAL_ATTESTATION_CHALLENGE_HEADER);
|
|
586
|
+
if (challenge) {
|
|
587
|
+
const proof = createLocalAttestationProof(localAttestationSecret, challenge, process.pid, healthPort);
|
|
588
|
+
if (proof) response.headers.set(LOCAL_ATTESTATION_PROOF_HEADER, proof);
|
|
589
|
+
}
|
|
590
|
+
return response;
|
|
549
591
|
}
|
|
550
592
|
|
|
551
593
|
if (url.pathname.startsWith("/api/")) {
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* belongs to src/integrations/writer.ts. Duplicating any of it here is how the
|
|
7
7
|
* API and the writer would start disagreeing about what happened to a file.
|
|
8
8
|
*
|
|
9
|
-
* Design of record: devlog/
|
|
9
|
+
* Design of record: devlog/_fin/260802_client_toggle_api/040_wp4_management_api.md.
|
|
10
10
|
*/
|
|
11
11
|
import { readFileSync } from "node:fs";
|
|
12
12
|
import type { IntegrationIO } from "../../integrations/config-io";
|
|
@@ -9,12 +9,12 @@
|
|
|
9
9
|
* route — turning them back on is the undo.
|
|
10
10
|
*
|
|
11
11
|
* That conclusion cost eleven audit rounds; the reasoning is in
|
|
12
|
-
* devlog/
|
|
12
|
+
* devlog/_fin/260803_integrations_toggle_all/, and 007 records why Codex and
|
|
13
13
|
* Claude Desktop are NOT here: their state spans several artifacts and a live
|
|
14
14
|
* database, so they need a durable operation record this module deliberately
|
|
15
15
|
* does not have.
|
|
16
16
|
*
|
|
17
|
-
* Design of record: devlog/
|
|
17
|
+
* Design of record: devlog/_fin/260803_integrations_toggle_all/030 (routes),
|
|
18
18
|
* 011 (Claude Code), 012 (Grok).
|
|
19
19
|
*/
|
|
20
20
|
import { readRuntimePort, saveConfigPreservingClaudeCode } from "../../config";
|
|
@@ -747,10 +747,18 @@ const UNREADABLE_ENCRYPTED_AGENT_TASK_MESSAGE =
|
|
|
747
747
|
// branch of the passthrough return path). 32 MiB matches the continuation snapshot read
|
|
748
748
|
// bound and is far above any legitimate non-streaming completion, including base64 image
|
|
749
749
|
// payloads. The stall deadlines only govern the body transfer — generation time before
|
|
750
|
-
// the response headers is untouched.
|
|
750
|
+
// the response headers is untouched. Generation after early/chunked headers but before
|
|
751
|
+
// the first body byte previously used the 30-second inactivity deadline; this call site
|
|
752
|
+
// gives it the full body deadline instead.
|
|
751
753
|
const MAX_UPSTREAM_JSON_BODY_BYTES = 32 * 1024 * 1024;
|
|
752
754
|
const UPSTREAM_JSON_BODY_TOTAL_TIMEOUT_MS = 180_000;
|
|
753
755
|
const UPSTREAM_JSON_BODY_INACTIVITY_TIMEOUT_MS = 30_000;
|
|
756
|
+
export const UPSTREAM_JSON_BODY_READ_OPTIONS = {
|
|
757
|
+
maxBytes: MAX_UPSTREAM_JSON_BODY_BYTES,
|
|
758
|
+
totalTimeoutMs: UPSTREAM_JSON_BODY_TOTAL_TIMEOUT_MS,
|
|
759
|
+
inactivityTimeoutMs: UPSTREAM_JSON_BODY_INACTIVITY_TIMEOUT_MS,
|
|
760
|
+
firstByteTimeoutMs: UPSTREAM_JSON_BODY_TOTAL_TIMEOUT_MS,
|
|
761
|
+
};
|
|
754
762
|
|
|
755
763
|
function unreadableEncryptedAgentTaskResponse(): Response {
|
|
756
764
|
return new Response(
|
|
@@ -1375,8 +1383,8 @@ async function handleResponsesInner(
|
|
|
1375
1383
|
logCtx.configuredServiceTier = readConfiguredCodexServiceTier();
|
|
1376
1384
|
logCtx.configuredSpeedLabel = requestLogSpeedLabel(logCtx.configuredServiceTier);
|
|
1377
1385
|
|
|
1378
|
-
// Shadow call intercept: rewrite Codex
|
|
1379
|
-
//
|
|
1386
|
+
// Shadow call intercept: rewrite Codex 0.145.0+ helper calls (gpt-5.6-luna).
|
|
1387
|
+
// Ancient clients using gpt-5.4-mini remain configurable via sourceModels.
|
|
1380
1388
|
const _sci = config.shadowCallIntercept;
|
|
1381
1389
|
if (_sci?.enabled && _sci.model && shouldInterceptShadowCall(
|
|
1382
1390
|
parsed.modelId,
|
|
@@ -2039,7 +2047,7 @@ async function handleResponsesInner(
|
|
|
2039
2047
|
// `streamMode: "eager-relay"` opt-in. Darwin `auto` always stays tee. The
|
|
2040
2048
|
// eager shape skips tee and uses one bounded reader with inline inspection
|
|
2041
2049
|
// (src/server/relay-eager.ts; policy:
|
|
2042
|
-
// devlog/
|
|
2050
|
+
// devlog/_fin/260731_macos_rss_retention/100_darwin_eager_optin.md).
|
|
2043
2051
|
// The bundled known-bad runtime remains on tee by default on both platforms.
|
|
2044
2052
|
if (isEventStream && upstreamResponse.body) {
|
|
2045
2053
|
const repairConfig = route.provider.responsesItemIdRepair;
|
|
@@ -2226,11 +2234,7 @@ async function handleResponsesInner(
|
|
|
2226
2234
|
// without limit. This path is no longer rare — WebSocket turns for models whose
|
|
2227
2235
|
// streaming terminal event is unreliable are deliberately answered with bounded JSON.
|
|
2228
2236
|
// Oversize and stall deadlines both fail closed; a partial body is never parsed.
|
|
2229
|
-
const bounded = await readBoundedResponseBody(upstreamResponse,
|
|
2230
|
-
maxBytes: MAX_UPSTREAM_JSON_BODY_BYTES,
|
|
2231
|
-
totalTimeoutMs: UPSTREAM_JSON_BODY_TOTAL_TIMEOUT_MS,
|
|
2232
|
-
inactivityTimeoutMs: UPSTREAM_JSON_BODY_INACTIVITY_TIMEOUT_MS,
|
|
2233
|
-
});
|
|
2237
|
+
const bounded = await readBoundedResponseBody(upstreamResponse, UPSTREAM_JSON_BODY_READ_OPTIONS);
|
|
2234
2238
|
if (bounded.oversized) {
|
|
2235
2239
|
return formatErrorResponse(502, "upstream_error", "upstream JSON response exceeded the safe body limit");
|
|
2236
2240
|
}
|
package/src/storage/scanner.ts
CHANGED
|
@@ -16,7 +16,7 @@ const IMMUTABLE_READONLY_FLAGS = constants.SQLITE_OPEN_READONLY | constants.SQLI
|
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Read-only CODEX_HOME storage scanner — Phase 1 of the Storage page epic
|
|
19
|
-
* (devlog/
|
|
19
|
+
* (devlog/_fin/500_storage-page-session-cleanup). Pure measurement: sizes via
|
|
20
20
|
* fs.stat walks, DB row counts via immutable readonly opens that degrade to
|
|
21
21
|
* null on lock/corruption. Performs zero writes under CODEX_HOME.
|
|
22
22
|
*/
|
package/src/types.ts
CHANGED
|
@@ -1066,6 +1066,12 @@ export interface OcxProviderConfig {
|
|
|
1066
1066
|
* the legacy `/v1/responses` construction.
|
|
1067
1067
|
*/
|
|
1068
1068
|
responsesPath?: string;
|
|
1069
|
+
/**
|
|
1070
|
+
* Command Code protocol version sent as `x-command-code-version` on /alpha/generate requests.
|
|
1071
|
+
* The internal endpoint's schema drifts with the CLI version; operators can pin a known-good
|
|
1072
|
+
* version here instead of waiting for a code change. Absent uses the adapter's current default.
|
|
1073
|
+
*/
|
|
1074
|
+
commandCodeVersion?: string;
|
|
1069
1075
|
/**
|
|
1070
1076
|
* Responses upstream that stores nothing server-side (DeepSeek documents "the API
|
|
1071
1077
|
* is stateless"). Stateful request parameters are dropped, `store` is pinned false,
|
package/src/usage/log.ts
CHANGED
|
@@ -421,7 +421,7 @@ export type UsageLogRevision = {
|
|
|
421
421
|
let usageReadCacheStats = { fullReads: 0, tailReads: 0, parsedLines: 0 };
|
|
422
422
|
const MANAGEMENT_USAGE_MAX_READ_BYTES = 64 * 1024 * 1024;
|
|
423
423
|
const MANAGEMENT_USAGE_READ_CHUNK_BYTES = 1024 * 1024;
|
|
424
|
-
const MANAGEMENT_USAGE_MAX_ENTRIES =
|
|
424
|
+
const MANAGEMENT_USAGE_MAX_ENTRIES = 500_000;
|
|
425
425
|
const MANAGEMENT_USAGE_FLIGHT_STALE_MS = 30_000;
|
|
426
426
|
export interface ManagementUsageSnapshot {
|
|
427
427
|
entries: PersistedUsageEntry[];
|