@omnicross/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +57 -0
  3. package/README.md +15 -0
  4. package/dist/ApiKeyPoolService-BmMkau07.d.cts +170 -0
  5. package/dist/ApiKeyPoolService-BmMkau07.d.ts +170 -0
  6. package/dist/ProviderProxy-f_8ziIhW.d.cts +120 -0
  7. package/dist/ProviderProxy-vjt8sQQk.d.ts +120 -0
  8. package/dist/SubscriptionAuthSource-Cr4fVEYY.d.cts +264 -0
  9. package/dist/SubscriptionAuthSource-D89zmiSS.d.ts +264 -0
  10. package/dist/auth/GeminiCodeAssistProjectResolver.cjs +218 -0
  11. package/dist/auth/GeminiCodeAssistProjectResolver.d.cts +68 -0
  12. package/dist/auth/GeminiCodeAssistProjectResolver.d.ts +68 -0
  13. package/dist/auth/GeminiCodeAssistProjectResolver.js +189 -0
  14. package/dist/completion/ApiKeyPoolService.cjs +331 -0
  15. package/dist/completion/ApiKeyPoolService.d.cts +2 -0
  16. package/dist/completion/ApiKeyPoolService.d.ts +2 -0
  17. package/dist/completion/ApiKeyPoolService.js +306 -0
  18. package/dist/completion.cjs +4027 -0
  19. package/dist/completion.d.cts +17 -0
  20. package/dist/completion.d.ts +17 -0
  21. package/dist/completion.js +3983 -0
  22. package/dist/index-BTSmc9Sm.d.ts +645 -0
  23. package/dist/index-DXazdTzZ.d.cts +645 -0
  24. package/dist/index.cjs +10428 -0
  25. package/dist/index.d.cts +128 -0
  26. package/dist/index.d.ts +128 -0
  27. package/dist/index.js +10339 -0
  28. package/dist/outbound-api/subscriptionRegistryPort.cjs +38 -0
  29. package/dist/outbound-api/subscriptionRegistryPort.d.cts +73 -0
  30. package/dist/outbound-api/subscriptionRegistryPort.d.ts +73 -0
  31. package/dist/outbound-api/subscriptionRegistryPort.js +12 -0
  32. package/dist/outbound-api.cjs +5264 -0
  33. package/dist/outbound-api.d.cts +320 -0
  34. package/dist/outbound-api.d.ts +320 -0
  35. package/dist/outbound-api.js +5218 -0
  36. package/dist/pipeline/SubscriptionAuthSource.cjs +131 -0
  37. package/dist/pipeline/SubscriptionAuthSource.d.cts +3 -0
  38. package/dist/pipeline/SubscriptionAuthSource.d.ts +3 -0
  39. package/dist/pipeline/SubscriptionAuthSource.js +103 -0
  40. package/dist/pipeline/SubscriptionAuthStrategy.cjs +18 -0
  41. package/dist/pipeline/SubscriptionAuthStrategy.d.cts +61 -0
  42. package/dist/pipeline/SubscriptionAuthStrategy.d.ts +61 -0
  43. package/dist/pipeline/SubscriptionAuthStrategy.js +0 -0
  44. package/dist/ports/gemini-code-assist-resolver.cjs +38 -0
  45. package/dist/ports/gemini-code-assist-resolver.d.cts +26 -0
  46. package/dist/ports/gemini-code-assist-resolver.d.ts +26 -0
  47. package/dist/ports/gemini-code-assist-resolver.js +12 -0
  48. package/dist/ports.cjs +18 -0
  49. package/dist/ports.d.cts +15 -0
  50. package/dist/ports.d.ts +15 -0
  51. package/dist/ports.js +0 -0
  52. package/dist/provider-proxy/ingress/providerProxyShared.cjs +2958 -0
  53. package/dist/provider-proxy/ingress/providerProxyShared.d.cts +77 -0
  54. package/dist/provider-proxy/ingress/providerProxyShared.d.ts +77 -0
  55. package/dist/provider-proxy/ingress/providerProxyShared.js +2925 -0
  56. package/dist/provider-proxy/matchText.cjs +73 -0
  57. package/dist/provider-proxy/matchText.d.cts +47 -0
  58. package/dist/provider-proxy/matchText.d.ts +47 -0
  59. package/dist/provider-proxy/matchText.js +45 -0
  60. package/dist/provider-proxy/types.cjs +18 -0
  61. package/dist/provider-proxy/types.d.cts +12 -0
  62. package/dist/provider-proxy/types.d.ts +12 -0
  63. package/dist/provider-proxy/types.js +0 -0
  64. package/dist/provider-proxy.cjs +4667 -0
  65. package/dist/provider-proxy.d.cts +69 -0
  66. package/dist/provider-proxy.d.ts +69 -0
  67. package/dist/provider-proxy.js +4636 -0
  68. package/dist/serializeError.cjs +82 -0
  69. package/dist/serializeError.d.cts +24 -0
  70. package/dist/serializeError.d.ts +24 -0
  71. package/dist/serializeError.js +57 -0
  72. package/dist/sse-parser.cjs +456 -0
  73. package/dist/sse-parser.d.cts +143 -0
  74. package/dist/sse-parser.d.ts +143 -0
  75. package/dist/sse-parser.js +430 -0
  76. package/dist/transformer/TransformerChainExecutor.cjs +321 -0
  77. package/dist/transformer/TransformerChainExecutor.d.cts +104 -0
  78. package/dist/transformer/TransformerChainExecutor.d.ts +104 -0
  79. package/dist/transformer/TransformerChainExecutor.js +294 -0
  80. package/dist/transformer/TransformerService.cjs +290 -0
  81. package/dist/transformer/TransformerService.d.cts +138 -0
  82. package/dist/transformer/TransformerService.d.ts +138 -0
  83. package/dist/transformer/TransformerService.js +265 -0
  84. package/dist/transformer/transformers/GeminiCodeAssistTransformer.cjs +1115 -0
  85. package/dist/transformer/transformers/GeminiCodeAssistTransformer.d.cts +102 -0
  86. package/dist/transformer/transformers/GeminiCodeAssistTransformer.d.ts +102 -0
  87. package/dist/transformer/transformers/GeminiCodeAssistTransformer.js +1085 -0
  88. package/dist/transformer/transformers/GeminiTransformer.cjs +1013 -0
  89. package/dist/transformer/transformers/GeminiTransformer.d.cts +70 -0
  90. package/dist/transformer/transformers/GeminiTransformer.d.ts +70 -0
  91. package/dist/transformer/transformers/GeminiTransformer.js +986 -0
  92. package/dist/transformer/transformers/OpenAIResponseTransformer.cjs +538 -0
  93. package/dist/transformer/transformers/OpenAIResponseTransformer.d.cts +53 -0
  94. package/dist/transformer/transformers/OpenAIResponseTransformer.d.ts +53 -0
  95. package/dist/transformer/transformers/OpenAIResponseTransformer.js +513 -0
  96. package/dist/transformer/transformers/OpenCodeGoTransformer.cjs +73 -0
  97. package/dist/transformer/transformers/OpenCodeGoTransformer.d.cts +51 -0
  98. package/dist/transformer/transformers/OpenCodeGoTransformer.d.ts +51 -0
  99. package/dist/transformer/transformers/OpenCodeGoTransformer.js +48 -0
  100. package/dist/transformer/types.cjs +18 -0
  101. package/dist/transformer/types.d.cts +405 -0
  102. package/dist/transformer/types.d.ts +405 -0
  103. package/dist/transformer/types.js +0 -0
  104. package/dist/transformer.cjs +3736 -0
  105. package/dist/transformer.d.cts +33 -0
  106. package/dist/transformer.d.ts +33 -0
  107. package/dist/transformer.js +3712 -0
  108. package/dist/types-CGGrKqC_.d.cts +142 -0
  109. package/dist/types-CbCN2NQP.d.ts +142 -0
  110. package/dist/types-DCzHkhJt.d.ts +467 -0
  111. package/dist/types-DZIQbgp0.d.cts +467 -0
  112. package/dist/usage-event-sink-BX7FE1NL.d.cts +59 -0
  113. package/dist/usage-event-sink-BX7FE1NL.d.ts +59 -0
  114. package/package.json +62 -0
@@ -0,0 +1,320 @@
1
+ import { E as EndpointRoutingConfig, a as OutboundApiDeps, e as OutboundApiServerStatus, g as OutboundFormatUrls, d as OutboundApiServerConfig, O as OutboundKeyDb, b as OutboundApiKeyCreated, R as RequestRole, f as OutboundEndpoint } from './types-CGGrKqC_.cjs';
2
+ export { c as OutboundApiKeyInfo, h as OutboundKeyDbRow } from './types-CGGrKqC_.cjs';
3
+ import { I as IngressFormat, P as ProviderConfigSource, g as RouteContext } from './types-DZIQbgp0.cjs';
4
+ import { SubscriptionProviderId } from '@omnicross/contracts/subscription-types';
5
+ import './ProviderProxy-f_8ziIhW.cjs';
6
+ import 'node:http';
7
+ import '@omnicross/contracts/completion-types';
8
+ import '@omnicross/contracts/usage-types';
9
+ import './ApiKeyPoolService-BmMkau07.cjs';
10
+ import '@omnicross/contracts/llm-config';
11
+ import './SubscriptionAuthSource-Cr4fVEYY.cjs';
12
+ import './pipeline/SubscriptionAuthStrategy.cjs';
13
+ import './transformer/types.cjs';
14
+ import './transformer/TransformerService.cjs';
15
+ import '@omnicross/contracts/websearch-types';
16
+
17
+ /**
18
+ * OutboundApiServer — the external-facing HTTP listener for the outbound API
19
+ * server (`outbound-api-server`, design D1/D4/D5).
20
+ *
21
+ * A SEPARATE long-lived `http.Server` (distinct from the resident loopback
22
+ * `ProviderProxy`). It binds `127.0.0.1` by default, or `0.0.0.0` when network
23
+ * binding is enabled. Every request — INCLUDING loopback — is authenticated by
24
+ * a named API key (no loopback bypass). Each authenticated request mints a route
25
+ * on the SHARED `ProviderProxyRouteMap` and delegates to the existing
26
+ * `routeRequest()` dispatch so the four ingress parsers + transformer are
27
+ * reused (one conversion stack).
28
+ *
29
+ * Lifecycle:
30
+ * - `applyConfig({ enabled, networkBinding, endpoints, port })` — restart ONLY
31
+ * when the bind address or port changes; per-endpoint routing config is read
32
+ * live per request (no restart).
33
+ * - `getStatus()` — running, actual port, loopback + optional LAN URLs, the
34
+ * four format URLs.
35
+ * - `EADDRINUSE` → fall back to an ephemeral port, persist it (via the caller's
36
+ * `onPortChange`), and surface the actual port.
37
+ *
38
+ * @module outbound-api/OutboundApiServer
39
+ */
40
+
41
+ /** Fixed default port (design D5). Persisted + configurable. */
42
+ declare const DEFAULT_OUTBOUND_PORT = 8765;
43
+ /** Per-call apply config (the persisted server config minus runtime state). */
44
+ interface ApplyConfigInput {
45
+ enabled: boolean;
46
+ networkBinding: boolean;
47
+ endpoints: EndpointRoutingConfig[];
48
+ port?: number;
49
+ }
50
+ declare class OutboundApiServer {
51
+ private readonly deps;
52
+ /** Called when the actual bound port differs from the requested one. */
53
+ private readonly onPortChange?;
54
+ private server;
55
+ private boundPort;
56
+ private boundAddr;
57
+ private endpoints;
58
+ private readonly rateLimiter;
59
+ constructor(deps: OutboundApiDeps,
60
+ /** Called when the actual bound port differs from the requested one. */
61
+ onPortChange?: ((port: number) => void) | undefined);
62
+ /**
63
+ * Apply a config. Restarts the listener ONLY when the bind address or port
64
+ * changes (or when toggling enabled); per-endpoint routing config is updated
65
+ * in place (read live per request — no restart).
66
+ */
67
+ applyConfig(input: ApplyConfigInput): Promise<void>;
68
+ /** Start the listener on `bindAddr:port`, falling back on EADDRINUSE. */
69
+ start(bindAddr: string, port: number): Promise<number>;
70
+ /** Bind once; on EADDRINUSE retry with an ephemeral port (port 0). */
71
+ private listen;
72
+ /** Per-request handler. Auth is enforced on EVERY request (incl. loopback). */
73
+ private onRequest;
74
+ /** Stop the listener and release the port. */
75
+ stop(): Promise<void>;
76
+ /** A live status snapshot for the Settings tab. */
77
+ getStatus(): OutboundApiServerStatus;
78
+ }
79
+ /** Build the four format endpoint URLs for a base URL. */
80
+ declare function formatUrls(base: string): OutboundFormatUrls;
81
+
82
+ /**
83
+ * apiServerConfig — load / save / default the outbound API server config
84
+ * (`outbound-api-server`, design D4).
85
+ *
86
+ * The config (`{ enabled, networkBinding, endpoints, port }`) is persisted via
87
+ * a small key/value store (the app SettingsService) under a single key, so it
88
+ * survives restart. Defaults: disabled, loopback, four endpoints with empty
89
+ * models + `useSubscription` OFF, default port. Shared by the router and the
90
+ * bootstrap wiring so both read/write the same shape.
91
+ *
92
+ * @module outbound-api/apiServerConfig
93
+ */
94
+
95
+ /** The settings key the config persists under. */
96
+ declare const OUTBOUND_API_SERVER_CONFIG_KEY = "outboundApiServer.config";
97
+ /** Structural subset of the settings store the config loader needs. */
98
+ interface ApiServerSettingsStore {
99
+ get<T = unknown>(key: string): Promise<T | undefined>;
100
+ set<T = unknown>(key: string, value: T): Promise<void>;
101
+ }
102
+ /** The default server config: disabled, loopback, four blank endpoints. */
103
+ declare function defaultServerConfig(): OutboundApiServerConfig;
104
+ /**
105
+ * Normalize a (possibly partial / legacy) persisted config to the full shape:
106
+ * ensure all four endpoints exist, `useSubscription` defaults OFF, and a port
107
+ * is present.
108
+ */
109
+ declare function normalizeServerConfig(raw: Partial<OutboundApiServerConfig> | undefined | null): OutboundApiServerConfig;
110
+ /** Load the persisted config (normalized), defaulting on a missing/blank key. */
111
+ declare function loadServerConfig(store: ApiServerSettingsStore): Promise<OutboundApiServerConfig>;
112
+ /** Persist the config. */
113
+ declare function saveServerConfig(store: ApiServerSettingsStore, config: OutboundApiServerConfig): Promise<void>;
114
+ /** Apply a partial patch to a config, returning the merged whole. */
115
+ declare function mergeServerConfig(current: OutboundApiServerConfig, patch: Partial<OutboundApiServerConfig>): OutboundApiServerConfig;
116
+
117
+ /**
118
+ * outboundApiKeyAuth — named-key generation, hashing, and verification for the
119
+ * outbound API server (`outbound-api-server`).
120
+ *
121
+ * Keys are 128-bit+ random secrets (`sk-omnicross-<base62>`), NOT human passwords,
122
+ * so a single fast hash (sha256) is sufficient and keeps the hot auth path
123
+ * cheap (design D3). Only the hash + a short display prefix are persisted; the
124
+ * full plaintext is returned exactly once at creation.
125
+ *
126
+ * @module outbound-api/outboundApiKeyAuth
127
+ */
128
+
129
+ /** Hash a presented/generated secret (sha256 hex). */
130
+ declare function hashKey(secret: string): string;
131
+ /**
132
+ * Create + persist a named key. Returns the plaintext ONCE; only the hash +
133
+ * prefix are stored.
134
+ */
135
+ declare function createNamedKey(db: OutboundKeyDb, name: string): Promise<OutboundApiKeyCreated>;
136
+ /** The id of a verified key (for rate-limiting + last-used bookkeeping). */
137
+ interface VerifiedKey {
138
+ id: string;
139
+ }
140
+ /**
141
+ * Verify a presented key against the DB. Matches by hash where the stored row
142
+ * is enabled AND not revoked (the DB query enforces this). On success bumps
143
+ * `lastUsedAt` (best-effort, fire-and-forget) and returns the key id; returns
144
+ * `null` on any miss / disabled / revoked key.
145
+ */
146
+ declare function verifyPresentedKey(db: OutboundKeyDb, presentedKey: string | undefined): Promise<VerifiedKey | null>;
147
+
148
+ /**
149
+ * outboundRateLimiter — per-API-key in-memory sliding-window rate limiter for
150
+ * the outbound API server (`outbound-api-server`, design D6).
151
+ *
152
+ * A simple fixed-size sliding window keyed by `apiKeyId`. In-memory only (resets
153
+ * on app restart, acceptable for v1). Exceeding the limit returns a deny with a
154
+ * `Retry-After` (seconds). Limits are conservative defaults, not user-
155
+ * configurable in v1.
156
+ *
157
+ * @module outbound-api/outboundRateLimiter
158
+ */
159
+ interface RateLimitDecision {
160
+ allowed: boolean;
161
+ /** Seconds until the window frees up (only meaningful when `!allowed`). */
162
+ retryAfterSeconds: number;
163
+ }
164
+ interface RateLimiterOptions {
165
+ windowMs?: number;
166
+ maxRequests?: number;
167
+ }
168
+ /**
169
+ * Per-key sliding-window limiter. `check(apiKeyId)` records the request and
170
+ * returns whether it is allowed.
171
+ */
172
+ declare class OutboundRateLimiter {
173
+ private readonly windowMs;
174
+ private readonly maxRequests;
175
+ /** apiKeyId → ascending request timestamps within the current window. */
176
+ private readonly hits;
177
+ constructor(options?: RateLimiterOptions);
178
+ /**
179
+ * Record a request for `apiKeyId` and decide whether it is allowed. Prunes
180
+ * timestamps older than the window first; when allowed, the request's
181
+ * timestamp is appended.
182
+ */
183
+ check(apiKeyId: string, now?: number): RateLimitDecision;
184
+ /** Drop all recorded state (tests / teardown). */
185
+ reset(): void;
186
+ }
187
+
188
+ /**
189
+ * roleDetection — classify an outbound request's ROLE (vision / background /
190
+ * default) so the route resolver can pick the endpoint's model for that role
191
+ * (`outbound-api-server`, design D2).
192
+ *
193
+ * Precedence: vision > background > default.
194
+ * - vision — the body carries image/vision content parts (per-format
195
+ * detection). The route resolver applies the vision→default
196
+ * fallback when the endpoint has no vision model.
197
+ * - background — the requested model id is in the endpoint's optional
198
+ * background-model-id override list (human decision after the
199
+ * proposal), OR the registry small/haiku-class name signal
200
+ * matches (Claude Code's haiku probe sends exactly this).
201
+ * - default — everything else.
202
+ *
203
+ * Each ingress's body shape is handled here in ONE place.
204
+ *
205
+ * @module outbound-api/roleDetection
206
+ */
207
+
208
+ /**
209
+ * Detect the request's role. `backgroundModelIds` is the endpoint's optional
210
+ * override list: when an incoming requested model id matches an entry there, the
211
+ * request is BACKGROUND regardless of the name signal; otherwise the registry
212
+ * small/haiku-class name signal is the baseline. Precedence vision > background
213
+ * > default.
214
+ */
215
+ declare function detectRequestRole(ingressFormat: IngressFormat, body: Record<string, unknown>, options?: {
216
+ backgroundModelIds?: string[];
217
+ }): RequestRole;
218
+ /** Map a settings endpoint id to the provider-proxy ingress format. */
219
+ declare function endpointToIngressFormat(endpoint: 'chat' | 'responses' | 'messages' | 'gemini'): IngressFormat;
220
+
221
+ /**
222
+ * subscriptionSupport — single source of truth for (a) which provider ids are
223
+ * subscription-backed and (b) which outbound endpoints can soundly serve a
224
+ * subscription route (`outbound-api-server`, design D2 + review M1/m1).
225
+ *
226
+ * The subscription-id catalog mirrors `SubscriptionProviderRegistry`'s ids; the
227
+ * static fast-path is backstopped at runtime by a live registry lookup (see
228
+ * `isSubscriptionProviderId`) so it degrades gracefully if it drifts. Both
229
+ * `routeResolver` and `apiServerHandlers` import from here — no duplicated set.
230
+ *
231
+ * @module outbound-api/subscriptionSupport
232
+ */
233
+
234
+ /**
235
+ * The registry-aligned subscription provider ids — the outbound layer's SSOT
236
+ * fast-path. NOTE: `SubscriptionProviderId` is currently a `z.string()` alias,
237
+ * so this `readonly SubscriptionProviderId[]` typing does NOT compile-enforce
238
+ * alignment with the registry; drift is instead tolerated at runtime by
239
+ * `isSubscriptionProviderId`'s live `registry.getProfile` fallback.
240
+ */
241
+ declare const SUBSCRIPTION_PROVIDER_IDS: readonly SubscriptionProviderId[];
242
+ /**
243
+ * True when a provider id refers to a subscription-backed provider. Checks the
244
+ * registry-aligned static catalog first, then falls back to a live registry
245
+ * lookup (so a registry addition is honored even before this list is updated).
246
+ *
247
+ * NOTE (review m2): this classifies by id string. The resolver MUST confirm the
248
+ * id does not resolve to a real BYO provider row before treating it as
249
+ * subscription-backed — see `routeResolver` (BYO rows win).
250
+ */
251
+ declare function isSubscriptionProviderId(providerId: string): boolean;
252
+ /** True when the endpoint's ingress can soundly serve a subscription route. */
253
+ declare function endpointSupportsSubscription(endpoint: OutboundEndpoint): boolean;
254
+
255
+ /**
256
+ * routeResolver — turn an endpoint's `EndpointRoutingConfig` + the detected
257
+ * request role into a `RouteContext` for the shared `provider-proxy` dispatch
258
+ * (`outbound-api-server`, design D2).
259
+ *
260
+ * Steps:
261
+ * 1. Pick the model for the role (vision → default fallback when `visionModel`
262
+ * is unset; vision is optional).
263
+ * 2. Parse the chosen `"providerId,modelId"` ref.
264
+ * 3. Gate by `useSubscription`: OFF → only BYO-key providers are eligible
265
+ * (subscription-backed providers excluded); ON → subscription-backed
266
+ * providers are eligible.
267
+ * 4. Resolve the provider (BYO via the `ProviderConfigSource`; subscription via the
268
+ * subscription registry) and build the `RouteContext` exactly as the
269
+ * internal callers do.
270
+ * 5. Return a clear `503`-style error when the role's required model is unset,
271
+ * the provider is unavailable, or the subscription gate excludes it.
272
+ *
273
+ * @module outbound-api/routeResolver
274
+ */
275
+
276
+ /** A 503-style resolution failure. */
277
+ interface RouteResolveError {
278
+ status: number;
279
+ message: string;
280
+ }
281
+ type RouteResolveResult = {
282
+ ok: true;
283
+ route: RouteContext;
284
+ usedRole: RequestRole;
285
+ } | {
286
+ ok: false;
287
+ error: RouteResolveError;
288
+ };
289
+ /** Resolve a route for one authenticated outbound request. */
290
+ declare function resolveRoute(args: {
291
+ config: EndpointRoutingConfig;
292
+ role: RequestRole;
293
+ ingressFormat: IngressFormat;
294
+ llmConfig: ProviderConfigSource;
295
+ sessionId?: string | null;
296
+ }): Promise<RouteResolveResult>;
297
+
298
+ /**
299
+ * Outbound API server module — barrel + singleton accessor.
300
+ *
301
+ * The outbound server is constructed ONCE at app bootstrap (sharing the
302
+ * resident `ProviderProxy`'s route map + deps) and started only when the
303
+ * persisted `enabled` setting is true. `getOutboundApiServer(deps)` mirrors
304
+ * `getProviderProxy()`: the first call constructs the instance from the supplied
305
+ * deps; later calls return it.
306
+ *
307
+ * @module outbound-api/index
308
+ */
309
+
310
+ /**
311
+ * Get (or lazily construct) the outbound API server singleton.
312
+ *
313
+ * @param deps Required on the FIRST call (app bootstrap). Ignored afterward.
314
+ * @param onPortChange Optional persistence hook for the EADDRINUSE fallback.
315
+ */
316
+ declare function getOutboundApiServer(deps?: OutboundApiDeps, onPortChange?: (port: number) => void): OutboundApiServer;
317
+ /** Reset the singleton (tests / teardown only). */
318
+ declare function __resetOutboundApiServerForTests(): void;
319
+
320
+ export { type ApiServerSettingsStore, type ApplyConfigInput, DEFAULT_OUTBOUND_PORT, EndpointRoutingConfig, OUTBOUND_API_SERVER_CONFIG_KEY, OutboundApiDeps, OutboundApiKeyCreated, OutboundApiServer, OutboundApiServerConfig, OutboundApiServerStatus, OutboundEndpoint, OutboundFormatUrls, OutboundKeyDb, OutboundRateLimiter, RequestRole, SUBSCRIPTION_PROVIDER_IDS, __resetOutboundApiServerForTests, createNamedKey, defaultServerConfig, detectRequestRole, endpointSupportsSubscription, endpointToIngressFormat, formatUrls, getOutboundApiServer, hashKey, isSubscriptionProviderId, loadServerConfig, mergeServerConfig, normalizeServerConfig, resolveRoute, saveServerConfig, verifyPresentedKey };
@@ -0,0 +1,320 @@
1
+ import { E as EndpointRoutingConfig, a as OutboundApiDeps, e as OutboundApiServerStatus, g as OutboundFormatUrls, d as OutboundApiServerConfig, O as OutboundKeyDb, b as OutboundApiKeyCreated, R as RequestRole, f as OutboundEndpoint } from './types-CbCN2NQP.js';
2
+ export { c as OutboundApiKeyInfo, h as OutboundKeyDbRow } from './types-CbCN2NQP.js';
3
+ import { I as IngressFormat, P as ProviderConfigSource, g as RouteContext } from './types-DCzHkhJt.js';
4
+ import { SubscriptionProviderId } from '@omnicross/contracts/subscription-types';
5
+ import './ProviderProxy-vjt8sQQk.js';
6
+ import 'node:http';
7
+ import '@omnicross/contracts/completion-types';
8
+ import '@omnicross/contracts/usage-types';
9
+ import './ApiKeyPoolService-BmMkau07.js';
10
+ import '@omnicross/contracts/llm-config';
11
+ import './SubscriptionAuthSource-D89zmiSS.js';
12
+ import './pipeline/SubscriptionAuthStrategy.js';
13
+ import './transformer/types.js';
14
+ import './transformer/TransformerService.js';
15
+ import '@omnicross/contracts/websearch-types';
16
+
17
+ /**
18
+ * OutboundApiServer — the external-facing HTTP listener for the outbound API
19
+ * server (`outbound-api-server`, design D1/D4/D5).
20
+ *
21
+ * A SEPARATE long-lived `http.Server` (distinct from the resident loopback
22
+ * `ProviderProxy`). It binds `127.0.0.1` by default, or `0.0.0.0` when network
23
+ * binding is enabled. Every request — INCLUDING loopback — is authenticated by
24
+ * a named API key (no loopback bypass). Each authenticated request mints a route
25
+ * on the SHARED `ProviderProxyRouteMap` and delegates to the existing
26
+ * `routeRequest()` dispatch so the four ingress parsers + transformer are
27
+ * reused (one conversion stack).
28
+ *
29
+ * Lifecycle:
30
+ * - `applyConfig({ enabled, networkBinding, endpoints, port })` — restart ONLY
31
+ * when the bind address or port changes; per-endpoint routing config is read
32
+ * live per request (no restart).
33
+ * - `getStatus()` — running, actual port, loopback + optional LAN URLs, the
34
+ * four format URLs.
35
+ * - `EADDRINUSE` → fall back to an ephemeral port, persist it (via the caller's
36
+ * `onPortChange`), and surface the actual port.
37
+ *
38
+ * @module outbound-api/OutboundApiServer
39
+ */
40
+
41
+ /** Fixed default port (design D5). Persisted + configurable. */
42
+ declare const DEFAULT_OUTBOUND_PORT = 8765;
43
+ /** Per-call apply config (the persisted server config minus runtime state). */
44
+ interface ApplyConfigInput {
45
+ enabled: boolean;
46
+ networkBinding: boolean;
47
+ endpoints: EndpointRoutingConfig[];
48
+ port?: number;
49
+ }
50
+ declare class OutboundApiServer {
51
+ private readonly deps;
52
+ /** Called when the actual bound port differs from the requested one. */
53
+ private readonly onPortChange?;
54
+ private server;
55
+ private boundPort;
56
+ private boundAddr;
57
+ private endpoints;
58
+ private readonly rateLimiter;
59
+ constructor(deps: OutboundApiDeps,
60
+ /** Called when the actual bound port differs from the requested one. */
61
+ onPortChange?: ((port: number) => void) | undefined);
62
+ /**
63
+ * Apply a config. Restarts the listener ONLY when the bind address or port
64
+ * changes (or when toggling enabled); per-endpoint routing config is updated
65
+ * in place (read live per request — no restart).
66
+ */
67
+ applyConfig(input: ApplyConfigInput): Promise<void>;
68
+ /** Start the listener on `bindAddr:port`, falling back on EADDRINUSE. */
69
+ start(bindAddr: string, port: number): Promise<number>;
70
+ /** Bind once; on EADDRINUSE retry with an ephemeral port (port 0). */
71
+ private listen;
72
+ /** Per-request handler. Auth is enforced on EVERY request (incl. loopback). */
73
+ private onRequest;
74
+ /** Stop the listener and release the port. */
75
+ stop(): Promise<void>;
76
+ /** A live status snapshot for the Settings tab. */
77
+ getStatus(): OutboundApiServerStatus;
78
+ }
79
+ /** Build the four format endpoint URLs for a base URL. */
80
+ declare function formatUrls(base: string): OutboundFormatUrls;
81
+
82
+ /**
83
+ * apiServerConfig — load / save / default the outbound API server config
84
+ * (`outbound-api-server`, design D4).
85
+ *
86
+ * The config (`{ enabled, networkBinding, endpoints, port }`) is persisted via
87
+ * a small key/value store (the app SettingsService) under a single key, so it
88
+ * survives restart. Defaults: disabled, loopback, four endpoints with empty
89
+ * models + `useSubscription` OFF, default port. Shared by the router and the
90
+ * bootstrap wiring so both read/write the same shape.
91
+ *
92
+ * @module outbound-api/apiServerConfig
93
+ */
94
+
95
+ /** The settings key the config persists under. */
96
+ declare const OUTBOUND_API_SERVER_CONFIG_KEY = "outboundApiServer.config";
97
+ /** Structural subset of the settings store the config loader needs. */
98
+ interface ApiServerSettingsStore {
99
+ get<T = unknown>(key: string): Promise<T | undefined>;
100
+ set<T = unknown>(key: string, value: T): Promise<void>;
101
+ }
102
+ /** The default server config: disabled, loopback, four blank endpoints. */
103
+ declare function defaultServerConfig(): OutboundApiServerConfig;
104
+ /**
105
+ * Normalize a (possibly partial / legacy) persisted config to the full shape:
106
+ * ensure all four endpoints exist, `useSubscription` defaults OFF, and a port
107
+ * is present.
108
+ */
109
+ declare function normalizeServerConfig(raw: Partial<OutboundApiServerConfig> | undefined | null): OutboundApiServerConfig;
110
+ /** Load the persisted config (normalized), defaulting on a missing/blank key. */
111
+ declare function loadServerConfig(store: ApiServerSettingsStore): Promise<OutboundApiServerConfig>;
112
+ /** Persist the config. */
113
+ declare function saveServerConfig(store: ApiServerSettingsStore, config: OutboundApiServerConfig): Promise<void>;
114
+ /** Apply a partial patch to a config, returning the merged whole. */
115
+ declare function mergeServerConfig(current: OutboundApiServerConfig, patch: Partial<OutboundApiServerConfig>): OutboundApiServerConfig;
116
+
117
+ /**
118
+ * outboundApiKeyAuth — named-key generation, hashing, and verification for the
119
+ * outbound API server (`outbound-api-server`).
120
+ *
121
+ * Keys are 128-bit+ random secrets (`sk-omnicross-<base62>`), NOT human passwords,
122
+ * so a single fast hash (sha256) is sufficient and keeps the hot auth path
123
+ * cheap (design D3). Only the hash + a short display prefix are persisted; the
124
+ * full plaintext is returned exactly once at creation.
125
+ *
126
+ * @module outbound-api/outboundApiKeyAuth
127
+ */
128
+
129
+ /** Hash a presented/generated secret (sha256 hex). */
130
+ declare function hashKey(secret: string): string;
131
+ /**
132
+ * Create + persist a named key. Returns the plaintext ONCE; only the hash +
133
+ * prefix are stored.
134
+ */
135
+ declare function createNamedKey(db: OutboundKeyDb, name: string): Promise<OutboundApiKeyCreated>;
136
+ /** The id of a verified key (for rate-limiting + last-used bookkeeping). */
137
+ interface VerifiedKey {
138
+ id: string;
139
+ }
140
+ /**
141
+ * Verify a presented key against the DB. Matches by hash where the stored row
142
+ * is enabled AND not revoked (the DB query enforces this). On success bumps
143
+ * `lastUsedAt` (best-effort, fire-and-forget) and returns the key id; returns
144
+ * `null` on any miss / disabled / revoked key.
145
+ */
146
+ declare function verifyPresentedKey(db: OutboundKeyDb, presentedKey: string | undefined): Promise<VerifiedKey | null>;
147
+
148
+ /**
149
+ * outboundRateLimiter — per-API-key in-memory sliding-window rate limiter for
150
+ * the outbound API server (`outbound-api-server`, design D6).
151
+ *
152
+ * A simple fixed-size sliding window keyed by `apiKeyId`. In-memory only (resets
153
+ * on app restart, acceptable for v1). Exceeding the limit returns a deny with a
154
+ * `Retry-After` (seconds). Limits are conservative defaults, not user-
155
+ * configurable in v1.
156
+ *
157
+ * @module outbound-api/outboundRateLimiter
158
+ */
159
+ interface RateLimitDecision {
160
+ allowed: boolean;
161
+ /** Seconds until the window frees up (only meaningful when `!allowed`). */
162
+ retryAfterSeconds: number;
163
+ }
164
+ interface RateLimiterOptions {
165
+ windowMs?: number;
166
+ maxRequests?: number;
167
+ }
168
+ /**
169
+ * Per-key sliding-window limiter. `check(apiKeyId)` records the request and
170
+ * returns whether it is allowed.
171
+ */
172
+ declare class OutboundRateLimiter {
173
+ private readonly windowMs;
174
+ private readonly maxRequests;
175
+ /** apiKeyId → ascending request timestamps within the current window. */
176
+ private readonly hits;
177
+ constructor(options?: RateLimiterOptions);
178
+ /**
179
+ * Record a request for `apiKeyId` and decide whether it is allowed. Prunes
180
+ * timestamps older than the window first; when allowed, the request's
181
+ * timestamp is appended.
182
+ */
183
+ check(apiKeyId: string, now?: number): RateLimitDecision;
184
+ /** Drop all recorded state (tests / teardown). */
185
+ reset(): void;
186
+ }
187
+
188
+ /**
189
+ * roleDetection — classify an outbound request's ROLE (vision / background /
190
+ * default) so the route resolver can pick the endpoint's model for that role
191
+ * (`outbound-api-server`, design D2).
192
+ *
193
+ * Precedence: vision > background > default.
194
+ * - vision — the body carries image/vision content parts (per-format
195
+ * detection). The route resolver applies the vision→default
196
+ * fallback when the endpoint has no vision model.
197
+ * - background — the requested model id is in the endpoint's optional
198
+ * background-model-id override list (human decision after the
199
+ * proposal), OR the registry small/haiku-class name signal
200
+ * matches (Claude Code's haiku probe sends exactly this).
201
+ * - default — everything else.
202
+ *
203
+ * Each ingress's body shape is handled here in ONE place.
204
+ *
205
+ * @module outbound-api/roleDetection
206
+ */
207
+
208
+ /**
209
+ * Detect the request's role. `backgroundModelIds` is the endpoint's optional
210
+ * override list: when an incoming requested model id matches an entry there, the
211
+ * request is BACKGROUND regardless of the name signal; otherwise the registry
212
+ * small/haiku-class name signal is the baseline. Precedence vision > background
213
+ * > default.
214
+ */
215
+ declare function detectRequestRole(ingressFormat: IngressFormat, body: Record<string, unknown>, options?: {
216
+ backgroundModelIds?: string[];
217
+ }): RequestRole;
218
+ /** Map a settings endpoint id to the provider-proxy ingress format. */
219
+ declare function endpointToIngressFormat(endpoint: 'chat' | 'responses' | 'messages' | 'gemini'): IngressFormat;
220
+
221
+ /**
222
+ * subscriptionSupport — single source of truth for (a) which provider ids are
223
+ * subscription-backed and (b) which outbound endpoints can soundly serve a
224
+ * subscription route (`outbound-api-server`, design D2 + review M1/m1).
225
+ *
226
+ * The subscription-id catalog mirrors `SubscriptionProviderRegistry`'s ids; the
227
+ * static fast-path is backstopped at runtime by a live registry lookup (see
228
+ * `isSubscriptionProviderId`) so it degrades gracefully if it drifts. Both
229
+ * `routeResolver` and `apiServerHandlers` import from here — no duplicated set.
230
+ *
231
+ * @module outbound-api/subscriptionSupport
232
+ */
233
+
234
+ /**
235
+ * The registry-aligned subscription provider ids — the outbound layer's SSOT
236
+ * fast-path. NOTE: `SubscriptionProviderId` is currently a `z.string()` alias,
237
+ * so this `readonly SubscriptionProviderId[]` typing does NOT compile-enforce
238
+ * alignment with the registry; drift is instead tolerated at runtime by
239
+ * `isSubscriptionProviderId`'s live `registry.getProfile` fallback.
240
+ */
241
+ declare const SUBSCRIPTION_PROVIDER_IDS: readonly SubscriptionProviderId[];
242
+ /**
243
+ * True when a provider id refers to a subscription-backed provider. Checks the
244
+ * registry-aligned static catalog first, then falls back to a live registry
245
+ * lookup (so a registry addition is honored even before this list is updated).
246
+ *
247
+ * NOTE (review m2): this classifies by id string. The resolver MUST confirm the
248
+ * id does not resolve to a real BYO provider row before treating it as
249
+ * subscription-backed — see `routeResolver` (BYO rows win).
250
+ */
251
+ declare function isSubscriptionProviderId(providerId: string): boolean;
252
+ /** True when the endpoint's ingress can soundly serve a subscription route. */
253
+ declare function endpointSupportsSubscription(endpoint: OutboundEndpoint): boolean;
254
+
255
+ /**
256
+ * routeResolver — turn an endpoint's `EndpointRoutingConfig` + the detected
257
+ * request role into a `RouteContext` for the shared `provider-proxy` dispatch
258
+ * (`outbound-api-server`, design D2).
259
+ *
260
+ * Steps:
261
+ * 1. Pick the model for the role (vision → default fallback when `visionModel`
262
+ * is unset; vision is optional).
263
+ * 2. Parse the chosen `"providerId,modelId"` ref.
264
+ * 3. Gate by `useSubscription`: OFF → only BYO-key providers are eligible
265
+ * (subscription-backed providers excluded); ON → subscription-backed
266
+ * providers are eligible.
267
+ * 4. Resolve the provider (BYO via the `ProviderConfigSource`; subscription via the
268
+ * subscription registry) and build the `RouteContext` exactly as the
269
+ * internal callers do.
270
+ * 5. Return a clear `503`-style error when the role's required model is unset,
271
+ * the provider is unavailable, or the subscription gate excludes it.
272
+ *
273
+ * @module outbound-api/routeResolver
274
+ */
275
+
276
+ /** A 503-style resolution failure. */
277
+ interface RouteResolveError {
278
+ status: number;
279
+ message: string;
280
+ }
281
+ type RouteResolveResult = {
282
+ ok: true;
283
+ route: RouteContext;
284
+ usedRole: RequestRole;
285
+ } | {
286
+ ok: false;
287
+ error: RouteResolveError;
288
+ };
289
+ /** Resolve a route for one authenticated outbound request. */
290
+ declare function resolveRoute(args: {
291
+ config: EndpointRoutingConfig;
292
+ role: RequestRole;
293
+ ingressFormat: IngressFormat;
294
+ llmConfig: ProviderConfigSource;
295
+ sessionId?: string | null;
296
+ }): Promise<RouteResolveResult>;
297
+
298
+ /**
299
+ * Outbound API server module — barrel + singleton accessor.
300
+ *
301
+ * The outbound server is constructed ONCE at app bootstrap (sharing the
302
+ * resident `ProviderProxy`'s route map + deps) and started only when the
303
+ * persisted `enabled` setting is true. `getOutboundApiServer(deps)` mirrors
304
+ * `getProviderProxy()`: the first call constructs the instance from the supplied
305
+ * deps; later calls return it.
306
+ *
307
+ * @module outbound-api/index
308
+ */
309
+
310
+ /**
311
+ * Get (or lazily construct) the outbound API server singleton.
312
+ *
313
+ * @param deps Required on the FIRST call (app bootstrap). Ignored afterward.
314
+ * @param onPortChange Optional persistence hook for the EADDRINUSE fallback.
315
+ */
316
+ declare function getOutboundApiServer(deps?: OutboundApiDeps, onPortChange?: (port: number) => void): OutboundApiServer;
317
+ /** Reset the singleton (tests / teardown only). */
318
+ declare function __resetOutboundApiServerForTests(): void;
319
+
320
+ export { type ApiServerSettingsStore, type ApplyConfigInput, DEFAULT_OUTBOUND_PORT, EndpointRoutingConfig, OUTBOUND_API_SERVER_CONFIG_KEY, OutboundApiDeps, OutboundApiKeyCreated, OutboundApiServer, OutboundApiServerConfig, OutboundApiServerStatus, OutboundEndpoint, OutboundFormatUrls, OutboundKeyDb, OutboundRateLimiter, RequestRole, SUBSCRIPTION_PROVIDER_IDS, __resetOutboundApiServerForTests, createNamedKey, defaultServerConfig, detectRequestRole, endpointSupportsSubscription, endpointToIngressFormat, formatUrls, getOutboundApiServer, hashKey, isSubscriptionProviderId, loadServerConfig, mergeServerConfig, normalizeServerConfig, resolveRoute, saveServerConfig, verifyPresentedKey };