@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.
- package/LICENSE +21 -0
- package/NOTICE +57 -0
- package/README.md +15 -0
- package/dist/ApiKeyPoolService-BmMkau07.d.cts +170 -0
- package/dist/ApiKeyPoolService-BmMkau07.d.ts +170 -0
- package/dist/ProviderProxy-f_8ziIhW.d.cts +120 -0
- package/dist/ProviderProxy-vjt8sQQk.d.ts +120 -0
- package/dist/SubscriptionAuthSource-Cr4fVEYY.d.cts +264 -0
- package/dist/SubscriptionAuthSource-D89zmiSS.d.ts +264 -0
- package/dist/auth/GeminiCodeAssistProjectResolver.cjs +218 -0
- package/dist/auth/GeminiCodeAssistProjectResolver.d.cts +68 -0
- package/dist/auth/GeminiCodeAssistProjectResolver.d.ts +68 -0
- package/dist/auth/GeminiCodeAssistProjectResolver.js +189 -0
- package/dist/completion/ApiKeyPoolService.cjs +331 -0
- package/dist/completion/ApiKeyPoolService.d.cts +2 -0
- package/dist/completion/ApiKeyPoolService.d.ts +2 -0
- package/dist/completion/ApiKeyPoolService.js +306 -0
- package/dist/completion.cjs +4027 -0
- package/dist/completion.d.cts +17 -0
- package/dist/completion.d.ts +17 -0
- package/dist/completion.js +3983 -0
- package/dist/index-BTSmc9Sm.d.ts +645 -0
- package/dist/index-DXazdTzZ.d.cts +645 -0
- package/dist/index.cjs +10428 -0
- package/dist/index.d.cts +128 -0
- package/dist/index.d.ts +128 -0
- package/dist/index.js +10339 -0
- package/dist/outbound-api/subscriptionRegistryPort.cjs +38 -0
- package/dist/outbound-api/subscriptionRegistryPort.d.cts +73 -0
- package/dist/outbound-api/subscriptionRegistryPort.d.ts +73 -0
- package/dist/outbound-api/subscriptionRegistryPort.js +12 -0
- package/dist/outbound-api.cjs +5264 -0
- package/dist/outbound-api.d.cts +320 -0
- package/dist/outbound-api.d.ts +320 -0
- package/dist/outbound-api.js +5218 -0
- package/dist/pipeline/SubscriptionAuthSource.cjs +131 -0
- package/dist/pipeline/SubscriptionAuthSource.d.cts +3 -0
- package/dist/pipeline/SubscriptionAuthSource.d.ts +3 -0
- package/dist/pipeline/SubscriptionAuthSource.js +103 -0
- package/dist/pipeline/SubscriptionAuthStrategy.cjs +18 -0
- package/dist/pipeline/SubscriptionAuthStrategy.d.cts +61 -0
- package/dist/pipeline/SubscriptionAuthStrategy.d.ts +61 -0
- package/dist/pipeline/SubscriptionAuthStrategy.js +0 -0
- package/dist/ports/gemini-code-assist-resolver.cjs +38 -0
- package/dist/ports/gemini-code-assist-resolver.d.cts +26 -0
- package/dist/ports/gemini-code-assist-resolver.d.ts +26 -0
- package/dist/ports/gemini-code-assist-resolver.js +12 -0
- package/dist/ports.cjs +18 -0
- package/dist/ports.d.cts +15 -0
- package/dist/ports.d.ts +15 -0
- package/dist/ports.js +0 -0
- package/dist/provider-proxy/ingress/providerProxyShared.cjs +2958 -0
- package/dist/provider-proxy/ingress/providerProxyShared.d.cts +77 -0
- package/dist/provider-proxy/ingress/providerProxyShared.d.ts +77 -0
- package/dist/provider-proxy/ingress/providerProxyShared.js +2925 -0
- package/dist/provider-proxy/matchText.cjs +73 -0
- package/dist/provider-proxy/matchText.d.cts +47 -0
- package/dist/provider-proxy/matchText.d.ts +47 -0
- package/dist/provider-proxy/matchText.js +45 -0
- package/dist/provider-proxy/types.cjs +18 -0
- package/dist/provider-proxy/types.d.cts +12 -0
- package/dist/provider-proxy/types.d.ts +12 -0
- package/dist/provider-proxy/types.js +0 -0
- package/dist/provider-proxy.cjs +4667 -0
- package/dist/provider-proxy.d.cts +69 -0
- package/dist/provider-proxy.d.ts +69 -0
- package/dist/provider-proxy.js +4636 -0
- package/dist/serializeError.cjs +82 -0
- package/dist/serializeError.d.cts +24 -0
- package/dist/serializeError.d.ts +24 -0
- package/dist/serializeError.js +57 -0
- package/dist/sse-parser.cjs +456 -0
- package/dist/sse-parser.d.cts +143 -0
- package/dist/sse-parser.d.ts +143 -0
- package/dist/sse-parser.js +430 -0
- package/dist/transformer/TransformerChainExecutor.cjs +321 -0
- package/dist/transformer/TransformerChainExecutor.d.cts +104 -0
- package/dist/transformer/TransformerChainExecutor.d.ts +104 -0
- package/dist/transformer/TransformerChainExecutor.js +294 -0
- package/dist/transformer/TransformerService.cjs +290 -0
- package/dist/transformer/TransformerService.d.cts +138 -0
- package/dist/transformer/TransformerService.d.ts +138 -0
- package/dist/transformer/TransformerService.js +265 -0
- package/dist/transformer/transformers/GeminiCodeAssistTransformer.cjs +1115 -0
- package/dist/transformer/transformers/GeminiCodeAssistTransformer.d.cts +102 -0
- package/dist/transformer/transformers/GeminiCodeAssistTransformer.d.ts +102 -0
- package/dist/transformer/transformers/GeminiCodeAssistTransformer.js +1085 -0
- package/dist/transformer/transformers/GeminiTransformer.cjs +1013 -0
- package/dist/transformer/transformers/GeminiTransformer.d.cts +70 -0
- package/dist/transformer/transformers/GeminiTransformer.d.ts +70 -0
- package/dist/transformer/transformers/GeminiTransformer.js +986 -0
- package/dist/transformer/transformers/OpenAIResponseTransformer.cjs +538 -0
- package/dist/transformer/transformers/OpenAIResponseTransformer.d.cts +53 -0
- package/dist/transformer/transformers/OpenAIResponseTransformer.d.ts +53 -0
- package/dist/transformer/transformers/OpenAIResponseTransformer.js +513 -0
- package/dist/transformer/transformers/OpenCodeGoTransformer.cjs +73 -0
- package/dist/transformer/transformers/OpenCodeGoTransformer.d.cts +51 -0
- package/dist/transformer/transformers/OpenCodeGoTransformer.d.ts +51 -0
- package/dist/transformer/transformers/OpenCodeGoTransformer.js +48 -0
- package/dist/transformer/types.cjs +18 -0
- package/dist/transformer/types.d.cts +405 -0
- package/dist/transformer/types.d.ts +405 -0
- package/dist/transformer/types.js +0 -0
- package/dist/transformer.cjs +3736 -0
- package/dist/transformer.d.cts +33 -0
- package/dist/transformer.d.ts +33 -0
- package/dist/transformer.js +3712 -0
- package/dist/types-CGGrKqC_.d.cts +142 -0
- package/dist/types-CbCN2NQP.d.ts +142 -0
- package/dist/types-DCzHkhJt.d.ts +467 -0
- package/dist/types-DZIQbgp0.d.cts +467 -0
- package/dist/usage-event-sink-BX7FE1NL.d.cts +59 -0
- package/dist/usage-event-sink-BX7FE1NL.d.ts +59 -0
- package/package.json +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sayo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/NOTICE
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
@omnicross/core — THIRD-PARTY NOTICES
|
|
2
|
+
======================================
|
|
3
|
+
|
|
4
|
+
This package contains code adapted from, and depends on, the following
|
|
5
|
+
third-party works. Their respective licenses apply to those portions.
|
|
6
|
+
|
|
7
|
+
--------------------------------------------------------------------------------
|
|
8
|
+
1. musistudio/llms (MIT License)
|
|
9
|
+
--------------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
The transformer subsystem (`src/transformer/`) — the unified-IR request/response
|
|
12
|
+
transformer chain (`TransformerService`, `TransformerChainExecutor`, and the
|
|
13
|
+
built-in transformers) — is adapted from the `musistudio/llms` project, the
|
|
14
|
+
upstream of the transformer intermediate representation.
|
|
15
|
+
|
|
16
|
+
Copyright (c) 2025 musistudio
|
|
17
|
+
https://github.com/musistudio/llms
|
|
18
|
+
|
|
19
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
20
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
21
|
+
in the Software without restriction, including without limitation the rights
|
|
22
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
23
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
24
|
+
furnished to do so, subject to the following conditions:
|
|
25
|
+
|
|
26
|
+
The above copyright notice and this permission notice shall be included in
|
|
27
|
+
all copies or substantial portions of the Software.
|
|
28
|
+
|
|
29
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
30
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
31
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
32
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
33
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
34
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
35
|
+
SOFTWARE.
|
|
36
|
+
|
|
37
|
+
--------------------------------------------------------------------------------
|
|
38
|
+
2. @mozilla/readability (Apache License 2.0)
|
|
39
|
+
--------------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
The built-in web-fetch tool (`src/completion/builtin-web-fetch.ts`) uses
|
|
42
|
+
`@mozilla/readability` to extract readable article content from fetched HTML.
|
|
43
|
+
|
|
44
|
+
Copyright Mozilla Foundation and contributors
|
|
45
|
+
https://github.com/mozilla/readability
|
|
46
|
+
|
|
47
|
+
Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
48
|
+
use this file except in compliance with the License. You may obtain a copy of
|
|
49
|
+
the License at
|
|
50
|
+
|
|
51
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
52
|
+
|
|
53
|
+
Unless required by applicable law or agreed to in writing, software
|
|
54
|
+
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
55
|
+
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
56
|
+
License for the specific language governing permissions and limitations under
|
|
57
|
+
the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# @omnicross/core
|
|
2
|
+
|
|
3
|
+
The omnicross LLM serving core — provider dispatch, the completion pipeline, the transformer chain, the outbound API server, and the resident provider proxy. Embed it in any Node project, or run it via [`@omnicross/daemon`](https://www.npmjs.com/package/@omnicross/daemon).
|
|
4
|
+
|
|
5
|
+
Part of the [omnicross](https://github.com/Dumoedss/omnicross) monorepo — see the root README for the full overview.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @omnicross/core @omnicross/contracts
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## License
|
|
12
|
+
|
|
13
|
+
[MIT](LICENSE)
|
|
14
|
+
|
|
15
|
+
This package adapts third-party work under its own license — see the `NOTICE` file.
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { ApiKeyEntry } from '@omnicross/contracts/llm-config';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `Logger` — core-owned port for the structured logger the serving core uses.
|
|
5
|
+
*
|
|
6
|
+
* The serving core MUST depend on THIS interface, never on the concrete host
|
|
7
|
+
* `LoggerService` class as a type. The host (`LoggerService`) already exposes a
|
|
8
|
+
* superset of this surface, so it is passed directly with NO adapter.
|
|
9
|
+
*
|
|
10
|
+
* `error` uses the WIDEST signature `(message, error?, meta?)` so every core
|
|
11
|
+
* call site — `error(msg)`, `error(msg, errInstance)`, `error(msg, err, meta)` —
|
|
12
|
+
* stays assignable (design Q2). `info`/`warn`/`debug` take an optional `meta`
|
|
13
|
+
* bag matching the host's `Record<string, unknown> | Error | object`.
|
|
14
|
+
*
|
|
15
|
+
* @module ports/logger
|
|
16
|
+
*/
|
|
17
|
+
interface Logger {
|
|
18
|
+
info(message: string, meta?: Record<string, unknown> | Error | object): void;
|
|
19
|
+
warn(message: string, meta?: Record<string, unknown> | Error | object): void;
|
|
20
|
+
error(message: string, error?: unknown, meta?: Record<string, unknown> | object): void;
|
|
21
|
+
debug(message: string, meta?: Record<string, unknown> | Error | object): void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* ApiKeyPoolService - Multi-API-key load balancing with session affinity
|
|
26
|
+
*
|
|
27
|
+
* Manages a pool of API keys per provider, selecting keys via weighted
|
|
28
|
+
* round-robin and maintaining session-level key affinity to preserve
|
|
29
|
+
* prompt cache across requests within the same session.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/** Live health snapshot for a single key currently in cooldown. */
|
|
33
|
+
interface KeyHealthEntry {
|
|
34
|
+
/** Epoch-ms when the cooldown expires */
|
|
35
|
+
until: number;
|
|
36
|
+
/** Number of consecutive errors */
|
|
37
|
+
errors: number;
|
|
38
|
+
/** HTTP status that triggered the most recent cooldown */
|
|
39
|
+
lastStatus: number | null;
|
|
40
|
+
}
|
|
41
|
+
/** Provider-scoped map of keyId → live cooldown health (cooling keys only). */
|
|
42
|
+
type KeyHealthMap = Record<string, KeyHealthEntry>;
|
|
43
|
+
/** Function type for loading API keys from the database */
|
|
44
|
+
type ApiKeysLoader = (providerId: string) => Promise<ApiKeyEntry[]>;
|
|
45
|
+
/** Function type for disabling a key in the database (on auth failure) */
|
|
46
|
+
type ApiKeyDisabler = (keyId: string) => Promise<boolean>;
|
|
47
|
+
/**
|
|
48
|
+
* Function type for auto-disabling a key in the database on auth failure,
|
|
49
|
+
* persisting the offending status + timestamp so the client UI can surface
|
|
50
|
+
* a per-key health indicator. Preferred over {@link ApiKeyDisabler} when set.
|
|
51
|
+
*/
|
|
52
|
+
type ApiKeyAutoDisabler = (keyId: string, status: number, at: number) => Promise<void>;
|
|
53
|
+
/** Function type for resolving environment variable references in API keys */
|
|
54
|
+
type ApiKeyResolver = (rawKey: string) => string;
|
|
55
|
+
declare class ApiKeyPoolService {
|
|
56
|
+
private loadKeys;
|
|
57
|
+
private resolveKey;
|
|
58
|
+
private logger;
|
|
59
|
+
private disableKey?;
|
|
60
|
+
private markAutoDisabled?;
|
|
61
|
+
/** Session 鈫?key binding (session affinity) */
|
|
62
|
+
private sessionBindings;
|
|
63
|
+
/** Provider 鈫?round-robin index */
|
|
64
|
+
private rrIndex;
|
|
65
|
+
/** Provider 鈫?cached key list */
|
|
66
|
+
private keyCache;
|
|
67
|
+
/** Key ID 鈫?cooldown state */
|
|
68
|
+
private cooldowns;
|
|
69
|
+
/** Cleanup interval handle */
|
|
70
|
+
private cleanupTimer;
|
|
71
|
+
private readonly DEFAULT_COOLDOWN_MS;
|
|
72
|
+
private readonly MAX_COOLDOWN_MS;
|
|
73
|
+
private readonly COOLDOWN_MULTIPLIER;
|
|
74
|
+
constructor(loadKeys: ApiKeysLoader, resolveKey: ApiKeyResolver, logger: Logger, disableKey?: ApiKeyDisabler | undefined, markAutoDisabled?: ApiKeyAutoDisabler | undefined);
|
|
75
|
+
/**
|
|
76
|
+
* Get the API key for a session. Implements session affinity.
|
|
77
|
+
*
|
|
78
|
+
* First call for a session binds it to a key via weighted round-robin.
|
|
79
|
+
* Subsequent calls return the same key (preserves prompt cache).
|
|
80
|
+
*
|
|
81
|
+
* @returns Resolved API key string, or empty string if no keys available
|
|
82
|
+
*/
|
|
83
|
+
/**
|
|
84
|
+
* Read which key id is currently bound to the given session, if any.
|
|
85
|
+
* Returns null when the session has not yet been bound (first call hasn't
|
|
86
|
+
* happened) or when the binding is for a different provider.
|
|
87
|
+
*
|
|
88
|
+
* Used by the usage-recorder attribution path: after `getKeyForSession`
|
|
89
|
+
* completes the caller looks up the keyId so the recorded usage
|
|
90
|
+
* row can attribute spend to a specific pool key.
|
|
91
|
+
*/
|
|
92
|
+
getKeyIdForSession(providerId: string, sessionId: string): string | null;
|
|
93
|
+
getKeyForSession(providerId: string, sessionId: string): Promise<string>;
|
|
94
|
+
/**
|
|
95
|
+
* Get a key without session affinity (for one-shot calls like testConnection).
|
|
96
|
+
*
|
|
97
|
+
* @returns Resolved API key string, or empty string if no keys available
|
|
98
|
+
*/
|
|
99
|
+
getKey(providerId: string): Promise<string>;
|
|
100
|
+
/**
|
|
101
|
+
* Report an error for the current session's key.
|
|
102
|
+
*
|
|
103
|
+
* - 429/529 (rate limit / overload): puts key in cooldown with exponential backoff
|
|
104
|
+
* - 401/403 (auth failure): permanently disables the key in the database
|
|
105
|
+
*
|
|
106
|
+
* In both cases, re-binds the session to a different key if available.
|
|
107
|
+
*
|
|
108
|
+
* @param statusCode HTTP status code
|
|
109
|
+
* @returns New resolved API key if re-binding succeeded, null if no keys available
|
|
110
|
+
*/
|
|
111
|
+
reportError(providerId: string, sessionId: string, statusCode: number): Promise<string | null>;
|
|
112
|
+
/**
|
|
113
|
+
* Report a successful request — resets cooldown counter for the session's key.
|
|
114
|
+
*/
|
|
115
|
+
reportSuccess(sessionId: string): void;
|
|
116
|
+
/**
|
|
117
|
+
* Release session binding (call when session ends or is deleted).
|
|
118
|
+
*/
|
|
119
|
+
releaseSession(sessionId: string): void;
|
|
120
|
+
/**
|
|
121
|
+
* Invalidate the key cache. Call after CRUD operations on API keys.
|
|
122
|
+
*/
|
|
123
|
+
invalidateCache(providerId?: string): void;
|
|
124
|
+
/**
|
|
125
|
+
* Check if a provider has any keys in the pool.
|
|
126
|
+
*/
|
|
127
|
+
hasKeys(providerId: string): Promise<boolean>;
|
|
128
|
+
/**
|
|
129
|
+
* Get the live (in-memory) rate-limit cooldown health for a provider's keys.
|
|
130
|
+
*
|
|
131
|
+
* Returns ONLY keys that are currently cooling down (cooldown `until` is in
|
|
132
|
+
* the future). A key absent from the returned map is not cooling. This is a
|
|
133
|
+
* pure read of the in-memory cooldown map — auth-failure auto-disable state
|
|
134
|
+
* is persisted on the key row itself (getApiKeys) and is NOT included here.
|
|
135
|
+
*/
|
|
136
|
+
getKeyHealth(providerId: string): Promise<KeyHealthMap>;
|
|
137
|
+
/**
|
|
138
|
+
* Dispose of the service (stop cleanup timer).
|
|
139
|
+
*/
|
|
140
|
+
dispose(): void;
|
|
141
|
+
/**
|
|
142
|
+
* Handle auth failure (401/403): disable the key in DB permanently.
|
|
143
|
+
*/
|
|
144
|
+
private handleAuthFailure;
|
|
145
|
+
/**
|
|
146
|
+
* Apply cooldown with exponential backoff for rate-limit errors (429/529).
|
|
147
|
+
*/
|
|
148
|
+
private applyCooldown;
|
|
149
|
+
/**
|
|
150
|
+
* Get all keys for a provider (with caching).
|
|
151
|
+
*/
|
|
152
|
+
private getAllKeys;
|
|
153
|
+
/**
|
|
154
|
+
* Get available keys: enabled AND not in cooldown.
|
|
155
|
+
*/
|
|
156
|
+
private getAvailableKeys;
|
|
157
|
+
/**
|
|
158
|
+
* Weighted round-robin selection.
|
|
159
|
+
*
|
|
160
|
+
* Each key's weight determines how many "slots" it occupies in the rotation.
|
|
161
|
+
* The round-robin index advances by 1 on each call per provider.
|
|
162
|
+
*/
|
|
163
|
+
private selectWeightedRoundRobin;
|
|
164
|
+
/**
|
|
165
|
+
* Clean up expired cooldowns.
|
|
166
|
+
*/
|
|
167
|
+
private cleanupExpiredCooldowns;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export { ApiKeyPoolService as A, type KeyHealthEntry as K, type Logger as L, type ApiKeyAutoDisabler as a, type ApiKeyDisabler as b, type ApiKeyResolver as c, type ApiKeysLoader as d, type KeyHealthMap as e };
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { ApiKeyEntry } from '@omnicross/contracts/llm-config';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `Logger` — core-owned port for the structured logger the serving core uses.
|
|
5
|
+
*
|
|
6
|
+
* The serving core MUST depend on THIS interface, never on the concrete host
|
|
7
|
+
* `LoggerService` class as a type. The host (`LoggerService`) already exposes a
|
|
8
|
+
* superset of this surface, so it is passed directly with NO adapter.
|
|
9
|
+
*
|
|
10
|
+
* `error` uses the WIDEST signature `(message, error?, meta?)` so every core
|
|
11
|
+
* call site — `error(msg)`, `error(msg, errInstance)`, `error(msg, err, meta)` —
|
|
12
|
+
* stays assignable (design Q2). `info`/`warn`/`debug` take an optional `meta`
|
|
13
|
+
* bag matching the host's `Record<string, unknown> | Error | object`.
|
|
14
|
+
*
|
|
15
|
+
* @module ports/logger
|
|
16
|
+
*/
|
|
17
|
+
interface Logger {
|
|
18
|
+
info(message: string, meta?: Record<string, unknown> | Error | object): void;
|
|
19
|
+
warn(message: string, meta?: Record<string, unknown> | Error | object): void;
|
|
20
|
+
error(message: string, error?: unknown, meta?: Record<string, unknown> | object): void;
|
|
21
|
+
debug(message: string, meta?: Record<string, unknown> | Error | object): void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* ApiKeyPoolService - Multi-API-key load balancing with session affinity
|
|
26
|
+
*
|
|
27
|
+
* Manages a pool of API keys per provider, selecting keys via weighted
|
|
28
|
+
* round-robin and maintaining session-level key affinity to preserve
|
|
29
|
+
* prompt cache across requests within the same session.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/** Live health snapshot for a single key currently in cooldown. */
|
|
33
|
+
interface KeyHealthEntry {
|
|
34
|
+
/** Epoch-ms when the cooldown expires */
|
|
35
|
+
until: number;
|
|
36
|
+
/** Number of consecutive errors */
|
|
37
|
+
errors: number;
|
|
38
|
+
/** HTTP status that triggered the most recent cooldown */
|
|
39
|
+
lastStatus: number | null;
|
|
40
|
+
}
|
|
41
|
+
/** Provider-scoped map of keyId → live cooldown health (cooling keys only). */
|
|
42
|
+
type KeyHealthMap = Record<string, KeyHealthEntry>;
|
|
43
|
+
/** Function type for loading API keys from the database */
|
|
44
|
+
type ApiKeysLoader = (providerId: string) => Promise<ApiKeyEntry[]>;
|
|
45
|
+
/** Function type for disabling a key in the database (on auth failure) */
|
|
46
|
+
type ApiKeyDisabler = (keyId: string) => Promise<boolean>;
|
|
47
|
+
/**
|
|
48
|
+
* Function type for auto-disabling a key in the database on auth failure,
|
|
49
|
+
* persisting the offending status + timestamp so the client UI can surface
|
|
50
|
+
* a per-key health indicator. Preferred over {@link ApiKeyDisabler} when set.
|
|
51
|
+
*/
|
|
52
|
+
type ApiKeyAutoDisabler = (keyId: string, status: number, at: number) => Promise<void>;
|
|
53
|
+
/** Function type for resolving environment variable references in API keys */
|
|
54
|
+
type ApiKeyResolver = (rawKey: string) => string;
|
|
55
|
+
declare class ApiKeyPoolService {
|
|
56
|
+
private loadKeys;
|
|
57
|
+
private resolveKey;
|
|
58
|
+
private logger;
|
|
59
|
+
private disableKey?;
|
|
60
|
+
private markAutoDisabled?;
|
|
61
|
+
/** Session 鈫?key binding (session affinity) */
|
|
62
|
+
private sessionBindings;
|
|
63
|
+
/** Provider 鈫?round-robin index */
|
|
64
|
+
private rrIndex;
|
|
65
|
+
/** Provider 鈫?cached key list */
|
|
66
|
+
private keyCache;
|
|
67
|
+
/** Key ID 鈫?cooldown state */
|
|
68
|
+
private cooldowns;
|
|
69
|
+
/** Cleanup interval handle */
|
|
70
|
+
private cleanupTimer;
|
|
71
|
+
private readonly DEFAULT_COOLDOWN_MS;
|
|
72
|
+
private readonly MAX_COOLDOWN_MS;
|
|
73
|
+
private readonly COOLDOWN_MULTIPLIER;
|
|
74
|
+
constructor(loadKeys: ApiKeysLoader, resolveKey: ApiKeyResolver, logger: Logger, disableKey?: ApiKeyDisabler | undefined, markAutoDisabled?: ApiKeyAutoDisabler | undefined);
|
|
75
|
+
/**
|
|
76
|
+
* Get the API key for a session. Implements session affinity.
|
|
77
|
+
*
|
|
78
|
+
* First call for a session binds it to a key via weighted round-robin.
|
|
79
|
+
* Subsequent calls return the same key (preserves prompt cache).
|
|
80
|
+
*
|
|
81
|
+
* @returns Resolved API key string, or empty string if no keys available
|
|
82
|
+
*/
|
|
83
|
+
/**
|
|
84
|
+
* Read which key id is currently bound to the given session, if any.
|
|
85
|
+
* Returns null when the session has not yet been bound (first call hasn't
|
|
86
|
+
* happened) or when the binding is for a different provider.
|
|
87
|
+
*
|
|
88
|
+
* Used by the usage-recorder attribution path: after `getKeyForSession`
|
|
89
|
+
* completes the caller looks up the keyId so the recorded usage
|
|
90
|
+
* row can attribute spend to a specific pool key.
|
|
91
|
+
*/
|
|
92
|
+
getKeyIdForSession(providerId: string, sessionId: string): string | null;
|
|
93
|
+
getKeyForSession(providerId: string, sessionId: string): Promise<string>;
|
|
94
|
+
/**
|
|
95
|
+
* Get a key without session affinity (for one-shot calls like testConnection).
|
|
96
|
+
*
|
|
97
|
+
* @returns Resolved API key string, or empty string if no keys available
|
|
98
|
+
*/
|
|
99
|
+
getKey(providerId: string): Promise<string>;
|
|
100
|
+
/**
|
|
101
|
+
* Report an error for the current session's key.
|
|
102
|
+
*
|
|
103
|
+
* - 429/529 (rate limit / overload): puts key in cooldown with exponential backoff
|
|
104
|
+
* - 401/403 (auth failure): permanently disables the key in the database
|
|
105
|
+
*
|
|
106
|
+
* In both cases, re-binds the session to a different key if available.
|
|
107
|
+
*
|
|
108
|
+
* @param statusCode HTTP status code
|
|
109
|
+
* @returns New resolved API key if re-binding succeeded, null if no keys available
|
|
110
|
+
*/
|
|
111
|
+
reportError(providerId: string, sessionId: string, statusCode: number): Promise<string | null>;
|
|
112
|
+
/**
|
|
113
|
+
* Report a successful request — resets cooldown counter for the session's key.
|
|
114
|
+
*/
|
|
115
|
+
reportSuccess(sessionId: string): void;
|
|
116
|
+
/**
|
|
117
|
+
* Release session binding (call when session ends or is deleted).
|
|
118
|
+
*/
|
|
119
|
+
releaseSession(sessionId: string): void;
|
|
120
|
+
/**
|
|
121
|
+
* Invalidate the key cache. Call after CRUD operations on API keys.
|
|
122
|
+
*/
|
|
123
|
+
invalidateCache(providerId?: string): void;
|
|
124
|
+
/**
|
|
125
|
+
* Check if a provider has any keys in the pool.
|
|
126
|
+
*/
|
|
127
|
+
hasKeys(providerId: string): Promise<boolean>;
|
|
128
|
+
/**
|
|
129
|
+
* Get the live (in-memory) rate-limit cooldown health for a provider's keys.
|
|
130
|
+
*
|
|
131
|
+
* Returns ONLY keys that are currently cooling down (cooldown `until` is in
|
|
132
|
+
* the future). A key absent from the returned map is not cooling. This is a
|
|
133
|
+
* pure read of the in-memory cooldown map — auth-failure auto-disable state
|
|
134
|
+
* is persisted on the key row itself (getApiKeys) and is NOT included here.
|
|
135
|
+
*/
|
|
136
|
+
getKeyHealth(providerId: string): Promise<KeyHealthMap>;
|
|
137
|
+
/**
|
|
138
|
+
* Dispose of the service (stop cleanup timer).
|
|
139
|
+
*/
|
|
140
|
+
dispose(): void;
|
|
141
|
+
/**
|
|
142
|
+
* Handle auth failure (401/403): disable the key in DB permanently.
|
|
143
|
+
*/
|
|
144
|
+
private handleAuthFailure;
|
|
145
|
+
/**
|
|
146
|
+
* Apply cooldown with exponential backoff for rate-limit errors (429/529).
|
|
147
|
+
*/
|
|
148
|
+
private applyCooldown;
|
|
149
|
+
/**
|
|
150
|
+
* Get all keys for a provider (with caching).
|
|
151
|
+
*/
|
|
152
|
+
private getAllKeys;
|
|
153
|
+
/**
|
|
154
|
+
* Get available keys: enabled AND not in cooldown.
|
|
155
|
+
*/
|
|
156
|
+
private getAvailableKeys;
|
|
157
|
+
/**
|
|
158
|
+
* Weighted round-robin selection.
|
|
159
|
+
*
|
|
160
|
+
* Each key's weight determines how many "slots" it occupies in the rotation.
|
|
161
|
+
* The round-robin index advances by 1 on each call per provider.
|
|
162
|
+
*/
|
|
163
|
+
private selectWeightedRoundRobin;
|
|
164
|
+
/**
|
|
165
|
+
* Clean up expired cooldowns.
|
|
166
|
+
*/
|
|
167
|
+
private cleanupExpiredCooldowns;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export { ApiKeyPoolService as A, type KeyHealthEntry as K, type Logger as L, type ApiKeyAutoDisabler as a, type ApiKeyDisabler as b, type ApiKeyResolver as c, type ApiKeysLoader as d, type KeyHealthMap as e };
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { g as RouteContext, a as ProviderProxyDeps } from './types-DZIQbgp0.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ProviderProxyRouteMap — the per-run `Map<token, RouteContext>` with crypto
|
|
5
|
+
* route-token minting + TTL/idle reaping.
|
|
6
|
+
*
|
|
7
|
+
* OpenSpec `engine-provider-decouple` tasks 2.2 + 2.3 (design D9). Isolation is
|
|
8
|
+
* CODE-enforced, not network-enforced:
|
|
9
|
+
* - tokens are minted from `node:crypto` (`randomBytes`), so they are
|
|
10
|
+
* unguessable and run A's token can never resolve run B's `RouteContext`;
|
|
11
|
+
* - a lookup that misses or whose entry has been reaped returns `undefined`
|
|
12
|
+
* (the caller rejects with no fallback);
|
|
13
|
+
* - each entry carries an idle timer (`armIdleTimer` / `clearIdleTimer` /
|
|
14
|
+
* `DEFAULT_IDLE_TIMEOUT_MS`): the timer is `.unref()`'d
|
|
15
|
+
* so it never holds the process open, is touched on each `lookup`, and
|
|
16
|
+
* reaps the entry after `DEFAULT_ROUTE_IDLE_MS` with no traffic.
|
|
17
|
+
*
|
|
18
|
+
* @module provider-proxy/providerProxyRouteMap
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Idle teardown: drop a route entry after this long with no request touching
|
|
23
|
+
* it. Mirrors the ACP manager's `DEFAULT_IDLE_TIMEOUT_MS` (10 min) — a run's
|
|
24
|
+
* route should outlive normal between-request gaps but never leak after the
|
|
25
|
+
* run ends without an explicit `removeRoute`.
|
|
26
|
+
*/
|
|
27
|
+
declare const DEFAULT_ROUTE_IDLE_MS: number;
|
|
28
|
+
/**
|
|
29
|
+
* The resident proxy owns ONE of these for the whole app session. Per-run
|
|
30
|
+
* state is added at run start (`addRoute`) and removed at run end
|
|
31
|
+
* (`removeRoute`) or reaped on idle TTL.
|
|
32
|
+
*/
|
|
33
|
+
declare class ProviderProxyRouteMap {
|
|
34
|
+
private readonly defaultIdleMs;
|
|
35
|
+
private readonly routes;
|
|
36
|
+
constructor(defaultIdleMs?: number);
|
|
37
|
+
/**
|
|
38
|
+
* Register a route for one run and return its crypto-random token. The
|
|
39
|
+
* caller (next batch) injects the token as the forwarded auth-header sentinel
|
|
40
|
+
* (`ANTHROPIC_AUTH_TOKEN` / `OPENAI_API_KEY`). Optionally override the idle
|
|
41
|
+
* timeout (tests use a short one).
|
|
42
|
+
*/
|
|
43
|
+
addRoute(context: RouteContext, idleMs?: number): string;
|
|
44
|
+
/**
|
|
45
|
+
* Look up a route by its token, touching the idle timer so an active run's
|
|
46
|
+
* context survives. Returns `undefined` on a miss / reaped entry — the caller
|
|
47
|
+
* rejects (no fallback).
|
|
48
|
+
*/
|
|
49
|
+
lookup(token: string | undefined | null): RouteContext | undefined;
|
|
50
|
+
/** Remove a route at run end. Returns true if an entry existed. */
|
|
51
|
+
removeRoute(token: string): boolean;
|
|
52
|
+
/** Current live-route count (tests / diagnostics). */
|
|
53
|
+
size(): number;
|
|
54
|
+
/** Whether a token currently resolves (does NOT touch the idle timer). */
|
|
55
|
+
has(token: string): boolean;
|
|
56
|
+
/** Tear down every route (proxy stop / app teardown). */
|
|
57
|
+
clear(): void;
|
|
58
|
+
private armIdleTimer;
|
|
59
|
+
private clearIdleTimer;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* ProviderProxy — the single resident `127.0.0.1` listener that subsumes the
|
|
64
|
+
* host's per-session proxies (Anthropic Messages ingress and OpenAI Responses
|
|
65
|
+
* ingress).
|
|
66
|
+
*
|
|
67
|
+
* OpenSpec `engine-provider-decouple` Phase 1 (design D0/D3/D7/D9). It is ONLY
|
|
68
|
+
* about Providers — it does NOT know or care which agent engine is upstream.
|
|
69
|
+
*
|
|
70
|
+
* Lifecycle (task 2.1): `start()` ONCE for the app session (not per run),
|
|
71
|
+
* `stop()` at teardown, `getBaseUrl()` for injector wiring. Per-run state lives
|
|
72
|
+
* in the `ProviderProxyRouteMap`:
|
|
73
|
+
* - `addRoute(ctx) → token` at run start (task 2.2), returned so the
|
|
74
|
+
* next-batch injector can mint the forwarded auth-header sentinel;
|
|
75
|
+
* - `removeRoute(token)` at run end;
|
|
76
|
+
* - idle TTL reaping inside the map (task 2.3).
|
|
77
|
+
*
|
|
78
|
+
* Isolation is code-enforced (task 2.4 + D9): the listener binds loopback only
|
|
79
|
+
* AND refuses any request whose socket peer is not a loopback address; the
|
|
80
|
+
* route token is unguessable; a lookup miss is rejected with no fallback.
|
|
81
|
+
*
|
|
82
|
+
* @module provider-proxy/ProviderProxy
|
|
83
|
+
*/
|
|
84
|
+
|
|
85
|
+
declare class ProviderProxy {
|
|
86
|
+
private readonly deps;
|
|
87
|
+
private server;
|
|
88
|
+
private port;
|
|
89
|
+
private readonly routes;
|
|
90
|
+
constructor(deps: ProviderProxyDeps, routes?: ProviderProxyRouteMap);
|
|
91
|
+
/**
|
|
92
|
+
* Start the resident listener on a stable port on 127.0.0.1. Idempotent —
|
|
93
|
+
* a second `start()` returns the already-bound port.
|
|
94
|
+
*/
|
|
95
|
+
start(): Promise<number>;
|
|
96
|
+
/** Stop the listener, clear all routes, and release the port. */
|
|
97
|
+
stop(): Promise<void>;
|
|
98
|
+
/** Base URL for injector wiring (`ANTHROPIC_BASE_URL` / codex `base_url`). */
|
|
99
|
+
getBaseUrl(): string;
|
|
100
|
+
/**
|
|
101
|
+
* The SHARED route map. Exposed so the outbound API server
|
|
102
|
+
* (`outbound-api-server`) can mint per-request routes on the SAME map and
|
|
103
|
+
* delegate to the existing `routeRequest()` dispatch — guaranteeing a single
|
|
104
|
+
* conversion stack. Not used by the resident per-run flow.
|
|
105
|
+
*/
|
|
106
|
+
getRouteMap(): ProviderProxyRouteMap;
|
|
107
|
+
/**
|
|
108
|
+
* The app-session deps the proxy services all routes with. Exposed so the
|
|
109
|
+
* outbound server can pass them verbatim into `routeRequest()`.
|
|
110
|
+
*/
|
|
111
|
+
getDeps(): ProviderProxyDeps;
|
|
112
|
+
/** Register a route for one run; returns the crypto route token (task 2.2). */
|
|
113
|
+
addRoute(context: RouteContext, idleMs?: number): string;
|
|
114
|
+
/** Remove a route at run end. Returns true if an entry existed. */
|
|
115
|
+
removeRoute(token: string): boolean;
|
|
116
|
+
/** Live-route count (diagnostics / tests). */
|
|
117
|
+
routeCount(): number;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export { DEFAULT_ROUTE_IDLE_MS as D, ProviderProxy as P, ProviderProxyRouteMap as a };
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { g as RouteContext, a as ProviderProxyDeps } from './types-DCzHkhJt.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ProviderProxyRouteMap — the per-run `Map<token, RouteContext>` with crypto
|
|
5
|
+
* route-token minting + TTL/idle reaping.
|
|
6
|
+
*
|
|
7
|
+
* OpenSpec `engine-provider-decouple` tasks 2.2 + 2.3 (design D9). Isolation is
|
|
8
|
+
* CODE-enforced, not network-enforced:
|
|
9
|
+
* - tokens are minted from `node:crypto` (`randomBytes`), so they are
|
|
10
|
+
* unguessable and run A's token can never resolve run B's `RouteContext`;
|
|
11
|
+
* - a lookup that misses or whose entry has been reaped returns `undefined`
|
|
12
|
+
* (the caller rejects with no fallback);
|
|
13
|
+
* - each entry carries an idle timer (`armIdleTimer` / `clearIdleTimer` /
|
|
14
|
+
* `DEFAULT_IDLE_TIMEOUT_MS`): the timer is `.unref()`'d
|
|
15
|
+
* so it never holds the process open, is touched on each `lookup`, and
|
|
16
|
+
* reaps the entry after `DEFAULT_ROUTE_IDLE_MS` with no traffic.
|
|
17
|
+
*
|
|
18
|
+
* @module provider-proxy/providerProxyRouteMap
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Idle teardown: drop a route entry after this long with no request touching
|
|
23
|
+
* it. Mirrors the ACP manager's `DEFAULT_IDLE_TIMEOUT_MS` (10 min) — a run's
|
|
24
|
+
* route should outlive normal between-request gaps but never leak after the
|
|
25
|
+
* run ends without an explicit `removeRoute`.
|
|
26
|
+
*/
|
|
27
|
+
declare const DEFAULT_ROUTE_IDLE_MS: number;
|
|
28
|
+
/**
|
|
29
|
+
* The resident proxy owns ONE of these for the whole app session. Per-run
|
|
30
|
+
* state is added at run start (`addRoute`) and removed at run end
|
|
31
|
+
* (`removeRoute`) or reaped on idle TTL.
|
|
32
|
+
*/
|
|
33
|
+
declare class ProviderProxyRouteMap {
|
|
34
|
+
private readonly defaultIdleMs;
|
|
35
|
+
private readonly routes;
|
|
36
|
+
constructor(defaultIdleMs?: number);
|
|
37
|
+
/**
|
|
38
|
+
* Register a route for one run and return its crypto-random token. The
|
|
39
|
+
* caller (next batch) injects the token as the forwarded auth-header sentinel
|
|
40
|
+
* (`ANTHROPIC_AUTH_TOKEN` / `OPENAI_API_KEY`). Optionally override the idle
|
|
41
|
+
* timeout (tests use a short one).
|
|
42
|
+
*/
|
|
43
|
+
addRoute(context: RouteContext, idleMs?: number): string;
|
|
44
|
+
/**
|
|
45
|
+
* Look up a route by its token, touching the idle timer so an active run's
|
|
46
|
+
* context survives. Returns `undefined` on a miss / reaped entry — the caller
|
|
47
|
+
* rejects (no fallback).
|
|
48
|
+
*/
|
|
49
|
+
lookup(token: string | undefined | null): RouteContext | undefined;
|
|
50
|
+
/** Remove a route at run end. Returns true if an entry existed. */
|
|
51
|
+
removeRoute(token: string): boolean;
|
|
52
|
+
/** Current live-route count (tests / diagnostics). */
|
|
53
|
+
size(): number;
|
|
54
|
+
/** Whether a token currently resolves (does NOT touch the idle timer). */
|
|
55
|
+
has(token: string): boolean;
|
|
56
|
+
/** Tear down every route (proxy stop / app teardown). */
|
|
57
|
+
clear(): void;
|
|
58
|
+
private armIdleTimer;
|
|
59
|
+
private clearIdleTimer;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* ProviderProxy — the single resident `127.0.0.1` listener that subsumes the
|
|
64
|
+
* host's per-session proxies (Anthropic Messages ingress and OpenAI Responses
|
|
65
|
+
* ingress).
|
|
66
|
+
*
|
|
67
|
+
* OpenSpec `engine-provider-decouple` Phase 1 (design D0/D3/D7/D9). It is ONLY
|
|
68
|
+
* about Providers — it does NOT know or care which agent engine is upstream.
|
|
69
|
+
*
|
|
70
|
+
* Lifecycle (task 2.1): `start()` ONCE for the app session (not per run),
|
|
71
|
+
* `stop()` at teardown, `getBaseUrl()` for injector wiring. Per-run state lives
|
|
72
|
+
* in the `ProviderProxyRouteMap`:
|
|
73
|
+
* - `addRoute(ctx) → token` at run start (task 2.2), returned so the
|
|
74
|
+
* next-batch injector can mint the forwarded auth-header sentinel;
|
|
75
|
+
* - `removeRoute(token)` at run end;
|
|
76
|
+
* - idle TTL reaping inside the map (task 2.3).
|
|
77
|
+
*
|
|
78
|
+
* Isolation is code-enforced (task 2.4 + D9): the listener binds loopback only
|
|
79
|
+
* AND refuses any request whose socket peer is not a loopback address; the
|
|
80
|
+
* route token is unguessable; a lookup miss is rejected with no fallback.
|
|
81
|
+
*
|
|
82
|
+
* @module provider-proxy/ProviderProxy
|
|
83
|
+
*/
|
|
84
|
+
|
|
85
|
+
declare class ProviderProxy {
|
|
86
|
+
private readonly deps;
|
|
87
|
+
private server;
|
|
88
|
+
private port;
|
|
89
|
+
private readonly routes;
|
|
90
|
+
constructor(deps: ProviderProxyDeps, routes?: ProviderProxyRouteMap);
|
|
91
|
+
/**
|
|
92
|
+
* Start the resident listener on a stable port on 127.0.0.1. Idempotent —
|
|
93
|
+
* a second `start()` returns the already-bound port.
|
|
94
|
+
*/
|
|
95
|
+
start(): Promise<number>;
|
|
96
|
+
/** Stop the listener, clear all routes, and release the port. */
|
|
97
|
+
stop(): Promise<void>;
|
|
98
|
+
/** Base URL for injector wiring (`ANTHROPIC_BASE_URL` / codex `base_url`). */
|
|
99
|
+
getBaseUrl(): string;
|
|
100
|
+
/**
|
|
101
|
+
* The SHARED route map. Exposed so the outbound API server
|
|
102
|
+
* (`outbound-api-server`) can mint per-request routes on the SAME map and
|
|
103
|
+
* delegate to the existing `routeRequest()` dispatch — guaranteeing a single
|
|
104
|
+
* conversion stack. Not used by the resident per-run flow.
|
|
105
|
+
*/
|
|
106
|
+
getRouteMap(): ProviderProxyRouteMap;
|
|
107
|
+
/**
|
|
108
|
+
* The app-session deps the proxy services all routes with. Exposed so the
|
|
109
|
+
* outbound server can pass them verbatim into `routeRequest()`.
|
|
110
|
+
*/
|
|
111
|
+
getDeps(): ProviderProxyDeps;
|
|
112
|
+
/** Register a route for one run; returns the crypto route token (task 2.2). */
|
|
113
|
+
addRoute(context: RouteContext, idleMs?: number): string;
|
|
114
|
+
/** Remove a route at run end. Returns true if an entry existed. */
|
|
115
|
+
removeRoute(token: string): boolean;
|
|
116
|
+
/** Live-route count (diagnostics / tests). */
|
|
117
|
+
routeCount(): number;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export { DEFAULT_ROUTE_IDLE_MS as D, ProviderProxy as P, ProviderProxyRouteMap as a };
|