@omnicross/subscriptions 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.
@@ -0,0 +1,868 @@
1
+ import { AccountTokensConfig } from '@omnicross/contracts/account-tokens-types';
2
+ import { SubscriptionProviderId, SubscriptionStatusEntry, SubscriptionListEntry, OpenCodeGoTokenConfig, OpenCodeGoScenario, OpenCodeGoModelEntry } from '@omnicross/contracts/subscription-types';
3
+ import http from 'node:http';
4
+ export { FetchLike, claudeOAuth, codexOAuth, geminiOAuth } from './oauth.js';
5
+
6
+ /**
7
+ * SubscriptionCredentialStore — the credential port the subscription block
8
+ * depends on instead of the host's concrete token store.
9
+ *
10
+ * The subscription auth strategies + account service + provider registry only
11
+ * ever consume a narrow six-method surface of the host's encrypted OAuth/static
12
+ * credential store. Declaring it here (in `@omnicross/subscriptions`) keeps the
13
+ * package host-clean: the host injects its concrete token store (which
14
+ * structurally satisfies this interface) at bootstrap.
15
+ *
16
+ * The serving core (`@omnicross/core`) does NOT consume credentials — it sees
17
+ * the subscription registry only through the `subscriptionRegistryPort` slot —
18
+ * so this port lives in THIS package, not in core (Phase-0b port pattern at the
19
+ * subscriptions layer).
20
+ */
21
+
22
+ /** The six methods the subscription block consumes from the host credential store. */
23
+ interface SubscriptionCredentialStore {
24
+ /** Full decrypted account-tokens config (all subscription providers). */
25
+ getFullConfig(): Promise<AccountTokensConfig>;
26
+ /** Current valid Claude OAuth access token, refreshing if near expiry; `null` if none. */
27
+ getValidClaudeAccessToken(): Promise<string | null>;
28
+ /** Force a Claude OAuth token refresh; `true` on success. */
29
+ refreshClaudeToken(): Promise<boolean>;
30
+ /** Force a Codex (ChatGPT) OAuth token refresh; `true` on success. */
31
+ refreshCodexToken(): Promise<boolean>;
32
+ /** Force a Gemini (Google) OAuth token refresh; `true` on success. */
33
+ refreshGeminiToken(): Promise<boolean>;
34
+ /** Current valid OpenCodeGo static API key; `null` if none. */
35
+ getValidOpenCodeGoApiKey(): Promise<string | null>;
36
+ }
37
+
38
+ /**
39
+ * SubscriptionAuthStrategy — the pluggable subscription-auth contract, defined
40
+ * in the serving core (`pipeline/`).
41
+ *
42
+ * Each subscription provider in the `SubscriptionProviderRegistry` (the
43
+ * subscriptions package) carries an `AuthStrategy` instance. The proxy calls
44
+ * `applyHeaders` before issuing the upstream request and `onUnauthorized`
45
+ * after a 401 to ask whether to retry.
46
+ *
47
+ * It is a PURE contract (no upstream semantics), defined down here so
48
+ * `pipeline/SubscriptionAuthSource.ts` consumes it WITHOUT importing upward
49
+ * (correct dependency direction). The subscriptions package's
50
+ * `auth/AuthStrategy.ts` RE-EXPORTS this type, and its three concrete
51
+ * strategies implement THIS interface — so they remain assignable and no
52
+ * downstream consumer's import path changes.
53
+ *
54
+ * NOTE: this `AuthApplyHints` (OPTIONAL fields) is the SUBSCRIPTION-side hint
55
+ * shape and is intentionally distinct from `pipeline/AuthSource.ts`'s
56
+ * `AuthApplyHints` (REQUIRED fields). They are not interchangeable; the
57
+ * `SubscriptionAuthSource` adapter maps between them at the boundary.
58
+ *
59
+ * @module pipeline/SubscriptionAuthStrategy
60
+ */
61
+
62
+ /** Hints the strategy may need to vary header formatting per request. */
63
+ interface AuthApplyHints {
64
+ /** Resolved upstream URL — used by some strategies (e.g. OpenCodeGo) to
65
+ * choose between Anthropic-shape and OpenAI-shape headers. */
66
+ upstreamUrl?: string;
67
+ /** Resolved model id — same purpose as `upstreamUrl`. */
68
+ resolvedModel?: string;
69
+ }
70
+ interface AuthStrategy {
71
+ /** Discriminator — also surfaced through `subscription:list` to renderers. */
72
+ readonly kind: 'pass-through' | 'oauth-bearer' | 'static-bearer';
73
+ /** Stable id of the bound subscription provider. */
74
+ readonly providerId: SubscriptionProviderId;
75
+ /**
76
+ * Inject any required authentication headers into the outbound request.
77
+ * Implementations MAY refresh expiring tokens here (transparent refresh).
78
+ *
79
+ * Pass-through implementations are a no-op; the proxy's pass-through
80
+ * code path preserves the SDK's own Authorization header instead.
81
+ */
82
+ applyHeaders(headers: Record<string, string>, hints?: AuthApplyHints): Promise<void>;
83
+ /**
84
+ * Called when the upstream returns 401. Return `true` to ask the proxy to
85
+ * retry the request once with freshly-applied headers; return `false` to
86
+ * surface the 401 immediately.
87
+ *
88
+ * Implementations SHOULD use a shared `RefreshMutex` to dedupe concurrent
89
+ * refreshes so N parallel 401s collapse into one upstream refresh call.
90
+ */
91
+ onUnauthorized(): Promise<boolean>;
92
+ /** Diagnostic surface for the `subscription:status` IPC. */
93
+ describeStatus(): Promise<SubscriptionStatusEntry>;
94
+ }
95
+
96
+ /**
97
+ * SubscriptionAccountService — the single resolver between the credential store and
98
+ * the dispatch proxy for subscription-mode dispatch.
99
+ *
100
+ * Responsibilities:
101
+ * - Hold long-lived `AuthStrategy` instances for each subscription provider.
102
+ * - Share a single `RefreshMutex` across all strategies so concurrent 401s
103
+ * for the same provider collapse into one upstream refresh call.
104
+ * - Expose `listAll()` / `getStatus()` for the `subscription:status` and
105
+ * `subscription:list` IPC channels.
106
+ */
107
+
108
+ declare class SubscriptionAccountService {
109
+ private readonly mutex;
110
+ private readonly strategies;
111
+ constructor(tokens: SubscriptionCredentialStore);
112
+ /** Returns the strategy bound to a subscription provider, or `null` for unknown ids. */
113
+ getStrategy(providerId: SubscriptionProviderId): AuthStrategy | null;
114
+ /** Diagnostic for the `subscription:status` IPC. */
115
+ getStatus(providerId: SubscriptionProviderId): Promise<SubscriptionStatusEntry>;
116
+ /** Catalog entry list for the `subscription:list` IPC. */
117
+ listAll(): Promise<SubscriptionListEntry[]>;
118
+ }
119
+ /** Set once at host bootstrap; consumed by the host's router + engine wiring. */
120
+ declare function setSubscriptionAccountService(svc: SubscriptionAccountService): void;
121
+ /** Returns the singleton if it has been set, else `null` (don't throw — callers
122
+ * may run before main wiring on early-boot diagnostics). */
123
+ declare function getSubscriptionAccountService(): SubscriptionAccountService | null;
124
+
125
+ /**
126
+ * TransformerService Types
127
+ *
128
+ * Type definitions for the Transformer system, providing unified
129
+ * request/response formats and transformer chain configuration.
130
+ *
131
+ * @module transformer/types
132
+ */
133
+ /**
134
+ * Thinking effort level for reasoning models
135
+ */
136
+ type ThinkLevel = 'none' | 'low' | 'medium' | 'high';
137
+ /**
138
+ * Thinking content with optional signature for extended context
139
+ */
140
+ interface ThinkingContent {
141
+ content: string;
142
+ signature?: string;
143
+ }
144
+ /**
145
+ * Reasoning configuration for request
146
+ */
147
+ interface ReasoningConfig {
148
+ /** OpenAI-style effort level */
149
+ effort?: ThinkLevel;
150
+ /** Anthropic-style max tokens for thinking */
151
+ max_tokens?: number;
152
+ /** Whether reasoning is enabled */
153
+ enabled?: boolean;
154
+ }
155
+ /**
156
+ * Text content block
157
+ */
158
+ interface TextContent {
159
+ type: 'text';
160
+ text: string;
161
+ cache_control?: {
162
+ type?: string;
163
+ };
164
+ }
165
+ /**
166
+ * Image content block
167
+ */
168
+ interface ImageContent {
169
+ type: 'image_url';
170
+ image_url: {
171
+ url: string;
172
+ };
173
+ media_type?: string;
174
+ }
175
+ /**
176
+ * Union of all content types
177
+ */
178
+ type MessageContent = TextContent | ImageContent;
179
+ /**
180
+ * Tool call definition
181
+ */
182
+ interface ToolCall {
183
+ id: string;
184
+ type: 'function';
185
+ function: {
186
+ name: string;
187
+ arguments: string;
188
+ };
189
+ }
190
+ /**
191
+ * Unified message format that works across all providers
192
+ */
193
+ interface UnifiedMessage {
194
+ role: 'user' | 'assistant' | 'system' | 'tool';
195
+ content: string | null | MessageContent[];
196
+ tool_calls?: ToolCall[];
197
+ tool_call_id?: string;
198
+ cache_control?: {
199
+ type?: string;
200
+ };
201
+ thinking?: ThinkingContent;
202
+ }
203
+ /**
204
+ * Tool parameter schema
205
+ */
206
+ interface ToolParameterSchema {
207
+ type: 'object';
208
+ properties: Record<string, unknown>;
209
+ required?: string[];
210
+ additionalProperties?: boolean;
211
+ $schema?: string;
212
+ }
213
+ /**
214
+ * Unified tool definition
215
+ */
216
+ interface UnifiedTool {
217
+ type: 'function';
218
+ function: {
219
+ name: string;
220
+ description: string;
221
+ parameters: ToolParameterSchema;
222
+ };
223
+ }
224
+ /**
225
+ * Tool choice configuration
226
+ */
227
+ type ToolChoice = 'auto' | 'none' | 'required' | string | {
228
+ type: 'function';
229
+ function: {
230
+ name: string;
231
+ };
232
+ };
233
+ /**
234
+ * Unified chat request format
235
+ */
236
+ interface UnifiedChatRequest {
237
+ messages: UnifiedMessage[];
238
+ model: string;
239
+ max_tokens?: number;
240
+ temperature?: number;
241
+ stream?: boolean;
242
+ tools?: UnifiedTool[];
243
+ tool_choice?: ToolChoice;
244
+ reasoning?: ReasoningConfig;
245
+ /**
246
+ * Internal-only routing metadata. Populated by callers (CompletionService /
247
+ * the host's engine adapters and proxy servers) so the usage-capture hook can attribute
248
+ * the recorded event to a chat message, session, engine origin, and api key.
249
+ *
250
+ * **Must not be serialised into the outbound HTTP body.** The transformer chain
251
+ * passes this object alongside the request internally; transformers that build
252
+ * provider request payloads must drop `meta` from their output.
253
+ */
254
+ meta?: UnifiedChatRequestMeta;
255
+ }
256
+ /**
257
+ * Routing metadata threaded through the transformer chain so the usage-capture
258
+ * hook can identify which chat message / session / api key produced the call.
259
+ * See `UnifiedChatRequest.meta`.
260
+ */
261
+ interface UnifiedChatRequestMeta {
262
+ /** Host message id of the assistant message being produced, when known. */
263
+ messageId?: string | null;
264
+ /** Parent assistant message id (for subagent dispatches that have a parent). */
265
+ parentMessageId?: string | null;
266
+ /** Host session id, when applicable. */
267
+ sessionId?: string | null;
268
+ /** Which engine path is making the call. See `UsageEngineOrigin`. */
269
+ engineOrigin?: 'completion' | 'claude-sdk' | 'claude-sdk-direct' | 'codex-ingress' | (string & {});
270
+ /** The api-key-pool entry id selected for this request. */
271
+ apiKeyId?: string | null;
272
+ }
273
+ /**
274
+ * LLM Provider configuration (runtime format)
275
+ */
276
+ interface LLMProvider {
277
+ name: string;
278
+ baseUrl: string;
279
+ apiKey: string;
280
+ models: string[];
281
+ transformer?: TransformerChainConfig;
282
+ /**
283
+ * Resolved Google **Code Assist** project id, threaded onto the transformer
284
+ * provider object by the subscription dispatch seam (SubscriptionDispatcher /
285
+ * openaiResponsesIngress) so `GeminiCodeAssistTransformer.transformRequestIn`
286
+ * can embed it in the top-level envelope. `undefined` is the valid fresh
287
+ * free-tier value (sending a project on free/legacy-tier → Precondition
288
+ * Failed). Only ever set for the gemini subscription profile; inert otherwise.
289
+ */
290
+ geminiProject?: string;
291
+ }
292
+ /**
293
+ * Transformer options passed during instantiation
294
+ */
295
+ interface TransformerOptions {
296
+ [key: string]: unknown;
297
+ }
298
+ /**
299
+ * Context passed through transformer chain
300
+ */
301
+ interface TransformerContext {
302
+ /** Original request object */
303
+ req?: unknown;
304
+ /** Logger instance */
305
+ logger?: TransformerLogger;
306
+ /** Provider name */
307
+ providerName?: string;
308
+ /** Additional context data */
309
+ [key: string]: unknown;
310
+ }
311
+ /**
312
+ * Logger interface for transformers
313
+ */
314
+ interface TransformerLogger {
315
+ debug(message: string, ...args: unknown[]): void;
316
+ info(message: string, ...args: unknown[]): void;
317
+ warn(message: string, ...args: unknown[]): void;
318
+ error(message: string, ...args: unknown[]): void;
319
+ }
320
+ /**
321
+ * Transformer interface - defines the contract for all transformers
322
+ */
323
+ interface Transformer {
324
+ /** Unique name for the transformer */
325
+ name?: string;
326
+ /** API endpoint path (e.g., '/v1/chat/completions') */
327
+ endPoint?: string;
328
+ /** Logger instance */
329
+ logger?: TransformerLogger;
330
+ /**
331
+ * Transform incoming request (provider-specific → unified format)
332
+ * Called when request enters the transformer chain
333
+ */
334
+ transformRequestIn?: (request: UnifiedChatRequest, provider: LLMProvider, context: TransformerContext) => Promise<Record<string, unknown>>;
335
+ /**
336
+ * Transform outgoing request (unified → provider-specific format)
337
+ * Called before sending to LLM provider
338
+ */
339
+ transformRequestOut?: (request: unknown, context: TransformerContext) => Promise<UnifiedChatRequest>;
340
+ /**
341
+ * Transform incoming response (provider-specific → unified format)
342
+ * Called after receiving response from provider
343
+ */
344
+ transformResponseIn?: (response: Response, context?: TransformerContext) => Promise<Response>;
345
+ /**
346
+ * Transform outgoing response (unified → client format)
347
+ * Called before returning response to client
348
+ */
349
+ transformResponseOut?: (response: Response, context: TransformerContext) => Promise<Response>;
350
+ /**
351
+ * Handle authentication for the request
352
+ */
353
+ auth?: (request: unknown, provider: LLMProvider, context: TransformerContext) => Promise<unknown>;
354
+ }
355
+ /**
356
+ * Transformer constructor interface
357
+ */
358
+ interface TransformerConstructor {
359
+ new (options?: TransformerOptions): Transformer;
360
+ TransformerName?: string;
361
+ }
362
+ /**
363
+ * Individual transformer reference in chain
364
+ * Can be a string name or [name, options] tuple
365
+ */
366
+ type TransformerReference = string | [string, TransformerOptions];
367
+ /**
368
+ * Model-specific transformer configuration
369
+ */
370
+ interface ModelTransformerConfig {
371
+ use?: TransformerReference[];
372
+ }
373
+ /**
374
+ * Transformer chain configuration for a provider
375
+ */
376
+ interface TransformerChainConfig {
377
+ /** Provider-level transformers applied to all models */
378
+ use?: TransformerReference[];
379
+ /** Model-specific transformer overrides */
380
+ [modelName: string]: ModelTransformerConfig | TransformerReference[] | undefined;
381
+ }
382
+ /**
383
+ * Resolved transformer chain (instances ready to execute)
384
+ */
385
+ interface ResolvedTransformerChain {
386
+ /** Provider-level transformer instances */
387
+ providerTransformers: Transformer[];
388
+ /** Model-specific transformer instances */
389
+ modelTransformers: Transformer[];
390
+ }
391
+ /**
392
+ * Result of request transformation
393
+ */
394
+ interface TransformRequestResult {
395
+ /** Transformed request body */
396
+ requestBody: unknown;
397
+ /** Additional request configuration */
398
+ config: RequestConfig;
399
+ /** Whether to bypass further transformations */
400
+ bypass: boolean;
401
+ }
402
+ /**
403
+ * Request configuration for HTTP call
404
+ */
405
+ interface RequestConfig {
406
+ /** Request URL */
407
+ url?: URL | string;
408
+ /** Request headers */
409
+ headers?: Record<string, string>;
410
+ /** Additional fetch options */
411
+ [key: string]: unknown;
412
+ }
413
+
414
+ /**
415
+ * TransformerChainExecutor - Executes transformer chains for requests and responses
416
+ *
417
+ * Implements the transformer pipeline:
418
+ * Request: transformRequestOut 鈫?Provider transformers 鈫?Model transformers 鈫?HTTP
419
+ * Response: Model transformers (reverse) 鈫?Provider transformers (reverse) 鈫?transformResponseIn
420
+ *
421
+ * @module transformer/TransformerChainExecutor
422
+ */
423
+
424
+ /**
425
+ * Options for chain execution
426
+ */
427
+ interface ChainExecutionOptions {
428
+ /** The primary/endpoint transformer */
429
+ endpointTransformer?: Transformer;
430
+ /** Request headers */
431
+ headers?: Record<string, string> | Headers;
432
+ /** Logger instance */
433
+ logger?: TransformerLogger;
434
+ /**
435
+ * 1M-context opt-in. When `enabled` is true and the post-chain request body
436
+ * looks Anthropic-shaped (has a `messages` array), the executor injects
437
+ * `'context-1m-2025-08-07'` into `body.anthropic_beta`. Capability gating
438
+ * (model id allowlist) lives inside `injectExtendedContextBeta` itself.
439
+ */
440
+ extendedContext?: {
441
+ enabled: boolean;
442
+ model: string;
443
+ };
444
+ }
445
+ /**
446
+ * TransformerChainExecutor handles the execution of transformer chains.
447
+ *
448
+ * The execution flow follows the llms project pattern:
449
+ *
450
+ * Request Phase:
451
+ * 1. transformRequestOut (endpoint transformer) - Convert to unified format
452
+ * 2. Provider transformers [].transformRequestIn (forward order)
453
+ * 3. Model transformers [].transformRequestIn (forward order)
454
+ *
455
+ * Response Phase:
456
+ * 4. Model transformers [].transformResponseOut (reverse order)
457
+ * 5. Provider transformers [].transformResponseOut (reverse order)
458
+ * 6. transformResponseIn (endpoint transformer) - Convert to client format
459
+ */
460
+ declare class TransformerChainExecutor {
461
+ private logger;
462
+ constructor(logger?: TransformerLogger);
463
+ /**
464
+ * Execute the request transformation chain
465
+ *
466
+ * @param request - Original request body
467
+ * @param provider - LLM provider configuration
468
+ * @param chain - Resolved transformer chain
469
+ * @param options - Execution options
470
+ * @returns Transformed request result
471
+ */
472
+ executeRequestChain(request: unknown, provider: LLMProvider, chain: ResolvedTransformerChain, options?: ChainExecutionOptions): Promise<TransformRequestResult>;
473
+ /**
474
+ * Execute the response transformation chain
475
+ *
476
+ * @param request - Original request (for context)
477
+ * @param response - Response from provider
478
+ * @param provider - LLM provider configuration
479
+ * @param chain - Resolved transformer chain
480
+ * @param options - Execution options
481
+ * @returns Transformed response
482
+ */
483
+ executeResponseChain(request: UnifiedChatRequest, response: Response, provider: LLMProvider, chain: ResolvedTransformerChain, options?: ChainExecutionOptions): Promise<Response>;
484
+ /**
485
+ * Execute authentication handler if available
486
+ *
487
+ * @param request - Request body
488
+ * @param provider - LLM provider
489
+ * @param endpointTransformer - Endpoint transformer with auth handler
490
+ * @param context - Transformer context
491
+ * @returns Auth result with potentially modified request and config
492
+ */
493
+ executeAuth(request: unknown, provider: LLMProvider, endpointTransformer: Transformer | undefined, context: TransformerContext): Promise<{
494
+ requestBody: unknown;
495
+ config: RequestConfig;
496
+ }>;
497
+ /**
498
+ * Check if transformers should be bypassed (optimization)
499
+ *
500
+ * Bypass is enabled when:
501
+ * - Provider has only one transformer that matches the endpoint transformer
502
+ * - Model has no specific transformers or only the same endpoint transformer
503
+ */
504
+ private shouldBypassTransformers;
505
+ /**
506
+ * Clean headers for pass-through
507
+ */
508
+ private cleanHeaders;
509
+ /**
510
+ * Get error message from unknown error
511
+ */
512
+ private getErrorMessage;
513
+ }
514
+
515
+ /**
516
+ * TransformerService - Core service for managing transformer lifecycle
517
+ *
518
+ * Handles transformer registration, retrieval, and initialization.
519
+ *
520
+ * Adapted from the `musistudio/llms` project (MIT, © 2025 musistudio) — the
521
+ * transformer IR upstream — for the Electron environment. See the package
522
+ * `NOTICE` for full third-party attribution.
523
+ *
524
+ * @module transformer/TransformerService
525
+ */
526
+
527
+ /**
528
+ * TransformerService manages the lifecycle of all transformers.
529
+ *
530
+ * Features:
531
+ * - Register transformers by name
532
+ * - Get transformer by name
533
+ * - Initialize default transformers
534
+ * - Resolve transformer chains from configuration
535
+ */
536
+ declare class TransformerService {
537
+ private transformers;
538
+ private logger;
539
+ private instanceCache;
540
+ private endpointTransformersCache;
541
+ private noEndpointTransformersCache;
542
+ constructor(logger?: TransformerLogger);
543
+ /**
544
+ * Clear instance caches
545
+ */
546
+ clearInstanceCache(): void;
547
+ /**
548
+ * Get cache statistics for diagnostics
549
+ */
550
+ getCacheStats(): {
551
+ instanceCacheSize: number;
552
+ hasEndpointCache: boolean;
553
+ };
554
+ /**
555
+ * Register a transformer by name
556
+ * @param name - Unique transformer name
557
+ * @param transformer - Transformer instance or constructor
558
+ */
559
+ registerTransformer(name: string, transformer: Transformer | TransformerConstructor): void;
560
+ /**
561
+ * Get a transformer by name
562
+ * @param name - Transformer name
563
+ * @returns Transformer instance/constructor or undefined
564
+ */
565
+ getTransformer(name: string): Transformer | TransformerConstructor | undefined;
566
+ /**
567
+ * Get all registered transformers
568
+ * @returns Map of all transformers
569
+ */
570
+ getAllTransformers(): Map<string, Transformer | TransformerConstructor>;
571
+ /**
572
+ * Get transformers that have an endpoint defined
573
+ * Uses cache to avoid redundant traversal
574
+ * @returns Array of transformers with endpoints
575
+ */
576
+ getTransformersWithEndpoint(): {
577
+ name: string;
578
+ transformer: Transformer;
579
+ }[];
580
+ /**
581
+ * Get transformers without endpoints
582
+ * Uses cache to avoid redundant traversal
583
+ * @returns Array of transformers without endpoints
584
+ */
585
+ getTransformersWithoutEndpoint(): {
586
+ name: string;
587
+ transformer: Transformer;
588
+ }[];
589
+ /**
590
+ * Remove a transformer by name
591
+ * @param name - Transformer name
592
+ * @returns true if removed, false if not found
593
+ */
594
+ removeTransformer(name: string): boolean;
595
+ /**
596
+ * Check if a transformer is registered
597
+ * @param name - Transformer name
598
+ * @returns true if registered
599
+ */
600
+ hasTransformer(name: string): boolean;
601
+ /**
602
+ * Initialize the service with default transformers
603
+ * @param defaultTransformers - Map or object of default transformers
604
+ */
605
+ initialize(defaultTransformers?: Record<string, Transformer | TransformerConstructor>): Promise<void>;
606
+ /**
607
+ * Register default transformers from a map
608
+ * @param transformers - Map of transformer name to transformer
609
+ */
610
+ private registerDefaultTransformers;
611
+ /**
612
+ * Resolve a transformer chain configuration to actual transformer instances
613
+ * @param chainConfig - Transformer chain configuration
614
+ * @param modelName - Model name for model-specific transformers
615
+ * @returns Resolved transformer chain with instances
616
+ */
617
+ resolveTransformerChain(chainConfig: TransformerChainConfig | undefined, modelName?: string): ResolvedTransformerChain;
618
+ /**
619
+ * Resolve an array of transformer references to instances
620
+ * @param refs - Array of transformer references
621
+ * @returns Array of transformer instances
622
+ */
623
+ resolveTransformerReferences(refs: TransformerReference[]): Transformer[];
624
+ /**
625
+ * Instantiate a transformer if it's a constructor
626
+ * Uses instance cache to avoid redundant instantiation
627
+ *
628
+ * @param transformer - Transformer instance or constructor
629
+ * @param options - Options to pass to constructor
630
+ * @returns Transformer instance
631
+ */
632
+ private instantiateIfNeeded;
633
+ /**
634
+ * Check if a value is a transformer constructor
635
+ * @param value - Value to check
636
+ * @returns true if it's a constructor
637
+ */
638
+ private isTransformerConstructor;
639
+ /**
640
+ * Get list of all registered transformer names
641
+ * @returns Array of transformer names
642
+ */
643
+ getTransformerNames(): string[];
644
+ /**
645
+ * Clear all registered transformers
646
+ */
647
+ clear(): void;
648
+ }
649
+
650
+ /**
651
+ * Shared contract types for the resident `ProviderProxy`.
652
+ *
653
+ * The `ProviderProxy` (OpenSpec `engine-provider-decouple`, design D0/D3/D7/D9)
654
+ * is the single resident `127.0.0.1` listener that subsumes both of the
655
+ * host's per-session proxies (Anthropic Messages ingress and OpenAI Responses
656
+ * ingress). Per-run state lives in a `Map<token, RouteContext>`
657
+ * minted at run start and reaped at run end / on idle TTL.
658
+ *
659
+ * These types are kept in their own module so the server, route map, router,
660
+ * and the two ingress parsers can share shapes without importing one another
661
+ * for type-only purposes.
662
+ *
663
+ * @module provider-proxy/types
664
+ */
665
+
666
+ /** Lightweight summary derived from the inbound Anthropic request body —
667
+ * consumed by `modelMapper` (scenario routing) without full body access. */
668
+ interface SubscriptionRequestSummary {
669
+ messageCount: number;
670
+ /** cl100k_base-estimated token count of system + messages (no tools). */
671
+ estimatedInputTokens: number;
672
+ /**
673
+ * OPTIONAL bounded per-message text slice (system prompt + the most recent
674
+ * user/system messages, each per-message-capped) consumed ONLY by the
675
+ * OpenCodeGo keyword matcher in `@omnicross/subscriptions`
676
+ * (`resolveOpenCodeGoScenario`). Core only WRITES this `string[]`; it never
677
+ * reads it and never names the matcher — keeping the cross-layer litmus at 0
678
+ * (no `@omnicross/core` → `@omnicross/subscriptions` edge). Optional so callers
679
+ * that omit it (legacy/tests) compile and degrade to the token-threshold +
680
+ * `default` routing.
681
+ */
682
+ matchText?: string[];
683
+ }
684
+ interface SubscriptionDispatchProfile {
685
+ readonly providerId: SubscriptionProviderId;
686
+ readonly displayName: string;
687
+ readonly authStrategy: AuthStrategy;
688
+ /** Pass-through providers (Claude) skip the transformer chain entirely.
689
+ * Transformer providers use the chain below + the proxy's existing
690
+ * `AnthropicTransformer` endpoint reverse-decoder. */
691
+ readonly mode: 'pass-through' | 'transformer';
692
+ /** Resolve the upstream URL for a given resolved model id. Required for
693
+ * `mode === 'transformer'`; unused for pass-through (proxy hard-codes
694
+ * `api.anthropic.com`). The OPTIONAL 2nd `config` arg lets the opencodego
695
+ * profile honor a per-account `baseUrl` override (D1) — additive, so existing
696
+ * one-arg callers compile unchanged. */
697
+ readonly resolveUpstreamUrl?: (resolvedModel: string, config?: OpenCodeGoTokenConfig) => string;
698
+ /** Names of transformers (registered in `TransformerService`) to run on the
699
+ * provider chain. The proxy adds `AnthropicTransformer` as the endpoint
700
+ * reverse-decoder. */
701
+ readonly providerTransformerNames?: readonly string[];
702
+ readonly modelTransformerNames?: readonly string[];
703
+ /**
704
+ * OPTIONAL shape-aware provider transformer-name resolver (opencodego zen).
705
+ * Parallel to `resolveUpstreamUrl(model, config)`: lets a profile vary its
706
+ * provider chain by the RESOLVED model's wire shape (e.g. zen `responses` ⇒
707
+ * `['openai-response']`, `gemini` ⇒ `['gemini']`). `config` is `unknown` on the
708
+ * core side (opaque-config discipline — core never names
709
+ * `OpenCodeGoTokenConfig` from `@omnicross/subscriptions`); the subscriptions
710
+ * implementation narrows it. When ABSENT, both ingress paths fall back to the
711
+ * static `providerTransformerNames` — BYTE-IDENTICAL for claude / codex / gemini
712
+ * (which leave this unset). Purely additive (optional).
713
+ */
714
+ readonly resolveProviderTransformerNames?: (model: string, config?: unknown) => readonly string[];
715
+ /** Optional model placeholder rewriter — only set for OpenCodeGo. */
716
+ readonly modelMapper?: (sdkModel: string, summary: SubscriptionRequestSummary, config: OpenCodeGoTokenConfig | undefined) => {
717
+ resolvedModel: string;
718
+ scenario: OpenCodeGoScenario;
719
+ };
720
+ /** Optional fallback resolver — for OpenCodeGo, picks the next model after
721
+ * an unrecoverable error. Returns `null` when exhausted. Cap = 3. The
722
+ * opencodego implementation ALSO consults the circuit breaker (D5): it skips
723
+ * models whose circuit is open. */
724
+ readonly nextFallback?: (scenario: OpenCodeGoScenario, attempted: readonly string[], config: OpenCodeGoTokenConfig | undefined) => OpenCodeGoModelEntry | null;
725
+ /** Optional circuit-breaker admission gate for the PRIMARY (mapped) model
726
+ * (D5 primary-gating). Only set for OpenCodeGo. Returns whether `modelId`'s
727
+ * circuit currently admits a request (side-effecting: flips an `open` model
728
+ * to `half-open` once its window elapses, exactly like `nextFallback`'s
729
+ * internal consult). `nextFallback` covers the FALLBACKS; this covers the
730
+ * primary the loop already holds. Absent/undefined ⇒ the loop treats the
731
+ * primary as always admitted (claude / codex / gemini have no breaker). */
732
+ readonly allowModel?: (modelId: string) => boolean;
733
+ /** Optional record-outcome callback (D5 record seam). Only set for OpenCodeGo.
734
+ * Both fallback loops invoke it after each attempt: `ok: true` on a `2xx`,
735
+ * `ok: false` on a thrown/network error / `5xx` / `429`; a non-429 `4xx` is
736
+ * NEUTRAL and the loops MUST NOT call it. Drives the per-model breaker.
737
+ * Absent/undefined for claude / codex / gemini ⇒ a no-op (no breaker). */
738
+ readonly recordModelOutcome?: (modelId: string, ok: boolean) => void;
739
+ }
740
+
741
+ /**
742
+ * SubscriptionProviderRegistry — fixed catalog of subscription dispatch profiles.
743
+ *
744
+ * Each entry maps a `SubscriptionProviderId` to a `SubscriptionDispatchProfile`
745
+ * that tells the dispatch proxy:
746
+ * - how to authenticate (via the bound `AuthStrategy`),
747
+ * - which transformer chain to run (resolved by name from `TransformerService`),
748
+ * - which upstream URL to hit (static OR computed per model id),
749
+ * - how to map the SDK-supplied model placeholder to a provider model
750
+ * (OpenCodeGo scenario routing),
751
+ * - which fallback model entries to try when the primary fails.
752
+ *
753
+ * The registry is intentionally a static in-memory map — subscription
754
+ * providers are a built-in catalog, NOT user-configurable LLM provider rows.
755
+ */
756
+
757
+ declare class SubscriptionProviderRegistry {
758
+ private readonly accounts;
759
+ private readonly tokens;
760
+ private readonly profiles;
761
+ /**
762
+ * Per-model circuit breaker for opencodego routing (D5). ONE registry-owned
763
+ * process singleton, built here and captured by the opencodego profile's
764
+ * `nextFallback` (consult) + `recordModelOutcome` (record) closures. Because
765
+ * the `SubscriptionProviderRegistry` is itself a process singleton (via
766
+ * `setSubscriptionProviderRegistry`), breaker state persists across requests —
767
+ * exactly the reference's long-lived `FallbackHandler`. Constructed with the
768
+ * default reference thresholds (3 / 30s / 3) and the default `Date.now` clock.
769
+ */
770
+ private readonly breaker;
771
+ constructor(accounts: SubscriptionAccountService, tokens: SubscriptionCredentialStore);
772
+ /** Returns the dispatch profile for a known subscription provider, or
773
+ * `null` for unknown ids (callers must treat null as "fall back to the
774
+ * legacy LLM provider DB lookup"). */
775
+ getProfile(providerId: string): SubscriptionDispatchProfile | null;
776
+ /** Read the currently-stored OpenCodeGo config so the proxy can pick up
777
+ * user overrides (modelMap / fallbacks / baseUrl). Wraps the injected
778
+ * `SubscriptionCredentialStore` so the proxy doesn't need to know about
779
+ * that surface. */
780
+ getOpenCodeGoConfig(): Promise<OpenCodeGoTokenConfig | undefined>;
781
+ }
782
+ declare function setSubscriptionProviderRegistry(svc: SubscriptionProviderRegistry): void;
783
+ declare function getSubscriptionProviderRegistry(): SubscriptionProviderRegistry | null;
784
+
785
+ /**
786
+ * SubscriptionDispatcher — handles the dispatch proxy's subscription-mode flow.
787
+ *
788
+ * Extracted from the host proxy server to keep that file focused on the legacy
789
+ * LLM-provider pipeline. The dispatcher mirrors the proxy's inner
790
+ * transformer-pipeline shape but sources upstream URL,
791
+ * transformer chain, auth, and model mapping from a
792
+ * `SubscriptionDispatchProfile` instead of the host's config service.
793
+ *
794
+ * The proxy delegates here from `handleRequest` whenever a profile is bound.
795
+ */
796
+
797
+ /** Hooks the dispatcher needs from the host proxy server's private surface. */
798
+ interface DispatcherHooks {
799
+ /** Anthropic endpoint transformer instance, reused across requests. */
800
+ readonly endpointTransformer: Transformer;
801
+ /** Shared transformer chain executor. */
802
+ readonly executor: TransformerChainExecutor;
803
+ /** Shared transformer service registry — looks up transformer-by-name. */
804
+ readonly transformerService: TransformerService;
805
+ /** Fetch + retry helper from the proxy (semaphore, 429/5xx loop). */
806
+ fetchWithRetry(url: string, headers: Record<string, string>, body: unknown, model: string): Promise<Response>;
807
+ /** Forward the upstream response to the SDK + tap usage. */
808
+ writeProxyResponse(res: http.ServerResponse, providerResponse: Response, isStream: boolean, reqId?: number): Promise<void>;
809
+ }
810
+ interface DispatchRequest {
811
+ reqId: number;
812
+ res: http.ServerResponse;
813
+ rawBody: string;
814
+ anthropicBody: Record<string, unknown>;
815
+ isStream: boolean;
816
+ sdkModel: string;
817
+ fallbackModel: string;
818
+ }
819
+ declare class SubscriptionDispatcher {
820
+ private readonly profile;
821
+ private readonly hooks;
822
+ private readonly getOpenCodeGoConfig;
823
+ constructor(profile: SubscriptionDispatchProfile, hooks: DispatcherHooks, getOpenCodeGoConfig: () => Promise<OpenCodeGoTokenConfig | undefined>);
824
+ /**
825
+ * Entry point — called by the host proxy's request handler after model
826
+ * resolution and probe-detection.
827
+ */
828
+ dispatch(req: DispatchRequest): Promise<void>;
829
+ /** Bypass path for OpenCodeGo MiniMax models — forwards Anthropic body verbatim. */
830
+ private dispatchAnthropicShapeBypass;
831
+ /** Standard subscription transformer chain — Codex/Gemini/OpenCodeGo OpenAI-shape. */
832
+ private dispatchTransformerChain;
833
+ /**
834
+ * D2 PRIMARY-GATING (opencodego, D5): pick the first-attempt model for a
835
+ * fallback loop. Consults the breaker for the mapped primary; when the primary's
836
+ * circuit is open, advance to the first admitting `nextFallback` candidate
837
+ * WITHOUT an upstream round-trip on the open primary. When EVERY candidate is
838
+ * open (all-open) the breaker FAILS OPEN — it attempts the original primary
839
+ * anyway. Returns the resolved first-attempt model plus the `attempted` list
840
+ * seeded for the loop (the SKIPPED primary is recorded at chain index 0 so the
841
+ * loop's `nextFallback` excludes it; on fail-open the list is left empty so the
842
+ * primary is the first real attempt). When the profile has no `allowModel`
843
+ * (claude / codex / gemini, or breaker unset) this is byte-identical to the
844
+ * prior behavior: the primary is the first attempt, `attempted` empty.
845
+ */
846
+ private gatePrimaryModel;
847
+ /**
848
+ * On a 401 error from the upstream, ask the AuthStrategy whether to retry.
849
+ * Returns `{ retryOnce: true, headers }` when the strategy refreshed
850
+ * successfully (caller should retry once); otherwise re-throws.
851
+ */
852
+ private maybeRetryAfterError;
853
+ private applyHeadersWithRetry;
854
+ /**
855
+ * Resolve the Code Assist project for the gemini subscription profile. Pulls
856
+ * the Bearer the bound `AuthStrategy` would inject (so the strategy stays the
857
+ * single source of the token), then runs the cached handshake. Returns
858
+ * `undefined` for a fresh free-tier account (valid — the envelope omits the
859
+ * project). A handshake hard failure (403/429) propagates to the dispatch
860
+ * error handler.
861
+ */
862
+ private resolveGeminiProject;
863
+ private resolveTransformers;
864
+ /** Build a lightweight request summary for OpenCodeGo scenario routing. */
865
+ private buildRequestSummary;
866
+ }
867
+
868
+ export { type AuthApplyHints, type AuthStrategy, type DispatchRequest, type DispatcherHooks, SubscriptionAccountService, type SubscriptionCredentialStore, type SubscriptionDispatchProfile, SubscriptionDispatcher, SubscriptionProviderRegistry, type SubscriptionRequestSummary, getSubscriptionAccountService, getSubscriptionProviderRegistry, setSubscriptionAccountService, setSubscriptionProviderRegistry };