@omnicross/core 0.1.6 → 0.1.8
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/dist/ApiConverter.cjs +2 -2
- package/dist/ApiConverter.js +1 -1
- package/dist/{ProviderProxy-C-xqrkKi.d.ts → ProviderProxy--ROec6VI.d.ts} +22 -3
- package/dist/{ProviderProxy-CnMQYN59.d.cts → ProviderProxy-QdwNV6l1.d.cts} +22 -3
- package/dist/auth/GeminiCodeAssistProjectResolver.cjs +4 -3
- package/dist/auth/GeminiCodeAssistProjectResolver.js +3 -2
- package/dist/{chunk-3Z5BQZFM.cjs → chunk-235MXAGX.cjs} +3 -1
- package/dist/{chunk-SQYVORNP.cjs → chunk-26K6T2YS.cjs} +1520 -523
- package/dist/{chunk-DLXZEZWO.cjs → chunk-2E42SAW3.cjs} +42 -4
- package/dist/{chunk-7AK7B36B.cjs → chunk-2Z4XM3H5.cjs} +6 -6
- package/dist/{chunk-OEWZUX4G.js → chunk-32O35CIF.js} +9 -20
- package/dist/chunk-3SCOLIPT.cjs +63 -0
- package/dist/{chunk-SYQF7OIR.js → chunk-3WD4HTR2.js} +2 -0
- package/dist/{chunk-AT7B7SOZ.js → chunk-5NJLQ6T6.js} +21 -10
- package/dist/chunk-5RSZYQJH.cjs +62 -0
- package/dist/{chunk-IFXJ2BG7.js → chunk-7C7DET6S.js} +51 -6
- package/dist/{chunk-SNYBEXLB.cjs → chunk-EPN67EGP.cjs} +6 -1
- package/dist/chunk-FU7EAR73.js +62 -0
- package/dist/{chunk-UX7VWA46.js → chunk-FXR6N3SR.js} +39 -1
- package/dist/{chunk-WYTEQ4Q5.cjs → chunk-GDIXXKF2.cjs} +21 -10
- package/dist/{chunk-WB6PH56J.js → chunk-GFIZ6LQI.js} +12 -8
- package/dist/chunk-GL6XM527.js +61 -0
- package/dist/{chunk-EJLH6NFT.cjs → chunk-J3QQ6JS5.cjs} +12 -8
- package/dist/{chunk-VRFBB5DB.js → chunk-MYYR3HZL.js} +4 -4
- package/dist/{chunk-YJVHCHQX.js → chunk-N7OOJI5V.js} +46 -2
- package/dist/chunk-NKVZ6Z3Y.cjs +61 -0
- package/dist/chunk-OTZC4TNH.cjs +79 -0
- package/dist/{chunk-6VUQPOV3.cjs → chunk-OVQBNVSV.cjs} +10 -21
- package/dist/{chunk-4VLRHHFE.cjs → chunk-OXHNSZ53.cjs} +9 -11
- package/dist/{chunk-6CL5HTPQ.js → chunk-PEGTIPB3.js} +8 -10
- package/dist/chunk-PO3XB56Z.js +79 -0
- package/dist/{chunk-UDCC7EMR.js → chunk-QYC54PMG.js} +1242 -245
- package/dist/{chunk-SIZQQD6T.js → chunk-RJ5ZGNI3.js} +42 -5
- package/dist/{chunk-KCVJWJST.cjs → chunk-RJUONLUQ.cjs} +47 -10
- package/dist/{chunk-BPKCU575.js → chunk-UAPBLNN2.js} +6 -1
- package/dist/{chunk-UNWKG6EI.cjs → chunk-XPIOJQS2.cjs} +51 -6
- package/dist/chunk-XXFYLL7R.js +63 -0
- package/dist/{chunk-YK57NMIF.cjs → chunk-YWL7YZWS.cjs} +47 -3
- package/dist/completion/ApiKeyPoolService.cjs +32 -1
- package/dist/completion/ApiKeyPoolService.d.cts +17 -1
- package/dist/completion/ApiKeyPoolService.d.ts +17 -1
- package/dist/completion/ApiKeyPoolService.js +31 -0
- package/dist/completion/CompletionService.cjs +23 -19
- package/dist/completion/CompletionService.d.cts +1 -0
- package/dist/completion/CompletionService.d.ts +1 -0
- package/dist/completion/CompletionService.js +22 -18
- package/dist/completion.cjs +23 -19
- package/dist/completion.d.cts +1 -0
- package/dist/completion.d.ts +1 -0
- package/dist/completion.js +22 -18
- package/dist/index.cjs +81 -21
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +82 -22
- package/dist/outbound-api/auditCapture.cjs +2 -2
- package/dist/outbound-api/auditCapture.d.cts +3 -4
- package/dist/outbound-api/auditCapture.d.ts +3 -4
- package/dist/outbound-api/auditCapture.js +1 -1
- package/dist/outbound-api/routeResolver.d.cts +4 -3
- package/dist/outbound-api/routeResolver.d.ts +4 -3
- package/dist/outbound-api/subscriptionRegistryPort.d.cts +1 -0
- package/dist/outbound-api/subscriptionRegistryPort.d.ts +1 -0
- package/dist/outbound-api/types.d.cts +3 -2
- package/dist/outbound-api/types.d.ts +3 -2
- package/dist/outbound-api.cjs +23 -19
- package/dist/outbound-api.d.cts +8 -6
- package/dist/outbound-api.d.ts +8 -6
- package/dist/outbound-api.js +22 -18
- package/dist/pipeline/AccountAllowanceScheduling.d.cts +3 -2
- package/dist/pipeline/AccountAllowanceScheduling.d.ts +3 -2
- package/dist/pipeline/AccountRouteActivity.cjs +10 -0
- package/dist/pipeline/AccountRouteActivity.d.cts +61 -0
- package/dist/pipeline/AccountRouteActivity.d.ts +61 -0
- package/dist/pipeline/AccountRouteActivity.js +10 -0
- package/dist/pipeline/ServerOverloadCounter.cjs +16 -0
- package/dist/pipeline/ServerOverloadCounter.d.cts +77 -0
- package/dist/pipeline/ServerOverloadCounter.d.ts +77 -0
- package/dist/pipeline/ServerOverloadCounter.js +16 -0
- package/dist/pipeline/SubscriptionAccountHealth.cjs +4 -2
- package/dist/pipeline/SubscriptionAccountHealth.d.cts +32 -3
- package/dist/pipeline/SubscriptionAccountHealth.d.ts +32 -3
- package/dist/pipeline/SubscriptionAccountHealth.js +3 -1
- package/dist/pipeline/upstreamFetch.cjs +4 -3
- package/dist/pipeline/upstreamFetch.d.cts +35 -6
- package/dist/pipeline/upstreamFetch.d.ts +35 -6
- package/dist/pipeline/upstreamFetch.js +3 -2
- package/dist/pipeline/upstreamTrace.cjs +4 -2
- package/dist/pipeline/upstreamTrace.d.cts +22 -8
- package/dist/pipeline/upstreamTrace.d.ts +22 -8
- package/dist/pipeline/upstreamTrace.js +3 -1
- package/dist/ports.d.cts +2 -2
- package/dist/ports.d.ts +2 -2
- package/dist/provider-proxy/ProviderProxy.cjs +23 -19
- package/dist/provider-proxy/ProviderProxy.d.cts +2 -1
- package/dist/provider-proxy/ProviderProxy.d.ts +2 -1
- package/dist/provider-proxy/ProviderProxy.js +22 -18
- package/dist/provider-proxy/identity/codexCliHeaders.cjs +10 -0
- package/dist/provider-proxy/identity/codexCliHeaders.d.cts +45 -0
- package/dist/provider-proxy/identity/codexCliHeaders.d.ts +45 -0
- package/dist/provider-proxy/identity/codexCliHeaders.js +10 -0
- package/dist/provider-proxy/ingress/providerProxyShared.cjs +23 -19
- package/dist/provider-proxy/ingress/providerProxyShared.d.cts +11 -1
- package/dist/provider-proxy/ingress/providerProxyShared.d.ts +11 -1
- package/dist/provider-proxy/ingress/providerProxyShared.js +22 -18
- package/dist/provider-proxy/matchText.cjs +2 -2
- package/dist/provider-proxy/matchText.d.cts +1 -3
- package/dist/provider-proxy/matchText.d.ts +1 -3
- package/dist/provider-proxy/matchText.js +1 -1
- package/dist/provider-proxy/types.d.cts +20 -1
- package/dist/provider-proxy/types.d.ts +20 -1
- package/dist/provider-proxy.cjs +79 -19
- package/dist/provider-proxy.d.cts +255 -8
- package/dist/provider-proxy.d.ts +255 -8
- package/dist/provider-proxy.js +80 -20
- package/dist/{routeResolver-CHTv_Bs5.d.ts → routeResolver-C0mkkZaM.d.ts} +1 -1
- package/dist/{routeResolver-kepxnfcC.d.cts → routeResolver-CVFE8KKe.d.cts} +1 -1
- package/dist/transformer/transformers/AnthropicTransformer.cjs +3 -2
- package/dist/transformer/transformers/AnthropicTransformer.js +2 -1
- package/dist/transformer/transformers/OpenAIResponseTransformer.cjs +3 -2
- package/dist/transformer/transformers/OpenAIResponseTransformer.js +2 -1
- package/dist/transformer/transformers.cjs +7 -6
- package/dist/transformer/transformers.js +6 -5
- package/dist/transformer.cjs +9 -8
- package/dist/transformer.js +8 -7
- package/dist/{types-hsoKrVQS.d.ts → types-BB8pxk_3.d.ts} +40 -1
- package/dist/{types-H3wGZSOo.d.cts → types-C2MwkLah.d.cts} +40 -1
- package/dist/usage/pricing-engine.cjs +2 -2
- package/dist/usage/pricing-engine.d.cts +20 -0
- package/dist/usage/pricing-engine.d.ts +20 -0
- package/dist/usage/pricing-engine.js +1 -1
- package/dist/usage/usage-recorder.cjs +2 -2
- package/dist/usage/usage-recorder.d.cts +11 -1
- package/dist/usage/usage-recorder.d.ts +11 -1
- package/dist/usage/usage-recorder.js +1 -1
- package/dist/usage.cjs +3 -3
- package/dist/usage.js +2 -2
- package/package.json +1 -1
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
|
+
REDACTED_BODY,
|
|
2
3
|
getUpstreamTracePath,
|
|
3
4
|
redactHeaders,
|
|
4
5
|
writeUpstreamTrace
|
|
5
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-3WD4HTR2.js";
|
|
6
7
|
import {
|
|
7
8
|
getSharedAccountAllowanceStore
|
|
8
9
|
} from "./chunk-MKF46GNZ.js";
|
|
10
|
+
import {
|
|
11
|
+
getSharedAccountRouteActivity
|
|
12
|
+
} from "./chunk-GL6XM527.js";
|
|
9
13
|
|
|
10
14
|
// src/pipeline/upstreamFetch.ts
|
|
11
15
|
import { Agent, buildConnector, ProxyAgent } from "undici";
|
|
@@ -114,17 +118,48 @@ function fetchUpstream(url, init, ctx) {
|
|
|
114
118
|
}
|
|
115
119
|
return response;
|
|
116
120
|
});
|
|
121
|
+
const activity = ctx?.routeActivity;
|
|
122
|
+
const activityStartedAt = activity && ctx?.providerId && ctx.accountId ? Date.now() : 0;
|
|
123
|
+
const recordActivity = (status) => {
|
|
124
|
+
if (!activity || !ctx?.providerId || !ctx.accountId) return;
|
|
125
|
+
const record = getSharedAccountRouteActivity().record({
|
|
126
|
+
providerId: ctx.providerId,
|
|
127
|
+
accountId: ctx.accountId,
|
|
128
|
+
endpoint: activity.endpoint,
|
|
129
|
+
sessionKey: activity.sessionKey,
|
|
130
|
+
sessionSource: activity.sessionSource,
|
|
131
|
+
model: activity.model,
|
|
132
|
+
status,
|
|
133
|
+
durationMs: Math.max(0, Date.now() - activityStartedAt),
|
|
134
|
+
ts: activityStartedAt
|
|
135
|
+
});
|
|
136
|
+
try {
|
|
137
|
+
activity.onRecorded?.(record);
|
|
138
|
+
} catch {
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
const doFetchWithActivity = () => doFetchWithAllowanceTap().then(
|
|
142
|
+
(response) => {
|
|
143
|
+
recordActivity(response.status);
|
|
144
|
+
return response;
|
|
145
|
+
},
|
|
146
|
+
(error) => {
|
|
147
|
+
recordActivity(0);
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
);
|
|
117
151
|
const tracePath = getUpstreamTracePath();
|
|
118
|
-
if (!tracePath || !ctx?.providerId) return
|
|
152
|
+
if (!tracePath || !ctx?.providerId) return doFetchWithActivity();
|
|
119
153
|
const startedAt = Date.now();
|
|
120
154
|
const method = typeof init.method === "string" ? init.method : "GET";
|
|
121
155
|
const requestHeaders = redactHeaders(
|
|
122
156
|
init.headers
|
|
123
157
|
);
|
|
124
|
-
const
|
|
158
|
+
const redactBodies = ctx.redactBodies === true;
|
|
159
|
+
const requestBody = redactBodies ? REDACTED_BODY : typeof init.body === "string" ? init.body : null;
|
|
125
160
|
const providerId = ctx.providerId;
|
|
126
161
|
const accountId = ctx.accountId;
|
|
127
|
-
return
|
|
162
|
+
return doFetchWithActivity().then((res) => {
|
|
128
163
|
const clone = res.clone();
|
|
129
164
|
const status = res.status;
|
|
130
165
|
const statusText = res.statusText;
|
|
@@ -142,7 +177,9 @@ function fetchUpstream(url, init, ctx) {
|
|
|
142
177
|
status,
|
|
143
178
|
statusText,
|
|
144
179
|
responseHeaders,
|
|
145
|
-
|
|
180
|
+
// The byte count stays REAL even when redacted — it is a useful
|
|
181
|
+
// diagnostic and reveals nothing about the token itself.
|
|
182
|
+
responseBody: redactBodies ? REDACTED_BODY : body,
|
|
146
183
|
responseBytes: Buffer.byteLength(body, "utf8")
|
|
147
184
|
});
|
|
148
185
|
}).catch(() => {
|
|
@@ -2,11 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
var _chunk235MXAGXcjs = require('./chunk-235MXAGX.cjs');
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
var _chunkHVBBDBS3cjs = require('./chunk-HVBBDBS3.cjs');
|
|
9
10
|
|
|
11
|
+
|
|
12
|
+
var _chunkNKVZ6Z3Ycjs = require('./chunk-NKVZ6Z3Y.cjs');
|
|
13
|
+
|
|
10
14
|
// src/pipeline/upstreamFetch.ts
|
|
11
15
|
var _undici = require('undici');
|
|
12
16
|
var _socks = require('socks');
|
|
@@ -114,23 +118,54 @@ function fetchUpstream(url, init, ctx) {
|
|
|
114
118
|
}
|
|
115
119
|
return response;
|
|
116
120
|
});
|
|
117
|
-
const
|
|
118
|
-
|
|
121
|
+
const activity = _optionalChain([ctx, 'optionalAccess', _4 => _4.routeActivity]);
|
|
122
|
+
const activityStartedAt = activity && _optionalChain([ctx, 'optionalAccess', _5 => _5.providerId]) && ctx.accountId ? Date.now() : 0;
|
|
123
|
+
const recordActivity = (status) => {
|
|
124
|
+
if (!activity || !_optionalChain([ctx, 'optionalAccess', _6 => _6.providerId]) || !ctx.accountId) return;
|
|
125
|
+
const record = _chunkNKVZ6Z3Ycjs.getSharedAccountRouteActivity.call(void 0, ).record({
|
|
126
|
+
providerId: ctx.providerId,
|
|
127
|
+
accountId: ctx.accountId,
|
|
128
|
+
endpoint: activity.endpoint,
|
|
129
|
+
sessionKey: activity.sessionKey,
|
|
130
|
+
sessionSource: activity.sessionSource,
|
|
131
|
+
model: activity.model,
|
|
132
|
+
status,
|
|
133
|
+
durationMs: Math.max(0, Date.now() - activityStartedAt),
|
|
134
|
+
ts: activityStartedAt
|
|
135
|
+
});
|
|
136
|
+
try {
|
|
137
|
+
_optionalChain([activity, 'access', _7 => _7.onRecorded, 'optionalCall', _8 => _8(record)]);
|
|
138
|
+
} catch (e5) {
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
const doFetchWithActivity = () => doFetchWithAllowanceTap().then(
|
|
142
|
+
(response) => {
|
|
143
|
+
recordActivity(response.status);
|
|
144
|
+
return response;
|
|
145
|
+
},
|
|
146
|
+
(error) => {
|
|
147
|
+
recordActivity(0);
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
);
|
|
151
|
+
const tracePath = _chunk235MXAGXcjs.getUpstreamTracePath.call(void 0, );
|
|
152
|
+
if (!tracePath || !_optionalChain([ctx, 'optionalAccess', _9 => _9.providerId])) return doFetchWithActivity();
|
|
119
153
|
const startedAt = Date.now();
|
|
120
154
|
const method = typeof init.method === "string" ? init.method : "GET";
|
|
121
|
-
const requestHeaders =
|
|
155
|
+
const requestHeaders = _chunk235MXAGXcjs.redactHeaders.call(void 0,
|
|
122
156
|
init.headers
|
|
123
157
|
);
|
|
124
|
-
const
|
|
158
|
+
const redactBodies = ctx.redactBodies === true;
|
|
159
|
+
const requestBody = redactBodies ? _chunk235MXAGXcjs.REDACTED_BODY : typeof init.body === "string" ? init.body : null;
|
|
125
160
|
const providerId = ctx.providerId;
|
|
126
161
|
const accountId = ctx.accountId;
|
|
127
|
-
return
|
|
162
|
+
return doFetchWithActivity().then((res) => {
|
|
128
163
|
const clone = res.clone();
|
|
129
164
|
const status = res.status;
|
|
130
165
|
const statusText = res.statusText;
|
|
131
|
-
const responseHeaders =
|
|
166
|
+
const responseHeaders = _chunk235MXAGXcjs.redactHeaders.call(void 0, res.headers);
|
|
132
167
|
void clone.text().then((body) => {
|
|
133
|
-
|
|
168
|
+
_chunk235MXAGXcjs.writeUpstreamTrace.call(void 0, {
|
|
134
169
|
ts: startedAt,
|
|
135
170
|
durationMs: Date.now() - startedAt,
|
|
136
171
|
providerId,
|
|
@@ -142,7 +177,9 @@ function fetchUpstream(url, init, ctx) {
|
|
|
142
177
|
status,
|
|
143
178
|
statusText,
|
|
144
179
|
responseHeaders,
|
|
145
|
-
|
|
180
|
+
// The byte count stays REAL even when redacted — it is a useful
|
|
181
|
+
// diagnostic and reveals nothing about the token itself.
|
|
182
|
+
responseBody: redactBodies ? _chunk235MXAGXcjs.REDACTED_BODY : body,
|
|
146
183
|
responseBytes: Buffer.byteLength(body, "utf8")
|
|
147
184
|
});
|
|
148
185
|
}).catch(() => {
|
|
@@ -150,7 +187,7 @@ function fetchUpstream(url, init, ctx) {
|
|
|
150
187
|
return res;
|
|
151
188
|
}).catch((err) => {
|
|
152
189
|
const msg = err instanceof Error ? err.message : String(err);
|
|
153
|
-
|
|
190
|
+
_chunk235MXAGXcjs.writeUpstreamTrace.call(void 0, {
|
|
154
191
|
ts: startedAt,
|
|
155
192
|
durationMs: Date.now() - startedAt,
|
|
156
193
|
providerId,
|
|
@@ -78,7 +78,12 @@ var UsageRecorder = class {
|
|
|
78
78
|
costSavedByCacheUsd,
|
|
79
79
|
rawUsage: input.rawUsage != null ? safeStringify(input.rawUsage) : null,
|
|
80
80
|
runId: input.runId ?? null,
|
|
81
|
-
|
|
81
|
+
routeLeaseId: input.routeLeaseId ?? null,
|
|
82
|
+
routeLeaseConsumer: input.routeLeaseConsumer ?? null,
|
|
83
|
+
routeLeaseStageId: input.routeLeaseStageId ?? null,
|
|
84
|
+
eventId: input.eventId ?? null,
|
|
85
|
+
...input.cacheKeySource !== void 0 ? { cacheKeySource: input.cacheKeySource } : {},
|
|
86
|
+
...input.cacheKeyInjected !== void 0 ? { cacheKeyInjected: input.cacheKeyInjected } : {}
|
|
82
87
|
};
|
|
83
88
|
try {
|
|
84
89
|
return await this.store.insert(row);
|
|
@@ -11,6 +11,29 @@ var _chunkXSVNC2UDcjs = require('./chunk-XSVNC2UD.cjs');
|
|
|
11
11
|
|
|
12
12
|
// src/outbound-api/auditCapture.ts
|
|
13
13
|
var _crypto = require('crypto');
|
|
14
|
+
function completeUtf8PrefixLength(buf) {
|
|
15
|
+
if (buf.length === 0) return 0;
|
|
16
|
+
let leadIndex = buf.length - 1;
|
|
17
|
+
while (leadIndex >= 0 && (buf[leadIndex] & 192) === 128) leadIndex -= 1;
|
|
18
|
+
if (leadIndex < 0) return 0;
|
|
19
|
+
const lead = buf[leadIndex];
|
|
20
|
+
let expectedLength = 1;
|
|
21
|
+
if ((lead & 224) === 192) expectedLength = 2;
|
|
22
|
+
else if ((lead & 240) === 224) expectedLength = 3;
|
|
23
|
+
else if ((lead & 248) === 240) expectedLength = 4;
|
|
24
|
+
return buf.length - leadIndex < expectedLength ? leadIndex : buf.length;
|
|
25
|
+
}
|
|
26
|
+
function truncateToBytes(text, maxBytes) {
|
|
27
|
+
if (maxBytes < 0) return text;
|
|
28
|
+
const buf = Buffer.from(text, "utf8");
|
|
29
|
+
if (buf.length <= maxBytes) return text;
|
|
30
|
+
const head = buf.subarray(0, maxBytes);
|
|
31
|
+
return head.subarray(0, completeUtf8PrefixLength(head)).toString("utf8");
|
|
32
|
+
}
|
|
33
|
+
function decodeCapturedBody(body, truncated) {
|
|
34
|
+
if (!truncated) return body.toString("utf8");
|
|
35
|
+
return body.subarray(0, completeUtf8PrefixLength(body)).toString("utf8");
|
|
36
|
+
}
|
|
14
37
|
function resolveClientIp(req, trustForwardedFor) {
|
|
15
38
|
if (trustForwardedFor) {
|
|
16
39
|
const xff = req.headers["x-forwarded-for"];
|
|
@@ -25,18 +48,39 @@ function resolveClientIp(req, trustForwardedFor) {
|
|
|
25
48
|
function beginAuditCapture(req, res, now) {
|
|
26
49
|
const config = _chunkAEOZIDEBcjs.getAuditCaptureConfig.call(void 0, );
|
|
27
50
|
if (!config) return null;
|
|
28
|
-
let
|
|
51
|
+
let requestBody;
|
|
29
52
|
const responseChunks = [];
|
|
53
|
+
let responseBytes = 0;
|
|
54
|
+
let responseTruncated = false;
|
|
30
55
|
let finished = false;
|
|
31
56
|
const ctx = {
|
|
32
57
|
setRequestBody(raw) {
|
|
33
|
-
if (config.captureBodies)
|
|
58
|
+
if (config.captureBodies) {
|
|
59
|
+
requestBody = truncateToBytes(_chunkFCR77GYMcjs.redactAuditText.call(void 0, raw), config.maxBodyBytes);
|
|
60
|
+
}
|
|
34
61
|
}
|
|
35
62
|
};
|
|
36
63
|
if (config.captureBodies) {
|
|
37
64
|
installResponseCapture(res, {
|
|
38
65
|
push(chunk) {
|
|
66
|
+
if (config.maxBodyBytes < 0) {
|
|
67
|
+
responseChunks.push(chunk);
|
|
68
|
+
responseBytes += chunk.length;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const remaining = config.maxBodyBytes - responseBytes;
|
|
72
|
+
if (remaining <= 0) {
|
|
73
|
+
responseTruncated = true;
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (chunk.length > remaining) {
|
|
77
|
+
responseChunks.push(chunk.subarray(0, remaining));
|
|
78
|
+
responseBytes += remaining;
|
|
79
|
+
responseTruncated = true;
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
39
82
|
responseChunks.push(chunk);
|
|
83
|
+
responseBytes += chunk.length;
|
|
40
84
|
}
|
|
41
85
|
});
|
|
42
86
|
}
|
|
@@ -69,12 +113,13 @@ function beginAuditCapture(req, res, now) {
|
|
|
69
113
|
}
|
|
70
114
|
if (ctx.error) record.error = _chunkFCR77GYMcjs.redactAuditText.call(void 0, ctx.error);
|
|
71
115
|
if (config.captureBodies) {
|
|
72
|
-
if (
|
|
73
|
-
record.requestBody =
|
|
116
|
+
if (requestBody != null && requestBody.length > 0) {
|
|
117
|
+
record.requestBody = requestBody;
|
|
74
118
|
}
|
|
75
119
|
if (responseChunks.length > 0) {
|
|
76
|
-
const
|
|
77
|
-
|
|
120
|
+
const captured = Buffer.concat(responseChunks, responseBytes);
|
|
121
|
+
const body = decodeCapturedBody(captured, responseTruncated);
|
|
122
|
+
record.responseBody = truncateToBytes(_chunkFCR77GYMcjs.redactAuditText.call(void 0, body), config.maxBodyBytes);
|
|
78
123
|
}
|
|
79
124
|
}
|
|
80
125
|
_chunkAEOZIDEBcjs.recordAudit.call(void 0, record);
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// src/provider-proxy/identity/headerMerge.ts
|
|
2
|
+
function fillMissingHeaders(headers, source) {
|
|
3
|
+
const present = new Set(Object.keys(headers).map((k) => k.toLowerCase()));
|
|
4
|
+
for (const [key, value] of Object.entries(source)) {
|
|
5
|
+
const lower = key.toLowerCase();
|
|
6
|
+
if (present.has(lower)) continue;
|
|
7
|
+
headers[lower] = value;
|
|
8
|
+
present.add(lower);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function flattenHeaderValue(raw) {
|
|
12
|
+
if (raw === void 0) return void 0;
|
|
13
|
+
const value = Array.isArray(raw) ? raw.filter((v) => typeof v === "string").join(", ") : raw;
|
|
14
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
15
|
+
}
|
|
16
|
+
var NEVER_FORWARD_HEADERS = /* @__PURE__ */ new Set([
|
|
17
|
+
"authorization",
|
|
18
|
+
"x-api-key",
|
|
19
|
+
"cookie",
|
|
20
|
+
"proxy-authorization",
|
|
21
|
+
"host",
|
|
22
|
+
"connection",
|
|
23
|
+
"content-length",
|
|
24
|
+
"content-type",
|
|
25
|
+
"accept-encoding"
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
// src/provider-proxy/identity/codexCliHeaders.ts
|
|
29
|
+
var DEFAULT_CODEX_CLI_HEADERS = Object.freeze({
|
|
30
|
+
originator: "codex_cli_rs",
|
|
31
|
+
"user-agent": "codex_cli_rs/0.144.5",
|
|
32
|
+
version: "0.144.5"
|
|
33
|
+
});
|
|
34
|
+
var CODEX_FORWARD_ALLOWLIST = /* @__PURE__ */ new Set([
|
|
35
|
+
"version",
|
|
36
|
+
"openai-beta",
|
|
37
|
+
"session_id",
|
|
38
|
+
"originator",
|
|
39
|
+
"user-agent"
|
|
40
|
+
]);
|
|
41
|
+
function extractCodexClientHeaders(reqHeaders) {
|
|
42
|
+
const out = {};
|
|
43
|
+
for (const [rawKey, rawVal] of Object.entries(reqHeaders)) {
|
|
44
|
+
const key = rawKey.toLowerCase();
|
|
45
|
+
if (NEVER_FORWARD_HEADERS.has(key)) continue;
|
|
46
|
+
if (!CODEX_FORWARD_ALLOWLIST.has(key)) continue;
|
|
47
|
+
const value = flattenHeaderValue(rawVal);
|
|
48
|
+
if (value !== void 0) out[key] = value;
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
function codexAcceptHeader(isStream) {
|
|
53
|
+
return isStream ? "text/event-stream" : "application/json";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export {
|
|
57
|
+
fillMissingHeaders,
|
|
58
|
+
flattenHeaderValue,
|
|
59
|
+
NEVER_FORWARD_HEADERS,
|
|
60
|
+
DEFAULT_CODEX_CLI_HEADERS,
|
|
61
|
+
extractCodexClientHeaders,
|
|
62
|
+
codexAcceptHeader
|
|
63
|
+
};
|
|
@@ -3,6 +3,7 @@ var OVERLOAD_TTL_MS = 10 * 6e4;
|
|
|
3
3
|
var OVERLOAD_ENABLED_DEFAULT = true;
|
|
4
4
|
var AUTH_ERROR_TTL_MS = 30 * 6e4;
|
|
5
5
|
var SERVER_ERROR_TTL_MS = 5 * 6e4;
|
|
6
|
+
var MAX_QUOTA_COOLDOWN_MS = 7 * 24 * 60 * 6e4;
|
|
6
7
|
var BAN_BODY_MARKERS = [
|
|
7
8
|
"this organization has been disabled",
|
|
8
9
|
"oauth authentication is currently not allowed"
|
|
@@ -38,7 +39,7 @@ function isBanBody(bodyText) {
|
|
|
38
39
|
return BAN_BODY_MARKERS.some((marker) => lower.includes(marker));
|
|
39
40
|
}
|
|
40
41
|
function isRecordEmpty(record) {
|
|
41
|
-
return record.rateLimitEndAt === void 0 && record.overloadUntil === void 0 && record.tempUnavailableUntil === void 0 && !record.blocked;
|
|
42
|
+
return record.rateLimitEndAt === void 0 && record.overloadUntil === void 0 && record.tempUnavailableUntil === void 0 && record.quotaExhaustedUntil === void 0 && !record.blocked;
|
|
42
43
|
}
|
|
43
44
|
var KEY_SEP = "\0";
|
|
44
45
|
var SubscriptionAccountHealth = (_class = class {
|
|
@@ -159,12 +160,47 @@ var SubscriptionAccountHealth = (_class = class {
|
|
|
159
160
|
this.emitAnomaly(anomaly);
|
|
160
161
|
}
|
|
161
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* Mark an account quota/usage-exhausted until `untilEpochMs` (the Codex weekly
|
|
165
|
+
* usage-limit wall). Unlike the status-driven timers set by
|
|
166
|
+
* {@link recordUpstreamOutcome}, this mark is NOT cleared by a 2xx: in-flight
|
|
167
|
+
* sessions on the same account keep succeeding after the window is exhausted,
|
|
168
|
+
* so a traffic-driven clear would re-strand every NEW session onto this
|
|
169
|
+
* account. It excludes the account from NEW-session selection until the
|
|
170
|
+
* deadline elapses (existing sessions are unaffected — they already hold their
|
|
171
|
+
* token). Self-heals lazily on read and via {@link sweepRecoveries}.
|
|
172
|
+
* `untilEpochMs` is clamped to {@link MAX_QUOTA_COOLDOWN_MS}; an already-elapsed
|
|
173
|
+
* deadline is a no-op. Emits a `quota_exhausted` anomaly on the healthy→
|
|
174
|
+
* unhealthy edge.
|
|
175
|
+
*/
|
|
176
|
+
markQuotaExhausted(providerId, accountId, untilEpochMs, now = this.now()) {
|
|
177
|
+
if (!Number.isFinite(untilEpochMs)) return;
|
|
178
|
+
const clamped = Math.min(untilEpochMs, now + MAX_QUOTA_COOLDOWN_MS);
|
|
179
|
+
if (clamped <= now) return;
|
|
180
|
+
const key = this.key(providerId, accountId);
|
|
181
|
+
const preExisting = this.records.get(key);
|
|
182
|
+
const record = _nullishCoalesce(preExisting, () => ( {}));
|
|
183
|
+
const wasUnhealthyBefore = preExisting ? this.isUnhealthyAt(preExisting, now) : false;
|
|
184
|
+
record.quotaExhaustedUntil = clamped;
|
|
185
|
+
this.records.set(key, record);
|
|
186
|
+
if (!wasUnhealthyBefore) {
|
|
187
|
+
const anomaly = {
|
|
188
|
+
providerId,
|
|
189
|
+
accountId,
|
|
190
|
+
at: now,
|
|
191
|
+
state: "quota_exhausted"
|
|
192
|
+
};
|
|
193
|
+
this.recordDiagnostic({ kind: "health-anomaly", ...anomaly });
|
|
194
|
+
this.emitAnomaly(anomaly);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
162
197
|
/** Whether a record is unhealthy at `now`: blocked, or any timer still future. */
|
|
163
198
|
isUnhealthyAt(record, now) {
|
|
164
199
|
if (record.blocked) return true;
|
|
165
200
|
if (record.rateLimitEndAt !== void 0 && now < record.rateLimitEndAt) return true;
|
|
166
201
|
if (record.overloadUntil !== void 0 && now < record.overloadUntil) return true;
|
|
167
202
|
if (record.tempUnavailableUntil !== void 0 && now < record.tempUnavailableUntil) return true;
|
|
203
|
+
if (record.quotaExhaustedUntil !== void 0 && now < record.quotaExhaustedUntil) return true;
|
|
168
204
|
return false;
|
|
169
205
|
}
|
|
170
206
|
/**
|
|
@@ -201,7 +237,7 @@ var SubscriptionAccountHealth = (_class = class {
|
|
|
201
237
|
const recovered = [];
|
|
202
238
|
for (const [key, record] of this.records) {
|
|
203
239
|
if (record.blocked) continue;
|
|
204
|
-
const hadTimer = record.rateLimitEndAt !== void 0 || record.overloadUntil !== void 0 || record.tempUnavailableUntil !== void 0;
|
|
240
|
+
const hadTimer = record.rateLimitEndAt !== void 0 || record.overloadUntil !== void 0 || record.tempUnavailableUntil !== void 0 || record.quotaExhaustedUntil !== void 0;
|
|
205
241
|
const active = this.clearExpired(record, now);
|
|
206
242
|
if (hadTimer && !active && isRecordEmpty(record)) {
|
|
207
243
|
const [pid, aid] = this.parseKey(key);
|
|
@@ -225,6 +261,9 @@ var SubscriptionAccountHealth = (_class = class {
|
|
|
225
261
|
const record = this.records.get(this.key(providerId, accountId));
|
|
226
262
|
if (!record) return { state: "healthy" };
|
|
227
263
|
if (record.blocked) return { state: "blocked" };
|
|
264
|
+
if (record.quotaExhaustedUntil !== void 0 && now < record.quotaExhaustedUntil) {
|
|
265
|
+
return { state: "quota_exhausted", cooldownUntil: record.quotaExhaustedUntil };
|
|
266
|
+
}
|
|
228
267
|
if (record.rateLimitEndAt !== void 0 && now < record.rateLimitEndAt) {
|
|
229
268
|
return { state: "rate_limited", cooldownUntil: record.rateLimitEndAt };
|
|
230
269
|
}
|
|
@@ -311,6 +350,10 @@ var SubscriptionAccountHealth = (_class = class {
|
|
|
311
350
|
if (now >= record.tempUnavailableUntil) delete record.tempUnavailableUntil;
|
|
312
351
|
else active = true;
|
|
313
352
|
}
|
|
353
|
+
if (record.quotaExhaustedUntil !== void 0) {
|
|
354
|
+
if (now >= record.quotaExhaustedUntil) delete record.quotaExhaustedUntil;
|
|
355
|
+
else active = true;
|
|
356
|
+
}
|
|
314
357
|
return active;
|
|
315
358
|
}
|
|
316
359
|
key(providerId, accountId) {
|
|
@@ -344,4 +387,5 @@ function __resetSharedAccountHealthForTests() {
|
|
|
344
387
|
|
|
345
388
|
|
|
346
389
|
|
|
347
|
-
|
|
390
|
+
|
|
391
|
+
exports.OVERLOAD_TTL_MS = OVERLOAD_TTL_MS; exports.OVERLOAD_ENABLED_DEFAULT = OVERLOAD_ENABLED_DEFAULT; exports.AUTH_ERROR_TTL_MS = AUTH_ERROR_TTL_MS; exports.SERVER_ERROR_TTL_MS = SERVER_ERROR_TTL_MS; exports.MAX_QUOTA_COOLDOWN_MS = MAX_QUOTA_COOLDOWN_MS; exports.ACCOUNT_HEALTH_DIAGNOSTIC_LIMIT = ACCOUNT_HEALTH_DIAGNOSTIC_LIMIT; exports.resolveResetSeconds = resolveResetSeconds; exports.SubscriptionAccountHealth = SubscriptionAccountHealth; exports.getSharedAccountHealth = getSharedAccountHealth; exports.setSharedAccountHealth = setSharedAccountHealth; exports.__resetSharedAccountHealthForTests = __resetSharedAccountHealthForTests;
|
|
@@ -172,6 +172,37 @@ var ApiKeyPoolService = (_class = class {
|
|
|
172
172
|
const keys = await this.getAllKeys(providerId);
|
|
173
173
|
return keys.length > 0;
|
|
174
174
|
}
|
|
175
|
+
/** Secret-free strict preflight for a named provider key. */
|
|
176
|
+
async hasUsableKey(providerId, keyId) {
|
|
177
|
+
const keys = await this.getAvailableKeys(providerId);
|
|
178
|
+
return keys.some((key) => key.id === keyId);
|
|
179
|
+
}
|
|
180
|
+
/** Secret-free existence/availability split for strict Route Lease targets. */
|
|
181
|
+
async getKeyAvailability(providerId, keyId) {
|
|
182
|
+
const all = await this.getAllKeys(providerId);
|
|
183
|
+
if (!all.some((key) => key.id === keyId)) return "not-found";
|
|
184
|
+
return (await this.getAvailableKeys(providerId)).some((key) => key.id === keyId) ? "usable" : "unavailable";
|
|
185
|
+
}
|
|
186
|
+
/** Secret-free preflight for at least one enabled, non-cooling pool key. */
|
|
187
|
+
async hasUsableKeys(providerId) {
|
|
188
|
+
return (await this.getAvailableKeys(providerId)).length > 0;
|
|
189
|
+
}
|
|
190
|
+
/** Preserve pool exhaustion and its earliest safe retry horizon for Route Lease. */
|
|
191
|
+
async getPoolAvailability(providerId) {
|
|
192
|
+
const all = await this.getAllKeys(providerId);
|
|
193
|
+
if (all.length === 0) return { outcome: "empty" };
|
|
194
|
+
if ((await this.getAvailableKeys(providerId)).length > 0) return { outcome: "usable" };
|
|
195
|
+
const now = Date.now();
|
|
196
|
+
const coolingUntil = all.filter((key) => key.enabled).map((key) => _nullishCoalesce(_optionalChain([this, 'access', _ => _.cooldowns, 'access', _2 => _2.get, 'call', _3 => _3(key.id), 'optionalAccess', _4 => _4.until]), () => ( 0))).filter((until) => until > now);
|
|
197
|
+
if (coolingUntil.length > 0) {
|
|
198
|
+
const earliest = Math.min(...coolingUntil);
|
|
199
|
+
return {
|
|
200
|
+
outcome: "exhausted",
|
|
201
|
+
retryAfterSeconds: Math.max(1, Math.min(3600, Math.ceil((earliest - now) / 1e3)))
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
return { outcome: "unavailable" };
|
|
205
|
+
}
|
|
175
206
|
/**
|
|
176
207
|
* Get the live (in-memory) rate-limit cooldown health for a provider's keys.
|
|
177
208
|
*
|
|
@@ -240,7 +271,7 @@ var ApiKeyPoolService = (_class = class {
|
|
|
240
271
|
*/
|
|
241
272
|
applyCooldown(keyId, providerId, statusCode) {
|
|
242
273
|
const current = this.cooldowns.get(keyId);
|
|
243
|
-
const errors = (_nullishCoalesce(_optionalChain([current, 'optionalAccess',
|
|
274
|
+
const errors = (_nullishCoalesce(_optionalChain([current, 'optionalAccess', _5 => _5.errors]), () => ( 0))) + 1;
|
|
244
275
|
const cooldownMs = Math.min(
|
|
245
276
|
this.DEFAULT_COOLDOWN_MS * Math.pow(this.COOLDOWN_MULTIPLIER, errors - 1),
|
|
246
277
|
this.MAX_COOLDOWN_MS
|
|
@@ -20,6 +20,14 @@ interface KeyHealthEntry {
|
|
|
20
20
|
}
|
|
21
21
|
/** Provider-scoped map of keyId → live cooldown health (cooling keys only). */
|
|
22
22
|
type KeyHealthMap = Record<string, KeyHealthEntry>;
|
|
23
|
+
type ProviderKeyPoolAvailability = {
|
|
24
|
+
readonly outcome: 'usable';
|
|
25
|
+
} | {
|
|
26
|
+
readonly outcome: 'empty' | 'unavailable';
|
|
27
|
+
} | {
|
|
28
|
+
readonly outcome: 'exhausted';
|
|
29
|
+
readonly retryAfterSeconds: number;
|
|
30
|
+
};
|
|
23
31
|
/** Function type for loading API keys from the database */
|
|
24
32
|
type ApiKeysLoader = (providerId: string) => Promise<ApiKeyEntry[]>;
|
|
25
33
|
/** Function type for disabling a key in the database (on auth failure) */
|
|
@@ -111,6 +119,14 @@ declare class ApiKeyPoolService {
|
|
|
111
119
|
* Check if a provider has any keys in the pool.
|
|
112
120
|
*/
|
|
113
121
|
hasKeys(providerId: string): Promise<boolean>;
|
|
122
|
+
/** Secret-free strict preflight for a named provider key. */
|
|
123
|
+
hasUsableKey(providerId: string, keyId: string): Promise<boolean>;
|
|
124
|
+
/** Secret-free existence/availability split for strict Route Lease targets. */
|
|
125
|
+
getKeyAvailability(providerId: string, keyId: string): Promise<'usable' | 'unavailable' | 'not-found'>;
|
|
126
|
+
/** Secret-free preflight for at least one enabled, non-cooling pool key. */
|
|
127
|
+
hasUsableKeys(providerId: string): Promise<boolean>;
|
|
128
|
+
/** Preserve pool exhaustion and its earliest safe retry horizon for Route Lease. */
|
|
129
|
+
getPoolAvailability(providerId: string): Promise<ProviderKeyPoolAvailability>;
|
|
114
130
|
/**
|
|
115
131
|
* Get the live (in-memory) rate-limit cooldown health for a provider's keys.
|
|
116
132
|
*
|
|
@@ -153,4 +169,4 @@ declare class ApiKeyPoolService {
|
|
|
153
169
|
private cleanupExpiredCooldowns;
|
|
154
170
|
}
|
|
155
171
|
|
|
156
|
-
export { type ApiKeyAutoDisabler, type ApiKeyDisabler, ApiKeyPoolService, type ApiKeyResolver, type ApiKeysLoader, type KeyHealthEntry, type KeyHealthMap };
|
|
172
|
+
export { type ApiKeyAutoDisabler, type ApiKeyDisabler, ApiKeyPoolService, type ApiKeyResolver, type ApiKeysLoader, type KeyHealthEntry, type KeyHealthMap, type ProviderKeyPoolAvailability };
|
|
@@ -20,6 +20,14 @@ interface KeyHealthEntry {
|
|
|
20
20
|
}
|
|
21
21
|
/** Provider-scoped map of keyId → live cooldown health (cooling keys only). */
|
|
22
22
|
type KeyHealthMap = Record<string, KeyHealthEntry>;
|
|
23
|
+
type ProviderKeyPoolAvailability = {
|
|
24
|
+
readonly outcome: 'usable';
|
|
25
|
+
} | {
|
|
26
|
+
readonly outcome: 'empty' | 'unavailable';
|
|
27
|
+
} | {
|
|
28
|
+
readonly outcome: 'exhausted';
|
|
29
|
+
readonly retryAfterSeconds: number;
|
|
30
|
+
};
|
|
23
31
|
/** Function type for loading API keys from the database */
|
|
24
32
|
type ApiKeysLoader = (providerId: string) => Promise<ApiKeyEntry[]>;
|
|
25
33
|
/** Function type for disabling a key in the database (on auth failure) */
|
|
@@ -111,6 +119,14 @@ declare class ApiKeyPoolService {
|
|
|
111
119
|
* Check if a provider has any keys in the pool.
|
|
112
120
|
*/
|
|
113
121
|
hasKeys(providerId: string): Promise<boolean>;
|
|
122
|
+
/** Secret-free strict preflight for a named provider key. */
|
|
123
|
+
hasUsableKey(providerId: string, keyId: string): Promise<boolean>;
|
|
124
|
+
/** Secret-free existence/availability split for strict Route Lease targets. */
|
|
125
|
+
getKeyAvailability(providerId: string, keyId: string): Promise<'usable' | 'unavailable' | 'not-found'>;
|
|
126
|
+
/** Secret-free preflight for at least one enabled, non-cooling pool key. */
|
|
127
|
+
hasUsableKeys(providerId: string): Promise<boolean>;
|
|
128
|
+
/** Preserve pool exhaustion and its earliest safe retry horizon for Route Lease. */
|
|
129
|
+
getPoolAvailability(providerId: string): Promise<ProviderKeyPoolAvailability>;
|
|
114
130
|
/**
|
|
115
131
|
* Get the live (in-memory) rate-limit cooldown health for a provider's keys.
|
|
116
132
|
*
|
|
@@ -153,4 +169,4 @@ declare class ApiKeyPoolService {
|
|
|
153
169
|
private cleanupExpiredCooldowns;
|
|
154
170
|
}
|
|
155
171
|
|
|
156
|
-
export { type ApiKeyAutoDisabler, type ApiKeyDisabler, ApiKeyPoolService, type ApiKeyResolver, type ApiKeysLoader, type KeyHealthEntry, type KeyHealthMap };
|
|
172
|
+
export { type ApiKeyAutoDisabler, type ApiKeyDisabler, ApiKeyPoolService, type ApiKeyResolver, type ApiKeysLoader, type KeyHealthEntry, type KeyHealthMap, type ProviderKeyPoolAvailability };
|
|
@@ -172,6 +172,37 @@ var ApiKeyPoolService = class {
|
|
|
172
172
|
const keys = await this.getAllKeys(providerId);
|
|
173
173
|
return keys.length > 0;
|
|
174
174
|
}
|
|
175
|
+
/** Secret-free strict preflight for a named provider key. */
|
|
176
|
+
async hasUsableKey(providerId, keyId) {
|
|
177
|
+
const keys = await this.getAvailableKeys(providerId);
|
|
178
|
+
return keys.some((key) => key.id === keyId);
|
|
179
|
+
}
|
|
180
|
+
/** Secret-free existence/availability split for strict Route Lease targets. */
|
|
181
|
+
async getKeyAvailability(providerId, keyId) {
|
|
182
|
+
const all = await this.getAllKeys(providerId);
|
|
183
|
+
if (!all.some((key) => key.id === keyId)) return "not-found";
|
|
184
|
+
return (await this.getAvailableKeys(providerId)).some((key) => key.id === keyId) ? "usable" : "unavailable";
|
|
185
|
+
}
|
|
186
|
+
/** Secret-free preflight for at least one enabled, non-cooling pool key. */
|
|
187
|
+
async hasUsableKeys(providerId) {
|
|
188
|
+
return (await this.getAvailableKeys(providerId)).length > 0;
|
|
189
|
+
}
|
|
190
|
+
/** Preserve pool exhaustion and its earliest safe retry horizon for Route Lease. */
|
|
191
|
+
async getPoolAvailability(providerId) {
|
|
192
|
+
const all = await this.getAllKeys(providerId);
|
|
193
|
+
if (all.length === 0) return { outcome: "empty" };
|
|
194
|
+
if ((await this.getAvailableKeys(providerId)).length > 0) return { outcome: "usable" };
|
|
195
|
+
const now = Date.now();
|
|
196
|
+
const coolingUntil = all.filter((key) => key.enabled).map((key) => this.cooldowns.get(key.id)?.until ?? 0).filter((until) => until > now);
|
|
197
|
+
if (coolingUntil.length > 0) {
|
|
198
|
+
const earliest = Math.min(...coolingUntil);
|
|
199
|
+
return {
|
|
200
|
+
outcome: "exhausted",
|
|
201
|
+
retryAfterSeconds: Math.max(1, Math.min(3600, Math.ceil((earliest - now) / 1e3)))
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
return { outcome: "unavailable" };
|
|
205
|
+
}
|
|
175
206
|
/**
|
|
176
207
|
* Get the live (in-memory) rate-limit cooldown health for a provider's keys.
|
|
177
208
|
*
|