@slkiser/opencode-quota 4.8.1 → 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 +76 -22
- package/dist/lib/display-sanitize.js.map +1 -1
- package/dist/lib/entries.d.ts +107 -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/glm-coding-plan.d.ts.map +1 -1
- package/dist/lib/glm-coding-plan.js +4 -2
- package/dist/lib/glm-coding-plan.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 +3 -307
- package/dist/lib/provider-metadata.d.ts.map +1 -1
- package/dist/lib/provider-metadata.js +18 -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 +43 -155
- 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 -304
- 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 +136 -24
- 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/google-gemini-cli.d.ts.map +1 -1
- package/dist/providers/google-gemini-cli.js +1 -1
- package/dist/providers/google-gemini-cli.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,245 +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"]) &&
|
|
152
|
-
typeof error.label === "string" &&
|
|
153
|
-
typeof error.message === "string");
|
|
154
|
-
}
|
|
155
|
-
function isQuotaProviderDiagnostic(value) {
|
|
156
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
157
|
-
return false;
|
|
158
|
-
const diagnostic = value;
|
|
159
|
-
return (hasOnlyKeys(diagnostic, [
|
|
160
|
-
"sourceId",
|
|
161
|
-
"providerId",
|
|
162
|
-
"mode",
|
|
163
|
-
"format",
|
|
164
|
-
"modelIds",
|
|
165
|
-
"apiKeyEnv",
|
|
166
|
-
"selected",
|
|
167
|
-
"attempted",
|
|
168
|
-
"credentialSource",
|
|
169
|
-
"outcome",
|
|
170
|
-
"httpStatus",
|
|
171
|
-
"entryCount",
|
|
172
|
-
"checkedPaths",
|
|
173
|
-
"authPaths",
|
|
174
|
-
"statePath",
|
|
175
|
-
"stateHealth",
|
|
176
|
-
"stateVersion",
|
|
177
|
-
"stateLastUpdatedAt",
|
|
178
|
-
]) &&
|
|
179
|
-
typeof diagnostic.sourceId === "string" &&
|
|
180
|
-
typeof diagnostic.providerId === "string" &&
|
|
181
|
-
["remote-api", "local-estimate"].includes(String(diagnostic.mode)) &&
|
|
182
|
-
(diagnostic.format === undefined ||
|
|
183
|
-
["quota-v1", "openrouter-key-v1", "json-v1"].includes(String(diagnostic.format))) &&
|
|
184
|
-
(diagnostic.mode === "remote-api"
|
|
185
|
-
? diagnostic.format !== undefined
|
|
186
|
-
: diagnostic.format === undefined) &&
|
|
187
|
-
(diagnostic.modelIds === null ||
|
|
188
|
-
(Array.isArray(diagnostic.modelIds) &&
|
|
189
|
-
diagnostic.modelIds.every((modelId) => typeof modelId === "string"))) &&
|
|
190
|
-
(diagnostic.apiKeyEnv === null || typeof diagnostic.apiKeyEnv === "string") &&
|
|
191
|
-
diagnostic.selected === true &&
|
|
192
|
-
typeof diagnostic.attempted === "boolean" &&
|
|
193
|
-
(diagnostic.credentialSource === null ||
|
|
194
|
-
["explicit_env", "global_opencode_json", "global_opencode_jsonc", "auth_json"].includes(String(diagnostic.credentialSource))) &&
|
|
195
|
-
[
|
|
196
|
-
"missing_credential",
|
|
197
|
-
"success",
|
|
198
|
-
"http_error",
|
|
199
|
-
"redirect_error",
|
|
200
|
-
"timeout",
|
|
201
|
-
"body_too_large",
|
|
202
|
-
"invalid_content_type",
|
|
203
|
-
"invalid_json",
|
|
204
|
-
"invalid_response",
|
|
205
|
-
"network_error",
|
|
206
|
-
"local_state_error",
|
|
207
|
-
].includes(String(diagnostic.outcome)) &&
|
|
208
|
-
(diagnostic.httpStatus === undefined ||
|
|
209
|
-
(typeof diagnostic.httpStatus === "number" &&
|
|
210
|
-
Number.isInteger(diagnostic.httpStatus) &&
|
|
211
|
-
diagnostic.httpStatus >= 100 &&
|
|
212
|
-
diagnostic.httpStatus <= 599)) &&
|
|
213
|
-
typeof diagnostic.entryCount === "number" &&
|
|
214
|
-
Number.isInteger(diagnostic.entryCount) &&
|
|
215
|
-
diagnostic.entryCount >= 0 &&
|
|
216
|
-
Array.isArray(diagnostic.checkedPaths) &&
|
|
217
|
-
diagnostic.checkedPaths.every((path) => typeof path === "string") &&
|
|
218
|
-
Array.isArray(diagnostic.authPaths) &&
|
|
219
|
-
diagnostic.authPaths.every((path) => typeof path === "string") &&
|
|
220
|
-
(diagnostic.statePath === undefined || typeof diagnostic.statePath === "string") &&
|
|
221
|
-
(diagnostic.stateHealth === undefined ||
|
|
222
|
-
["missing", "healthy", "malformed", "version_mismatch"].includes(String(diagnostic.stateHealth))) &&
|
|
223
|
-
(diagnostic.stateVersion === undefined ||
|
|
224
|
-
diagnostic.stateVersion === null ||
|
|
225
|
-
(typeof diagnostic.stateVersion === "number" &&
|
|
226
|
-
Number.isInteger(diagnostic.stateVersion) &&
|
|
227
|
-
diagnostic.stateVersion >= 0)) &&
|
|
228
|
-
(diagnostic.stateLastUpdatedAt === undefined ||
|
|
229
|
-
diagnostic.stateLastUpdatedAt === null ||
|
|
230
|
-
(typeof diagnostic.stateLastUpdatedAt === "number" &&
|
|
231
|
-
Number.isFinite(diagnostic.stateLastUpdatedAt))));
|
|
62
|
+
return `provider-${createHash("sha256").update(providerId).digest("hex")}`;
|
|
232
63
|
}
|
|
233
|
-
function
|
|
234
|
-
|
|
235
|
-
return false;
|
|
236
|
-
const detail = value;
|
|
237
|
-
return (hasOnlyKeys(detail, ["key", "value"]) &&
|
|
238
|
-
typeof detail.key === "string" &&
|
|
239
|
-
typeof detail.value === "string");
|
|
240
|
-
}
|
|
241
|
-
function isQuotaProviderPresentation(value) {
|
|
242
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
243
|
-
return false;
|
|
244
|
-
const presentation = value;
|
|
245
|
-
return (hasOnlyKeys(presentation, [
|
|
246
|
-
"singleWindowDisplayName",
|
|
247
|
-
"singleWindowShowRight",
|
|
248
|
-
"redundantQuotaFamily",
|
|
249
|
-
"classicStrategy",
|
|
250
|
-
]) &&
|
|
251
|
-
(presentation.singleWindowDisplayName === undefined ||
|
|
252
|
-
typeof presentation.singleWindowDisplayName === "string") &&
|
|
253
|
-
(presentation.singleWindowShowRight === undefined ||
|
|
254
|
-
typeof presentation.singleWindowShowRight === "boolean") &&
|
|
255
|
-
(presentation.redundantQuotaFamily === undefined ||
|
|
256
|
-
typeof presentation.redundantQuotaFamily === "string") &&
|
|
257
|
-
(presentation.classicStrategy === undefined || presentation.classicStrategy === "preserve"));
|
|
258
|
-
}
|
|
259
|
-
function isQuotaProviderResult(value) {
|
|
260
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
261
|
-
return false;
|
|
262
|
-
const result = value;
|
|
263
|
-
return (hasOnlyKeys(result, [
|
|
264
|
-
"attempted",
|
|
265
|
-
"entries",
|
|
266
|
-
"errors",
|
|
267
|
-
"diagnostics",
|
|
268
|
-
"statusDetails",
|
|
269
|
-
"rawDetails",
|
|
270
|
-
"presentation",
|
|
271
|
-
]) &&
|
|
272
|
-
typeof result.attempted === "boolean" &&
|
|
273
|
-
Array.isArray(result.entries) &&
|
|
274
|
-
result.entries.every(isQuotaToastEntry) &&
|
|
275
|
-
Array.isArray(result.errors) &&
|
|
276
|
-
result.errors.every(isQuotaToastError) &&
|
|
277
|
-
(result.diagnostics === undefined ||
|
|
278
|
-
(Array.isArray(result.diagnostics) && result.diagnostics.every(isQuotaProviderDiagnostic))) &&
|
|
279
|
-
(result.statusDetails === undefined ||
|
|
280
|
-
(Array.isArray(result.statusDetails) &&
|
|
281
|
-
result.statusDetails.every(isQuotaProviderStatusDetail))) &&
|
|
282
|
-
(result.rawDetails === undefined ||
|
|
283
|
-
(Array.isArray(result.rawDetails) && result.rawDetails.every(isQuotaProviderStatusDetail))) &&
|
|
284
|
-
(result.presentation === undefined || isQuotaProviderPresentation(result.presentation)));
|
|
64
|
+
export function getQuotaProviderStateCacheFilePath(providerId, key) {
|
|
65
|
+
return join(getQuotaProviderCacheDir(), `${getQuotaProviderCacheFileStem(providerId)}-${getQuotaProviderStateCacheLocator(key)}.json`);
|
|
285
66
|
}
|
|
286
67
|
async function getQuotaProviderCachePackageVersion() {
|
|
287
68
|
return (await getPackageVersion()) ?? QUOTA_PROVIDER_CACHE_PACKAGE_VERSION_FALLBACK;
|
|
288
69
|
}
|
|
289
|
-
function isPersistedQuotaProviderCacheEntry(value, key, providerId, packageVersion) {
|
|
290
|
-
if (!value || typeof value !== "object") {
|
|
291
|
-
return false;
|
|
292
|
-
}
|
|
293
|
-
const entry = value;
|
|
294
|
-
return (entry.version === QUOTA_PROVIDER_CACHE_VERSION &&
|
|
295
|
-
entry.packageVersion === packageVersion &&
|
|
296
|
-
entry.key === key &&
|
|
297
|
-
entry.providerId === providerId &&
|
|
298
|
-
typeof entry.timestamp === "number" &&
|
|
299
|
-
Number.isFinite(entry.timestamp) &&
|
|
300
|
-
isQuotaProviderResult(entry.result));
|
|
301
|
-
}
|
|
302
70
|
async function safeRm(path) {
|
|
303
71
|
try {
|
|
304
72
|
await rm(path, { force: true, recursive: true });
|
|
@@ -340,32 +108,31 @@ async function readPersistedQuotaProviderCacheEntry(params) {
|
|
|
340
108
|
return null;
|
|
341
109
|
}
|
|
342
110
|
const path = getQuotaProviderStateCacheFilePath(params.providerId, params.key);
|
|
111
|
+
const cacheLocator = getQuotaProviderStateCacheLocator(params.key);
|
|
343
112
|
try {
|
|
344
113
|
const raw = await readFile(path, "utf-8");
|
|
345
114
|
const parsed = JSON.parse(raw);
|
|
346
|
-
|
|
115
|
+
const decoded = decodePersistedQuotaProviderCacheEntry(parsed, {
|
|
116
|
+
key: cacheLocator,
|
|
117
|
+
providerId: params.providerId,
|
|
118
|
+
packageVersion: params.packageVersion,
|
|
119
|
+
});
|
|
120
|
+
if (!decoded) {
|
|
347
121
|
await safeRm(path);
|
|
348
122
|
return null;
|
|
349
123
|
}
|
|
350
|
-
if (!params.ignoreExpiry && params.now -
|
|
124
|
+
if (!params.ignoreExpiry && params.now - decoded.timestamp >= params.ttlMs) {
|
|
351
125
|
return null;
|
|
352
126
|
}
|
|
353
|
-
return
|
|
354
|
-
version: parsed.version,
|
|
355
|
-
packageVersion: parsed.packageVersion,
|
|
356
|
-
key: parsed.key,
|
|
357
|
-
providerId: parsed.providerId,
|
|
358
|
-
timestamp: parsed.timestamp,
|
|
359
|
-
result: cloneQuotaProviderResult(parsed.result),
|
|
360
|
-
};
|
|
127
|
+
return decoded;
|
|
361
128
|
}
|
|
362
129
|
catch {
|
|
363
130
|
return null;
|
|
364
131
|
}
|
|
365
132
|
}
|
|
366
|
-
async function writePersistedQuotaProviderCacheEntry(entry) {
|
|
133
|
+
async function writePersistedQuotaProviderCacheEntry(entry, logicalKey) {
|
|
367
134
|
try {
|
|
368
|
-
await writeJsonAtomic(getQuotaProviderStateCacheFilePath(entry.providerId,
|
|
135
|
+
await writeJsonAtomic(getQuotaProviderStateCacheFilePath(entry.providerId, logicalKey), entry, {
|
|
369
136
|
trailingNewline: true,
|
|
370
137
|
});
|
|
371
138
|
}
|
|
@@ -373,11 +140,11 @@ async function writePersistedQuotaProviderCacheEntry(entry) {
|
|
|
373
140
|
// persistence failures should not break quota fetches
|
|
374
141
|
}
|
|
375
142
|
}
|
|
376
|
-
async function fetchValidatedProviderResult(provider, ctx) {
|
|
377
|
-
const fetched = await provider.fetch(ctx);
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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;
|
|
381
148
|
return {
|
|
382
149
|
attempted: true,
|
|
383
150
|
entries: [],
|
|
@@ -394,8 +161,7 @@ async function resolveRuntimeEligibleQuotaProviders(providerId, ctx) {
|
|
|
394
161
|
return undefined;
|
|
395
162
|
}
|
|
396
163
|
try {
|
|
397
|
-
const
|
|
398
|
-
const availableProviderIds = new Set((response.data?.providers ?? []).map((provider) => provider.id));
|
|
164
|
+
const availableProviderIds = await ctx.resolveRuntimeProviderIds();
|
|
399
165
|
return selectEligibleQuotaProviderDefinitions({
|
|
400
166
|
definitions: ctx.config.quotaProviders ?? [],
|
|
401
167
|
availableProviderIds,
|
|
@@ -408,16 +174,77 @@ async function resolveRuntimeEligibleQuotaProviders(providerId, ctx) {
|
|
|
408
174
|
return null;
|
|
409
175
|
}
|
|
410
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
|
+
}
|
|
411
238
|
function publishQuotaTelemetry(params) {
|
|
412
239
|
if (!params.ctx.config.telemetryToken)
|
|
413
240
|
return;
|
|
414
241
|
const uncachedSnapshotId = `uncached:${params.providerId}`;
|
|
242
|
+
const cachedSnapshotId = `cached:${params.providerId}`;
|
|
243
|
+
const isUncached = params.snapshotId.startsWith("uncached:");
|
|
415
244
|
updateQuotaTelemetrySnapshot({
|
|
416
245
|
token: params.ctx.config.telemetryToken,
|
|
417
|
-
snapshotId:
|
|
418
|
-
|
|
419
|
-
? { supersededSnapshotIds: [uncachedSnapshotId] }
|
|
420
|
-
: {}),
|
|
246
|
+
snapshotId: isUncached ? uncachedSnapshotId : cachedSnapshotId,
|
|
247
|
+
supersededSnapshotIds: [isUncached ? cachedSnapshotId : uncachedSnapshotId],
|
|
421
248
|
providerId: params.providerId,
|
|
422
249
|
result: params.result,
|
|
423
250
|
...(params.cacheTimestamp !== undefined ? { cacheTimestamp: params.cacheTimestamp } : {}),
|
|
@@ -425,8 +252,9 @@ function publishQuotaTelemetry(params) {
|
|
|
425
252
|
}
|
|
426
253
|
export async function fetchQuotaProviderResult(params) {
|
|
427
254
|
const { provider, ctx, ttlMs, bypassCache = false } = params;
|
|
428
|
-
|
|
429
|
-
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 });
|
|
430
258
|
publishQuotaTelemetry({
|
|
431
259
|
ctx,
|
|
432
260
|
providerId: provider.id,
|
|
@@ -434,34 +262,48 @@ export async function fetchQuotaProviderResult(params) {
|
|
|
434
262
|
result: snapshot,
|
|
435
263
|
});
|
|
436
264
|
return snapshot;
|
|
437
|
-
}
|
|
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
|
+
});
|
|
438
279
|
if (isLiveLocalUsageProviderId(provider.id)) {
|
|
439
|
-
const snapshot = await fetchValidatedProviderResult(provider, ctx);
|
|
440
|
-
|
|
441
|
-
|
|
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),
|
|
442
287
|
providerId: provider.id,
|
|
443
|
-
|
|
288
|
+
timestamp: Date.now(),
|
|
444
289
|
result: snapshot,
|
|
445
290
|
});
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
const snapshot = await fetchValidatedProviderResult(provider, ctx);
|
|
291
|
+
inMemoryCache.set(key, {
|
|
292
|
+
...entry,
|
|
293
|
+
result: cloneQuotaProviderResult(entry.result),
|
|
294
|
+
});
|
|
451
295
|
publishQuotaTelemetry({
|
|
452
296
|
ctx,
|
|
453
297
|
providerId: provider.id,
|
|
454
|
-
snapshotId:
|
|
298
|
+
snapshotId: key,
|
|
455
299
|
result: snapshot,
|
|
300
|
+
cacheTimestamp: entry.timestamp,
|
|
456
301
|
});
|
|
457
302
|
return snapshot;
|
|
458
303
|
}
|
|
459
304
|
const forceAggregateRefresh = provider.id === QUOTA_PROVIDERS_AGGREGATE_ID &&
|
|
460
305
|
runtimeEligibleQuotaProviders?.some((definition) => definition.mode === "local-estimate") ===
|
|
461
306
|
true;
|
|
462
|
-
const key = buildQuotaProviderStateCacheKey(provider.id, ctx, {
|
|
463
|
-
runtimeEligibleQuotaProviders,
|
|
464
|
-
});
|
|
465
307
|
const now = Date.now();
|
|
466
308
|
const packageVersion = await getQuotaProviderCachePackageVersion();
|
|
467
309
|
await maybePrunePersistedQuotaProviderCache(now);
|
|
@@ -514,26 +356,39 @@ export async function fetchQuotaProviderResult(params) {
|
|
|
514
356
|
});
|
|
515
357
|
return cloneQuotaProviderResult(persisted.result);
|
|
516
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
|
+
}
|
|
517
371
|
const fetchPromise = (async () => {
|
|
518
|
-
const snapshot = await fetchValidatedProviderResult(provider, ctx);
|
|
519
|
-
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))) {
|
|
520
376
|
inMemoryCache.delete(key);
|
|
521
377
|
await safeRm(getQuotaProviderStateCacheFilePath(provider.id, key));
|
|
522
378
|
return snapshot;
|
|
523
379
|
}
|
|
524
|
-
const entry = {
|
|
525
|
-
version: QUOTA_PROVIDER_CACHE_VERSION,
|
|
380
|
+
const entry = encodePersistedQuotaProviderCacheEntry({
|
|
526
381
|
packageVersion,
|
|
527
|
-
key,
|
|
382
|
+
key: getQuotaProviderStateCacheLocator(key),
|
|
528
383
|
providerId: provider.id,
|
|
529
384
|
timestamp: Date.now(),
|
|
530
|
-
result:
|
|
531
|
-
};
|
|
385
|
+
result: snapshot,
|
|
386
|
+
});
|
|
532
387
|
inMemoryCache.set(key, {
|
|
533
388
|
...entry,
|
|
534
389
|
result: cloneQuotaProviderResult(entry.result),
|
|
535
390
|
});
|
|
536
|
-
await writePersistedQuotaProviderCacheEntry(entry);
|
|
391
|
+
await writePersistedQuotaProviderCacheEntry(entry, key);
|
|
537
392
|
return snapshot;
|
|
538
393
|
})().finally(() => {
|
|
539
394
|
inFlightByKey.delete(key);
|
|
@@ -551,14 +406,21 @@ export async function fetchQuotaProviderResult(params) {
|
|
|
551
406
|
}
|
|
552
407
|
export async function readCachedProviderResult(params) {
|
|
553
408
|
const runtimeEligibleQuotaProviders = await resolveRuntimeEligibleQuotaProviders(params.provider.id, params.ctx);
|
|
554
|
-
if (runtimeEligibleQuotaProviders === null)
|
|
409
|
+
if (runtimeEligibleQuotaProviders === null)
|
|
555
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
|
+
});
|
|
556
418
|
}
|
|
557
419
|
const key = buildQuotaProviderStateCacheKey(params.provider.id, params.ctx, {
|
|
558
420
|
runtimeEligibleQuotaProviders,
|
|
421
|
+
resolvedAuthIdentity: scope.resolvedAuthIdentity,
|
|
559
422
|
});
|
|
560
423
|
const now = Date.now();
|
|
561
|
-
// Check in-memory cache first.
|
|
562
424
|
const inMemory = inMemoryCache.get(key);
|
|
563
425
|
if (inMemory) {
|
|
564
426
|
publishQuotaTelemetry({
|
|
@@ -574,7 +436,6 @@ export async function readCachedProviderResult(params) {
|
|
|
574
436
|
timestamp: inMemory.timestamp,
|
|
575
437
|
};
|
|
576
438
|
}
|
|
577
|
-
// Fall back to disk cache with no expiry guard.
|
|
578
439
|
const packageVersion = await getQuotaProviderCachePackageVersion();
|
|
579
440
|
const persisted = await readPersistedQuotaProviderCacheEntry({
|
|
580
441
|
key,
|
|
@@ -585,7 +446,6 @@ export async function readCachedProviderResult(params) {
|
|
|
585
446
|
ignoreExpiry: true,
|
|
586
447
|
});
|
|
587
448
|
if (persisted) {
|
|
588
|
-
// Populate in-memory cache for subsequent reads.
|
|
589
449
|
inMemoryCache.set(key, {
|
|
590
450
|
...persisted,
|
|
591
451
|
result: cloneQuotaProviderResult(persisted.result),
|
|
@@ -608,6 +468,7 @@ export async function readCachedProviderResult(params) {
|
|
|
608
468
|
export function __resetQuotaStateForTests() {
|
|
609
469
|
inMemoryCache.clear();
|
|
610
470
|
inFlightByKey.clear();
|
|
471
|
+
processLocalLatestByRuntime = new WeakMap();
|
|
611
472
|
lastPruneAtMs = 0;
|
|
612
473
|
}
|
|
613
474
|
//# sourceMappingURL=quota-state.js.map
|