@bitkyc08/opencodex 2.52.0-preview.20260911 → 2.52.0-preview.20260912
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/gui/dist/assets/index-D_t6sCWs.js +115 -0
- package/gui/dist/assets/index-EdoPnm9_.css +1 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/devin.svg +49 -0
- package/gui/dist/provider-icons/omo.svg +42 -0
- package/package.json +3 -1
- package/src/AGENTS.md +1 -1
- package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +0 -1
- package/src/adapters/command-code.ts +0 -1
- package/src/adapters/cursor/checkpoint-store.ts +37 -0
- package/src/adapters/cursor/live-transport.ts +74 -2
- package/src/adapters/cursor.ts +6 -0
- package/src/adapters/devin/cloud-direct/auth.ts +264 -0
- package/src/adapters/devin/cloud-direct/catalog.ts +306 -0
- package/src/adapters/devin/cloud-direct/chat.ts +1274 -0
- package/src/adapters/devin/cloud-direct/index.ts +65 -0
- package/src/adapters/devin/cloud-direct/metadata.ts +134 -0
- package/src/adapters/devin/cloud-direct/wire.ts +206 -0
- package/src/adapters/devin/live-models.ts +133 -0
- package/src/adapters/devin-cli/acp.ts +204 -0
- package/src/adapters/devin-cli/adapter.ts +345 -0
- package/src/adapters/devin-cli/binary.ts +69 -0
- package/src/adapters/devin-cli/models.ts +57 -0
- package/src/adapters/devin.ts +326 -0
- package/src/adapters/google.ts +1 -1
- package/src/adapters/openai-chat.ts +31 -10
- package/src/adapters/openai-responses.ts +16 -1
- package/src/adapters/registry.ts +25 -1
- package/src/bridge.ts +8 -2
- package/src/claude/inbound-cache-stabilize.ts +130 -0
- package/src/claude/inbound.ts +45 -5
- package/src/cli/account-auth.ts +60 -1
- package/src/cli/account-extended.ts +23 -30
- package/src/cli/account.ts +2 -1
- package/src/cli/capabilities.ts +50 -8
- package/src/cli/config-command.ts +2 -2
- package/src/cli/dispatch.ts +14 -2
- package/src/cli/export-command.ts +11 -25
- package/src/cli/help.ts +2 -2
- package/src/cli/opencode.ts +5 -0
- package/src/cli/registry.ts +15 -4
- package/src/clients/config-export/cline.ts +71 -0
- package/src/clients/config-export/contracts.ts +10 -1
- package/src/clients/config-export/model-metadata.ts +33 -0
- package/src/clients/config-export/zcode.ts +23 -12
- package/src/clients/config-export.ts +156 -4
- package/src/codex/account-store.ts +7 -2
- package/src/codex/auth-context.ts +1 -1
- package/src/codex/catalog/effort.ts +4 -3
- package/src/codex/catalog/metadata.ts +8 -4
- package/src/codex/catalog/native-models.ts +24 -4
- package/src/codex/catalog/parsing.ts +19 -1
- package/src/codex/catalog/provider-fetch.ts +57 -0
- package/src/codex/catalog/sync.ts +1 -1
- package/src/codex/context-compat.ts +97 -0
- package/src/codex/context-owner.ts +201 -0
- package/src/codex/history-provider.ts +161 -14
- package/src/codex/inject-coordination.ts +3 -2
- package/src/codex/inject.ts +128 -9
- package/src/codex/pool-rotation.ts +8 -292
- package/src/codex/quota.ts +41 -12
- package/src/codex/retired-model-migration.ts +41 -0
- package/src/codex/routing.ts +327 -22
- package/src/codex/warmup.ts +2 -2
- package/src/combos/failover.ts +5 -0
- package/src/combos/resolve.ts +11 -15
- package/src/config.ts +46 -14
- package/src/generated/compatibility-version.json +293 -113
- package/src/grok/grpc-web.ts +120 -0
- package/src/grok/reset-coupon-ledger.ts +139 -0
- package/src/grok/reset-coupons.ts +278 -0
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/cline-document.ts +73 -0
- package/src/integrations/cline-io.ts +149 -0
- package/src/integrations/cline-transaction.ts +42 -0
- package/src/integrations/config-io.ts +21 -2
- package/src/integrations/journal.ts +43 -1
- package/src/integrations/omp-yaml-source.ts +123 -4
- package/src/integrations/ownership-policy.ts +7 -0
- package/src/integrations/ownership.ts +36 -0
- package/src/integrations/registry.ts +27 -0
- package/src/integrations/state.ts +5 -2
- package/src/integrations/store.ts +6 -0
- package/src/integrations/writer.ts +53 -9
- package/src/lab/subject/behavior-fingerprint.ts +1 -1
- package/src/lib/abort.ts +36 -0
- package/src/lib/local-destinations.ts +1 -1
- package/src/lib/upstream-retry.ts +36 -1
- package/src/oauth/account-quota-rank.ts +11 -0
- package/src/oauth/callback-server.ts +10 -5
- package/src/oauth/devin/api-base.ts +63 -0
- package/src/oauth/devin/login.ts +1 -0
- package/src/oauth/devin/register-user.ts +186 -0
- package/src/oauth/devin/types.ts +71 -0
- package/src/oauth/devin-cli.ts +149 -0
- package/src/oauth/devin.ts +170 -0
- package/src/oauth/generic-account-failover.ts +169 -1
- package/src/oauth/index.ts +20 -1
- package/src/oauth/login-cli.ts +19 -5
- package/src/oauth/pool-kernel.ts +321 -0
- package/src/oauth/pool-settings-capability.ts +127 -9
- package/src/oauth/store.ts +7 -3
- package/src/oauth/token-guardian.ts +1 -1
- package/src/providers/codebuddy-models.ts +0 -3
- package/src/providers/command-code-efforts.ts +23 -4
- package/src/providers/default-aliases.ts +4 -0
- package/src/providers/derive.ts +8 -0
- package/src/providers/devin-cli-authmode-migration.ts +57 -0
- package/src/providers/key-failover.ts +175 -0
- package/src/providers/model-rename-startup.ts +21 -1
- package/src/providers/qoder-models.ts +0 -1
- package/src/providers/quota-key-accounts.ts +50 -0
- package/src/providers/quota-routing-cache.ts +65 -6
- package/src/providers/quota-types.ts +7 -0
- package/src/providers/quota.ts +113 -30
- package/src/providers/registry.ts +237 -77
- package/src/providers/stale-context-window-migration.ts +92 -0
- package/src/providers/zai-responses-migration.ts +45 -0
- package/src/quota/reset-observer.ts +2 -1
- package/src/quota/reset-seen-store.ts +9 -1
- package/src/remote-control/crypto.ts +442 -0
- package/src/remote-control/host.ts +175 -0
- package/src/remote-control/index.ts +100 -0
- package/src/remote-control/protocol.ts +200 -0
- package/src/remote-control/relay.ts +162 -0
- package/src/remote-control/workspace-agent-protocol.ts +246 -0
- package/src/remote-control/workspace-rpc-framing.ts +128 -0
- package/src/remote-control/workspace-tools.ts +237 -0
- package/src/remote-control/workspace-utf8.ts +24 -0
- package/src/responses/custom-tool-compat.ts +23 -0
- package/src/router.ts +6 -1
- package/src/routing/compatibility/behavior.ts +3 -0
- package/src/server/adapter-resolve.ts +6 -2
- package/src/server/auth-cors.ts +71 -6
- package/src/server/chat-completions.ts +5 -3
- package/src/server/chat-native.ts +9 -0
- package/src/server/claude-messages.ts +25 -30
- package/src/server/context-history.ts +207 -0
- package/src/server/images.ts +28 -1
- package/src/server/index.ts +42 -22
- package/src/server/live.ts +2 -1
- package/src/server/management/agent-settings-routes.ts +7 -1
- package/src/server/management/config-routes.ts +3 -3
- package/src/server/management/grok-coupon-routes.ts +287 -0
- package/src/server/management/integration-routes.ts +6 -1
- package/src/server/management/oauth-account-routes.ts +124 -7
- package/src/server/management/provider-routes.ts +77 -2
- package/src/server/management/route-registry.ts +6 -1
- package/src/server/management-api.ts +7 -0
- package/src/server/request-log.ts +4 -0
- package/src/server/responses/codex-ws-exchange.ts +90 -14
- package/src/server/responses/codex-ws-wire.ts +51 -1
- package/src/server/responses/compact.ts +8 -1
- package/src/server/responses/core.ts +188 -22
- package/src/server/responses/ws-upstream.ts +1 -1
- package/src/server/responses-undeclared-tool-guard.ts +261 -12
- package/src/server/zai-responses-startup.ts +21 -0
- package/src/types/config.ts +33 -3
- package/src/types/provider.ts +54 -4
- package/src/types/request.ts +1 -1
- package/src/types/tools.ts +36 -6
- package/src/update/job.ts +33 -11
- package/src/vision/plan.ts +40 -37
- package/src/web-search/index.ts +1 -0
- package/gui/dist/assets/index-BoBRSehJ.css +0 -1
- package/gui/dist/assets/index-Dx0xv2EA.js +0 -115
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { ExportContext, ManagedContribution } from "./contracts";
|
|
2
|
+
import { LOOPBACK_API_KEY_PLACEHOLDER, OPENCODE_PROVIDER_ID } from "./constants";
|
|
3
|
+
import { authoritativeContextWindow, exportModelLabel, inputModalitiesForClient, normalizeExportModels } from "./model-metadata";
|
|
4
|
+
|
|
5
|
+
/** Cline CLI/shared SDK schema at cline/cline cfe9cadab996 (2026-09-12). */
|
|
6
|
+
export interface ClineModel {
|
|
7
|
+
name: string;
|
|
8
|
+
contextWindow?: number;
|
|
9
|
+
modalities?: { input: string[]; output: string[] };
|
|
10
|
+
supportsVision?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function buildClineClientConfig(ctx: ExportContext) {
|
|
14
|
+
const models: Record<string, ClineModel> = {};
|
|
15
|
+
for (const model of normalizeExportModels(ctx.models)) {
|
|
16
|
+
const input = inputModalitiesForClient("pi", model.inputModalities);
|
|
17
|
+
if (input === null) continue;
|
|
18
|
+
const contextWindow = authoritativeContextWindow(model.contextWindow);
|
|
19
|
+
models[model.namespaced] = {
|
|
20
|
+
name: exportModelLabel(model),
|
|
21
|
+
...(contextWindow === undefined ? {} : { contextWindow }),
|
|
22
|
+
...(model.inputModalities?.length ? {
|
|
23
|
+
modalities: { input, output: ["text"] },
|
|
24
|
+
supportsVision: input.includes("image"),
|
|
25
|
+
} : {}),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
const connection = { baseUrl: ctx.baseUrl, protocol: "openai-responses", client: "openai" } as const;
|
|
29
|
+
return {
|
|
30
|
+
settings: {
|
|
31
|
+
version: 1,
|
|
32
|
+
modes: {},
|
|
33
|
+
providers: {
|
|
34
|
+
[OPENCODE_PROVIDER_ID]: {
|
|
35
|
+
settings: { provider: OPENCODE_PROVIDER_ID, ...connection, apiKey: LOOPBACK_API_KEY_PLACEHOLDER },
|
|
36
|
+
// Required by Cline's schema; a stable generation sentinel keeps export deterministic.
|
|
37
|
+
// The operation's actual time belongs to the OpenCodex journal.
|
|
38
|
+
updatedAt: "1970-01-01T00:00:00.000Z",
|
|
39
|
+
tokenSource: "manual",
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
catalog: {
|
|
44
|
+
version: 1,
|
|
45
|
+
providers: {
|
|
46
|
+
[OPENCODE_PROVIDER_ID]: {
|
|
47
|
+
provider: { name: "OpenCodex", ...connection },
|
|
48
|
+
models,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type ClineGeneratedConfig = ReturnType<typeof buildClineClientConfig>;
|
|
56
|
+
|
|
57
|
+
export function summarizeCline(document: unknown) {
|
|
58
|
+
const models = Object.values((document as ClineGeneratedConfig | undefined)?.catalog?.providers?.[OPENCODE_PROVIDER_ID]?.models ?? {});
|
|
59
|
+
return { modelCount: models.length, modelsWithoutLimits: models.filter(model => model.contextWindow === undefined).length };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function buildClineContribution(ctx: ExportContext): ManagedContribution {
|
|
63
|
+
const document = buildClineClientConfig(ctx);
|
|
64
|
+
return {
|
|
65
|
+
clientId: "cline",
|
|
66
|
+
fragments: [
|
|
67
|
+
{ path: ["settings", "providers", OPENCODE_PROVIDER_ID], value: document.settings.providers[OPENCODE_PROVIDER_ID] },
|
|
68
|
+
{ path: ["catalog", "providers", OPENCODE_PROVIDER_ID], value: document.catalog.providers[OPENCODE_PROVIDER_ID] },
|
|
69
|
+
],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
@@ -38,6 +38,13 @@ export interface OpencodeCatalogModel {
|
|
|
38
38
|
id?: string;
|
|
39
39
|
contextWindow?: number;
|
|
40
40
|
displayName?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Declared input modalities, carried verbatim from `/api/models`. Serialized as opencode's
|
|
43
|
+
* per-model `attachment` + `modalities`, because opencode gates attachments CLIENT-side:
|
|
44
|
+
* without them every `opencodex` model is text-only in its picker and an image never
|
|
45
|
+
* reaches the proxy or the vision sidecar (#4286).
|
|
46
|
+
*/
|
|
47
|
+
inputModalities?: readonly string[];
|
|
41
48
|
/** Declared effort ladder. Exported as opencode model variants where the client reads them. */
|
|
42
49
|
reasoningEfforts?: readonly string[];
|
|
43
50
|
/**
|
|
@@ -94,7 +101,9 @@ export type ExportClientId =
|
|
|
94
101
|
| "zcode"
|
|
95
102
|
| "prime"
|
|
96
103
|
| "aside"
|
|
97
|
-
| "raycast"
|
|
104
|
+
| "raycast"
|
|
105
|
+
| "omo"
|
|
106
|
+
| "cline";
|
|
98
107
|
|
|
99
108
|
export interface ExportClientSpec {
|
|
100
109
|
id: ExportClientId;
|
|
@@ -72,6 +72,39 @@ export function inputModalitiesForClient(
|
|
|
72
72
|
return kept.length > 0 ? kept : null;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Input modalities opencode's model schema accepts (opencode.ai/config.json, both
|
|
77
|
+
* `modalities.input` and `modalities.output`). Wider than our internal `text | image | audio`
|
|
78
|
+
* vocabulary, so unlike Pi and Gajae this filter can only drop a value no current ingress
|
|
79
|
+
* produces: `/api/custom-models`, `ocx models add` and the catalog writer all normalize to
|
|
80
|
+
* the internal three. It exists so a future ingress cannot do to opencode what `audio` did
|
|
81
|
+
* to Gajae, whose loader rejected the whole config file over one out-of-enum value.
|
|
82
|
+
*/
|
|
83
|
+
const OPENCODE_INPUT_MODALITIES: ReadonlySet<string> = new Set(["text", "audio", "image", "video", "pdf"]);
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* opencode's per-model capability fields for one catalog row, or `undefined` when the row
|
|
87
|
+
* declares nothing.
|
|
88
|
+
*
|
|
89
|
+
* `undefined` rather than `{ input: ["text"] }`: opencode already computes an entry without
|
|
90
|
+
* capabilities as text-only, and leaving the keys out keeps every model that declares
|
|
91
|
+
* nothing byte-identical to what shipped before. A declared list is carried across as-is, so
|
|
92
|
+
* an audio-only row keeps `attachment: true` instead of being rewritten to text it cannot
|
|
93
|
+
* read — the same call Pi's exporter makes, in the opposite direction.
|
|
94
|
+
*/
|
|
95
|
+
export function opencodeModelCapabilities(
|
|
96
|
+
modalities: readonly string[] | undefined,
|
|
97
|
+
): { attachment: boolean; modalities: { input: string[]; output: string[] } } | undefined {
|
|
98
|
+
const input: string[] = [];
|
|
99
|
+
for (const value of modalities ?? []) {
|
|
100
|
+
if (OPENCODE_INPUT_MODALITIES.has(value) && !input.includes(value)) input.push(value);
|
|
101
|
+
}
|
|
102
|
+
if (input.length === 0) return undefined;
|
|
103
|
+
// `attachment` is what opencode's client gates pasting on; `modalities` refines it into
|
|
104
|
+
// which kinds. Output is always text — nothing in the catalog declares otherwise.
|
|
105
|
+
return { attachment: input.some(value => value !== "text"), modalities: { input, output: ["text"] } };
|
|
106
|
+
}
|
|
107
|
+
|
|
75
108
|
/**
|
|
76
109
|
* Label shared by every client: `"<displayName|id> (<native|provider|routed>)"`. The
|
|
77
110
|
* provider suffix is what makes two same-named models from different upstreams
|
|
@@ -6,9 +6,18 @@ import { OPENCODE_PROVIDER_ID, LOOPBACK_API_KEY_PLACEHOLDER } from "./constants"
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* ZCode's `~/.zcode/v2/config.json` provider entry (observed schema, validated
|
|
10
|
-
*
|
|
11
|
-
* OpenAI
|
|
9
|
+
* ZCode's `~/.zcode/v2/config.json` provider entry (observed schema, validated live
|
|
10
|
+
* against ZCode 3.7.7 / 3.8.1 and re-extracted from 3.11.2's bundle).
|
|
11
|
+
* `kind: "openai"` selects the OpenAI Responses protocol, which the proxy serves at
|
|
12
|
+
* `/v1/responses`. ZCode's own dispatch is the authority: its
|
|
13
|
+
* `getDefaultModelProviderEndpointPathForKind` maps `anthropic` to `/v1/messages`,
|
|
14
|
+
* `openai` to `/responses`, and `openai-compatible` to `/chat/completions`.
|
|
15
|
+
*
|
|
16
|
+
* Responses is the only surface the proxy speaks natively. The Chat and Anthropic
|
|
17
|
+
* inbounds translate their body into a Responses shape and replay it through
|
|
18
|
+
* `handleResponses`, then translate the stream back, so the previous
|
|
19
|
+
* `openai-compatible` wiring paid two translations per turn and reshaped tool-call
|
|
20
|
+
* deltas and reasoning blocks on the way through.
|
|
12
21
|
* `apiKeyRequired` keeps ZCode's UI from prompting for a key it does not need on
|
|
13
22
|
* loopback; the serialized key is always the non-secret loopback placeholder.
|
|
14
23
|
*/
|
|
@@ -30,7 +39,7 @@ export interface ZcodeModelEntry {
|
|
|
30
39
|
|
|
31
40
|
export interface ZcodeProviderBlock {
|
|
32
41
|
name: "OpenCodex";
|
|
33
|
-
kind: "openai
|
|
42
|
+
kind: "openai";
|
|
34
43
|
enabled: true;
|
|
35
44
|
source: "custom";
|
|
36
45
|
options: {
|
|
@@ -46,11 +55,12 @@ export interface ZcodeGeneratedConfig {
|
|
|
46
55
|
}
|
|
47
56
|
|
|
48
57
|
/**
|
|
49
|
-
* ZCode dials the OpenAI
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* `
|
|
53
|
-
*
|
|
58
|
+
* ZCode dials the OpenAI Responses surface (`openai`), which appends `/responses` to
|
|
59
|
+
* `baseURL`. We supply `baseURL` with the `/v1` suffix so requests land on
|
|
60
|
+
* `/v1/responses`; ZCode's `normalizeModelProviderBaseUrlForKind` strips only the
|
|
61
|
+
* `/responses` suffix for this kind, so the `/v1` root survives. Model ids are the
|
|
62
|
+
* proxy's canonical `provider/id` selectors, which `/v1/responses` resolves directly.
|
|
63
|
+
* Context limits follow the authoritative-window rule: a model without one ships
|
|
54
64
|
* without `limit` rather than guessing. Modalities are ZCode's observed
|
|
55
65
|
* `text`-floor vocabulary; image-capable rows advertise image input.
|
|
56
66
|
*/
|
|
@@ -73,8 +83,9 @@ export function buildZcodeClientConfig(ctx: ExportContext): ZcodeGeneratedConfig
|
|
|
73
83
|
entry.limit = { context };
|
|
74
84
|
}
|
|
75
85
|
// `none` is a Codex omit-sentinel, not a ZCode picker option. Keep catalog
|
|
76
|
-
// `ultra` when present: ZCode forwards the selected variant
|
|
77
|
-
//
|
|
86
|
+
// `ultra` when present: ZCode forwards the selected variant to the wire field its
|
|
87
|
+
// kind uses — `reasoning.effort` on `openai`, which is what `/v1/responses` reads
|
|
88
|
+
// natively. Set `defaultVariant` only when it survives that filter.
|
|
78
89
|
const efforts = sanitizeCodexReasoningEfforts(model.reasoningEfforts)
|
|
79
90
|
?.filter(effort => effort !== "none");
|
|
80
91
|
if (efforts && efforts.length > 0) {
|
|
@@ -91,7 +102,7 @@ export function buildZcodeClientConfig(ctx: ExportContext): ZcodeGeneratedConfig
|
|
|
91
102
|
provider: {
|
|
92
103
|
[OPENCODE_PROVIDER_ID]: {
|
|
93
104
|
name: "OpenCodex",
|
|
94
|
-
kind: "openai
|
|
105
|
+
kind: "openai",
|
|
95
106
|
enabled: true,
|
|
96
107
|
source: "custom",
|
|
97
108
|
options: {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import { homedir } from "node:os";
|
|
23
23
|
import { existsSync, readFileSync } from "node:fs";
|
|
24
|
-
import { isAbsolute, join, resolve } from "node:path";
|
|
24
|
+
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
25
25
|
import { shouldInjectApiAuthHeader, standaloneCodexRoutingTarget } from "../codex/inject";
|
|
26
26
|
import { FORMAT_MEDIA_TYPE, serializeDocument, type ConfigFormat } from "../integrations/serialize";
|
|
27
27
|
import { canonicalizeReasoningEfforts } from "../reasoning-effort";
|
|
@@ -42,11 +42,12 @@ export { buildRaycastClientConfig, summarizeRaycast, buildRaycastContribution }
|
|
|
42
42
|
|
|
43
43
|
import type { OpencodeLaunchEnv, OpencodeCatalogModel, ExportContext, PiModelEntry, ManagedContribution, ManagedFragment, ExportClientId, ExportClientSpec } from "./config-export/contracts";
|
|
44
44
|
import { OPENCODE_API_KEY_ENV_REF, OPENCODE_PROVIDER_BLOCK_DEFAULT_CONFIG, OPENCODE_CONFIG_SCHEMA, OPENCODE_PROVIDER_ID, PI_API_DIALECT, LOOPBACK_API_KEY_PLACEHOLDER, HERMES_API_KEY_ENV_REF, OPENCLAW_API_KEY_ENV_REF, GAJAE_API_KEY_ENV, OPENCODE_API_KEY_ENV, HERMES_API_KEY_ENV, OPENCLAW_API_KEY_ENV } from "./config-export/constants";
|
|
45
|
-
import { exportModelLabel, authoritativeContextWindow, outputBudgetFor, normalizeExportModels, inputModalitiesForClient, proxyAdmissionHeaders, singleFragment } from "./config-export/model-metadata";
|
|
45
|
+
import { exportModelLabel, authoritativeContextWindow, outputBudgetFor, normalizeExportModels, inputModalitiesForClient, opencodeModelCapabilities, proxyAdmissionHeaders, singleFragment } from "./config-export/model-metadata";
|
|
46
46
|
import { buildOmpClientConfig, summarizeOmp, buildOmpContribution } from "./config-export/omp";
|
|
47
47
|
import { buildDshClientConfig, summarizeDsh, buildDshContribution } from "./config-export/dsh";
|
|
48
48
|
import { buildMcodeClientConfig, summarizeMcode, buildMcodeContribution } from "./config-export/mcode";
|
|
49
49
|
import { buildZcodeClientConfig, summarizeZcode, buildZcodeContribution } from "./config-export/zcode";
|
|
50
|
+
import { buildClineClientConfig, summarizeCline, buildClineContribution } from "./config-export/cline";
|
|
50
51
|
import { buildRaycastClientConfig, summarizeRaycast, buildRaycastContribution } from "./config-export/raycast";
|
|
51
52
|
|
|
52
53
|
|
|
@@ -54,6 +55,14 @@ import { buildRaycastClientConfig, summarizeRaycast, buildRaycastContribution }
|
|
|
54
55
|
export interface OpencodeModelEntry {
|
|
55
56
|
name: string;
|
|
56
57
|
limit?: { context: number; output: number };
|
|
58
|
+
/**
|
|
59
|
+
* opencode's own capability fields, derived from the catalog row's declared input
|
|
60
|
+
* modalities. Written only when the row declares at least one — an entry without them is
|
|
61
|
+
* what opencode already treats as text-only, and omitting them keeps an undeclared model
|
|
62
|
+
* byte-identical to what shipped before.
|
|
63
|
+
*/
|
|
64
|
+
attachment?: boolean;
|
|
65
|
+
modalities?: { input: string[]; output: string[] };
|
|
57
66
|
}
|
|
58
67
|
|
|
59
68
|
/**
|
|
@@ -458,6 +467,51 @@ export function primeConfigPath(env: OpencodeLaunchEnv = process.env, home: stri
|
|
|
458
467
|
return join(primeAgentDir(env, home), "models.json");
|
|
459
468
|
}
|
|
460
469
|
|
|
470
|
+
/**
|
|
471
|
+
* omo resolves its agent directory from THREE variables, in its own order:
|
|
472
|
+
* `OMO_CODING_AGENT_DIR`, then `SENPI_CODING_AGENT_DIR`, then
|
|
473
|
+
* `PI_CODING_AGENT_DIR`, falling back to `~/.omo/agent`. That is not an
|
|
474
|
+
* inference from the family resemblance — `bin/lib/agent-dir.js` publishes the
|
|
475
|
+
* list as `AGENT_DIR_ENV_NAMES` and the launcher pins the first two to whatever
|
|
476
|
+
* it resolves before spawning senpi, so the engine can never disagree with it.
|
|
477
|
+
*
|
|
478
|
+
* The order is load-bearing rather than cosmetic. A user with both
|
|
479
|
+
* `PI_CODING_AGENT_DIR` and `OMO_CODING_AGENT_DIR` set runs omo out of the omo
|
|
480
|
+
* one; checking Pi's first would have us write a catalog omo never reads.
|
|
481
|
+
*
|
|
482
|
+
* Each variable reports under its OWN name, because telling someone that
|
|
483
|
+
* `PI_CODING_AGENT_DIR` is relative when they set `OMO_CODING_AGENT_DIR` sends
|
|
484
|
+
* them to the wrong line of their shell profile. An empty or whitespace value
|
|
485
|
+
* falls through to the next name, which is what omo's own `.trim()`-then-test
|
|
486
|
+
* loop does.
|
|
487
|
+
*
|
|
488
|
+
* One divergence is deliberate: omo `resolve()`s a relative override against its
|
|
489
|
+
* own cwd and does not expand `~`. We refuse the relative form and do expand
|
|
490
|
+
* `~`, exactly as Pi, Prime, MCode and ZCode already do, because a background
|
|
491
|
+
* proxy and a foreground client have different working directories and would
|
|
492
|
+
* otherwise disagree about which file is named.
|
|
493
|
+
*
|
|
494
|
+
* Consequence worth knowing: a user who sets only `PI_CODING_AGENT_DIR` has Pi
|
|
495
|
+
* and omo reading ONE `models.json`. Both clients emit the same provider block
|
|
496
|
+
* through the same builder so the bytes agree; what cannot be shared is the
|
|
497
|
+
* ownership record, since two enabled clients would claim one file. That is
|
|
498
|
+
* omo's contract, not ours to paper over.
|
|
499
|
+
*/
|
|
500
|
+
export function omoAgentDir(env: OpencodeLaunchEnv = process.env, home: string = homedir()): string {
|
|
501
|
+
const omo = env.OMO_CODING_AGENT_DIR?.trim();
|
|
502
|
+
if (omo) return absoluteClientPath(omo, home, "OMO_CODING_AGENT_DIR");
|
|
503
|
+
const senpi = env.SENPI_CODING_AGENT_DIR?.trim();
|
|
504
|
+
if (senpi) return absoluteClientPath(senpi, home, "SENPI_CODING_AGENT_DIR");
|
|
505
|
+
const pi = env.PI_CODING_AGENT_DIR?.trim();
|
|
506
|
+
if (pi) return absoluteClientPath(pi, home, "PI_CODING_AGENT_DIR");
|
|
507
|
+
return join(home, ".omo", "agent");
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/** omo's canonical custom-provider catalog, read by the senpi engine it wraps. */
|
|
511
|
+
export function omoConfigPath(env: OpencodeLaunchEnv = process.env, home: string = homedir()): string {
|
|
512
|
+
return join(omoAgentDir(env, home), "models.json");
|
|
513
|
+
}
|
|
514
|
+
|
|
461
515
|
/**
|
|
462
516
|
* Aside's state root. Unlike every other client here, Aside ships NO variable
|
|
463
517
|
* that relocates it: its CLI carries `ASIDE_DAEMON_BASE_URL`,
|
|
@@ -614,13 +668,30 @@ export function opencodeProviderBlocks(
|
|
|
614
668
|
if (context !== undefined) {
|
|
615
669
|
entry.limit = { context, output: outputBudgetFor(context) };
|
|
616
670
|
}
|
|
671
|
+
// `attachment` / `modalities` are fields of opencode's V1 model schema — the shape its
|
|
672
|
+
// published config.json defines and the one its loader reads (verified against opencode
|
|
673
|
+
// 1.18.30, src/provider/provider.ts: `model.attachment ?? …` / `model.modalities?.input`).
|
|
674
|
+
// They ride on both generations anyway: the two blocks are two spellings of one model list,
|
|
675
|
+
// and the V2 model schema (capabilities.{tools,input,output}, which opencode fills by
|
|
676
|
+
// migrating this same `modalities` field) ignores keys it does not define — its loader
|
|
677
|
+
// decodes with `onExcessProperty: "ignore"`. Same values on both, so a merge cannot make
|
|
678
|
+
// the two entries disagree.
|
|
679
|
+
const capabilities = opencodeModelCapabilities(model.inputModalities);
|
|
680
|
+
if (capabilities) {
|
|
681
|
+
entry.attachment = capabilities.attachment;
|
|
682
|
+
entry.modalities = capabilities.modalities;
|
|
683
|
+
}
|
|
617
684
|
v1Models[key] = entry;
|
|
618
685
|
const variants = opencodeEffortVariants(model);
|
|
619
|
-
// Own `limit`
|
|
620
|
-
// about separately, and an in-place edit of one must never move
|
|
686
|
+
// Own `limit` and `modalities` objects, not shared references: the two blocks are
|
|
687
|
+
// serialized and reasoned about separately, and an in-place edit of one must never move
|
|
688
|
+
// the other.
|
|
621
689
|
v2Models[key] = {
|
|
622
690
|
...entry,
|
|
623
691
|
...(entry.limit ? { limit: { ...entry.limit } } : {}),
|
|
692
|
+
...(entry.modalities
|
|
693
|
+
? { modalities: { input: [...entry.modalities.input], output: [...entry.modalities.output] } }
|
|
694
|
+
: {}),
|
|
624
695
|
...(variants ? { variants } : {}),
|
|
625
696
|
};
|
|
626
697
|
}
|
|
@@ -1109,6 +1180,44 @@ function buildAsideContribution(ctx: ExportContext): ManagedContribution {
|
|
|
1109
1180
|
return singleFragment("aside", ["providers", OPENCODE_PROVIDER_ID], doc.providers[OPENCODE_PROVIDER_ID]);
|
|
1110
1181
|
}
|
|
1111
1182
|
|
|
1183
|
+
/**
|
|
1184
|
+
* omo is the Pi document again, and this time the engine was checked rather
|
|
1185
|
+
* than inferred: `omo-ai@beta` is a launcher around `@code-yeongyu/senpi`, and
|
|
1186
|
+
* senpi's compiled validator accepts what `buildPiClientConfig` emits verbatim —
|
|
1187
|
+
* the keyed `providers`, the `models` ARRAY whose identity is `id`, the
|
|
1188
|
+
* `openai-completions` dialect, the loopback placeholder, and the
|
|
1189
|
+
* `thinkingLevelMap` levels. Evidence:
|
|
1190
|
+
* `devlog/_plan/260912_omo_client_integration/001_omo_contract.md`.
|
|
1191
|
+
*
|
|
1192
|
+
* The flag is passed HERE as well as in the spec's `build`, which is the one
|
|
1193
|
+
* thing Prime and Aside do not do. They pass the default on both paths, so they
|
|
1194
|
+
* are consistent; passing it on only one would make `ocx export --client omo`
|
|
1195
|
+
* emit a `compat` block while enable and refresh wrote a file without it, and
|
|
1196
|
+
* the two would drift apart at the first refresh.
|
|
1197
|
+
*/
|
|
1198
|
+
function buildOmoContribution(ctx: ExportContext): ManagedContribution {
|
|
1199
|
+
const doc = buildPiClientConfig(ctx, true);
|
|
1200
|
+
return singleFragment("omo", ["providers", OPENCODE_PROVIDER_ID], doc.providers[OPENCODE_PROVIDER_ID]);
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
/** Cline's shared SDK store; command-local --config must be mirrored by the env override. */
|
|
1204
|
+
export function clineConfigPath(env: OpencodeLaunchEnv = process.env, home: string = homedir()): string {
|
|
1205
|
+
const explicit = env.CLINE_PROVIDER_SETTINGS_PATH?.trim();
|
|
1206
|
+
const data = env.CLINE_DATA_DIR?.trim();
|
|
1207
|
+
const root = env.CLINE_DIR?.trim();
|
|
1208
|
+
const path = explicit ? absoluteClientPath(explicit, home, "CLINE_PROVIDER_SETTINGS_PATH")
|
|
1209
|
+
: join(data ? absoluteClientPath(data, home, "CLINE_DATA_DIR")
|
|
1210
|
+
: join(root ? absoluteClientPath(root, home, "CLINE_DIR") : join(home, ".cline"), "data"), "settings", "providers.json");
|
|
1211
|
+
if (basename(path).toLowerCase() === "models.json") {
|
|
1212
|
+
throw new ClientPathError("CLINE_PROVIDER_SETTINGS_PATH must differ from the sibling models.json catalog");
|
|
1213
|
+
}
|
|
1214
|
+
return path;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
export function clineSettingsDir(env: OpencodeLaunchEnv = process.env, home: string = homedir()): string {
|
|
1218
|
+
return dirname(clineConfigPath(env, home));
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1112
1221
|
export const EXPORT_CLIENTS: Record<ExportClientId, ExportClientSpec> = {
|
|
1113
1222
|
opencode: {
|
|
1114
1223
|
id: "opencode",
|
|
@@ -1297,6 +1406,49 @@ export const EXPORT_CLIENTS: Record<ExportClientId, ExportClientSpec> = {
|
|
|
1297
1406
|
// remote bind would be a plaintext secret on disk. Refuse instead.
|
|
1298
1407
|
loopbackOnly: true,
|
|
1299
1408
|
},
|
|
1409
|
+
/*
|
|
1410
|
+
* Appended rather than filed beside the other Pi-family clients on purpose.
|
|
1411
|
+
* `EXPORT_CLIENT_IDS` is `Object.keys(EXPORT_CLIENTS)`, so this object's
|
|
1412
|
+
* insertion order IS the public order, and three tests assert it exactly. The
|
|
1413
|
+
* existing sequence is landing order — `pi` second, `prime` eleventh — not a
|
|
1414
|
+
* grouping, so appending is the edit that leaves the other thirteen alone.
|
|
1415
|
+
*/
|
|
1416
|
+
omo: {
|
|
1417
|
+
id: "omo",
|
|
1418
|
+
// Not a bare `models.json`: same Downloads-folder collision argument as
|
|
1419
|
+
// `prime-models.json` and `aside-models.json`.
|
|
1420
|
+
filename: "omo-models.json",
|
|
1421
|
+
destination: env => omoConfigPath(env),
|
|
1422
|
+
apiKeyEnv: "",
|
|
1423
|
+
exportHint: "omo reads a non-secret placeholder from models.json; loopback needs no key.",
|
|
1424
|
+
build: ctx => buildPiClientConfig(ctx, true),
|
|
1425
|
+
format: "json",
|
|
1426
|
+
summarize: summarizePi,
|
|
1427
|
+
buildContribution: buildOmoContribution,
|
|
1428
|
+
/*
|
|
1429
|
+
* Loopback-only for OMP's and Prime's reason, NOT Pi's and Aside's. senpi's
|
|
1430
|
+
* provider block does accept a `headers` map and does interpolate `$ENV` in
|
|
1431
|
+
* its values, so unlike Aside there is somewhere the dedicated admission
|
|
1432
|
+
* header could live. What does not exist is a builder that emits one:
|
|
1433
|
+
* `buildPiClientConfig` writes no headers at all, which is why `pi` is
|
|
1434
|
+
* loopback-only too. Teaching the shared builder to emit them would change
|
|
1435
|
+
* four clients at once, so remote wiring is deferred and a non-loopback bind
|
|
1436
|
+
* refuses rather than generating a config that 401s.
|
|
1437
|
+
*/
|
|
1438
|
+
loopbackOnly: true,
|
|
1439
|
+
},
|
|
1440
|
+
cline: {
|
|
1441
|
+
id: "cline",
|
|
1442
|
+
filename: "cline-config-bundle.json",
|
|
1443
|
+
destination: env => clineConfigPath(env),
|
|
1444
|
+
apiKeyEnv: "",
|
|
1445
|
+
exportHint: "Cline CLI bundle: settings goes in providers.json, catalog in sibling models.json. Stop Cline before enabling/syncing/restoring, then restart. Select --provider opencodex; the default provider stays unchanged. Loopback only.",
|
|
1446
|
+
build: buildClineClientConfig,
|
|
1447
|
+
format: "json",
|
|
1448
|
+
summarize: summarizeCline,
|
|
1449
|
+
buildContribution: buildClineContribution,
|
|
1450
|
+
loopbackOnly: true,
|
|
1451
|
+
},
|
|
1300
1452
|
};
|
|
1301
1453
|
|
|
1302
1454
|
export const EXPORT_CLIENT_IDS: readonly ExportClientId[] = Object.keys(EXPORT_CLIENTS) as ExportClientId[];
|
|
@@ -751,8 +751,13 @@ export async function forceRefreshCodexPoolToken(
|
|
|
751
751
|
};
|
|
752
752
|
}
|
|
753
753
|
|
|
754
|
-
export async function getValidCodexToken(
|
|
755
|
-
|
|
754
|
+
export async function getValidCodexToken(
|
|
755
|
+
id: string,
|
|
756
|
+
options: { signal?: AbortSignal } = {},
|
|
757
|
+
): Promise<CodexTokenResult> {
|
|
758
|
+
// Cancellation ends THIS caller's wait. A shared refresh already in flight keeps running for
|
|
759
|
+
// whoever else awaits it, which is what `awaitOwnCancellation` inside the resolver preserves.
|
|
760
|
+
const result = await resolveCodexToken(id, undefined, options.signal);
|
|
756
761
|
return {
|
|
757
762
|
accessToken: result.accessToken,
|
|
758
763
|
chatgptAccountId: result.chatgptAccountId,
|
|
@@ -1023,7 +1023,7 @@ export async function resolveCodexAuthContext(
|
|
|
1023
1023
|
}
|
|
1024
1024
|
|
|
1025
1025
|
try {
|
|
1026
|
-
const token = await getValidCodexToken(accountId);
|
|
1026
|
+
const token = await getValidCodexToken(accountId, { signal: options.signal });
|
|
1027
1027
|
assertCodexAccountValidationReady(accountId);
|
|
1028
1028
|
return {
|
|
1029
1029
|
kind: "pool",
|
|
@@ -59,9 +59,10 @@ export function nativeEffortClamp(slug: string, effort: string | undefined): str
|
|
|
59
59
|
: [];
|
|
60
60
|
if (levels.length === 0) {
|
|
61
61
|
// Not snapshot-covered. gpt-5.6 natives have a REAL max rung (ensureGpt56ReasoningLevels
|
|
62
|
-
// restores it even off-snapshot) -> never clamp. Every other bare native (gpt-5.5
|
|
63
|
-
// 5.
|
|
64
|
-
// ChatGPT backend error names exactly none..xhigh — so clamp
|
|
62
|
+
// restores it even off-snapshot) -> never clamp. Every other bare native (gpt-5.5,
|
|
63
|
+
// 5.3-codex-spark, a retired slug a client still asks for, and future old-ladder slugs)
|
|
64
|
+
// really stops at xhigh — the ChatGPT backend error names exactly none..xhigh — so clamp
|
|
65
|
+
// the synthetic top tier.
|
|
65
66
|
return isGpt56NativeSlug(slug) ? null : "xhigh";
|
|
66
67
|
}
|
|
67
68
|
const supported = levels.flatMap(l => typeof l.effort === "string" ? [l.effort] : []);
|
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
NATIVE_OPENAI_MODELS,
|
|
49
49
|
SELF_DESCRIBED_NATIVE_OPENAI_MODELS,
|
|
50
50
|
SUPPORTED_NATIVE_OPENAI_SLUGS,
|
|
51
|
+
RETIRED_NATIVE_OPENAI_MODELS,
|
|
51
52
|
hasNativeOpenAiCapabilityMetadata,
|
|
52
53
|
isNativeOpenAiCapabilityAliasModel,
|
|
53
54
|
nativeOpenAiAliasPresentation,
|
|
@@ -63,6 +64,7 @@ export {
|
|
|
63
64
|
NATIVE_OPENAI_MODELS,
|
|
64
65
|
SELF_DESCRIBED_NATIVE_OPENAI_MODELS,
|
|
65
66
|
SUPPORTED_NATIVE_OPENAI_SLUGS,
|
|
67
|
+
RETIRED_NATIVE_OPENAI_MODELS,
|
|
66
68
|
hasNativeOpenAiCapabilityMetadata,
|
|
67
69
|
isNativeOpenAiCapabilityAliasModel,
|
|
68
70
|
nativeOpenAiAliasPresentation,
|
|
@@ -120,8 +122,7 @@ export function isUnsupportedOpenAiNativeSlug(slug: string): boolean {
|
|
|
120
122
|
*
|
|
121
123
|
* This is an OPERATING CAP, not the hard ceiling — the same shape upstream uses. The live
|
|
122
124
|
* catalog reports `context_window: 272000` against a `max_context_window: 872000` for these
|
|
123
|
-
* slugs
|
|
124
|
-
* inside what the model can take.
|
|
125
|
+
* slugs: the advertised window is always well inside what the model can take.
|
|
125
126
|
*
|
|
126
127
|
* The hard ceiling here was measured on 2026-08-17 against a real Codex-login account:
|
|
127
128
|
* `POST /backend-api/codex/responses` admitted 921,508 input tokens and refused 922,013 with
|
|
@@ -162,7 +163,6 @@ const NATIVE_GPT56_FAMILY = new Set<string>([
|
|
|
162
163
|
|
|
163
164
|
export const NATIVE_OPENAI_CONTEXT_OVERRIDES: Record<string, { contextWindow?: number; maxContextWindow?: number; maxInputTokens?: number }> = {
|
|
164
165
|
"gpt-5.5": { contextWindow: 272_000, maxContextWindow: 272_000 },
|
|
165
|
-
"gpt-5.4": { contextWindow: 1_000_000, maxContextWindow: 1_000_000 },
|
|
166
166
|
"gpt-5.3-codex-spark": { contextWindow: 100_000, maxContextWindow: 100_000 },
|
|
167
167
|
"gpt-5.6-sol": { contextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxContextWindow: NATIVE_GPT56_MAX_INPUT_TOKENS, maxInputTokens: NATIVE_GPT56_MAX_INPUT_TOKENS },
|
|
168
168
|
"gpt-5.6-terra": { contextWindow: NATIVE_GPT56_CONTEXT_WINDOW, maxContextWindow: NATIVE_GPT56_MAX_INPUT_TOKENS, maxInputTokens: NATIVE_GPT56_MAX_INPUT_TOKENS },
|
|
@@ -537,7 +537,7 @@ function upstreamNativeEntryForSlug(slug: string): RawEntry | undefined {
|
|
|
537
537
|
const sourceSlug = nativeOpenAiCapabilitySourceSlug(slug);
|
|
538
538
|
// A self-described native returns its OWN pinned row; the alias-cloning branch below stays
|
|
539
539
|
// reserved for slugs that genuinely borrow another model's identity. The allowlist is explicit
|
|
540
|
-
// rather than "has a pinned entry", which would also admit gpt-5.5/gpt-5.
|
|
540
|
+
// rather than "has a pinned entry", which would also admit gpt-5.5/gpt-5.2/codex-auto-review into
|
|
541
541
|
// the sync-replacement authority this map carries.
|
|
542
542
|
if (!sourceSlug.startsWith("gpt-5.6-") && !SELF_DESCRIBED_NATIVE_OPENAI_MODELS.has(slug)) {
|
|
543
543
|
return undefined;
|
|
@@ -696,6 +696,10 @@ function hasNativeCatalogRowShape(entry: RawEntry): boolean {
|
|
|
696
696
|
function observedAccountBoundNativeSlug(entry: RawEntry): string | undefined {
|
|
697
697
|
const accountBound = trustedAccountBoundNativeCatalogSlug(entry);
|
|
698
698
|
const slug = accountBound ?? (typeof entry.slug === "string" ? entry.slug : "");
|
|
699
|
+
// A retired native is refused at this one choke point rather than at each call site: every
|
|
700
|
+
// observation path funnels through here, and an admitted retired slug comes back as a real
|
|
701
|
+
// catalog row — bare or account-qualified — with capabilities synthesized from the template.
|
|
702
|
+
if (RETIRED_NATIVE_OPENAI_MODELS.has(slug)) return undefined;
|
|
699
703
|
if (!isAccountBoundOpenAiNativeSlug(slug)
|
|
700
704
|
|| (entry.supported_in_api !== true && !(slug === NATIVE_RESERVE_MODEL && entry.supported_in_api === false))
|
|
701
705
|
|| (slug === NATIVE_RESERVE_MODEL && entry[RESERVE_METADATA_SOURCE_FIELD] !== undefined
|
|
@@ -72,9 +72,11 @@ const NATIVE_OPENAI_CAPABILITY_SOURCES: Readonly<Record<string, string>> = Objec
|
|
|
72
72
|
*
|
|
73
73
|
* Membership authorizes `upstreamNativeEntryForSlug` to return the pinned entry directly. It is
|
|
74
74
|
* an explicit list, not a structural `PINNED_UPSTREAM_MODELS.has(slug)` predicate: the pin also
|
|
75
|
-
* holds `gpt-5.5`, `gpt-5.
|
|
75
|
+
* holds `gpt-5.5`, `gpt-5.2` and `codex-auto-review`, and admitting those into
|
|
76
76
|
* `UPSTREAM_NATIVE_ENTRIES` would newly authorize replacing their persisted catalog rows during
|
|
77
|
-
* sync — an invariant that map's own comment reserves for the GPT-5.6 family.
|
|
77
|
+
* sync — an invariant that map's own comment reserves for the GPT-5.6 family. The snapshot
|
|
78
|
+
* keeps rows this runtime does not expose, which is exactly why presence in the pin cannot be
|
|
79
|
+
* the predicate: `gpt-5.4` and `gpt-5.4-mini` are still pinned after their retirement.
|
|
78
80
|
*/
|
|
79
81
|
export const SELF_DESCRIBED_NATIVE_OPENAI_MODELS: ReadonlySet<string> = new Set([
|
|
80
82
|
NATIVE_GPT6_ASTRA_MODEL,
|
|
@@ -153,7 +155,7 @@ export function nativeOpenAiAliasPresentation(slug: string): { displayName: stri
|
|
|
153
155
|
* Devlog: 260816_codexrs_multiagent_v2_and_history_perf/011 §4-bis.
|
|
154
156
|
*/
|
|
155
157
|
export const NATIVE_OPENAI_MODELS = [
|
|
156
|
-
"gpt-5.5", "gpt-5.
|
|
158
|
+
"gpt-5.5", "gpt-5.3-codex-spark",
|
|
157
159
|
"gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna",
|
|
158
160
|
NATIVE_DAYBREAK_BLUE_MODEL,
|
|
159
161
|
NATIVE_GPT6_ASTRA_MODEL,
|
|
@@ -161,6 +163,24 @@ export const NATIVE_OPENAI_MODELS = [
|
|
|
161
163
|
|
|
162
164
|
export const SUPPORTED_NATIVE_OPENAI_SLUGS = new Set(NATIVE_OPENAI_MODELS);
|
|
163
165
|
|
|
166
|
+
/**
|
|
167
|
+
* Natives this runtime used to ship that upstream has since retired.
|
|
168
|
+
*
|
|
169
|
+
* Leaving `NATIVE_OPENAI_MODELS` is not enough on its own. An account-bound observation admits
|
|
170
|
+
* any native it sees that is NOT already in `SUPPORTED_NATIVE_OPENAI_SLUGS` — that is how a
|
|
171
|
+
* genuinely new upstream model reaches one entitled account before this repo knows about it. A
|
|
172
|
+
* retired slug fails that same membership test, so a stale `selector/gpt-5.4` row persisted in a
|
|
173
|
+
* user's catalog or models cache would be re-observed as an unknown native and synthesized
|
|
174
|
+
* straight back into the picker, one sync after the removal took it out.
|
|
175
|
+
*
|
|
176
|
+
* This set is the difference between the two cases: unknown-and-new is admitted, known-and-dead
|
|
177
|
+
* is refused. It is deliberately explicit rather than a version heuristic, because the only
|
|
178
|
+
* thing that makes a slug retired is upstream withdrawing it.
|
|
179
|
+
*/
|
|
180
|
+
export const RETIRED_NATIVE_OPENAI_MODELS: ReadonlySet<string> = new Set([
|
|
181
|
+
"gpt-5.4", "gpt-5.4-mini",
|
|
182
|
+
]);
|
|
183
|
+
|
|
164
184
|
/**
|
|
165
185
|
* Natives that retain the physical main account as a read-free sentinel during a native-main
|
|
166
186
|
* drain, instead of reading as unavailable and letting the subagent fallback chain advance.
|
|
@@ -172,7 +192,7 @@ export const SUPPORTED_NATIVE_OPENAI_SLUGS = new Set(NATIVE_OPENAI_MODELS);
|
|
|
172
192
|
* flipped false — letting a drain silently rewrite the operator's configured subagent model.
|
|
173
193
|
*
|
|
174
194
|
* It is an explicit list rather than `SUPPORTED_NATIVE_OPENAI_SLUGS`, which would have widened
|
|
175
|
-
* the sentinel to `gpt-5.5
|
|
195
|
+
* the sentinel to `gpt-5.5` and `gpt-5.3-codex-spark` as well. Those
|
|
176
196
|
* models were never covered, and widening would turn "fell back and answered" into a
|
|
177
197
|
* maintenance error for the most commonly configured fallback slug in the repo. Membership is
|
|
178
198
|
* the set the drain behaviour was actually reasoned about: the account-gated natives plus the
|
|
@@ -183,6 +183,23 @@ export const ROUTED_MODEL_COMPATIBILITY_EXCLUSIONS = new Set([
|
|
|
183
183
|
// Issue #2330: OpenCode Go models absent from current documentation or returning terminal HTTP 400 errors.
|
|
184
184
|
"opencode-go/mimo-v2-omni",
|
|
185
185
|
"opencode-go/mimo-v2-pro",
|
|
186
|
+
/*
|
|
187
|
+
* DeepSeek retired `deepseek-v4-pro` on 2026-09-14 04:00 UTC and routes its requests to
|
|
188
|
+
* V4.1-Flash (api-docs.deepseek.com/news/news260910). Deleting the registry rows removes
|
|
189
|
+
* the model on providers that publish a static roster, but every provider below discovers
|
|
190
|
+
* its models live — there, a deleted row does not remove anything, it only strips the
|
|
191
|
+
* context window, the effort ladder and the text-only hint, so the retired model would
|
|
192
|
+
* keep appearing with its capabilities broken. Excluding the slug is what actually takes
|
|
193
|
+
* it out of the routed catalog.
|
|
194
|
+
*/
|
|
195
|
+
"command-code/deepseek-deepseek-v4-pro",
|
|
196
|
+
"commandcode/deepseek-deepseek-v4-pro",
|
|
197
|
+
"orcarouter/deepseek-deepseek-v4-pro",
|
|
198
|
+
"cline-pass/cline-pass-deepseek-v4-pro",
|
|
199
|
+
"baseten/deepseek-ai-DeepSeek-V4-Pro",
|
|
200
|
+
"digitalocean/deepseek-v4-pro",
|
|
201
|
+
"qoder/DeepSeek-V4-Pro",
|
|
202
|
+
"codebuddy/deepseek-v4-pro",
|
|
186
203
|
]);
|
|
187
204
|
|
|
188
205
|
export function isRoutedModelCompatibilityExcluded(slug: string): boolean {
|
|
@@ -501,7 +518,8 @@ export function applyNativeOpenAiContextOverride(entry: RawEntry, limits?: Nativ
|
|
|
501
518
|
}
|
|
502
519
|
// providerContextCaps.openai is a ceiling for native OpenAI rows regardless of where the
|
|
503
520
|
// advertised window came from (#1430): preserved rows without a hardcoded override (e.g.
|
|
504
|
-
// gpt-5.
|
|
521
|
+
// gpt-5.3-codex-spark) must stay under the cap too, and auto-compaction follows the capped
|
|
522
|
+
// window.
|
|
505
523
|
// The per-model window narrows the same rows for the same reason.
|
|
506
524
|
const currentContext = typeof entry.context_window === "number" ? entry.context_window : undefined;
|
|
507
525
|
const cappedContext = narrowNativeMaxContextWindow(nativeSlug, currentContext, limits);
|