@omnicross/cli-launcher 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 +38 -0
- package/README.md +21 -0
- package/dist/index.cjs +865 -0
- package/dist/index.d.cts +468 -0
- package/dist/index.d.ts +468 -0
- package/dist/index.js +851 -0
- package/package.json +56 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
import { ChildProcess } from 'node:child_process';
|
|
2
|
+
import { ProviderConfigSource, UsageRecorderImport } from '@omnicross/core';
|
|
3
|
+
import { ApiKeyPoolService } from '@omnicross/core/completion/ApiKeyPoolService';
|
|
4
|
+
import { SubscriptionAuthProfile } from '@omnicross/core/pipeline/SubscriptionAuthSource';
|
|
5
|
+
import { RouteAuthMode } from '@omnicross/core/provider-proxy';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Process Supervisor types.
|
|
9
|
+
*
|
|
10
|
+
* Defines all interfaces for subprocess lifecycle management:
|
|
11
|
+
* spawn inputs, run records, exit results, and module contracts.
|
|
12
|
+
*
|
|
13
|
+
* @module types
|
|
14
|
+
*/
|
|
15
|
+
type RunState = 'starting' | 'running' | 'exiting' | 'exited';
|
|
16
|
+
type TerminationReason = 'manual-cancel' | 'overall-timeout' | 'no-output-timeout' | 'spawn-error' | 'signal' | 'exit';
|
|
17
|
+
interface SpawnBaseInput {
|
|
18
|
+
runId?: string;
|
|
19
|
+
sessionId: string;
|
|
20
|
+
backendId: string;
|
|
21
|
+
scopeKey?: string;
|
|
22
|
+
replaceExistingScope?: boolean;
|
|
23
|
+
cwd?: string;
|
|
24
|
+
env?: Record<string, string>;
|
|
25
|
+
timeoutMs?: number;
|
|
26
|
+
noOutputTimeoutMs?: number;
|
|
27
|
+
captureOutput?: boolean;
|
|
28
|
+
onStdout?: (chunk: string) => void;
|
|
29
|
+
onStderr?: (chunk: string) => void;
|
|
30
|
+
}
|
|
31
|
+
interface SpawnChildInput extends SpawnBaseInput {
|
|
32
|
+
mode: 'child';
|
|
33
|
+
argv: string[];
|
|
34
|
+
windowsVerbatimArguments?: boolean;
|
|
35
|
+
input?: string;
|
|
36
|
+
stdinMode?: 'inherit' | 'pipe-open' | 'pipe-closed';
|
|
37
|
+
}
|
|
38
|
+
interface SpawnPtyInput extends SpawnBaseInput {
|
|
39
|
+
mode: 'pty';
|
|
40
|
+
/** Shell executable (defaults: powershell.exe on Windows, /bin/bash on Unix). */
|
|
41
|
+
shell?: string;
|
|
42
|
+
/** Arguments passed to the shell. */
|
|
43
|
+
args?: string[];
|
|
44
|
+
/** Terminal columns (default: 120). */
|
|
45
|
+
cols?: number;
|
|
46
|
+
/** Terminal rows (default: 30). */
|
|
47
|
+
rows?: number;
|
|
48
|
+
}
|
|
49
|
+
/** Spawn input union — child-process or PTY mode. */
|
|
50
|
+
type SpawnInput = SpawnChildInput | SpawnPtyInput;
|
|
51
|
+
interface RunRecord {
|
|
52
|
+
runId: string;
|
|
53
|
+
sessionId: string;
|
|
54
|
+
backendId: string;
|
|
55
|
+
scopeKey?: string;
|
|
56
|
+
pid?: number;
|
|
57
|
+
startedAtMs: number;
|
|
58
|
+
lastOutputAtMs: number;
|
|
59
|
+
createdAtMs: number;
|
|
60
|
+
updatedAtMs: number;
|
|
61
|
+
state: RunState;
|
|
62
|
+
terminationReason?: TerminationReason;
|
|
63
|
+
exitCode?: number | null;
|
|
64
|
+
exitSignal?: string | null;
|
|
65
|
+
}
|
|
66
|
+
interface RunExit {
|
|
67
|
+
reason: TerminationReason;
|
|
68
|
+
exitCode: number | null;
|
|
69
|
+
exitSignal: string | null;
|
|
70
|
+
durationMs: number;
|
|
71
|
+
stdout: string;
|
|
72
|
+
stderr: string;
|
|
73
|
+
timedOut: boolean;
|
|
74
|
+
noOutputTimedOut: boolean;
|
|
75
|
+
}
|
|
76
|
+
interface ManagedRunStdin {
|
|
77
|
+
write(data: string): void;
|
|
78
|
+
end(): void;
|
|
79
|
+
destroy(): void;
|
|
80
|
+
}
|
|
81
|
+
interface ManagedRun {
|
|
82
|
+
runId: string;
|
|
83
|
+
pid?: number;
|
|
84
|
+
startedAtMs: number;
|
|
85
|
+
stdin?: ManagedRunStdin;
|
|
86
|
+
wait: () => Promise<RunExit>;
|
|
87
|
+
cancel: (reason?: TerminationReason) => void;
|
|
88
|
+
}
|
|
89
|
+
interface ProcessSupervisor {
|
|
90
|
+
spawn(input: SpawnInput): ManagedRun;
|
|
91
|
+
cancel(runId: string, reason?: TerminationReason): void;
|
|
92
|
+
cancelScope(scopeKey: string, reason?: TerminationReason): void;
|
|
93
|
+
getRecord(runId: string): RunRecord | undefined;
|
|
94
|
+
/** Resize the PTY terminal. No-op for non-PTY runs. */
|
|
95
|
+
resizePty(runId: string, cols: number, rows: number): boolean;
|
|
96
|
+
}
|
|
97
|
+
interface RunRegistry {
|
|
98
|
+
add(record: RunRecord): void;
|
|
99
|
+
get(runId: string): RunRecord | undefined;
|
|
100
|
+
list(): RunRecord[];
|
|
101
|
+
listByScope(scopeKey: string): RunRecord[];
|
|
102
|
+
updateState(runId: string, state: RunState, patch?: Partial<RunRecord>): void;
|
|
103
|
+
touchOutput(runId: string): void;
|
|
104
|
+
finalize(runId: string, reason: TerminationReason, exitCode?: number | null, exitSignal?: string | null): void;
|
|
105
|
+
delete(runId: string): void;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* child_process spawn adapter.
|
|
110
|
+
*
|
|
111
|
+
* Wraps Node.js child_process.spawn with platform-specific defaults:
|
|
112
|
+
* - Windows: windowsHide=true, not detached
|
|
113
|
+
* - Unix: detached=true for process group kill support
|
|
114
|
+
*
|
|
115
|
+
* @module child-adapter
|
|
116
|
+
*/
|
|
117
|
+
|
|
118
|
+
interface ChildAdapterHandle {
|
|
119
|
+
pid: number | undefined;
|
|
120
|
+
onStdout: (cb: (chunk: string) => void) => void;
|
|
121
|
+
onStderr: (cb: (chunk: string) => void) => void;
|
|
122
|
+
wait: () => Promise<{
|
|
123
|
+
code: number | null;
|
|
124
|
+
signal: string | null;
|
|
125
|
+
}>;
|
|
126
|
+
kill: (signal?: NodeJS.Signals) => void;
|
|
127
|
+
stdin: ChildProcess['stdin'];
|
|
128
|
+
dispose: () => void;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* PTY Adapter — wraps node-pty into the same handle shape
|
|
133
|
+
* used by child-adapter, so ProcessSupervisor can manage
|
|
134
|
+
* both child_process and PTY sessions uniformly.
|
|
135
|
+
*
|
|
136
|
+
* node-pty is lazily loaded — the import only happens when
|
|
137
|
+
* a PTY spawn is actually requested.
|
|
138
|
+
*
|
|
139
|
+
* @module pty-adapter
|
|
140
|
+
*/
|
|
141
|
+
|
|
142
|
+
interface PtyAdapterHandle {
|
|
143
|
+
pid: number | undefined;
|
|
144
|
+
onStdout(cb: (data: string) => void): void;
|
|
145
|
+
onStderr(cb: (data: string) => void): void;
|
|
146
|
+
stdin: {
|
|
147
|
+
write(data: string): void;
|
|
148
|
+
end(): void;
|
|
149
|
+
destroy(): void;
|
|
150
|
+
};
|
|
151
|
+
write(data: string): void;
|
|
152
|
+
resize(cols: number, rows: number): void;
|
|
153
|
+
wait(): Promise<{
|
|
154
|
+
code: number | null;
|
|
155
|
+
signal: string | null;
|
|
156
|
+
}>;
|
|
157
|
+
kill(signal?: string): void;
|
|
158
|
+
dispose(): void;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Per-CLI launch-config builders for the GENUINELY-NEW interceptable backends
|
|
163
|
+
* (qwen / copilot / opencode) — the OpenAI-Chat-Completions analogue of
|
|
164
|
+
* `codex/codex-proxy-env.ts`'s `buildCodexLaunchConfig`.
|
|
165
|
+
*
|
|
166
|
+
* Each backend's model egress is redirected at the resident `ProviderProxy`'s
|
|
167
|
+
* OpenAI Chat Completions ingress (`POST <base>/v1/chat/completions`). Like the
|
|
168
|
+
* codex builder, this registers ONE route on the resident proxy (`addRoute →
|
|
169
|
+
* token`) and returns an `onSessionEnd` that drops it. The route TOKEN is
|
|
170
|
+
* forwarded by the CLI as its API key; the proxy looks it up, discards it, and
|
|
171
|
+
* re-authenticates upstream from the route's own provider credential — so the
|
|
172
|
+
* key the CLI carries is never used upstream.
|
|
173
|
+
*
|
|
174
|
+
* The redirect MECHANISM differs per CLI (from the 2026-05-27 R1/R2 research):
|
|
175
|
+
* - qwen-code: env `OPENAI_BASE_URL` + `OPENAI_API_KEY` (token) + `OPENAI_MODEL`.
|
|
176
|
+
* - copilot: env `COPILOT_PROVIDER_BASE_URL` + `COPILOT_PROVIDER_TYPE=openai`
|
|
177
|
+
* + `COPILOT_PROVIDER_API_KEY` (token) + `COPILOT_MODEL`.
|
|
178
|
+
* - opencode: a CONFIG FILE (`@ai-sdk/openai-compatible` adapter, no base-url
|
|
179
|
+
* env redirect — the anthropic adapter's baseURL is buggy). The
|
|
180
|
+
* file sets `provider.<id>.options.baseURL` + `apiKey:"{env:VAR}"`
|
|
181
|
+
* with the token in that env var; opencode is fixed to this
|
|
182
|
+
* provider until restart (NOT live-reconfigurable mid-session).
|
|
183
|
+
*
|
|
184
|
+
* Gating (the two-bucket rule) lives in the CALLER (`applyCliProxyForCli`):
|
|
185
|
+
* these builders are invoked ONLY for `providerChannel ∈ {api-key, relay}`.
|
|
186
|
+
*
|
|
187
|
+
* @module @omnicross/cli-launcher/proxy-env/cli-proxy-env
|
|
188
|
+
*/
|
|
189
|
+
|
|
190
|
+
/** Which genuinely-new chat-completions backend this launch config is for. */
|
|
191
|
+
type ChatCliBackendId = 'qwen' | 'copilot' | 'opencode';
|
|
192
|
+
/**
|
|
193
|
+
* The base PATH the OpenAI Chat Completions ingress is served under. The proxy
|
|
194
|
+
* matches any path ENDING in `/chat/completions`; we serve the canonical
|
|
195
|
+
* `/v1/chat/completions` so the qwen / copilot OpenAI clients append nothing
|
|
196
|
+
* (they treat `OPENAI_BASE_URL` as `<base>` and POST `<base>/chat/completions`,
|
|
197
|
+
* so the base we hand them is `<listener>/v1`).
|
|
198
|
+
*/
|
|
199
|
+
declare const CHAT_PROXY_BASE_PATH = "/v1";
|
|
200
|
+
/**
|
|
201
|
+
* The opencode provider id written into the session-scoped config file. Distinct,
|
|
202
|
+
* proxy-owned so it can never collide with a provider in the user's own opencode
|
|
203
|
+
* config.
|
|
204
|
+
*/
|
|
205
|
+
declare const OPENCODE_PROXY_PROVIDER_ID = "omnicross";
|
|
206
|
+
/** The env var the opencode config's `apiKey:"{env:VAR}"` reference resolves. */
|
|
207
|
+
declare const OPENCODE_PROXY_TOKEN_ENV = "OMNICROSS_OPENCODE_TOKEN";
|
|
208
|
+
/** Inputs for `buildChatCliLaunchConfig`. */
|
|
209
|
+
interface ChatCliLaunchConfigInputs {
|
|
210
|
+
readonly backendId: ChatCliBackendId;
|
|
211
|
+
readonly llmConfig: ProviderConfigSource;
|
|
212
|
+
/** OpenAI-compatible provider row id (BYO / api-key / relay) the proxy re-auths with. */
|
|
213
|
+
readonly providerId: string;
|
|
214
|
+
/** The provider model the CLI's model name is mapped to. */
|
|
215
|
+
readonly model: string;
|
|
216
|
+
/** Pool for session-affine key selection + 429/401 failover (optional). */
|
|
217
|
+
readonly apiKeyPool?: ApiKeyPoolService | null;
|
|
218
|
+
/** Session id for pool affinity + usage attribution. */
|
|
219
|
+
readonly sessionId?: string | null;
|
|
220
|
+
/** Usage recorder — when set, non-stream chat-ingress usage is persisted. */
|
|
221
|
+
readonly usageRecorder?: UsageRecorderImport | null;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* The launch redirection for a chat-completions CLI subprocess. Mirrors
|
|
225
|
+
* `CodexLaunchConfig`: `env` + `onSessionEnd`, plus the listener `baseUrl`. (No
|
|
226
|
+
* `extraArgs` — these backends redirect via env / config file, not CLI flags.)
|
|
227
|
+
*/
|
|
228
|
+
interface ChatCliLaunchConfig {
|
|
229
|
+
/** Env vars to merge into the CLI subprocess env (redirect + auth sentinel). */
|
|
230
|
+
readonly env: Record<string, string>;
|
|
231
|
+
/** Listener base (`http://127.0.0.1:<port>`). */
|
|
232
|
+
readonly baseUrl: string;
|
|
233
|
+
/** Drop this run's route (+ clean up any temp config file). Best-effort; never throws. */
|
|
234
|
+
readonly onSessionEnd: () => void;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Build the env + route for a chat-completions CLI redirect. Validates the
|
|
238
|
+
* provider row + key up front (throws BEFORE registering a route when the
|
|
239
|
+
* provider is missing / keyless), then registers one route on the resident proxy
|
|
240
|
+
* and shapes the per-backend env.
|
|
241
|
+
*/
|
|
242
|
+
declare function buildChatCliLaunchConfig(inputs: ChatCliLaunchConfigInputs): Promise<ChatCliLaunchConfig>;
|
|
243
|
+
/**
|
|
244
|
+
* Inputs for `buildGeminiCliLaunchConfig`. Same shape as the chat-CLI inputs
|
|
245
|
+
* minus `backendId` (this builder is gemini-CLI only).
|
|
246
|
+
*/
|
|
247
|
+
interface GeminiCliLaunchConfigInputs {
|
|
248
|
+
readonly llmConfig: ProviderConfigSource;
|
|
249
|
+
/** OpenAI-/Gemini-compatible provider row id the proxy re-auths with. */
|
|
250
|
+
readonly providerId: string;
|
|
251
|
+
/** The provider model the gemini-CLI's path model is mapped to. */
|
|
252
|
+
readonly model: string;
|
|
253
|
+
readonly apiKeyPool?: ApiKeyPoolService | null;
|
|
254
|
+
readonly sessionId?: string | null;
|
|
255
|
+
readonly usageRecorder?: UsageRecorderImport | null;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Build the env + route for the gemini-CLI redirect (api-key/relay ONLY).
|
|
259
|
+
*
|
|
260
|
+
* The gemini-CLI treats `GOOGLE_GEMINI_BASE_URL` as the API BASE and itself
|
|
261
|
+
* appends `/v1beta/models/<model>:generateContent` (or `:streamGenerateContent`),
|
|
262
|
+
* so the base we hand it is the listener ROOT (no `/v1` suffix — unlike the
|
|
263
|
+
* chat-completions CLIs). The route TOKEN rides `GEMINI_API_KEY`; the gemini-CLI
|
|
264
|
+
* forwards it as the `x-goog-api-key` header, which the proxy router reads for
|
|
265
|
+
* the route lookup and then discards (re-authing upstream from the route's own
|
|
266
|
+
* provider credential).
|
|
267
|
+
*
|
|
268
|
+
* CRITICAL — force API-key mode: the gemini-CLI's DEFAULT egress is the
|
|
269
|
+
* OAuth/Code-Assist (`LOGIN_WITH_GOOGLE`) path, which talks to
|
|
270
|
+
* `cloudcode-pa.googleapis.com` and is NOT interceptable. The CLI's
|
|
271
|
+
* auth-selection treats the PRESENCE of `GEMINI_API_KEY` as
|
|
272
|
+
* `AuthType.USE_GEMINI` (the Gemini-API-key path that DOES honor
|
|
273
|
+
* `GOOGLE_GEMINI_BASE_URL`). We additionally set `GOOGLE_GENAI_USE_GCA=false` to
|
|
274
|
+
* defensively suppress the Code-Assist branch, and deliberately do NOT set
|
|
275
|
+
* `GOOGLE_API_KEY` / `GOOGLE_GENAI_USE_VERTEXAI` / `GOOGLE_CLOUD_PROJECT` (which
|
|
276
|
+
* would steer the CLI onto the Vertex egress). See the change notes for the
|
|
277
|
+
* binary-unverified caveat.
|
|
278
|
+
*/
|
|
279
|
+
declare function buildGeminiCliLaunchConfig(inputs: GeminiCliLaunchConfigInputs): Promise<ChatCliLaunchConfig>;
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Claude CLI launch-config builder — the claude analogue of
|
|
283
|
+
* `cli-proxy-env.ts`'s `buildChatCliLaunchConfig` (daemon-parity launch knife).
|
|
284
|
+
*
|
|
285
|
+
* The claude CLI honors `ANTHROPIC_BASE_URL` as the API base (it appends
|
|
286
|
+
* `/v1/messages` itself) and forwards `ANTHROPIC_AUTH_TOKEN` as
|
|
287
|
+
* `Authorization: Bearer <value>` — which is exactly where the resident
|
|
288
|
+
* `ProviderProxy` router reads the route token from (`providerProxyRouter.ts`
|
|
289
|
+
* `resolveRouteToken`: `Authorization: Bearer` first, `x-goog-api-key` as the
|
|
290
|
+
* gemini-only fallback; `x-api-key` is NOT a route-token source). So the token
|
|
291
|
+
* rides `ANTHROPIC_AUTH_TOKEN`, the proxy looks the route up, discards the
|
|
292
|
+
* sentinel, and re-authenticates upstream from the route's own provider
|
|
293
|
+
* credential.
|
|
294
|
+
*
|
|
295
|
+
* `ANTHROPIC_API_KEY` is set to a NON-SECRET placeholder (`omnicross-proxy`)
|
|
296
|
+
* purely to suppress the CLI's interactive login/keychain prompts. A host that
|
|
297
|
+
* passes the REAL key for anthropic-format providers would let the Claude Agent
|
|
298
|
+
* SDK detect a first-party provider and enable server-side WebSearch; this
|
|
299
|
+
* launch contract DELIBERATELY does not — "upstream credentials NEVER enter the
|
|
300
|
+
* CLI env" — so claude's server-side tools won't self-enable.
|
|
301
|
+
*
|
|
302
|
+
* `targetProviderFormat` mirrors the BYO mint point: `'anthropic'` for an
|
|
303
|
+
* anthropic-format provider (same-format fast path) and `'transform'` otherwise.
|
|
304
|
+
*
|
|
305
|
+
* @module @omnicross/cli-launcher/proxy-env/claude-proxy-env
|
|
306
|
+
*/
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Non-secret `ANTHROPIC_API_KEY` placeholder. The proxy router strips every
|
|
310
|
+
* auth header (`AUTH_HEADER_KEYS` includes `x-api-key`) before re-authing
|
|
311
|
+
* upstream, so this value never reaches a provider.
|
|
312
|
+
*/
|
|
313
|
+
declare const CLAUDE_PROXY_API_KEY_SENTINEL = "omnicross-proxy";
|
|
314
|
+
/** Inputs for `buildClaudeCliLaunchConfig` — mirrors the chat-CLI inputs. */
|
|
315
|
+
interface ClaudeCliLaunchConfigInputs {
|
|
316
|
+
readonly llmConfig: ProviderConfigSource;
|
|
317
|
+
/** Provider row id (BYO) the proxy re-auths with. */
|
|
318
|
+
readonly providerId: string;
|
|
319
|
+
/** The provider model the claude CLI's model name is mapped to. */
|
|
320
|
+
readonly model: string;
|
|
321
|
+
/** Pool for session-affine key selection + 429/401 failover (optional). */
|
|
322
|
+
readonly apiKeyPool?: ApiKeyPoolService | null;
|
|
323
|
+
/** Session id for pool affinity + usage attribution. */
|
|
324
|
+
readonly sessionId?: string | null;
|
|
325
|
+
/** Usage recorder — when set, anthropic-ingress usage is persisted. */
|
|
326
|
+
readonly usageRecorder?: UsageRecorderImport | null;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Build the env + route for a claude CLI redirect (BYO only). Validates the
|
|
330
|
+
* provider row + key up front (throws BEFORE registering a route when the
|
|
331
|
+
* provider is missing / keyless), then registers one anthropic-messages route
|
|
332
|
+
* on the resident proxy.
|
|
333
|
+
*/
|
|
334
|
+
declare function buildClaudeCliLaunchConfig(inputs: ClaudeCliLaunchConfigInputs): Promise<ChatCliLaunchConfig>;
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Codex CLI launch-config builder — the Codex analogue of the host's
|
|
338
|
+
* Claude-SDK provider-env builder (`buildProviderEnvWithProxy`).
|
|
339
|
+
*
|
|
340
|
+
* The Claude Agent SDK is redirected at a local proxy server by injecting
|
|
341
|
+
* `ANTHROPIC_BASE_URL` (+ auth token) into the SDK subprocess env. The Codex CLI
|
|
342
|
+
* is the same idea with a different redirect MECHANISM (see STEP 1 finding in the
|
|
343
|
+
* `codex-responses-ingress` change): the Codex CLI does NOT honor an
|
|
344
|
+
* `OPENAI_BASE_URL` env var for base-url redirection. It is redirected via its
|
|
345
|
+
* config — `~/.codex/config.toml`'s `[model_providers.<name>]` block
|
|
346
|
+
* (`base_url`, `wire_api = "responses"`, `requires_openai_auth = true`) selected
|
|
347
|
+
* by `model_provider = "<name>"`. Codex exposes those config keys as
|
|
348
|
+
* command-line `-c key=value` overrides (the SAME mechanism the canvas-MCP and
|
|
349
|
+
* builtin-MCP CLI injectors already use for `mcp_servers.*`), so this builder
|
|
350
|
+
* returns the `-c` overrides as `extraArgs` rather than writing the user's
|
|
351
|
+
* `~/.codex/config.toml` on disk. This keeps the redirection session-scoped and
|
|
352
|
+
* leaves the user's real config untouched (no file management, no rollback risk).
|
|
353
|
+
*
|
|
354
|
+
* The API key the CLI sends is supplied through the `requires_openai_auth` env
|
|
355
|
+
* (`OPENAI_API_KEY`) — a placeholder is sufficient because the proxy
|
|
356
|
+
* re-authenticates every upstream call with the resolved provider/subscription
|
|
357
|
+
* credential; the value the CLI forwards is never used upstream. We still inject
|
|
358
|
+
* a sentinel so the CLI's `requires_openai_auth = true` precondition is met
|
|
359
|
+
* without the CLI falling back to a real `~/.codex/auth.json`.
|
|
360
|
+
*
|
|
361
|
+
* What this builder returns (mirroring `ProviderEnvResult` from provider-env.ts):
|
|
362
|
+
* - `env` — env vars merged into the Codex subprocess (the auth sentinel).
|
|
363
|
+
* - `extraArgs` — the `-c` config overrides that point the CLI at the proxy.
|
|
364
|
+
* - `onSessionEnd` — stops the booted proxy listener (proxy lifecycle tied
|
|
365
|
+
* to the Codex run, exactly like provider-env.ts).
|
|
366
|
+
* - `baseUrl` — the listener base (`http://127.0.0.1:<port>`); the CLI
|
|
367
|
+
* appends the configured path → `<baseUrl>/openai/responses`.
|
|
368
|
+
*
|
|
369
|
+
* @module @omnicross/cli-launcher/proxy-env/codex-proxy-env
|
|
370
|
+
*/
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* The provider/model-provider NAME the Codex CLI selects via `model_provider`.
|
|
374
|
+
* Distinct, proxy-owned name so it can never collide with a real provider the
|
|
375
|
+
* user configured in their own `~/.codex/config.toml`.
|
|
376
|
+
*/
|
|
377
|
+
declare const CODEX_PROXY_PROVIDER_NAME = "omnicross";
|
|
378
|
+
/**
|
|
379
|
+
* The base path the listener serves the Responses-API route under. The Codex
|
|
380
|
+
* CLI appends `/responses` to `base_url`, so with `base_url = <listener>/openai`
|
|
381
|
+
* the listener receives `POST /openai/responses` — the route the proxy's
|
|
382
|
+
* Responses ingress matches.
|
|
383
|
+
*/
|
|
384
|
+
declare const CODEX_PROXY_BASE_PATH = "/openai";
|
|
385
|
+
/** Inputs for `buildCodexLaunchConfig` — mirrors provider-env.ts's positional args as a struct. */
|
|
386
|
+
interface CodexLaunchConfigInputs {
|
|
387
|
+
readonly llmConfig: ProviderConfigSource;
|
|
388
|
+
/**
|
|
389
|
+
* OpenAI-compatible provider row id (BYO mode) whose key/headers authenticate
|
|
390
|
+
* the upstream call. In subscription mode this is still the row to attribute
|
|
391
|
+
* usage to, but auth flows through `subscriptionProfile`.
|
|
392
|
+
*/
|
|
393
|
+
readonly providerId: string;
|
|
394
|
+
/** The provider model the Codex CLI's model name is mapped to. */
|
|
395
|
+
readonly model: string;
|
|
396
|
+
/** Auth mode (design D4). Defaults to `'byo'`. */
|
|
397
|
+
readonly authMode?: RouteAuthMode;
|
|
398
|
+
/** REQUIRED when `authMode === 'subscription'`; ignored otherwise. */
|
|
399
|
+
readonly subscriptionProfile?: SubscriptionAuthProfile | null;
|
|
400
|
+
/** Pool for session-affine key selection + 429/401 failover (optional). */
|
|
401
|
+
readonly apiKeyPool?: ApiKeyPoolService | null;
|
|
402
|
+
/** Session id for pool affinity + usage attribution. */
|
|
403
|
+
readonly sessionId?: string | null;
|
|
404
|
+
/** Usage recorder — when set, non-stream codex-ingress usage is persisted. */
|
|
405
|
+
readonly usageRecorder?: UsageRecorderImport | null;
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* The launch redirection for the Codex CLI subprocess. Shaped to mirror
|
|
409
|
+
* `ProviderEnvResult` (from provider-env.ts): `env` + `onSessionEnd`, plus the
|
|
410
|
+
* Codex-specific `extraArgs` (config overrides) and the `baseUrl`.
|
|
411
|
+
*/
|
|
412
|
+
interface CodexLaunchConfig {
|
|
413
|
+
/**
|
|
414
|
+
* Env vars to merge into the Codex subprocess env. The Codex CLI's
|
|
415
|
+
* `requires_openai_auth = true` precondition is satisfied by a sentinel
|
|
416
|
+
* `OPENAI_API_KEY` (the value is never used upstream — the proxy re-auths).
|
|
417
|
+
*/
|
|
418
|
+
readonly env: Record<string, string>;
|
|
419
|
+
/**
|
|
420
|
+
* `-c key=value` config overrides appended to the Codex argv. These override
|
|
421
|
+
* `~/.codex/config.toml` for THIS spawn only, pointing the CLI at the proxy:
|
|
422
|
+
* `-c model_provider="omnicross"`
|
|
423
|
+
* `-c model_providers.omnicross.name="omnicross"`
|
|
424
|
+
* `-c model_providers.omnicross.base_url="http://127.0.0.1:<port>/openai"`
|
|
425
|
+
* `-c model_providers.omnicross.wire_api="responses"`
|
|
426
|
+
* `-c model_providers.omnicross.requires_openai_auth=true`
|
|
427
|
+
* (`requires_openai_auth` is an UNQUOTED boolean — TOML overrides are typed.)
|
|
428
|
+
*/
|
|
429
|
+
readonly extraArgs: string[];
|
|
430
|
+
/** Stops the booted proxy listener. Best-effort; never throws. */
|
|
431
|
+
readonly onSessionEnd: () => void;
|
|
432
|
+
/** Listener base (`http://127.0.0.1:<port>`); CLI appends `/openai/responses`. */
|
|
433
|
+
readonly baseUrl: string;
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Build the `-c` config overrides that redirect the Codex CLI at the proxy.
|
|
437
|
+
* Split out so it can be unit-tested without booting a server (the values are
|
|
438
|
+
* the load-bearing contract — the TOML keys + types the CLI must receive).
|
|
439
|
+
*
|
|
440
|
+
* @param baseUrl listener base (`http://127.0.0.1:<port>`)
|
|
441
|
+
*/
|
|
442
|
+
declare function buildCodexConfigOverrides(baseUrl: string): string[];
|
|
443
|
+
/**
|
|
444
|
+
* Boot the codex proxy route and return the launch redirection for the Codex CLI
|
|
445
|
+
* subprocess. The analogue of `buildProviderEnvWithProxy` for the Codex CLI.
|
|
446
|
+
*
|
|
447
|
+
* Lifecycle: this starts the proxy (so `baseUrl` has a real port) and returns an
|
|
448
|
+
* `onSessionEnd` that stops it — the caller wires `onSessionEnd` into the run's
|
|
449
|
+
* cleanup (the host CLI runner's `finally`), exactly as provider-env callers wire
|
|
450
|
+
* `ProviderEnvResult.onSessionEnd`.
|
|
451
|
+
*
|
|
452
|
+
* In BYO mode this validates the provider row + key up front and throws a clear
|
|
453
|
+
* error (no proxy is started) when the key is missing, mirroring
|
|
454
|
+
* `buildProviderEnvWithProxy`'s empty-env error contract — except here we throw
|
|
455
|
+
* rather than return `{}` because the CLI launch has no "fall back to process.env"
|
|
456
|
+
* path that would make sense for a Responses-API redirect.
|
|
457
|
+
*/
|
|
458
|
+
declare function buildCodexLaunchConfig(inputs: CodexLaunchConfigInputs): Promise<CodexLaunchConfig>;
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Process Supervisor module — singleton accessor and re-exports.
|
|
462
|
+
*
|
|
463
|
+
* @module index
|
|
464
|
+
*/
|
|
465
|
+
|
|
466
|
+
declare function getProcessSupervisor(): ProcessSupervisor;
|
|
467
|
+
|
|
468
|
+
export { CHAT_PROXY_BASE_PATH, CLAUDE_PROXY_API_KEY_SENTINEL, CODEX_PROXY_BASE_PATH, CODEX_PROXY_PROVIDER_NAME, type ChatCliBackendId, type ChatCliLaunchConfig, type ChatCliLaunchConfigInputs, type ChildAdapterHandle, type ClaudeCliLaunchConfigInputs, type CodexLaunchConfig, type CodexLaunchConfigInputs, type GeminiCliLaunchConfigInputs, type ManagedRun, type ManagedRunStdin, OPENCODE_PROXY_PROVIDER_ID, OPENCODE_PROXY_TOKEN_ENV, type ProcessSupervisor, type PtyAdapterHandle, type RunExit, type RunRecord, type RunRegistry, type RunState, type SpawnChildInput, type SpawnInput, type SpawnPtyInput, type TerminationReason, buildChatCliLaunchConfig, buildClaudeCliLaunchConfig, buildCodexConfigOverrides, buildCodexLaunchConfig, buildGeminiCliLaunchConfig, getProcessSupervisor };
|