@slkiser/opencode-quota 4.8.2 → 4.9.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 +17 -18
- package/dist/bin/opencode-quota.d.ts.map +1 -1
- package/dist/bin/opencode-quota.js +5 -3
- package/dist/bin/opencode-quota.js.map +1 -1
- package/dist/lib/accounting-format.d.ts +54 -0
- package/dist/lib/accounting-format.d.ts.map +1 -0
- package/dist/lib/accounting-format.js +271 -0
- package/dist/lib/accounting-format.js.map +1 -0
- package/dist/lib/anthropic.d.ts +2 -0
- package/dist/lib/anthropic.d.ts.map +1 -1
- package/dist/lib/anthropic.js +79 -19
- package/dist/lib/anthropic.js.map +1 -1
- package/dist/lib/cli-show.d.ts.map +1 -1
- package/dist/lib/cli-show.js +1 -0
- package/dist/lib/cli-show.js.map +1 -1
- package/dist/lib/config.d.ts +10 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +35 -3
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/copilot.d.ts +2 -0
- package/dist/lib/copilot.d.ts.map +1 -1
- package/dist/lib/copilot.js +38 -0
- package/dist/lib/copilot.js.map +1 -1
- package/dist/lib/deepseek.d.ts +12 -12
- package/dist/lib/deepseek.d.ts.map +1 -1
- package/dist/lib/deepseek.js +33 -29
- package/dist/lib/deepseek.js.map +1 -1
- package/dist/lib/display-sanitize.d.ts.map +1 -1
- package/dist/lib/display-sanitize.js +75 -22
- package/dist/lib/display-sanitize.js.map +1 -1
- package/dist/lib/entries.d.ts +105 -23
- package/dist/lib/entries.d.ts.map +1 -1
- package/dist/lib/entries.js +37 -1
- package/dist/lib/entries.js.map +1 -1
- package/dist/lib/format.d.ts +1 -0
- package/dist/lib/format.d.ts.map +1 -1
- package/dist/lib/format.js +84 -19
- package/dist/lib/format.js.map +1 -1
- package/dist/lib/google-agy.d.ts +2 -0
- package/dist/lib/google-agy.d.ts.map +1 -1
- package/dist/lib/google-agy.js +29 -14
- package/dist/lib/google-agy.js.map +1 -1
- package/dist/lib/google-gemini-cli.d.ts +2 -0
- package/dist/lib/google-gemini-cli.d.ts.map +1 -1
- package/dist/lib/google-gemini-cli.js +29 -3
- package/dist/lib/google-gemini-cli.js.map +1 -1
- package/dist/lib/google-token-cache.d.ts +5 -8
- package/dist/lib/google-token-cache.d.ts.map +1 -1
- package/dist/lib/google-token-cache.js +30 -118
- package/dist/lib/google-token-cache.js.map +1 -1
- package/dist/lib/google.d.ts +2 -0
- package/dist/lib/google.d.ts.map +1 -1
- package/dist/lib/google.js +31 -6
- package/dist/lib/google.js.map +1 -1
- package/dist/lib/grouped-entry-normalization.d.ts.map +1 -1
- package/dist/lib/grouped-entry-normalization.js +53 -20
- package/dist/lib/grouped-entry-normalization.js.map +1 -1
- package/dist/lib/minimax-auth.d.ts +1 -1
- package/dist/lib/minimax-auth.js +1 -1
- package/dist/lib/nanogpt.d.ts +5 -5
- package/dist/lib/nanogpt.d.ts.map +1 -1
- package/dist/lib/nanogpt.js +59 -23
- package/dist/lib/nanogpt.js.map +1 -1
- package/dist/lib/openai.d.ts +4 -0
- package/dist/lib/openai.d.ts.map +1 -1
- package/dist/lib/openai.js +22 -3
- package/dist/lib/openai.js.map +1 -1
- package/dist/lib/opencode-go-auth.js +1 -1
- package/dist/lib/opencode-go-auth.js.map +1 -1
- package/dist/lib/opencode-go.d.ts.map +1 -1
- package/dist/lib/opencode-go.js +6 -1
- package/dist/lib/opencode-go.js.map +1 -1
- package/dist/lib/opencode-zen-config.d.ts +0 -1
- package/dist/lib/opencode-zen-config.d.ts.map +1 -1
- package/dist/lib/opencode-zen-config.js +0 -22
- package/dist/lib/opencode-zen-config.js.map +1 -1
- package/dist/lib/opencode-zen.d.ts +3 -0
- package/dist/lib/opencode-zen.d.ts.map +1 -1
- package/dist/lib/opencode-zen.js +18 -1
- package/dist/lib/opencode-zen.js.map +1 -1
- package/dist/lib/openrouter.d.ts +2 -0
- package/dist/lib/openrouter.d.ts.map +1 -1
- package/dist/lib/openrouter.js +10 -0
- package/dist/lib/openrouter.js.map +1 -1
- package/dist/lib/provider-metadata.d.ts +2 -307
- package/dist/lib/provider-metadata.d.ts.map +1 -1
- package/dist/lib/provider-metadata.js +14 -316
- package/dist/lib/provider-metadata.js.map +1 -1
- package/dist/lib/provider-registration.d.ts +325 -0
- package/dist/lib/provider-registration.d.ts.map +1 -0
- package/dist/lib/provider-registration.js +338 -0
- package/dist/lib/provider-registration.js.map +1 -0
- package/dist/lib/quota-accounting-projection.d.ts +7 -0
- package/dist/lib/quota-accounting-projection.d.ts.map +1 -0
- package/dist/lib/quota-accounting-projection.js +239 -0
- package/dist/lib/quota-accounting-projection.js.map +1 -0
- package/dist/lib/quota-command-format.d.ts +3 -2
- package/dist/lib/quota-command-format.d.ts.map +1 -1
- package/dist/lib/quota-command-format.js +35 -9
- package/dist/lib/quota-command-format.js.map +1 -1
- package/dist/lib/quota-dialog-commands.d.ts.map +1 -1
- package/dist/lib/quota-dialog-commands.js +1 -0
- package/dist/lib/quota-dialog-commands.js.map +1 -1
- package/dist/lib/quota-export.d.ts +2 -1
- package/dist/lib/quota-export.d.ts.map +1 -1
- package/dist/lib/quota-export.js +36 -10
- package/dist/lib/quota-export.js.map +1 -1
- package/dist/lib/quota-render-data.d.ts +0 -1
- package/dist/lib/quota-render-data.d.ts.map +1 -1
- package/dist/lib/quota-render-data.js +9 -138
- package/dist/lib/quota-render-data.js.map +1 -1
- package/dist/lib/quota-reset-notifications.d.ts.map +1 -1
- package/dist/lib/quota-reset-notifications.js +3 -0
- package/dist/lib/quota-reset-notifications.js.map +1 -1
- package/dist/lib/quota-state-codec.d.ts +16 -0
- package/dist/lib/quota-state-codec.d.ts.map +1 -0
- package/dist/lib/quota-state-codec.js +409 -0
- package/dist/lib/quota-state-codec.js.map +1 -0
- package/dist/lib/quota-state.d.ts +2 -11
- package/dist/lib/quota-state.d.ts.map +1 -1
- package/dist/lib/quota-state.js +165 -305
- package/dist/lib/quota-state.js.map +1 -1
- package/dist/lib/quota-status.d.ts.map +1 -1
- package/dist/lib/quota-status.js +14 -7
- package/dist/lib/quota-status.js.map +1 -1
- package/dist/lib/quota-telemetry.d.ts.map +1 -1
- package/dist/lib/quota-telemetry.js +7 -3
- package/dist/lib/quota-telemetry.js.map +1 -1
- package/dist/lib/quota-toast-runtime.d.ts +60 -0
- package/dist/lib/quota-toast-runtime.d.ts.map +1 -0
- package/dist/lib/quota-toast-runtime.js +765 -0
- package/dist/lib/quota-toast-runtime.js.map +1 -0
- package/dist/lib/resolved-auth-identity.d.ts +36 -0
- package/dist/lib/resolved-auth-identity.d.ts.map +1 -0
- package/dist/lib/resolved-auth-identity.js +193 -0
- package/dist/lib/resolved-auth-identity.js.map +1 -0
- package/dist/lib/scoped-update-migration.d.ts +101 -0
- package/dist/lib/scoped-update-migration.d.ts.map +1 -0
- package/dist/lib/scoped-update-migration.js +348 -0
- package/dist/lib/scoped-update-migration.js.map +1 -0
- package/dist/lib/scoped-update.d.ts +8 -0
- package/dist/lib/scoped-update.d.ts.map +1 -1
- package/dist/lib/scoped-update.js +263 -50
- package/dist/lib/scoped-update.js.map +1 -1
- package/dist/lib/toast-format-grouped.d.ts +1 -0
- package/dist/lib/toast-format-grouped.d.ts.map +1 -1
- package/dist/lib/toast-format-grouped.js +111 -24
- package/dist/lib/toast-format-grouped.js.map +1 -1
- package/dist/lib/tui-compact-format.d.ts +1 -0
- package/dist/lib/tui-compact-format.d.ts.map +1 -1
- package/dist/lib/tui-compact-format.js +133 -22
- package/dist/lib/tui-compact-format.js.map +1 -1
- package/dist/lib/tui-panel-state.d.ts +2 -0
- package/dist/lib/tui-panel-state.d.ts.map +1 -1
- package/dist/lib/tui-panel-state.js.map +1 -1
- package/dist/lib/tui-runtime.d.ts.map +1 -1
- package/dist/lib/tui-runtime.js +84 -10
- package/dist/lib/tui-runtime.js.map +1 -1
- package/dist/lib/tui-sidebar-format.d.ts +1 -1
- package/dist/lib/tui-sidebar-format.d.ts.map +1 -1
- package/dist/lib/tui-sidebar-format.js +1 -0
- package/dist/lib/tui-sidebar-format.js.map +1 -1
- package/dist/lib/types.d.ts +7 -1
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/types.js +1 -0
- package/dist/lib/types.js.map +1 -1
- package/dist/lib/xai.d.ts +2 -0
- package/dist/lib/xai.d.ts.map +1 -1
- package/dist/lib/xai.js +10 -0
- package/dist/lib/xai.js.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +27 -720
- package/dist/plugin.js.map +1 -1
- package/dist/providers/accounting-decimal.d.ts +2 -0
- package/dist/providers/accounting-decimal.d.ts.map +1 -0
- package/dist/providers/accounting-decimal.js +23 -0
- package/dist/providers/accounting-decimal.js.map +1 -0
- package/dist/providers/cache-policies.d.ts +64 -0
- package/dist/providers/cache-policies.d.ts.map +1 -0
- package/dist/providers/cache-policies.js +197 -0
- package/dist/providers/cache-policies.js.map +1 -0
- package/dist/providers/cursor.d.ts.map +1 -1
- package/dist/providers/cursor.js +73 -58
- package/dist/providers/cursor.js.map +1 -1
- package/dist/providers/deepseek.d.ts +2 -2
- package/dist/providers/deepseek.d.ts.map +1 -1
- package/dist/providers/deepseek.js +64 -33
- package/dist/providers/deepseek.js.map +1 -1
- package/dist/providers/google-agy.js +2 -2
- package/dist/providers/google-agy.js.map +1 -1
- package/dist/providers/kilo.d.ts.map +1 -1
- package/dist/providers/kilo.js +56 -29
- package/dist/providers/kilo.js.map +1 -1
- package/dist/providers/mimo.d.ts.map +1 -1
- package/dist/providers/mimo.js +37 -15
- package/dist/providers/mimo.js.map +1 -1
- package/dist/providers/minimax-coding-plan.d.ts +8 -7
- package/dist/providers/minimax-coding-plan.d.ts.map +1 -1
- package/dist/providers/minimax-coding-plan.js +97 -83
- package/dist/providers/minimax-coding-plan.js.map +1 -1
- package/dist/providers/nanogpt.d.ts.map +1 -1
- package/dist/providers/nanogpt.js +72 -51
- package/dist/providers/nanogpt.js.map +1 -1
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +0 -3
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/opencode-go.d.ts.map +1 -1
- package/dist/providers/opencode-go.js +3 -4
- package/dist/providers/opencode-go.js.map +1 -1
- package/dist/providers/opencode-zen.d.ts.map +1 -1
- package/dist/providers/opencode-zen.js +78 -17
- package/dist/providers/opencode-zen.js.map +1 -1
- package/dist/providers/quota-providers.d.ts.map +1 -1
- package/dist/providers/quota-providers.js +31 -23
- package/dist/providers/quota-providers.js.map +1 -1
- package/dist/providers/registry.d.ts +3 -2
- package/dist/providers/registry.d.ts.map +1 -1
- package/dist/providers/registry.js +41 -31
- package/dist/providers/registry.js.map +1 -1
- package/dist/providers/result-helpers.d.ts +10 -5
- package/dist/providers/result-helpers.d.ts.map +1 -1
- package/dist/providers/result-helpers.js +3 -3
- package/dist/providers/result-helpers.js.map +1 -1
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +11 -3
- package/dist/tui.tsx +16 -7
- package/package.json +1 -1
package/dist/lib/quota-state.js
CHANGED
|
@@ -1,47 +1,21 @@
|
|
|
1
|
-
import { createHash } from "crypto";
|
|
2
|
-
import { readdir, readFile, rm, stat } from "fs/promises";
|
|
3
|
-
import { join } from "path";
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readdir, readFile, rm, stat } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
4
|
import { writeJsonAtomic } from "./atomic-json.js";
|
|
5
5
|
import { getOpencodeRuntimeDirs } from "./opencode-runtime-paths.js";
|
|
6
6
|
import { getQuotaProviderDisplayLabel, isLiveLocalUsageProviderId } from "./provider-metadata.js";
|
|
7
7
|
import { QUOTA_PROVIDERS_AGGREGATE_ID, selectEligibleQuotaProviderDefinitions, } from "./quota-providers.js";
|
|
8
|
+
import { cloneQuotaProviderResult, decodePersistedQuotaProviderCacheEntry, encodePersistedQuotaProviderCacheEntry, normalizeQuotaProviderResult, } from "./quota-state-codec.js";
|
|
8
9
|
import { updateQuotaTelemetrySnapshot } from "./quota-telemetry.js";
|
|
9
10
|
import { getPackageVersion } from "./version.js";
|
|
10
|
-
const QUOTA_PROVIDER_CACHE_VERSION = 2;
|
|
11
11
|
const QUOTA_PROVIDER_CACHE_PACKAGE_VERSION_FALLBACK = "unknown";
|
|
12
12
|
const QUOTA_PROVIDER_CACHE_DIRNAME = "quota-provider-state";
|
|
13
13
|
const QUOTA_PROVIDER_CACHE_RETENTION_MS = 24 * 60 * 60 * 1000;
|
|
14
14
|
const QUOTA_PROVIDER_CACHE_PRUNE_INTERVAL_MS = 60 * 60 * 1000;
|
|
15
15
|
const inMemoryCache = new Map();
|
|
16
16
|
const inFlightByKey = new Map();
|
|
17
|
+
let processLocalLatestByRuntime = new WeakMap();
|
|
17
18
|
let lastPruneAtMs = 0;
|
|
18
|
-
export function cloneQuotaProviderResult(result) {
|
|
19
|
-
return {
|
|
20
|
-
attempted: result.attempted,
|
|
21
|
-
entries: result.entries.map((entry) => ({
|
|
22
|
-
...entry,
|
|
23
|
-
accounting: { ...entry.accounting },
|
|
24
|
-
})),
|
|
25
|
-
errors: result.errors.map((error) => ({ ...error })),
|
|
26
|
-
...(result.diagnostics
|
|
27
|
-
? {
|
|
28
|
-
diagnostics: result.diagnostics.map((diagnostic) => ({
|
|
29
|
-
...diagnostic,
|
|
30
|
-
modelIds: diagnostic.modelIds ? [...diagnostic.modelIds] : null,
|
|
31
|
-
checkedPaths: [...diagnostic.checkedPaths],
|
|
32
|
-
authPaths: [...diagnostic.authPaths],
|
|
33
|
-
})),
|
|
34
|
-
}
|
|
35
|
-
: {}),
|
|
36
|
-
...(result.statusDetails
|
|
37
|
-
? { statusDetails: result.statusDetails.map((detail) => ({ ...detail })) }
|
|
38
|
-
: {}),
|
|
39
|
-
...(result.rawDetails
|
|
40
|
-
? { rawDetails: result.rawDetails.map((detail) => ({ ...detail })) }
|
|
41
|
-
: {}),
|
|
42
|
-
...(result.presentation ? { presentation: { ...result.presentation } } : {}),
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
19
|
export function buildQuotaProviderStateCacheKey(providerId, ctx, options = {}) {
|
|
46
20
|
const googleModels = ctx.config.googleModels.join(",");
|
|
47
21
|
const cursorPlan = ctx.config.cursorPlan;
|
|
@@ -60,246 +34,39 @@ export function buildQuotaProviderStateCacheKey(providerId, ctx, options = {}) {
|
|
|
60
34
|
const quotaProvidersIdentity = relevantQuotaProviders.length > 0
|
|
61
35
|
? `|quotaProviders=${JSON.stringify(["quota-providers-cache-v1", relevantQuotaProviders])}`
|
|
62
36
|
: "";
|
|
37
|
+
const resolvedAuthIdentity = options.resolvedAuthIdentity
|
|
38
|
+
? `|resolvedAuthIdentity=${options.resolvedAuthIdentity}`
|
|
39
|
+
: "";
|
|
63
40
|
const runtimeEligibleIdentity = isAggregateCache
|
|
64
41
|
? `|runtimeEligibleQuotaProviders=${JSON.stringify([
|
|
65
42
|
"quota-providers-runtime-eligible-v1",
|
|
66
43
|
options.runtimeEligibleQuotaProviders ?? [],
|
|
67
44
|
])}`
|
|
68
45
|
: "";
|
|
69
|
-
return `${providerId}${quotaProvidersIdentity}${runtimeEligibleIdentity}|anthropicBinaryPath=${anthropicBinaryPath}|googleModels=${googleModels}|cursorPlan=${cursorPlan}|cursorIncludedApiUsd=${cursorIncludedApiUsd}|cursorBillingCycleStartDay=${cursorBillingCycleStartDay}|opencodeGoWindows=${opencodeGoWindows}|onlyCurrentModel=${onlyCurrentModel}|currentModel=${currentModel}|currentProviderID=${currentProviderID}`;
|
|
46
|
+
return `${providerId}${quotaProvidersIdentity}${runtimeEligibleIdentity}|anthropicBinaryPath=${anthropicBinaryPath}|googleModels=${googleModels}|cursorPlan=${cursorPlan}|cursorIncludedApiUsd=${cursorIncludedApiUsd}|cursorBillingCycleStartDay=${cursorBillingCycleStartDay}|opencodeGoWindows=${opencodeGoWindows}|onlyCurrentModel=${onlyCurrentModel}|currentModel=${currentModel}|currentProviderID=${currentProviderID}${resolvedAuthIdentity}`;
|
|
70
47
|
}
|
|
71
48
|
function getQuotaProviderCacheDir() {
|
|
72
49
|
return join(getOpencodeRuntimeDirs().cacheDir, QUOTA_PROVIDER_CACHE_DIRNAME);
|
|
73
50
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return join(getQuotaProviderCacheDir(), `${providerId}-${digest}.json`);
|
|
77
|
-
}
|
|
78
|
-
function hasOnlyKeys(value, allowed) {
|
|
79
|
-
const allowedKeys = new Set(allowed);
|
|
80
|
-
return Object.keys(value).every((key) => allowedKeys.has(key));
|
|
81
|
-
}
|
|
82
|
-
const ISO_TIMESTAMP_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
83
|
-
function isOptionalIsoTimestamp(value) {
|
|
84
|
-
return (value === undefined ||
|
|
85
|
-
(typeof value === "string" &&
|
|
86
|
-
ISO_TIMESTAMP_RE.test(value) &&
|
|
87
|
-
Number.isFinite(Date.parse(value))));
|
|
51
|
+
function getQuotaProviderStateCacheLocator(key) {
|
|
52
|
+
return createHash("sha256").update(key).digest("hex");
|
|
88
53
|
}
|
|
89
|
-
function
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
"ownership",
|
|
97
|
-
"authority",
|
|
98
|
-
"sourceId",
|
|
99
|
-
"observedAtIso",
|
|
100
|
-
]) &&
|
|
101
|
-
["quota", "rate_limit", "usage", "spend", "budget", "balance", "status"].includes(String(accounting.resultType)) &&
|
|
102
|
-
[
|
|
103
|
-
"remote_api",
|
|
104
|
-
"dashboard_scrape",
|
|
105
|
-
"local_cli",
|
|
106
|
-
"local_runtime_accounting",
|
|
107
|
-
"local_estimation",
|
|
108
|
-
].includes(String(accounting.acquisitionMethod)) &&
|
|
109
|
-
["maintained", "user_configured"].includes(String(accounting.ownership)) &&
|
|
110
|
-
["provider_reported", "locally_derived"].includes(String(accounting.authority)) &&
|
|
111
|
-
(accounting.sourceId === undefined || typeof accounting.sourceId === "string") &&
|
|
112
|
-
isOptionalIsoTimestamp(accounting.observedAtIso));
|
|
113
|
-
}
|
|
114
|
-
function isQuotaToastEntry(value) {
|
|
115
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
116
|
-
return false;
|
|
117
|
-
const entry = value;
|
|
118
|
-
if (!hasOnlyKeys(entry, [
|
|
119
|
-
"accounting",
|
|
120
|
-
"kind",
|
|
121
|
-
"name",
|
|
122
|
-
"percentRemaining",
|
|
123
|
-
"value",
|
|
124
|
-
"resetTimeIso",
|
|
125
|
-
"group",
|
|
126
|
-
"label",
|
|
127
|
-
"metricLabel",
|
|
128
|
-
"right",
|
|
129
|
-
"sortPriority",
|
|
130
|
-
]) ||
|
|
131
|
-
!isAccountingMetadata(entry.accounting) ||
|
|
132
|
-
typeof entry.name !== "string" ||
|
|
133
|
-
!isOptionalIsoTimestamp(entry.resetTimeIso) ||
|
|
134
|
-
(entry.sortPriority !== undefined &&
|
|
135
|
-
(typeof entry.sortPriority !== "number" || !Number.isFinite(entry.sortPriority))) ||
|
|
136
|
-
!["group", "label", "metricLabel", "right"].every((key) => entry[key] === undefined || typeof entry[key] === "string")) {
|
|
137
|
-
return false;
|
|
138
|
-
}
|
|
139
|
-
if (entry.kind === "value") {
|
|
140
|
-
return typeof entry.value === "string" && entry.percentRemaining === undefined;
|
|
54
|
+
function getQuotaProviderCacheFileStem(providerId) {
|
|
55
|
+
const windowsReserved = /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\..*)?$/iu;
|
|
56
|
+
if (providerId !== "." &&
|
|
57
|
+
providerId !== ".." &&
|
|
58
|
+
/^[A-Za-z0-9._-]+$/u.test(providerId) &&
|
|
59
|
+
!windowsReserved.test(providerId)) {
|
|
60
|
+
return providerId;
|
|
141
61
|
}
|
|
142
|
-
return ((
|
|
143
|
-
typeof entry.percentRemaining === "number" &&
|
|
144
|
-
Number.isFinite(entry.percentRemaining) &&
|
|
145
|
-
entry.value === undefined);
|
|
146
|
-
}
|
|
147
|
-
function isQuotaToastError(value) {
|
|
148
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
149
|
-
return false;
|
|
150
|
-
const error = value;
|
|
151
|
-
return (hasOnlyKeys(error, ["label", "message", "kind"]) &&
|
|
152
|
-
typeof error.label === "string" &&
|
|
153
|
-
typeof error.message === "string" &&
|
|
154
|
-
(error.kind === undefined || error.kind === "intentional-filter"));
|
|
155
|
-
}
|
|
156
|
-
function isQuotaProviderDiagnostic(value) {
|
|
157
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
158
|
-
return false;
|
|
159
|
-
const diagnostic = value;
|
|
160
|
-
return (hasOnlyKeys(diagnostic, [
|
|
161
|
-
"sourceId",
|
|
162
|
-
"providerId",
|
|
163
|
-
"mode",
|
|
164
|
-
"format",
|
|
165
|
-
"modelIds",
|
|
166
|
-
"apiKeyEnv",
|
|
167
|
-
"selected",
|
|
168
|
-
"attempted",
|
|
169
|
-
"credentialSource",
|
|
170
|
-
"outcome",
|
|
171
|
-
"httpStatus",
|
|
172
|
-
"entryCount",
|
|
173
|
-
"checkedPaths",
|
|
174
|
-
"authPaths",
|
|
175
|
-
"statePath",
|
|
176
|
-
"stateHealth",
|
|
177
|
-
"stateVersion",
|
|
178
|
-
"stateLastUpdatedAt",
|
|
179
|
-
]) &&
|
|
180
|
-
typeof diagnostic.sourceId === "string" &&
|
|
181
|
-
typeof diagnostic.providerId === "string" &&
|
|
182
|
-
["remote-api", "local-estimate"].includes(String(diagnostic.mode)) &&
|
|
183
|
-
(diagnostic.format === undefined ||
|
|
184
|
-
["quota-v1", "openrouter-key-v1", "json-v1"].includes(String(diagnostic.format))) &&
|
|
185
|
-
(diagnostic.mode === "remote-api"
|
|
186
|
-
? diagnostic.format !== undefined
|
|
187
|
-
: diagnostic.format === undefined) &&
|
|
188
|
-
(diagnostic.modelIds === null ||
|
|
189
|
-
(Array.isArray(diagnostic.modelIds) &&
|
|
190
|
-
diagnostic.modelIds.every((modelId) => typeof modelId === "string"))) &&
|
|
191
|
-
(diagnostic.apiKeyEnv === null || typeof diagnostic.apiKeyEnv === "string") &&
|
|
192
|
-
diagnostic.selected === true &&
|
|
193
|
-
typeof diagnostic.attempted === "boolean" &&
|
|
194
|
-
(diagnostic.credentialSource === null ||
|
|
195
|
-
["explicit_env", "global_opencode_json", "global_opencode_jsonc", "auth_json"].includes(String(diagnostic.credentialSource))) &&
|
|
196
|
-
[
|
|
197
|
-
"missing_credential",
|
|
198
|
-
"success",
|
|
199
|
-
"http_error",
|
|
200
|
-
"redirect_error",
|
|
201
|
-
"timeout",
|
|
202
|
-
"body_too_large",
|
|
203
|
-
"invalid_content_type",
|
|
204
|
-
"invalid_json",
|
|
205
|
-
"invalid_response",
|
|
206
|
-
"network_error",
|
|
207
|
-
"local_state_error",
|
|
208
|
-
].includes(String(diagnostic.outcome)) &&
|
|
209
|
-
(diagnostic.httpStatus === undefined ||
|
|
210
|
-
(typeof diagnostic.httpStatus === "number" &&
|
|
211
|
-
Number.isInteger(diagnostic.httpStatus) &&
|
|
212
|
-
diagnostic.httpStatus >= 100 &&
|
|
213
|
-
diagnostic.httpStatus <= 599)) &&
|
|
214
|
-
typeof diagnostic.entryCount === "number" &&
|
|
215
|
-
Number.isInteger(diagnostic.entryCount) &&
|
|
216
|
-
diagnostic.entryCount >= 0 &&
|
|
217
|
-
Array.isArray(diagnostic.checkedPaths) &&
|
|
218
|
-
diagnostic.checkedPaths.every((path) => typeof path === "string") &&
|
|
219
|
-
Array.isArray(diagnostic.authPaths) &&
|
|
220
|
-
diagnostic.authPaths.every((path) => typeof path === "string") &&
|
|
221
|
-
(diagnostic.statePath === undefined || typeof diagnostic.statePath === "string") &&
|
|
222
|
-
(diagnostic.stateHealth === undefined ||
|
|
223
|
-
["missing", "healthy", "malformed", "version_mismatch"].includes(String(diagnostic.stateHealth))) &&
|
|
224
|
-
(diagnostic.stateVersion === undefined ||
|
|
225
|
-
diagnostic.stateVersion === null ||
|
|
226
|
-
(typeof diagnostic.stateVersion === "number" &&
|
|
227
|
-
Number.isInteger(diagnostic.stateVersion) &&
|
|
228
|
-
diagnostic.stateVersion >= 0)) &&
|
|
229
|
-
(diagnostic.stateLastUpdatedAt === undefined ||
|
|
230
|
-
diagnostic.stateLastUpdatedAt === null ||
|
|
231
|
-
(typeof diagnostic.stateLastUpdatedAt === "number" &&
|
|
232
|
-
Number.isFinite(diagnostic.stateLastUpdatedAt))));
|
|
62
|
+
return `provider-${createHash("sha256").update(providerId).digest("hex")}`;
|
|
233
63
|
}
|
|
234
|
-
function
|
|
235
|
-
|
|
236
|
-
return false;
|
|
237
|
-
const detail = value;
|
|
238
|
-
return (hasOnlyKeys(detail, ["key", "value"]) &&
|
|
239
|
-
typeof detail.key === "string" &&
|
|
240
|
-
typeof detail.value === "string");
|
|
241
|
-
}
|
|
242
|
-
function isQuotaProviderPresentation(value) {
|
|
243
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
244
|
-
return false;
|
|
245
|
-
const presentation = value;
|
|
246
|
-
return (hasOnlyKeys(presentation, [
|
|
247
|
-
"singleWindowDisplayName",
|
|
248
|
-
"singleWindowShowRight",
|
|
249
|
-
"redundantQuotaFamily",
|
|
250
|
-
"classicStrategy",
|
|
251
|
-
]) &&
|
|
252
|
-
(presentation.singleWindowDisplayName === undefined ||
|
|
253
|
-
typeof presentation.singleWindowDisplayName === "string") &&
|
|
254
|
-
(presentation.singleWindowShowRight === undefined ||
|
|
255
|
-
typeof presentation.singleWindowShowRight === "boolean") &&
|
|
256
|
-
(presentation.redundantQuotaFamily === undefined ||
|
|
257
|
-
typeof presentation.redundantQuotaFamily === "string") &&
|
|
258
|
-
(presentation.classicStrategy === undefined || presentation.classicStrategy === "preserve"));
|
|
259
|
-
}
|
|
260
|
-
function isQuotaProviderResult(value) {
|
|
261
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
262
|
-
return false;
|
|
263
|
-
const result = value;
|
|
264
|
-
return (hasOnlyKeys(result, [
|
|
265
|
-
"attempted",
|
|
266
|
-
"entries",
|
|
267
|
-
"errors",
|
|
268
|
-
"diagnostics",
|
|
269
|
-
"statusDetails",
|
|
270
|
-
"rawDetails",
|
|
271
|
-
"presentation",
|
|
272
|
-
]) &&
|
|
273
|
-
typeof result.attempted === "boolean" &&
|
|
274
|
-
Array.isArray(result.entries) &&
|
|
275
|
-
result.entries.every(isQuotaToastEntry) &&
|
|
276
|
-
Array.isArray(result.errors) &&
|
|
277
|
-
result.errors.every(isQuotaToastError) &&
|
|
278
|
-
(result.diagnostics === undefined ||
|
|
279
|
-
(Array.isArray(result.diagnostics) && result.diagnostics.every(isQuotaProviderDiagnostic))) &&
|
|
280
|
-
(result.statusDetails === undefined ||
|
|
281
|
-
(Array.isArray(result.statusDetails) &&
|
|
282
|
-
result.statusDetails.every(isQuotaProviderStatusDetail))) &&
|
|
283
|
-
(result.rawDetails === undefined ||
|
|
284
|
-
(Array.isArray(result.rawDetails) && result.rawDetails.every(isQuotaProviderStatusDetail))) &&
|
|
285
|
-
(result.presentation === undefined || isQuotaProviderPresentation(result.presentation)));
|
|
64
|
+
export function getQuotaProviderStateCacheFilePath(providerId, key) {
|
|
65
|
+
return join(getQuotaProviderCacheDir(), `${getQuotaProviderCacheFileStem(providerId)}-${getQuotaProviderStateCacheLocator(key)}.json`);
|
|
286
66
|
}
|
|
287
67
|
async function getQuotaProviderCachePackageVersion() {
|
|
288
68
|
return (await getPackageVersion()) ?? QUOTA_PROVIDER_CACHE_PACKAGE_VERSION_FALLBACK;
|
|
289
69
|
}
|
|
290
|
-
function isPersistedQuotaProviderCacheEntry(value, key, providerId, packageVersion) {
|
|
291
|
-
if (!value || typeof value !== "object") {
|
|
292
|
-
return false;
|
|
293
|
-
}
|
|
294
|
-
const entry = value;
|
|
295
|
-
return (entry.version === QUOTA_PROVIDER_CACHE_VERSION &&
|
|
296
|
-
entry.packageVersion === packageVersion &&
|
|
297
|
-
entry.key === key &&
|
|
298
|
-
entry.providerId === providerId &&
|
|
299
|
-
typeof entry.timestamp === "number" &&
|
|
300
|
-
Number.isFinite(entry.timestamp) &&
|
|
301
|
-
isQuotaProviderResult(entry.result));
|
|
302
|
-
}
|
|
303
70
|
async function safeRm(path) {
|
|
304
71
|
try {
|
|
305
72
|
await rm(path, { force: true, recursive: true });
|
|
@@ -341,32 +108,31 @@ async function readPersistedQuotaProviderCacheEntry(params) {
|
|
|
341
108
|
return null;
|
|
342
109
|
}
|
|
343
110
|
const path = getQuotaProviderStateCacheFilePath(params.providerId, params.key);
|
|
111
|
+
const cacheLocator = getQuotaProviderStateCacheLocator(params.key);
|
|
344
112
|
try {
|
|
345
113
|
const raw = await readFile(path, "utf-8");
|
|
346
114
|
const parsed = JSON.parse(raw);
|
|
347
|
-
|
|
115
|
+
const decoded = decodePersistedQuotaProviderCacheEntry(parsed, {
|
|
116
|
+
key: cacheLocator,
|
|
117
|
+
providerId: params.providerId,
|
|
118
|
+
packageVersion: params.packageVersion,
|
|
119
|
+
});
|
|
120
|
+
if (!decoded) {
|
|
348
121
|
await safeRm(path);
|
|
349
122
|
return null;
|
|
350
123
|
}
|
|
351
|
-
if (!params.ignoreExpiry && params.now -
|
|
124
|
+
if (!params.ignoreExpiry && params.now - decoded.timestamp >= params.ttlMs) {
|
|
352
125
|
return null;
|
|
353
126
|
}
|
|
354
|
-
return
|
|
355
|
-
version: parsed.version,
|
|
356
|
-
packageVersion: parsed.packageVersion,
|
|
357
|
-
key: parsed.key,
|
|
358
|
-
providerId: parsed.providerId,
|
|
359
|
-
timestamp: parsed.timestamp,
|
|
360
|
-
result: cloneQuotaProviderResult(parsed.result),
|
|
361
|
-
};
|
|
127
|
+
return decoded;
|
|
362
128
|
}
|
|
363
129
|
catch {
|
|
364
130
|
return null;
|
|
365
131
|
}
|
|
366
132
|
}
|
|
367
|
-
async function writePersistedQuotaProviderCacheEntry(entry) {
|
|
133
|
+
async function writePersistedQuotaProviderCacheEntry(entry, logicalKey) {
|
|
368
134
|
try {
|
|
369
|
-
await writeJsonAtomic(getQuotaProviderStateCacheFilePath(entry.providerId,
|
|
135
|
+
await writeJsonAtomic(getQuotaProviderStateCacheFilePath(entry.providerId, logicalKey), entry, {
|
|
370
136
|
trailingNewline: true,
|
|
371
137
|
});
|
|
372
138
|
}
|
|
@@ -374,11 +140,11 @@ async function writePersistedQuotaProviderCacheEntry(entry) {
|
|
|
374
140
|
// persistence failures should not break quota fetches
|
|
375
141
|
}
|
|
376
142
|
}
|
|
377
|
-
async function fetchValidatedProviderResult(provider, ctx) {
|
|
378
|
-
const fetched = await provider.fetch(ctx);
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
143
|
+
async function fetchValidatedProviderResult(provider, ctx, cacheContext) {
|
|
144
|
+
const fetched = await provider.fetch(ctx, cacheContext);
|
|
145
|
+
const normalized = normalizeQuotaProviderResult(fetched);
|
|
146
|
+
if (normalized)
|
|
147
|
+
return normalized;
|
|
382
148
|
return {
|
|
383
149
|
attempted: true,
|
|
384
150
|
entries: [],
|
|
@@ -395,8 +161,7 @@ async function resolveRuntimeEligibleQuotaProviders(providerId, ctx) {
|
|
|
395
161
|
return undefined;
|
|
396
162
|
}
|
|
397
163
|
try {
|
|
398
|
-
const
|
|
399
|
-
const availableProviderIds = new Set((response.data?.providers ?? []).map((provider) => provider.id));
|
|
164
|
+
const availableProviderIds = await ctx.resolveRuntimeProviderIds();
|
|
400
165
|
return selectEligibleQuotaProviderDefinitions({
|
|
401
166
|
definitions: ctx.config.quotaProviders ?? [],
|
|
402
167
|
availableProviderIds,
|
|
@@ -409,16 +174,77 @@ async function resolveRuntimeEligibleQuotaProviders(providerId, ctx) {
|
|
|
409
174
|
return null;
|
|
410
175
|
}
|
|
411
176
|
}
|
|
177
|
+
async function resolveProviderCacheScope(provider, ctx, cacheContext) {
|
|
178
|
+
const policy = provider.cachePolicy;
|
|
179
|
+
if (!policy || policy.kind === "uncached")
|
|
180
|
+
return null;
|
|
181
|
+
if (policy.kind === "account-neutral")
|
|
182
|
+
return {};
|
|
183
|
+
try {
|
|
184
|
+
const resolvedAuthIdentity = await policy.resolveIdentity(ctx, cacheContext);
|
|
185
|
+
return resolvedAuthIdentity ? { resolvedAuthIdentity } : null;
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
async function isProviderCacheScopeCurrent(provider, ctx, scope) {
|
|
192
|
+
if (provider.cachePolicy?.kind === "account-neutral")
|
|
193
|
+
return true;
|
|
194
|
+
if (provider.cachePolicy?.kind !== "resolved-auth")
|
|
195
|
+
return false;
|
|
196
|
+
try {
|
|
197
|
+
const runtimeEligibleQuotaProviders = await resolveRuntimeEligibleQuotaProviders(provider.id, ctx);
|
|
198
|
+
if (runtimeEligibleQuotaProviders === null)
|
|
199
|
+
return false;
|
|
200
|
+
return ((await provider.cachePolicy.resolveIdentity(ctx, { runtimeEligibleQuotaProviders })) ===
|
|
201
|
+
scope.resolvedAuthIdentity);
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function getProcessLocalRuntimeOwner(ctx) {
|
|
208
|
+
const owner = ctx.client;
|
|
209
|
+
return owner && (typeof owner === "object" || typeof owner === "function") ? owner : null;
|
|
210
|
+
}
|
|
211
|
+
function rememberProcessLocalLatest(params) {
|
|
212
|
+
const runtimeOwner = getProcessLocalRuntimeOwner(params.ctx);
|
|
213
|
+
if (!runtimeOwner)
|
|
214
|
+
return;
|
|
215
|
+
let runtimeSnapshots = processLocalLatestByRuntime.get(runtimeOwner);
|
|
216
|
+
if (!runtimeSnapshots) {
|
|
217
|
+
runtimeSnapshots = new WeakMap();
|
|
218
|
+
processLocalLatestByRuntime.set(runtimeOwner, runtimeSnapshots);
|
|
219
|
+
}
|
|
220
|
+
runtimeSnapshots.set(params.provider, {
|
|
221
|
+
result: cloneQuotaProviderResult(params.result),
|
|
222
|
+
timestamp: Date.now(),
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
function readProcessLocalLatest(params) {
|
|
226
|
+
const runtimeOwner = getProcessLocalRuntimeOwner(params.ctx);
|
|
227
|
+
if (!runtimeOwner)
|
|
228
|
+
return { hit: false };
|
|
229
|
+
const entry = processLocalLatestByRuntime.get(runtimeOwner)?.get(params.provider);
|
|
230
|
+
if (!entry)
|
|
231
|
+
return { hit: false };
|
|
232
|
+
return {
|
|
233
|
+
hit: true,
|
|
234
|
+
result: cloneQuotaProviderResult(entry.result),
|
|
235
|
+
timestamp: entry.timestamp,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
412
238
|
function publishQuotaTelemetry(params) {
|
|
413
239
|
if (!params.ctx.config.telemetryToken)
|
|
414
240
|
return;
|
|
415
241
|
const uncachedSnapshotId = `uncached:${params.providerId}`;
|
|
242
|
+
const cachedSnapshotId = `cached:${params.providerId}`;
|
|
243
|
+
const isUncached = params.snapshotId.startsWith("uncached:");
|
|
416
244
|
updateQuotaTelemetrySnapshot({
|
|
417
245
|
token: params.ctx.config.telemetryToken,
|
|
418
|
-
snapshotId:
|
|
419
|
-
|
|
420
|
-
? { supersededSnapshotIds: [uncachedSnapshotId] }
|
|
421
|
-
: {}),
|
|
246
|
+
snapshotId: isUncached ? uncachedSnapshotId : cachedSnapshotId,
|
|
247
|
+
supersededSnapshotIds: [isUncached ? cachedSnapshotId : uncachedSnapshotId],
|
|
422
248
|
providerId: params.providerId,
|
|
423
249
|
result: params.result,
|
|
424
250
|
...(params.cacheTimestamp !== undefined ? { cacheTimestamp: params.cacheTimestamp } : {}),
|
|
@@ -426,8 +252,9 @@ function publishQuotaTelemetry(params) {
|
|
|
426
252
|
}
|
|
427
253
|
export async function fetchQuotaProviderResult(params) {
|
|
428
254
|
const { provider, ctx, ttlMs, bypassCache = false } = params;
|
|
429
|
-
|
|
430
|
-
const snapshot = await fetchValidatedProviderResult(provider, ctx);
|
|
255
|
+
const fetchUncached = async (cacheContext = {}) => {
|
|
256
|
+
const snapshot = await fetchValidatedProviderResult(provider, ctx, cacheContext);
|
|
257
|
+
rememberProcessLocalLatest({ provider, ctx, result: snapshot });
|
|
431
258
|
publishQuotaTelemetry({
|
|
432
259
|
ctx,
|
|
433
260
|
providerId: provider.id,
|
|
@@ -435,34 +262,48 @@ export async function fetchQuotaProviderResult(params) {
|
|
|
435
262
|
result: snapshot,
|
|
436
263
|
});
|
|
437
264
|
return snapshot;
|
|
438
|
-
}
|
|
265
|
+
};
|
|
266
|
+
if (bypassCache && !isLiveLocalUsageProviderId(provider.id))
|
|
267
|
+
return fetchUncached();
|
|
268
|
+
const runtimeEligibleQuotaProviders = await resolveRuntimeEligibleQuotaProviders(provider.id, ctx);
|
|
269
|
+
if (runtimeEligibleQuotaProviders === null)
|
|
270
|
+
return fetchUncached();
|
|
271
|
+
const cacheContext = { runtimeEligibleQuotaProviders };
|
|
272
|
+
const scope = await resolveProviderCacheScope(provider, ctx, cacheContext);
|
|
273
|
+
if (!scope)
|
|
274
|
+
return fetchUncached(cacheContext);
|
|
275
|
+
const key = buildQuotaProviderStateCacheKey(provider.id, ctx, {
|
|
276
|
+
runtimeEligibleQuotaProviders,
|
|
277
|
+
resolvedAuthIdentity: scope.resolvedAuthIdentity,
|
|
278
|
+
});
|
|
439
279
|
if (isLiveLocalUsageProviderId(provider.id)) {
|
|
440
|
-
const snapshot = await fetchValidatedProviderResult(provider, ctx);
|
|
441
|
-
|
|
442
|
-
|
|
280
|
+
const snapshot = await fetchValidatedProviderResult(provider, ctx, cacheContext);
|
|
281
|
+
if (!(await isProviderCacheScopeCurrent(provider, ctx, scope))) {
|
|
282
|
+
return snapshot;
|
|
283
|
+
}
|
|
284
|
+
const entry = encodePersistedQuotaProviderCacheEntry({
|
|
285
|
+
packageVersion: await getQuotaProviderCachePackageVersion(),
|
|
286
|
+
key: getQuotaProviderStateCacheLocator(key),
|
|
443
287
|
providerId: provider.id,
|
|
444
|
-
|
|
288
|
+
timestamp: Date.now(),
|
|
445
289
|
result: snapshot,
|
|
446
290
|
});
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
const snapshot = await fetchValidatedProviderResult(provider, ctx);
|
|
291
|
+
inMemoryCache.set(key, {
|
|
292
|
+
...entry,
|
|
293
|
+
result: cloneQuotaProviderResult(entry.result),
|
|
294
|
+
});
|
|
452
295
|
publishQuotaTelemetry({
|
|
453
296
|
ctx,
|
|
454
297
|
providerId: provider.id,
|
|
455
|
-
snapshotId:
|
|
298
|
+
snapshotId: key,
|
|
456
299
|
result: snapshot,
|
|
300
|
+
cacheTimestamp: entry.timestamp,
|
|
457
301
|
});
|
|
458
302
|
return snapshot;
|
|
459
303
|
}
|
|
460
304
|
const forceAggregateRefresh = provider.id === QUOTA_PROVIDERS_AGGREGATE_ID &&
|
|
461
305
|
runtimeEligibleQuotaProviders?.some((definition) => definition.mode === "local-estimate") ===
|
|
462
306
|
true;
|
|
463
|
-
const key = buildQuotaProviderStateCacheKey(provider.id, ctx, {
|
|
464
|
-
runtimeEligibleQuotaProviders,
|
|
465
|
-
});
|
|
466
307
|
const now = Date.now();
|
|
467
308
|
const packageVersion = await getQuotaProviderCachePackageVersion();
|
|
468
309
|
await maybePrunePersistedQuotaProviderCache(now);
|
|
@@ -515,26 +356,39 @@ export async function fetchQuotaProviderResult(params) {
|
|
|
515
356
|
});
|
|
516
357
|
return cloneQuotaProviderResult(persisted.result);
|
|
517
358
|
}
|
|
359
|
+
const inFlightAfterDiskRead = inFlightByKey.get(key);
|
|
360
|
+
if (inFlightAfterDiskRead) {
|
|
361
|
+
const snapshot = await inFlightAfterDiskRead;
|
|
362
|
+
publishQuotaTelemetry({
|
|
363
|
+
ctx,
|
|
364
|
+
providerId: provider.id,
|
|
365
|
+
snapshotId: key,
|
|
366
|
+
result: snapshot,
|
|
367
|
+
cacheTimestamp: inMemoryCache.get(key)?.timestamp,
|
|
368
|
+
});
|
|
369
|
+
return cloneQuotaProviderResult(snapshot);
|
|
370
|
+
}
|
|
518
371
|
const fetchPromise = (async () => {
|
|
519
|
-
const snapshot = await fetchValidatedProviderResult(provider, ctx);
|
|
520
|
-
if (!snapshot.attempted ||
|
|
372
|
+
const snapshot = await fetchValidatedProviderResult(provider, ctx, cacheContext);
|
|
373
|
+
if (!snapshot.attempted ||
|
|
374
|
+
snapshot.entries.length === 0 ||
|
|
375
|
+
!(await isProviderCacheScopeCurrent(provider, ctx, scope))) {
|
|
521
376
|
inMemoryCache.delete(key);
|
|
522
377
|
await safeRm(getQuotaProviderStateCacheFilePath(provider.id, key));
|
|
523
378
|
return snapshot;
|
|
524
379
|
}
|
|
525
|
-
const entry = {
|
|
526
|
-
version: QUOTA_PROVIDER_CACHE_VERSION,
|
|
380
|
+
const entry = encodePersistedQuotaProviderCacheEntry({
|
|
527
381
|
packageVersion,
|
|
528
|
-
key,
|
|
382
|
+
key: getQuotaProviderStateCacheLocator(key),
|
|
529
383
|
providerId: provider.id,
|
|
530
384
|
timestamp: Date.now(),
|
|
531
|
-
result:
|
|
532
|
-
};
|
|
385
|
+
result: snapshot,
|
|
386
|
+
});
|
|
533
387
|
inMemoryCache.set(key, {
|
|
534
388
|
...entry,
|
|
535
389
|
result: cloneQuotaProviderResult(entry.result),
|
|
536
390
|
});
|
|
537
|
-
await writePersistedQuotaProviderCacheEntry(entry);
|
|
391
|
+
await writePersistedQuotaProviderCacheEntry(entry, key);
|
|
538
392
|
return snapshot;
|
|
539
393
|
})().finally(() => {
|
|
540
394
|
inFlightByKey.delete(key);
|
|
@@ -552,14 +406,21 @@ export async function fetchQuotaProviderResult(params) {
|
|
|
552
406
|
}
|
|
553
407
|
export async function readCachedProviderResult(params) {
|
|
554
408
|
const runtimeEligibleQuotaProviders = await resolveRuntimeEligibleQuotaProviders(params.provider.id, params.ctx);
|
|
555
|
-
if (runtimeEligibleQuotaProviders === null)
|
|
409
|
+
if (runtimeEligibleQuotaProviders === null)
|
|
556
410
|
return { hit: false };
|
|
411
|
+
const cacheContext = { runtimeEligibleQuotaProviders };
|
|
412
|
+
const scope = await resolveProviderCacheScope(params.provider, params.ctx, cacheContext);
|
|
413
|
+
if (!scope) {
|
|
414
|
+
return readProcessLocalLatest({
|
|
415
|
+
provider: params.provider,
|
|
416
|
+
ctx: params.ctx,
|
|
417
|
+
});
|
|
557
418
|
}
|
|
558
419
|
const key = buildQuotaProviderStateCacheKey(params.provider.id, params.ctx, {
|
|
559
420
|
runtimeEligibleQuotaProviders,
|
|
421
|
+
resolvedAuthIdentity: scope.resolvedAuthIdentity,
|
|
560
422
|
});
|
|
561
423
|
const now = Date.now();
|
|
562
|
-
// Check in-memory cache first.
|
|
563
424
|
const inMemory = inMemoryCache.get(key);
|
|
564
425
|
if (inMemory) {
|
|
565
426
|
publishQuotaTelemetry({
|
|
@@ -575,7 +436,6 @@ export async function readCachedProviderResult(params) {
|
|
|
575
436
|
timestamp: inMemory.timestamp,
|
|
576
437
|
};
|
|
577
438
|
}
|
|
578
|
-
// Fall back to disk cache with no expiry guard.
|
|
579
439
|
const packageVersion = await getQuotaProviderCachePackageVersion();
|
|
580
440
|
const persisted = await readPersistedQuotaProviderCacheEntry({
|
|
581
441
|
key,
|
|
@@ -586,7 +446,6 @@ export async function readCachedProviderResult(params) {
|
|
|
586
446
|
ignoreExpiry: true,
|
|
587
447
|
});
|
|
588
448
|
if (persisted) {
|
|
589
|
-
// Populate in-memory cache for subsequent reads.
|
|
590
449
|
inMemoryCache.set(key, {
|
|
591
450
|
...persisted,
|
|
592
451
|
result: cloneQuotaProviderResult(persisted.result),
|
|
@@ -609,6 +468,7 @@ export async function readCachedProviderResult(params) {
|
|
|
609
468
|
export function __resetQuotaStateForTests() {
|
|
610
469
|
inMemoryCache.clear();
|
|
611
470
|
inFlightByKey.clear();
|
|
471
|
+
processLocalLatestByRuntime = new WeakMap();
|
|
612
472
|
lastPruneAtMs = 0;
|
|
613
473
|
}
|
|
614
474
|
//# sourceMappingURL=quota-state.js.map
|