@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
package/src/oauth/index.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { KiroOAuthMetadata, OAuthController, OAuthCredentials } from "./types";
|
|
2
2
|
import { initializeProviderModelSelection } from "../providers/initial-model-selection";
|
|
3
|
-
import { parseCallbackInput } from "./callback-server";
|
|
4
3
|
import type { OcxConfig, OcxProviderConfig, RefreshPolicy } from "../types";
|
|
5
4
|
import { ConfigMutationLockError, loadConfig, mutatePersistedConfig, saveConfig } from "../config";
|
|
6
5
|
import { resolveProviderApiKey } from "../providers/key-store";
|
|
@@ -51,8 +50,9 @@ import { providerModelsUrl, resolveProviderModelDiscoveryUrl } from "../provider
|
|
|
51
50
|
import { resolveProviderTransport } from "../providers/xai-transport";
|
|
52
51
|
import { detectClaudeCodeToken, detectGrokCliToken, hasComparableGrokIdentity, isSameGrokIdentity, shouldAdoptGrokGeneration } from "./local-token-detect";
|
|
53
52
|
import { logOAuthEvent } from "./log";
|
|
54
|
-
import { captureConfigGeneration, sweepExpiredOnWrite
|
|
55
|
-
import {
|
|
53
|
+
import { captureConfigGeneration, sweepExpiredOnWrite } from "../lib/state-store-sweeper";
|
|
54
|
+
import { clearManualCodeSlot, ensureManualCodeSlot, kiroLoginSettling, loginAbort, loginState, waitForManualLoginCode } from "./login-flow-state";
|
|
55
|
+
export { reconcileOAuthFlowState, submitManualLoginCode } from "./login-flow-state";
|
|
56
56
|
import { randomUUID } from "node:crypto";
|
|
57
57
|
export {
|
|
58
58
|
CODEX_HEALTH_AUTH_FAILED_NOTE,
|
|
@@ -1710,110 +1710,6 @@ export async function runLogin(
|
|
|
1710
1710
|
* localhost), the GUI can POST the final redirect URL or authorization code via
|
|
1711
1711
|
* submitManualLoginCode(), which feeds OAuthController.onManualCodeInput.
|
|
1712
1712
|
*/
|
|
1713
|
-
const loginState = new Map<string, { error?: string; done: boolean }>();
|
|
1714
|
-
const loginAbort = new Map<string, AbortController>();
|
|
1715
|
-
const kiroLoginSettling = new Set<string>();
|
|
1716
|
-
|
|
1717
|
-
/** Pending paste for a login in progress: either a waiter or a stashed early submission. */
|
|
1718
|
-
interface ManualCodeSlot {
|
|
1719
|
-
pendingInput?: string;
|
|
1720
|
-
resolve?: (value: string) => void;
|
|
1721
|
-
/** Registered by the callback flow so submits can validate state synchronously. */
|
|
1722
|
-
expectedState?: string;
|
|
1723
|
-
}
|
|
1724
|
-
const loginManual = new Map<string, ManualCodeSlot>();
|
|
1725
|
-
const OAUTH_PENDING_CODE_MAX_BYTES = 4 * 1024;
|
|
1726
|
-
let lastOAuthFlowReconciledGeneration = 0;
|
|
1727
|
-
|
|
1728
|
-
export function reconcileOAuthFlowState(context: GenerationContext): number {
|
|
1729
|
-
if (context.generation <= lastOAuthFlowReconciledGeneration) return 0;
|
|
1730
|
-
let removed = 0;
|
|
1731
|
-
for (const [provider, state] of loginState) {
|
|
1732
|
-
if (context.providerNames.has(provider) || !state.done || loginAbort.has(provider)) continue;
|
|
1733
|
-
if (loginState.delete(provider)) removed += 1;
|
|
1734
|
-
if (loginManual.delete(provider)) removed += 1;
|
|
1735
|
-
if (loginAbort.delete(provider)) removed += 1;
|
|
1736
|
-
}
|
|
1737
|
-
lastOAuthFlowReconciledGeneration = context.generation;
|
|
1738
|
-
return removed;
|
|
1739
|
-
}
|
|
1740
|
-
|
|
1741
|
-
function clearManualCodeSlot(provider: string): void {
|
|
1742
|
-
loginManual.delete(provider);
|
|
1743
|
-
}
|
|
1744
|
-
|
|
1745
|
-
function ensureManualCodeSlot(provider: string): ManualCodeSlot {
|
|
1746
|
-
let slot = loginManual.get(provider);
|
|
1747
|
-
if (!slot) {
|
|
1748
|
-
slot = {};
|
|
1749
|
-
loginManual.set(provider, slot);
|
|
1750
|
-
}
|
|
1751
|
-
return slot;
|
|
1752
|
-
}
|
|
1753
|
-
|
|
1754
|
-
/** Wait for a GUI/CLI paste of the OAuth redirect URL or code (or return a stashed early submit). */
|
|
1755
|
-
function waitForManualLoginCode(provider: string, signal: AbortSignal, expectedState?: string): Promise<string> {
|
|
1756
|
-
if (signal.aborted) {
|
|
1757
|
-
return Promise.reject(new Error(`OAuth callback cancelled: ${signal.reason}`));
|
|
1758
|
-
}
|
|
1759
|
-
const slot = ensureManualCodeSlot(provider);
|
|
1760
|
-
if (expectedState !== undefined) slot.expectedState = expectedState;
|
|
1761
|
-
if (slot.pendingInput !== undefined) {
|
|
1762
|
-
const value = slot.pendingInput;
|
|
1763
|
-
slot.pendingInput = undefined;
|
|
1764
|
-
return Promise.resolve(value);
|
|
1765
|
-
}
|
|
1766
|
-
return new Promise<string>((resolve, reject) => {
|
|
1767
|
-
const onAbort = () => {
|
|
1768
|
-
if (slot.resolve === resolve) slot.resolve = undefined;
|
|
1769
|
-
reject(new Error(`OAuth callback cancelled: ${signal.reason}`));
|
|
1770
|
-
};
|
|
1771
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
1772
|
-
slot.resolve = (value: string) => {
|
|
1773
|
-
signal.removeEventListener("abort", onAbort);
|
|
1774
|
-
if (slot.resolve === resolve) slot.resolve = undefined;
|
|
1775
|
-
resolve(value);
|
|
1776
|
-
};
|
|
1777
|
-
});
|
|
1778
|
-
}
|
|
1779
|
-
|
|
1780
|
-
/**
|
|
1781
|
-
* Feed a pasted redirect URL or authorization code into an in-progress GUI login.
|
|
1782
|
-
* Returns ok:false when no login is waiting (or input is empty). Invalid pastes are accepted
|
|
1783
|
-
* here and re-prompted by the OAuth callback loop if they cannot be parsed / fail state checks.
|
|
1784
|
-
*/
|
|
1785
|
-
export function submitManualLoginCode(provider: string, input: string): { ok: true } | { ok: false; error: string } {
|
|
1786
|
-
const trimmed = input.trim();
|
|
1787
|
-
if (!trimmed) return { ok: false, error: "empty code" };
|
|
1788
|
-
if (retainedUtf8Bytes(trimmed) > OAUTH_PENDING_CODE_MAX_BYTES) return { ok: false, error: "code too large" };
|
|
1789
|
-
const st = loginState.get(provider);
|
|
1790
|
-
if (!st || st.done) return { ok: false, error: "no login in progress" };
|
|
1791
|
-
const slot = ensureManualCodeSlot(provider);
|
|
1792
|
-
// Synchronous validation (validated request/ack): reject un-parseable input and
|
|
1793
|
-
// authorization responses (url/query kind) whose state is missing or mismatched
|
|
1794
|
-
// once the flow has registered its expected state. Raw codes stay in-session-PKCE
|
|
1795
|
-
// protected. Early posts (flow not yet waiting, no expectedState) are stashed and
|
|
1796
|
-
// re-validated by the callback loop.
|
|
1797
|
-
const parsed = parseCallbackInput(trimmed);
|
|
1798
|
-
// Command Code's manual fallback accepts a pasted JSON callback payload
|
|
1799
|
-
// (`{ apiKey, state, ... }`) which has no `code` param. Let it through the
|
|
1800
|
-
// shared gate so the provider-specific parser can validate it.
|
|
1801
|
-
const isCommandCodeJson = provider === "command-code" && trimmed.startsWith("{") && !parsed.code;
|
|
1802
|
-
if (!parsed.code && !isCommandCodeJson) return { ok: false, error: "no authorization code found in input" };
|
|
1803
|
-
if (parsed.kind !== "raw" && slot.expectedState !== undefined) {
|
|
1804
|
-
if (parsed.state === undefined) return { ok: false, error: "redirect URL is missing the state parameter" };
|
|
1805
|
-
if (parsed.state !== slot.expectedState) return { ok: false, error: "state mismatch — paste the redirect URL from THIS login attempt" };
|
|
1806
|
-
}
|
|
1807
|
-
if (slot.resolve) {
|
|
1808
|
-
const resolve = slot.resolve;
|
|
1809
|
-
slot.resolve = undefined;
|
|
1810
|
-
resolve(trimmed);
|
|
1811
|
-
} else {
|
|
1812
|
-
// Race: GUI may POST before the flow reaches onManualCodeInput — stash for the waiter.
|
|
1813
|
-
slot.pendingInput = trimmed;
|
|
1814
|
-
}
|
|
1815
|
-
return { ok: true };
|
|
1816
|
-
}
|
|
1817
1713
|
|
|
1818
1714
|
export interface OAuthAccountSummary {
|
|
1819
1715
|
id: string;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { parseCallbackInput } from "./callback-server";
|
|
2
|
+
import { retainedUtf8Bytes } from "../lib/admission";
|
|
3
|
+
import type { GenerationContext } from "../lib/state-store-sweeper";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* In-flight login flow state and the manual paste slot that feeds it.
|
|
7
|
+
*
|
|
8
|
+
* Split out of `index.ts` when that file crossed the repository file-size ratchet at 2009
|
|
9
|
+
* lines. The cut follows a seam rather than a line count: everything here is bookkeeping for
|
|
10
|
+
* a login that has started and not yet settled, and none of it reads or writes a stored
|
|
11
|
+
* credential. `index.ts` re-exports the two public names, so existing importers are unaffected.
|
|
12
|
+
*/
|
|
13
|
+
export const loginState = new Map<string, { error?: string; done: boolean }>();
|
|
14
|
+
export const loginAbort = new Map<string, AbortController>();
|
|
15
|
+
export const kiroLoginSettling = new Set<string>();
|
|
16
|
+
|
|
17
|
+
/** Pending paste for a login in progress: either a waiter or a stashed early submission. */
|
|
18
|
+
export interface ManualCodeSlot {
|
|
19
|
+
pendingInput?: string;
|
|
20
|
+
resolve?: (value: string) => void;
|
|
21
|
+
/** Registered by the callback flow so submits can validate state synchronously. */
|
|
22
|
+
expectedState?: string;
|
|
23
|
+
}
|
|
24
|
+
const loginManual = new Map<string, ManualCodeSlot>();
|
|
25
|
+
const OAUTH_PENDING_CODE_MAX_BYTES = 4 * 1024;
|
|
26
|
+
let lastOAuthFlowReconciledGeneration = 0;
|
|
27
|
+
|
|
28
|
+
export function reconcileOAuthFlowState(context: GenerationContext): number {
|
|
29
|
+
if (context.generation <= lastOAuthFlowReconciledGeneration) return 0;
|
|
30
|
+
let removed = 0;
|
|
31
|
+
for (const [provider, state] of loginState) {
|
|
32
|
+
if (context.providerNames.has(provider) || !state.done || loginAbort.has(provider)) continue;
|
|
33
|
+
if (loginState.delete(provider)) removed += 1;
|
|
34
|
+
if (loginManual.delete(provider)) removed += 1;
|
|
35
|
+
if (loginAbort.delete(provider)) removed += 1;
|
|
36
|
+
}
|
|
37
|
+
lastOAuthFlowReconciledGeneration = context.generation;
|
|
38
|
+
return removed;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function clearManualCodeSlot(provider: string): void {
|
|
42
|
+
loginManual.delete(provider);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function ensureManualCodeSlot(provider: string): ManualCodeSlot {
|
|
46
|
+
let slot = loginManual.get(provider);
|
|
47
|
+
if (!slot) {
|
|
48
|
+
slot = {};
|
|
49
|
+
loginManual.set(provider, slot);
|
|
50
|
+
}
|
|
51
|
+
return slot;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Wait for a GUI/CLI paste of the OAuth redirect URL or code (or return a stashed early submit). */
|
|
55
|
+
export function waitForManualLoginCode(provider: string, signal: AbortSignal, expectedState?: string): Promise<string> {
|
|
56
|
+
if (signal.aborted) {
|
|
57
|
+
return Promise.reject(new Error(`OAuth callback cancelled: ${signal.reason}`));
|
|
58
|
+
}
|
|
59
|
+
const slot = ensureManualCodeSlot(provider);
|
|
60
|
+
if (expectedState !== undefined) slot.expectedState = expectedState;
|
|
61
|
+
if (slot.pendingInput !== undefined) {
|
|
62
|
+
const value = slot.pendingInput;
|
|
63
|
+
slot.pendingInput = undefined;
|
|
64
|
+
return Promise.resolve(value);
|
|
65
|
+
}
|
|
66
|
+
return new Promise<string>((resolve, reject) => {
|
|
67
|
+
const onAbort = () => {
|
|
68
|
+
if (slot.resolve === resolve) slot.resolve = undefined;
|
|
69
|
+
reject(new Error(`OAuth callback cancelled: ${signal.reason}`));
|
|
70
|
+
};
|
|
71
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
72
|
+
slot.resolve = (value: string) => {
|
|
73
|
+
signal.removeEventListener("abort", onAbort);
|
|
74
|
+
if (slot.resolve === resolve) slot.resolve = undefined;
|
|
75
|
+
resolve(value);
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Feed a pasted redirect URL or authorization code into an in-progress GUI login.
|
|
82
|
+
* Returns ok:false when no login is waiting (or input is empty). Invalid pastes are accepted
|
|
83
|
+
* here and re-prompted by the OAuth callback loop if they cannot be parsed / fail state checks.
|
|
84
|
+
*/
|
|
85
|
+
export function submitManualLoginCode(provider: string, input: string): { ok: true } | { ok: false; error: string } {
|
|
86
|
+
const trimmed = input.trim();
|
|
87
|
+
if (!trimmed) return { ok: false, error: "empty code" };
|
|
88
|
+
if (retainedUtf8Bytes(trimmed) > OAUTH_PENDING_CODE_MAX_BYTES) return { ok: false, error: "code too large" };
|
|
89
|
+
const st = loginState.get(provider);
|
|
90
|
+
if (!st || st.done) return { ok: false, error: "no login in progress" };
|
|
91
|
+
const slot = ensureManualCodeSlot(provider);
|
|
92
|
+
// Synchronous validation (validated request/ack): reject un-parseable input and
|
|
93
|
+
// authorization responses (url/query kind) whose state is missing or mismatched
|
|
94
|
+
// once the flow has registered its expected state. Raw codes stay in-session-PKCE
|
|
95
|
+
// protected — but a raw paste carrying an explicit #state suffix is state-bearing
|
|
96
|
+
// and checked too. Early posts (flow not yet waiting, no expectedState) are
|
|
97
|
+
// stashed and re-validated by the callback loop.
|
|
98
|
+
const parsed = parseCallbackInput(trimmed);
|
|
99
|
+
// Command Code's manual fallback accepts a pasted JSON callback payload
|
|
100
|
+
// (`{ apiKey, state, ... }`) which has no `code` param. Let it through the
|
|
101
|
+
// shared gate so the provider-specific parser can validate it.
|
|
102
|
+
const isCommandCodeJson = provider === "command-code" && trimmed.startsWith("{") && !parsed.code;
|
|
103
|
+
if (!parsed.code && !isCommandCodeJson) return { ok: false, error: "no authorization code found in input" };
|
|
104
|
+
// A raw paste carrying an explicit code#state suffix is state-bearing too: it
|
|
105
|
+
// must match the expected state rather than bypass validation.
|
|
106
|
+
const stateBearing = parsed.kind !== "raw" || parsed.state !== undefined;
|
|
107
|
+
if (stateBearing && slot.expectedState !== undefined) {
|
|
108
|
+
if (parsed.state === undefined) return { ok: false, error: "redirect URL is missing the state parameter" };
|
|
109
|
+
if (parsed.state !== slot.expectedState) {
|
|
110
|
+
return {
|
|
111
|
+
ok: false,
|
|
112
|
+
error: parsed.kind === "raw"
|
|
113
|
+
? "state mismatch — paste the bare code, or the correct code#state from THIS login attempt"
|
|
114
|
+
: "state mismatch — paste the redirect URL from THIS login attempt",
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (slot.resolve) {
|
|
119
|
+
const resolve = slot.resolve;
|
|
120
|
+
slot.resolve = undefined;
|
|
121
|
+
resolve(trimmed);
|
|
122
|
+
} else {
|
|
123
|
+
// Race: GUI may POST before the flow reaches onManualCodeInput — stash for the waiter.
|
|
124
|
+
slot.pendingInput = trimmed;
|
|
125
|
+
}
|
|
126
|
+
return { ok: true };
|
|
127
|
+
}
|
package/src/oauth/orcarouter.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { OAuthCallbackFlow, type OAuthCallbackFlowOptions } from "./callback-server";
|
|
3
3
|
import { generatePKCE } from "./pkce";
|
|
4
4
|
import type { OAuthController, OAuthCredentials } from "./types";
|
|
5
|
+
import { BOUNDED_BODY_MAX_BYTES, readBoundedResponseBytes } from "../lib/bounded-body";
|
|
5
6
|
|
|
6
7
|
export const ORCAROUTER_DEFAULT_API_BASE_URL = "https://api.orcarouter.ai";
|
|
7
8
|
export const ORCAROUTER_DEFAULT_AUTH_BASE_URL = "https://www.orcarouter.ai";
|
|
@@ -147,6 +148,7 @@ export class OrcaRouterOAuthFlow extends OAuthCallbackFlow {
|
|
|
147
148
|
|
|
148
149
|
async exchangeToken(code: string, _state: string, _redirectUri: string): Promise<OAuthCredentials> {
|
|
149
150
|
if (!this.#verifier) throw new Error("OrcaRouter PKCE verifier was not initialized");
|
|
151
|
+
const signal = requestSignal(this.ctrl.signal);
|
|
150
152
|
let response: Response;
|
|
151
153
|
try {
|
|
152
154
|
response = await fetch(new URL("/api/v1/auth/keys", this.#authBaseUrl), {
|
|
@@ -158,7 +160,7 @@ export class OrcaRouterOAuthFlow extends OAuthCallbackFlow {
|
|
|
158
160
|
code_challenge_method: "S256",
|
|
159
161
|
}),
|
|
160
162
|
redirect: "error",
|
|
161
|
-
signal
|
|
163
|
+
signal,
|
|
162
164
|
});
|
|
163
165
|
} catch (error) {
|
|
164
166
|
if (this.ctrl.signal?.aborted) {
|
|
@@ -171,9 +173,20 @@ export class OrcaRouterOAuthFlow extends OAuthCallbackFlow {
|
|
|
171
173
|
// never turn a code, verifier, or accidentally returned key into console output.
|
|
172
174
|
throw new Error(`OrcaRouter key exchange failed with HTTP ${response.status}`);
|
|
173
175
|
}
|
|
176
|
+
const { bytes, oversized } = await readBoundedResponseBytes(response, {
|
|
177
|
+
maxBytes: BOUNDED_BODY_MAX_BYTES,
|
|
178
|
+
signal,
|
|
179
|
+
}).catch(() => {
|
|
180
|
+
if (signal.aborted) throw signal.reason;
|
|
181
|
+
// Preserve the existing non-reflective error for response-body failures.
|
|
182
|
+
throw new Error("OrcaRouter key exchange returned invalid JSON");
|
|
183
|
+
});
|
|
184
|
+
if (oversized) {
|
|
185
|
+
throw new Error(`OrcaRouter key exchange response exceeded the ${BOUNDED_BODY_MAX_BYTES}-byte limit`);
|
|
186
|
+
}
|
|
174
187
|
let payload: unknown;
|
|
175
188
|
try {
|
|
176
|
-
payload =
|
|
189
|
+
payload = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
177
190
|
} catch {
|
|
178
191
|
throw new Error("OrcaRouter key exchange returned invalid JSON");
|
|
179
192
|
}
|
package/src/oauth/store.ts
CHANGED
|
@@ -437,6 +437,14 @@ function backupLegacyOnce(): void {
|
|
|
437
437
|
try {
|
|
438
438
|
copyFileSync(path, backup);
|
|
439
439
|
try { chmodSync(backup, 0o600); } catch { /* best-effort */ }
|
|
440
|
+
try {
|
|
441
|
+
// Register only the copy we just created. An unowned home still needs downgrade recovery.
|
|
442
|
+
if (!recordOwnedConfigPath(getConfigDir(), backup)) {
|
|
443
|
+
console.warn("[oauth] Recovery backup created, but uninstall ownership registration failed.");
|
|
444
|
+
}
|
|
445
|
+
} catch {
|
|
446
|
+
console.warn("[oauth] Recovery backup created, but uninstall ownership registration failed.");
|
|
447
|
+
}
|
|
440
448
|
} catch { /* best-effort */ }
|
|
441
449
|
}
|
|
442
450
|
|
|
@@ -36,6 +36,15 @@ export const CODEX_CAPACITY_MAX_QUOTA_AGE_MS = 30 * 60_000;
|
|
|
36
36
|
export type CodexCapacityQuota = {
|
|
37
37
|
fiveHourPercent?: number;
|
|
38
38
|
fiveHourResetAt?: number;
|
|
39
|
+
/**
|
|
40
|
+
* Local observation time for the burst-window percentage.
|
|
41
|
+
*
|
|
42
|
+
* Carried because the terminal-short-window rule needs it whenever the reading has no reset
|
|
43
|
+
* instant, and the dashboard's account-switch warning evaluates that rule against this DTO.
|
|
44
|
+
* Dropping it here made the warning return "no opinion" for a snapshot routing was already
|
|
45
|
+
* refusing on (#5045).
|
|
46
|
+
*/
|
|
47
|
+
shortObservedAt?: number;
|
|
39
48
|
weeklyPercent?: number;
|
|
40
49
|
weeklyResetAt?: number;
|
|
41
50
|
monthlyPercent?: number;
|
package/src/providers/derive.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CodexAccountMode, OcxProviderConfig } from "../types";
|
|
2
2
|
import { cloneFastWire } from "./fastwire";
|
|
3
|
+
import { resolveModelPolicy } from "./resolved-model-policy";
|
|
3
4
|
import {
|
|
4
5
|
PROVIDER_REGISTRY,
|
|
5
6
|
registryEntryForProviderDestination,
|
|
@@ -220,6 +221,14 @@ export function applyDirectReasoningEffortContracts(
|
|
|
220
221
|
*/
|
|
221
222
|
export function providerConfigSeed(entry: ProviderRegistryEntry): OcxProviderConfig {
|
|
222
223
|
const liveModels = registryEntrySupportsLiveModelDiscovery(entry) ? entry.liveModels : false;
|
|
224
|
+
const staticPolicy = resolveModelPolicy({
|
|
225
|
+
providerName: entry.id,
|
|
226
|
+
modelId: entry.defaultModel ?? entry.models?.[0] ?? "__provider_seed__",
|
|
227
|
+
provider: { adapter: entry.adapter, baseUrl: entry.baseUrl, authMode: entry.authKind },
|
|
228
|
+
registryEntry: entry,
|
|
229
|
+
transportMatchedRegistry: true,
|
|
230
|
+
effectiveAuth: { authMode: entry.authKind },
|
|
231
|
+
}).provider;
|
|
223
232
|
return {
|
|
224
233
|
adapter: entry.adapter,
|
|
225
234
|
baseUrl: entry.baseUrl,
|
|
@@ -239,17 +248,17 @@ export function providerConfigSeed(entry: ProviderRegistryEntry): OcxProviderCon
|
|
|
239
248
|
...(entry.models ? { models: [...entry.models] } : {}),
|
|
240
249
|
...(liveModels !== undefined ? { liveModels } : {}),
|
|
241
250
|
...(entry.contextWindow !== undefined ? { contextWindow: entry.contextWindow } : {}),
|
|
242
|
-
...(
|
|
243
|
-
...(
|
|
244
|
-
...(
|
|
245
|
-
...(
|
|
251
|
+
...(staticPolicy.modelContextWindows ? { modelContextWindows: { ...staticPolicy.modelContextWindows } } : {}),
|
|
252
|
+
...(staticPolicy.modelDisplayNames ? { modelDisplayNames: { ...staticPolicy.modelDisplayNames } } : {}),
|
|
253
|
+
...(staticPolicy.modelInputModalities ? { modelInputModalities: cloneRecordOfArrays(staticPolicy.modelInputModalities) } : {}),
|
|
254
|
+
...(staticPolicy.modelMaxInputTokens ? { modelMaxInputTokens: { ...staticPolicy.modelMaxInputTokens } } : {}),
|
|
246
255
|
...(entry.defaultMaxOutputTokens !== undefined ? { defaultMaxOutputTokens: entry.defaultMaxOutputTokens } : {}),
|
|
247
|
-
...(
|
|
256
|
+
...(staticPolicy.modelMaxOutputTokens ? { modelMaxOutputTokens: { ...staticPolicy.modelMaxOutputTokens } } : {}),
|
|
248
257
|
...(entry.reasoningEfforts ? { reasoningEfforts: [...entry.reasoningEfforts] } : {}),
|
|
249
|
-
...(
|
|
250
|
-
...(
|
|
251
|
-
...(
|
|
252
|
-
...(
|
|
258
|
+
...(staticPolicy.modelReasoningEfforts ? { modelReasoningEfforts: cloneRecordOfArrays(staticPolicy.modelReasoningEfforts) } : {}),
|
|
259
|
+
...(staticPolicy.modelDefaultReasoningEfforts ? { modelDefaultReasoningEfforts: { ...staticPolicy.modelDefaultReasoningEfforts } } : {}),
|
|
260
|
+
...(staticPolicy.reasoningEffortMap ? { reasoningEffortMap: { ...staticPolicy.reasoningEffortMap } } : {}),
|
|
261
|
+
...(staticPolicy.modelReasoningEffortMap ? { modelReasoningEffortMap: cloneNestedRecord(staticPolicy.modelReasoningEffortMap) } : {}),
|
|
253
262
|
...(entry.reasoningWireFormat ? { reasoningWireFormat: entry.reasoningWireFormat } : {}),
|
|
254
263
|
...(entry.noVisionModels ? { noVisionModels: [...entry.noVisionModels] } : {}),
|
|
255
264
|
...(entry.noReasoningModels ? { noReasoningModels: [...entry.noReasoningModels] } : {}),
|
|
@@ -447,13 +456,16 @@ function applyVerbosityDefaults(prov: OcxProviderConfig, entry: ProviderRegistry
|
|
|
447
456
|
* was skipped and the reasoning ladder was advertised without summary support — exactly the
|
|
448
457
|
* inconsistency that makes Codex drop the inbound reasoning object.
|
|
449
458
|
*
|
|
450
|
-
* Deliberately narrow: reasoning-summary and
|
|
459
|
+
* Deliberately narrow: reasoning-summary, effort, and replay-compatibility metadata only, via
|
|
451
460
|
* `registryEntryForProviderDestination`, which matches fixed key destinations and refuses
|
|
452
461
|
* templated or overridable base URLs. A custom row keeps its own identity for everything else.
|
|
453
462
|
*/
|
|
454
463
|
function enrichReasoningMetadataByDestination(prov: OcxProviderConfig): void {
|
|
455
464
|
const destination = registryEntryForProviderDestination(prov);
|
|
456
465
|
applyReasoningSummaryDefaults(prov, destination?.modelSupportsReasoningSummaries);
|
|
466
|
+
if (prov.dropResponsesReasoningItems === undefined && destination?.dropResponsesReasoningItems !== undefined) {
|
|
467
|
+
prov.dropResponsesReasoningItems = destination.dropResponsesReasoningItems;
|
|
468
|
+
}
|
|
457
469
|
if (destination?.modelReasoningEfforts) {
|
|
458
470
|
prov.modelReasoningEfforts = fillRecordOfArrays(destination.modelReasoningEfforts, prov.modelReasoningEfforts);
|
|
459
471
|
}
|
|
@@ -491,6 +503,14 @@ export function enrichProviderFromRegistry(name: string, prov: OcxProviderConfig
|
|
|
491
503
|
modelReasoningEffortMap: prov.modelReasoningEffortMap,
|
|
492
504
|
};
|
|
493
505
|
const seed = providerConfigSeed(entry);
|
|
506
|
+
const resolvedStatic = resolveModelPolicy({
|
|
507
|
+
providerName: name,
|
|
508
|
+
modelId: prov.defaultModel ?? entry.defaultModel ?? "__provider_enrich__",
|
|
509
|
+
provider: prov,
|
|
510
|
+
registryEntry: entry,
|
|
511
|
+
transportMatchedRegistry: true,
|
|
512
|
+
...(prov.authMode ? { effectiveAuth: { authMode: prov.authMode } } : {}),
|
|
513
|
+
}).provider;
|
|
494
514
|
repairStaticModelCatalogProvider(name, prov);
|
|
495
515
|
if (prov.apiKeyTransport === undefined && seed.apiKeyTransport !== undefined) prov.apiKeyTransport = seed.apiKeyTransport;
|
|
496
516
|
if (!prov.defaultModel && seed.defaultModel) prov.defaultModel = seed.defaultModel;
|
|
@@ -504,10 +524,8 @@ export function enrichProviderFromRegistry(name: string, prov: OcxProviderConfig
|
|
|
504
524
|
if (!prov.modelContextWindows && seed.modelContextWindows) prov.modelContextWindows = { ...seed.modelContextWindows };
|
|
505
525
|
// Per-model fill, not all-or-nothing: an operator who renamed ONE model must still receive
|
|
506
526
|
// labels for the rest, and an existing install must pick up newly seeded rows on enrich.
|
|
507
|
-
if (
|
|
508
|
-
|
|
509
|
-
}
|
|
510
|
-
if (seed.modelInputModalities) prov.modelInputModalities = fillRecordOfArrays(seed.modelInputModalities, prov.modelInputModalities);
|
|
527
|
+
if (resolvedStatic.modelDisplayNames) prov.modelDisplayNames = { ...resolvedStatic.modelDisplayNames };
|
|
528
|
+
if (resolvedStatic.modelInputModalities) prov.modelInputModalities = cloneRecordOfArrays(resolvedStatic.modelInputModalities);
|
|
511
529
|
if (prov.defaultMaxOutputTokens === undefined && seed.defaultMaxOutputTokens !== undefined) prov.defaultMaxOutputTokens = seed.defaultMaxOutputTokens;
|
|
512
530
|
if (!prov.modelMaxOutputTokens && seed.modelMaxOutputTokens) prov.modelMaxOutputTokens = { ...seed.modelMaxOutputTokens };
|
|
513
531
|
if ((!prov.reasoningEfforts || hasLegacyClinePassReasoningEfforts(name, prov)) && seed.reasoningEfforts) {
|
|
@@ -518,9 +536,7 @@ export function enrichProviderFromRegistry(name: string, prov: OcxProviderConfig
|
|
|
518
536
|
// provider. That split the two planes apart — routing merges these maps per key
|
|
519
537
|
// (mergeRecordFill in src/router.ts), so the wire honored the effort while /v1/models and
|
|
520
538
|
// every client export showed no effort control at all.
|
|
521
|
-
if (
|
|
522
|
-
prov.modelReasoningEfforts = fillRecordOfArrays(seed.modelReasoningEfforts, prov.modelReasoningEfforts);
|
|
523
|
-
}
|
|
539
|
+
if (resolvedStatic.modelReasoningEfforts) prov.modelReasoningEfforts = cloneRecordOfArrays(resolvedStatic.modelReasoningEfforts);
|
|
524
540
|
if (!prov.modelDefaultReasoningEfforts && seed.modelDefaultReasoningEfforts) prov.modelDefaultReasoningEfforts = { ...seed.modelDefaultReasoningEfforts };
|
|
525
541
|
if (!prov.reasoningEffortMap && seed.reasoningEffortMap) prov.reasoningEffortMap = { ...seed.reasoningEffortMap };
|
|
526
542
|
if (!prov.modelReasoningEffortMap && seed.modelReasoningEffortMap) prov.modelReasoningEffortMap = cloneNestedRecord(seed.modelReasoningEffortMap);
|
|
@@ -563,6 +579,7 @@ export function enrichProviderFromRegistry(name: string, prov: OcxProviderConfig
|
|
|
563
579
|
prov.supportsResponsesCustomTools = entry.supportsResponsesCustomTools;
|
|
564
580
|
}
|
|
565
581
|
if (prov.preserveResponsesReasoningContent === undefined && entry.preserveResponsesReasoningContent !== undefined) prov.preserveResponsesReasoningContent = entry.preserveResponsesReasoningContent;
|
|
582
|
+
if (prov.dropResponsesReasoningItems === undefined && entry.dropResponsesReasoningItems !== undefined) prov.dropResponsesReasoningItems = entry.dropResponsesReasoningItems;
|
|
566
583
|
applyReasoningSummaryDefaults(prov, entry.modelSupportsReasoningSummaries);
|
|
567
584
|
applyServiceTierModelDefaults(prov, serviceTierModelDefaultsFor(entry, prov));
|
|
568
585
|
applyVerbosityDefaults(prov, entry);
|
|
@@ -12,14 +12,12 @@
|
|
|
12
12
|
* key: the `devin-cli` registry entry is gone, so the PROVIDER_REGISTRY lookup
|
|
13
13
|
* that gated the rewrite here could never fire again.
|
|
14
14
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* adapter is rewritten to `devin`, whatever the row is called, and each
|
|
22
|
-
* rewrite is reported.
|
|
15
|
+
* Custom-named rows fail closed. The retired ACP transport used the installed
|
|
16
|
+
* CLI's identity and ignored both configured and caller credentials, so
|
|
17
|
+
* silently changing one of those rows into a cloud provider could send a
|
|
18
|
+
* credential with different provenance to Cognition. The warning tells the
|
|
19
|
+
* operator to reconfigure it explicitly; retaining the unknown adapter id
|
|
20
|
+
* prevents requests from reaching any upstream in the meantime.
|
|
23
21
|
*
|
|
24
22
|
* A row carrying the retired ACP identity URL is repointed at the api-server
|
|
25
23
|
* in the same pass. That URL was never a destination — it existed only so
|
|
@@ -55,10 +53,16 @@ export function projectDevinCliAuthMode(config: OcxConfig): DevinCliAuthModeProj
|
|
|
55
53
|
const warnings: string[] = [];
|
|
56
54
|
let changed = false;
|
|
57
55
|
|
|
58
|
-
// Every row, not only the registry id: a custom-named row had no registry pin,
|
|
59
|
-
// so after the ACP removal it is the one that cannot construct an adapter.
|
|
60
56
|
for (const [name, row] of Object.entries(config.providers ?? {})) {
|
|
61
57
|
if (!row || row.adapter !== RETIRED_ACP_ADAPTER) continue;
|
|
58
|
+
if (name !== RETIRED_ACP_ADAPTER) {
|
|
59
|
+
warnings.push(
|
|
60
|
+
`left custom provider "${name}" unchanged because adapter ${RETIRED_ACP_ADAPTER} was removed: `
|
|
61
|
+
+ "the row was not migrated; switch its adapter to devin and configure Devin authentication "
|
|
62
|
+
+ "explicitly before using this provider.",
|
|
63
|
+
);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
62
66
|
row.adapter = "devin";
|
|
63
67
|
changed = true;
|
|
64
68
|
let detail = "";
|
|
@@ -195,33 +195,54 @@ const DEFAULT_DEPS: DevinProviderMergeStartupDeps = {
|
|
|
195
195
|
* the snapshot is taken strictly before the save, and a backup failure throws
|
|
196
196
|
* rather than writing without a rollback point.
|
|
197
197
|
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
198
|
+
* Both destination slots are inspected before either account-bound file is
|
|
199
|
+
* changed. The auth write itself is deliberately detached. `startServer` is
|
|
200
|
+
* synchronous — an `await` in the boot window would suspend the composition root — and
|
|
200
201
|
* `mutateStore` is async-only, so the rekey is fired after its snapshot and
|
|
201
|
-
* its outcome is logged when it lands.
|
|
202
|
-
*
|
|
203
|
-
* refuses by design, and a failed rekey simply retries on the next boot.
|
|
202
|
+
* its outcome is logged when it lands. A late concurrent conflict refuses by
|
|
203
|
+
* design, and a failed rekey simply retries on the next boot.
|
|
204
204
|
*/
|
|
205
205
|
export function runDevinProviderMergeStartupMigration(
|
|
206
206
|
config: OcxConfig,
|
|
207
207
|
deps: DevinProviderMergeStartupDeps = DEFAULT_DEPS,
|
|
208
208
|
): OcxConfig {
|
|
209
209
|
const projection = deps.project(config);
|
|
210
|
-
|
|
210
|
+
const hasSourceConfig = config.providers?.[FROM_ID] !== undefined;
|
|
211
|
+
const hasSourceAuth = deps.hasAuthSlot(FROM_ID);
|
|
212
|
+
const hasDestinationAuth = deps.hasAuthSlot(TO_ID);
|
|
213
|
+
|
|
214
|
+
// A configured provider and its credentials are one account-bound unit. Do
|
|
215
|
+
// not move either half if the config projection refused, or if the target
|
|
216
|
+
// credential slot could belong to another account.
|
|
217
|
+
if (hasSourceConfig && (!projection.changed || hasDestinationAuth)) {
|
|
218
|
+
// Projection warnings still matter on a no-op: a config collision is the warning.
|
|
219
|
+
for (const warning of projection.warnings) console.warn(`[devin-provider-merge] ${warning}`);
|
|
220
|
+
if (projection.changed && hasDestinationAuth) {
|
|
221
|
+
console.warn(
|
|
222
|
+
`[devin-provider-merge] auth.json already has a "${TO_ID}" credential slot; `
|
|
223
|
+
+ `provider "${FROM_ID}" and both credential slots were left untouched. Remove the `
|
|
224
|
+
+ "unused destination credential manually, then restart.",
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
return config;
|
|
228
|
+
}
|
|
229
|
+
|
|
211
230
|
for (const warning of projection.warnings) console.warn(`[devin-provider-merge] ${warning}`);
|
|
231
|
+
|
|
212
232
|
let result = config;
|
|
213
233
|
if (projection.changed) {
|
|
234
|
+
// Snapshot both account-bound files before changing either one.
|
|
235
|
+
if (hasSourceAuth) deps.backupAuth();
|
|
214
236
|
deps.backupConfig();
|
|
215
237
|
deps.save(projection.config);
|
|
216
238
|
result = projection.config;
|
|
217
239
|
}
|
|
218
240
|
|
|
219
|
-
//
|
|
220
|
-
//
|
|
221
|
-
//
|
|
222
|
-
|
|
223
|
-
if (!deps.
|
|
224
|
-
deps.backupAuth();
|
|
241
|
+
// With no legacy config row, a `devin-cli` credential slot is orphaned and
|
|
242
|
+
// can still be rekeyed under the helper's refuse-on-occupied rule. A refused
|
|
243
|
+
// config migration returned above so its account-bound slot stays put.
|
|
244
|
+
if (!hasSourceAuth) return result;
|
|
245
|
+
if (!projection.changed) deps.backupAuth();
|
|
225
246
|
void deps.rekey(FROM_ID, TO_ID).then(outcome => {
|
|
226
247
|
if (outcome === "conflict") {
|
|
227
248
|
console.warn(
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { getProviderRegistryEntry } from "./registry";
|
|
2
|
+
|
|
1
3
|
export type ProviderAccessGroup =
|
|
2
4
|
| "recurring-or-keyless"
|
|
3
5
|
| "recurring-uncapped"
|
|
@@ -18,7 +20,7 @@ export const FREE_PROVIDER_ACCESS_GROUPS = {
|
|
|
18
20
|
],
|
|
19
21
|
"recurring-credit": ["bytez", "nous-research"],
|
|
20
22
|
"signup-credit": [
|
|
21
|
-
"agentrouter", "ai21", "baichuan", "baseten", "deepinfra", "deepseek", "doubao", "fireworks", "freemodel-dev", "glm-cn",
|
|
23
|
+
"agentrouter", "ai21", "baichuan", "baseten", "crusoe", "deepinfra", "deepseek", "doubao", "fireworks", "freemodel-dev", "glm-cn",
|
|
22
24
|
"hyperbolic", "longcat", "monsterapi", "nebius", "novita", "nscale", "nvidia", "predibase", "publicai", "qoder", "qoder-cn",
|
|
23
25
|
"scaleway", "sensenova", "stepfun", "together", "vertex",
|
|
24
26
|
],
|
|
@@ -72,6 +74,21 @@ const openAi = (baseUrl: string, dashboardUrl: string, extra: Partial<Connectabl
|
|
|
72
74
|
...extra,
|
|
73
75
|
});
|
|
74
76
|
|
|
77
|
+
function registryLiveKeyProvider(id: string): ConnectableOverride {
|
|
78
|
+
const entry = getProviderRegistryEntry(id);
|
|
79
|
+
if (!entry || entry.authKind !== "key" || !entry.dashboardUrl || entry.liveModels !== true) {
|
|
80
|
+
throw new TypeError(`Free directory registry projection requires a live key provider with a dashboard: ${id}`);
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
baseUrl: entry.baseUrl,
|
|
84
|
+
dashboardUrl: entry.dashboardUrl,
|
|
85
|
+
adapter: entry.adapter,
|
|
86
|
+
authKind: "key",
|
|
87
|
+
discovery: "live",
|
|
88
|
+
liveModels: true,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
75
92
|
// API roots are limited to documented or primary-source integrations. Consumer-web/session
|
|
76
93
|
// providers remain reference-only: this directory never asks users to paste cookies or bypass WAFs.
|
|
77
94
|
const CONNECTABLE: Record<string, ConnectableOverride> = {
|
|
@@ -127,6 +144,7 @@ const CONNECTABLE: Record<string, ConnectableOverride> = {
|
|
|
127
144
|
// Verified end-to-end 2026-07-30: /v1/models returns the OpenAI-shaped live catalog (13 models),
|
|
128
145
|
// and a chat completion against moonshotai/Kimi-K3 returned a standard chat.completion payload.
|
|
129
146
|
baseten: openAi("https://inference.baseten.co/v1", "https://app.baseten.co/settings/api_keys", { supportLevel: "supported", verification: "official", documentationUrl: "https://docs.baseten.co/inference/model-apis/overview", modelsUrl: "https://inference.baseten.co/v1/models", lastVerified: "2026-07-30" }),
|
|
147
|
+
crusoe: { ...registryLiveKeyProvider("crusoe"), supportLevel: "supported", verification: "official", documentationUrl: "https://docs.crusoecloud.com/quickstart/getting-started-with-serverless-inference", modelsUrl: "https://api.inference.crusoecloud.com/v1/models", lastVerified: "2026-09-11" },
|
|
130
148
|
deepinfra: openAi("https://api.deepinfra.com/v1/openai", "https://deepinfra.com/dash/api_keys", { supportLevel: "supported", verification: "official", documentationUrl: "https://deepinfra.com/docs/openai_api" }),
|
|
131
149
|
deepseek: openAi("https://api.deepseek.com", "https://platform.deepseek.com/api_keys", { supportLevel: "supported", verification: "official", documentationUrl: "https://api-docs.deepseek.com/api/list-models" }),
|
|
132
150
|
doubao: openAi("https://ark.cn-beijing.volces.com/api/v3", "https://console.volcengine.com/ark/region:ark+cn-beijing/apiKey", { verification: "official" }),
|
|
@@ -181,7 +199,7 @@ const LABELS: Record<string, string> = {
|
|
|
181
199
|
"t3-web": "T3 Web", uncloseai: "UncloseAI", ainative: "AI Native", baidu: "Baidu Qianfan",
|
|
182
200
|
glm: "Z.AI GLM", "glm-cn": "BigModel GLM (CN)", "kilo-gateway": "Kilo Gateway", "opencode-zen": "OpenCode Zen",
|
|
183
201
|
sealion: "SEA-LION", bytez: "Bytez", "nous-research": "Nous Research", agentrouter: "AgentRouter",
|
|
184
|
-
ai21: "AI21", baichuan: "Baichuan", deepinfra: "DeepInfra", deepseek: "DeepSeek", doubao: "Doubao",
|
|
202
|
+
ai21: "AI21", baichuan: "Baichuan", crusoe: "Crusoe", deepinfra: "DeepInfra", deepseek: "DeepSeek", doubao: "Doubao",
|
|
185
203
|
"freemodel-dev": "FreeModel.dev", sambanova: "SambaNova Cloud", nebius: "Nebius Token Factory",
|
|
186
204
|
novita: "Novita", nscale: "Nscale", nvidia: "NVIDIA NIM",
|
|
187
205
|
publicai: "PublicAI", qoder: "Qoder", "qoder-cn": "Qoder CN", sensenova: "SenseNova", stepfun: "StepFun", vertex: "Google Vertex AI",
|