codex-multi-auth 2.16.0 → 2.17.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/.codex-plugin/plugin.json +1 -1
- package/README.md +22 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -3
- package/dist/lib/account-policy.d.ts +4 -0
- package/dist/lib/account-policy.d.ts.map +1 -1
- package/dist/lib/account-policy.js +8 -0
- package/dist/lib/accounts/rate-limits.d.ts.map +1 -1
- package/dist/lib/accounts/rate-limits.js +2 -0
- package/dist/lib/accounts.d.ts +21 -3
- package/dist/lib/accounts.d.ts.map +1 -1
- package/dist/lib/accounts.js +229 -17
- package/dist/lib/api-route-store.d.ts +24 -0
- package/dist/lib/api-route-store.d.ts.map +1 -0
- package/dist/lib/api-route-store.js +145 -0
- package/dist/lib/auth/account-access.d.ts +115 -0
- package/dist/lib/auth/account-access.d.ts.map +1 -0
- package/dist/lib/auth/account-access.js +214 -0
- package/dist/lib/auth/token-utils.d.ts +54 -1
- package/dist/lib/auth/token-utils.d.ts.map +1 -1
- package/dist/lib/auth/token-utils.js +92 -7
- package/dist/lib/cli.d.ts +1 -1
- package/dist/lib/cli.d.ts.map +1 -1
- package/dist/lib/cli.js +4 -0
- package/dist/lib/codex-cli/state.d.ts +7 -0
- package/dist/lib/codex-cli/state.d.ts.map +1 -1
- package/dist/lib/codex-cli/state.js +18 -0
- package/dist/lib/codex-cli/writer.d.ts.map +1 -1
- package/dist/lib/codex-cli/writer.js +60 -8
- package/dist/lib/codex-manager/account-manager-commands.d.ts.map +1 -1
- package/dist/lib/codex-manager/account-manager-commands.js +1 -0
- package/dist/lib/codex-manager/account-pool-write.d.ts +3 -1
- package/dist/lib/codex-manager/account-pool-write.d.ts.map +1 -1
- package/dist/lib/codex-manager/account-pool-write.js +4 -0
- package/dist/lib/codex-manager/api-login-menu.d.ts +14 -0
- package/dist/lib/codex-manager/api-login-menu.d.ts.map +1 -0
- package/dist/lib/codex-manager/api-login-menu.js +224 -0
- package/dist/lib/codex-manager/commands/account.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/account.js +27 -0
- package/dist/lib/codex-manager/commands/best.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/best.js +6 -2
- package/dist/lib/codex-manager/commands/check.d.ts +7 -1
- package/dist/lib/codex-manager/commands/check.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/check.js +24 -3
- package/dist/lib/codex-manager/commands/forecast.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/forecast.js +5 -1
- package/dist/lib/codex-manager/commands/report.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/report.js +5 -1
- package/dist/lib/codex-manager/commands/resets.d.ts +2 -0
- package/dist/lib/codex-manager/commands/resets.d.ts.map +1 -0
- package/dist/lib/codex-manager/commands/resets.js +67 -0
- package/dist/lib/codex-manager/commands/rotation.d.ts +2 -2
- package/dist/lib/codex-manager/commands/rotation.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/rotation.js +63 -12
- package/dist/lib/codex-manager/commands/status.d.ts +12 -1
- package/dist/lib/codex-manager/commands/status.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/status.js +166 -22
- package/dist/lib/codex-manager/commands/uninstall.js +1 -1
- package/dist/lib/codex-manager/formatters/quota-formatters.d.ts.map +1 -1
- package/dist/lib/codex-manager/formatters/quota-formatters.js +3 -2
- package/dist/lib/codex-manager/health-check.d.ts +3 -0
- package/dist/lib/codex-manager/health-check.d.ts.map +1 -1
- package/dist/lib/codex-manager/health-check.js +56 -6
- package/dist/lib/codex-manager/help.d.ts.map +1 -1
- package/dist/lib/codex-manager/help.js +4 -2
- package/dist/lib/codex-manager/login-flow.d.ts.map +1 -1
- package/dist/lib/codex-manager/login-flow.js +69 -7
- package/dist/lib/codex-manager/login-menu-data.d.ts.map +1 -1
- package/dist/lib/codex-manager/login-menu-data.js +14 -5
- package/dist/lib/codex-manager/login-oauth.d.ts +3 -0
- package/dist/lib/codex-manager/login-oauth.d.ts.map +1 -1
- package/dist/lib/codex-manager/login-oauth.js +6 -1
- package/dist/lib/codex-manager/login-workspace-choice.d.ts +11 -0
- package/dist/lib/codex-manager/login-workspace-choice.d.ts.map +1 -0
- package/dist/lib/codex-manager/login-workspace-choice.js +37 -0
- package/dist/lib/codex-manager/persist-selected-account.d.ts.map +1 -1
- package/dist/lib/codex-manager/persist-selected-account.js +2 -1
- package/dist/lib/codex-manager/repair-commands.d.ts +13 -0
- package/dist/lib/codex-manager/repair-commands.d.ts.map +1 -1
- package/dist/lib/codex-manager/repair-commands.js +143 -8
- package/dist/lib/codex-manager.d.ts.map +1 -1
- package/dist/lib/codex-manager.js +25 -6
- package/dist/lib/concurrency.d.ts +3 -0
- package/dist/lib/concurrency.d.ts.map +1 -0
- package/dist/lib/concurrency.js +31 -0
- package/dist/lib/model-route-policy.d.ts +52 -0
- package/dist/lib/model-route-policy.d.ts.map +1 -0
- package/dist/lib/model-route-policy.js +159 -0
- package/dist/lib/policy/runtime-policy.d.ts +1 -0
- package/dist/lib/policy/runtime-policy.d.ts.map +1 -1
- package/dist/lib/policy/runtime-policy.js +3 -0
- package/dist/lib/preemptive-quota-scheduler.d.ts +2 -0
- package/dist/lib/preemptive-quota-scheduler.d.ts.map +1 -1
- package/dist/lib/preemptive-quota-scheduler.js +4 -0
- package/dist/lib/quota-probe.d.ts +5 -0
- package/dist/lib/quota-probe.d.ts.map +1 -1
- package/dist/lib/quota-probe.js +26 -2
- package/dist/lib/request/client-cancellation.d.ts +5 -0
- package/dist/lib/request/client-cancellation.d.ts.map +1 -0
- package/dist/lib/request/client-cancellation.js +7 -0
- package/dist/lib/request/response-outcome.d.ts +25 -0
- package/dist/lib/request/response-outcome.d.ts.map +1 -0
- package/dist/lib/request/response-outcome.js +49 -0
- package/dist/lib/request/stream-failover-runtime.d.ts +2 -1
- package/dist/lib/request/stream-failover-runtime.d.ts.map +1 -1
- package/dist/lib/request/stream-failover-runtime.js +29 -10
- package/dist/lib/runtime/account-check.d.ts.map +1 -1
- package/dist/lib/runtime/account-check.js +2 -8
- package/dist/lib/runtime/account-model-catalog.d.ts +64 -0
- package/dist/lib/runtime/account-model-catalog.d.ts.map +1 -0
- package/dist/lib/runtime/account-model-catalog.js +154 -0
- package/dist/lib/runtime/account-pool.d.ts +3 -1
- package/dist/lib/runtime/account-pool.d.ts.map +1 -1
- package/dist/lib/runtime/account-pool.js +4 -0
- package/dist/lib/runtime/account-reset-credits.d.ts +42 -0
- package/dist/lib/runtime/account-reset-credits.d.ts.map +1 -0
- package/dist/lib/runtime/account-reset-credits.js +82 -0
- package/dist/lib/runtime/api-model-capabilities.d.ts +30 -0
- package/dist/lib/runtime/api-model-capabilities.d.ts.map +1 -0
- package/dist/lib/runtime/api-model-capabilities.js +434 -0
- package/dist/lib/runtime/api-model-runtime.d.ts +42 -0
- package/dist/lib/runtime/api-model-runtime.d.ts.map +1 -0
- package/dist/lib/runtime/api-model-runtime.js +358 -0
- package/dist/lib/runtime/app-bind.d.ts +19 -1
- package/dist/lib/runtime/app-bind.d.ts.map +1 -1
- package/dist/lib/runtime/app-bind.js +79 -9
- package/dist/lib/runtime/automatic-account-checks.d.ts +31 -0
- package/dist/lib/runtime/automatic-account-checks.d.ts.map +1 -0
- package/dist/lib/runtime/automatic-account-checks.js +113 -0
- package/dist/lib/runtime/automatic-subscription-checks.d.ts +7 -0
- package/dist/lib/runtime/automatic-subscription-checks.d.ts.map +1 -0
- package/dist/lib/runtime/automatic-subscription-checks.js +57 -0
- package/dist/lib/runtime/catalog-capabilities.d.ts +8 -0
- package/dist/lib/runtime/catalog-capabilities.d.ts.map +1 -0
- package/dist/lib/runtime/catalog-capabilities.js +61 -0
- package/dist/lib/runtime/inference-activity.d.ts +15 -0
- package/dist/lib/runtime/inference-activity.d.ts.map +1 -0
- package/dist/lib/runtime/inference-activity.js +95 -0
- package/dist/lib/runtime/model-discovery-status.d.ts +100 -0
- package/dist/lib/runtime/model-discovery-status.d.ts.map +1 -0
- package/dist/lib/runtime/model-discovery-status.js +387 -0
- package/dist/lib/runtime/native-account-storage.d.ts +11 -0
- package/dist/lib/runtime/native-account-storage.d.ts.map +1 -0
- package/dist/lib/runtime/native-account-storage.js +50 -0
- package/dist/lib/runtime/native-account-sync.d.ts +19 -0
- package/dist/lib/runtime/native-account-sync.d.ts.map +1 -0
- package/dist/lib/runtime/native-account-sync.js +62 -0
- package/dist/lib/runtime/native-binding-lock.d.ts +3 -0
- package/dist/lib/runtime/native-binding-lock.d.ts.map +1 -0
- package/dist/lib/runtime/native-binding-lock.js +5 -0
- package/dist/lib/runtime/native-client-auth.d.ts +3 -0
- package/dist/lib/runtime/native-client-auth.d.ts.map +1 -0
- package/dist/lib/runtime/native-client-auth.js +42 -0
- package/dist/lib/runtime/native-provider-config.d.ts +4 -0
- package/dist/lib/runtime/native-provider-config.d.ts.map +1 -0
- package/dist/lib/runtime/native-provider-config.js +52 -0
- package/dist/lib/runtime/native-rate-limits.d.ts +18 -0
- package/dist/lib/runtime/native-rate-limits.d.ts.map +1 -0
- package/dist/lib/runtime/native-rate-limits.js +135 -0
- package/dist/lib/runtime/reset-credit-routing.d.ts +41 -0
- package/dist/lib/runtime/reset-credit-routing.d.ts.map +1 -0
- package/dist/lib/runtime/reset-credit-routing.js +66 -0
- package/dist/lib/runtime/reset-credits.d.ts +101 -0
- package/dist/lib/runtime/reset-credits.d.ts.map +1 -0
- package/dist/lib/runtime/reset-credits.js +162 -0
- package/dist/lib/runtime/response-output-history.d.ts +17 -0
- package/dist/lib/runtime/response-output-history.d.ts.map +1 -0
- package/dist/lib/runtime/response-output-history.js +85 -0
- package/dist/lib/runtime/responses-websocket.d.ts +31 -0
- package/dist/lib/runtime/responses-websocket.d.ts.map +1 -0
- package/dist/lib/runtime/responses-websocket.js +636 -0
- package/dist/lib/runtime/resume-picker.d.ts +41 -0
- package/dist/lib/runtime/resume-picker.d.ts.map +1 -0
- package/dist/lib/runtime/resume-picker.js +169 -0
- package/dist/lib/runtime/rotation-account-selection.d.ts +6 -0
- package/dist/lib/runtime/rotation-account-selection.d.ts.map +1 -1
- package/dist/lib/runtime/rotation-account-selection.js +63 -0
- package/dist/lib/runtime/rotation-proxy-state.d.ts +33 -0
- package/dist/lib/runtime/rotation-proxy-state.d.ts.map +1 -1
- package/dist/lib/runtime/rotation-server-types.d.ts +18 -0
- package/dist/lib/runtime/rotation-server-types.d.ts.map +1 -1
- package/dist/lib/runtime/runtime-capability-failures.d.ts +14 -0
- package/dist/lib/runtime/runtime-capability-failures.d.ts.map +1 -0
- package/dist/lib/runtime/runtime-capability-failures.js +76 -0
- package/dist/lib/runtime/runtime-observability.d.ts +7 -0
- package/dist/lib/runtime/runtime-observability.d.ts.map +1 -1
- package/dist/lib/runtime/runtime-observability.js +42 -2
- package/dist/lib/runtime/subscription-first-use.d.ts +10 -0
- package/dist/lib/runtime/subscription-first-use.d.ts.map +1 -0
- package/dist/lib/runtime/subscription-first-use.js +64 -0
- package/dist/lib/runtime/subscription-quota-event.d.ts +6 -0
- package/dist/lib/runtime/subscription-quota-event.d.ts.map +1 -0
- package/dist/lib/runtime/subscription-quota-event.js +31 -0
- package/dist/lib/runtime/subscription-quota-order.d.ts +17 -0
- package/dist/lib/runtime/subscription-quota-order.d.ts.map +1 -0
- package/dist/lib/runtime/subscription-quota-order.js +31 -0
- package/dist/lib/runtime/workspace-model-scopes.d.ts +15 -0
- package/dist/lib/runtime/workspace-model-scopes.d.ts.map +1 -0
- package/dist/lib/runtime/workspace-model-scopes.js +34 -0
- package/dist/lib/runtime-constants.d.ts +3 -0
- package/dist/lib/runtime-constants.d.ts.map +1 -1
- package/dist/lib/runtime-constants.js +5 -0
- package/dist/lib/runtime-paths.d.ts.map +1 -1
- package/dist/lib/runtime-paths.js +4 -1
- package/dist/lib/runtime-rotation-proxy.d.ts.map +1 -1
- package/dist/lib/runtime-rotation-proxy.js +852 -42
- package/dist/lib/schemas.d.ts +20 -0
- package/dist/lib/schemas.d.ts.map +1 -1
- package/dist/lib/schemas.js +3 -0
- package/dist/lib/storage/credential-sidecars.d.ts +13 -0
- package/dist/lib/storage/credential-sidecars.d.ts.map +1 -0
- package/dist/lib/storage/credential-sidecars.js +52 -0
- package/dist/lib/storage/file-lock.d.ts +9 -0
- package/dist/lib/storage/file-lock.d.ts.map +1 -0
- package/dist/lib/storage/file-lock.js +186 -0
- package/dist/lib/storage/flagged-storage.d.ts.map +1 -1
- package/dist/lib/storage/flagged-storage.js +12 -0
- package/dist/lib/storage/path-state.d.ts +2 -0
- package/dist/lib/storage/path-state.d.ts.map +1 -1
- package/dist/lib/storage/path-state.js +9 -0
- package/dist/lib/storage/paths.js +4 -4
- package/dist/lib/storage/pending-auth.d.ts +17 -0
- package/dist/lib/storage/pending-auth.d.ts.map +1 -0
- package/dist/lib/storage/pending-auth.js +153 -0
- package/dist/lib/storage/public-types.d.ts +13 -0
- package/dist/lib/storage/public-types.d.ts.map +1 -1
- package/dist/lib/storage/snapshot-merge.d.ts +16 -0
- package/dist/lib/storage/snapshot-merge.d.ts.map +1 -0
- package/dist/lib/storage/snapshot-merge.js +206 -0
- package/dist/lib/storage/transactions.d.ts.map +1 -1
- package/dist/lib/storage/transactions.js +5 -4
- package/dist/lib/storage.d.ts +6 -0
- package/dist/lib/storage.d.ts.map +1 -1
- package/dist/lib/storage.js +77 -7
- package/dist/lib/ui/auth-menu-builder.d.ts +2 -0
- package/dist/lib/ui/auth-menu-builder.d.ts.map +1 -1
- package/dist/lib/ui/auth-menu-builder.js +1 -0
- package/dist/lib/ui/auth-menu.d.ts.map +1 -1
- package/dist/lib/ui/auth-menu.js +1 -0
- package/dist/lib/ui/check-progress.d.ts +8 -0
- package/dist/lib/ui/check-progress.d.ts.map +1 -0
- package/dist/lib/ui/check-progress.js +30 -0
- package/dist/lib/ui/format.d.ts +3 -1
- package/dist/lib/ui/format.d.ts.map +1 -1
- package/dist/lib/ui/format.js +8 -0
- package/dist/lib/ui/ui-copy.d.ts +2 -2
- package/dist/lib/ui/ui-copy.js +2 -2
- package/dist/lib/usage/pricing.js +2 -2
- package/dist/lib/usage/usage-extraction.d.ts +3 -1
- package/dist/lib/usage/usage-extraction.d.ts.map +1 -1
- package/dist/lib/usage/usage-extraction.js +46 -9
- package/package.json +7 -4
- package/scripts/codex-app-router.js +7 -0
- package/scripts/codex-bin-resolver.js +40 -8
- package/scripts/codex-routing.js +1 -0
- package/scripts/codex.js +100 -22
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
import { withFileTransactionLock } from "../storage/file-lock.js";
|
|
2
|
+
import { styleReportText as paint } from "../ui/format.js";
|
|
3
|
+
import { withCheckProgress } from "../ui/check-progress.js";
|
|
4
|
+
import { mapWithConcurrency } from "../concurrency.js";
|
|
5
|
+
import { ApiModelCapabilities } from "./api-model-capabilities.js";
|
|
6
|
+
import { getAppBindStatus } from "./app-bind.js";
|
|
7
|
+
import { isRecord } from "../utils.js";
|
|
8
|
+
import { promises as fs } from "node:fs";
|
|
9
|
+
import { join, dirname } from "node:path";
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
import { modelScopeId, workspaceModelScopes } from "./workspace-model-scopes.js";
|
|
12
|
+
import { AccountManager } from "../accounts.js";
|
|
13
|
+
import { loadAccounts } from "../storage.js";
|
|
14
|
+
import { loadApiRoutes, updateApiModelDiscovery, } from "../api-route-store.js";
|
|
15
|
+
import { ApiModelRuntime, boundedJson } from "./api-model-runtime.js";
|
|
16
|
+
import { AccountModelCatalog } from "./account-model-catalog.js";
|
|
17
|
+
import { ensureFreshAccessToken } from "./rotation-token-refresh.js";
|
|
18
|
+
import { createCodexHeaders } from "../request/headers.js";
|
|
19
|
+
import { CODEX_BASE_URL } from "../constants.js";
|
|
20
|
+
import { getCodexMultiAuthDir } from "../runtime-paths.js";
|
|
21
|
+
import { tempPathFor } from "../temp-path.js";
|
|
22
|
+
import { withRetry } from "../fs-retry.js";
|
|
23
|
+
const printable = z
|
|
24
|
+
.string()
|
|
25
|
+
.max(256)
|
|
26
|
+
.regex(/^[^\x00-\x1f\x7f]*$/);
|
|
27
|
+
const entitlementSchema = z.object({
|
|
28
|
+
model: printable, checkedAt: z.number().optional(),
|
|
29
|
+
probes: z.record(printable, z.enum(["verified", "unsupported", "unverified", "downgraded"])).optional(),
|
|
30
|
+
serviceTiers: z.array(z.object({ id: printable, name: printable })).max(100),
|
|
31
|
+
reasoningLevels: z.array(printable).max(100), accessPrograms: z.array(printable).max(100),
|
|
32
|
+
});
|
|
33
|
+
const entrySchema = z.object({
|
|
34
|
+
id: printable.optional(), routable: z.boolean().optional(), bound: z.boolean().optional(), selected: z.boolean().optional(),
|
|
35
|
+
changes: z.object({ observedAt: z.number(), added: z.array(printable).max(10000), removed: z.array(printable).max(10000), capabilities: z.array(printable).max(10000) }).optional(),
|
|
36
|
+
lastSuccessful: z.object({ models: z.array(printable).max(10000), entitlements: z.array(entitlementSchema).max(1000).optional() }).optional(),
|
|
37
|
+
label: printable,
|
|
38
|
+
kind: z.enum(["oauth", "api", "zdr"]),
|
|
39
|
+
enabled: z.boolean(),
|
|
40
|
+
checkedAt: z.number(),
|
|
41
|
+
error: z.boolean(),
|
|
42
|
+
models: z.array(printable).max(10000),
|
|
43
|
+
visibleModels: z.array(printable).max(10000),
|
|
44
|
+
entitlements: z.array(entitlementSchema).max(1000).optional(),
|
|
45
|
+
});
|
|
46
|
+
const schema = z.object({
|
|
47
|
+
apiConfigurationUnavailable: z.boolean().optional(),
|
|
48
|
+
version: z.literal(1),
|
|
49
|
+
checkedAt: z.number(),
|
|
50
|
+
clientVersion: z
|
|
51
|
+
.string()
|
|
52
|
+
.max(80)
|
|
53
|
+
.regex(/^[0-9A-Za-z.+_-]+$/)
|
|
54
|
+
.optional(),
|
|
55
|
+
entries: z.array(entrySchema).max(3000),
|
|
56
|
+
});
|
|
57
|
+
const probeCacheSchema = z.object({
|
|
58
|
+
version: z.literal(1),
|
|
59
|
+
entries: z.array(z.tuple([
|
|
60
|
+
z.string().regex(/^[0-9a-f]{64}$/),
|
|
61
|
+
z.object({ at: z.number(), levels: z.array(printable).max(100), tiers: z.array(printable).max(100), status: z.record(printable, printable) }),
|
|
62
|
+
])).max(1000),
|
|
63
|
+
});
|
|
64
|
+
function probeCachePath() {
|
|
65
|
+
return join(getCodexMultiAuthDir(), "api-capability-probes.json");
|
|
66
|
+
}
|
|
67
|
+
async function loadProbeCache() {
|
|
68
|
+
try {
|
|
69
|
+
return probeCacheSchema.parse(JSON.parse(await fs.readFile(probeCachePath(), "utf8"))).entries;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
export async function discoverModelInventory(storage, routes, options = {}) {
|
|
76
|
+
const fetcher = options.fetchImpl ?? fetch, now = options.now ?? Date.now;
|
|
77
|
+
const manager = new AccountManager(undefined, storage ?? {
|
|
78
|
+
version: 3,
|
|
79
|
+
accounts: [],
|
|
80
|
+
activeIndex: 0,
|
|
81
|
+
activeIndexByFamily: {},
|
|
82
|
+
});
|
|
83
|
+
const entries = [];
|
|
84
|
+
const scopes = manager.getAccountsSnapshot().flatMap(workspaceModelScopes);
|
|
85
|
+
const catalog = new AccountModelCatalog(async (key) => {
|
|
86
|
+
const scope = scopes.find(item => item.id === key);
|
|
87
|
+
const account = scope?.enabled ? manager.getAccountByIndex(scope.accountIndex) : null;
|
|
88
|
+
if (!account || account.enabled === false)
|
|
89
|
+
throw Error("Disabled");
|
|
90
|
+
const fresh = await ensureFreshAccessToken({
|
|
91
|
+
accountManager: manager,
|
|
92
|
+
account,
|
|
93
|
+
family: "codex",
|
|
94
|
+
model: null,
|
|
95
|
+
now: now(),
|
|
96
|
+
tokenRefreshSkewMs: 60000,
|
|
97
|
+
tokenInvalidationCooldownMs: 30000,
|
|
98
|
+
});
|
|
99
|
+
if (!fresh.ok)
|
|
100
|
+
throw Error("Authentication unavailable");
|
|
101
|
+
const id = scope?.accountId;
|
|
102
|
+
if (!id)
|
|
103
|
+
throw Error("Identity unavailable");
|
|
104
|
+
const url = new URL(`${CODEX_BASE_URL}/codex/models`);
|
|
105
|
+
const version = options.clientVersion;
|
|
106
|
+
if (version && /^[0-9A-Za-z.+_-]{1,80}$/.test(version))
|
|
107
|
+
url.searchParams.set("client_version", version);
|
|
108
|
+
const response = await fetcher(url, {
|
|
109
|
+
headers: createCodexHeaders({
|
|
110
|
+
accountId: id,
|
|
111
|
+
accessToken: fresh.accessToken,
|
|
112
|
+
}),
|
|
113
|
+
redirect: "error",
|
|
114
|
+
signal: AbortSignal.timeout(15000),
|
|
115
|
+
});
|
|
116
|
+
if (!response.ok) {
|
|
117
|
+
await response.body?.cancel();
|
|
118
|
+
throw Error("Catalog unavailable");
|
|
119
|
+
}
|
|
120
|
+
return boundedJson(response);
|
|
121
|
+
}, now);
|
|
122
|
+
try {
|
|
123
|
+
entries.push(...await mapWithConcurrency(scopes, 3, async (scope) => {
|
|
124
|
+
if (scope.enabled)
|
|
125
|
+
await catalog.list([scope.id]);
|
|
126
|
+
const snapshot = catalog.snapshot(scope.id);
|
|
127
|
+
return { id: scope.id, label: scope.label, kind: "oauth", enabled: scope.enabled, routable: scope.routable, bound: scope.bound, selected: scope.selected,
|
|
128
|
+
...snapshot, error: scope.enabled && snapshot.error, checkedAt: now(), visibleModels: scope.routable ? snapshot.models : [] };
|
|
129
|
+
}));
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
await manager.flushPendingSave();
|
|
133
|
+
}
|
|
134
|
+
const capabilities = new ApiModelCapabilities(fetcher, now);
|
|
135
|
+
if (options.persistProbes)
|
|
136
|
+
capabilities.importProbes(await loadProbeCache());
|
|
137
|
+
const api = new ApiModelRuntime(fetcher, now, options.updateApiDiscovery, options.updateApiDiscovery
|
|
138
|
+
? (models, refresh, route, forceProbes) => capabilities.enrich(models, refresh, route, forceProbes)
|
|
139
|
+
: undefined);
|
|
140
|
+
await api.catalogs(routes, true, options.forceProbes === true);
|
|
141
|
+
if (options.persistProbes) {
|
|
142
|
+
const path = probeCachePath();
|
|
143
|
+
await withFileTransactionLock(path, async () => {
|
|
144
|
+
// Merge with the latest file: a concurrent check may have probed other
|
|
145
|
+
// credentials since this one loaded. The newer result wins per key.
|
|
146
|
+
const merged = new Map(await loadProbeCache());
|
|
147
|
+
for (const [key, value] of capabilities.exportProbes()) {
|
|
148
|
+
const disk = merged.get(key);
|
|
149
|
+
if (!disk || disk.at <= value.at)
|
|
150
|
+
merged.set(key, value);
|
|
151
|
+
}
|
|
152
|
+
const entries = [...merged].sort((x, y) => x[1].at - y[1].at).slice(-1000);
|
|
153
|
+
await writeFileAtomic(path, JSON.stringify(probeCacheSchema.parse({ version: 1, entries })) + "\n");
|
|
154
|
+
}).catch(() => undefined);
|
|
155
|
+
}
|
|
156
|
+
entries.push(...api.statuses(routes).map((s, i) => ({
|
|
157
|
+
id: modelScopeId(s.kind, s.id),
|
|
158
|
+
label: s.label,
|
|
159
|
+
kind: s.kind,
|
|
160
|
+
enabled: routes[i]?.enabled ?? false,
|
|
161
|
+
checkedAt: s.checkedAt,
|
|
162
|
+
error: s.error,
|
|
163
|
+
models: s.availableModels,
|
|
164
|
+
visibleModels: s.visibleModels,
|
|
165
|
+
entitlements: s.entitlements,
|
|
166
|
+
})));
|
|
167
|
+
return {
|
|
168
|
+
version: 1,
|
|
169
|
+
checkedAt: now(),
|
|
170
|
+
clientVersion: options.clientVersion,
|
|
171
|
+
entries,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function inventoryPath() {
|
|
175
|
+
return join(getCodexMultiAuthDir(), "model-discovery.json");
|
|
176
|
+
}
|
|
177
|
+
export async function loadModelInventory() {
|
|
178
|
+
try {
|
|
179
|
+
const file = await fs.open(inventoryPath(), "r");
|
|
180
|
+
try {
|
|
181
|
+
const buffer = Buffer.alloc(4 * 1024 * 1024 + 1);
|
|
182
|
+
const { bytesRead } = await file.read(buffer, 0, buffer.length, 0);
|
|
183
|
+
if (bytesRead > 4 * 1024 * 1024)
|
|
184
|
+
return null;
|
|
185
|
+
return schema.parse(JSON.parse(buffer.subarray(0, bytesRead).toString("utf8")));
|
|
186
|
+
}
|
|
187
|
+
finally {
|
|
188
|
+
await file.close();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
let inventoryWrites = Promise.resolve();
|
|
196
|
+
export function saveModelInventory(value) {
|
|
197
|
+
const path = inventoryPath();
|
|
198
|
+
const pending = inventoryWrites.then(() => withFileTransactionLock(path, () => saveModelInventorySerial(value, path)));
|
|
199
|
+
inventoryWrites = pending.catch(() => undefined);
|
|
200
|
+
return pending;
|
|
201
|
+
}
|
|
202
|
+
async function saveModelInventorySerial(value, path) {
|
|
203
|
+
const data = schema.parse(withInventoryChanges(value, await loadModelInventory()));
|
|
204
|
+
const serialized = JSON.stringify(data) + "\n";
|
|
205
|
+
if (Buffer.byteLength(serialized) > 4 * 1024 * 1024)
|
|
206
|
+
throw Error("Model discovery status exceeds size limit");
|
|
207
|
+
await writeFileAtomic(path, serialized);
|
|
208
|
+
}
|
|
209
|
+
async function writeFileAtomic(path, serialized) {
|
|
210
|
+
const temp = tempPathFor(path);
|
|
211
|
+
await fs.mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
212
|
+
const retry = { maxAttempts: 6, backoffMs: 25 };
|
|
213
|
+
try {
|
|
214
|
+
await fs.writeFile(temp, serialized, {
|
|
215
|
+
mode: 0o600,
|
|
216
|
+
flag: "wx",
|
|
217
|
+
});
|
|
218
|
+
await withRetry(() => fs.rename(temp, path), retry);
|
|
219
|
+
}
|
|
220
|
+
finally {
|
|
221
|
+
await withRetry(() => fs.unlink(temp), retry).catch((e) => {
|
|
222
|
+
if (e.code !== "ENOENT")
|
|
223
|
+
throw e;
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
export function formatModelInventory(inventory, now = Date.now()) {
|
|
228
|
+
if (!inventory)
|
|
229
|
+
return [paint("Models: not checked; run codex-multi-auth check.", "warning")];
|
|
230
|
+
return [
|
|
231
|
+
paint("Model discovery (catalog access, not an inference probe):", "heading"),
|
|
232
|
+
...inventoryHighlights(inventory),
|
|
233
|
+
...inventory.entries.flatMap((e) => [
|
|
234
|
+
` ${paint(e.label, "heading")}${e.routable === true ? " [routing eligible]" : e.routable === false ? " [discovery only]" : ""}${e.bound ? " [stored binding]" : ""}${e.selected ? " [preferred workspace]" : ""} [${e.kind.toUpperCase()}]: ${!e.enabled ? paint("disabled", "muted") : e.error ? paint("discovery failed", "danger") : paint(`${e.models.length} available`, "success")}; ${e.checkedAt ? new Date(e.checkedAt).toISOString() : "never checked"}${now - e.checkedAt > 60000 ? paint(" (stale; run check)", "warning") : ""}`,
|
|
235
|
+
` ${paint("Available:", "muted")} ${e.models.join(", ") || "none"}`,
|
|
236
|
+
...(e.entitlements ?? []).map((m) => ` ${paint(m.model, "accent")}: speeds=${m.serviceTiers.map((t) => `${t.name} (${t.id})`).join(", ") || "not advertised"}; reasoning=${m.reasoningLevels.join(", ") || "not advertised"}; programs=${m.accessPrograms.join(", ") || "not advertised"}${m.probes
|
|
237
|
+
? `; probes: ${Object.entries(m.probes)
|
|
238
|
+
.map(([k, v]) => `${k}=${paint(v, v === "verified" ? "success" : v === "unsupported" || v === "downgraded" ? "warning" : "muted")}`)
|
|
239
|
+
.join(", ")} (${new Date(m.checkedAt ?? 0).toISOString()})`
|
|
240
|
+
: ""}`),
|
|
241
|
+
...(e.kind !== "oauth"
|
|
242
|
+
? [
|
|
243
|
+
` ${paint("Visible:", "accent")} ${e.visibleModels.map((m) => `${e.kind}/${m}`).join(", ") || "none"}`,
|
|
244
|
+
]
|
|
245
|
+
: []),
|
|
246
|
+
]),
|
|
247
|
+
];
|
|
248
|
+
}
|
|
249
|
+
export async function refreshAndPrintModelInventory(log, deps = {}) {
|
|
250
|
+
try {
|
|
251
|
+
const load = deps.loadInventory ?? loadModelInventory;
|
|
252
|
+
const previous = await load();
|
|
253
|
+
const binding = await (deps.getBinding ?? getAppBindStatus)();
|
|
254
|
+
if (binding.running && binding.state?.nativeOpenai) {
|
|
255
|
+
const started = (deps.now ?? Date.now)();
|
|
256
|
+
const url = new URL(binding.state.baseUrl);
|
|
257
|
+
if (url.protocol !== "http:" ||
|
|
258
|
+
!["127.0.0.1", "[::1]"].includes(url.hostname) ||
|
|
259
|
+
url.username ||
|
|
260
|
+
url.password ||
|
|
261
|
+
url.pathname !== "/" ||
|
|
262
|
+
url.search ||
|
|
263
|
+
url.hash)
|
|
264
|
+
throw Error("Invalid local refresh address");
|
|
265
|
+
url.pathname = "/models";
|
|
266
|
+
url.searchParams.set("refresh_capabilities", "1");
|
|
267
|
+
if (deps.forceProbes)
|
|
268
|
+
url.searchParams.set("force_probes", "1");
|
|
269
|
+
if (previous?.clientVersion)
|
|
270
|
+
url.searchParams.set("client_version", previous.clientVersion);
|
|
271
|
+
const clientApiKey = binding.state.clientApiKey;
|
|
272
|
+
const current = await withCheckProgress("Refreshing workspace catalogs and API/ZDR probes", async () => {
|
|
273
|
+
const response = await (deps.fetchImpl ?? fetch)(url, {
|
|
274
|
+
headers: { authorization: `Bearer ${clientApiKey}` },
|
|
275
|
+
redirect: "error",
|
|
276
|
+
signal: AbortSignal.timeout(60000),
|
|
277
|
+
});
|
|
278
|
+
if (!response.ok) {
|
|
279
|
+
await response.body?.cancel();
|
|
280
|
+
throw Error("Proxy refresh failed");
|
|
281
|
+
}
|
|
282
|
+
const body = await boundedJson(response);
|
|
283
|
+
if (!isRecord(body) || !Array.isArray(body.models))
|
|
284
|
+
throw Error("Invalid refreshed catalog");
|
|
285
|
+
const current = await load();
|
|
286
|
+
if (!current || current.checkedAt < started)
|
|
287
|
+
throw Error("Refreshed status unavailable");
|
|
288
|
+
return current;
|
|
289
|
+
}, log);
|
|
290
|
+
log("Running proxy model catalog refreshed. Desktop picks it up on its next response or scheduled catalog refresh.");
|
|
291
|
+
for (const line of formatModelInventory(withInventoryChanges(current, previous)))
|
|
292
|
+
log(line);
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
const value = await withCheckProgress("Discovering workspace models and API/ZDR capabilities", async () => {
|
|
296
|
+
let apiConfigurationUnavailable = false;
|
|
297
|
+
const value = await discoverModelInventory(await loadAccounts(), await loadApiRoutes().catch(() => { apiConfigurationUnavailable = true; log("API configuration unavailable; checking subscription models only."); return []; }), {
|
|
298
|
+
clientVersion: previous?.clientVersion,
|
|
299
|
+
updateApiDiscovery: updateApiModelDiscovery,
|
|
300
|
+
forceProbes: deps.forceProbes === true,
|
|
301
|
+
persistProbes: true,
|
|
302
|
+
});
|
|
303
|
+
if (apiConfigurationUnavailable)
|
|
304
|
+
value.apiConfigurationUnavailable = true;
|
|
305
|
+
await saveModelInventory(value);
|
|
306
|
+
return value;
|
|
307
|
+
}, log);
|
|
308
|
+
for (const line of formatModelInventory(withInventoryChanges(value, previous)))
|
|
309
|
+
log(line);
|
|
310
|
+
return true;
|
|
311
|
+
}
|
|
312
|
+
catch {
|
|
313
|
+
log("Model discovery failed; previous status may be stale. Check credentials and configuration.");
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
function entitlementFeatures(entry) {
|
|
318
|
+
return [...new Set([
|
|
319
|
+
...entry.serviceTiers.map(t => `speed ${t.id === "fast" ? "priority" : t.id}`),
|
|
320
|
+
...entry.reasoningLevels.map(level => `effort ${level}`),
|
|
321
|
+
...entry.accessPrograms.map(program => `program ${program}`),
|
|
322
|
+
])].sort();
|
|
323
|
+
}
|
|
324
|
+
/** Compare stable credential/workspace scopes, keeping the last successful baseline through outages. */
|
|
325
|
+
export function withInventoryChanges(current, previous) {
|
|
326
|
+
const prior = new Map(previous?.entries.filter(e => e.id).map(e => [e.id, e]));
|
|
327
|
+
const entries = current.apiConfigurationUnavailable
|
|
328
|
+
? [...current.entries, ...(previous?.entries ?? []).filter(entry => entry.kind !== "oauth" && !current.entries.some(row => row.id === entry.id)).map(entry => ({ ...entry, error: true, routable: false, models: [], visibleModels: [] }))]
|
|
329
|
+
: current.entries;
|
|
330
|
+
return { ...current, entries: entries.map(entry => {
|
|
331
|
+
const old = entry.id ? prior.get(entry.id) : undefined;
|
|
332
|
+
const baseline = old?.lastSuccessful ?? (old?.enabled && !old.error ? { models: old.models, entitlements: old.entitlements } : undefined);
|
|
333
|
+
const recent = old?.changes && current.checkedAt - old.changes.observedAt < 24 * 60 * 60 * 1000 ? old.changes : undefined;
|
|
334
|
+
if (!entry.enabled || entry.error)
|
|
335
|
+
return { ...entry, lastSuccessful: baseline, changes: recent };
|
|
336
|
+
const lastSuccessful = { models: entry.models, entitlements: entry.entitlements };
|
|
337
|
+
if (!baseline)
|
|
338
|
+
return { ...entry, lastSuccessful, changes: undefined };
|
|
339
|
+
const added = entry.models.filter(m => !baseline.models.includes(m)).sort();
|
|
340
|
+
const removed = baseline.models.filter(m => !entry.models.includes(m)).sort();
|
|
341
|
+
const before = new Map(baseline.entitlements?.map(e => [e.model, entitlementFeatures(e)]));
|
|
342
|
+
const capabilities = (entry.entitlements ?? []).filter(e => before.has(e.model) && JSON.stringify(before.get(e.model)) !== JSON.stringify(entitlementFeatures(e))).map(e => e.model).sort();
|
|
343
|
+
const changes = added.length || removed.length || capabilities.length ? { observedAt: current.checkedAt, added, removed, capabilities } : recent;
|
|
344
|
+
return { ...entry, lastSuccessful, changes };
|
|
345
|
+
}) };
|
|
346
|
+
}
|
|
347
|
+
function inventoryHighlights(inventory) {
|
|
348
|
+
const lines = [];
|
|
349
|
+
for (const entry of inventory.entries) {
|
|
350
|
+
const change = entry.changes;
|
|
351
|
+
if (!change)
|
|
352
|
+
continue;
|
|
353
|
+
const stamp = new Date(change.observedAt).toISOString();
|
|
354
|
+
if (change.added.length)
|
|
355
|
+
lines.push(paint(` NEW on ${entry.label} (${stamp}): ${change.added.join(", ")}`, "success"));
|
|
356
|
+
if (change.removed.length)
|
|
357
|
+
lines.push(paint(` No longer advertised on ${entry.label} (${stamp}): ${change.removed.join(", ")}`, "warning"));
|
|
358
|
+
if (change.capabilities.length)
|
|
359
|
+
lines.push(paint(` CHANGED capabilities on ${entry.label} (${stamp}): ${change.capabilities.join(", ")}`, "accent"));
|
|
360
|
+
}
|
|
361
|
+
for (const kind of ["oauth", "api", "zdr"]) {
|
|
362
|
+
const enabled = inventory.entries.filter(e => e.kind === kind && e.enabled);
|
|
363
|
+
const successful = enabled.filter(e => !e.error);
|
|
364
|
+
if (enabled.length < 2)
|
|
365
|
+
continue;
|
|
366
|
+
const incomplete = enabled.length !== successful.length;
|
|
367
|
+
if (incomplete)
|
|
368
|
+
lines.push(paint(` ${kind.toUpperCase()} coverage incomplete; failed scopes have unknown access.`, "warning"));
|
|
369
|
+
const models = [...new Set(successful.flatMap(e => e.models))].sort();
|
|
370
|
+
for (const model of models) {
|
|
371
|
+
const providers = successful.filter(e => e.models.includes(model));
|
|
372
|
+
if (providers.length < enabled.length)
|
|
373
|
+
lines.push(` ${!incomplete && providers.length === 1 ? "Only on" : "Observed on"} ${providers.map(e => e.label).join(", ")}: ${model}`);
|
|
374
|
+
const entitlements = providers.map(e => ({ label: e.label, features: e.entitlements?.find(m => m.model === model) }));
|
|
375
|
+
const features = [...new Set(entitlements.flatMap(e => e.features ? entitlementFeatures(e.features) : []))].sort();
|
|
376
|
+
for (const feature of features) {
|
|
377
|
+
const advertising = entitlements.filter(e => e.features && entitlementFeatures(e.features).includes(feature));
|
|
378
|
+
if (advertising.length < providers.length)
|
|
379
|
+
lines.push(` ${model} ${feature}: ${advertising.map(e => e.label).join(", ")} (not advertised by other scopes)`);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
if (!lines.length)
|
|
384
|
+
return [];
|
|
385
|
+
const maximum = 40;
|
|
386
|
+
return [paint("Highlights (catalog observations; recent changes retained for 24h):", "heading"), ...lines.slice(0, maximum), ...(lines.length > maximum ? [` ${lines.length - maximum} more differences; see per-scope details below.`] : [])];
|
|
387
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type AccountStorageV3 } from "../storage.js";
|
|
2
|
+
export interface NativeAccountSnapshot {
|
|
3
|
+
storage: AccountStorageV3 | null;
|
|
4
|
+
verified: boolean;
|
|
5
|
+
transientFailure?: boolean;
|
|
6
|
+
/** Independent client auth may retain routing for at most 30 seconds. */
|
|
7
|
+
routingAvailable?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/** Cache parsed primary storage only; backup recovery must never resurrect revoked credentials. */
|
|
10
|
+
export declare function createNativeAccountStorageReader(read?: () => Promise<AccountStorageV3 | null>, path?: string): () => Promise<NativeAccountSnapshot>;
|
|
11
|
+
//# sourceMappingURL=native-account-storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native-account-storage.d.ts","sourceRoot":"","sources":["../../../lib/runtime/native-account-storage.ts"],"names":[],"mappings":"AACA,OAAO,EAA2C,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAI/F,MAAM,WAAW,qBAAqB;IAClC,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACjC,QAAQ,EAAE,OAAO,CAAC;IAClB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,yEAAyE;IACzE,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC9B;AACD,mGAAmG;AACnG,wBAAgB,gCAAgC,CAAC,IAAI,CAAC,EAAE,MAAM,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,EAAE,IAAI,SAAmB,GAAG,MAAM,OAAO,CAAC,qBAAqB,CAAC,CA2C7J"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { stat } from "node:fs/promises";
|
|
2
|
+
import { getStoragePath, normalizeAccountStorage } from "../storage.js";
|
|
3
|
+
import { loadAccountsFromPath } from "../storage/storage-parser.js";
|
|
4
|
+
import { applyPendingAuth } from "../storage/pending-auth.js";
|
|
5
|
+
import { isRecord } from "../utils.js";
|
|
6
|
+
/** Cache parsed primary storage only; backup recovery must never resurrect revoked credentials. */
|
|
7
|
+
export function createNativeAccountStorageReader(read, path = getStoragePath()) {
|
|
8
|
+
let cached = null;
|
|
9
|
+
let signature;
|
|
10
|
+
let lastSuccess = 0;
|
|
11
|
+
let pending;
|
|
12
|
+
return () => {
|
|
13
|
+
if (pending)
|
|
14
|
+
return pending;
|
|
15
|
+
pending = (async () => {
|
|
16
|
+
try {
|
|
17
|
+
let nextSignature;
|
|
18
|
+
if (!read) {
|
|
19
|
+
const info = await stat(path);
|
|
20
|
+
nextSignature = `${info.dev}:${info.ino}:${info.size}:${info.mtimeMs}:${info.ctimeMs}`;
|
|
21
|
+
if (cached && nextSignature === signature && Date.now() - Math.max(info.mtimeMs, info.ctimeMs) > 2000) {
|
|
22
|
+
lastSuccess = Date.now();
|
|
23
|
+
// The pending journal can change without touching the primary.
|
|
24
|
+
return { storage: await applyPendingAuth(path, structuredClone(cached)), verified: true };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const storage = read ? await read() : (await loadAccountsFromPath(path, { normalizeAccountStorage, isRecord })).normalized;
|
|
28
|
+
cached = structuredClone(storage);
|
|
29
|
+
signature = nextSignature;
|
|
30
|
+
lastSuccess = Date.now();
|
|
31
|
+
// A rotated token journaled after a locked write is newer than the
|
|
32
|
+
// spent one still in the primary; syncing the raw file would revert it.
|
|
33
|
+
return { storage: await applyPendingAuth(path, storage), verified: true };
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
const code = error.code;
|
|
37
|
+
if (["EBUSY", "EPERM", "EACCES"].includes(code ?? "")) {
|
|
38
|
+
// Availability grace is bounded, never refreshes its own age, and cannot
|
|
39
|
+
// authenticate a managed bearer. Retry the changed file on the next request.
|
|
40
|
+
signature = undefined;
|
|
41
|
+
return { storage: Date.now() - lastSuccess < 2000 ? await applyPendingAuth(path, structuredClone(cached)) : null, verified: false, transientFailure: true, routingAvailable: cached !== null && Date.now() - lastSuccess < 30000 };
|
|
42
|
+
}
|
|
43
|
+
cached = null;
|
|
44
|
+
signature = undefined;
|
|
45
|
+
return { storage: null, verified: false };
|
|
46
|
+
}
|
|
47
|
+
})().finally(() => { pending = undefined; });
|
|
48
|
+
return pending;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { AccountManager } from "../accounts.js";
|
|
2
|
+
import type { AccountStorageV3 } from "../storage.js";
|
|
3
|
+
type Identity = {
|
|
4
|
+
recordId?: string;
|
|
5
|
+
accountId?: string;
|
|
6
|
+
email?: string;
|
|
7
|
+
refreshToken?: string;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* The manager stores sanitized (lower-cased) emails while storage keeps the raw
|
|
11
|
+
* value, so match a stored record ID first and otherwise the canonical identity.
|
|
12
|
+
* Two rows with neither an accountId nor an email share no identity at all
|
|
13
|
+
* (undefined === undefined), so those match only by their refresh token.
|
|
14
|
+
*/
|
|
15
|
+
export declare function isSameNativeAccount(account: Identity, disk: Identity | undefined): boolean;
|
|
16
|
+
/** Adopt credentials from a re-login without resetting independent quota state. */
|
|
17
|
+
export declare function syncNativeAccountCredentials(manager: AccountManager, storage: AccountStorageV3): boolean;
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=native-account-sync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native-account-sync.d.ts","sourceRoot":"","sources":["../../../lib/runtime/native-account-sync.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEtD,KAAK,QAAQ,GAAG;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AACjG;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,GAAG,SAAS,GAAG,OAAO,CAY1F;AAED,mFAAmF;AACnF,wBAAgB,4BAA4B,CAC3C,OAAO,EAAE,cAAc,EACvB,OAAO,EAAE,gBAAgB,GACvB,OAAO,CAyCT"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { sanitizeEmail } from "../auth/token-utils.js";
|
|
2
|
+
/**
|
|
3
|
+
* The manager stores sanitized (lower-cased) emails while storage keeps the raw
|
|
4
|
+
* value, so match a stored record ID first and otherwise the canonical identity.
|
|
5
|
+
* Two rows with neither an accountId nor an email share no identity at all
|
|
6
|
+
* (undefined === undefined), so those match only by their refresh token.
|
|
7
|
+
*/
|
|
8
|
+
export function isSameNativeAccount(account, disk) {
|
|
9
|
+
if (!disk)
|
|
10
|
+
return false;
|
|
11
|
+
const left = account.recordId?.trim();
|
|
12
|
+
const right = disk.recordId?.trim();
|
|
13
|
+
if (left && right)
|
|
14
|
+
return left === right;
|
|
15
|
+
const accountId = account.accountId?.trim() || undefined;
|
|
16
|
+
const email = sanitizeEmail(account.email);
|
|
17
|
+
if (!accountId && !email) {
|
|
18
|
+
const token = account.refreshToken?.trim();
|
|
19
|
+
return !disk.accountId?.trim() && !sanitizeEmail(disk.email) && !!token && token === disk.refreshToken?.trim();
|
|
20
|
+
}
|
|
21
|
+
return accountId === (disk.accountId?.trim() || undefined) && email === sanitizeEmail(disk.email);
|
|
22
|
+
}
|
|
23
|
+
/** Adopt credentials from a re-login without resetting independent quota state. */
|
|
24
|
+
export function syncNativeAccountCredentials(manager, storage) {
|
|
25
|
+
let changed = manager.syncWorkspaceSelections(storage);
|
|
26
|
+
for (const snapshot of manager.getAccountsSnapshot()) {
|
|
27
|
+
const account = manager.getAccountByIndex(snapshot.index);
|
|
28
|
+
if (!account)
|
|
29
|
+
continue;
|
|
30
|
+
const disk = storage.accounts.find((a) => isSameNativeAccount(account, a));
|
|
31
|
+
if (!disk) {
|
|
32
|
+
account.enabled = false;
|
|
33
|
+
changed = true;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if ((account.enabled !== false) !== (disk.enabled !== false)) {
|
|
37
|
+
account.enabled = disk.enabled;
|
|
38
|
+
changed = true;
|
|
39
|
+
}
|
|
40
|
+
if (disk.authInvalidatedAt &&
|
|
41
|
+
disk.authInvalidatedAt !== account.authInvalidatedAt) {
|
|
42
|
+
account.authInvalidatedAt = disk.authInvalidatedAt;
|
|
43
|
+
account.authInvalidationErrorCode = disk.authInvalidationErrorCode;
|
|
44
|
+
changed = true;
|
|
45
|
+
}
|
|
46
|
+
if (disk.accessToken === account.access &&
|
|
47
|
+
disk.refreshToken === account.refreshToken &&
|
|
48
|
+
disk.expiresAt === account.expires)
|
|
49
|
+
continue;
|
|
50
|
+
account.access = disk.accessToken;
|
|
51
|
+
account.refreshToken = disk.refreshToken;
|
|
52
|
+
account.expires = disk.expiresAt;
|
|
53
|
+
if (disk.accessToken && disk.refreshToken && !disk.authInvalidatedAt) {
|
|
54
|
+
delete account.authInvalidatedAt;
|
|
55
|
+
delete account.authInvalidationErrorCode;
|
|
56
|
+
if (account.cooldownReason === "auth-failure")
|
|
57
|
+
manager.clearAccountCooldown(account);
|
|
58
|
+
}
|
|
59
|
+
changed = true;
|
|
60
|
+
}
|
|
61
|
+
return changed;
|
|
62
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native-binding-lock.d.ts","sourceRoot":"","sources":["../../../lib/runtime/native-binding-lock.ts"],"names":[],"mappings":"AACA,6FAA6F;AAC7F,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAEjG"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { withFileTransactionLock } from "../storage/file-lock.js";
|
|
2
|
+
/** Shared across CLI processes: protect the native provider check and desktop auth write. */
|
|
3
|
+
export function withNativeBindingLock(configPath, action) {
|
|
4
|
+
return withFileTransactionLock(`${configPath}.native-bind`, action);
|
|
5
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native-client-auth.d.ts","sourceRoot":"","sources":["../../../lib/runtime/native-client-auth.ts"],"names":[],"mappings":"AAkBA,uFAAuF;AACvF,wBAAsB,mBAAmB,CACxC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,MAAM,OAAO,CAAC,MAAM,CAAmB,GAC3C,OAAO,CAAC,OAAO,CAAC,CAqBlB"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { createHash, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { open } from "node:fs/promises";
|
|
3
|
+
import { decodeJWT } from "../auth/auth.js";
|
|
4
|
+
import { getCodexCliAuthPath } from "../codex-cli/state.js";
|
|
5
|
+
import { isRecord } from "../utils.js";
|
|
6
|
+
async function readDesktopAuth() {
|
|
7
|
+
const file = await open(getCodexCliAuthPath(), "r");
|
|
8
|
+
try {
|
|
9
|
+
const buffer = Buffer.alloc(1024 * 1024 + 1);
|
|
10
|
+
const { bytesRead } = await file.read(buffer, 0, buffer.length, 0);
|
|
11
|
+
if (bytesRead > 1024 * 1024)
|
|
12
|
+
throw new Error("Oversized desktop auth file");
|
|
13
|
+
return buffer.subarray(0, bytesRead).toString("utf8");
|
|
14
|
+
}
|
|
15
|
+
finally {
|
|
16
|
+
await file.close();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** Trust the exact local desktop credential, never claims from the incoming bearer. */
|
|
20
|
+
export async function isNativeClientToken(bearer, now, read = readDesktopAuth) {
|
|
21
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
22
|
+
try {
|
|
23
|
+
const auth = JSON.parse(await read());
|
|
24
|
+
if (!isRecord(auth) || !isRecord(auth.tokens))
|
|
25
|
+
return false;
|
|
26
|
+
const token = auth.tokens.access_token;
|
|
27
|
+
if (typeof token !== "string" || !token)
|
|
28
|
+
return false;
|
|
29
|
+
const claims = decodeJWT(token);
|
|
30
|
+
if (typeof claims?.exp !== "number" || claims.exp * 1000 <= now)
|
|
31
|
+
return false;
|
|
32
|
+
return timingSafeEqual(createHash("sha256").update(bearer).digest(), createHash("sha256").update(token).digest());
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
const retryable = error instanceof SyntaxError || ["EBUSY", "EPERM", "EACCES"].includes(error.code ?? "");
|
|
36
|
+
if (!retryable || attempt === 2)
|
|
37
|
+
return false;
|
|
38
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { hasNativeProviderConfig } from "../runtime-constants.js";
|
|
2
|
+
export declare function rewriteNativeProviderConfig(content: string, baseUrl: string): string;
|
|
3
|
+
export declare function restoreNativeProviderConfig(content: string, original: string): string;
|
|
4
|
+
//# sourceMappingURL=native-provider-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native-provider-config.d.ts","sourceRoot":"","sources":["../../../lib/runtime/native-provider-config.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAqBlE,wBAAgB,2BAA2B,CAC1C,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GACb,MAAM,CAqBR;AACD,wBAAgB,2BAA2B,CAC1C,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACd,MAAM,CAMR"}
|