@oh-my-pi/pi-ai 16.3.14 → 16.4.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 +45 -0
- package/README.md +5 -2
- package/dist/types/auth-broker/remote-store.d.ts +1 -0
- package/dist/types/auth-broker/wire-schemas.d.ts +8 -0
- package/dist/types/auth-storage.d.ts +5 -0
- package/dist/types/providers/azure-openai-responses.d.ts +1 -1
- package/dist/types/providers/ollama.d.ts +1 -1
- package/dist/types/providers/openai-chat-server-schema.d.ts +1 -1
- package/dist/types/providers/openai-chat-wire.d.ts +1 -1
- package/dist/types/providers/openai-codex/request-transformer.d.ts +43 -5
- package/dist/types/providers/openai-codex-responses.d.ts +51 -8
- package/dist/types/providers/openai-completions.d.ts +1 -1
- package/dist/types/providers/openai-responses-wire.d.ts +16 -7
- package/dist/types/providers/openai-responses.d.ts +1 -1
- package/dist/types/providers/openai-shared.d.ts +38 -8
- package/dist/types/registry/novita.d.ts +6 -0
- package/dist/types/registry/oauth/device-code.d.ts +25 -0
- package/dist/types/registry/oauth/index.d.ts +1 -25
- package/dist/types/registry/oauth/xai-oauth.d.ts +9 -25
- package/dist/types/registry/registry.d.ts +4 -1
- package/dist/types/registry/xai-oauth.d.ts +0 -1
- package/dist/types/types.d.ts +26 -3
- package/package.json +4 -4
- package/src/auth-broker/remote-store.ts +60 -5
- package/src/auth-broker/server.ts +1 -0
- package/src/auth-broker/wire-schemas.ts +1 -0
- package/src/auth-gateway/server.ts +2 -2
- package/src/auth-storage.ts +198 -60
- package/src/error/flags.ts +4 -1
- package/src/error/rate-limit.ts +7 -1
- package/src/providers/amazon-bedrock.ts +1 -0
- package/src/providers/anthropic-messages-server.ts +18 -0
- package/src/providers/azure-openai-responses.ts +3 -3
- package/src/providers/ollama.ts +1 -1
- package/src/providers/openai-chat-server-schema.ts +1 -1
- package/src/providers/openai-chat-server.ts +8 -1
- package/src/providers/openai-chat-wire.ts +1 -1
- package/src/providers/openai-codex/request-transformer.ts +94 -41
- package/src/providers/openai-codex-responses.ts +475 -39
- package/src/providers/openai-completions.ts +38 -12
- package/src/providers/openai-responses-server.ts +8 -1
- package/src/providers/openai-responses-wire.ts +16 -7
- package/src/providers/openai-responses.ts +18 -5
- package/src/providers/openai-shared.ts +125 -13
- package/src/registry/novita.ts +22 -0
- package/src/registry/oauth/__tests__/xai-oauth.test.ts +191 -80
- package/src/registry/oauth/device-code.ts +92 -0
- package/src/registry/oauth/index.ts +1 -91
- package/src/registry/oauth/xai-oauth.ts +231 -191
- package/src/registry/registry.ts +2 -0
- package/src/registry/xai-oauth.ts +0 -1
- package/src/stream.ts +7 -1
- package/src/types.ts +29 -3
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import type { FetchImpl } from "../../types.js";
|
|
2
|
-
import { OAuthCallbackFlow } from "./callback-server.js";
|
|
3
2
|
import type { OAuthController, OAuthCredentials } from "./types.js";
|
|
4
3
|
/**
|
|
5
|
-
* Validate an xAI OIDC
|
|
4
|
+
* Validate an xAI OIDC endpoint against its scheme and host.
|
|
6
5
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* receive every future refresh_token. Rejecting non-HTTPS or non-`x.ai` /
|
|
11
|
-
* `*.x.ai` hosts pins the cached endpoint to the xAI auth origin.
|
|
6
|
+
* The discovery response is long-lived and its token endpoint receives every
|
|
7
|
+
* future refresh token. Rejecting non-HTTPS or non-`x.ai` / `*.x.ai` hosts
|
|
8
|
+
* pins that endpoint to the xAI auth origin.
|
|
12
9
|
*
|
|
13
10
|
* @throws Error with message `Invalid xAI <field>: <url>` when the URL fails
|
|
14
11
|
* either scheme or host validation.
|
|
@@ -18,29 +15,16 @@ export declare function validateXAIEndpoint(url: string, field: string): string;
|
|
|
18
15
|
* Check whether a JWT access token is at or past its `exp` claim (with an
|
|
19
16
|
* optional refresh-skew margin).
|
|
20
17
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* non-JWTs ("no token in cache") must NOT trigger a spurious refresh.
|
|
18
|
+
* Returns `false` for malformed input because this is a refresh-trigger check,
|
|
19
|
+
* not token validation.
|
|
24
20
|
*/
|
|
25
21
|
export declare function isXAIAccessTokenExpiring(jwt: string, skewSeconds?: number): boolean;
|
|
26
|
-
/**
|
|
27
|
-
* xAI Grok OAuth code flow (Hermes `_xai_oauth_loopback_login` L5315-5469).
|
|
28
|
-
*/
|
|
29
|
-
export declare class XAIOAuthFlow extends OAuthCallbackFlow {
|
|
30
|
-
#private;
|
|
31
|
-
constructor(ctrl: OAuthController);
|
|
32
|
-
generateAuthUrl(state: string, redirectUri: string): Promise<{
|
|
33
|
-
url: string;
|
|
34
|
-
instructions?: string;
|
|
35
|
-
}>;
|
|
36
|
-
exchangeToken(code: string, _state: string, redirectUri: string): Promise<OAuthCredentials>;
|
|
37
|
-
}
|
|
22
|
+
/** Log in to xAI Grok with the RFC 8628 device authorization grant. */
|
|
38
23
|
export declare function loginXAIOAuth(ctrl: OAuthController): Promise<OAuthCredentials>;
|
|
39
24
|
/**
|
|
40
25
|
* Refresh an xAI OAuth access token using a stored refresh_token.
|
|
41
26
|
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* cached-but-poisoned endpoint cannot silently leak a refresh_token.
|
|
27
|
+
* Re-runs OIDC discovery and re-validates the token endpoint before sending
|
|
28
|
+
* the stored refresh token.
|
|
45
29
|
*/
|
|
46
30
|
export declare function refreshXAIOAuthToken(refreshToken: string, fetchOverride?: FetchImpl): Promise<OAuthCredentials>;
|
|
@@ -165,6 +165,10 @@ declare const ALL: ({
|
|
|
165
165
|
readonly id: "nanogpt";
|
|
166
166
|
readonly name: "NanoGPT";
|
|
167
167
|
readonly login: (cb: import("./oauth/index.js").OAuthLoginCallbacks) => Promise<string>;
|
|
168
|
+
} | {
|
|
169
|
+
id: "novita";
|
|
170
|
+
name: string;
|
|
171
|
+
login: (options: import("./oauth/index.js").OAuthController) => Promise<string>;
|
|
168
172
|
} | {
|
|
169
173
|
readonly id: "nvidia";
|
|
170
174
|
readonly name: "NVIDIA";
|
|
@@ -268,7 +272,6 @@ declare const ALL: ({
|
|
|
268
272
|
readonly name: "xAI Grok OAuth (SuperGrok or X Premium+)";
|
|
269
273
|
readonly login: (cb: import("./oauth/index.js").OAuthLoginCallbacks) => Promise<import("./oauth/index.js").OAuthCredentials>;
|
|
270
274
|
readonly refreshToken: (credentials: import("./oauth/index.js").OAuthCredentials) => Promise<import("./oauth/index.js").OAuthCredentials>;
|
|
271
|
-
readonly pasteCodeFlow: true;
|
|
272
275
|
} | {
|
|
273
276
|
readonly id: "xiaomi";
|
|
274
277
|
readonly name: "Xiaomi MiMo";
|
|
@@ -4,5 +4,4 @@ export declare const xaiOauthProvider: {
|
|
|
4
4
|
readonly name: "xAI Grok OAuth (SuperGrok or X Premium+)";
|
|
5
5
|
readonly login: (cb: OAuthLoginCallbacks) => Promise<OAuthCredentials>;
|
|
6
6
|
readonly refreshToken: (credentials: OAuthCredentials) => Promise<OAuthCredentials>;
|
|
7
|
-
readonly pasteCodeFlow: true;
|
|
8
7
|
};
|
package/dist/types/types.d.ts
CHANGED
|
@@ -166,6 +166,27 @@ export interface RawSseEvent {
|
|
|
166
166
|
data: string;
|
|
167
167
|
raw: string[];
|
|
168
168
|
}
|
|
169
|
+
/** Lifecycle fields shared by every Codex compaction implementation. */
|
|
170
|
+
export interface CodexCompactionContext {
|
|
171
|
+
/** Stable only for one logical compaction, including parallel summary calls. */
|
|
172
|
+
operationId: string;
|
|
173
|
+
trigger: "manual" | "auto";
|
|
174
|
+
reason: "user_requested" | "context_limit" | "model_downshift" | "comp_hash_changed";
|
|
175
|
+
phase: "standalone_turn" | "pre_turn" | "mid_turn";
|
|
176
|
+
strategy: "memento" | "prefix_compaction";
|
|
177
|
+
}
|
|
178
|
+
/** Canonical nested metadata serialized into the Codex turn envelope. */
|
|
179
|
+
export interface CodexCompactionMetadata {
|
|
180
|
+
trigger: "manual" | "auto";
|
|
181
|
+
reason: "user_requested" | "context_limit" | "model_downshift" | "comp_hash_changed";
|
|
182
|
+
implementation: "responses" | "responses_compaction_v2" | "responses_compact";
|
|
183
|
+
phase: "standalone_turn" | "pre_turn" | "mid_turn";
|
|
184
|
+
strategy: "memento" | "prefix_compaction";
|
|
185
|
+
}
|
|
186
|
+
/** Dispatch context combining canonical metadata with its local operation identity. */
|
|
187
|
+
export interface CodexCompactionRequestContext extends CodexCompactionMetadata {
|
|
188
|
+
operationId: string;
|
|
189
|
+
}
|
|
169
190
|
export interface StreamOptions {
|
|
170
191
|
temperature?: number;
|
|
171
192
|
topP?: number;
|
|
@@ -231,9 +252,9 @@ export interface StreamOptions {
|
|
|
231
252
|
*/
|
|
232
253
|
sessionId?: string;
|
|
233
254
|
/**
|
|
234
|
-
* Optional prompt-cache identity.
|
|
235
|
-
*
|
|
236
|
-
*
|
|
255
|
+
* Optional prompt-cache identity. OpenAI-family providers use this for
|
|
256
|
+
* `prompt_cache_key` payloads and cache-affinity headers such as
|
|
257
|
+
* `x-grok-conv-id`; when omitted, they fall back to `sessionId`.
|
|
237
258
|
*/
|
|
238
259
|
promptCacheKey?: string;
|
|
239
260
|
/**
|
|
@@ -241,6 +262,8 @@ export interface StreamOptions {
|
|
|
241
262
|
* Providers can use this to persist transport/session state between turns.
|
|
242
263
|
*/
|
|
243
264
|
providerSessionState?: Map<string, ProviderSessionState>;
|
|
265
|
+
/** Canonical Codex compaction classification; ignored by other providers. */
|
|
266
|
+
codexCompaction?: CodexCompactionRequestContext;
|
|
244
267
|
/**
|
|
245
268
|
* Force Gemini model-mode Interactions API transport for providers that support it.
|
|
246
269
|
* When unset, those providers may still use Interactions to continue known
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-ai",
|
|
4
|
-
"version": "16.
|
|
4
|
+
"version": "16.4.0",
|
|
5
5
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@bufbuild/protobuf": "^2.12.0",
|
|
41
|
-
"@oh-my-pi/pi-catalog": "16.
|
|
42
|
-
"@oh-my-pi/pi-utils": "16.
|
|
43
|
-
"@oh-my-pi/pi-wire": "16.
|
|
41
|
+
"@oh-my-pi/pi-catalog": "16.4.0",
|
|
42
|
+
"@oh-my-pi/pi-utils": "16.4.0",
|
|
43
|
+
"@oh-my-pi/pi-wire": "16.4.0",
|
|
44
44
|
"arktype": "^2.2.0",
|
|
45
45
|
"zod": "^4"
|
|
46
46
|
},
|
|
@@ -39,6 +39,7 @@ import type {
|
|
|
39
39
|
* one broker call instead of N.
|
|
40
40
|
*/
|
|
41
41
|
const USAGE_CACHE_TTL_MS = 15_000;
|
|
42
|
+
const CREDENTIAL_BLOCK_RECONCILE_DELAY_MS = 5 * 60_000;
|
|
42
43
|
const WAIT_THRESHOLD_MS = 1_000;
|
|
43
44
|
const MAX_WAIT_MS = 5_000;
|
|
44
45
|
const BACKGROUND_WAIT_MS = 30_000;
|
|
@@ -50,7 +51,9 @@ function compareCredentialBlockSnapshots(a: CredentialBlockSnapshot, b: Credenti
|
|
|
50
51
|
if (provider !== 0) return provider;
|
|
51
52
|
const scope = a.blockScope.localeCompare(b.blockScope);
|
|
52
53
|
if (scope !== 0) return scope;
|
|
53
|
-
|
|
54
|
+
const blockedUntil = a.blockedUntilMs - b.blockedUntilMs;
|
|
55
|
+
if (blockedUntil !== 0) return blockedUntil;
|
|
56
|
+
return (a.updatedAtMs ?? 0) - (b.updatedAtMs ?? 0);
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
function toCredentialBlockSnapshot(block: StoredCredentialBlock): CredentialBlockSnapshot {
|
|
@@ -58,6 +61,7 @@ function toCredentialBlockSnapshot(block: StoredCredentialBlock): CredentialBloc
|
|
|
58
61
|
providerKey: block.providerKey,
|
|
59
62
|
blockScope: block.blockScope,
|
|
60
63
|
blockedUntilMs: block.blockedUntilMs,
|
|
64
|
+
...(block.updatedAtMs !== undefined ? { updatedAtMs: block.updatedAtMs } : {}),
|
|
61
65
|
};
|
|
62
66
|
}
|
|
63
67
|
|
|
@@ -74,7 +78,8 @@ function credentialBlockSnapshotsEqual(
|
|
|
74
78
|
if (
|
|
75
79
|
leftBlock.providerKey !== rightBlock.providerKey ||
|
|
76
80
|
leftBlock.blockScope !== rightBlock.blockScope ||
|
|
77
|
-
leftBlock.blockedUntilMs !== rightBlock.blockedUntilMs
|
|
81
|
+
leftBlock.blockedUntilMs !== rightBlock.blockedUntilMs ||
|
|
82
|
+
leftBlock.updatedAtMs !== rightBlock.updatedAtMs
|
|
78
83
|
) {
|
|
79
84
|
return false;
|
|
80
85
|
}
|
|
@@ -208,6 +213,7 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
208
213
|
#cache: Map<string, CacheEntry> = new Map();
|
|
209
214
|
#usageCache?: UsageCacheEntry;
|
|
210
215
|
#usageInflight?: Promise<UsageReport[] | null>;
|
|
216
|
+
#credentialBlockReconcileAfter: Map<string, number> = new Map();
|
|
211
217
|
#usageCacheEpoch = 0;
|
|
212
218
|
#closed = false;
|
|
213
219
|
/**
|
|
@@ -236,10 +242,12 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
236
242
|
return this.#snapshot;
|
|
237
243
|
}
|
|
238
244
|
|
|
239
|
-
#applySnapshot(snapshot: SnapshotResponse, generation: number): void {
|
|
245
|
+
#applySnapshot(snapshot: SnapshotResponse, generation: number, protectNewBlocks = true): void {
|
|
240
246
|
const nowMs = Date.now();
|
|
247
|
+
const previousCredentials = this.#snapshot.credentials;
|
|
241
248
|
const credentials = snapshot.credentials.map(entry => this.#normalizeSnapshotEntryBlocks(entry, nowMs));
|
|
242
|
-
if (snapshotBlocksChanged(
|
|
249
|
+
if (snapshotBlocksChanged(previousCredentials, credentials)) this.#invalidateUsageCache();
|
|
250
|
+
if (protectNewBlocks) this.#protectNewSnapshotBlocks(previousCredentials, credentials, nowMs);
|
|
243
251
|
this.#snapshot = { ...snapshot, credentials };
|
|
244
252
|
this.#generation = generation;
|
|
245
253
|
this.#snapshotReceivedAt = nowMs;
|
|
@@ -251,6 +259,34 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
251
259
|
logger.debug("auth-broker snapshot callback failed", { error: String(error) });
|
|
252
260
|
}
|
|
253
261
|
}
|
|
262
|
+
#protectNewSnapshotBlocks(previous: readonly SnapshotEntry[], next: readonly SnapshotEntry[], nowMs: number): void {
|
|
263
|
+
const previousBlocksByKey = new Map<string, string>();
|
|
264
|
+
for (const entry of previous) {
|
|
265
|
+
for (const block of entry.blocks ?? []) {
|
|
266
|
+
previousBlocksByKey.set(
|
|
267
|
+
`${entry.id}\0${block.providerKey}\0${block.blockScope}`,
|
|
268
|
+
`${block.blockedUntilMs}\0${block.updatedAtMs ?? ""}`,
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
const activeKeys = new Set<string>();
|
|
273
|
+
for (const entry of next) {
|
|
274
|
+
for (const block of entry.blocks ?? []) {
|
|
275
|
+
const key = `${entry.id}\0${block.providerKey}\0${block.blockScope}`;
|
|
276
|
+
activeKeys.add(key);
|
|
277
|
+
const signature = `${block.blockedUntilMs}\0${block.updatedAtMs ?? ""}`;
|
|
278
|
+
if (previousBlocksByKey.get(key) === signature) continue;
|
|
279
|
+
const updatedAtMs = block.updatedAtMs ?? nowMs;
|
|
280
|
+
this.#credentialBlockReconcileAfter.set(
|
|
281
|
+
key,
|
|
282
|
+
Math.min(block.blockedUntilMs, updatedAtMs + CREDENTIAL_BLOCK_RECONCILE_DELAY_MS),
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
for (const key of this.#credentialBlockReconcileAfter.keys()) {
|
|
287
|
+
if (!activeKeys.has(key)) this.#credentialBlockReconcileAfter.delete(key);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
254
290
|
|
|
255
291
|
async #runBackground(): Promise<void> {
|
|
256
292
|
let backoffMs = BACKGROUND_BACKOFF_INITIAL_MS;
|
|
@@ -340,11 +376,13 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
340
376
|
const incoming = this.#normalizeSnapshotEntryBlocks(entry, Date.now());
|
|
341
377
|
const index = this.#snapshot.credentials.findIndex(candidate => candidate.id === incoming.id);
|
|
342
378
|
const previousBlocks = index === -1 ? undefined : this.#snapshot.credentials[index]?.blocks;
|
|
343
|
-
|
|
379
|
+
const blocksChanged = !credentialBlockSnapshotsEqual(previousBlocks, incoming.blocks);
|
|
380
|
+
if (blocksChanged) this.#invalidateUsageCache();
|
|
344
381
|
const credentials =
|
|
345
382
|
index === -1
|
|
346
383
|
? [...this.#snapshot.credentials, incoming]
|
|
347
384
|
: this.#snapshot.credentials.map((candidate, i) => (i === index ? incoming : candidate));
|
|
385
|
+
if (blocksChanged) this.#protectNewSnapshotBlocks(this.#snapshot.credentials, credentials, Date.now());
|
|
348
386
|
this.#snapshot = { ...this.#snapshot, generation, serverNowMs, refresher, credentials };
|
|
349
387
|
this.#generation = generation;
|
|
350
388
|
this.#snapshotReceivedAt = Date.now();
|
|
@@ -392,6 +430,11 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
392
430
|
return block.blockedUntilMs;
|
|
393
431
|
}
|
|
394
432
|
|
|
433
|
+
getCredentialBlockReconcileAfter(credentialId: number, providerKey: string, blockScope: string): number | undefined {
|
|
434
|
+
if (this.getCredentialBlock(credentialId, providerKey, blockScope) === undefined) return undefined;
|
|
435
|
+
return this.#credentialBlockReconcileAfter.get(`${credentialId}\0${providerKey}\0${blockScope}`);
|
|
436
|
+
}
|
|
437
|
+
|
|
395
438
|
listCredentialBlocks(credentialIds: readonly number[]): StoredCredentialBlock[] {
|
|
396
439
|
const nowMs = Date.now();
|
|
397
440
|
this.cleanExpiredCredentialBlocks(nowMs);
|
|
@@ -406,6 +449,7 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
406
449
|
providerKey: block.providerKey,
|
|
407
450
|
blockScope: block.blockScope,
|
|
408
451
|
blockedUntilMs: block.blockedUntilMs,
|
|
452
|
+
updatedAtMs: block.updatedAtMs,
|
|
409
453
|
});
|
|
410
454
|
}
|
|
411
455
|
}
|
|
@@ -416,6 +460,10 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
416
460
|
upsertCredentialBlock(block: StoredCredentialBlock): void {
|
|
417
461
|
this.#upsertSnapshotBlock(block);
|
|
418
462
|
this.#invalidateUsageCache();
|
|
463
|
+
this.#credentialBlockReconcileAfter.set(
|
|
464
|
+
`${block.credentialId}\0${block.providerKey}\0${block.blockScope}`,
|
|
465
|
+
Math.min(block.blockedUntilMs, Date.now() + CREDENTIAL_BLOCK_RECONCILE_DELAY_MS),
|
|
466
|
+
);
|
|
419
467
|
const body = toCredentialBlockSnapshot(block);
|
|
420
468
|
void this.#client
|
|
421
469
|
.upsertCredentialBlock(block.credentialId, body)
|
|
@@ -434,6 +482,9 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
434
482
|
|
|
435
483
|
deleteCredentialBlocks(credentialId: number): void {
|
|
436
484
|
this.#deleteSnapshotBlocks(credentialId);
|
|
485
|
+
for (const key of this.#credentialBlockReconcileAfter.keys()) {
|
|
486
|
+
if (key.startsWith(`${credentialId}\0`)) this.#credentialBlockReconcileAfter.delete(key);
|
|
487
|
+
}
|
|
437
488
|
this.#invalidateUsageCache();
|
|
438
489
|
void this.#client
|
|
439
490
|
.deleteCredentialBlocks(credentialId)
|
|
@@ -450,6 +501,9 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
450
501
|
|
|
451
502
|
cleanExpiredCredentialBlocks(nowMs: number): void {
|
|
452
503
|
this.#pruneExpiredCredentialBlocks(nowMs);
|
|
504
|
+
for (const [key, reconcileAfterMs] of this.#credentialBlockReconcileAfter) {
|
|
505
|
+
if (reconcileAfterMs <= nowMs) this.#credentialBlockReconcileAfter.delete(key);
|
|
506
|
+
}
|
|
453
507
|
}
|
|
454
508
|
|
|
455
509
|
/**
|
|
@@ -647,6 +701,7 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
647
701
|
providerKey: block.providerKey,
|
|
648
702
|
blockScope: block.blockScope,
|
|
649
703
|
blockedUntilMs: block.blockedUntilMs,
|
|
704
|
+
...(block.updatedAtMs !== undefined ? { updatedAtMs: block.updatedAtMs } : {}),
|
|
650
705
|
}))
|
|
651
706
|
.sort(compareCredentialBlockSnapshots);
|
|
652
707
|
if (blocks.length > 0) return { ...entry, blocks };
|
|
@@ -290,6 +290,7 @@ function buildCredentialBlockGroups(
|
|
|
290
290
|
providerKey: block.providerKey,
|
|
291
291
|
blockScope: block.blockScope,
|
|
292
292
|
blockedUntilMs: block.blockedUntilMs,
|
|
293
|
+
updatedAtMs: block.updatedAtMs,
|
|
293
294
|
};
|
|
294
295
|
const existing = byCredentialId.get(block.credentialId);
|
|
295
296
|
if (existing) {
|
|
@@ -112,8 +112,8 @@ function deriveSessionId(modelId: string, context: Context): string {
|
|
|
112
112
|
parts.push(JSON.stringify({ role: first.role, content: first.content }));
|
|
113
113
|
}
|
|
114
114
|
const seed = parts.join("\u0000");
|
|
115
|
-
// The 36-char UUID flows through unchanged:
|
|
116
|
-
// `
|
|
115
|
+
// The 36-char UUID flows through unchanged:
|
|
116
|
+
// `normalizeOpenAIPromptCacheKey` accepts ≤64 chars verbatim.
|
|
117
117
|
return deterministicUuid(seed);
|
|
118
118
|
}
|
|
119
119
|
|