@bitkyc08/opencodex 2.58.0 → 2.60.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 +28 -10
- package/gui/dist/assets/index-BTuCbqQd.css +1 -0
- package/gui/dist/assets/index-DoBVdPHP.js +134 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/crusoe.svg +1 -0
- package/gui/dist/provider-icons/opper.svg +3 -0
- package/package.json +4 -1
- package/src/adapters/anthropic-image-codec.ts +16 -2
- package/src/adapters/anthropic-image-normalize.ts +49 -2
- package/src/adapters/anthropic.ts +4 -1
- package/src/adapters/base.ts +34 -1
- package/src/adapters/coding-agent/turn.ts +22 -2
- package/src/adapters/command-code.ts +50 -3
- package/src/adapters/cursor/catalog.ts +11 -0
- package/src/adapters/cursor/checkpoint-store.ts +3 -0
- package/src/adapters/cursor/discovery.ts +11 -8
- package/src/adapters/cursor/effort-map.ts +16 -2
- package/src/adapters/cursor/envelope-echo.ts +55 -2
- package/src/adapters/cursor/live-transport.ts +26 -9
- package/src/adapters/cursor/message-mapper.ts +3 -2
- package/src/adapters/cursor/protobuf-request.ts +8 -5
- package/src/adapters/cursor/request-builder.ts +21 -4
- package/src/adapters/cursor/thread-continuity.ts +105 -31
- package/src/adapters/cursor/tool-guidance.ts +5 -4
- package/src/adapters/cursor/transport.ts +19 -0
- package/src/adapters/cursor.ts +45 -2
- package/src/adapters/devin/cloud-direct/chat.ts +14 -3
- package/src/adapters/devin/cloud-direct/index.ts +7 -0
- package/src/adapters/devin/cloud-direct/stated-reset-retry.ts +140 -0
- package/src/adapters/devin.ts +125 -26
- package/src/adapters/google-antigravity-replay.ts +1 -1
- package/src/adapters/google-antigravity-wire.ts +55 -4
- package/src/adapters/google-http.ts +57 -11
- package/src/adapters/google-tool-schema.ts +595 -31
- package/src/adapters/google-wire-compiler.ts +93 -10
- package/src/adapters/google-wire-shape.ts +461 -0
- package/src/adapters/google.ts +60 -10
- package/src/adapters/openai-chat/response-events.ts +61 -0
- package/src/adapters/openai-chat-images.ts +3 -1
- package/src/adapters/openai-chat.ts +10 -11
- package/src/adapters/openai-responses/image-gen.ts +8 -6
- package/src/adapters/openai-responses/passthrough.ts +25 -4
- package/src/adapters/openai-responses/reasoning.ts +7 -0
- package/src/adapters/openai-responses/tool-output-recovery.ts +75 -0
- package/src/adapters/openai-responses/tool-schema.ts +19 -7
- package/src/adapters/opencode-go-additional-tools.ts +12 -2
- package/src/adapters/responses-tool-schema.ts +76 -46
- package/src/adapters/run-turn-queue.ts +17 -4
- package/src/bridge/response-json.ts +1 -1
- package/src/bridge/sse.ts +28 -35
- package/src/claude/context-windows.ts +22 -0
- package/src/claude/outbound.ts +35 -4
- package/src/cli/account-api.ts +4 -3
- package/src/cli/account-extended.ts +26 -6
- package/src/cli/account-orca-import.ts +63 -0
- package/src/cli/account.ts +32 -4
- package/src/cli/capabilities.ts +40 -0
- package/src/cli/claude.ts +29 -1
- package/src/cli/codex-cli-update.ts +97 -2
- package/src/cli/dispatch.ts +57 -3
- package/src/cli/doctor.ts +218 -4
- package/src/cli/help.ts +4 -1
- package/src/cli/hub.ts +3 -2
- package/src/cli/index.ts +95 -22
- package/src/cli/models-runtime.ts +33 -4
- package/src/cli/opencode.ts +2 -2
- package/src/cli/provider.ts +13 -1
- package/src/cli/registry.ts +11 -1
- package/src/cli/runtime-api.ts +44 -0
- package/src/cli/start-args.ts +94 -0
- package/src/cli/system-command.ts +2 -0
- package/src/client/machine-api.ts +6 -5
- package/src/client/machine-listener.ts +16 -3
- package/src/client/runtime.ts +26 -2
- package/src/clients/config-export/constants.ts +2 -3
- package/src/clients/config-export.ts +5 -5
- package/src/codex/account-store.ts +146 -5
- package/src/codex/auth-api/account-list.ts +19 -11
- package/src/codex/auth-api/pool-quota-probe.ts +44 -10
- package/src/codex/auth-api/routes.ts +17 -2
- package/src/codex/auth-context.ts +16 -12
- package/src/codex/catalog/build-entries.ts +25 -4
- package/src/codex/catalog/derive-entry.ts +8 -1
- package/src/codex/catalog/effort.ts +10 -6
- package/src/codex/catalog/gather-capture.ts +22 -2
- package/src/codex/catalog/model-hints.ts +66 -33
- package/src/codex/catalog/parsing.ts +90 -5
- package/src/codex/catalog/provider-models.ts +19 -2
- package/src/codex/catalog/reserve-warn.ts +96 -0
- package/src/codex/catalog/retained-sync.ts +41 -26
- package/src/codex/catalog/routed-gather.ts +61 -3
- package/src/codex/cli-installation-identity.ts +210 -0
- package/src/codex/cli-installation-targets.ts +158 -0
- package/src/codex/context-compat.ts +5 -2
- package/src/codex/convergence.ts +5 -0
- package/src/codex/desired-state.ts +4 -1
- package/src/codex/history-job.ts +6 -6
- package/src/codex/history-provider.ts +24 -167
- package/src/codex/history-rollout-read.ts +174 -0
- package/src/codex/history-state-open.ts +105 -0
- package/src/codex/inject/config-toml.ts +44 -2
- package/src/codex/inject.ts +3 -2
- package/src/codex/internal/catalog-writer.ts +33 -1
- package/src/codex/lineage.ts +83 -32
- package/src/codex/loopback-target.ts +31 -0
- package/src/codex/main-account-hard-lock.ts +2 -1
- package/src/codex/main-account.ts +10 -3
- package/src/codex/main-device-reauth.ts +17 -9
- package/src/codex/model-cache.ts +47 -0
- package/src/codex/model-entitlements.ts +80 -2
- package/src/codex/observed-model-denials.ts +230 -0
- package/src/codex/orca-auth-source.ts +94 -0
- package/src/codex/orca-import.ts +219 -0
- package/src/codex/prompt-text-probe.ts +289 -16
- package/src/codex/quota-401-recovery.ts +12 -0
- package/src/codex/quota-types.ts +65 -0
- package/src/codex/quota.ts +24 -19
- package/src/codex/routing/cooldown-math.ts +8 -47
- package/src/codex/routing/pin-drain.ts +57 -0
- package/src/codex/routing.ts +20 -16
- package/src/codex/shim.ts +1 -1
- package/src/codex/subagent-model-fallback.ts +114 -2
- package/src/codex/windows-installation-files.ts +224 -0
- package/src/combos/failover.ts +125 -5
- package/src/config/admitted-identity.ts +222 -0
- package/src/config/diagnostics.ts +43 -1
- package/src/config/feature-flags.ts +5 -0
- package/src/config/load-degrade.ts +33 -0
- package/src/config/pending-teardown.ts +8 -0
- package/src/config/process-state.ts +36 -3
- package/src/config/provider-relative-send-path.ts +16 -0
- package/src/config/proxy-env.ts +31 -7
- package/src/config/schema/compaction-triggers.ts +11 -0
- package/src/config/schema/config-schema.ts +25 -0
- package/src/config/schema/leaf-validators.ts +76 -17
- package/src/config.ts +2 -2
- package/src/generated/compatibility-version.json +410 -258
- package/src/generated/model-metadata.ts +1 -1
- package/src/grok/reset-coupons.ts +38 -19
- package/src/images/loop.ts +6 -1
- package/src/integrations/aside-profile-context.ts +37 -3
- package/src/integrations/aside-profile-journal.ts +68 -3
- package/src/integrations/aside-profiles.ts +128 -3
- package/src/integrations/mutation-plan.ts +815 -0
- package/src/integrations/writer.ts +85 -99
- package/src/lab/live/transport.ts +4 -0
- package/src/lab/live/types.ts +5 -0
- package/src/lab/subject/behavior-fingerprint.ts +1 -1
- package/src/lib/admin-secrets.ts +9 -1
- package/src/lib/bounded-body.ts +4 -2
- package/src/lib/debug-log-buffer.ts +6 -1
- package/src/lib/debug.ts +23 -0
- package/src/lib/destination-policy.ts +48 -6
- package/src/lib/errors.ts +82 -15
- package/src/lib/http-response-semantics.ts +57 -0
- package/src/lib/lab-live-pinned-sender.ts +26 -12
- package/src/lib/local-destinations.ts +32 -5
- package/src/lib/pinned-http.ts +142 -2
- package/src/lib/plain-data.ts +103 -0
- package/src/lib/process-control.ts +13 -5
- package/src/lib/provider-outbound.ts +53 -5
- package/src/lib/proxy-env.ts +70 -3
- package/src/lib/request-execution-budget.ts +11 -3
- package/src/lib/response-body-inactivity.ts +193 -0
- package/src/lib/retry-delay.ts +69 -0
- package/src/lib/socks5-fetch.ts +741 -0
- package/src/lib/spend-ledger-owner.ts +364 -0
- package/src/lib/spend-reservation-ledger.ts +332 -35
- package/src/lib/windows-system-proxy.ts +16 -11
- package/src/lib/workflow-budget.ts +145 -8
- package/src/oauth/account-quota-rank.ts +72 -15
- package/src/oauth/callback-server.ts +4 -3
- package/src/oauth/generic-account-failover.ts +41 -27
- package/src/oauth/health.ts +12 -1
- package/src/oauth/index.ts +3 -107
- package/src/oauth/login-flow-state.ts +127 -0
- package/src/oauth/orcarouter.ts +15 -2
- package/src/oauth/store.ts +8 -0
- package/src/providers/codex-capacity.ts +9 -0
- package/src/providers/derive.ts +34 -17
- package/src/providers/devin-cli-authmode-migration.ts +14 -10
- package/src/providers/devin-provider-merge-migration.ts +33 -12
- package/src/providers/free-directory.ts +20 -2
- package/src/providers/key-failover.ts +327 -25
- package/src/providers/model-rename-migration.ts +56 -1
- package/src/providers/model-rename-startup.ts +7 -5
- package/src/providers/openai-sidecar.ts +4 -0
- package/src/providers/openai-virtual-models.ts +42 -2
- package/src/providers/opencode-go-transport.ts +14 -5
- package/src/providers/quota/antigravity.ts +22 -2
- package/src/providers/quota/report-cache.ts +3 -0
- package/src/providers/quota/vendor-probes-key.ts +1 -1
- package/src/providers/registry/entries-core.ts +39 -17
- package/src/providers/registry/entries-extended.ts +131 -1
- package/src/providers/registry/model-ids.ts +168 -0
- package/src/providers/registry/model-seeds.ts +87 -21
- package/src/providers/registry/types.ts +2 -0
- package/src/providers/resolved-model-policy-merge.ts +167 -0
- package/src/providers/resolved-model-policy.ts +406 -0
- package/src/providers/stale-vision-classification-migration.ts +137 -0
- package/src/responses/apply-patch-envelope.ts +32 -11
- package/src/responses/bridge-search-replay-cache.ts +152 -0
- package/src/responses/code-mode-helper-compat.ts +26 -16
- package/src/responses/custom-tool-compat.ts +1 -1
- package/src/responses/freeform-wrapper-scan.ts +279 -0
- package/src/responses/hosted-tool-policy.ts +85 -2
- package/src/responses/legacy-dotted-tool-name-repair.ts +134 -0
- package/src/responses/progressive-freeform-input.ts +130 -0
- package/src/responses/reasoning-envelope.ts +30 -0
- package/src/responses/schema.ts +9 -2
- package/src/responses/state.ts +5 -12
- package/src/responses/tool-name-aliases.ts +15 -1
- package/src/router.ts +91 -115
- package/src/routing/compatibility/behavior.ts +9 -0
- package/src/routing/compatibility/subject.ts +16 -1
- package/src/server/adapter-resolve.ts +9 -0
- package/src/server/auth-cors.ts +29 -0
- package/src/server/chat-completions.ts +13 -5
- package/src/server/chat-native-sse.ts +26 -9
- package/src/server/chat-native.ts +10 -4
- package/src/server/claude-messages.ts +30 -5
- package/src/server/effort-row.ts +11 -3
- package/src/server/grok-responses-control-frame.ts +160 -1
- package/src/server/gui-static.ts +36 -2
- package/src/server/inbound-body-admission.ts +187 -0
- package/src/server/index/serve-options.ts +86 -29
- package/src/server/index/spend-ledger-lifecycle.ts +66 -0
- package/src/server/index/websocket-handler.ts +6 -1
- package/src/server/index.ts +24 -25
- package/src/server/management/api-access.ts +3 -4
- package/src/server/management/aside-profile-routes.ts +266 -7
- package/src/server/management/config-routes.ts +48 -7
- package/src/server/management/context.ts +3 -0
- package/src/server/management/integration-routes.ts +287 -5
- package/src/server/management/metrics-routes.ts +20 -0
- package/src/server/management/model-rows.ts +224 -12
- package/src/server/management/provider-capability-config.ts +35 -7
- package/src/server/management/provider-routes.ts +70 -18
- package/src/server/management/route-registry.ts +14 -0
- package/src/server/management/shared.ts +10 -3
- package/src/server/management/system-restart.ts +7 -2
- package/src/server/management/system-routes.ts +2 -0
- package/src/server/management/usage-aggregate-cache.ts +4 -0
- package/src/server/management-api.ts +2 -0
- package/src/server/management-auth.ts +15 -1
- package/src/server/proxy-liveness.ts +97 -2
- package/src/server/readiness.ts +29 -10
- package/src/server/relay-eager.ts +24 -2
- package/src/server/relay.ts +136 -34
- package/src/server/request-log.ts +80 -3
- package/src/server/request-metrics.ts +236 -0
- package/src/server/responses/adapter-continuation.ts +74 -30
- package/src/server/responses/adapter-delivery.ts +39 -8
- package/src/server/responses/adapter-dispatch.ts +63 -30
- package/src/server/responses/compact.ts +89 -18
- package/src/server/responses/compaction-routing.ts +111 -0
- package/src/server/responses/core-codex-account.ts +90 -24
- package/src/server/responses/core-combo.ts +7 -7
- package/src/server/responses/core-normalize.ts +16 -15
- package/src/server/responses/core-opaque-recovery.ts +1 -0
- package/src/server/responses/core-options.ts +4 -0
- package/src/server/responses/encrypted-payload.ts +20 -2
- package/src/server/responses/fetch-helpers.ts +68 -2
- package/src/server/responses/passthrough-delivery.ts +41 -15
- package/src/server/responses/passthrough-dispatch.ts +145 -53
- package/src/server/responses/passthrough-execution.ts +11 -1
- package/src/server/responses/policy-fallback.ts +5 -13
- package/src/server/responses/request-prepare.ts +96 -17
- package/src/server/responses/request-send-budget.ts +89 -8
- package/src/server/responses/request-sidecar-auth.ts +17 -9
- package/src/server/responses/request-spend.ts +38 -9
- package/src/server/responses/request-transport.ts +15 -12
- package/src/server/responses/run-turn-execution.ts +45 -8
- package/src/server/responses/sidecar-execution.ts +19 -2
- package/src/server/responses/ws-upstream.ts +16 -28
- package/src/server/responses-custom-tool-repair.ts +29 -56
- package/src/server/responses-undeclared-tool-guard.ts +31 -1
- package/src/server/sse-frame-buffer.ts +12 -10
- package/src/server/sse-payload-rewrite.ts +37 -10
- package/src/server/system-env-shell.ts +5 -1
- package/src/server/system-env.ts +7 -1
- package/src/server/workflow-refusal.ts +56 -2
- package/src/service/cli.ts +16 -6
- package/src/service/guards.ts +10 -0
- package/src/service/health.ts +43 -0
- package/src/service/state.ts +7 -2
- package/src/tray/windows-tray.ps1 +155 -3
- package/src/types/accounts.ts +4 -0
- package/src/types/config.ts +111 -6
- package/src/types/provider.ts +37 -0
- package/src/types/request.ts +9 -1
- package/src/types/tools.ts +14 -0
- package/src/types/wire.ts +9 -1
- package/src/types.ts +1 -0
- package/src/usage/expected-prices.ts +28 -0
- package/src/usage/log.ts +87 -4
- package/src/vision/eligibility.ts +88 -9
- package/src/vision/plan.ts +34 -10
- package/src/web-search/executor.ts +41 -2
- package/src/web-search/loop.ts +6 -1
- package/src/web-search/passthrough-bridge.ts +39 -5
- package/gui/dist/assets/index-BbrHOIY0.js +0 -128
- package/gui/dist/assets/index-C5-RdDmD.css +0 -1
|
@@ -12,10 +12,12 @@ function hasHeaderCaseInsensitive(
|
|
|
12
12
|
return Object.keys(headers ?? {}).some(key => key.toLowerCase() === target);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
/** Derive a provider-scoped opaque value without exposing Codex task or subagent ids. */
|
|
16
|
-
export function deriveOpenCodeGoSessionId(sessionLane: string): string {
|
|
15
|
+
/** Derive a provider- and wire-scoped opaque value without exposing Codex task or subagent ids. */
|
|
16
|
+
export function deriveOpenCodeGoSessionId(sessionLane: string, wireProtocol: string): string {
|
|
17
17
|
const digest = createHash("sha256")
|
|
18
|
-
.update("opencodex/opencode-go/session/
|
|
18
|
+
.update("opencodex/opencode-go/session/v2\0")
|
|
19
|
+
.update(wireProtocol)
|
|
20
|
+
.update("\0")
|
|
19
21
|
.update(sessionLane)
|
|
20
22
|
.digest("hex")
|
|
21
23
|
.slice(0, 32);
|
|
@@ -30,12 +32,19 @@ export function deriveOpenCodeGoSessionId(sessionLane: string): string {
|
|
|
30
32
|
* request reaching this helper from the proxy always carries a lane. The `!sessionLane` guard stays
|
|
31
33
|
* for direct callers that have no request context; it is not a per-request identity of its own, and
|
|
32
34
|
* minting one here would hand each retry a different value.
|
|
35
|
+
*
|
|
36
|
+
* `provider` is already settled onto the final wire, while `destinationProvider` is the original
|
|
37
|
+
* routed row. Keeping both explicit prevents an Anthropic hard pin from defeating the registry's
|
|
38
|
+
* adapter-sensitive destination recognition.
|
|
33
39
|
*/
|
|
34
40
|
export function resolveOpenCodeGoTransport<T extends OcxProviderConfig>(
|
|
35
41
|
provider: T,
|
|
36
42
|
sessionLane: string | undefined,
|
|
43
|
+
destinationProvider:
|
|
44
|
+
Pick<OcxProviderConfig, "baseUrl" | "adapter">
|
|
45
|
+
& Partial<Pick<OcxProviderConfig, "authMode">>,
|
|
37
46
|
): T {
|
|
38
|
-
if (registryEntryForProviderDestination(
|
|
47
|
+
if (registryEntryForProviderDestination(destinationProvider)?.id !== "opencode-go") return provider;
|
|
39
48
|
if (!sessionLane) return provider;
|
|
40
49
|
if (hasHeaderCaseInsensitive(provider.headers, OPENCODE_GO_SESSION_HEADER)) return provider;
|
|
41
50
|
|
|
@@ -43,7 +52,7 @@ export function resolveOpenCodeGoTransport<T extends OcxProviderConfig>(
|
|
|
43
52
|
...provider,
|
|
44
53
|
headers: {
|
|
45
54
|
...(provider.headers ?? {}),
|
|
46
|
-
[OPENCODE_GO_SESSION_HEADER]: deriveOpenCodeGoSessionId(sessionLane),
|
|
55
|
+
[OPENCODE_GO_SESSION_HEADER]: deriveOpenCodeGoSessionId(sessionLane, provider.adapter),
|
|
47
56
|
},
|
|
48
57
|
};
|
|
49
58
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { antigravityUserAgent } from "../../adapters/client-fingerprint";
|
|
2
2
|
import { DestinationDnsResolutionError } from "../../lib/destination-policy";
|
|
3
|
-
import { PinnedHttpError } from "../../lib/pinned-http";
|
|
3
|
+
import { PinnedHttpError, type PinnedHttpErrorCode } from "../../lib/pinned-http";
|
|
4
4
|
import { ProviderOutboundPolicyError, providerOutboundPost, providerRedirectError, type ProviderOutboundDependencies } from "../../lib/provider-outbound";
|
|
5
5
|
import { getValidAccessToken } from "../../oauth";
|
|
6
6
|
import { getAccountCredential, getCredential } from "../../oauth/store";
|
|
@@ -193,10 +193,30 @@ type AntigravityQuotaProbeResult =
|
|
|
193
193
|
| { kind: "available"; quota: ProviderQuota; source: "google-antigravity:retrieveUserQuotaSummary" | "google-antigravity:fetchAvailableModels" }
|
|
194
194
|
| { kind: "unavailable"; failure: QuotaFailureCode; legacy: { kind: "null" } | { kind: "throw"; error: unknown } };
|
|
195
195
|
|
|
196
|
+
/**
|
|
197
|
+
* Every pinned-transport failure code, classified once.
|
|
198
|
+
*
|
|
199
|
+
* A conditional that named one code and sent the rest to `timeout` was correct only for as long
|
|
200
|
+
* as the union held exactly the codes it was written against. When the transport learned to
|
|
201
|
+
* report a coding it cannot undo, that answer was reported as a timeout, which is a different
|
|
202
|
+
* operational story entirely. A total map makes a new code a compile error here rather than a
|
|
203
|
+
* quiet misdiagnosis.
|
|
204
|
+
*/
|
|
205
|
+
const PINNED_QUOTA_FAILURES = {
|
|
206
|
+
connect_timeout: "timeout",
|
|
207
|
+
first_byte_timeout: "timeout",
|
|
208
|
+
inactivity_timeout: "timeout",
|
|
209
|
+
// The response arrived and cannot be used: too large, coded in a format this transport cannot
|
|
210
|
+
// undo, or coded bytes that did not decode. None of these is a timing failure.
|
|
211
|
+
output_byte_limit: "response_unusable",
|
|
212
|
+
unsupported_content_encoding: "response_unusable",
|
|
213
|
+
content_decode_failed: "response_unusable",
|
|
214
|
+
} satisfies Record<PinnedHttpErrorCode, QuotaFailureCode>;
|
|
215
|
+
|
|
196
216
|
function quotaTransportFailure(error: unknown): QuotaFailureCode {
|
|
197
217
|
if (error instanceof ProviderOutboundPolicyError) return "destination_blocked";
|
|
198
218
|
if (error instanceof DestinationDnsResolutionError) return "dns_failed";
|
|
199
|
-
if (error instanceof PinnedHttpError) return error.code
|
|
219
|
+
if (error instanceof PinnedHttpError) return PINNED_QUOTA_FAILURES[error.code];
|
|
200
220
|
if (error instanceof DOMException && error.name === "TimeoutError") return "timeout";
|
|
201
221
|
return "transport_error";
|
|
202
222
|
}
|
|
@@ -151,6 +151,9 @@ export function providerQuotaFromCodexQuota(
|
|
|
151
151
|
const projected: CodexCapacityQuota = {
|
|
152
152
|
...(quota.shortPercent !== undefined ? { fiveHourPercent: quota.shortPercent } : {}),
|
|
153
153
|
...(quota.shortResetAt !== undefined ? { fiveHourResetAt: quota.shortResetAt } : {}),
|
|
154
|
+
// Freshness for the reset-less terminal rule. Without it the dashboard evaluates that rule
|
|
155
|
+
// with no evidence and returns null while routing refuses the same account (#5045).
|
|
156
|
+
...(quota.shortObservedAt !== undefined ? { shortObservedAt: quota.shortObservedAt } : {}),
|
|
154
157
|
...(quota.weeklyPercent !== undefined ? { weeklyPercent: quota.weeklyPercent } : {}),
|
|
155
158
|
...(quota.weeklyResetAt !== undefined ? { weeklyResetAt: quota.weeklyResetAt } : {}),
|
|
156
159
|
...(quota.monthlyPercent !== undefined ? { monthlyPercent: quota.monthlyPercent } : {}),
|
|
@@ -915,7 +915,7 @@ async function fetchNeuralwattQuota(provider: string, config: OcxProviderConfig)
|
|
|
915
915
|
}
|
|
916
916
|
|
|
917
917
|
|
|
918
|
-
function normalizedBaseUrl(value: string): string | null {
|
|
918
|
+
export function normalizedBaseUrl(value: string): string | null {
|
|
919
919
|
try {
|
|
920
920
|
const url = new URL(value);
|
|
921
921
|
if (url.username || url.password || url.search || url.hash) return null;
|
|
@@ -30,6 +30,8 @@ import {
|
|
|
30
30
|
OPENAI_API_GPT56_REASONING_EFFORTS,
|
|
31
31
|
META_MUSE_REASONING_EFFORTS,
|
|
32
32
|
META_MUSE_REASONING_EFFORT_MAP,
|
|
33
|
+
META_MUSE_CODE_REASONING_EFFORTS,
|
|
34
|
+
META_MUSE_CODE_REASONING_EFFORT_MAP,
|
|
33
35
|
META_MUSE_CONTEXT_WINDOW,
|
|
34
36
|
META_MUSE_MODELS,
|
|
35
37
|
OPENAI_DAYBREAK_MODELS,
|
|
@@ -599,12 +601,15 @@ export const PROVIDER_REGISTRY_CORE: readonly ProviderRegistryEntry[] = [
|
|
|
599
601
|
label: "Meta Muse Code (CLI credential)",
|
|
600
602
|
adapter: "openai-responses",
|
|
601
603
|
baseUrl: "https://api.meta.ai/v1",
|
|
602
|
-
// Meta own client sends
|
|
603
|
-
//
|
|
604
|
+
// Meta's own client sends these on every Muse Code call. The compatibility marker
|
|
605
|
+
// is transparent while selecting the credential surface that accepts `max`.
|
|
604
606
|
// Declared here rather than in a transport hook so it also covers model discovery
|
|
605
607
|
// (src/oauth/index.ts:1176) and still yields to a user-set header
|
|
606
608
|
// (mergeRegistryStaticHeaders, src/providers/registry.ts:3494).
|
|
607
|
-
staticHeaders: {
|
|
609
|
+
staticHeaders: {
|
|
610
|
+
"User-Agent": "muse-build/1.3.0 (opencodex compatibility)",
|
|
611
|
+
"x-api-version": "1.0.0",
|
|
612
|
+
},
|
|
608
613
|
authKind: "oauth",
|
|
609
614
|
oauthId: "meta-muse",
|
|
610
615
|
dashboardUrl: "https://dev.meta.ai",
|
|
@@ -615,8 +620,8 @@ export const PROVIDER_REGISTRY_CORE: readonly ProviderRegistryEntry[] = [
|
|
|
615
620
|
liveModels: false,
|
|
616
621
|
modelContextWindows: Object.fromEntries(META_MUSE_MODELS.map(id => [id, META_MUSE_CONTEXT_WINDOW])),
|
|
617
622
|
modelInputModalities: Object.fromEntries(META_MUSE_MODELS.map(id => [id, ["text", "image"] as ["text", "image"]])),
|
|
618
|
-
modelReasoningEfforts: Object.fromEntries(META_MUSE_MODELS.map(id => [id,
|
|
619
|
-
modelReasoningEffortMap: Object.fromEntries(META_MUSE_MODELS.map(id => [id,
|
|
623
|
+
modelReasoningEfforts: Object.fromEntries(META_MUSE_MODELS.map(id => [id, META_MUSE_CODE_REASONING_EFFORTS])),
|
|
624
|
+
modelReasoningEffortMap: Object.fromEntries(META_MUSE_MODELS.map(id => [id, META_MUSE_CODE_REASONING_EFFORT_MAP])),
|
|
620
625
|
note: "Signs in to Meta with a browser device code on any platform, then mints the Muse Code subscription key. That grant is reimplemented from the one the Muse Code CLI performs and has NOT been exercised against Meta from OpenCodex, so treat the first login as unverified. If the Muse Code CLI is already signed in on macOS, the existing key is imported instead of starting a new grant. A pasted key from https://dev.meta.ai still works as a fallback when a device login cannot complete, and faces the same format check and live validation. A device login authenticates as Meta own Muse Code client, which is a stronger claim than reusing a key the CLI already minted. Meta scopes that credential to the Muse Code CLI, so this is an UNSUPPORTED use: Meta does not authorize subscription coverage outside its own CLI, how these calls settle is not observable from the API, and you should treat every call as billable against your account. The key, imported or pasted, is copied into OpenCodex's auth store. For an account signed in with the device login, OpenCodex refreshes Meta's subscription windows on demand from the same key endpoint the login uses, at most once every five minutes. For an imported or pasted key there is no endpoint to query them on demand, so OpenCodex reads them from streaming responses and shows the last observed value with its age; refreshing one then requires another streaming turn, and translated (non-passthrough) turns report none. Rate limits apply per team, not per key. For a supported path use the meta-model provider with your own key (export it as META_MODEL_API_KEY).",
|
|
621
626
|
},
|
|
622
627
|
{
|
|
@@ -652,6 +657,11 @@ export const PROVIDER_REGISTRY_CORE: readonly ProviderRegistryEntry[] = [
|
|
|
652
657
|
// Zen Go can close a Chat stream after a fully assembled function call without sending
|
|
653
658
|
// finish_reason or [DONE] (#2260). The adapter still rejects incomplete argument JSON.
|
|
654
659
|
openaiChatEofTolerance: true,
|
|
660
|
+
// Muse Spark on OpenCode Go can sit silent during prolonged reasoning and close without a protocol terminal.
|
|
661
|
+
modelResponsesTerminalRepair: {
|
|
662
|
+
"muse-spark-1.2-contributor": { graceMs: 5_000 },
|
|
663
|
+
"muse-spark-1.3-contributor": { graceMs: 5_000 },
|
|
664
|
+
},
|
|
655
665
|
// Go rejects reasoning.encrypted_content with previous_response_id (#3838).
|
|
656
666
|
// Use explicit replay history and the existing stateless Responses policy.
|
|
657
667
|
statelessResponses: true,
|
|
@@ -696,17 +706,25 @@ export const PROVIDER_REGISTRY_CORE: readonly ProviderRegistryEntry[] = [
|
|
|
696
706
|
"glm-5.3-flash": ["text", "image"],
|
|
697
707
|
// Experimental DeepSeek vision preview — expected to merge into deepseek-v4-flash later.
|
|
698
708
|
[DEEPSEEK_VISION_PREVIEW_MODEL]: ["text", "image"],
|
|
699
|
-
// This route
|
|
700
|
-
//
|
|
701
|
-
//
|
|
702
|
-
//
|
|
703
|
-
//
|
|
704
|
-
//
|
|
705
|
-
//
|
|
706
|
-
//
|
|
707
|
-
//
|
|
708
|
-
//
|
|
709
|
-
|
|
709
|
+
// This route became natively multimodal; it is NOT a sidecar consumer.
|
|
710
|
+
//
|
|
711
|
+
// History: the id was declared text-only here and listed in this preset's
|
|
712
|
+
// noVisionModels, which routed its images through the vision sidecar (#4505).
|
|
713
|
+
// That classification came from jawcode metadata and went stale. Probed
|
|
714
|
+
// 2026-09-19 against https://opencode.ai/zen/go/v1/chat/completions with the
|
|
715
|
+
// headers this proxy sends: the route accepts an image_url part and the model
|
|
716
|
+
// reads it correctly (a four-band colour chart was described in the right
|
|
717
|
+
// order). Its sibling deepseek-v4-flash on the same gateway still answers
|
|
718
|
+
// HTTP 400 "Model only supports text input", which is what keeps the two
|
|
719
|
+
// distinct here rather than collapsing them.
|
|
720
|
+
//
|
|
721
|
+
// The declaration is what reaches an EXISTING install: derive.ts fills
|
|
722
|
+
// noVisionModels all-or-nothing, so a config persisted while the stale list
|
|
723
|
+
// was current keeps it forever, and modelInputModalities is filled per-key
|
|
724
|
+
// BENEATH the saved value. Both halves are repaired by
|
|
725
|
+
// stale-vision-classification-migration.ts; correcting the registry alone
|
|
726
|
+
// would fix new installs and leave existing ones stripping images.
|
|
727
|
+
"deepseek-v4.1-flash": ["text", "image"],
|
|
710
728
|
// Muse Spark Contributor is natively multimodal on Zen Go: it accepts input_image
|
|
711
729
|
// parts over /responses (probed 2026-08-26). Without this declaration the catalog
|
|
712
730
|
// advertises it text-only and the Codex app blocks image attachments client-side with
|
|
@@ -760,7 +778,10 @@ export const PROVIDER_REGISTRY_CORE: readonly ProviderRegistryEntry[] = [
|
|
|
760
778
|
// Kimi K2.7 Code accepts text+image+video: do NOT list it here.
|
|
761
779
|
noVisionModels: [
|
|
762
780
|
"glm-5.3", "glm-5.2", "glm-5", "glm-5.1",
|
|
763
|
-
|
|
781
|
+
// deepseek-v4.1-flash is deliberately absent: probed natively multimodal on this
|
|
782
|
+
// gateway 2026-09-19 (see the modelInputModalities note above). Its sibling
|
|
783
|
+
// deepseek-v4-flash stays listed — that route rejects image_url upstream.
|
|
784
|
+
"deepseek-v4-flash",
|
|
764
785
|
"mimo-v2-pro", "mimo-v2.5-pro",
|
|
765
786
|
"minimax-m2.5", "minimax-m2.7",
|
|
766
787
|
"qwen3.7-max",
|
|
@@ -1237,3 +1258,4 @@ export const PROVIDER_REGISTRY_CORE: readonly ProviderRegistryEntry[] = [
|
|
|
1237
1258
|
note: "Serverless Inference subscription API. Live discovery exposes only kimi-k2-instruct because Vultr documents it as the sole tool-calling model.",
|
|
1238
1259
|
},
|
|
1239
1260
|
];
|
|
1261
|
+
|
|
@@ -98,6 +98,10 @@ import {
|
|
|
98
98
|
DIGITALOCEAN_CHAT_COMPLETION_MODELS,
|
|
99
99
|
SCALEWAY_SERVERLESS_CHAT_MODELS,
|
|
100
100
|
SCALEWAY_MODEL_INPUT_MODALITIES,
|
|
101
|
+
OPPER_MODELS,
|
|
102
|
+
OPPER_MODEL_CONTEXT_WINDOWS,
|
|
103
|
+
OPPER_MODEL_MAX_OUTPUT_TOKENS,
|
|
104
|
+
OPPER_MODEL_INPUT_MODALITIES,
|
|
101
105
|
} from "./model-seeds";
|
|
102
106
|
|
|
103
107
|
export const PROVIDER_REGISTRY_EXTENDED: readonly ProviderRegistryEntry[] = [
|
|
@@ -219,6 +223,72 @@ export const PROVIDER_REGISTRY_EXTENDED: readonly ProviderRegistryEntry[] = [
|
|
|
219
223
|
},
|
|
220
224
|
note: "Shared Token Factory text-output inference only; live discovery excludes embedding and image-generation rows.",
|
|
221
225
|
},
|
|
226
|
+
{
|
|
227
|
+
// Primary sources checked 2026-09-11:
|
|
228
|
+
// - https://docs.crusoecloud.com/quickstart/getting-started-with-serverless-inference documents
|
|
229
|
+
// the fixed OpenAI-compatible host https://api.inference.crusoecloud.com/v1, Bearer API keys
|
|
230
|
+
// created in the Cloud console (Intelligence Foundry > Inference > Create API Key), and an
|
|
231
|
+
// OpenAI SDK chat.completions example against meta-llama/Llama-3.3-70B-Instruct.
|
|
232
|
+
// - https://docs.crusoecloud.com/serverless-inference/available-models lists the served models
|
|
233
|
+
// with slash-delimited ids; https://docs.crusoecloud.com/serverless-inference/rate-limits
|
|
234
|
+
// documents per-project, per-model TPM/RPM limits (429 when exceeded, 503 under shared load).
|
|
235
|
+
// - GET /v1/models rejects unauthenticated requests with 401 {"errors":["Authentication failed"]},
|
|
236
|
+
// so a successful authenticated list response is evidence that the supplied key is valid.
|
|
237
|
+
// An authenticated capture on 2026-09-12 returned 18 rows shaped like OpenRouter's catalog
|
|
238
|
+
// (`is_public`, `type`, `context_length`, `architecture.modality` of "text" or "multimodal",
|
|
239
|
+
// `tags`, `pricing`, `supported_parameters`); 17 were public serverless models and one was an
|
|
240
|
+
// account-private dedicated deployment with empty `type`/`modality`. `type` is blank on one
|
|
241
|
+
// public model, so the filter keys on `is_public` plus `architecture.modality` instead.
|
|
242
|
+
// - https://legal.crusoe.ai/ hosts the Crusoe Cloud Platform Terms of Service v1.10 (effective
|
|
243
|
+
// 2026-08-10), which name Crusoe Technologies LLC as the contracting entity, and the Service
|
|
244
|
+
// Specific Terms v5.0 (effective 2026-07-14), whose Crusoe Intelligence Foundry Terms cover the
|
|
245
|
+
// Managed Inference Service reached through the Crusoe API.
|
|
246
|
+
// - https://models.dev/api.json (provider "crusoe") records openai/gpt-oss-120b as the one served
|
|
247
|
+
// model with a low/medium/high reasoning_effort ladder; the other reasoning models expose an
|
|
248
|
+
// on/off toggle only.
|
|
249
|
+
// Maintainer: @acheamponge, who works at Crusoe (affiliation disclosed) and also maintains the
|
|
250
|
+
// models.dev crusoe entry.
|
|
251
|
+
id: "crusoe",
|
|
252
|
+
label: "Crusoe",
|
|
253
|
+
baseUrl: "https://api.inference.crusoecloud.com/v1",
|
|
254
|
+
adapter: "openai-chat",
|
|
255
|
+
authKind: "key",
|
|
256
|
+
dashboardUrl: "https://console.crusoecloud.com",
|
|
257
|
+
liveModels: true,
|
|
258
|
+
preserveCustomDestination: true,
|
|
259
|
+
// The getting-started guide documents tools through the OpenAI SDK but no provider-wide
|
|
260
|
+
// parallel tool-call contract.
|
|
261
|
+
parallelToolCalls: false,
|
|
262
|
+
// Only gpt-oss-120b has a real effort ladder; toggle-style reasoning models must not be promoted
|
|
263
|
+
// to Codex's full fallback ladder.
|
|
264
|
+
reasoningEfforts: [],
|
|
265
|
+
modelReasoningEfforts: { "openai/gpt-oss-120b": ["low", "medium", "high"] },
|
|
266
|
+
directReasoningEffortModels: ["openai/gpt-oss-120b"],
|
|
267
|
+
// The catalog reports `architecture.modality: "multimodal"` without an input list. Four rows
|
|
268
|
+
// also carry the explicit "image text to text" tag; yutori/n2 instead reports multimodal
|
|
269
|
+
// type/modality plus browser/computer-use tags. Those five captured rows are classified here.
|
|
270
|
+
modelInputModalities: {
|
|
271
|
+
"google/gemma-4-31b-it": ["text", "image"],
|
|
272
|
+
"moonshotai/Kimi-K2.6": ["text", "image"],
|
|
273
|
+
"nvidia/Nemotron-3-Nano-Omni-Reasoning-30B-A3B": ["text", "image"],
|
|
274
|
+
"yutori/n2": ["text", "image"],
|
|
275
|
+
"zai-org/GLM-5.3-Flash": ["text", "image"],
|
|
276
|
+
},
|
|
277
|
+
modelDiscovery: {
|
|
278
|
+
path: "models",
|
|
279
|
+
maxResponseBytes: 256 * 1024,
|
|
280
|
+
maxModels: 256,
|
|
281
|
+
filter: {
|
|
282
|
+
// Keep public serverless rows whose architecture produces text; account-private
|
|
283
|
+
// deployments (blank modality) and any embedding or media rows fail closed.
|
|
284
|
+
allOf: [
|
|
285
|
+
{ path: ["is_public"], equalsAny: [true] },
|
|
286
|
+
{ path: ["architecture", "modality"], equalsAny: ["text", "multimodal"] },
|
|
287
|
+
],
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
note: "Public Serverless Inference chat models on the shared OpenAI-compatible host; account-private and self-serve dedicated deployments are excluded from discovery and out of scope.",
|
|
291
|
+
},
|
|
222
292
|
{
|
|
223
293
|
id: "digitalocean",
|
|
224
294
|
label: "DigitalOcean Serverless Inference",
|
|
@@ -676,9 +746,22 @@ export const PROVIDER_REGISTRY_EXTENDED: readonly ProviderRegistryEntry[] = [
|
|
|
676
746
|
id: "volcengine-coding-plan",
|
|
677
747
|
label: "Volcengine Ark Coding Plan",
|
|
678
748
|
baseUrl: "https://ark.cn-beijing.volces.com/api/coding/v3",
|
|
679
|
-
|
|
749
|
+
responsesPath: "/responses",
|
|
750
|
+
adapter: "openai-responses",
|
|
680
751
|
authKind: "key",
|
|
752
|
+
supportsServiceTier: false,
|
|
681
753
|
preserveCustomDestination: true,
|
|
754
|
+
// A row already saved on Chat keeps Chat. This is a `preserveCustomDestination` key entry,
|
|
755
|
+
// so `providerMatchesRegistryTransport` refuses the adapter mismatch and the request path
|
|
756
|
+
// returns the stored row untouched; the alias below still hands it this entry's metadata.
|
|
757
|
+
// Deliberately no startup config migration: the Z.AI one (`zai-responses-migration.ts`) is
|
|
758
|
+
// safe only because it rewrites rows the router already canonicalizes, and it gates on
|
|
759
|
+
// `providerMatchesRegistryTransport` to guarantee that. A Chat row here is NOT canonicalized,
|
|
760
|
+
// so migrating it would change a wire the operator is actually using, and a marker added
|
|
761
|
+
// now cannot tell the old default apart from a deliberate pre-upgrade Chat choice.
|
|
762
|
+
destinationAliases: [{ baseUrl: "https://ark.cn-beijing.volces.com/api/coding/v3", adapter: "openai-chat" }],
|
|
763
|
+
// Validated Ark Coding Plan continuations reject replayed reasoning items.
|
|
764
|
+
dropResponsesReasoningItems: true,
|
|
682
765
|
dashboardUrl: "https://console.volcengine.com/ark/region:ark+cn-beijing/overview",
|
|
683
766
|
defaultModel: "ark-code-latest",
|
|
684
767
|
models: VOLCENGINE_CODING_PLAN_MODELS,
|
|
@@ -728,6 +811,21 @@ export const PROVIDER_REGISTRY_EXTENDED: readonly ProviderRegistryEntry[] = [
|
|
|
728
811
|
defaultModel: "qwen3.8-max",
|
|
729
812
|
models: ALIBABA_TOKEN_PLAN_MODELS,
|
|
730
813
|
liveModels: false,
|
|
814
|
+
// Alibaba documents an OpenAI-compatible Responses API on this same /compatible-mode/v1 base
|
|
815
|
+
// and ships an official Codex integration guide on wire_api = "responses" (#5097). The
|
|
816
|
+
// gateway serves the same models over both wires, and qwen3.8-flash, qwen3.7-plus and
|
|
817
|
+
// glm-5.3 carry live end-to-end evidence there (custom tools, reasoning replay, streaming,
|
|
818
|
+
// multi-turn continuation).
|
|
819
|
+
//
|
|
820
|
+
// That is deliberately NOT expressed as a modelWireDefaults pin. Pinning would move every
|
|
821
|
+
// existing Codex user of those models onto a different upstream with no config change, and
|
|
822
|
+
// one delta is unresolved: preserveReasoningContentModels below is read by the CHAT adapter,
|
|
823
|
+
// while the Responses serializer reads preserveResponsesReasoningContent, which this entry
|
|
824
|
+
// does not set. On the Responses wire those models would replay with blanked reasoning
|
|
825
|
+
// content -- less state than they carry today. Z.AI and DeepSeek set both flags together for
|
|
826
|
+
// exactly this reason. Until that flag is justified against this gateway, Responses stays a
|
|
827
|
+
// documented per-model modelAdapters opt-in;
|
|
828
|
+
// tests/providers/alibaba-token-plan-responses-optin.test.ts holds both halves.
|
|
731
829
|
note: "Token Plan Personal Edition · China (Beijing)",
|
|
732
830
|
modelInputModalities: ALIBABA_TOKEN_PLAN_INPUT_MODALITIES,
|
|
733
831
|
modelContextWindows: ALIBABA_TOKEN_PLAN_CONTEXT_WINDOWS,
|
|
@@ -736,6 +834,7 @@ export const PROVIDER_REGISTRY_EXTENDED: readonly ProviderRegistryEntry[] = [
|
|
|
736
834
|
...Object.fromEntries(ALIBABA_TOKEN_PLAN_QWEN_MODELS.map(id => [id, THINKING_BUDGET_EFFORTS])),
|
|
737
835
|
...Object.fromEntries(QWEN38_FAMILY.map(id => [id, QWEN38_REASONING_EFFORTS])),
|
|
738
836
|
"glm-5.2": ZAI_GLM_52_REASONING_EFFORTS,
|
|
837
|
+
"glm-5.3": ZAI_GLM_53_REASONING_EFFORTS,
|
|
739
838
|
"deepseek-v4-pro": deepseekThinkingEffortsFor("deepseek-v4-pro"),
|
|
740
839
|
"deepseek-v4-pro-0813": deepseekThinkingEffortsFor("deepseek-v4-pro-0813"),
|
|
741
840
|
"deepseek-v4-flash-0731": deepseekThinkingEffortsFor("deepseek-v4-flash-0731"),
|
|
@@ -781,6 +880,7 @@ export const PROVIDER_REGISTRY_EXTENDED: readonly ProviderRegistryEntry[] = [
|
|
|
781
880
|
...Object.fromEntries(ALIBABA_INTL_TOKEN_PLAN_QWEN_MODELS.map(id => [id, THINKING_BUDGET_EFFORTS])),
|
|
782
881
|
...Object.fromEntries(QWEN38_FAMILY.map(id => [id, QWEN38_REASONING_EFFORTS])),
|
|
783
882
|
"glm-5.2": ZAI_GLM_52_REASONING_EFFORTS,
|
|
883
|
+
"glm-5.3": ZAI_GLM_53_REASONING_EFFORTS,
|
|
784
884
|
"deepseek-v4-pro": deepseekThinkingEffortsFor("deepseek-v4-pro"),
|
|
785
885
|
"deepseek-v4-pro-0813": deepseekThinkingEffortsFor("deepseek-v4-pro-0813"),
|
|
786
886
|
"deepseek-v4-flash": deepseekThinkingEffortsFor("deepseek-v4-flash"),
|
|
@@ -955,8 +1055,37 @@ export const PROVIDER_REGISTRY_EXTENDED: readonly ProviderRegistryEntry[] = [
|
|
|
955
1055
|
// Same DeepSeek routes as the Go preset above, behind the same vendor, so they carry
|
|
956
1056
|
// the same json_schema rejection (#1338 / #1415).
|
|
957
1057
|
noJsonSchemaModels: [...DEEPSEEK_GATEWAY_THINKING_MODELS, ...OPENCODE_FREE_DEEPSEEK_MODELS],
|
|
1058
|
+
// Muse Spark on Zen can sit silent during prolonged reasoning and close without a protocol terminal.
|
|
1059
|
+
modelResponsesTerminalRepair: {
|
|
1060
|
+
"muse-spark-1.2-contributor-free": { graceMs: 5_000 },
|
|
1061
|
+
"muse-spark-1.3-contributor-free": { graceMs: 5_000 },
|
|
1062
|
+
},
|
|
958
1063
|
},
|
|
959
1064
|
{ 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" },
|
|
1065
|
+
{
|
|
1066
|
+
// Opper: EU-hosted AI gateway (Opper AI AB, Stockholm). One OpenAI-compatible endpoint and one
|
|
1067
|
+
// key in front of 30+ upstream providers. Seeded ids are Opper *pools* (bare names such as
|
|
1068
|
+
// `claude-sonnet-4-6`): the gateway chooses the provider/region per request, and a
|
|
1069
|
+
// `vendor/model` id (`anthropic/claude-sonnet-4-6`, `aws/claude-sonnet-4-6-eu`) pins one route.
|
|
1070
|
+
// The original provider author reported on 2026-09-08 that GET /v3/compat/models answers 401
|
|
1071
|
+
// without a key, so the default discovery URL doubles as key validation. Windows, output caps
|
|
1072
|
+
// and modalities live in model-seeds.ts (smallest value / shared modality across each pool's
|
|
1073
|
+
// members); live discovery owns which models exist.
|
|
1074
|
+
id: "opper",
|
|
1075
|
+
label: "Opper",
|
|
1076
|
+
adapter: "openai-chat",
|
|
1077
|
+
baseUrl: "https://api.opper.ai/v3/compat",
|
|
1078
|
+
authKind: "key",
|
|
1079
|
+
dashboardUrl: "https://platform.opper.ai",
|
|
1080
|
+
liveModels: true,
|
|
1081
|
+
preserveCustomDestination: true,
|
|
1082
|
+
defaultModel: "claude-sonnet-4-6",
|
|
1083
|
+
models: OPPER_MODELS,
|
|
1084
|
+
modelContextWindows: OPPER_MODEL_CONTEXT_WINDOWS,
|
|
1085
|
+
modelMaxOutputTokens: OPPER_MODEL_MAX_OUTPUT_TOKENS,
|
|
1086
|
+
modelInputModalities: OPPER_MODEL_INPUT_MODALITIES,
|
|
1087
|
+
note: "EU-hosted AI gateway: one OpenAI-compatible endpoint and one key in front of 30+ providers. Bare model ids are pools (claude-sonnet-4-6, gpt-5.5) and Opper picks the route per request; vendor/model ids (anthropic/claude-sonnet-4-6) pin one provider. The catalogue is discovered live from /v3/compat/models with your key; the public list is at opper.ai/models. Token rates are the model providers' rates with no markup; Opper charges a 3% fee when you buy credits.",
|
|
1088
|
+
},
|
|
960
1089
|
{
|
|
961
1090
|
id: "opencode-free",
|
|
962
1091
|
label: "OpenCode Free",
|
|
@@ -1233,3 +1362,4 @@ export const PROVIDER_REGISTRY_EXTENDED: readonly ProviderRegistryEntry[] = [
|
|
|
1233
1362
|
note: "Official CodeBuddy Code CLI (Tencent Cloud), China/internal environment. Uses the documented CODEBUDDY_API_KEY + headless CLI surface; never reads desktop sessions or private console endpoints. Region-isolated from codebuddy (Global); credentials are never exchanged across regions. v1 disables CLI tools (--tools \"\"): text/reasoning only for now. Requires `npm i -g @tencent-ai/codebuddy-code`. AUP/routing authorization flagged for maintainer security review.",
|
|
1234
1363
|
},
|
|
1235
1364
|
];
|
|
1365
|
+
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import type { ProviderRegistryEntry } from "./types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* What a registry field's KEYS mean for selector decoding.
|
|
5
|
+
*
|
|
6
|
+
* An encoded selector is decoded against the native model ids a provider is known to accept.
|
|
7
|
+
* That set used to be a hand-written list of eight registry maps, so an id declared only in an
|
|
8
|
+
* unlisted map could not be decoded, and every new model-keyed field had to be remembered.
|
|
9
|
+
*
|
|
10
|
+
* Every field of ProviderRegistryEntry is classified below, including the ones that carry no
|
|
11
|
+
* model identity at all. That is deliberate: the "satisfies" check makes a new registry field a
|
|
12
|
+
* TYPE error until somebody decides what its keys mean, rather than letting it default to
|
|
13
|
+
* invisible. The two checks are different and both matter - the satisfies clause proves the
|
|
14
|
+
* classification is exhaustive when this compiles, and the parity test proves the fields the
|
|
15
|
+
* shipped registry actually carries are the ones classified here.
|
|
16
|
+
*
|
|
17
|
+
* These are decode hints and nothing more. A hint does not publish a catalog row, grant
|
|
18
|
+
* availability or entitlement, and it does not confer transport authority: the caller still
|
|
19
|
+
* checks registry transport identity before asking for them, and an ambiguous selector is still
|
|
20
|
+
* rejected rather than guessed.
|
|
21
|
+
*/
|
|
22
|
+
type RegistryFieldModelIdRole =
|
|
23
|
+
| { readonly kind: "none" }
|
|
24
|
+
| { readonly kind: "record-keys" }
|
|
25
|
+
| { readonly kind: "nested-record-keys"; readonly key: "modelSupportsServiceTier" };
|
|
26
|
+
|
|
27
|
+
const NONE = { kind: "none" } as const;
|
|
28
|
+
const RECORD_KEYS = { kind: "record-keys" } as const;
|
|
29
|
+
/**
|
|
30
|
+
* Key-auth declares its own service-tier support under a nested map.
|
|
31
|
+
*
|
|
32
|
+
* Reading its keys is identity evidence only. It does not activate the key-auth service tier,
|
|
33
|
+
* which stays the business of the service-tier resolver.
|
|
34
|
+
*/
|
|
35
|
+
const KEY_AUTH_SERVICE_TIER = { kind: "nested-record-keys", key: "modelSupportsServiceTier" } as const;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Not a configuration surface. Exported so the parity suite can compare this classification
|
|
39
|
+
* against the fields the shipped registry entries actually carry at runtime.
|
|
40
|
+
*/
|
|
41
|
+
export const REGISTRY_FIELD_MODEL_ID_ROLES = {
|
|
42
|
+
id: NONE,
|
|
43
|
+
label: NONE,
|
|
44
|
+
adapter: NONE,
|
|
45
|
+
baseUrl: NONE,
|
|
46
|
+
apiKeyTransport: NONE,
|
|
47
|
+
alias: NONE,
|
|
48
|
+
authKind: NONE,
|
|
49
|
+
codexAccountMode: NONE,
|
|
50
|
+
allowKeyAuthOverride: NONE,
|
|
51
|
+
allowPrivateNetworkByDefault: NONE,
|
|
52
|
+
keyOptional: NONE,
|
|
53
|
+
apiKeyValidation: NONE,
|
|
54
|
+
freeTier: NONE,
|
|
55
|
+
allowBaseUrlOverride: NONE,
|
|
56
|
+
preserveCustomDestination: NONE,
|
|
57
|
+
baseUrlChoices: NONE,
|
|
58
|
+
staticHeaders: NONE,
|
|
59
|
+
modelSuffixBracketStrip: NONE,
|
|
60
|
+
featured: NONE,
|
|
61
|
+
sponsor: NONE,
|
|
62
|
+
dashboardPreset: NONE,
|
|
63
|
+
note: NONE,
|
|
64
|
+
dashboardUrl: NONE,
|
|
65
|
+
defaultModel: NONE,
|
|
66
|
+
models: NONE,
|
|
67
|
+
liveModels: NONE,
|
|
68
|
+
modelWireDefaults: RECORD_KEYS,
|
|
69
|
+
fastWire: NONE,
|
|
70
|
+
modelResponsesUpstreamStreaming: RECORD_KEYS,
|
|
71
|
+
modelResponsesTerminalRepair: RECORD_KEYS,
|
|
72
|
+
responsesItemIdRepair: NONE,
|
|
73
|
+
responsesPath: NONE,
|
|
74
|
+
chatCompletionsPath: NONE,
|
|
75
|
+
destinationAliases: NONE,
|
|
76
|
+
statelessResponses: NONE,
|
|
77
|
+
requiresAdjacentResponsesToolResults: NONE,
|
|
78
|
+
requiresPairedResponsesToolResults: NONE,
|
|
79
|
+
annotateEmptyToolOutputs: NONE,
|
|
80
|
+
supportsServiceTier: NONE,
|
|
81
|
+
supportsOpenAiWebSearchToolFields: NONE,
|
|
82
|
+
supportsResponsesCustomTools: NONE,
|
|
83
|
+
modelSupportsServiceTier: RECORD_KEYS,
|
|
84
|
+
keyAuthServiceTier: KEY_AUTH_SERVICE_TIER,
|
|
85
|
+
fastTierDescription: NONE,
|
|
86
|
+
modelServiceTierCapabilityBaseUrlGuard: NONE,
|
|
87
|
+
preserveResponsesReasoningContent: NONE,
|
|
88
|
+
dropResponsesReasoningItems: NONE,
|
|
89
|
+
modelSupportsReasoningSummaries: RECORD_KEYS,
|
|
90
|
+
modelSupportsVerbosity: RECORD_KEYS,
|
|
91
|
+
supportsVerbosity: NONE,
|
|
92
|
+
modelDiscovery: NONE,
|
|
93
|
+
contextWindow: NONE,
|
|
94
|
+
modelContextWindows: RECORD_KEYS,
|
|
95
|
+
modelDisplayNames: RECORD_KEYS,
|
|
96
|
+
modelInputModalities: RECORD_KEYS,
|
|
97
|
+
defaultMaxOutputTokens: NONE,
|
|
98
|
+
modelMaxOutputTokens: RECORD_KEYS,
|
|
99
|
+
reasoningEfforts: NONE,
|
|
100
|
+
modelReasoningEfforts: RECORD_KEYS,
|
|
101
|
+
modelDefaultReasoningEfforts: RECORD_KEYS,
|
|
102
|
+
reasoningEffortMap: NONE,
|
|
103
|
+
modelReasoningEffortMap: RECORD_KEYS,
|
|
104
|
+
directReasoningEffortModels: NONE,
|
|
105
|
+
reasoningWireFormat: NONE,
|
|
106
|
+
noVisionModels: NONE,
|
|
107
|
+
noReasoningModels: NONE,
|
|
108
|
+
noTemperatureModels: NONE,
|
|
109
|
+
noTopPModels: NONE,
|
|
110
|
+
noPenaltyModels: NONE,
|
|
111
|
+
noJsonSchemaModels: NONE,
|
|
112
|
+
parallelToolCalls: NONE,
|
|
113
|
+
promptCacheKey: NONE,
|
|
114
|
+
chatServiceTier: NONE,
|
|
115
|
+
openaiChatEofTolerance: NONE,
|
|
116
|
+
autoToolChoiceOnlyModels: NONE,
|
|
117
|
+
preserveReasoningContentModels: NONE,
|
|
118
|
+
requiresReasoningPlaceholderModels: NONE,
|
|
119
|
+
showThinkingSummary: NONE,
|
|
120
|
+
reasoningSplitModels: NONE,
|
|
121
|
+
reasoningDetailsModels: NONE,
|
|
122
|
+
thinkingToggleModels: NONE,
|
|
123
|
+
thinkingBudgetModels: NONE,
|
|
124
|
+
escapeBuiltinToolNames: NONE,
|
|
125
|
+
oauthId: NONE,
|
|
126
|
+
virtualModels: RECORD_KEYS,
|
|
127
|
+
modelMaxInputTokens: RECORD_KEYS,
|
|
128
|
+
jawcodeBundle: NONE,
|
|
129
|
+
extraMetadataAliases: NONE,
|
|
130
|
+
metadataModelIdNormalize: NONE,
|
|
131
|
+
googleMode: NONE,
|
|
132
|
+
project: NONE,
|
|
133
|
+
location: NONE,
|
|
134
|
+
} satisfies Record<keyof ProviderRegistryEntry, RegistryFieldModelIdRole>;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* The native model ids this registry entry names, in registry-field order, first seen wins.
|
|
138
|
+
*
|
|
139
|
+
* Only KEYS are collected. virtualModels is a map whose keys are the selected identities and
|
|
140
|
+
* whose values name a transport target, so its wireModelId values are deliberately not read:
|
|
141
|
+
* a wire target is not something a client may select by name.
|
|
142
|
+
*/
|
|
143
|
+
export function registryModelIdKeys(entry: ProviderRegistryEntry): readonly string[] {
|
|
144
|
+
const ids: string[] = [];
|
|
145
|
+
const seen = new Set<string>();
|
|
146
|
+
const add = (key: string): void => {
|
|
147
|
+
if (key.length === 0 || seen.has(key)) return;
|
|
148
|
+
seen.add(key);
|
|
149
|
+
ids.push(key);
|
|
150
|
+
};
|
|
151
|
+
for (const field of Object.keys(REGISTRY_FIELD_MODEL_ID_ROLES) as (keyof ProviderRegistryEntry)[]) {
|
|
152
|
+
const role = REGISTRY_FIELD_MODEL_ID_ROLES[field];
|
|
153
|
+
if (role.kind === "none") continue;
|
|
154
|
+
if (role.kind === "record-keys") {
|
|
155
|
+
const map = entry[field];
|
|
156
|
+
if (typeof map !== "object" || map === null) continue;
|
|
157
|
+
for (const key of Object.keys(map)) add(key);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
// The nested role belongs to keyAuthServiceTier alone, so its owner is read by name.
|
|
161
|
+
// ProviderRegistryEntry is an interface and carries no implicit index signature, so a
|
|
162
|
+
// by-name read is what keeps this loop typed instead of asserted through unknown.
|
|
163
|
+
const nested = entry.keyAuthServiceTier?.[role.key];
|
|
164
|
+
if (typeof nested !== "object" || nested === null) continue;
|
|
165
|
+
for (const key of Object.keys(nested)) add(key);
|
|
166
|
+
}
|
|
167
|
+
return Object.freeze(ids);
|
|
168
|
+
}
|