@omnicross/core 0.1.2 → 0.1.3

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 (131) hide show
  1. package/dist/auth/GeminiCodeAssistProjectResolver.cjs +6 -1
  2. package/dist/auth/GeminiCodeAssistProjectResolver.js +6 -1
  3. package/dist/{chunk-SN3YWBX7.cjs → chunk-2FMI2B6V.cjs} +2329 -533
  4. package/dist/{chunk-XBSYYZIY.cjs → chunk-3QOSDDNI.cjs} +39 -3
  5. package/dist/{chunk-2FEVTJWG.js → chunk-4DECIFU7.js} +2162 -366
  6. package/dist/chunk-4IH4EL7M.cjs +9 -0
  7. package/dist/chunk-5ENKBSWO.js +311 -0
  8. package/dist/chunk-6RPZADX3.cjs +92 -0
  9. package/dist/chunk-AEOZIDEB.cjs +32 -0
  10. package/dist/chunk-BO4RFWBR.js +89 -0
  11. package/dist/{chunk-MNYKI4CI.js → chunk-CWM2ZDU5.js} +36 -0
  12. package/dist/chunk-CZHZGRU7.js +383 -0
  13. package/dist/{chunk-4NBS6KPV.cjs → chunk-DQ5VAYZ6.cjs} +7 -8
  14. package/dist/chunk-HUZZZ3JL.js +32 -0
  15. package/dist/{chunk-QOCNX236.js → chunk-IAXWICZU.js} +24 -1
  16. package/dist/{chunk-V5KPWNYX.cjs → chunk-JMJBSACD.cjs} +3 -3
  17. package/dist/chunk-L74VDN4A.cjs +32 -0
  18. package/dist/{chunk-74TMJA7Z.js → chunk-N5QFKSDO.js} +7 -4
  19. package/dist/{chunk-LKZJEL6E.cjs → chunk-Q27JY5RG.cjs} +6 -4
  20. package/dist/{chunk-SPVWWUHX.cjs → chunk-QWBWGODS.cjs} +24 -1
  21. package/dist/chunk-RPII3E6Z.cjs +145 -0
  22. package/dist/chunk-S6JGLF3G.cjs +89 -0
  23. package/dist/chunk-SVXC36MU.cjs +383 -0
  24. package/dist/chunk-TKJMZBAH.js +32 -0
  25. package/dist/{chunk-ZSVQT3PW.js → chunk-UBWEBKRC.js} +5 -6
  26. package/dist/chunk-UNEFIWXI.cjs +32 -0
  27. package/dist/chunk-VQY5W4YW.cjs +311 -0
  28. package/dist/chunk-WNY4WMRF.js +32 -0
  29. package/dist/chunk-XEKD23J5.js +9 -0
  30. package/dist/chunk-XTEI64OU.js +145 -0
  31. package/dist/chunk-XX6NQJMA.js +92 -0
  32. package/dist/{chunk-N3V2J5ZO.cjs → chunk-Y6OM7EV7.cjs} +7 -4
  33. package/dist/{chunk-745DV5FL.js → chunk-Y7FO65VT.js} +3 -3
  34. package/dist/{chunk-PXUJF5HS.js → chunk-ZY6KU6P3.js} +4 -2
  35. package/dist/completion/CompletionService.cjs +22 -15
  36. package/dist/completion/CompletionService.js +21 -14
  37. package/dist/completion/NativeSearchInjector.cjs +3 -3
  38. package/dist/completion/NativeSearchInjector.js +2 -2
  39. package/dist/completion/openrouter-headers.cjs +3 -6
  40. package/dist/completion/openrouter-headers.d.cts +9 -12
  41. package/dist/completion/openrouter-headers.d.ts +9 -12
  42. package/dist/completion/openrouter-headers.js +2 -5
  43. package/dist/completion/openrouter-models.cjs +4 -3
  44. package/dist/completion/openrouter-models.js +2 -1
  45. package/dist/completion.cjs +22 -15
  46. package/dist/completion.js +21 -14
  47. package/dist/index.cjs +139 -17
  48. package/dist/index.d.cts +40 -10
  49. package/dist/index.d.ts +40 -10
  50. package/dist/index.js +146 -24
  51. package/dist/outbound-api/routeResolver.cjs +5 -2
  52. package/dist/outbound-api/routeResolver.d.cts +8 -2
  53. package/dist/outbound-api/routeResolver.d.ts +8 -2
  54. package/dist/outbound-api/routeResolver.js +4 -1
  55. package/dist/outbound-api/types.cjs +6 -1
  56. package/dist/outbound-api/types.d.cts +11 -144
  57. package/dist/outbound-api/types.d.ts +11 -144
  58. package/dist/outbound-api/types.js +6 -0
  59. package/dist/outbound-api.cjs +133 -15
  60. package/dist/outbound-api.d.cts +786 -25
  61. package/dist/outbound-api.d.ts +786 -25
  62. package/dist/outbound-api.js +137 -19
  63. package/dist/pipeline/AuthSource.d.cts +23 -1
  64. package/dist/pipeline/AuthSource.d.ts +23 -1
  65. package/dist/pipeline/LlmConfigProviderAuth.cjs +3 -3
  66. package/dist/pipeline/LlmConfigProviderAuth.js +2 -2
  67. package/dist/pipeline/SubscriptionAccountHealth.cjs +22 -0
  68. package/dist/pipeline/SubscriptionAccountHealth.d.cts +235 -0
  69. package/dist/pipeline/SubscriptionAccountHealth.d.ts +235 -0
  70. package/dist/pipeline/SubscriptionAccountHealth.js +22 -0
  71. package/dist/pipeline/SubscriptionAuthSource.cjs +2 -2
  72. package/dist/pipeline/SubscriptionAuthSource.d.cts +3 -2
  73. package/dist/pipeline/SubscriptionAuthSource.d.ts +3 -2
  74. package/dist/pipeline/SubscriptionAuthSource.js +1 -1
  75. package/dist/pipeline/SubscriptionAuthStrategy.d.cts +24 -1
  76. package/dist/pipeline/SubscriptionAuthStrategy.d.ts +24 -1
  77. package/dist/pipeline/auditSink.cjs +14 -0
  78. package/dist/pipeline/auditSink.d.cts +48 -0
  79. package/dist/pipeline/auditSink.d.ts +48 -0
  80. package/dist/pipeline/auditSink.js +14 -0
  81. package/dist/pipeline/billingEmit.cjs +14 -0
  82. package/dist/pipeline/billingEmit.d.cts +49 -0
  83. package/dist/pipeline/billingEmit.d.ts +49 -0
  84. package/dist/pipeline/billingEmit.js +14 -0
  85. package/dist/ports/usage-event-store.d.cts +26 -1
  86. package/dist/ports/usage-event-store.d.ts +26 -1
  87. package/dist/ports.d.cts +7 -1
  88. package/dist/ports.d.ts +7 -1
  89. package/dist/provider-proxy/ProviderProxy.cjs +22 -15
  90. package/dist/provider-proxy/ProviderProxy.js +21 -14
  91. package/dist/provider-proxy/identity/SubscriptionIdentityStore.cjs +15 -0
  92. package/dist/provider-proxy/identity/SubscriptionIdentityStore.d.cts +121 -0
  93. package/dist/provider-proxy/identity/SubscriptionIdentityStore.d.ts +121 -0
  94. package/dist/provider-proxy/identity/SubscriptionIdentityStore.js +15 -0
  95. package/dist/provider-proxy/identity/fingerprintHeaders.cjs +20 -0
  96. package/dist/provider-proxy/identity/fingerprintHeaders.d.cts +89 -0
  97. package/dist/provider-proxy/identity/fingerprintHeaders.d.ts +89 -0
  98. package/dist/provider-proxy/identity/fingerprintHeaders.js +20 -0
  99. package/dist/provider-proxy/ingress/providerProxyShared.cjs +22 -15
  100. package/dist/provider-proxy/ingress/providerProxyShared.d.cts +11 -2
  101. package/dist/provider-proxy/ingress/providerProxyShared.d.ts +11 -2
  102. package/dist/provider-proxy/ingress/providerProxyShared.js +21 -14
  103. package/dist/provider-proxy/matchText.cjs +4 -2
  104. package/dist/provider-proxy/matchText.d.cts +14 -1
  105. package/dist/provider-proxy/matchText.d.ts +14 -1
  106. package/dist/provider-proxy/matchText.js +3 -1
  107. package/dist/provider-proxy/types.d.cts +23 -0
  108. package/dist/provider-proxy/types.d.ts +23 -0
  109. package/dist/provider-proxy.cjs +22 -15
  110. package/dist/provider-proxy.js +21 -14
  111. package/dist/{routeResolver-HE-ZO0fO.d.ts → routeResolver-B1NglTfe.d.ts} +36 -10
  112. package/dist/{routeResolver-BrbK6ja9.d.cts → routeResolver-CbG5uBgm.d.cts} +36 -10
  113. package/dist/transformer/transformers.cjs +7 -7
  114. package/dist/transformer/transformers.js +7 -7
  115. package/dist/transformer.cjs +5 -5
  116. package/dist/transformer.js +4 -4
  117. package/dist/types-6OUY_xfA.d.ts +830 -0
  118. package/dist/types-aBFFXoBz.d.cts +830 -0
  119. package/dist/usage/usage-recorder.cjs +2 -2
  120. package/dist/usage/usage-recorder.d.cts +19 -1
  121. package/dist/usage/usage-recorder.d.ts +19 -1
  122. package/dist/usage/usage-recorder.js +1 -1
  123. package/dist/usage.cjs +2 -2
  124. package/dist/usage.js +1 -1
  125. package/package.json +4 -2
  126. package/dist/chunk-3MEACFK3.js +0 -193
  127. package/dist/chunk-6VIXXLMX.cjs +0 -14
  128. package/dist/chunk-E3WHL7CO.js +0 -14
  129. package/dist/chunk-G2FUJNA2.cjs +0 -193
  130. package/dist/{chunk-UYPEN5XE.cjs → chunk-FFALUUZZ.cjs} +5 -5
  131. package/dist/{chunk-O466Y272.js → chunk-MQNDWYPN.js} +6 -6
@@ -0,0 +1,235 @@
1
+ /**
2
+ * SubscriptionAccountHealth — the in-memory account health state machine
3
+ * (subscription-account-health, design D1/D2/D3/D6).
4
+ *
5
+ * Shaped like the existing per-model `CircuitBreaker` (`opencodego/CircuitBreaker`):
6
+ * an in-memory `Map`, an injectable `now` clock, and NO external deps / NO
7
+ * persistence. It is keyed by an opaque `providerId + '\0' + accountId` string —
8
+ * core never learns "account" semantics, exactly as the breaker keys by opaque
9
+ * `modelId`. It is hosted in `@omnicross/core` because all three consumers reach
10
+ * it in the allowed dependency direction:
11
+ * - the subscriptions strategies COMPUTE `schedulable` (`isSchedulable`) + mark
12
+ * a final-401;
13
+ * - the core `/v1/messages` relay MARKS 429/529/403/5xx from the upstream
14
+ * response (`recordUpstreamOutcome`);
15
+ * - the daemon `AccountHealthSweeper` drives proactive recovery
16
+ * (`sweepRecoveries` + `onRecovered`).
17
+ *
18
+ * It carries NO secrets — only statuses + epoch-ms timers — so the secret-free
19
+ * discipline is untouched. Health is IN-MEMORY (cleared on restart): the worst
20
+ * case after a restart is one request hitting a still-cooling account and getting
21
+ * the upstream's authoritative 429 (which re-marks it). Nothing is written to
22
+ * `tokens.json`.
23
+ *
24
+ * @module pipeline/SubscriptionAccountHealth
25
+ */
26
+ /** Default 529 overload cooldown (LEAD OQ1: ON by default, bounded 10 min). */
27
+ declare const OVERLOAD_TTL_MS: number;
28
+ /** 529 overload cooldown enabled by default (LEAD OQ1). */
29
+ declare const OVERLOAD_ENABLED_DEFAULT = true;
30
+ /** Transient cooldown for a final-401 / plain-403 (CRS auth_error 1800s). */
31
+ declare const AUTH_ERROR_TTL_MS: number;
32
+ /** Transient cooldown for a 5xx / thrown network failure (CRS server_error 300s). */
33
+ declare const SERVER_ERROR_TTL_MS: number;
34
+ /** Per-account health record (all timers are epoch ms). Absent fields ⇒ healthy. */
35
+ interface HealthRecord {
36
+ /** From a 429 authoritative reset header — rate-limited until this instant. */
37
+ rateLimitEndAt?: number;
38
+ /** From a 529 overload — in overload cooldown until this instant. */
39
+ overloadUntil?: number;
40
+ /** From a final-401 / plain-403 / 5xx / thrown — transient cooldown until this. */
41
+ tempUnavailableUntil?: number;
42
+ /** From a 403-ban — permanent, self-heal never clears it. */
43
+ blocked?: boolean;
44
+ }
45
+ /** The coarse health state surfaced to the admin accounts view (secret-free). */
46
+ type AccountHealthState = 'healthy' | 'rate_limited' | 'overloaded' | 'transient' | 'blocked';
47
+ /** Admin-facing status projection for one account. */
48
+ interface AccountHealthStatus {
49
+ state: AccountHealthState;
50
+ /** Epoch ms the current cooldown elapses (absent for healthy / blocked). */
51
+ cooldownUntil?: number;
52
+ }
53
+ /**
54
+ * The recovery signal emitted when an account transitions unhealthy → schedulable.
55
+ * FROZEN here (subscription-account-health OQ3) as the stable seam #5 (webhooks)
56
+ * and #8 (health-cron) CONSUME — they must not re-derive health.
57
+ */
58
+ interface AccountRecoveryEvent {
59
+ /** Opaque provider id ('claude' | 'codex' | 'gemini' | 'opencodego'). */
60
+ providerId: string;
61
+ /** The recovered account id. */
62
+ accountId: string;
63
+ /** Epoch ms of the recovery observation. */
64
+ at: number;
65
+ /** Discriminator so consumers can widen this to an anomaly union later. */
66
+ kind: 'rateLimitRecovery';
67
+ }
68
+ /** A recovery listener (fire-and-forget; the emitter never awaits it). */
69
+ type AccountRecoveryListener = (event: AccountRecoveryEvent) => void;
70
+ /** The coarse anomaly state an account transitioned into (secret-free). */
71
+ type AccountAnomalyState = 'blocked' | 'unauthorized' | 'rate_limited' | 'overloaded';
72
+ /**
73
+ * The anomaly signal emitted when a HEALTHY account transitions to unhealthy —
74
+ * the additive union #5 (webhooks) consumes, which this tracker's frozen `kind`
75
+ * discriminator (on {@link AccountRecoveryEvent}) explicitly anticipated. ADDITIVE:
76
+ * it fires ONLY on the healthy→unhealthy EDGE (de-duped) and changes NOTHING about
77
+ * the existing marking / recovery behavior. A #8 probe failure flows through the
78
+ * same `recordUpstreamOutcome` path, so anomaly covers it for free.
79
+ */
80
+ interface AccountAnomalyEvent {
81
+ /** Opaque provider id ('claude' | 'codex' | 'gemini' | 'opencodego'). */
82
+ providerId: string;
83
+ /** The account id that became unhealthy. */
84
+ accountId: string;
85
+ /** Epoch ms of the transition. */
86
+ at: number;
87
+ /** The coarse state the account entered (mapped from the marking). */
88
+ state: AccountAnomalyState;
89
+ }
90
+ /** An anomaly listener (fire-and-forget; the emitter never awaits it). */
91
+ type AccountAnomalyListener = (event: AccountAnomalyEvent) => void;
92
+ /** The inputs one upstream attempt contributes to health marking (design D3). */
93
+ interface RecordUpstreamOutcomeInput {
94
+ /** The final HTTP status, or `null` for a thrown / network failure. */
95
+ status: number | null;
96
+ /** Parsed authoritative reset (epoch SECONDS) — `anthropic-ratelimit-unified-reset`. */
97
+ resetHeaderSeconds?: number | null;
98
+ /** Parsed `retry-after` DELTA seconds (non-claude fallback, OQ2). */
99
+ retryAfterSeconds?: number | null;
100
+ /** Bounded response body text for the 403-ban sniff (only read on a 403). */
101
+ bodyText?: string;
102
+ /** Injectable clock (default the tracker's `now`). */
103
+ now?: number;
104
+ }
105
+ /** Constructor knobs (all default to the exported constants). */
106
+ interface SubscriptionAccountHealthOptions {
107
+ /** Injectable clock (ms). Default `Date.now`. */
108
+ now?: () => number;
109
+ /** Whether a 529 places the account in overload cooldown (LEAD OQ1: ON). */
110
+ overloadEnabled?: boolean;
111
+ /** The 529 overload cooldown duration (ms). */
112
+ overloadTtlMs?: number;
113
+ /** The final-401 / plain-403 transient cooldown (ms). */
114
+ authErrorTtlMs?: number;
115
+ /** The 5xx / thrown transient cooldown (ms). */
116
+ serverErrorTtlMs?: number;
117
+ }
118
+ /** Minimal structural read over a `Headers` OR a plain header record. */
119
+ type HeadersLike = {
120
+ get(name: string): string | null;
121
+ } | Record<string, string | undefined>;
122
+ /**
123
+ * Resolve the 429 cooldown drivers per provider (OQ2 seam) — a single pure
124
+ * function. claude is STRICT (only the authoritative
125
+ * `anthropic-ratelimit-unified-reset`, epoch seconds; a bare 429 stays unmarked);
126
+ * codex / gemini / opencodego additionally accept `retry-after` (delta seconds)
127
+ * and a provider-specific reset header as the cooldown driver.
128
+ */
129
+ declare function resolveResetSeconds(providerId: string, headers: HeadersLike): {
130
+ resetHeaderSeconds: number | null;
131
+ retryAfterSeconds: number | null;
132
+ };
133
+ declare class SubscriptionAccountHealth {
134
+ private readonly records;
135
+ private readonly listeners;
136
+ private readonly anomalyListeners;
137
+ private readonly now;
138
+ private overloadEnabled;
139
+ private overloadTtlMs;
140
+ private authErrorTtlMs;
141
+ private serverErrorTtlMs;
142
+ constructor(opts?: SubscriptionAccountHealthOptions);
143
+ /**
144
+ * Re-apply cooldown config to the LIVE shared instance (subscription-account-
145
+ * health, LEAD OQ1). `buildDaemon` is synchronous so it cannot await the
146
+ * persisted server config; the async `start.ts` path loads it and applies the
147
+ * `accountHealth` segment here — every strategy/relay/sweeper that already
148
+ * captured this instance picks the new values up. Only defined fields override.
149
+ */
150
+ configure(opts: Pick<SubscriptionAccountHealthOptions, 'overloadEnabled' | 'overloadTtlMs' | 'authErrorTtlMs' | 'serverErrorTtlMs'>): void;
151
+ /**
152
+ * Whether an account may be scheduled RIGHT NOW. `blocked` → false; any timer
153
+ * still in the future → false. Expired timers are lazily deleted on read (CRS
154
+ * lazy-clear parity) so an elapsed cooldown restores the account WITHOUT a
155
+ * timer — but the recovery SIGNAL is the sweeper's job (`sweepRecoveries`), so
156
+ * this read never emits (correctness is independent of the tick).
157
+ */
158
+ isSchedulable(providerId: string, accountId: string, now?: number): boolean;
159
+ /**
160
+ * The single marking entry point (design D3). Faithful to CRS status semantics:
161
+ * - 429 + authoritative reset → `rateLimitEndAt`; a non-claude 429 may use
162
+ * `retryAfterSeconds`; a bare 429 (no resolvable reset) is NOT marked;
163
+ * - 529 → `overloadUntil` (gated by `overloadEnabled`);
164
+ * - 403-ban body → `blocked` (permanent); plain 403 → transient;
165
+ * - final-401 → transient; 5xx / thrown(null) → transient;
166
+ * - non-429 4xx → NEUTRAL (never marked);
167
+ * - 2xx → clear rate-limit + transient state (keep `blocked`).
168
+ */
169
+ recordUpstreamOutcome(providerId: string, accountId: string, input: RecordUpstreamOutcomeInput): void;
170
+ /** Whether a record is unhealthy at `now`: blocked, or any timer still future. */
171
+ private isUnhealthyAt;
172
+ /**
173
+ * Clear ONLY the auth/transient unavailability mark (`tempUnavailableUntil`) —
174
+ * the narrow healing a background PROBE is allowed to do (subscription-account-
175
+ * probe #8, review M1). A probe 2xx from the lightweight `GET /v1/models` attests
176
+ * the TOKEN works (so a final-401 / plain-403 / 5xx transient may be healed), but
177
+ * it does NOT prove the traffic endpoint's rate-limit / overload has recovered —
178
+ * so it MUST NOT touch `rateLimitEndAt` / `overloadUntil` (those clear only on the
179
+ * upstream's authoritative reset, or a REAL-traffic 2xx). `blocked` (a ban) is
180
+ * never cleared here. Distinct from `recordUpstreamOutcome`'s 2xx path, which is
181
+ * UNCHANGED and still clears rate + transient together because it IS traffic.
182
+ * Returns whether a transient mark was actually cleared. Does NOT emit a recovery
183
+ * signal (a probe is internal maintenance; the account is schedulable-on-read).
184
+ */
185
+ clearTransientMark(providerId: string, accountId: string): boolean;
186
+ /**
187
+ * Proactively surface accounts that transitioned unhealthy → schedulable since
188
+ * the last sweep (design D6). Clears their expired timers, emits a recovery
189
+ * signal for each through `onRecovered`, and returns the list. A `blocked`
190
+ * account never recovers here. Correctness does NOT depend on this running —
191
+ * `isSchedulable` already restores an elapsed cooldown lazily; this adds the
192
+ * proactive signal (#5/#8) + lets the daemon nudge a token refresh.
193
+ */
194
+ sweepRecoveries(now?: number): AccountRecoveryEvent[];
195
+ /** Admin-facing coarse status + cooldown-until for one account (secret-free). */
196
+ getStatus(providerId: string, accountId: string, now?: number): AccountHealthStatus;
197
+ /**
198
+ * Register a recovery listener (the OQ3 seam #5 webhooks + #8 health-cron
199
+ * consume). Returns an unsubscribe function. Two edges fire it:
200
+ * - TRAFFIC-DRIVEN: a `recordUpstreamOutcome` 2xx that clears an unhealthy
201
+ * account (guaranteed — the sweep would miss it, as the 2xx deletes the
202
+ * record first); and
203
+ * - PURE-IDLE: `sweepRecoveries` when an idle account's cooldown elapsed with
204
+ * no traffic (best-effort, on the 60s tick).
205
+ * A lazy `isSchedulable` read that heals an account does NOT emit (a request is
206
+ * already using it) — so consumers never double-count a recovery.
207
+ */
208
+ onRecovered(listener: AccountRecoveryListener): () => void;
209
+ private emit;
210
+ /**
211
+ * Register an anomaly listener (the ADDITIVE seam #5 webhooks consumes; the
212
+ * frozen `kind` discriminator on {@link AccountRecoveryEvent} anticipated it).
213
+ * Fires ONLY on the healthy→unhealthy EDGE inside `recordUpstreamOutcome`
214
+ * (de-duped: an already-unhealthy account escalating does NOT re-fire). Returns
215
+ * an unsubscribe function. Existing marking/recovery behavior is UNCHANGED.
216
+ */
217
+ onAnomaly(listener: AccountAnomalyListener): () => void;
218
+ private emitAnomaly;
219
+ /**
220
+ * Delete every EXPIRED timer field on the record in place; return whether any
221
+ * timer is STILL active (in the future). `blocked` is not a timer and is left
222
+ * untouched here (only an explicit reset / restart clears a ban).
223
+ */
224
+ private clearExpired;
225
+ private key;
226
+ private parseKey;
227
+ }
228
+ /** The shared tracker; lazily constructs a default-config instance on first use. */
229
+ declare function getSharedAccountHealth(): SubscriptionAccountHealth;
230
+ /** Install a config-tuned tracker at bootstrap (idempotent; last write wins). */
231
+ declare function setSharedAccountHealth(instance: SubscriptionAccountHealth): void;
232
+ /** Reset the shared singleton (tests / teardown only). */
233
+ declare function __resetSharedAccountHealthForTests(): void;
234
+
235
+ export { AUTH_ERROR_TTL_MS, type AccountAnomalyEvent, type AccountAnomalyListener, type AccountAnomalyState, type AccountHealthState, type AccountHealthStatus, type AccountRecoveryEvent, type AccountRecoveryListener, type HeadersLike, type HealthRecord, OVERLOAD_ENABLED_DEFAULT, OVERLOAD_TTL_MS, type RecordUpstreamOutcomeInput, SERVER_ERROR_TTL_MS, SubscriptionAccountHealth, type SubscriptionAccountHealthOptions, __resetSharedAccountHealthForTests, getSharedAccountHealth, resolveResetSeconds, setSharedAccountHealth };
@@ -0,0 +1,22 @@
1
+ import {
2
+ AUTH_ERROR_TTL_MS,
3
+ OVERLOAD_ENABLED_DEFAULT,
4
+ OVERLOAD_TTL_MS,
5
+ SERVER_ERROR_TTL_MS,
6
+ SubscriptionAccountHealth,
7
+ __resetSharedAccountHealthForTests,
8
+ getSharedAccountHealth,
9
+ resolveResetSeconds,
10
+ setSharedAccountHealth
11
+ } from "../chunk-5ENKBSWO.js";
12
+ export {
13
+ AUTH_ERROR_TTL_MS,
14
+ OVERLOAD_ENABLED_DEFAULT,
15
+ OVERLOAD_TTL_MS,
16
+ SERVER_ERROR_TTL_MS,
17
+ SubscriptionAccountHealth,
18
+ __resetSharedAccountHealthForTests,
19
+ getSharedAccountHealth,
20
+ resolveResetSeconds,
21
+ setSharedAccountHealth
22
+ };
@@ -1,9 +1,9 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
2
 
3
3
 
4
- var _chunkN3V2J5ZOcjs = require('../chunk-N3V2J5ZO.cjs');
4
+ var _chunkY6OM7EV7cjs = require('../chunk-Y6OM7EV7.cjs');
5
5
  require('../chunk-SVNDB62D.cjs');
6
6
 
7
7
 
8
8
 
9
- exports.SubscriptionAuthSource = _chunkN3V2J5ZOcjs.SubscriptionAuthSource; exports.stripAuthHeaders = _chunkN3V2J5ZOcjs.stripAuthHeaders;
9
+ exports.SubscriptionAuthSource = _chunkY6OM7EV7cjs.SubscriptionAuthSource; exports.stripAuthHeaders = _chunkY6OM7EV7cjs.stripAuthHeaders;
@@ -156,8 +156,9 @@ declare class SubscriptionAuthSource implements AuthSource {
156
156
  * model }` at the boundary.
157
157
  */
158
158
  applyHeaders(headers: Record<string, string>, hints: AuthApplyHints): Promise<void>;
159
- /** Delegate the 401-refresh decision to the bound strategy. */
160
- onUnauthorized(): Promise<boolean>;
159
+ /** Delegate the 401-refresh decision to the bound strategy, threading the
160
+ * session key so the account actually served is the one refreshed (D7). */
161
+ onUnauthorized(sessionKey?: string): Promise<boolean>;
161
162
  /** Resolve the upstream URL from the profile, when it provides one. */
162
163
  resolveUpstreamUrl(model: string): string | undefined;
163
164
  }
@@ -156,8 +156,9 @@ declare class SubscriptionAuthSource implements AuthSource {
156
156
  * model }` at the boundary.
157
157
  */
158
158
  applyHeaders(headers: Record<string, string>, hints: AuthApplyHints): Promise<void>;
159
- /** Delegate the 401-refresh decision to the bound strategy. */
160
- onUnauthorized(): Promise<boolean>;
159
+ /** Delegate the 401-refresh decision to the bound strategy, threading the
160
+ * session key so the account actually served is the one refreshed (D7). */
161
+ onUnauthorized(sessionKey?: string): Promise<boolean>;
161
162
  /** Resolve the upstream URL from the profile, when it provides one. */
162
163
  resolveUpstreamUrl(model: string): string | undefined;
163
164
  }
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  SubscriptionAuthSource,
3
3
  stripAuthHeaders
4
- } from "../chunk-74TMJA7Z.js";
4
+ } from "../chunk-N5QFKSDO.js";
5
5
  import "../chunk-AVYJ4YBN.js";
6
6
  export {
7
7
  SubscriptionAuthSource,
@@ -31,6 +31,24 @@ interface AuthApplyHints {
31
31
  upstreamUrl?: string;
32
32
  /** Resolved model id — same purpose as `upstreamUrl`. */
33
33
  resolvedModel?: string;
34
+ /**
35
+ * Stable per-conversation session key (subscription-account-scheduling, D5).
36
+ * When present it drives the account pool's sticky session affinity; absent ⇒
37
+ * pure priority/LRU selection (still correct — affinity only loses stickiness).
38
+ */
39
+ sessionKey?: string;
40
+ /**
41
+ * Per-request selection callback (subscription-account-health, D5;
42
+ * subscription-account-model-map, D3). The strategy invokes it with the
43
+ * EFFECTIVE account id it resolved so the relay can mark that account's health
44
+ * against the upstream outcome. The OPTIONAL 3rd `remappedModel` carries the
45
+ * selected account's ACTUAL upstream model when its `supportedModels` object
46
+ * remaps `resolvedModel` — the relay rewrites the outbound `body.model` to it on
47
+ * the pass-through / same-format path; absent ⇒ the body is forwarded verbatim.
48
+ * Absent callback ⇒ health simply isn't marked on that path (degrades to the
49
+ * pre-health behavior).
50
+ */
51
+ reportSelection?: (accountId: string, isActive: boolean, remappedModel?: string) => void;
34
52
  }
35
53
  interface AuthStrategy {
36
54
  /** Discriminator — also surfaced through `subscription:list` to renderers. */
@@ -52,8 +70,13 @@ interface AuthStrategy {
52
70
  *
53
71
  * Implementations SHOULD use a shared `RefreshMutex` to dedupe concurrent
54
72
  * refreshes so N parallel 401s collapse into one upstream refresh call.
73
+ *
74
+ * The OPTIONAL `sessionKey` (subscription-account-scheduling, D7) refreshes the
75
+ * account the request was ACTUALLY served by: when it resolves to a sticky
76
+ * non-active account the strategy refreshes THAT account by id; absent (or an
77
+ * active pick) ⇒ the active-account refresh, unchanged.
55
78
  */
56
- onUnauthorized(): Promise<boolean>;
79
+ onUnauthorized(sessionKey?: string): Promise<boolean>;
57
80
  /** Diagnostic surface for the `subscription:status` IPC. */
58
81
  describeStatus(): Promise<SubscriptionStatusEntry>;
59
82
  }
@@ -31,6 +31,24 @@ interface AuthApplyHints {
31
31
  upstreamUrl?: string;
32
32
  /** Resolved model id — same purpose as `upstreamUrl`. */
33
33
  resolvedModel?: string;
34
+ /**
35
+ * Stable per-conversation session key (subscription-account-scheduling, D5).
36
+ * When present it drives the account pool's sticky session affinity; absent ⇒
37
+ * pure priority/LRU selection (still correct — affinity only loses stickiness).
38
+ */
39
+ sessionKey?: string;
40
+ /**
41
+ * Per-request selection callback (subscription-account-health, D5;
42
+ * subscription-account-model-map, D3). The strategy invokes it with the
43
+ * EFFECTIVE account id it resolved so the relay can mark that account's health
44
+ * against the upstream outcome. The OPTIONAL 3rd `remappedModel` carries the
45
+ * selected account's ACTUAL upstream model when its `supportedModels` object
46
+ * remaps `resolvedModel` — the relay rewrites the outbound `body.model` to it on
47
+ * the pass-through / same-format path; absent ⇒ the body is forwarded verbatim.
48
+ * Absent callback ⇒ health simply isn't marked on that path (degrades to the
49
+ * pre-health behavior).
50
+ */
51
+ reportSelection?: (accountId: string, isActive: boolean, remappedModel?: string) => void;
34
52
  }
35
53
  interface AuthStrategy {
36
54
  /** Discriminator — also surfaced through `subscription:list` to renderers. */
@@ -52,8 +70,13 @@ interface AuthStrategy {
52
70
  *
53
71
  * Implementations SHOULD use a shared `RefreshMutex` to dedupe concurrent
54
72
  * refreshes so N parallel 401s collapse into one upstream refresh call.
73
+ *
74
+ * The OPTIONAL `sessionKey` (subscription-account-scheduling, D7) refreshes the
75
+ * account the request was ACTUALLY served by: when it resolves to a sticky
76
+ * non-active account the strategy refreshes THAT account by id; absent (or an
77
+ * active pick) ⇒ the active-account refresh, unchanged.
55
78
  */
56
- onUnauthorized(): Promise<boolean>;
79
+ onUnauthorized(sessionKey?: string): Promise<boolean>;
57
80
  /** Diagnostic surface for the `subscription:status` IPC. */
58
81
  describeStatus(): Promise<SubscriptionStatusEntry>;
59
82
  }
@@ -0,0 +1,14 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
+
3
+
4
+
5
+
6
+
7
+ var _chunkAEOZIDEBcjs = require('../chunk-AEOZIDEB.cjs');
8
+
9
+
10
+
11
+
12
+
13
+
14
+ exports.__resetAuditSinkForTests = _chunkAEOZIDEBcjs.__resetAuditSinkForTests; exports.getAuditCaptureConfig = _chunkAEOZIDEBcjs.getAuditCaptureConfig; exports.recordAudit = _chunkAEOZIDEBcjs.recordAudit; exports.setAuditCaptureConfig = _chunkAEOZIDEBcjs.setAuditCaptureConfig; exports.setAuditSink = _chunkAEOZIDEBcjs.setAuditSink;
@@ -0,0 +1,48 @@
1
+ import { AuditRecord, AuditConfig } from '@omnicross/contracts/audit-types';
2
+
3
+ /**
4
+ * auditSink — the module-slot sink + capture-config port for `@omnicross/core`
5
+ * (request-audit-log, design D5).
6
+ *
7
+ * The outbound server's post-response capture hook calls {@link recordAudit}
8
+ * with an assembled {@link AuditRecord}. The concrete `AuditWriter` (the
9
+ * date-rotated jsonl store) lives in the DAEMON and registers itself as the sink
10
+ * at bootstrap via {@link setAuditSink}. Core imports NOTHING from the daemon —
11
+ * exactly the `setWebhookSink` / `setUpstreamProxyResolver` precedent.
12
+ *
13
+ * A SECOND slot holds the active {@link AuditConfig} ({@link setAuditCaptureConfig}
14
+ * / {@link getAuditCaptureConfig}). The capture hook reads it to GATE assembly:
15
+ * absent/`enabled:false` ⇒ the hook does nothing (not even record assembly), so
16
+ * audit-disabled is byte-identical zero regression. The daemon sets BOTH slots
17
+ * together (config + sink) when audit is enabled and clears BOTH when disabled.
18
+ *
19
+ * HARD CONTRACT (design D5 / risk "the audit write adding latency"):
20
+ * {@link recordAudit} NEVER throws and NEVER awaits — it hands the record to the
21
+ * sink (which enqueues and returns immediately) and swallows any sink error, so
22
+ * a slow/failing/throwing writer can never affect the relay path. Absent a sink
23
+ * it is a pure no-op.
24
+ *
25
+ * @module @omnicross/core/pipeline/auditSink
26
+ */
27
+
28
+ /** The sink the daemon registers; it MUST enqueue-and-return (never block/throw). */
29
+ type AuditSink = (record: AuditRecord) => void;
30
+ /** Install (or clear, with `null`) the audit sink. Idempotent; last write wins. */
31
+ declare function setAuditSink(fn: AuditSink | null): void;
32
+ /**
33
+ * Install (or clear, with `null`) the active audit-capture config. `null` (or an
34
+ * `enabled:false` config) disables capture entirely — the hook does no work.
35
+ */
36
+ declare function setAuditCaptureConfig(config: AuditConfig | null): void;
37
+ /** The active capture config, or `null` when audit is disabled/unset. */
38
+ declare function getAuditCaptureConfig(): AuditConfig | null;
39
+ /**
40
+ * Hand one audit record to the registered sink. A no-op when no sink is set
41
+ * (zero regression). NEVER throws (a misbehaving sink is swallowed) and NEVER
42
+ * awaits (the sink enqueues + returns) — so no relay path is ever delayed.
43
+ */
44
+ declare function recordAudit(record: AuditRecord): void;
45
+ /** TEST SEAM — clear both slots so a suite starts from the no-audit baseline. */
46
+ declare function __resetAuditSinkForTests(): void;
47
+
48
+ export { type AuditSink, __resetAuditSinkForTests, getAuditCaptureConfig, recordAudit, setAuditCaptureConfig, setAuditSink };
@@ -0,0 +1,48 @@
1
+ import { AuditRecord, AuditConfig } from '@omnicross/contracts/audit-types';
2
+
3
+ /**
4
+ * auditSink — the module-slot sink + capture-config port for `@omnicross/core`
5
+ * (request-audit-log, design D5).
6
+ *
7
+ * The outbound server's post-response capture hook calls {@link recordAudit}
8
+ * with an assembled {@link AuditRecord}. The concrete `AuditWriter` (the
9
+ * date-rotated jsonl store) lives in the DAEMON and registers itself as the sink
10
+ * at bootstrap via {@link setAuditSink}. Core imports NOTHING from the daemon —
11
+ * exactly the `setWebhookSink` / `setUpstreamProxyResolver` precedent.
12
+ *
13
+ * A SECOND slot holds the active {@link AuditConfig} ({@link setAuditCaptureConfig}
14
+ * / {@link getAuditCaptureConfig}). The capture hook reads it to GATE assembly:
15
+ * absent/`enabled:false` ⇒ the hook does nothing (not even record assembly), so
16
+ * audit-disabled is byte-identical zero regression. The daemon sets BOTH slots
17
+ * together (config + sink) when audit is enabled and clears BOTH when disabled.
18
+ *
19
+ * HARD CONTRACT (design D5 / risk "the audit write adding latency"):
20
+ * {@link recordAudit} NEVER throws and NEVER awaits — it hands the record to the
21
+ * sink (which enqueues and returns immediately) and swallows any sink error, so
22
+ * a slow/failing/throwing writer can never affect the relay path. Absent a sink
23
+ * it is a pure no-op.
24
+ *
25
+ * @module @omnicross/core/pipeline/auditSink
26
+ */
27
+
28
+ /** The sink the daemon registers; it MUST enqueue-and-return (never block/throw). */
29
+ type AuditSink = (record: AuditRecord) => void;
30
+ /** Install (or clear, with `null`) the audit sink. Idempotent; last write wins. */
31
+ declare function setAuditSink(fn: AuditSink | null): void;
32
+ /**
33
+ * Install (or clear, with `null`) the active audit-capture config. `null` (or an
34
+ * `enabled:false` config) disables capture entirely — the hook does no work.
35
+ */
36
+ declare function setAuditCaptureConfig(config: AuditConfig | null): void;
37
+ /** The active capture config, or `null` when audit is disabled/unset. */
38
+ declare function getAuditCaptureConfig(): AuditConfig | null;
39
+ /**
40
+ * Hand one audit record to the registered sink. A no-op when no sink is set
41
+ * (zero regression). NEVER throws (a misbehaving sink is swallowed) and NEVER
42
+ * awaits (the sink enqueues + returns) — so no relay path is ever delayed.
43
+ */
44
+ declare function recordAudit(record: AuditRecord): void;
45
+ /** TEST SEAM — clear both slots so a suite starts from the no-audit baseline. */
46
+ declare function __resetAuditSinkForTests(): void;
47
+
48
+ export { type AuditSink, __resetAuditSinkForTests, getAuditCaptureConfig, recordAudit, setAuditCaptureConfig, setAuditSink };
@@ -0,0 +1,14 @@
1
+ import {
2
+ __resetAuditSinkForTests,
3
+ getAuditCaptureConfig,
4
+ recordAudit,
5
+ setAuditCaptureConfig,
6
+ setAuditSink
7
+ } from "../chunk-WNY4WMRF.js";
8
+ export {
9
+ __resetAuditSinkForTests,
10
+ getAuditCaptureConfig,
11
+ recordAudit,
12
+ setAuditCaptureConfig,
13
+ setAuditSink
14
+ };
@@ -0,0 +1,14 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
+
3
+
4
+
5
+
6
+
7
+ var _chunkL74VDN4Acjs = require('../chunk-L74VDN4A.cjs');
8
+
9
+
10
+
11
+
12
+
13
+
14
+ exports.__resetBillingSinkForTests = _chunkL74VDN4Acjs.__resetBillingSinkForTests; exports.getBillingCaptureConfig = _chunkL74VDN4Acjs.getBillingCaptureConfig; exports.publishBillingEvent = _chunkL74VDN4Acjs.publishBillingEvent; exports.setBillingCaptureConfig = _chunkL74VDN4Acjs.setBillingCaptureConfig; exports.setBillingSink = _chunkL74VDN4Acjs.setBillingSink;
@@ -0,0 +1,49 @@
1
+ import { BillingEvent, BillingConfig } from '@omnicross/contracts/billing-types';
2
+
3
+ /**
4
+ * billingEmit — the module-slot sink + capture-gate port for `@omnicross/core`
5
+ * (billing-event-stream, design D4/D7).
6
+ *
7
+ * The outbound server's post-response billing hook calls {@link publishBillingEvent}
8
+ * with an assembled {@link BillingEvent}. The concrete `BillingPublisher` (the
9
+ * durable-first jsonl ledger + built-in POST) lives in the DAEMON and registers
10
+ * itself as the sink at bootstrap via {@link setBillingSink}. Core imports NOTHING
11
+ * from the daemon — exactly the `setWebhookSink` / `setAuditSink` precedent.
12
+ *
13
+ * A SECOND slot holds the active {@link BillingConfig} ({@link setBillingCaptureConfig}
14
+ * / {@link getBillingCaptureConfig}). The capture hook reads it to GATE assembly:
15
+ * absent/`enabled:false` ⇒ the hook does nothing (not even event assembly), so
16
+ * billing-disabled is byte-identical zero regression. The daemon sets BOTH slots
17
+ * together (config + sink) when billing is enabled and clears BOTH when disabled.
18
+ *
19
+ * HARD CONTRACT (design D7 / risk "a billing POST blocking the request path"):
20
+ * {@link publishBillingEvent} NEVER throws and NEVER awaits — it hands the event
21
+ * to the sink (which durably appends + enqueues delivery and returns immediately)
22
+ * and swallows any sink error, so a slow/failing/throwing publisher can never
23
+ * affect the relay path. Absent a sink it is a pure no-op.
24
+ *
25
+ * @module @omnicross/core/pipeline/billingEmit
26
+ */
27
+
28
+ /** The sink the daemon registers; it MUST durably-append-and-return (never block/throw). */
29
+ type BillingSink = (event: BillingEvent) => void;
30
+ /** Install (or clear, with `null`) the billing sink. Idempotent; last write wins. */
31
+ declare function setBillingSink(fn: BillingSink | null): void;
32
+ /**
33
+ * Install (or clear, with `null`) the active billing config. `null` (or an
34
+ * `enabled:false` config) disables the capture entirely — the hook does no work.
35
+ */
36
+ declare function setBillingCaptureConfig(config: BillingConfig | null): void;
37
+ /** The active billing config, or `null` when billing is disabled/unset. */
38
+ declare function getBillingCaptureConfig(): BillingConfig | null;
39
+ /**
40
+ * Publish one billing event to the registered sink. A no-op when no sink is set
41
+ * (zero regression). NEVER throws (a misbehaving sink is swallowed) and NEVER
42
+ * awaits (the sink durably appends + enqueues delivery, then returns) — so no
43
+ * relay path is ever delayed.
44
+ */
45
+ declare function publishBillingEvent(event: BillingEvent): void;
46
+ /** TEST SEAM — clear both slots so a suite starts from the no-billing baseline. */
47
+ declare function __resetBillingSinkForTests(): void;
48
+
49
+ export { type BillingSink, __resetBillingSinkForTests, getBillingCaptureConfig, publishBillingEvent, setBillingCaptureConfig, setBillingSink };
@@ -0,0 +1,49 @@
1
+ import { BillingEvent, BillingConfig } from '@omnicross/contracts/billing-types';
2
+
3
+ /**
4
+ * billingEmit — the module-slot sink + capture-gate port for `@omnicross/core`
5
+ * (billing-event-stream, design D4/D7).
6
+ *
7
+ * The outbound server's post-response billing hook calls {@link publishBillingEvent}
8
+ * with an assembled {@link BillingEvent}. The concrete `BillingPublisher` (the
9
+ * durable-first jsonl ledger + built-in POST) lives in the DAEMON and registers
10
+ * itself as the sink at bootstrap via {@link setBillingSink}. Core imports NOTHING
11
+ * from the daemon — exactly the `setWebhookSink` / `setAuditSink` precedent.
12
+ *
13
+ * A SECOND slot holds the active {@link BillingConfig} ({@link setBillingCaptureConfig}
14
+ * / {@link getBillingCaptureConfig}). The capture hook reads it to GATE assembly:
15
+ * absent/`enabled:false` ⇒ the hook does nothing (not even event assembly), so
16
+ * billing-disabled is byte-identical zero regression. The daemon sets BOTH slots
17
+ * together (config + sink) when billing is enabled and clears BOTH when disabled.
18
+ *
19
+ * HARD CONTRACT (design D7 / risk "a billing POST blocking the request path"):
20
+ * {@link publishBillingEvent} NEVER throws and NEVER awaits — it hands the event
21
+ * to the sink (which durably appends + enqueues delivery and returns immediately)
22
+ * and swallows any sink error, so a slow/failing/throwing publisher can never
23
+ * affect the relay path. Absent a sink it is a pure no-op.
24
+ *
25
+ * @module @omnicross/core/pipeline/billingEmit
26
+ */
27
+
28
+ /** The sink the daemon registers; it MUST durably-append-and-return (never block/throw). */
29
+ type BillingSink = (event: BillingEvent) => void;
30
+ /** Install (or clear, with `null`) the billing sink. Idempotent; last write wins. */
31
+ declare function setBillingSink(fn: BillingSink | null): void;
32
+ /**
33
+ * Install (or clear, with `null`) the active billing config. `null` (or an
34
+ * `enabled:false` config) disables the capture entirely — the hook does no work.
35
+ */
36
+ declare function setBillingCaptureConfig(config: BillingConfig | null): void;
37
+ /** The active billing config, or `null` when billing is disabled/unset. */
38
+ declare function getBillingCaptureConfig(): BillingConfig | null;
39
+ /**
40
+ * Publish one billing event to the registered sink. A no-op when no sink is set
41
+ * (zero regression). NEVER throws (a misbehaving sink is swallowed) and NEVER
42
+ * awaits (the sink durably appends + enqueues delivery, then returns) — so no
43
+ * relay path is ever delayed.
44
+ */
45
+ declare function publishBillingEvent(event: BillingEvent): void;
46
+ /** TEST SEAM — clear both slots so a suite starts from the no-billing baseline. */
47
+ declare function __resetBillingSinkForTests(): void;
48
+
49
+ export { type BillingSink, __resetBillingSinkForTests, getBillingCaptureConfig, publishBillingEvent, setBillingCaptureConfig, setBillingSink };
@@ -0,0 +1,14 @@
1
+ import {
2
+ __resetBillingSinkForTests,
3
+ getBillingCaptureConfig,
4
+ publishBillingEvent,
5
+ setBillingCaptureConfig,
6
+ setBillingSink
7
+ } from "../chunk-TKJMZBAH.js";
8
+ export {
9
+ __resetBillingSinkForTests,
10
+ getBillingCaptureConfig,
11
+ publishBillingEvent,
12
+ setBillingCaptureConfig,
13
+ setBillingSink
14
+ };