@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.
@@ -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 };