@omnicross/core 0.1.5 → 0.1.6
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/{CompletionService-DdWaAxq0.d.ts → CompletionService-C3BX0Ovy.d.ts} +4 -3
- package/dist/{CompletionService-D3a47vdU.d.cts → CompletionService-kNA17cfI.d.cts} +4 -3
- package/dist/auth/GeminiCodeAssistProjectResolver.cjs +6 -5
- package/dist/auth/GeminiCodeAssistProjectResolver.js +4 -3
- package/dist/{chunk-HRRYG2AX.js → chunk-23S5OPIO.js} +10 -1
- package/dist/chunk-25GXJCEZ.cjs +40 -0
- package/dist/chunk-2JYRIADS.js +40 -0
- package/dist/{chunk-4XMYPW46.cjs → chunk-4VLRHHFE.cjs} +75 -33
- package/dist/{chunk-VACWOIRE.cjs → chunk-4VZX5F4T.cjs} +39 -15
- package/dist/{chunk-C6OV7GCC.js → chunk-6CL5HTPQ.js} +44 -2
- package/dist/chunk-6QQG7YG3.cjs +153 -0
- package/dist/{chunk-3Q6Z6JTO.cjs → chunk-6VUQPOV3.cjs} +277 -86
- package/dist/chunk-7AK7B36B.cjs +44 -0
- package/dist/{chunk-5ZQBEOMD.js → chunk-AT7B7SOZ.js} +25 -2
- package/dist/{chunk-IK3KE3AU.js → chunk-DE2SRBFZ.js} +1 -1
- package/dist/chunk-DLXZEZWO.cjs +285 -0
- package/dist/{chunk-UHGVBFU3.js → chunk-EBLFCNPY.js} +38 -14
- package/dist/chunk-EJLH6NFT.cjs +166 -0
- package/dist/chunk-HVBBDBS3.cjs +350 -0
- package/dist/{chunk-46ZIFWYG.cjs → chunk-IIRPUVPH.cjs} +7 -1
- package/dist/{chunk-JWVTXXPX.cjs → chunk-KBM2W6UY.cjs} +8 -4
- package/dist/{chunk-PG7PS2NN.cjs → chunk-KCVJWJST.cjs} +14 -2
- package/dist/chunk-LB6EBS6Y.js +69 -0
- package/dist/{chunk-IWQAX3CS.js → chunk-LVUCAWMK.js} +1 -1
- package/dist/chunk-MKF46GNZ.js +350 -0
- package/dist/{chunk-HYN75H6G.cjs → chunk-MUHWHQVM.cjs} +1 -1
- package/dist/{chunk-MFZ5437A.js → chunk-N5T4M4GU.js} +1 -1
- package/dist/{chunk-BODMAL6G.js → chunk-OEWZUX4G.js} +262 -71
- package/dist/chunk-OF3BECU2.cjs +69 -0
- package/dist/{chunk-DDMRUL76.cjs → chunk-OKMJLEXX.cjs} +5 -5
- package/dist/chunk-OPRS4IQZ.js +153 -0
- package/dist/{chunk-D3TDKWRP.js → chunk-SIZQQD6T.js} +14 -2
- package/dist/{chunk-CGKLE6IW.cjs → chunk-SQYVORNP.cjs} +1219 -510
- package/dist/{chunk-6QZGDLL4.js → chunk-TOVPMMHM.js} +5 -1
- package/dist/{chunk-FH2P4OCD.js → chunk-UDCC7EMR.js} +976 -267
- package/dist/{chunk-ZLX3KGXX.js → chunk-UX7VWA46.js} +135 -23
- package/dist/{chunk-K52THW2C.cjs → chunk-V4TXMEJS.cjs} +5 -5
- package/dist/chunk-VRFBB5DB.js +44 -0
- package/dist/chunk-WB6PH56J.js +166 -0
- package/dist/{chunk-G2UTNSGU.js → chunk-WNKWAEUR.js} +7 -1
- package/dist/{chunk-SUEGJ75D.cjs → chunk-WYTEQ4Q5.cjs} +25 -2
- package/dist/{chunk-5ENKBSWO.js → chunk-YJVHCHQX.js} +42 -6
- package/dist/{chunk-VQY5W4YW.cjs → chunk-YK57NMIF.cjs} +44 -8
- package/dist/{chunk-KFI44N2R.cjs → chunk-YNJNTCSQ.cjs} +10 -1
- package/dist/completion/ApiKeyPoolService.cjs +12 -0
- package/dist/completion/ApiKeyPoolService.d.cts +6 -0
- package/dist/completion/ApiKeyPoolService.d.ts +6 -0
- package/dist/completion/ApiKeyPoolService.js +12 -0
- package/dist/completion/CompletionService.cjs +25 -23
- package/dist/completion/CompletionService.d.cts +2 -1
- package/dist/completion/CompletionService.d.ts +2 -1
- package/dist/completion/CompletionService.js +24 -22
- package/dist/completion.cjs +25 -23
- package/dist/completion.d.cts +2 -1
- package/dist/completion.d.ts +2 -1
- package/dist/completion.js +24 -22
- package/dist/index.cjs +49 -26
- package/dist/index.d.cts +8 -8
- package/dist/index.d.ts +8 -8
- package/dist/index.js +52 -29
- package/dist/{keyPolicy-Cp0FntZl.d.ts → keyPolicy-zdWTu3CQ.d.cts} +1 -1
- package/dist/{keyPolicy-Cp0FntZl.d.cts → keyPolicy-zdWTu3CQ.d.ts} +1 -1
- package/dist/outbound-api/quotaWarn.d.cts +1 -1
- package/dist/outbound-api/quotaWarn.d.ts +1 -1
- package/dist/outbound-api/routeResolver.cjs +2 -2
- package/dist/outbound-api/routeResolver.d.cts +4 -3
- package/dist/outbound-api/routeResolver.d.ts +4 -3
- package/dist/outbound-api/routeResolver.js +1 -1
- 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 +47 -24
- package/dist/outbound-api.d.cts +127 -85
- package/dist/outbound-api.d.ts +127 -85
- package/dist/outbound-api.js +50 -27
- package/dist/pipeline/AccountAllowanceScheduling.cjs +15 -0
- package/dist/pipeline/AccountAllowanceScheduling.d.cts +90 -0
- package/dist/pipeline/AccountAllowanceScheduling.d.ts +90 -0
- package/dist/pipeline/AccountAllowanceScheduling.js +15 -0
- package/dist/pipeline/AccountAllowanceStore.cjs +20 -0
- package/dist/pipeline/AccountAllowanceStore.d.cts +77 -0
- package/dist/pipeline/AccountAllowanceStore.d.ts +77 -0
- package/dist/pipeline/AccountAllowanceStore.js +20 -0
- package/dist/pipeline/AuthSource.d.cts +11 -2
- package/dist/pipeline/AuthSource.d.ts +11 -2
- package/dist/pipeline/BoundAccountSelectionError.cjs +10 -0
- package/dist/pipeline/BoundAccountSelectionError.d.cts +28 -0
- package/dist/pipeline/BoundAccountSelectionError.d.ts +28 -0
- package/dist/pipeline/BoundAccountSelectionError.js +10 -0
- package/dist/pipeline/LlmConfigProviderAuth.d.cts +1 -0
- package/dist/pipeline/LlmConfigProviderAuth.d.ts +1 -0
- package/dist/pipeline/SubscriptionAccountHealth.cjs +4 -2
- package/dist/pipeline/SubscriptionAccountHealth.d.cts +34 -6
- package/dist/pipeline/SubscriptionAccountHealth.d.ts +34 -6
- package/dist/pipeline/SubscriptionAccountHealth.js +3 -1
- package/dist/pipeline/SubscriptionAuthSource.cjs +5 -2
- package/dist/pipeline/SubscriptionAuthSource.d.cts +1 -0
- package/dist/pipeline/SubscriptionAuthSource.d.ts +1 -0
- package/dist/pipeline/SubscriptionAuthSource.js +4 -1
- package/dist/pipeline/SubscriptionAuthStrategy.d.cts +8 -3
- package/dist/pipeline/SubscriptionAuthStrategy.d.ts +8 -3
- package/dist/pipeline/executeProviderCall.cjs +2 -2
- package/dist/pipeline/executeProviderCall.d.cts +8 -3
- package/dist/pipeline/executeProviderCall.d.ts +8 -3
- package/dist/pipeline/executeProviderCall.js +1 -1
- package/dist/pipeline/resolveProviderChain.d.cts +2 -3
- package/dist/pipeline/resolveProviderChain.d.ts +2 -3
- package/dist/pipeline/resolveSubscriptionChain.d.cts +1 -0
- package/dist/pipeline/resolveSubscriptionChain.d.ts +1 -0
- package/dist/pipeline/upstreamFetch.cjs +3 -2
- package/dist/pipeline/upstreamFetch.js +2 -1
- package/dist/ports/pricing-store.d.cts +4 -3
- package/dist/ports/pricing-store.d.ts +4 -3
- package/dist/ports.d.cts +4 -3
- package/dist/ports.d.ts +4 -3
- package/dist/provider-proxy/ProviderProxy.cjs +25 -23
- package/dist/provider-proxy/ProviderProxy.d.cts +1 -0
- package/dist/provider-proxy/ProviderProxy.d.ts +1 -0
- package/dist/provider-proxy/ProviderProxy.js +24 -22
- package/dist/provider-proxy/ingress/providerProxyShared.cjs +27 -23
- package/dist/provider-proxy/ingress/providerProxyShared.d.cts +34 -5
- package/dist/provider-proxy/ingress/providerProxyShared.d.ts +34 -5
- package/dist/provider-proxy/ingress/providerProxyShared.js +26 -22
- package/dist/provider-proxy/matchText.cjs +4 -2
- package/dist/provider-proxy/matchText.d.cts +41 -6
- package/dist/provider-proxy/matchText.d.ts +41 -6
- package/dist/provider-proxy/matchText.js +3 -1
- package/dist/provider-proxy/types.d.cts +30 -2
- package/dist/provider-proxy/types.d.ts +30 -2
- package/dist/provider-proxy.cjs +25 -23
- package/dist/provider-proxy.d.cts +1 -0
- package/dist/provider-proxy.d.ts +1 -0
- package/dist/provider-proxy.js +24 -22
- package/dist/{routeResolver-C4T7yClx.d.ts → routeResolver-CHTv_Bs5.d.ts} +1 -1
- package/dist/{routeResolver-CB3tvJKy.d.cts → routeResolver-kepxnfcC.d.cts} +1 -1
- package/dist/transformer/TransformerChainExecutor.cjs +2 -2
- package/dist/transformer/TransformerChainExecutor.js +1 -1
- package/dist/transformer/transformers/AnthropicTransformer.cjs +2 -2
- package/dist/transformer/transformers/AnthropicTransformer.js +1 -1
- package/dist/transformer/transformers/GeminiCodeAssistTransformer.cjs +3 -3
- package/dist/transformer/transformers/GeminiCodeAssistTransformer.js +2 -2
- package/dist/transformer/transformers/GeminiTransformer.cjs +3 -3
- package/dist/transformer/transformers/GeminiTransformer.js +2 -2
- package/dist/transformer/transformers/OpenAIResponseTransformer.cjs +2 -2
- package/dist/transformer/transformers/OpenAIResponseTransformer.d.cts +1 -1
- package/dist/transformer/transformers/OpenAIResponseTransformer.d.ts +1 -1
- package/dist/transformer/transformers/OpenAIResponseTransformer.js +1 -1
- package/dist/transformer/transformers/OpenAITransformer.cjs +6 -0
- package/dist/transformer/transformers/OpenAITransformer.d.cts +68 -0
- package/dist/transformer/transformers/OpenAITransformer.d.ts +68 -0
- package/dist/transformer/transformers/OpenAITransformer.js +6 -0
- package/dist/transformer/transformers.cjs +8 -14
- package/dist/transformer/transformers.d.cts +12 -48
- package/dist/transformer/transformers.d.ts +12 -48
- package/dist/transformer/transformers.js +12 -18
- package/dist/transformer/types.d.cts +17 -0
- package/dist/transformer/types.d.ts +17 -0
- package/dist/transformer.cjs +10 -11
- package/dist/transformer.d.cts +1 -2
- package/dist/transformer.d.ts +1 -2
- package/dist/transformer.js +9 -10
- package/dist/{types-B8Arpxn0.d.cts → types-H3wGZSOo.d.cts} +134 -7
- package/dist/{types-lgPYQuZS.d.ts → types-hsoKrVQS.d.ts} +134 -7
- package/dist/usage/pricing-engine.cjs +2 -2
- package/dist/usage/pricing-engine.d.cts +9 -1
- package/dist/usage/pricing-engine.d.ts +9 -1
- package/dist/usage/pricing-engine.js +1 -1
- package/dist/usage.cjs +2 -2
- package/dist/usage.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-7FEBOPJO.cjs +0 -50
- package/dist/chunk-DWKQZ2SK.cjs +0 -254
- package/dist/chunk-IAXWICZU.js +0 -69
- package/dist/chunk-IB3FDI4O.js +0 -50
- package/dist/chunk-OQWKZCXP.js +0 -242
- package/dist/chunk-QWBWGODS.cjs +0 -69
- package/dist/chunk-YMFAJFQT.cjs +0 -173
- package/dist/chunk-ZCIQ3L3R.cjs +0 -242
- package/dist/chunk-ZJZAOKI7.js +0 -254
- package/dist/transformer/transformers/OpenCodeGoTransformer.cjs +0 -6
- package/dist/transformer/transformers/OpenCodeGoTransformer.d.cts +0 -51
- package/dist/transformer/transformers/OpenCodeGoTransformer.d.ts +0 -51
- package/dist/transformer/transformers/OpenCodeGoTransformer.js +0 -6
- package/dist/transformer/transformers/ReasoningTransformer.cjs +0 -6
- package/dist/transformer/transformers/ReasoningTransformer.d.cts +0 -47
- package/dist/transformer/transformers/ReasoningTransformer.d.ts +0 -47
- package/dist/transformer/transformers/ReasoningTransformer.js +0 -6
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
// src/usage/pricing-engine.ts
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_LITELLM_PRICING_URL,
|
|
4
|
+
DEFAULT_OPENROUTER_PRICING_URL
|
|
5
|
+
} from "@omnicross/contracts/pricing-types";
|
|
3
6
|
var PricingEngine = class {
|
|
4
7
|
constructor(store, logger, options = {}) {
|
|
5
8
|
this.store = store;
|
|
@@ -19,6 +22,8 @@ var PricingEngine = class {
|
|
|
19
22
|
*/
|
|
20
23
|
cacheByModel = /* @__PURE__ */ new Map();
|
|
21
24
|
cacheLoaded = false;
|
|
25
|
+
/** Coalesce manual and background refreshes to prevent a catalog stampede. */
|
|
26
|
+
refreshInFlight = null;
|
|
22
27
|
// ===== Cache management =====
|
|
23
28
|
/** Lazy-load and cache the full pricing table. */
|
|
24
29
|
async ensureCache() {
|
|
@@ -27,7 +32,7 @@ var PricingEngine = class {
|
|
|
27
32
|
this.cache = new Map(rows.map((r) => [keyOf(r.providerId, r.modelId), r]));
|
|
28
33
|
this.cacheByModel = /* @__PURE__ */ new Map();
|
|
29
34
|
for (const r of rows) {
|
|
30
|
-
if (r.providerId === "*") continue;
|
|
35
|
+
if (r.providerId === "*" || r.providerId === "openrouter" || r.source === "openrouter") continue;
|
|
31
36
|
if (!this.cacheByModel.has(r.modelId)) this.cacheByModel.set(r.modelId, r);
|
|
32
37
|
}
|
|
33
38
|
this.cacheLoaded = true;
|
|
@@ -90,40 +95,82 @@ var PricingEngine = class {
|
|
|
90
95
|
true
|
|
91
96
|
);
|
|
92
97
|
this.cache.set(keyOf(updated.providerId, updated.modelId), updated);
|
|
93
|
-
if (updated.providerId !== "*"
|
|
98
|
+
if (updated.providerId !== "*" && updated.providerId !== "openrouter" && updated.source !== "openrouter") {
|
|
99
|
+
this.cacheByModel.set(updated.modelId, updated);
|
|
100
|
+
}
|
|
94
101
|
return updated;
|
|
95
102
|
}
|
|
96
103
|
// ===== Source refresh =====
|
|
97
104
|
async fetchLatestFromSource() {
|
|
105
|
+
if (this.refreshInFlight) return this.refreshInFlight;
|
|
106
|
+
const refresh = this.refreshSources();
|
|
107
|
+
this.refreshInFlight = refresh;
|
|
108
|
+
try {
|
|
109
|
+
return await refresh;
|
|
110
|
+
} finally {
|
|
111
|
+
if (this.refreshInFlight === refresh) this.refreshInFlight = null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async refreshSources() {
|
|
98
115
|
const sourceUrl = this.options.sourceUrl ?? DEFAULT_LITELLM_PRICING_URL;
|
|
116
|
+
const openRouterSourceUrl = this.options.openRouterSourceUrl ?? DEFAULT_OPENROUTER_PRICING_URL;
|
|
99
117
|
const fetchImpl = this.options.fetchImpl ?? fetch;
|
|
118
|
+
const timeoutMs = this.options.fetchTimeoutMs ?? 1e4;
|
|
100
119
|
const fetchedAt = Date.now();
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
120
|
+
const [litellm, openrouter] = await Promise.all([
|
|
121
|
+
fetchCatalog(fetchImpl, sourceUrl, timeoutMs, parseLitellmJson),
|
|
122
|
+
fetchCatalog(fetchImpl, openRouterSourceUrl, timeoutMs, parseOpenRouterJson)
|
|
123
|
+
]);
|
|
124
|
+
const sources = [
|
|
125
|
+
sourceResult("litellm", sourceUrl, litellm),
|
|
126
|
+
sourceResult("openrouter", openRouterSourceUrl, openrouter)
|
|
127
|
+
];
|
|
128
|
+
for (const source of sources) {
|
|
129
|
+
if (source.status === "failed") {
|
|
130
|
+
this.logger.warn(`[PricingEngine] ${source.source} pricing-source fetch failed`, {
|
|
131
|
+
error: source.error
|
|
132
|
+
});
|
|
106
133
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
134
|
+
}
|
|
135
|
+
if (!litellm.ok && !openrouter.ok) {
|
|
136
|
+
throw new Error(
|
|
137
|
+
`Pricing-source fetch failed: LiteLLM (${litellm.error}); OpenRouter (${openrouter.error})`
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
const applied = [];
|
|
141
|
+
const conflicts = [];
|
|
142
|
+
if (litellm.ok) {
|
|
143
|
+
const result = await this.store.bulkApplyFromSource(litellm.entries, "litellm");
|
|
144
|
+
applied.push(...result.applied);
|
|
145
|
+
conflicts.push(
|
|
146
|
+
...result.conflicts.map((c) => ({
|
|
147
|
+
providerId: c.current.providerId,
|
|
148
|
+
modelId: c.current.modelId,
|
|
149
|
+
current: c.current,
|
|
150
|
+
incoming: c.incoming
|
|
151
|
+
}))
|
|
152
|
+
);
|
|
153
|
+
sources[0].appliedCount = result.applied.length;
|
|
154
|
+
}
|
|
155
|
+
if (openrouter.ok) {
|
|
156
|
+
const current = new Map(
|
|
157
|
+
(await this.store.getAll()).map((row) => [keyOf(row.providerId, row.modelId), row])
|
|
158
|
+
);
|
|
159
|
+
const supplemental = openrouter.entries.filter((incoming) => {
|
|
160
|
+
const row = current.get(keyOf(incoming.providerId, incoming.modelId));
|
|
161
|
+
return !row || row.source === "openrouter" || row.source === "builtin";
|
|
111
162
|
});
|
|
112
|
-
|
|
163
|
+
const result = await this.store.bulkApplyFromSource(supplemental, "openrouter");
|
|
164
|
+
applied.push(...result.applied);
|
|
165
|
+
sources[1].appliedCount = result.applied.length;
|
|
113
166
|
}
|
|
114
|
-
|
|
115
|
-
const { applied, conflicts } = await this.store.bulkApplyFromSource(incoming);
|
|
116
|
-
await this.invalidateCache();
|
|
167
|
+
if (applied.length > 0) await this.invalidateCache();
|
|
117
168
|
return {
|
|
118
169
|
applied,
|
|
119
|
-
conflicts
|
|
120
|
-
providerId: c.current.providerId,
|
|
121
|
-
modelId: c.current.modelId,
|
|
122
|
-
current: c.current,
|
|
123
|
-
incoming: c.incoming
|
|
124
|
-
})),
|
|
170
|
+
conflicts,
|
|
125
171
|
fetchedAt,
|
|
126
|
-
sourceUrl
|
|
172
|
+
sourceUrl,
|
|
173
|
+
sources
|
|
127
174
|
};
|
|
128
175
|
}
|
|
129
176
|
// ===== Conflict resolution =====
|
|
@@ -147,8 +194,12 @@ var PROVIDER_ALIASES = {
|
|
|
147
194
|
azure: "openai"
|
|
148
195
|
};
|
|
149
196
|
var parseLitellmJson = (json) => {
|
|
197
|
+
if (!json || typeof json !== "object" || Array.isArray(json)) {
|
|
198
|
+
throw new Error("invalid LiteLLM catalog shape");
|
|
199
|
+
}
|
|
150
200
|
const out = [];
|
|
151
201
|
for (const [modelId, entry] of Object.entries(json)) {
|
|
202
|
+
if (modelId === "sample_spec") continue;
|
|
152
203
|
if (!entry || typeof entry !== "object") continue;
|
|
153
204
|
if (entry.input_cost_per_token == null || entry.output_cost_per_token == null) {
|
|
154
205
|
continue;
|
|
@@ -167,6 +218,67 @@ var parseLitellmJson = (json) => {
|
|
|
167
218
|
}
|
|
168
219
|
return out;
|
|
169
220
|
};
|
|
221
|
+
var parseOpenRouterJson = (json) => {
|
|
222
|
+
if (!json || !Array.isArray(json.data)) {
|
|
223
|
+
throw new Error("invalid OpenRouter catalog shape");
|
|
224
|
+
}
|
|
225
|
+
const out = [];
|
|
226
|
+
for (const model of json.data) {
|
|
227
|
+
const modelId = typeof model?.id === "string" ? model.id.trim() : "";
|
|
228
|
+
const input = parseUnitPrice(model?.pricing?.prompt);
|
|
229
|
+
const output = parseUnitPrice(model?.pricing?.completion);
|
|
230
|
+
if (!modelId || input === null || output === null) continue;
|
|
231
|
+
const cacheRead = parseUnitPrice(model.pricing?.input_cache_read);
|
|
232
|
+
const cacheWrite = parseUnitPrice(model.pricing?.input_cache_write);
|
|
233
|
+
out.push({
|
|
234
|
+
providerId: "openrouter",
|
|
235
|
+
modelId,
|
|
236
|
+
inputPricePer1m: input * 1e6,
|
|
237
|
+
outputPricePer1m: output * 1e6,
|
|
238
|
+
cacheReadPricePer1m: cacheRead === null ? null : cacheRead * 1e6,
|
|
239
|
+
cacheWritePricePer1m: cacheWrite === null ? null : cacheWrite * 1e6
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
return out;
|
|
243
|
+
};
|
|
244
|
+
function parseUnitPrice(value) {
|
|
245
|
+
if (value === "" || value === null || value === void 0) return null;
|
|
246
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
247
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
|
|
248
|
+
}
|
|
249
|
+
async function fetchCatalog(fetchImpl, url, timeoutMs, parse) {
|
|
250
|
+
const controller = new AbortController();
|
|
251
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
252
|
+
timer.unref?.();
|
|
253
|
+
try {
|
|
254
|
+
const response = await fetchImpl(url, { signal: controller.signal });
|
|
255
|
+
if (!response.ok) throw new Error(`status ${response.status}`);
|
|
256
|
+
const entries = parse(await response.json());
|
|
257
|
+
if (entries.length === 0) throw new Error("pricing catalog contained no usable entries");
|
|
258
|
+
return { ok: true, entries };
|
|
259
|
+
} catch (error) {
|
|
260
|
+
const message = controller.signal.aborted ? `timeout after ${timeoutMs}ms` : error instanceof Error ? error.message : String(error);
|
|
261
|
+
return { ok: false, error: message };
|
|
262
|
+
} finally {
|
|
263
|
+
clearTimeout(timer);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
function sourceResult(source, sourceUrl, attempt) {
|
|
267
|
+
return attempt.ok ? {
|
|
268
|
+
source,
|
|
269
|
+
sourceUrl,
|
|
270
|
+
status: "applied",
|
|
271
|
+
parsedCount: attempt.entries.length,
|
|
272
|
+
appliedCount: 0
|
|
273
|
+
} : {
|
|
274
|
+
source,
|
|
275
|
+
sourceUrl,
|
|
276
|
+
status: "failed",
|
|
277
|
+
parsedCount: 0,
|
|
278
|
+
appliedCount: 0,
|
|
279
|
+
error: attempt.error
|
|
280
|
+
};
|
|
281
|
+
}
|
|
170
282
|
|
|
171
283
|
export {
|
|
172
284
|
PricingEngine
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
var
|
|
6
|
+
var _chunkKBM2W6UYcjs = require('./chunk-KBM2W6UY.cjs');
|
|
7
7
|
|
|
8
8
|
// src/transformer/transformers/GeminiTransformer.ts
|
|
9
9
|
var GeminiTransformer = (_class = class {
|
|
@@ -44,7 +44,7 @@ var GeminiTransformer = (_class = class {
|
|
|
44
44
|
* Also builds the correct URL for the Gemini API
|
|
45
45
|
*/
|
|
46
46
|
async transformRequestIn(request, provider, _context) {
|
|
47
|
-
const body =
|
|
47
|
+
const body = _chunkKBM2W6UYcjs.buildRequestBody.call(void 0, request);
|
|
48
48
|
const action = request.stream ? "streamGenerateContent?alt=sse" : "generateContent";
|
|
49
49
|
const url = new URL(`./${request.model}:${action}`, provider.baseUrl);
|
|
50
50
|
const headers = {};
|
|
@@ -65,20 +65,20 @@ var GeminiTransformer = (_class = class {
|
|
|
65
65
|
* (For requests coming into the Gemini endpoint)
|
|
66
66
|
*/
|
|
67
67
|
async transformRequestOut(request, _context) {
|
|
68
|
-
return
|
|
68
|
+
return _chunkKBM2W6UYcjs.transformRequestOut.call(void 0, request);
|
|
69
69
|
}
|
|
70
70
|
/**
|
|
71
71
|
* Transform Gemini response to OpenAI-compatible format
|
|
72
72
|
*/
|
|
73
73
|
async transformResponseOut(response, _context) {
|
|
74
|
-
return
|
|
74
|
+
return _chunkKBM2W6UYcjs.transformResponseOut.call(void 0, response, this.name, this.logger);
|
|
75
75
|
}
|
|
76
76
|
/**
|
|
77
77
|
* Transform OpenAI-compatible response back to Gemini format
|
|
78
78
|
* (For endpoint mode — returning Gemini-format responses to the client)
|
|
79
79
|
*/
|
|
80
80
|
async transformResponseIn(response, _context) {
|
|
81
|
-
return
|
|
81
|
+
return _chunkKBM2W6UYcjs.transformResponseIn.call(void 0, response, this.logger);
|
|
82
82
|
}
|
|
83
83
|
}, _class.__initStatic(), _class);
|
|
84
84
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {
|
|
2
|
+
GeminiTransformer
|
|
3
|
+
} from "./chunk-LVUCAWMK.js";
|
|
4
|
+
import {
|
|
5
|
+
OpenAIResponseTransformer
|
|
6
|
+
} from "./chunk-OEWZUX4G.js";
|
|
7
|
+
import {
|
|
8
|
+
OpenAITransformer
|
|
9
|
+
} from "./chunk-LB6EBS6Y.js";
|
|
10
|
+
import {
|
|
11
|
+
AnthropicTransformer
|
|
12
|
+
} from "./chunk-6CL5HTPQ.js";
|
|
13
|
+
import {
|
|
14
|
+
GeminiCodeAssistTransformer
|
|
15
|
+
} from "./chunk-N5T4M4GU.js";
|
|
16
|
+
|
|
17
|
+
// src/transformer/transformers/index.ts
|
|
18
|
+
var BuiltinTransformers = {
|
|
19
|
+
OpenAITransformer,
|
|
20
|
+
GeminiTransformer,
|
|
21
|
+
GeminiCodeAssistTransformer,
|
|
22
|
+
AnthropicTransformer,
|
|
23
|
+
OpenAIResponseTransformer
|
|
24
|
+
};
|
|
25
|
+
function getBuiltinTransformers() {
|
|
26
|
+
return BuiltinTransformers;
|
|
27
|
+
}
|
|
28
|
+
var BUILTIN_TRANSFORMER_NAMES = [
|
|
29
|
+
"openai",
|
|
30
|
+
"gemini",
|
|
31
|
+
"gemini-code-assist",
|
|
32
|
+
"anthropic",
|
|
33
|
+
"openai-response"
|
|
34
|
+
];
|
|
35
|
+
async function registerBuiltinTransformers(service) {
|
|
36
|
+
await service.initialize(getBuiltinTransformers());
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export {
|
|
40
|
+
BuiltinTransformers,
|
|
41
|
+
getBuiltinTransformers,
|
|
42
|
+
BUILTIN_TRANSFORMER_NAMES,
|
|
43
|
+
registerBuiltinTransformers
|
|
44
|
+
};
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// src/provider-proxy/matchText.ts
|
|
2
|
+
var MATCH_TEXT_PER_MESSAGE_CAP = 8192;
|
|
3
|
+
var MATCH_TEXT_RECENT_MESSAGES = 6;
|
|
4
|
+
function flattenMatchText(value) {
|
|
5
|
+
if (typeof value === "string") return value;
|
|
6
|
+
if (Array.isArray(value)) {
|
|
7
|
+
const parts = [];
|
|
8
|
+
for (const item of value) {
|
|
9
|
+
const text = flattenMatchText(item);
|
|
10
|
+
if (text) parts.push(text);
|
|
11
|
+
}
|
|
12
|
+
return parts.join("\n");
|
|
13
|
+
}
|
|
14
|
+
if (value && typeof value === "object") {
|
|
15
|
+
const obj = value;
|
|
16
|
+
if (obj.type === "tool_result" && obj.content !== void 0) {
|
|
17
|
+
return flattenMatchText(obj.content);
|
|
18
|
+
}
|
|
19
|
+
if (typeof obj.text === "string") return obj.text;
|
|
20
|
+
}
|
|
21
|
+
return "";
|
|
22
|
+
}
|
|
23
|
+
function collectMatchText(anthropicBody) {
|
|
24
|
+
const messages = Array.isArray(anthropicBody.messages) ? anthropicBody.messages : [];
|
|
25
|
+
const slices = [];
|
|
26
|
+
const sys = flattenMatchText(anthropicBody.system).trim();
|
|
27
|
+
if (sys) slices.push(sys.slice(0, MATCH_TEXT_PER_MESSAGE_CAP));
|
|
28
|
+
const recent = [];
|
|
29
|
+
for (let i = messages.length - 1; i >= 0 && recent.length < MATCH_TEXT_RECENT_MESSAGES; i--) {
|
|
30
|
+
const message = messages[i];
|
|
31
|
+
if (!message || typeof message !== "object") continue;
|
|
32
|
+
const role = message.role;
|
|
33
|
+
if (role !== "user" && role !== "system") continue;
|
|
34
|
+
const text = flattenMatchText(message.content).trim();
|
|
35
|
+
if (text) recent.push(text.slice(0, MATCH_TEXT_PER_MESSAGE_CAP));
|
|
36
|
+
}
|
|
37
|
+
for (let i = recent.length - 1; i >= 0; i--) slices.push(recent[i]);
|
|
38
|
+
return slices;
|
|
39
|
+
}
|
|
40
|
+
function deriveSubscriptionSessionKey(anthropicBody) {
|
|
41
|
+
const system = flattenMatchText(anthropicBody.system).trim().slice(0, MATCH_TEXT_PER_MESSAGE_CAP);
|
|
42
|
+
const messages = Array.isArray(anthropicBody.messages) ? anthropicBody.messages : [];
|
|
43
|
+
let firstUser = "";
|
|
44
|
+
for (const message of messages) {
|
|
45
|
+
if (message && typeof message === "object" && message.role === "user") {
|
|
46
|
+
firstUser = flattenMatchText(message.content).trim().slice(0, MATCH_TEXT_PER_MESSAGE_CAP);
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const anchor = `${system}\0${firstUser}`;
|
|
51
|
+
if (!system && !firstUser) return void 0;
|
|
52
|
+
return fnv1a(anchor);
|
|
53
|
+
}
|
|
54
|
+
var SESSION_ID_CAP = 1024;
|
|
55
|
+
function deriveGatewaySessionKey(body, headers = {}, options = {}) {
|
|
56
|
+
const sessionHeader = firstHeaderValue(headers, ["session-id", "session_id", "x-session-id"]);
|
|
57
|
+
if (sessionHeader) {
|
|
58
|
+
return { key: fnv1a(sessionHeader), source: "session-header" };
|
|
59
|
+
}
|
|
60
|
+
const threadHeader = firstHeaderValue(headers, ["thread-id", "thread_id", "x-thread-id"]);
|
|
61
|
+
if (threadHeader) {
|
|
62
|
+
return { key: fnv1a(threadHeader), source: "thread-header" };
|
|
63
|
+
}
|
|
64
|
+
const bodySessionId = scalarSessionValue(body.session_id);
|
|
65
|
+
if (bodySessionId) {
|
|
66
|
+
return { key: fnv1a(bodySessionId), source: "body-session-id" };
|
|
67
|
+
}
|
|
68
|
+
const bodyThreadId = scalarSessionValue(body.thread_id ?? body.threadId);
|
|
69
|
+
if (bodyThreadId) {
|
|
70
|
+
return { key: fnv1a(bodyThreadId), source: "body-thread-id" };
|
|
71
|
+
}
|
|
72
|
+
const conversationId = scalarSessionValue(body.conversation_id);
|
|
73
|
+
if (conversationId) {
|
|
74
|
+
return { key: fnv1a(conversationId), source: "body-session-id" };
|
|
75
|
+
}
|
|
76
|
+
const promptCacheKey = scalarSessionValue(body.prompt_cache_key);
|
|
77
|
+
if (promptCacheKey) {
|
|
78
|
+
return { key: fnv1a(promptCacheKey), source: "prompt-cache-key" };
|
|
79
|
+
}
|
|
80
|
+
const fingerprint = deriveResponsesContentFingerprint(body);
|
|
81
|
+
if (fingerprint) {
|
|
82
|
+
return { key: fnv1a(fingerprint), source: "content-fingerprint" };
|
|
83
|
+
}
|
|
84
|
+
const endpoint = options.endpoint?.trim() || "responses";
|
|
85
|
+
const fallback = options.fallbackKey?.trim() || "anonymous";
|
|
86
|
+
return {
|
|
87
|
+
key: fnv1a(`${endpoint}\0${fallback}`),
|
|
88
|
+
source: "api-key-fallback"
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function firstHeaderValue(headers, names) {
|
|
92
|
+
const byLower = /* @__PURE__ */ new Map();
|
|
93
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
94
|
+
if (typeof value === "string" || Array.isArray(value)) {
|
|
95
|
+
const lower = name.toLowerCase();
|
|
96
|
+
if (!byLower.has(lower)) byLower.set(lower, value);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
for (const name of names) {
|
|
100
|
+
const value = byLower.get(name);
|
|
101
|
+
const values = Array.isArray(value) ? value : [value];
|
|
102
|
+
for (const item of values) {
|
|
103
|
+
if (typeof item !== "string") continue;
|
|
104
|
+
const trimmed = item.trim();
|
|
105
|
+
if (trimmed) return trimmed.slice(0, SESSION_ID_CAP);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return void 0;
|
|
109
|
+
}
|
|
110
|
+
function scalarSessionValue(value) {
|
|
111
|
+
if (typeof value !== "string" && typeof value !== "number") return void 0;
|
|
112
|
+
const trimmed = String(value).trim();
|
|
113
|
+
return trimmed ? trimmed.slice(0, SESSION_ID_CAP) : void 0;
|
|
114
|
+
}
|
|
115
|
+
function deriveResponsesContentFingerprint(body) {
|
|
116
|
+
const stable = [];
|
|
117
|
+
const instructions = flattenMatchText(body.instructions).trim();
|
|
118
|
+
if (instructions) stable.push(instructions.slice(0, MATCH_TEXT_PER_MESSAGE_CAP));
|
|
119
|
+
const input = body.input;
|
|
120
|
+
let firstUser = "";
|
|
121
|
+
const visitItem = (item) => {
|
|
122
|
+
if (typeof item === "string") {
|
|
123
|
+
if (!firstUser) firstUser = item.trim().slice(0, MATCH_TEXT_PER_MESSAGE_CAP);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (!item || typeof item !== "object") return;
|
|
127
|
+
const record = item;
|
|
128
|
+
const role = typeof record.role === "string" ? record.role.toLowerCase() : "";
|
|
129
|
+
const content = record.content ?? record.text ?? record.input;
|
|
130
|
+
const text = flattenMatchText(content !== void 0 ? content : record).trim();
|
|
131
|
+
if (!text) return;
|
|
132
|
+
if (role === "developer" || role === "system") {
|
|
133
|
+
stable.push(text.slice(0, MATCH_TEXT_PER_MESSAGE_CAP));
|
|
134
|
+
} else if (role === "user" || !role) {
|
|
135
|
+
if (!firstUser) firstUser = text.slice(0, MATCH_TEXT_PER_MESSAGE_CAP);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
if (Array.isArray(input)) {
|
|
139
|
+
for (const item of input) visitItem(item);
|
|
140
|
+
} else if (input !== void 0) {
|
|
141
|
+
visitItem(input);
|
|
142
|
+
}
|
|
143
|
+
if (stable.length === 0) {
|
|
144
|
+
const legacySystem = flattenMatchText(body.system).trim();
|
|
145
|
+
if (legacySystem) stable.push(legacySystem.slice(0, MATCH_TEXT_PER_MESSAGE_CAP));
|
|
146
|
+
}
|
|
147
|
+
if (stable.length === 0 && !firstUser) return void 0;
|
|
148
|
+
return `${stable.join("\0")}\0${firstUser}`;
|
|
149
|
+
}
|
|
150
|
+
function fnv1a(str) {
|
|
151
|
+
let h = 2166136261;
|
|
152
|
+
for (let i = 0; i < str.length; i++) {
|
|
153
|
+
h ^= str.charCodeAt(i);
|
|
154
|
+
h = Math.imul(h, 16777619);
|
|
155
|
+
}
|
|
156
|
+
return (h >>> 0).toString(16).padStart(8, "0");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export {
|
|
160
|
+
MATCH_TEXT_PER_MESSAGE_CAP,
|
|
161
|
+
MATCH_TEXT_RECENT_MESSAGES,
|
|
162
|
+
flattenMatchText,
|
|
163
|
+
collectMatchText,
|
|
164
|
+
deriveSubscriptionSessionKey,
|
|
165
|
+
deriveGatewaySessionKey
|
|
166
|
+
};
|
|
@@ -134,7 +134,13 @@ var TransformerChainExecutor = class {
|
|
|
134
134
|
const { endpointTransformer } = options;
|
|
135
135
|
const context = {
|
|
136
136
|
logger: this.logger,
|
|
137
|
-
providerName: provider.name
|
|
137
|
+
providerName: provider.name,
|
|
138
|
+
// The unified REQUEST this response answers. Response encoders need it to
|
|
139
|
+
// restore request-scoped protocol state that the chat wire cannot carry —
|
|
140
|
+
// e.g. which codex tools were declared `custom` (see
|
|
141
|
+
// `UnifiedChatRequestMeta.codexTools`), which decides whether a tool call
|
|
142
|
+
// is encoded back as a `function_call` or a `custom_tool_call` item.
|
|
143
|
+
req: request
|
|
138
144
|
};
|
|
139
145
|
let finalResponse = response;
|
|
140
146
|
const bypass = this.shouldBypassTransformers(chain, endpointTransformer, request);
|
|
@@ -59,6 +59,26 @@ function hasThinkingEnabled(request) {
|
|
|
59
59
|
}
|
|
60
60
|
return false;
|
|
61
61
|
}
|
|
62
|
+
function flattenToolResultContent(content) {
|
|
63
|
+
if (typeof content === "string") return content;
|
|
64
|
+
if (content == null) return "";
|
|
65
|
+
if (!Array.isArray(content)) return JSON.stringify(content);
|
|
66
|
+
const parts = [];
|
|
67
|
+
for (const block of content) {
|
|
68
|
+
if (typeof block === "string") {
|
|
69
|
+
parts.push(block);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (!block || typeof block !== "object") continue;
|
|
73
|
+
const typed = block;
|
|
74
|
+
if (typed.type === "image" || typed.type === "image_url") {
|
|
75
|
+
parts.push("[image omitted]");
|
|
76
|
+
} else if (typeof typed.text === "string") {
|
|
77
|
+
parts.push(typed.text);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return parts.filter((text) => text !== "").join("\n");
|
|
81
|
+
}
|
|
62
82
|
|
|
63
83
|
// src/api-converter/anthropic-to-openai.ts
|
|
64
84
|
function convertAnthropicToOpenAI(response) {
|
|
@@ -158,7 +178,7 @@ function convertAnthropicRequestToOpenAI(request) {
|
|
|
158
178
|
});
|
|
159
179
|
} else if (part.type === "tool_result") {
|
|
160
180
|
toolResultId = part.tool_use_id;
|
|
161
|
-
toolResultContent =
|
|
181
|
+
toolResultContent = flattenToolResultContent(part.content);
|
|
162
182
|
}
|
|
163
183
|
}
|
|
164
184
|
if (toolResultId && toolResultContent !== void 0) {
|
|
@@ -368,7 +388,10 @@ function convertOpenAIToAnthropic(request, config) {
|
|
|
368
388
|
const toolResult = {
|
|
369
389
|
type: "tool_result",
|
|
370
390
|
tool_use_id: msg.tool_call_id,
|
|
371
|
-
|
|
391
|
+
// An OpenAI tool message is normally a plain string, but a parts array
|
|
392
|
+
// must be flattened to text rather than stringified into a literal
|
|
393
|
+
// `[{"type":"text",…}]` envelope the model then has to read.
|
|
394
|
+
content: flattenToolResultContent(msg.content)
|
|
372
395
|
};
|
|
373
396
|
const lastMsg = messages[messages.length - 1];
|
|
374
397
|
if (lastMsg && lastMsg.role === "user" && Array.isArray(lastMsg.content)) {
|
|
@@ -7,6 +7,7 @@ var BAN_BODY_MARKERS = [
|
|
|
7
7
|
"this organization has been disabled",
|
|
8
8
|
"oauth authentication is currently not allowed"
|
|
9
9
|
];
|
|
10
|
+
var ACCOUNT_HEALTH_DIAGNOSTIC_LIMIT = 200;
|
|
10
11
|
function readHeader(headers, name) {
|
|
11
12
|
if (typeof headers.get === "function") {
|
|
12
13
|
return headers.get(name);
|
|
@@ -44,6 +45,7 @@ var SubscriptionAccountHealth = class {
|
|
|
44
45
|
records = /* @__PURE__ */ new Map();
|
|
45
46
|
listeners = /* @__PURE__ */ new Set();
|
|
46
47
|
anomalyListeners = /* @__PURE__ */ new Set();
|
|
48
|
+
diagnostics = [];
|
|
47
49
|
now;
|
|
48
50
|
overloadEnabled;
|
|
49
51
|
overloadTtlMs;
|
|
@@ -71,8 +73,8 @@ var SubscriptionAccountHealth = class {
|
|
|
71
73
|
}
|
|
72
74
|
/**
|
|
73
75
|
* Whether an account may be scheduled RIGHT NOW. `blocked` → false; any timer
|
|
74
|
-
* still in the future → false. Expired timers are lazily deleted on read
|
|
75
|
-
*
|
|
76
|
+
* still in the future → false. Expired timers are lazily deleted on read so
|
|
77
|
+
* an elapsed cooldown restores the account WITHOUT a
|
|
76
78
|
* timer — but the recovery SIGNAL is the sweeper's job (`sweepRecoveries`), so
|
|
77
79
|
* this read never emits (correctness is independent of the tick).
|
|
78
80
|
*/
|
|
@@ -86,7 +88,7 @@ var SubscriptionAccountHealth = class {
|
|
|
86
88
|
return !active;
|
|
87
89
|
}
|
|
88
90
|
/**
|
|
89
|
-
* The single marking entry point (design D3)
|
|
91
|
+
* The single marking entry point (design D3):
|
|
90
92
|
* - 429 + authoritative reset → `rateLimitEndAt`; a non-claude 429 may use
|
|
91
93
|
* `retryAfterSeconds`; a bare 429 (no resolvable reset) is NOT marked;
|
|
92
94
|
* - 529 → `overloadUntil` (gated by `overloadEnabled`);
|
|
@@ -141,9 +143,20 @@ var SubscriptionAccountHealth = class {
|
|
|
141
143
|
}
|
|
142
144
|
if (isRecordEmpty(record)) this.records.delete(key);
|
|
143
145
|
else this.records.set(key, record);
|
|
144
|
-
if (recovered)
|
|
146
|
+
if (recovered) {
|
|
147
|
+
const recovery = {
|
|
148
|
+
providerId,
|
|
149
|
+
accountId,
|
|
150
|
+
at: now,
|
|
151
|
+
kind: "rateLimitRecovery"
|
|
152
|
+
};
|
|
153
|
+
this.recordDiagnostic({ kind: "health-recovery", providerId, accountId, at: now });
|
|
154
|
+
this.emit(recovery);
|
|
155
|
+
}
|
|
145
156
|
if (anomalyState && !wasUnhealthyBefore) {
|
|
146
|
-
|
|
157
|
+
const anomaly = { providerId, accountId, at: now, state: anomalyState };
|
|
158
|
+
this.recordDiagnostic({ kind: "health-anomaly", ...anomaly });
|
|
159
|
+
this.emitAnomaly(anomaly);
|
|
147
160
|
}
|
|
148
161
|
}
|
|
149
162
|
/** Whether a record is unhealthy at `now`: blocked, or any timer still future. */
|
|
@@ -196,7 +209,15 @@ var SubscriptionAccountHealth = class {
|
|
|
196
209
|
this.records.delete(key);
|
|
197
210
|
}
|
|
198
211
|
}
|
|
199
|
-
for (const event of recovered)
|
|
212
|
+
for (const event of recovered) {
|
|
213
|
+
this.recordDiagnostic({
|
|
214
|
+
kind: "health-recovery",
|
|
215
|
+
providerId: event.providerId,
|
|
216
|
+
accountId: event.accountId,
|
|
217
|
+
at: event.at
|
|
218
|
+
});
|
|
219
|
+
this.emit(event);
|
|
220
|
+
}
|
|
200
221
|
return recovered;
|
|
201
222
|
}
|
|
202
223
|
/** Admin-facing coarse status + cooldown-until for one account (secret-free). */
|
|
@@ -215,6 +236,14 @@ var SubscriptionAccountHealth = class {
|
|
|
215
236
|
}
|
|
216
237
|
return { state: "healthy" };
|
|
217
238
|
}
|
|
239
|
+
/** Return newest-first, bounded, secret-free health edge diagnostics. */
|
|
240
|
+
getDiagnostics(filter = {}) {
|
|
241
|
+
const requested = typeof filter.limit === "number" && Number.isFinite(filter.limit) ? Math.trunc(filter.limit) : ACCOUNT_HEALTH_DIAGNOSTIC_LIMIT;
|
|
242
|
+
const limit = Math.min(ACCOUNT_HEALTH_DIAGNOSTIC_LIMIT, Math.max(1, requested));
|
|
243
|
+
return this.diagnostics.filter(
|
|
244
|
+
(event) => (!filter.providerId || event.providerId === filter.providerId) && (!filter.accountId || event.accountId === filter.accountId)
|
|
245
|
+
).slice(-limit).reverse().map((event) => ({ ...event }));
|
|
246
|
+
}
|
|
218
247
|
/**
|
|
219
248
|
* Register a recovery listener (the OQ3 seam #5 webhooks + #8 health-cron
|
|
220
249
|
* consume). Returns an unsubscribe function. Two edges fire it:
|
|
@@ -257,6 +286,12 @@ var SubscriptionAccountHealth = class {
|
|
|
257
286
|
}
|
|
258
287
|
}
|
|
259
288
|
}
|
|
289
|
+
recordDiagnostic(event) {
|
|
290
|
+
this.diagnostics.push({ ...event });
|
|
291
|
+
if (this.diagnostics.length > ACCOUNT_HEALTH_DIAGNOSTIC_LIMIT) {
|
|
292
|
+
this.diagnostics.splice(0, this.diagnostics.length - ACCOUNT_HEALTH_DIAGNOSTIC_LIMIT);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
260
295
|
/**
|
|
261
296
|
* Delete every EXPIRED timer field on the record in place; return whether any
|
|
262
297
|
* timer is STILL active (in the future). `blocked` is not a timer and is left
|
|
@@ -303,6 +338,7 @@ export {
|
|
|
303
338
|
OVERLOAD_ENABLED_DEFAULT,
|
|
304
339
|
AUTH_ERROR_TTL_MS,
|
|
305
340
|
SERVER_ERROR_TTL_MS,
|
|
341
|
+
ACCOUNT_HEALTH_DIAGNOSTIC_LIMIT,
|
|
306
342
|
resolveResetSeconds,
|
|
307
343
|
SubscriptionAccountHealth,
|
|
308
344
|
getSharedAccountHealth,
|