@oh-my-pi/pi-ai 17.1.7 → 17.2.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/CHANGELOG.md +64 -0
- package/dist/types/auth-broker/server.d.ts +5 -0
- package/dist/types/auth-broker/types.d.ts +4 -0
- package/dist/types/auth-storage.d.ts +26 -1
- package/dist/types/error/classes.d.ts +5 -1
- package/dist/types/error/rate-limit.d.ts +5 -4
- package/dist/types/providers/anthropic-client.d.ts +13 -1
- package/dist/types/providers/anthropic.d.ts +9 -1
- package/dist/types/providers/cursor/exec-modern.d.ts +98 -0
- package/dist/types/providers/cursor-pi-args.d.ts +99 -0
- package/dist/types/providers/cursor.d.ts +53 -3
- package/dist/types/providers/error-message.d.ts +2 -4
- package/dist/types/providers/openai-codex-responses.d.ts +10 -0
- package/dist/types/providers/openai-responses-wire.d.ts +8 -0
- package/dist/types/providers/openai-shared.d.ts +13 -1
- package/dist/types/registry/exa.d.ts +8 -0
- package/dist/types/registry/registry.d.ts +7 -1
- package/dist/types/registry/xai.d.ts +4 -1
- package/dist/types/stream.d.ts +2 -0
- package/dist/types/types.d.ts +101 -1
- package/dist/types/usage/umans.d.ts +2 -0
- package/dist/types/utils/block-symbols.d.ts +11 -0
- package/dist/types/utils/harmony-leak.d.ts +15 -7
- package/dist/types/utils/schema/normalize.d.ts +1 -0
- package/dist/types/utils.d.ts +8 -0
- package/package.json +4 -4
- package/src/auth-broker/client.ts +5 -1
- package/src/auth-broker/server.ts +103 -9
- package/src/auth-broker/snapshot-cache.ts +12 -3
- package/src/auth-broker/types.ts +6 -0
- package/src/auth-storage.ts +447 -28
- package/src/error/classes.ts +98 -3
- package/src/error/flags.ts +6 -1
- package/src/error/rate-limit.ts +18 -12
- package/src/providers/amazon-bedrock.ts +3 -3
- package/src/providers/anthropic-client.ts +24 -2
- package/src/providers/anthropic.ts +31 -6
- package/src/providers/cursor/exec-modern.ts +495 -0
- package/src/providers/cursor/proto/agent.proto +1007 -0
- package/src/providers/cursor-pi-args.ts +135 -0
- package/src/providers/cursor.ts +1138 -66
- package/src/providers/devin.ts +2 -1
- package/src/providers/error-message.ts +3 -1
- package/src/providers/google-vertex.ts +3 -5
- package/src/providers/openai-codex-responses.ts +109 -27
- package/src/providers/openai-completions.ts +19 -4
- package/src/providers/openai-responses-wire.ts +8 -0
- package/src/providers/openai-responses.ts +12 -1
- package/src/providers/openai-shared.ts +83 -14
- package/src/registry/api-key-validation.ts +18 -34
- package/src/registry/exa.ts +19 -0
- package/src/registry/novita.ts +6 -3
- package/src/registry/registry.ts +3 -1
- package/src/registry/xai.ts +17 -1
- package/src/stream.ts +3 -4
- package/src/types.ts +115 -0
- package/src/usage/umans.ts +192 -0
- package/src/utils/block-symbols.ts +12 -0
- package/src/utils/harmony-leak.ts +32 -0
- package/src/utils/idle-iterator.ts +2 -2
- package/src/utils/schema/normalize.ts +140 -38
- package/src/utils.ts +173 -3
package/src/auth-storage.ts
CHANGED
|
@@ -65,6 +65,7 @@ import {
|
|
|
65
65
|
} from "./usage/openai-codex-reset";
|
|
66
66
|
import { opencodeGoUsageProvider } from "./usage/opencode-go";
|
|
67
67
|
import { syntheticUsageProvider } from "./usage/synthetic";
|
|
68
|
+
import { umansUsageProvider } from "./usage/umans";
|
|
68
69
|
import { xaiOauthUsageProvider } from "./usage/xai-oauth";
|
|
69
70
|
import { zaiRankingStrategy, zaiUsageProvider } from "./usage/zai";
|
|
70
71
|
|
|
@@ -373,6 +374,13 @@ export interface CredentialRefreshLeaseFence {
|
|
|
373
374
|
|
|
374
375
|
export interface AuthCredentialStore {
|
|
375
376
|
close(): void;
|
|
377
|
+
/**
|
|
378
|
+
* Stateful probe for commits made by another process to the backing store.
|
|
379
|
+
* Returns true once per observed change.
|
|
380
|
+
*/
|
|
381
|
+
pollExternalChanges?(): boolean;
|
|
382
|
+
/** Record the current auth revision after a local mutation already notified consumers. */
|
|
383
|
+
acknowledgeLocalChanges?(): void;
|
|
376
384
|
/** Optional hook to notify the underlying store that usage report cache is stale. */
|
|
377
385
|
invalidateUsageCache?(signal?: AbortSignal): Promise<void>;
|
|
378
386
|
listAuthCredentials(provider?: string): StoredAuthCredential[];
|
|
@@ -653,6 +661,7 @@ const DEFAULT_USAGE_PROVIDERS: UsageProvider[] = [
|
|
|
653
661
|
ollamaCloudUsageProvider,
|
|
654
662
|
claudeUsageProvider,
|
|
655
663
|
zaiUsageProvider,
|
|
664
|
+
umansUsageProvider,
|
|
656
665
|
opencodeGoUsageProvider,
|
|
657
666
|
githubCopilotUsageProvider,
|
|
658
667
|
cursorUsageProvider,
|
|
@@ -1346,6 +1355,19 @@ export class AuthStorage {
|
|
|
1346
1355
|
return this.#generation;
|
|
1347
1356
|
}
|
|
1348
1357
|
|
|
1358
|
+
/**
|
|
1359
|
+
* Reload state after another process commits to the backing store, then
|
|
1360
|
+
* notify snapshot consumers even when only credential blocks changed.
|
|
1361
|
+
*/
|
|
1362
|
+
async pollExternalChanges(): Promise<boolean> {
|
|
1363
|
+
const pollExternalChanges = this.#store.pollExternalChanges?.bind(this.#store);
|
|
1364
|
+
if (!pollExternalChanges?.()) return false;
|
|
1365
|
+
const previousGeneration = this.#generation;
|
|
1366
|
+
await this.reload();
|
|
1367
|
+
if (this.#generation === previousGeneration) this.#bumpGeneration("external-store");
|
|
1368
|
+
return true;
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1349
1371
|
onGenerationChanged(listener: (generation: number) => void): () => void {
|
|
1350
1372
|
this.#generationListeners.add(listener);
|
|
1351
1373
|
return () => {
|
|
@@ -1359,6 +1381,7 @@ export class AuthStorage {
|
|
|
1359
1381
|
|
|
1360
1382
|
#bumpGeneration(reason: string): void {
|
|
1361
1383
|
this.#generation += 1;
|
|
1384
|
+
this.#store.acknowledgeLocalChanges?.();
|
|
1362
1385
|
for (const listener of [...this.#generationListeners]) {
|
|
1363
1386
|
try {
|
|
1364
1387
|
listener(this.#generation);
|
|
@@ -1788,15 +1811,20 @@ export class AuthStorage {
|
|
|
1788
1811
|
}
|
|
1789
1812
|
}
|
|
1790
1813
|
|
|
1791
|
-
/**
|
|
1814
|
+
/**
|
|
1815
|
+
* Records which credential was used for a session (for rate-limit switching).
|
|
1816
|
+
* `lastUsedAtMs` backdates the sticky (session-file pin restores on resume);
|
|
1817
|
+
* it defaults to now for live selections.
|
|
1818
|
+
*/
|
|
1792
1819
|
#recordSessionCredential(
|
|
1793
1820
|
provider: string,
|
|
1794
1821
|
sessionId: string | undefined,
|
|
1795
1822
|
type: AuthCredential["type"],
|
|
1796
1823
|
index: number,
|
|
1824
|
+
lastUsedAtMs?: number,
|
|
1797
1825
|
): void {
|
|
1798
1826
|
if (!sessionId) return;
|
|
1799
|
-
const nowMs = Date.now();
|
|
1827
|
+
const nowMs = lastUsedAtMs ?? Date.now();
|
|
1800
1828
|
const sessionMap = this.#sessionLastCredential.get(provider) ?? new Map();
|
|
1801
1829
|
sessionMap.set(sessionId, { type, index, lastUsedAtMs: nowMs });
|
|
1802
1830
|
this.#sessionLastCredential.set(provider, sessionMap);
|
|
@@ -1994,7 +2022,6 @@ export class AuthStorage {
|
|
|
1994
2022
|
const windows = usage ? strategy.findWindowLimits(usage, args.rankingContext) : undefined;
|
|
1995
2023
|
const primary = windows?.primary;
|
|
1996
2024
|
const secondary = windows?.secondary;
|
|
1997
|
-
const secondaryTarget = secondary ?? primary;
|
|
1998
2025
|
ranked.push({
|
|
1999
2026
|
selection,
|
|
2000
2027
|
usage,
|
|
@@ -2003,9 +2030,9 @@ export class AuthStorage {
|
|
|
2003
2030
|
blockedUntil,
|
|
2004
2031
|
hasPriorityBoost: strategy.hasPriorityBoost?.(primary) ?? false,
|
|
2005
2032
|
planPriority: 0,
|
|
2006
|
-
secondaryUsed: this.#normalizeUsageFraction(
|
|
2033
|
+
secondaryUsed: this.#normalizeUsageFraction(secondary),
|
|
2007
2034
|
secondaryRequiredDrain: this.#computeWindowRequiredDrain(
|
|
2008
|
-
|
|
2035
|
+
secondary,
|
|
2009
2036
|
nowMs,
|
|
2010
2037
|
strategy.windowDefaults.secondaryMs,
|
|
2011
2038
|
),
|
|
@@ -4205,7 +4232,10 @@ export class AuthStorage {
|
|
|
4205
4232
|
let blockedUntil = now + (options?.retryAfterMs ?? AuthStorage.#defaultBackoffMs);
|
|
4206
4233
|
|
|
4207
4234
|
if (credentialType === "oauth" && target.credential.type === "oauth" && strategy) {
|
|
4208
|
-
const report = await
|
|
4235
|
+
const report = await raceUsageWithSignal(
|
|
4236
|
+
this.#getUsageReport(provider, target.credential, options),
|
|
4237
|
+
options?.signal,
|
|
4238
|
+
);
|
|
4209
4239
|
if (report) {
|
|
4210
4240
|
const scopedLimits = this.#getScopedUsageLimits(strategy, report, rankingContext);
|
|
4211
4241
|
if (this.#isUsageLimitReached(scopedLimits)) {
|
|
@@ -4216,6 +4246,7 @@ export class AuthStorage {
|
|
|
4216
4246
|
}
|
|
4217
4247
|
}
|
|
4218
4248
|
}
|
|
4249
|
+
options?.signal?.throwIfAborted();
|
|
4219
4250
|
|
|
4220
4251
|
// Usage lookup may refresh, disable, or remove a row. Re-resolve its
|
|
4221
4252
|
// durable id before applying positional in-memory and persisted blocks.
|
|
@@ -4451,7 +4482,6 @@ export class AuthStorage {
|
|
|
4451
4482
|
const windows = usage ? strategy.findWindowLimits(usage, args.rankingContext) : undefined;
|
|
4452
4483
|
const primary = windows?.primary;
|
|
4453
4484
|
const secondary = windows?.secondary;
|
|
4454
|
-
const secondaryTarget = secondary ?? primary;
|
|
4455
4485
|
ranked.push({
|
|
4456
4486
|
selection,
|
|
4457
4487
|
usage,
|
|
@@ -4460,9 +4490,9 @@ export class AuthStorage {
|
|
|
4460
4490
|
blockedUntil,
|
|
4461
4491
|
hasPriorityBoost: strategy.hasPriorityBoost?.(primary) ?? false,
|
|
4462
4492
|
planPriority: getOpenAICodexPlanPriority(usage, args.planRequirement),
|
|
4463
|
-
secondaryUsed: this.#normalizeUsageFraction(
|
|
4493
|
+
secondaryUsed: this.#normalizeUsageFraction(secondary),
|
|
4464
4494
|
secondaryRequiredDrain: this.#computeWindowRequiredDrain(
|
|
4465
|
-
|
|
4495
|
+
secondary,
|
|
4466
4496
|
nowMs,
|
|
4467
4497
|
strategy.windowDefaults.secondaryMs,
|
|
4468
4498
|
),
|
|
@@ -5378,8 +5408,18 @@ export class AuthStorage {
|
|
|
5378
5408
|
* The durable credential id keeps the pin stable across credential refreshes,
|
|
5379
5409
|
* storage reordering, and process restarts. Normal auth retry and usage-limit
|
|
5380
5410
|
* handling may still route around an unavailable account.
|
|
5411
|
+
*
|
|
5412
|
+
* `options.lastUsedAtMs` backdates the sticky's last-use timestamp so a pin
|
|
5413
|
+
* restored from a persisted session keeps the provider's warm-window
|
|
5414
|
+
* semantics: a resume inside the prompt-cache TTL reuses the account, a
|
|
5415
|
+
* stale resume still re-ranks.
|
|
5381
5416
|
*/
|
|
5382
|
-
pinSessionOAuthAccount(
|
|
5417
|
+
pinSessionOAuthAccount(
|
|
5418
|
+
provider: string,
|
|
5419
|
+
sessionId: string,
|
|
5420
|
+
credentialId: number,
|
|
5421
|
+
options?: { lastUsedAtMs?: number },
|
|
5422
|
+
): boolean {
|
|
5383
5423
|
if (!sessionId || this.#runtimeOverrides.has(provider) || this.#configOverrides.has(provider)) {
|
|
5384
5424
|
return false;
|
|
5385
5425
|
}
|
|
@@ -5387,7 +5427,7 @@ export class AuthStorage {
|
|
|
5387
5427
|
const index = stored.findIndex(entry => entry.id === credentialId);
|
|
5388
5428
|
const target = stored[index];
|
|
5389
5429
|
if (target?.credential.type !== "oauth") return false;
|
|
5390
|
-
this.#recordSessionCredential(provider, sessionId, "oauth", index);
|
|
5430
|
+
this.#recordSessionCredential(provider, sessionId, "oauth", index, options?.lastUsedAtMs);
|
|
5391
5431
|
return true;
|
|
5392
5432
|
}
|
|
5393
5433
|
|
|
@@ -6373,8 +6413,11 @@ type SerializedCredentialRecord = {
|
|
|
6373
6413
|
identityKey: string | null;
|
|
6374
6414
|
};
|
|
6375
6415
|
|
|
6376
|
-
const AUTH_SCHEMA_VERSION =
|
|
6416
|
+
const AUTH_SCHEMA_VERSION = 7;
|
|
6377
6417
|
const SQLITE_NOW_EPOCH = "CAST(strftime('%s','now') AS INTEGER)";
|
|
6418
|
+
const LEGACY_CODEX_BLOCK_PROVIDER_KEY = "openai-codex:oauth";
|
|
6419
|
+
const LEGACY_CODEX_BLOCK_SCOPE = "shared";
|
|
6420
|
+
const CODEX_METER_BLOCK_SCOPES = ["chat", "spark"] as const;
|
|
6378
6421
|
|
|
6379
6422
|
/**
|
|
6380
6423
|
* SQLite's busy result code family — base `SQLITE_BUSY` plus the extended
|
|
@@ -6666,11 +6709,17 @@ export class SqliteAuthCredentialStore implements AuthCredentialStore {
|
|
|
6666
6709
|
#lastUsageHistoryStmt: Statement;
|
|
6667
6710
|
#listUsageHistoryStmt: Statement;
|
|
6668
6711
|
#updateUsageHistoryStmt: Statement;
|
|
6712
|
+
#dataVersion: number;
|
|
6713
|
+
#authRevision: number;
|
|
6714
|
+
#localAuthRevision: number;
|
|
6669
6715
|
#closed = false;
|
|
6670
6716
|
|
|
6671
6717
|
constructor(db: Database) {
|
|
6672
6718
|
this.#db = db;
|
|
6673
6719
|
this.#initializeSchema();
|
|
6720
|
+
this.#dataVersion = this.#readDataVersion();
|
|
6721
|
+
this.#authRevision = this.#readAuthRevision();
|
|
6722
|
+
this.#localAuthRevision = this.#readLocalAuthRevision();
|
|
6674
6723
|
|
|
6675
6724
|
this.#listActiveStmt = this.#db.prepare(
|
|
6676
6725
|
"SELECT id, provider, credential_type, data, disabled_cause, identity_key FROM auth_credentials WHERE disabled_cause IS NULL ORDER BY id ASC",
|
|
@@ -6734,7 +6783,11 @@ export class SqliteAuthCredentialStore implements AuthCredentialStore {
|
|
|
6734
6783
|
"SELECT blocked_until_ms, updated_at FROM auth_credential_blocks WHERE credential_id = ? AND provider_key = ? AND block_scope = ? AND blocked_until_ms > ?",
|
|
6735
6784
|
);
|
|
6736
6785
|
this.#listCredentialBlocksByCredentialStmt = this.#db.prepare(
|
|
6737
|
-
|
|
6786
|
+
`SELECT credential_id, provider_key, block_scope, blocked_until_ms, updated_at
|
|
6787
|
+
FROM auth_credential_blocks
|
|
6788
|
+
WHERE credential_id = ? AND blocked_until_ms > ?
|
|
6789
|
+
AND NOT (provider_key = ? AND block_scope = ?)
|
|
6790
|
+
ORDER BY provider_key ASC, block_scope ASC`,
|
|
6738
6791
|
);
|
|
6739
6792
|
this.#upsertCredentialBlockStmt = this.#db.prepare(
|
|
6740
6793
|
`INSERT INTO auth_credential_blocks (credential_id, provider_key, block_scope, blocked_until_ms, updated_at)
|
|
@@ -6915,6 +6968,8 @@ export class SqliteAuthCredentialStore implements AuthCredentialStore {
|
|
|
6915
6968
|
this.#createAuthCredentialsTable();
|
|
6916
6969
|
this.#createAuthCredentialBlocksTable();
|
|
6917
6970
|
this.#createAuthCredentialRefreshLeasesTable();
|
|
6971
|
+
this.#createAuthCredentialBlockCompatibilityObjects();
|
|
6972
|
+
this.#createAuthChangeTrackingObjects();
|
|
6918
6973
|
this.#writeAuthSchemaVersion(AUTH_SCHEMA_VERSION);
|
|
6919
6974
|
return;
|
|
6920
6975
|
}
|
|
@@ -6933,6 +6988,10 @@ export class SqliteAuthCredentialStore implements AuthCredentialStore {
|
|
|
6933
6988
|
this.#createAuthCredentialIndexes();
|
|
6934
6989
|
this.#createAuthCredentialBlocksTable();
|
|
6935
6990
|
this.#createAuthCredentialRefreshLeasesTable();
|
|
6991
|
+
if (schemaVersion <= AUTH_SCHEMA_VERSION) {
|
|
6992
|
+
this.#createAuthCredentialBlockCompatibilityObjects();
|
|
6993
|
+
}
|
|
6994
|
+
this.#createAuthChangeTrackingObjects();
|
|
6936
6995
|
this.#backfillCredentialIdentityKeys();
|
|
6937
6996
|
// Rewriting an already-current version row is a no-op write transaction
|
|
6938
6997
|
// on every boot; only persist when the recorded version actually changes.
|
|
@@ -7030,6 +7089,208 @@ export class SqliteAuthCredentialStore implements AuthCredentialStore {
|
|
|
7030
7089
|
`);
|
|
7031
7090
|
}
|
|
7032
7091
|
|
|
7092
|
+
#createAuthChangeTrackingObjects(): void {
|
|
7093
|
+
this.#db.run(`
|
|
7094
|
+
CREATE TABLE IF NOT EXISTS auth_change_revision (
|
|
7095
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
7096
|
+
revision INTEGER NOT NULL
|
|
7097
|
+
);
|
|
7098
|
+
INSERT OR IGNORE INTO auth_change_revision (id, revision) VALUES (1, 0);
|
|
7099
|
+
CREATE TEMP TABLE IF NOT EXISTS auth_local_change_revision (
|
|
7100
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
7101
|
+
revision INTEGER NOT NULL
|
|
7102
|
+
);
|
|
7103
|
+
INSERT OR IGNORE INTO auth_local_change_revision (id, revision) VALUES (1, 0);
|
|
7104
|
+
`);
|
|
7105
|
+
for (const table of ["auth_credentials", "auth_credential_blocks"] as const) {
|
|
7106
|
+
for (const event of ["INSERT", "UPDATE", "DELETE"] as const) {
|
|
7107
|
+
this.#db.run(`
|
|
7108
|
+
CREATE TRIGGER IF NOT EXISTS auth_change_revision_${table}_${event.toLowerCase()}
|
|
7109
|
+
AFTER ${event} ON ${table}
|
|
7110
|
+
BEGIN
|
|
7111
|
+
UPDATE auth_change_revision SET revision = revision + 1 WHERE id = 1;
|
|
7112
|
+
END;
|
|
7113
|
+
`);
|
|
7114
|
+
this.#db.run(`
|
|
7115
|
+
CREATE TEMP TRIGGER IF NOT EXISTS auth_local_change_revision_${table}_${event.toLowerCase()}
|
|
7116
|
+
AFTER ${event} ON main.${table}
|
|
7117
|
+
BEGIN
|
|
7118
|
+
UPDATE auth_local_change_revision SET revision = revision + 1 WHERE id = 1;
|
|
7119
|
+
END;
|
|
7120
|
+
`);
|
|
7121
|
+
}
|
|
7122
|
+
}
|
|
7123
|
+
}
|
|
7124
|
+
|
|
7125
|
+
#createAuthCredentialBlockMirrorGuardTable(): void {
|
|
7126
|
+
this.#db.run(`
|
|
7127
|
+
CREATE TABLE IF NOT EXISTS auth_credential_block_mirror_guard (
|
|
7128
|
+
credential_id INTEGER PRIMARY KEY
|
|
7129
|
+
) WITHOUT ROWID;
|
|
7130
|
+
`);
|
|
7131
|
+
}
|
|
7132
|
+
|
|
7133
|
+
/**
|
|
7134
|
+
* Keep a physical Codex `shared` row for pre-meter binaries that read this
|
|
7135
|
+
* database directly. Meter rows are canonical for current code. The guard
|
|
7136
|
+
* suppresses feedback while triggers update the compatibility projection.
|
|
7137
|
+
*/
|
|
7138
|
+
#createAuthCredentialBlockCompatibilityTriggers(): void {
|
|
7139
|
+
for (const event of ["INSERT", "UPDATE"] as const) {
|
|
7140
|
+
const eventName = event.toLowerCase();
|
|
7141
|
+
this.#db.run(`
|
|
7142
|
+
CREATE TRIGGER IF NOT EXISTS auth_codex_shared_${eventName}_to_meters
|
|
7143
|
+
AFTER ${event} ON auth_credential_blocks
|
|
7144
|
+
WHEN NEW.provider_key = 'openai-codex:oauth'
|
|
7145
|
+
AND NEW.block_scope = 'shared'
|
|
7146
|
+
AND NOT EXISTS (
|
|
7147
|
+
SELECT 1 FROM auth_credential_block_mirror_guard
|
|
7148
|
+
WHERE credential_id = NEW.credential_id
|
|
7149
|
+
)
|
|
7150
|
+
BEGIN
|
|
7151
|
+
INSERT OR IGNORE INTO auth_credential_block_mirror_guard (credential_id)
|
|
7152
|
+
VALUES (NEW.credential_id);
|
|
7153
|
+
INSERT INTO auth_credential_blocks (
|
|
7154
|
+
credential_id,
|
|
7155
|
+
provider_key,
|
|
7156
|
+
block_scope,
|
|
7157
|
+
blocked_until_ms,
|
|
7158
|
+
updated_at
|
|
7159
|
+
)
|
|
7160
|
+
VALUES (
|
|
7161
|
+
NEW.credential_id,
|
|
7162
|
+
NEW.provider_key,
|
|
7163
|
+
'chat',
|
|
7164
|
+
NEW.blocked_until_ms,
|
|
7165
|
+
NEW.updated_at
|
|
7166
|
+
)
|
|
7167
|
+
ON CONFLICT(credential_id, provider_key, block_scope) DO UPDATE SET
|
|
7168
|
+
blocked_until_ms = MAX(auth_credential_blocks.blocked_until_ms, excluded.blocked_until_ms),
|
|
7169
|
+
updated_at = MAX(auth_credential_blocks.updated_at, excluded.updated_at);
|
|
7170
|
+
INSERT INTO auth_credential_blocks (
|
|
7171
|
+
credential_id,
|
|
7172
|
+
provider_key,
|
|
7173
|
+
block_scope,
|
|
7174
|
+
blocked_until_ms,
|
|
7175
|
+
updated_at
|
|
7176
|
+
)
|
|
7177
|
+
VALUES (
|
|
7178
|
+
NEW.credential_id,
|
|
7179
|
+
NEW.provider_key,
|
|
7180
|
+
'spark',
|
|
7181
|
+
NEW.blocked_until_ms,
|
|
7182
|
+
NEW.updated_at
|
|
7183
|
+
)
|
|
7184
|
+
ON CONFLICT(credential_id, provider_key, block_scope) DO UPDATE SET
|
|
7185
|
+
blocked_until_ms = MAX(auth_credential_blocks.blocked_until_ms, excluded.blocked_until_ms),
|
|
7186
|
+
updated_at = MAX(auth_credential_blocks.updated_at, excluded.updated_at);
|
|
7187
|
+
DELETE FROM auth_credential_block_mirror_guard
|
|
7188
|
+
WHERE credential_id = NEW.credential_id;
|
|
7189
|
+
END;
|
|
7190
|
+
|
|
7191
|
+
CREATE TRIGGER IF NOT EXISTS auth_codex_meter_${eventName}_to_shared
|
|
7192
|
+
AFTER ${event} ON auth_credential_blocks
|
|
7193
|
+
WHEN NEW.provider_key = 'openai-codex:oauth'
|
|
7194
|
+
AND NEW.block_scope IN ('chat', 'spark')
|
|
7195
|
+
AND NOT EXISTS (
|
|
7196
|
+
SELECT 1 FROM auth_credential_block_mirror_guard
|
|
7197
|
+
WHERE credential_id = NEW.credential_id
|
|
7198
|
+
)
|
|
7199
|
+
BEGIN
|
|
7200
|
+
INSERT OR IGNORE INTO auth_credential_block_mirror_guard (credential_id)
|
|
7201
|
+
VALUES (NEW.credential_id);
|
|
7202
|
+
DELETE FROM auth_credential_blocks
|
|
7203
|
+
WHERE credential_id = NEW.credential_id
|
|
7204
|
+
AND provider_key = NEW.provider_key
|
|
7205
|
+
AND block_scope = 'shared';
|
|
7206
|
+
INSERT INTO auth_credential_blocks (
|
|
7207
|
+
credential_id,
|
|
7208
|
+
provider_key,
|
|
7209
|
+
block_scope,
|
|
7210
|
+
blocked_until_ms,
|
|
7211
|
+
updated_at
|
|
7212
|
+
)
|
|
7213
|
+
SELECT
|
|
7214
|
+
NEW.credential_id,
|
|
7215
|
+
NEW.provider_key,
|
|
7216
|
+
'shared',
|
|
7217
|
+
MAX(blocked_until_ms),
|
|
7218
|
+
MAX(updated_at)
|
|
7219
|
+
FROM auth_credential_blocks
|
|
7220
|
+
WHERE credential_id = NEW.credential_id
|
|
7221
|
+
AND provider_key = NEW.provider_key
|
|
7222
|
+
AND block_scope IN ('chat', 'spark')
|
|
7223
|
+
GROUP BY credential_id, provider_key;
|
|
7224
|
+
DELETE FROM auth_credential_block_mirror_guard
|
|
7225
|
+
WHERE credential_id = NEW.credential_id;
|
|
7226
|
+
END;
|
|
7227
|
+
`);
|
|
7228
|
+
}
|
|
7229
|
+
|
|
7230
|
+
this.#db.run(`
|
|
7231
|
+
CREATE TRIGGER IF NOT EXISTS auth_codex_shared_delete_to_meters
|
|
7232
|
+
AFTER DELETE ON auth_credential_blocks
|
|
7233
|
+
WHEN OLD.provider_key = 'openai-codex:oauth'
|
|
7234
|
+
AND OLD.block_scope = 'shared'
|
|
7235
|
+
AND NOT EXISTS (
|
|
7236
|
+
SELECT 1 FROM auth_credential_block_mirror_guard
|
|
7237
|
+
WHERE credential_id = OLD.credential_id
|
|
7238
|
+
)
|
|
7239
|
+
BEGIN
|
|
7240
|
+
INSERT OR IGNORE INTO auth_credential_block_mirror_guard (credential_id)
|
|
7241
|
+
VALUES (OLD.credential_id);
|
|
7242
|
+
DELETE FROM auth_credential_blocks
|
|
7243
|
+
WHERE credential_id = OLD.credential_id
|
|
7244
|
+
AND provider_key = OLD.provider_key
|
|
7245
|
+
AND block_scope IN ('chat', 'spark');
|
|
7246
|
+
DELETE FROM auth_credential_block_mirror_guard
|
|
7247
|
+
WHERE credential_id = OLD.credential_id;
|
|
7248
|
+
END;
|
|
7249
|
+
|
|
7250
|
+
CREATE TRIGGER IF NOT EXISTS auth_codex_meter_delete_to_shared
|
|
7251
|
+
AFTER DELETE ON auth_credential_blocks
|
|
7252
|
+
WHEN OLD.provider_key = 'openai-codex:oauth'
|
|
7253
|
+
AND OLD.block_scope IN ('chat', 'spark')
|
|
7254
|
+
AND NOT EXISTS (
|
|
7255
|
+
SELECT 1 FROM auth_credential_block_mirror_guard
|
|
7256
|
+
WHERE credential_id = OLD.credential_id
|
|
7257
|
+
)
|
|
7258
|
+
BEGIN
|
|
7259
|
+
INSERT OR IGNORE INTO auth_credential_block_mirror_guard (credential_id)
|
|
7260
|
+
VALUES (OLD.credential_id);
|
|
7261
|
+
DELETE FROM auth_credential_blocks
|
|
7262
|
+
WHERE credential_id = OLD.credential_id
|
|
7263
|
+
AND provider_key = OLD.provider_key
|
|
7264
|
+
AND block_scope = 'shared';
|
|
7265
|
+
INSERT INTO auth_credential_blocks (
|
|
7266
|
+
credential_id,
|
|
7267
|
+
provider_key,
|
|
7268
|
+
block_scope,
|
|
7269
|
+
blocked_until_ms,
|
|
7270
|
+
updated_at
|
|
7271
|
+
)
|
|
7272
|
+
SELECT
|
|
7273
|
+
OLD.credential_id,
|
|
7274
|
+
OLD.provider_key,
|
|
7275
|
+
'shared',
|
|
7276
|
+
MAX(blocked_until_ms),
|
|
7277
|
+
MAX(updated_at)
|
|
7278
|
+
FROM auth_credential_blocks
|
|
7279
|
+
WHERE credential_id = OLD.credential_id
|
|
7280
|
+
AND provider_key = OLD.provider_key
|
|
7281
|
+
AND block_scope IN ('chat', 'spark')
|
|
7282
|
+
GROUP BY credential_id, provider_key;
|
|
7283
|
+
DELETE FROM auth_credential_block_mirror_guard
|
|
7284
|
+
WHERE credential_id = OLD.credential_id;
|
|
7285
|
+
END;
|
|
7286
|
+
`);
|
|
7287
|
+
}
|
|
7288
|
+
|
|
7289
|
+
#createAuthCredentialBlockCompatibilityObjects(): void {
|
|
7290
|
+
this.#createAuthCredentialBlockMirrorGuardTable();
|
|
7291
|
+
this.#createAuthCredentialBlockCompatibilityTriggers();
|
|
7292
|
+
}
|
|
7293
|
+
|
|
7033
7294
|
#createAuthCredentialRefreshLeasesTable(): void {
|
|
7034
7295
|
SqliteAuthCredentialStore.#ensureAuthCredentialRefreshLeasesTable(this.#db);
|
|
7035
7296
|
}
|
|
@@ -7050,6 +7311,9 @@ export class SqliteAuthCredentialStore implements AuthCredentialStore {
|
|
|
7050
7311
|
if (fromVersion < 6) {
|
|
7051
7312
|
this.#migrateAuthSchemaV5ToV6();
|
|
7052
7313
|
}
|
|
7314
|
+
if (fromVersion < 7) {
|
|
7315
|
+
this.#migrateAuthSchemaV6ToV7();
|
|
7316
|
+
}
|
|
7053
7317
|
}
|
|
7054
7318
|
|
|
7055
7319
|
#migrateAuthSchemaV0ToV1(): void {
|
|
@@ -7150,6 +7414,77 @@ export class SqliteAuthCredentialStore implements AuthCredentialStore {
|
|
|
7150
7414
|
migrate();
|
|
7151
7415
|
}
|
|
7152
7416
|
|
|
7417
|
+
#migrateAuthSchemaV6ToV7(): void {
|
|
7418
|
+
const migrate = this.#db.transaction(() => {
|
|
7419
|
+
this.#createAuthCredentialBlocksTable();
|
|
7420
|
+
this.#createAuthCredentialBlockMirrorGuardTable();
|
|
7421
|
+
this.#db.run(`
|
|
7422
|
+
DELETE FROM auth_credential_block_mirror_guard;
|
|
7423
|
+
INSERT OR IGNORE INTO auth_credential_block_mirror_guard (credential_id)
|
|
7424
|
+
SELECT DISTINCT credential_id
|
|
7425
|
+
FROM auth_credential_blocks
|
|
7426
|
+
WHERE provider_key = 'openai-codex:oauth'
|
|
7427
|
+
AND block_scope IN ('chat', 'spark', 'shared');
|
|
7428
|
+
|
|
7429
|
+
INSERT INTO auth_credential_blocks (
|
|
7430
|
+
credential_id,
|
|
7431
|
+
provider_key,
|
|
7432
|
+
block_scope,
|
|
7433
|
+
blocked_until_ms,
|
|
7434
|
+
updated_at
|
|
7435
|
+
)
|
|
7436
|
+
SELECT credential_id, provider_key, 'chat', blocked_until_ms, updated_at
|
|
7437
|
+
FROM auth_credential_blocks
|
|
7438
|
+
WHERE provider_key = 'openai-codex:oauth'
|
|
7439
|
+
AND block_scope = 'shared'
|
|
7440
|
+
ON CONFLICT(credential_id, provider_key, block_scope) DO UPDATE SET
|
|
7441
|
+
blocked_until_ms = MAX(auth_credential_blocks.blocked_until_ms, excluded.blocked_until_ms),
|
|
7442
|
+
updated_at = MAX(auth_credential_blocks.updated_at, excluded.updated_at);
|
|
7443
|
+
|
|
7444
|
+
INSERT INTO auth_credential_blocks (
|
|
7445
|
+
credential_id,
|
|
7446
|
+
provider_key,
|
|
7447
|
+
block_scope,
|
|
7448
|
+
blocked_until_ms,
|
|
7449
|
+
updated_at
|
|
7450
|
+
)
|
|
7451
|
+
SELECT credential_id, provider_key, 'spark', blocked_until_ms, updated_at
|
|
7452
|
+
FROM auth_credential_blocks
|
|
7453
|
+
WHERE provider_key = 'openai-codex:oauth'
|
|
7454
|
+
AND block_scope = 'shared'
|
|
7455
|
+
ON CONFLICT(credential_id, provider_key, block_scope) DO UPDATE SET
|
|
7456
|
+
blocked_until_ms = MAX(auth_credential_blocks.blocked_until_ms, excluded.blocked_until_ms),
|
|
7457
|
+
updated_at = MAX(auth_credential_blocks.updated_at, excluded.updated_at);
|
|
7458
|
+
|
|
7459
|
+
INSERT INTO auth_credential_blocks (
|
|
7460
|
+
credential_id,
|
|
7461
|
+
provider_key,
|
|
7462
|
+
block_scope,
|
|
7463
|
+
blocked_until_ms,
|
|
7464
|
+
updated_at
|
|
7465
|
+
)
|
|
7466
|
+
SELECT
|
|
7467
|
+
credential_id,
|
|
7468
|
+
provider_key,
|
|
7469
|
+
'shared',
|
|
7470
|
+
MAX(blocked_until_ms),
|
|
7471
|
+
MAX(updated_at)
|
|
7472
|
+
FROM auth_credential_blocks
|
|
7473
|
+
WHERE provider_key = 'openai-codex:oauth'
|
|
7474
|
+
AND block_scope IN ('chat', 'spark')
|
|
7475
|
+
GROUP BY credential_id, provider_key
|
|
7476
|
+
ON CONFLICT(credential_id, provider_key, block_scope) DO UPDATE SET
|
|
7477
|
+
blocked_until_ms = excluded.blocked_until_ms,
|
|
7478
|
+
updated_at = excluded.updated_at;
|
|
7479
|
+
|
|
7480
|
+
DELETE FROM auth_credential_block_mirror_guard;
|
|
7481
|
+
`);
|
|
7482
|
+
this.#createAuthCredentialBlockCompatibilityTriggers();
|
|
7483
|
+
this.#writeAuthSchemaVersion(7);
|
|
7484
|
+
});
|
|
7485
|
+
migrate.immediate();
|
|
7486
|
+
}
|
|
7487
|
+
|
|
7153
7488
|
#backfillCredentialIdentityKeys(): void {
|
|
7154
7489
|
const selectRowsStmt = this.#db.prepare(
|
|
7155
7490
|
"SELECT id, provider, credential_type, data, disabled_cause, identity_key FROM auth_credentials WHERE identity_key IS NULL ORDER BY id ASC",
|
|
@@ -7418,7 +7753,7 @@ export class SqliteAuthCredentialStore implements AuthCredentialStore {
|
|
|
7418
7753
|
id,
|
|
7419
7754
|
expectedData,
|
|
7420
7755
|
) as { changes: number });
|
|
7421
|
-
if (result.changes
|
|
7756
|
+
if (result.changes === 0) return false;
|
|
7422
7757
|
if (provider) {
|
|
7423
7758
|
this.#purgeSupersededDisabledRows(provider, this.listAuthCredentials(provider));
|
|
7424
7759
|
}
|
|
@@ -7457,7 +7792,7 @@ export class SqliteAuthCredentialStore implements AuthCredentialStore {
|
|
|
7457
7792
|
: (this.#deleteIfMatchesStmt.run(normalizeDisabledCause(disabledCause), id, expectedData) as {
|
|
7458
7793
|
changes: number;
|
|
7459
7794
|
});
|
|
7460
|
-
return result.changes
|
|
7795
|
+
return result.changes > 0;
|
|
7461
7796
|
}
|
|
7462
7797
|
deleteAuthCredentialsForProvider(provider: string, disabledCause: string): void {
|
|
7463
7798
|
try {
|
|
@@ -7504,7 +7839,13 @@ export class SqliteAuthCredentialStore implements AuthCredentialStore {
|
|
|
7504
7839
|
|
|
7505
7840
|
getCredentialBlock(credentialId: number, providerKey: string, blockScope: string): number | undefined {
|
|
7506
7841
|
const nowMs = Date.now();
|
|
7507
|
-
|
|
7842
|
+
const isCodexBlock = providerKey === LEGACY_CODEX_BLOCK_PROVIDER_KEY;
|
|
7843
|
+
// Current callers use meter scopes. The physical shared row exists only
|
|
7844
|
+
// for direct SQLite readers from pre-meter releases.
|
|
7845
|
+
if (isCodexBlock && blockScope === LEGACY_CODEX_BLOCK_SCOPE) {
|
|
7846
|
+
return undefined;
|
|
7847
|
+
}
|
|
7848
|
+
if (!isCodexBlock) this.#deleteExpiredCredentialBlocksStmt.run(nowMs);
|
|
7508
7849
|
const row = this.#getCredentialBlockStmt.get(credentialId, providerKey, blockScope, nowMs) as
|
|
7509
7850
|
| { blocked_until_ms?: number; updated_at?: number }
|
|
7510
7851
|
| undefined;
|
|
@@ -7513,7 +7854,11 @@ export class SqliteAuthCredentialStore implements AuthCredentialStore {
|
|
|
7513
7854
|
|
|
7514
7855
|
getCredentialBlockReconcileAfter(credentialId: number, providerKey: string, blockScope: string): number | undefined {
|
|
7515
7856
|
const nowMs = Date.now();
|
|
7516
|
-
|
|
7857
|
+
const isCodexBlock = providerKey === LEGACY_CODEX_BLOCK_PROVIDER_KEY;
|
|
7858
|
+
if (isCodexBlock && blockScope === LEGACY_CODEX_BLOCK_SCOPE) {
|
|
7859
|
+
return undefined;
|
|
7860
|
+
}
|
|
7861
|
+
if (!isCodexBlock) this.#deleteExpiredCredentialBlocksStmt.run(nowMs);
|
|
7517
7862
|
const row = this.#getCredentialBlockStmt.get(credentialId, providerKey, blockScope, nowMs) as
|
|
7518
7863
|
| { blocked_until_ms?: number; updated_at?: number }
|
|
7519
7864
|
| undefined;
|
|
@@ -7527,16 +7872,33 @@ export class SqliteAuthCredentialStore implements AuthCredentialStore {
|
|
|
7527
7872
|
}
|
|
7528
7873
|
|
|
7529
7874
|
upsertCredentialBlock(block: StoredCredentialBlock): void {
|
|
7530
|
-
|
|
7531
|
-
block.
|
|
7532
|
-
|
|
7533
|
-
|
|
7534
|
-
|
|
7535
|
-
|
|
7536
|
-
|
|
7537
|
-
|
|
7538
|
-
|
|
7539
|
-
|
|
7875
|
+
const isLegacyCodexBlock =
|
|
7876
|
+
block.providerKey === LEGACY_CODEX_BLOCK_PROVIDER_KEY && block.blockScope === LEGACY_CODEX_BLOCK_SCOPE;
|
|
7877
|
+
const blockScopes = isLegacyCodexBlock ? CODEX_METER_BLOCK_SCOPES : [block.blockScope];
|
|
7878
|
+
const upsert = this.#db.transaction(() => {
|
|
7879
|
+
for (const blockScope of blockScopes) {
|
|
7880
|
+
this.#upsertCredentialBlockStmt.run(
|
|
7881
|
+
block.credentialId,
|
|
7882
|
+
block.providerKey,
|
|
7883
|
+
blockScope,
|
|
7884
|
+
block.blockedUntilMs,
|
|
7885
|
+
);
|
|
7886
|
+
}
|
|
7887
|
+
});
|
|
7888
|
+
upsert.immediate();
|
|
7889
|
+
|
|
7890
|
+
const reconcileAfterMs = Math.min(block.blockedUntilMs, Date.now() + USAGE_REPORT_TTL_MS);
|
|
7891
|
+
for (const blockScope of blockScopes) {
|
|
7892
|
+
this.#credentialBlockReconcileAfter.set(
|
|
7893
|
+
`${block.credentialId}\0${block.providerKey}\0${blockScope}`,
|
|
7894
|
+
reconcileAfterMs,
|
|
7895
|
+
);
|
|
7896
|
+
}
|
|
7897
|
+
if (isLegacyCodexBlock) {
|
|
7898
|
+
this.#credentialBlockReconcileAfter.delete(
|
|
7899
|
+
`${block.credentialId}\0${block.providerKey}\0${LEGACY_CODEX_BLOCK_SCOPE}`,
|
|
7900
|
+
);
|
|
7901
|
+
}
|
|
7540
7902
|
}
|
|
7541
7903
|
|
|
7542
7904
|
deleteCredentialBlock(credentialId: number, providerKey: string, blockScope: string): void {
|
|
@@ -7567,7 +7929,12 @@ export class SqliteAuthCredentialStore implements AuthCredentialStore {
|
|
|
7567
7929
|
for (const credentialId of credentialIds) {
|
|
7568
7930
|
if (seenCredentialIds.has(credentialId)) continue;
|
|
7569
7931
|
seenCredentialIds.add(credentialId);
|
|
7570
|
-
const rows = this.#listCredentialBlocksByCredentialStmt.all(
|
|
7932
|
+
const rows = this.#listCredentialBlocksByCredentialStmt.all(
|
|
7933
|
+
credentialId,
|
|
7934
|
+
nowMs,
|
|
7935
|
+
LEGACY_CODEX_BLOCK_PROVIDER_KEY,
|
|
7936
|
+
LEGACY_CODEX_BLOCK_SCOPE,
|
|
7937
|
+
) as CredentialBlockRow[];
|
|
7571
7938
|
for (const row of rows) {
|
|
7572
7939
|
blocks.push({
|
|
7573
7940
|
credentialId: row.credential_id,
|
|
@@ -7894,6 +8261,50 @@ export class SqliteAuthCredentialStore implements AuthCredentialStore {
|
|
|
7894
8261
|
this.deleteAuthCredentialsForProvider(provider, "deleted by user");
|
|
7895
8262
|
}
|
|
7896
8263
|
|
|
8264
|
+
/**
|
|
8265
|
+
* SQLite increments `data_version` when another connection commits. Own
|
|
8266
|
+
* writes leave it unchanged and already notify AuthStorage directly.
|
|
8267
|
+
*/
|
|
8268
|
+
pollExternalChanges(): boolean {
|
|
8269
|
+
this.#acknowledgeLocalAuthChanges();
|
|
8270
|
+
const dataVersion = this.#readDataVersion();
|
|
8271
|
+
if (dataVersion === this.#dataVersion) return false;
|
|
8272
|
+
this.#dataVersion = dataVersion;
|
|
8273
|
+
const authRevision = this.#readAuthRevision();
|
|
8274
|
+
if (authRevision === this.#authRevision) return false;
|
|
8275
|
+
this.#authRevision = authRevision;
|
|
8276
|
+
return true;
|
|
8277
|
+
}
|
|
8278
|
+
|
|
8279
|
+
acknowledgeLocalChanges(): void {
|
|
8280
|
+
this.#acknowledgeLocalAuthChanges();
|
|
8281
|
+
}
|
|
8282
|
+
|
|
8283
|
+
#acknowledgeLocalAuthChanges(): void {
|
|
8284
|
+
const localAuthRevision = this.#readLocalAuthRevision();
|
|
8285
|
+
this.#authRevision += localAuthRevision - this.#localAuthRevision;
|
|
8286
|
+
this.#localAuthRevision = localAuthRevision;
|
|
8287
|
+
}
|
|
8288
|
+
|
|
8289
|
+
#readDataVersion(): number {
|
|
8290
|
+
const row = this.#db.query("PRAGMA data_version").get() as { data_version?: number } | null;
|
|
8291
|
+
return row?.data_version ?? 0;
|
|
8292
|
+
}
|
|
8293
|
+
|
|
8294
|
+
#readAuthRevision(): number {
|
|
8295
|
+
const row = this.#db.query("SELECT revision FROM auth_change_revision WHERE id = 1").get() as {
|
|
8296
|
+
revision?: number;
|
|
8297
|
+
} | null;
|
|
8298
|
+
return row?.revision ?? 0;
|
|
8299
|
+
}
|
|
8300
|
+
|
|
8301
|
+
#readLocalAuthRevision(): number {
|
|
8302
|
+
const row = this.#db.query("SELECT revision FROM auth_local_change_revision WHERE id = 1").get() as {
|
|
8303
|
+
revision?: number;
|
|
8304
|
+
} | null;
|
|
8305
|
+
return row?.revision ?? 0;
|
|
8306
|
+
}
|
|
8307
|
+
|
|
7897
8308
|
close(): void {
|
|
7898
8309
|
if (this.#closed) return;
|
|
7899
8310
|
this.#closed = true;
|
|
@@ -7923,6 +8334,14 @@ export class SqliteAuthCredentialStore implements AuthCredentialStore {
|
|
|
7923
8334
|
this.#updateUsageHistoryStmt.finalize();
|
|
7924
8335
|
this.#insertUsageCostStmt.finalize();
|
|
7925
8336
|
this.#listUsageCostsStmt.finalize();
|
|
8337
|
+
this.#updateIfMatchesStmt.finalize();
|
|
8338
|
+
this.#updateIfMatchesWithLeaseStmt.finalize();
|
|
8339
|
+
this.#deleteIfMatchesWithLeaseStmt.finalize();
|
|
8340
|
+
this.#deleteCachePrefixStmt.finalize();
|
|
8341
|
+
this.#acquireCredentialRefreshLeaseStmt.finalize();
|
|
8342
|
+
this.#getCredentialRefreshLeaseStmt.finalize();
|
|
8343
|
+
this.#renewCredentialRefreshLeaseStmt.finalize();
|
|
8344
|
+
this.#releaseCredentialRefreshLeaseStmt.finalize();
|
|
7926
8345
|
this.#db.close();
|
|
7927
8346
|
}
|
|
7928
8347
|
}
|