@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.
Files changed (190) hide show
  1. package/dist/ApiConverter.cjs +2 -2
  2. package/dist/ApiConverter.js +1 -1
  3. package/dist/{CompletionService-DdWaAxq0.d.ts → CompletionService-C3BX0Ovy.d.ts} +4 -3
  4. package/dist/{CompletionService-D3a47vdU.d.cts → CompletionService-kNA17cfI.d.cts} +4 -3
  5. package/dist/auth/GeminiCodeAssistProjectResolver.cjs +6 -5
  6. package/dist/auth/GeminiCodeAssistProjectResolver.js +4 -3
  7. package/dist/{chunk-HRRYG2AX.js → chunk-23S5OPIO.js} +10 -1
  8. package/dist/chunk-25GXJCEZ.cjs +40 -0
  9. package/dist/chunk-2JYRIADS.js +40 -0
  10. package/dist/{chunk-4XMYPW46.cjs → chunk-4VLRHHFE.cjs} +75 -33
  11. package/dist/{chunk-VACWOIRE.cjs → chunk-4VZX5F4T.cjs} +39 -15
  12. package/dist/{chunk-C6OV7GCC.js → chunk-6CL5HTPQ.js} +44 -2
  13. package/dist/chunk-6QQG7YG3.cjs +153 -0
  14. package/dist/{chunk-3Q6Z6JTO.cjs → chunk-6VUQPOV3.cjs} +277 -86
  15. package/dist/chunk-7AK7B36B.cjs +44 -0
  16. package/dist/{chunk-5ZQBEOMD.js → chunk-AT7B7SOZ.js} +25 -2
  17. package/dist/{chunk-IK3KE3AU.js → chunk-DE2SRBFZ.js} +1 -1
  18. package/dist/chunk-DLXZEZWO.cjs +285 -0
  19. package/dist/{chunk-UHGVBFU3.js → chunk-EBLFCNPY.js} +38 -14
  20. package/dist/chunk-EJLH6NFT.cjs +166 -0
  21. package/dist/chunk-HVBBDBS3.cjs +350 -0
  22. package/dist/{chunk-46ZIFWYG.cjs → chunk-IIRPUVPH.cjs} +7 -1
  23. package/dist/{chunk-JWVTXXPX.cjs → chunk-KBM2W6UY.cjs} +8 -4
  24. package/dist/{chunk-PG7PS2NN.cjs → chunk-KCVJWJST.cjs} +14 -2
  25. package/dist/chunk-LB6EBS6Y.js +69 -0
  26. package/dist/{chunk-IWQAX3CS.js → chunk-LVUCAWMK.js} +1 -1
  27. package/dist/chunk-MKF46GNZ.js +350 -0
  28. package/dist/{chunk-HYN75H6G.cjs → chunk-MUHWHQVM.cjs} +1 -1
  29. package/dist/{chunk-MFZ5437A.js → chunk-N5T4M4GU.js} +1 -1
  30. package/dist/{chunk-BODMAL6G.js → chunk-OEWZUX4G.js} +262 -71
  31. package/dist/chunk-OF3BECU2.cjs +69 -0
  32. package/dist/{chunk-DDMRUL76.cjs → chunk-OKMJLEXX.cjs} +5 -5
  33. package/dist/chunk-OPRS4IQZ.js +153 -0
  34. package/dist/{chunk-D3TDKWRP.js → chunk-SIZQQD6T.js} +14 -2
  35. package/dist/{chunk-CGKLE6IW.cjs → chunk-SQYVORNP.cjs} +1219 -510
  36. package/dist/{chunk-6QZGDLL4.js → chunk-TOVPMMHM.js} +5 -1
  37. package/dist/{chunk-FH2P4OCD.js → chunk-UDCC7EMR.js} +976 -267
  38. package/dist/{chunk-ZLX3KGXX.js → chunk-UX7VWA46.js} +135 -23
  39. package/dist/{chunk-K52THW2C.cjs → chunk-V4TXMEJS.cjs} +5 -5
  40. package/dist/chunk-VRFBB5DB.js +44 -0
  41. package/dist/chunk-WB6PH56J.js +166 -0
  42. package/dist/{chunk-G2UTNSGU.js → chunk-WNKWAEUR.js} +7 -1
  43. package/dist/{chunk-SUEGJ75D.cjs → chunk-WYTEQ4Q5.cjs} +25 -2
  44. package/dist/{chunk-5ENKBSWO.js → chunk-YJVHCHQX.js} +42 -6
  45. package/dist/{chunk-VQY5W4YW.cjs → chunk-YK57NMIF.cjs} +44 -8
  46. package/dist/{chunk-KFI44N2R.cjs → chunk-YNJNTCSQ.cjs} +10 -1
  47. package/dist/completion/ApiKeyPoolService.cjs +12 -0
  48. package/dist/completion/ApiKeyPoolService.d.cts +6 -0
  49. package/dist/completion/ApiKeyPoolService.d.ts +6 -0
  50. package/dist/completion/ApiKeyPoolService.js +12 -0
  51. package/dist/completion/CompletionService.cjs +25 -23
  52. package/dist/completion/CompletionService.d.cts +2 -1
  53. package/dist/completion/CompletionService.d.ts +2 -1
  54. package/dist/completion/CompletionService.js +24 -22
  55. package/dist/completion.cjs +25 -23
  56. package/dist/completion.d.cts +2 -1
  57. package/dist/completion.d.ts +2 -1
  58. package/dist/completion.js +24 -22
  59. package/dist/index.cjs +49 -26
  60. package/dist/index.d.cts +8 -8
  61. package/dist/index.d.ts +8 -8
  62. package/dist/index.js +52 -29
  63. package/dist/{keyPolicy-Cp0FntZl.d.ts → keyPolicy-zdWTu3CQ.d.cts} +1 -1
  64. package/dist/{keyPolicy-Cp0FntZl.d.cts → keyPolicy-zdWTu3CQ.d.ts} +1 -1
  65. package/dist/outbound-api/quotaWarn.d.cts +1 -1
  66. package/dist/outbound-api/quotaWarn.d.ts +1 -1
  67. package/dist/outbound-api/routeResolver.cjs +2 -2
  68. package/dist/outbound-api/routeResolver.d.cts +4 -3
  69. package/dist/outbound-api/routeResolver.d.ts +4 -3
  70. package/dist/outbound-api/routeResolver.js +1 -1
  71. package/dist/outbound-api/subscriptionRegistryPort.d.cts +1 -0
  72. package/dist/outbound-api/subscriptionRegistryPort.d.ts +1 -0
  73. package/dist/outbound-api/types.d.cts +3 -2
  74. package/dist/outbound-api/types.d.ts +3 -2
  75. package/dist/outbound-api.cjs +47 -24
  76. package/dist/outbound-api.d.cts +127 -85
  77. package/dist/outbound-api.d.ts +127 -85
  78. package/dist/outbound-api.js +50 -27
  79. package/dist/pipeline/AccountAllowanceScheduling.cjs +15 -0
  80. package/dist/pipeline/AccountAllowanceScheduling.d.cts +90 -0
  81. package/dist/pipeline/AccountAllowanceScheduling.d.ts +90 -0
  82. package/dist/pipeline/AccountAllowanceScheduling.js +15 -0
  83. package/dist/pipeline/AccountAllowanceStore.cjs +20 -0
  84. package/dist/pipeline/AccountAllowanceStore.d.cts +77 -0
  85. package/dist/pipeline/AccountAllowanceStore.d.ts +77 -0
  86. package/dist/pipeline/AccountAllowanceStore.js +20 -0
  87. package/dist/pipeline/AuthSource.d.cts +11 -2
  88. package/dist/pipeline/AuthSource.d.ts +11 -2
  89. package/dist/pipeline/BoundAccountSelectionError.cjs +10 -0
  90. package/dist/pipeline/BoundAccountSelectionError.d.cts +28 -0
  91. package/dist/pipeline/BoundAccountSelectionError.d.ts +28 -0
  92. package/dist/pipeline/BoundAccountSelectionError.js +10 -0
  93. package/dist/pipeline/LlmConfigProviderAuth.d.cts +1 -0
  94. package/dist/pipeline/LlmConfigProviderAuth.d.ts +1 -0
  95. package/dist/pipeline/SubscriptionAccountHealth.cjs +4 -2
  96. package/dist/pipeline/SubscriptionAccountHealth.d.cts +34 -6
  97. package/dist/pipeline/SubscriptionAccountHealth.d.ts +34 -6
  98. package/dist/pipeline/SubscriptionAccountHealth.js +3 -1
  99. package/dist/pipeline/SubscriptionAuthSource.cjs +5 -2
  100. package/dist/pipeline/SubscriptionAuthSource.d.cts +1 -0
  101. package/dist/pipeline/SubscriptionAuthSource.d.ts +1 -0
  102. package/dist/pipeline/SubscriptionAuthSource.js +4 -1
  103. package/dist/pipeline/SubscriptionAuthStrategy.d.cts +8 -3
  104. package/dist/pipeline/SubscriptionAuthStrategy.d.ts +8 -3
  105. package/dist/pipeline/executeProviderCall.cjs +2 -2
  106. package/dist/pipeline/executeProviderCall.d.cts +8 -3
  107. package/dist/pipeline/executeProviderCall.d.ts +8 -3
  108. package/dist/pipeline/executeProviderCall.js +1 -1
  109. package/dist/pipeline/resolveProviderChain.d.cts +2 -3
  110. package/dist/pipeline/resolveProviderChain.d.ts +2 -3
  111. package/dist/pipeline/resolveSubscriptionChain.d.cts +1 -0
  112. package/dist/pipeline/resolveSubscriptionChain.d.ts +1 -0
  113. package/dist/pipeline/upstreamFetch.cjs +3 -2
  114. package/dist/pipeline/upstreamFetch.js +2 -1
  115. package/dist/ports/pricing-store.d.cts +4 -3
  116. package/dist/ports/pricing-store.d.ts +4 -3
  117. package/dist/ports.d.cts +4 -3
  118. package/dist/ports.d.ts +4 -3
  119. package/dist/provider-proxy/ProviderProxy.cjs +25 -23
  120. package/dist/provider-proxy/ProviderProxy.d.cts +1 -0
  121. package/dist/provider-proxy/ProviderProxy.d.ts +1 -0
  122. package/dist/provider-proxy/ProviderProxy.js +24 -22
  123. package/dist/provider-proxy/ingress/providerProxyShared.cjs +27 -23
  124. package/dist/provider-proxy/ingress/providerProxyShared.d.cts +34 -5
  125. package/dist/provider-proxy/ingress/providerProxyShared.d.ts +34 -5
  126. package/dist/provider-proxy/ingress/providerProxyShared.js +26 -22
  127. package/dist/provider-proxy/matchText.cjs +4 -2
  128. package/dist/provider-proxy/matchText.d.cts +41 -6
  129. package/dist/provider-proxy/matchText.d.ts +41 -6
  130. package/dist/provider-proxy/matchText.js +3 -1
  131. package/dist/provider-proxy/types.d.cts +30 -2
  132. package/dist/provider-proxy/types.d.ts +30 -2
  133. package/dist/provider-proxy.cjs +25 -23
  134. package/dist/provider-proxy.d.cts +1 -0
  135. package/dist/provider-proxy.d.ts +1 -0
  136. package/dist/provider-proxy.js +24 -22
  137. package/dist/{routeResolver-C4T7yClx.d.ts → routeResolver-CHTv_Bs5.d.ts} +1 -1
  138. package/dist/{routeResolver-CB3tvJKy.d.cts → routeResolver-kepxnfcC.d.cts} +1 -1
  139. package/dist/transformer/TransformerChainExecutor.cjs +2 -2
  140. package/dist/transformer/TransformerChainExecutor.js +1 -1
  141. package/dist/transformer/transformers/AnthropicTransformer.cjs +2 -2
  142. package/dist/transformer/transformers/AnthropicTransformer.js +1 -1
  143. package/dist/transformer/transformers/GeminiCodeAssistTransformer.cjs +3 -3
  144. package/dist/transformer/transformers/GeminiCodeAssistTransformer.js +2 -2
  145. package/dist/transformer/transformers/GeminiTransformer.cjs +3 -3
  146. package/dist/transformer/transformers/GeminiTransformer.js +2 -2
  147. package/dist/transformer/transformers/OpenAIResponseTransformer.cjs +2 -2
  148. package/dist/transformer/transformers/OpenAIResponseTransformer.d.cts +1 -1
  149. package/dist/transformer/transformers/OpenAIResponseTransformer.d.ts +1 -1
  150. package/dist/transformer/transformers/OpenAIResponseTransformer.js +1 -1
  151. package/dist/transformer/transformers/OpenAITransformer.cjs +6 -0
  152. package/dist/transformer/transformers/OpenAITransformer.d.cts +68 -0
  153. package/dist/transformer/transformers/OpenAITransformer.d.ts +68 -0
  154. package/dist/transformer/transformers/OpenAITransformer.js +6 -0
  155. package/dist/transformer/transformers.cjs +8 -14
  156. package/dist/transformer/transformers.d.cts +12 -48
  157. package/dist/transformer/transformers.d.ts +12 -48
  158. package/dist/transformer/transformers.js +12 -18
  159. package/dist/transformer/types.d.cts +17 -0
  160. package/dist/transformer/types.d.ts +17 -0
  161. package/dist/transformer.cjs +10 -11
  162. package/dist/transformer.d.cts +1 -2
  163. package/dist/transformer.d.ts +1 -2
  164. package/dist/transformer.js +9 -10
  165. package/dist/{types-B8Arpxn0.d.cts → types-H3wGZSOo.d.cts} +134 -7
  166. package/dist/{types-lgPYQuZS.d.ts → types-hsoKrVQS.d.ts} +134 -7
  167. package/dist/usage/pricing-engine.cjs +2 -2
  168. package/dist/usage/pricing-engine.d.cts +9 -1
  169. package/dist/usage/pricing-engine.d.ts +9 -1
  170. package/dist/usage/pricing-engine.js +1 -1
  171. package/dist/usage.cjs +2 -2
  172. package/dist/usage.js +1 -1
  173. package/package.json +1 -1
  174. package/dist/chunk-7FEBOPJO.cjs +0 -50
  175. package/dist/chunk-DWKQZ2SK.cjs +0 -254
  176. package/dist/chunk-IAXWICZU.js +0 -69
  177. package/dist/chunk-IB3FDI4O.js +0 -50
  178. package/dist/chunk-OQWKZCXP.js +0 -242
  179. package/dist/chunk-QWBWGODS.cjs +0 -69
  180. package/dist/chunk-YMFAJFQT.cjs +0 -173
  181. package/dist/chunk-ZCIQ3L3R.cjs +0 -242
  182. package/dist/chunk-ZJZAOKI7.js +0 -254
  183. package/dist/transformer/transformers/OpenCodeGoTransformer.cjs +0 -6
  184. package/dist/transformer/transformers/OpenCodeGoTransformer.d.cts +0 -51
  185. package/dist/transformer/transformers/OpenCodeGoTransformer.d.ts +0 -51
  186. package/dist/transformer/transformers/OpenCodeGoTransformer.js +0 -6
  187. package/dist/transformer/transformers/ReasoningTransformer.cjs +0 -6
  188. package/dist/transformer/transformers/ReasoningTransformer.d.cts +0 -47
  189. package/dist/transformer/transformers/ReasoningTransformer.d.ts +0 -47
  190. package/dist/transformer/transformers/ReasoningTransformer.js +0 -6
@@ -1,5 +1,8 @@
1
1
  // src/usage/pricing-engine.ts
2
- import { DEFAULT_LITELLM_PRICING_URL } from "@omnicross/contracts/pricing-types";
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 !== "*") this.cacheByModel.set(updated.modelId, updated);
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
- let json;
102
- try {
103
- const res = await fetchImpl(sourceUrl);
104
- if (!res.ok) {
105
- throw new Error(`Pricing-source fetch failed with status ${res.status}`);
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
- json = await res.json();
108
- } catch (e) {
109
- this.logger.warn("[PricingEngine] pricing-source fetch failed", {
110
- error: e instanceof Error ? e.message : String(e)
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
- throw e;
163
+ const result = await this.store.bulkApplyFromSource(supplemental, "openrouter");
164
+ applied.push(...result.applied);
165
+ sources[1].appliedCount = result.applied.length;
113
166
  }
114
- const incoming = parseLitellmJson(json);
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: conflicts.map((c) => ({
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 _chunkJWVTXXPXcjs = require('./chunk-JWVTXXPX.cjs');
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 = _chunkJWVTXXPXcjs.buildRequestBody.call(void 0, request);
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 _chunkJWVTXXPXcjs.transformRequestOut.call(void 0, request);
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 _chunkJWVTXXPXcjs.transformResponseOut.call(void 0, response, this.name, this.logger);
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 _chunkJWVTXXPXcjs.transformResponseIn.call(void 0, response, this.logger);
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 = typeof part.content === "string" ? part.content : JSON.stringify(part.content);
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
- content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)
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 (CRS
75
- * lazy-clear parity) so an elapsed cooldown restores the account WITHOUT a
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). Faithful to CRS status semantics:
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) this.emit({ providerId, accountId, at: now, kind: "rateLimitRecovery" });
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
- this.emitAnomaly({ providerId, accountId, at: now, state: anomalyState });
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) this.emit(event);
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,