@jacobbd/relay-ai 0.9.4 → 0.9.6

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/dist/cli.js CHANGED
@@ -12,7 +12,6 @@ import {
12
12
  GLOBAL_OPENCODE_KEYRING_ACCOUNT,
13
13
  MAX_MODEL_CATALOG,
14
14
  PREVIEW_PROXY_PORT,
15
- UNSUPPORTED_VOICE_MESSAGE,
16
15
  VERSION,
17
16
  VERTEX_ANTHROPIC_NPM,
18
17
  addCustomEndpointProvider,
@@ -85,6 +84,7 @@ import {
85
84
  getCodexProxyDebugLogPath,
86
85
  getConfigPath,
87
86
  getGeminiProxyDebugLogPath,
87
+ getLogsPath,
88
88
  getProvidersPath,
89
89
  getProxyDebugLogPath,
90
90
  getReasoningCapabilities,
@@ -169,8 +169,9 @@ import {
169
169
  resolveRelayCatalogSlots,
170
170
  routableModelsForTarget,
171
171
  routeLookupIds,
172
+ runCodexCommand,
173
+ runCodexCommandSync,
172
174
  runServerCommand,
173
- sanitizeUnsupportedInlineData,
174
175
  savePreferences,
175
176
  saveProviderCredential,
176
177
  saveRegistry,
@@ -186,20 +187,18 @@ import {
186
187
  startProxy,
187
188
  startProxyCatalog,
188
189
  startServer,
189
- summarizeSdkRequestForTrace,
190
190
  supportsClaudeTransparentMode,
191
191
  supportsMultiAgentV2,
192
192
  supportsNativeOAuth,
193
193
  syntheticTemplate,
194
194
  thinkingProviderOptions,
195
195
  toggleProviderEnabled,
196
- translateRequest,
197
196
  updateCustomEndpointProvider,
198
197
  upstreamHttpStatus,
199
198
  validateCustomEndpointUrl,
200
199
  writeSecureLogLine,
201
200
  zenRegistryStub
202
- } from "./chunk-KDIY732Q.js";
201
+ } from "./chunk-PYJQMEJD.js";
203
202
  import {
204
203
  filterTemplates,
205
204
  getTemplateById,
@@ -1297,7 +1296,7 @@ ${pc4.bold("Subcommands:")}
1297
1296
  (none) Provider hub wizard ${pc4.dim("[Phase 1.1]")}
1298
1297
  add Add a provider (Groq, Mistral, Together AI, \u2026) ${pc4.dim("[Phase 1.1]")}
1299
1298
  import Optional one-time import from OpenCode CLI ${pc4.dim("[Phase 1.0]")}
1300
- auth Sign in with OAuth (GitHub Copilot, xAI, OpenAI, ClinePass)
1299
+ auth Sign in with OAuth (Antigravity, GitHub Copilot, xAI, OpenAI, ClinePass)
1301
1300
  list Show configured providers ${pc4.dim("[Phase 1.0]")}
1302
1301
  remove Remove a provider by id ${pc4.dim("[Phase 1.1]")}
1303
1302
  refresh-models Update cached model lists ${pc4.dim("[Phase 1.2]")}`;
@@ -2235,8 +2234,7 @@ async function runProvidersCommand(args) {
2235
2234
  // src/codex.ts
2236
2235
  import pc7 from "picocolors";
2237
2236
  import * as p8 from "@clack/prompts";
2238
- import { execFileSync as execFileSync2 } from "child_process";
2239
- import { join as join5 } from "path";
2237
+ import { join as join6 } from "path";
2240
2238
 
2241
2239
  // src/codex-proxy.ts
2242
2240
  import { createHash as createHash2 } from "crypto";
@@ -3041,18 +3039,18 @@ async function writeResponsesStream(fullStream, modelId, write, onDone, onProgre
3041
3039
  });
3042
3040
  outputItems.unshift(reasoningItem);
3043
3041
  }
3044
- for (const tool3 of toolStates) {
3045
- const normalizedArgs = normalizeCodexSubagentArguments(tool3.name, tool3.args);
3042
+ for (const tool4 of toolStates) {
3043
+ const normalizedArgs = normalizeCodexSubagentArguments(tool4.name, tool4.args);
3046
3044
  emit("response.function_call_arguments.done", {
3047
3045
  type: "response.function_call_arguments.done",
3048
- item_id: tool3.itemId,
3049
- output_index: tool3.outputIndex,
3046
+ item_id: tool4.itemId,
3047
+ output_index: tool4.outputIndex,
3050
3048
  arguments: normalizedArgs
3051
3049
  });
3052
- const fcItem = buildFinalToolItem(resolveOutputKind(tool3.name, options?.toolContext), tool3.name, tool3.callId, tool3.itemId, normalizedArgs);
3050
+ const fcItem = buildFinalToolItem(resolveOutputKind(tool4.name, options?.toolContext), tool4.name, tool4.callId, tool4.itemId, normalizedArgs);
3053
3051
  emit("response.output_item.done", {
3054
3052
  type: "response.output_item.done",
3055
- output_index: tool3.outputIndex,
3053
+ output_index: tool4.outputIndex,
3056
3054
  item: fcItem
3057
3055
  });
3058
3056
  outputItems.push(fcItem);
@@ -3180,7 +3178,7 @@ function decodeCompactionContent(encrypted) {
3180
3178
  if (!encrypted) return null;
3181
3179
  try {
3182
3180
  const obj = JSON.parse(Buffer.from(encrypted, "base64").toString("utf8"));
3183
- return typeof obj?.summary === "string" ? obj.summary : null;
3181
+ return obj?.v === 1 && typeof obj.summary === "string" ? obj.summary : null;
3184
3182
  } catch {
3185
3183
  return null;
3186
3184
  }
@@ -3464,6 +3462,41 @@ function allowlistedNativeHeaders(inboundHeaders) {
3464
3462
  }
3465
3463
  return out;
3466
3464
  }
3465
+ function prepareNativeCodexBody(body) {
3466
+ if (!Array.isArray(body.input)) return body;
3467
+ let changed = false;
3468
+ const input = body.input.map((item) => {
3469
+ if (!item || typeof item !== "object" || Array.isArray(item)) return item;
3470
+ const record = item;
3471
+ if (record.type !== "compaction" && record.type !== "context_compaction") return item;
3472
+ const summary = decodeCompactionContent(
3473
+ typeof record.encrypted_content === "string" ? record.encrypted_content : void 0
3474
+ );
3475
+ if (summary === null) return item;
3476
+ changed = true;
3477
+ return {
3478
+ type: "message",
3479
+ role: "user",
3480
+ content: [{
3481
+ type: "input_text",
3482
+ text: `[Summary of earlier conversation]
3483
+ ${summary}`
3484
+ }]
3485
+ };
3486
+ });
3487
+ return changed ? { ...body, input } : body;
3488
+ }
3489
+ function prepareNativeHttpBody(body) {
3490
+ const text5 = typeof body === "string" ? body : Buffer.from(body).toString("utf8");
3491
+ try {
3492
+ const parsed = JSON.parse(text5);
3493
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return body;
3494
+ const prepared = prepareNativeCodexBody(parsed);
3495
+ return prepared === parsed ? body : JSON.stringify(prepared);
3496
+ } catch {
3497
+ return body;
3498
+ }
3499
+ }
3467
3500
  async function forwardNativeCodexHttp(options) {
3468
3501
  const fetchImpl = options.fetchImpl ?? fetch;
3469
3502
  const headers = allowlistedNativeHeaders(options.inboundHeaders);
@@ -3471,7 +3504,7 @@ async function forwardNativeCodexHttp(options) {
3471
3504
  return fetchImpl(options.nativeUrl ?? NATIVE_CODEX_RESPONSES_URL, {
3472
3505
  method: "POST",
3473
3506
  headers,
3474
- body: options.body,
3507
+ body: prepareNativeHttpBody(options.body),
3475
3508
  signal: options.signal,
3476
3509
  redirect: "manual"
3477
3510
  });
@@ -3549,17 +3582,17 @@ var COLLABORATION_TOOL_NAMES = /* @__PURE__ */ new Set([
3549
3582
  ]);
3550
3583
  function isCollaborationTool(value) {
3551
3584
  if (!value || typeof value !== "object") return false;
3552
- const tool3 = value;
3553
- const name = typeof tool3.name === "string" ? tool3.name : "";
3554
- if (tool3.type === "namespace") return name === "collaboration" || name === "multi_agent_v1";
3585
+ const tool4 = value;
3586
+ const name = typeof tool4.name === "string" ? tool4.name : "";
3587
+ if (tool4.type === "namespace") return name === "collaboration" || name === "multi_agent_v1";
3555
3588
  return COLLABORATION_TOOL_NAMES.has(name) || name.startsWith("collaboration__") || name.startsWith("multi_agent_v1__");
3556
3589
  }
3557
3590
  function stripCollaborationToolList(value) {
3558
3591
  if (!Array.isArray(value)) return value;
3559
- return value.filter((tool3) => !isCollaborationTool(tool3)).map((tool3) => {
3560
- if (!tool3 || typeof tool3 !== "object") return tool3;
3561
- const record = tool3;
3562
- if (!Array.isArray(record.tools)) return tool3;
3592
+ return value.filter((tool4) => !isCollaborationTool(tool4)).map((tool4) => {
3593
+ if (!tool4 || typeof tool4 !== "object") return tool4;
3594
+ const record = tool4;
3595
+ if (!Array.isArray(record.tools)) return tool4;
3563
3596
  return { ...record, tools: stripCollaborationToolList(record.tools) };
3564
3597
  });
3565
3598
  }
@@ -3738,6 +3771,53 @@ async function resolveRoutedCollaborationInput(input, context) {
3738
3771
  return normalizePlaintextCollaborationForExternal(out);
3739
3772
  }
3740
3773
 
3774
+ // src/codex/route-audit.ts
3775
+ import { chmodSync, mkdirSync, writeFileSync } from "fs";
3776
+ import { join as join2 } from "path";
3777
+ var DIR_MODE = 448;
3778
+ var FILE_MODE = 384;
3779
+ var CODEX_ROUTE_AUDIT_LOG = "codex-route-audit.jsonl";
3780
+ function safeIdentifier(value) {
3781
+ if (value === void 0) return void 0;
3782
+ return value.replace(/[\u0000-\u001f\u007f]/g, "_").slice(0, 300);
3783
+ }
3784
+ function sanitizeCodexRouteAuditEvent(event) {
3785
+ return {
3786
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
3787
+ transport: event.transport,
3788
+ requestedModel: safeIdentifier(event.requestedModel),
3789
+ dispatch: event.dispatch,
3790
+ phase: event.phase,
3791
+ ...event.provider ? { provider: safeIdentifier(event.provider) } : {},
3792
+ ...event.routeModel ? { routeModel: safeIdentifier(event.routeModel) } : {},
3793
+ ...event.upstreamModel ? { upstreamModel: safeIdentifier(event.upstreamModel) } : {},
3794
+ ...event.outcome ? { outcome: event.outcome } : {},
3795
+ ...event.status !== void 0 ? { status: typeof event.status === "string" ? safeIdentifier(event.status) : event.status } : {}
3796
+ };
3797
+ }
3798
+ function getCodexRouteAuditLogPath() {
3799
+ const dir = getLogsPath();
3800
+ mkdirSync(dir, { recursive: true, mode: DIR_MODE });
3801
+ try {
3802
+ chmodSync(dir, DIR_MODE);
3803
+ } catch {
3804
+ }
3805
+ return join2(dir, CODEX_ROUTE_AUDIT_LOG);
3806
+ }
3807
+ function prepareCodexRouteAuditLog(path3 = getCodexRouteAuditLogPath()) {
3808
+ writeFileSync(path3, "", { mode: FILE_MODE });
3809
+ chmodSync(path3, FILE_MODE);
3810
+ return path3;
3811
+ }
3812
+ function appendCodexRouteAudit(path3, event) {
3813
+ try {
3814
+ writeFileSync(path3, `${JSON.stringify(sanitizeCodexRouteAuditEvent(event))}
3815
+ `, { flag: "a", mode: FILE_MODE });
3816
+ chmodSync(path3, FILE_MODE);
3817
+ } catch {
3818
+ }
3819
+ }
3820
+
3741
3821
  // src/codex-proxy.ts
3742
3822
  function captureCompletedResponse(sseText) {
3743
3823
  if (!sseText.includes("response.completed")) return void 0;
@@ -3745,11 +3825,34 @@ function captureCompletedResponse(sseText) {
3745
3825
  if (!dataLine) return void 0;
3746
3826
  try {
3747
3827
  const obj = JSON.parse(dataLine.slice(5).trim());
3748
- if (obj && obj.type === "response.completed") return obj.response;
3828
+ if (obj && obj.type === "response.completed" && obj.response && typeof obj.response === "object") {
3829
+ return obj.response;
3830
+ }
3749
3831
  } catch {
3750
3832
  }
3751
3833
  return void 0;
3752
3834
  }
3835
+ var MAX_EXTERNAL_RESPONSE_STATES = 8;
3836
+ var EXTERNAL_TOOL_OUTPUT_TYPES = /* @__PURE__ */ new Set([
3837
+ "function_call_output",
3838
+ "custom_tool_call_output",
3839
+ "tool_search_output"
3840
+ ]);
3841
+ function responsesInputItems(input) {
3842
+ if (Array.isArray(input)) return input;
3843
+ if (typeof input === "string") {
3844
+ return [{ type: "message", role: "user", content: input }];
3845
+ }
3846
+ return [];
3847
+ }
3848
+ function isExternalToolOutputItem(item) {
3849
+ if (!item || typeof item !== "object" || Array.isArray(item)) return false;
3850
+ const type = item.type;
3851
+ return typeof type === "string" && EXTERNAL_TOOL_OUTPUT_TYPES.has(type);
3852
+ }
3853
+ function isExternalToolContinuation(input) {
3854
+ return Array.isArray(input) && input.length > 0 && input.every(isExternalToolOutputItem);
3855
+ }
3753
3856
  function estimateCodexRequestChars(params) {
3754
3857
  let chars = (params.system ?? "").length;
3755
3858
  for (const msg of params.messages) {
@@ -3881,6 +3984,18 @@ function codexRouteLookupIds(requestedModel) {
3881
3984
  return [...new Set(ids)];
3882
3985
  }
3883
3986
  function findCodexProxyRoute(routes, requestedModel) {
3987
+ const bareRequestedModel = parseCodexAppModelSlug(requestedModel);
3988
+ const providerSeparator = bareRequestedModel.indexOf("__");
3989
+ if (providerSeparator > 0) {
3990
+ const requestedProvider = bareRequestedModel.slice(0, providerSeparator);
3991
+ const requestedIds = codexRouteLookupIds(bareRequestedModel.slice(providerSeparator + 2));
3992
+ const providerRoute = routes.find((route) => {
3993
+ if (route.providerId !== requestedProvider) return false;
3994
+ const routeIds = codexRouteLookupIds(route.modelId);
3995
+ return requestedIds.some((id) => routeIds.includes(id));
3996
+ });
3997
+ if (providerRoute) return providerRoute;
3998
+ }
3884
3999
  const ids = codexRouteLookupIds(requestedModel);
3885
4000
  for (const id of ids) {
3886
4001
  const route = routes.find(
@@ -3936,11 +4051,32 @@ async function prepareExternalCodexBody(body, context) {
3936
4051
  );
3937
4052
  return { ...externalBody, input: resolvedInput };
3938
4053
  }
4054
+ function applyExternalCodexRuntimeIdentity(params, route) {
4055
+ const selectedModel = route.auditUpstreamModelId ?? route.upstreamModelId ?? route.modelId;
4056
+ const provider = route.providerId ?? "relay";
4057
+ const identity = [
4058
+ "<external-model-identity>",
4059
+ `The selected model for this turn is ${JSON.stringify(selectedModel)} through provider ${JSON.stringify(provider)}.`,
4060
+ "Codex is the host application and agent environment, not the model identity.",
4061
+ "Follow Codex host and tool instructions normally, but do not infer that you are an OpenAI or GPT model from host names, tool names, documentation, or conversation context.",
4062
+ "If asked what model you are, report the selected model and provider above; do not use self-identification as evidence of the network route.",
4063
+ "</external-model-identity>"
4064
+ ].join("\n");
4065
+ return {
4066
+ ...params,
4067
+ system: params.system?.trim() ? `${identity}
4068
+
4069
+ ${params.system}` : identity
4070
+ };
4071
+ }
3939
4072
  async function startCodexProxy(routes, options = {}) {
3940
4073
  const opts = typeof options === "boolean" ? { debug: options } : options;
3941
4074
  const debug = opts.debug ?? false;
3942
4075
  const requireAuth = opts.requireAuth ?? true;
3943
4076
  const mixedNative = opts.mixedNative;
4077
+ const audit = (event) => {
4078
+ if (opts.routeAuditPath) appendCodexRouteAudit(opts.routeAuditPath, event);
4079
+ };
3944
4080
  const nativePayloadRelay = mixedNative ? createNativePayloadRelay({}) : void 0;
3945
4081
  silenceSdkWarnings();
3946
4082
  const models = /* @__PURE__ */ new Map();
@@ -4114,6 +4250,7 @@ async function startCodexProxy(routes, options = {}) {
4114
4250
  log14(`subagent dispatch: requested=${modelId} route=${subagentRoute?.modelId ?? "(none)"}`);
4115
4251
  }
4116
4252
  if (mixedNative && markedSubagent && !subagentRoute) {
4253
+ audit({ transport: "http", requestedModel: modelId, dispatch: "relay-subagent", phase: "complete", outcome: "error", status: 503 });
4117
4254
  sendJson(res, 503, {
4118
4255
  error: {
4119
4256
  message: "Codex marked this request as a Sub-agent, but no configured Codex Sub-agent route is available.",
@@ -4126,10 +4263,20 @@ async function startCodexProxy(routes, options = {}) {
4126
4263
  if (!markedSubagent) {
4127
4264
  const dispatch = classifyCodexDispatch(modelId, routes, mixedNative.nativeModelIds);
4128
4265
  if (dispatch.kind === "unknown") {
4266
+ audit({ transport: "http", requestedModel: modelId, dispatch: "unknown", phase: "complete", outcome: "error", status: 404 });
4129
4267
  sendJson(res, 404, { error: { message: `Unknown model: ${modelId}`, type: "invalid_request_error" } });
4130
4268
  return;
4131
4269
  }
4132
4270
  if (dispatch.kind === "native") {
4271
+ audit({
4272
+ transport: "http",
4273
+ requestedModel: modelId,
4274
+ dispatch: "native",
4275
+ phase: "dispatch",
4276
+ provider: "openai-native",
4277
+ routeModel: modelId,
4278
+ upstreamModel: modelId
4279
+ });
4133
4280
  const controller = new AbortController();
4134
4281
  req.once("aborted", () => controller.abort());
4135
4282
  try {
@@ -4143,7 +4290,29 @@ async function startCodexProxy(routes, options = {}) {
4143
4290
  const contentType = nativeResponse.headers.get("content-type");
4144
4291
  res.writeHead(nativeResponse.status, contentType ? { "content-type": contentType } : void 0);
4145
4292
  res.end(Buffer.from(await nativeResponse.arrayBuffer()));
4293
+ audit({
4294
+ transport: "http",
4295
+ requestedModel: modelId,
4296
+ dispatch: "native",
4297
+ phase: "complete",
4298
+ provider: "openai-native",
4299
+ routeModel: modelId,
4300
+ upstreamModel: modelId,
4301
+ outcome: nativeResponse.ok ? "ok" : "error",
4302
+ status: nativeResponse.status
4303
+ });
4146
4304
  } catch (err) {
4305
+ audit({
4306
+ transport: "http",
4307
+ requestedModel: modelId,
4308
+ dispatch: "native",
4309
+ phase: "complete",
4310
+ provider: "openai-native",
4311
+ routeModel: modelId,
4312
+ upstreamModel: modelId,
4313
+ outcome: "error",
4314
+ status: "forward-failed"
4315
+ });
4147
4316
  if (!res.writableEnded) sendJson(res, 502, { error: { message: "Native Codex request failed", type: "upstream_error" } });
4148
4317
  }
4149
4318
  return;
@@ -4168,13 +4337,23 @@ async function startCodexProxy(routes, options = {}) {
4168
4337
  }
4169
4338
  }
4170
4339
  const { route, languageModel } = resolved;
4340
+ const relayDispatch = markedSubagent ? "relay-subagent" : "relay";
4341
+ audit({
4342
+ transport: "http",
4343
+ requestedModel: modelId,
4344
+ dispatch: relayDispatch,
4345
+ phase: "dispatch",
4346
+ provider: route.providerId ?? "relay",
4347
+ routeModel: route.modelId,
4348
+ upstreamModel: route.auditUpstreamModelId ?? route.upstreamModelId
4349
+ });
4171
4350
  try {
4172
4351
  const routedBody = await prepareExternalCodexBody(body, {
4173
4352
  relay: nativePayloadRelay,
4174
4353
  mixedNative,
4175
4354
  headers: req.headers
4176
4355
  });
4177
- let params = applyClaudeCodeOAuthIdentity(route, translateResponsesRequest(
4356
+ let params = applyClaudeCodeOAuthIdentity(route, applyExternalCodexRuntimeIdentity(translateResponsesRequest(
4178
4357
  routedBody,
4179
4358
  route.npm,
4180
4359
  {
@@ -4186,7 +4365,7 @@ async function startCodexProxy(routes, options = {}) {
4186
4365
  upstreamModelId: route.upstreamModelId
4187
4366
  },
4188
4367
  { maxTools: maxToolsForNpm(route.npm) }
4189
- ));
4368
+ ), route));
4190
4369
  if (route.contextWindow && route.contextWindow > 0) {
4191
4370
  const before = params.messages.length;
4192
4371
  const estimatedChars = estimateCodexRequestChars(params);
@@ -4241,9 +4420,31 @@ async function startCodexProxy(routes, options = {}) {
4241
4420
  log14(`response progress: model=${route.modelId} elapsedMs=${progress.elapsedMs} reasoningChars=${progress.reasoningChars} textChars=${progress.textChars} toolCalls=${progress.toolCallCount} reasoningTail=${JSON.stringify(progress.reasoningTail)}`);
4242
4421
  }
4243
4422
  });
4423
+ audit({
4424
+ transport: "http",
4425
+ requestedModel: modelId,
4426
+ dispatch: relayDispatch,
4427
+ phase: "complete",
4428
+ provider: route.providerId ?? "relay",
4429
+ routeModel: route.modelId,
4430
+ upstreamModel: route.auditUpstreamModelId ?? route.upstreamModelId,
4431
+ outcome: "ok",
4432
+ status: 200
4433
+ });
4244
4434
  } catch (err) {
4245
4435
  const msg = formatUpstreamError(err);
4246
4436
  const status = upstreamHttpStatus(err, msg);
4437
+ audit({
4438
+ transport: "http",
4439
+ requestedModel: modelId,
4440
+ dispatch: relayDispatch,
4441
+ phase: "complete",
4442
+ provider: route.providerId ?? "relay",
4443
+ routeModel: route.modelId,
4444
+ upstreamModel: route.auditUpstreamModelId ?? route.upstreamModelId,
4445
+ outcome: "error",
4446
+ status
4447
+ });
4247
4448
  if (debug) log14(`sdk error: ${route.modelId}: ${msg}`);
4248
4449
  if (status === 429) {
4249
4450
  writeResponsesRateLimitStream(modelId, msg, write);
@@ -4265,9 +4466,31 @@ async function startCodexProxy(routes, options = {}) {
4265
4466
  });
4266
4467
  }
4267
4468
  sendJson(res, 200, response);
4469
+ audit({
4470
+ transport: "http",
4471
+ requestedModel: modelId,
4472
+ dispatch: relayDispatch,
4473
+ phase: "complete",
4474
+ provider: route.providerId ?? "relay",
4475
+ routeModel: route.modelId,
4476
+ upstreamModel: route.auditUpstreamModelId ?? route.upstreamModelId,
4477
+ outcome: "ok",
4478
+ status: 200
4479
+ });
4268
4480
  } catch (err) {
4269
4481
  const msg = formatUpstreamError(err);
4270
4482
  const status = upstreamHttpStatus(err, msg);
4483
+ audit({
4484
+ transport: "http",
4485
+ requestedModel: modelId,
4486
+ dispatch: relayDispatch,
4487
+ phase: "complete",
4488
+ provider: route.providerId ?? "relay",
4489
+ routeModel: route.modelId,
4490
+ upstreamModel: route.auditUpstreamModelId ?? route.upstreamModelId,
4491
+ outcome: "error",
4492
+ status
4493
+ });
4271
4494
  if (debug) log14(`sdk error: ${route.modelId}: ${msg}`);
4272
4495
  if (status === 429) {
4273
4496
  sendJson(res, 200, responsesRateLimitBody(modelId, msg));
@@ -4392,21 +4615,53 @@ Sec-WebSocket-Accept: ${wsAcceptKey(clientKey)}\r
4392
4615
  `
4393
4616
  );
4394
4617
  let frameBuf = Buffer.alloc(0);
4395
- let handled = false;
4618
+ let externalActive = false;
4396
4619
  let nativeActive = false;
4397
4620
  let nativeUpstream;
4621
+ let nativeSendTurn;
4622
+ let socketClosing = false;
4623
+ const externalResponseStates = /* @__PURE__ */ new Map();
4624
+ let currentExternalCompletedResponse;
4625
+ let currentExternalStateInput;
4626
+ let currentExternalConsumedResponseId;
4398
4627
  let currentRequestModel = "";
4399
- const closeSocket = (code = 1e3) => {
4400
- if (!socket.destroyed) {
4401
- socket.write(wsCloseFrame(code));
4402
- socket.end();
4628
+ const rememberExternalResponse = (response, input) => {
4629
+ const responseId = typeof response.id === "string" ? response.id : void 0;
4630
+ const output = Array.isArray(response.output) ? response.output : void 0;
4631
+ if (!responseId || !output || response.error) return;
4632
+ externalResponseStates.delete(responseId);
4633
+ externalResponseStates.set(responseId, { input: [...input], output: [...output] });
4634
+ while (externalResponseStates.size > MAX_EXTERNAL_RESPONSE_STATES) {
4635
+ const oldest = externalResponseStates.keys().next().value;
4636
+ if (!oldest) break;
4637
+ externalResponseStates.delete(oldest);
4403
4638
  }
4404
4639
  };
4640
+ const resolveExternalContinuation = (body) => {
4641
+ const previousResponseId = typeof body.previous_response_id === "string" ? body.previous_response_id : void 0;
4642
+ if (!previousResponseId || !isExternalToolContinuation(body.input)) return { body };
4643
+ const previous = externalResponseStates.get(previousResponseId);
4644
+ if (!previous) return { body, orphanedResponseId: previousResponseId };
4645
+ return {
4646
+ body: {
4647
+ ...body,
4648
+ input: [...previous.input, ...previous.output, ...body.input]
4649
+ },
4650
+ consumedResponseId: previousResponseId
4651
+ };
4652
+ };
4653
+ const closeSocket = (code = 1e3) => {
4654
+ if (socketClosing || socket.destroyed) return;
4655
+ socketClosing = true;
4656
+ socket.write(wsCloseFrame(code));
4657
+ socket.end();
4658
+ };
4405
4659
  const sendWsEvent = (sseChunk2) => {
4406
- if (socket.destroyed) return;
4407
- if (debug) {
4408
- const completed = captureCompletedResponse(sseChunk2);
4409
- if (completed) {
4660
+ if (socketClosing || socket.destroyed) return;
4661
+ const completed = captureCompletedResponse(sseChunk2);
4662
+ if (completed) {
4663
+ currentExternalCompletedResponse = completed;
4664
+ if (debug) {
4410
4665
  appendCodexBodyDump({
4411
4666
  ts: (/* @__PURE__ */ new Date()).toISOString(),
4412
4667
  transport: "ws",
@@ -4424,7 +4679,6 @@ Sec-WebSocket-Accept: ${wsAcceptKey(clientKey)}\r
4424
4679
  };
4425
4680
  const onData = (chunk) => {
4426
4681
  frameBuf = Buffer.concat([frameBuf, chunk]);
4427
- if (handled && !nativeActive) return;
4428
4682
  const frame = wsDecodeFrame(frameBuf);
4429
4683
  if (!frame) return;
4430
4684
  frameBuf = Buffer.alloc(0);
@@ -4446,7 +4700,10 @@ Sec-WebSocket-Accept: ${wsAcceptKey(clientKey)}\r
4446
4700
  socket.end();
4447
4701
  return;
4448
4702
  }
4449
- handled = true;
4703
+ if (externalActive) {
4704
+ closeSocket(1008);
4705
+ return;
4706
+ }
4450
4707
  void (async () => {
4451
4708
  let body;
4452
4709
  try {
@@ -4487,6 +4744,7 @@ data: ${JSON.stringify({ error: { message: "Invalid JSON", type: "invalid_reques
4487
4744
  log14(`WS subagent dispatch: requested=${modelId} route=${subagentRoute?.modelId ?? "(none)"}`);
4488
4745
  }
4489
4746
  if (mixedNative && markedSubagent && !subagentRoute) {
4747
+ audit({ transport: "ws", requestedModel: modelId, dispatch: "relay-subagent", phase: "complete", outcome: "error", status: 503 });
4490
4748
  sendWsEvent(`event: error
4491
4749
  data: ${JSON.stringify({ error: {
4492
4750
  message: "Codex marked this request as a Sub-agent, but no configured Codex Sub-agent route is available.",
@@ -4501,6 +4759,7 @@ data: ${JSON.stringify({ error: {
4501
4759
  if (!markedSubagent) {
4502
4760
  const dispatch = classifyCodexDispatch(modelId, routes, mixedNative.nativeModelIds);
4503
4761
  if (dispatch.kind === "unknown") {
4762
+ audit({ transport: "ws", requestedModel: modelId, dispatch: "unknown", phase: "complete", outcome: "error", status: 404 });
4504
4763
  sendWsEvent(`event: error
4505
4764
  data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}`, type: "invalid_request_error" } })}
4506
4765
 
@@ -4509,10 +4768,23 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}`, type: "i
4509
4768
  return;
4510
4769
  }
4511
4770
  if (dispatch.kind === "native") {
4771
+ audit({
4772
+ transport: "ws",
4773
+ requestedModel: modelId,
4774
+ dispatch: "native",
4775
+ phase: "dispatch",
4776
+ provider: "openai-native",
4777
+ routeModel: modelId,
4778
+ upstreamModel: modelId
4779
+ });
4780
+ const nativeBody = prepareNativeCodexBody(body);
4781
+ if (debug && nativeBody !== body) {
4782
+ log14(`WS native history normalized: model=${modelId} converted Relay compaction for native verification`);
4783
+ }
4512
4784
  if (nativeActive && nativeUpstream) {
4513
4785
  if (nativeUpstream.readyState === WebSocket.OPEN) {
4514
4786
  if (debug) log14(`WS native forwarding next turn: model=${modelId}`);
4515
- nativeUpstream.send(JSON.stringify({ type: "response.create", ...body }));
4787
+ nativeSendTurn?.(nativeBody, modelId);
4516
4788
  } else if (debug) {
4517
4789
  log14(`WS native cannot forward next turn: upstream_state=${nativeUpstream.readyState}`);
4518
4790
  }
@@ -4523,6 +4795,7 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}`, type: "i
4523
4795
  let upstream;
4524
4796
  let nativeOpened = false;
4525
4797
  let nativeCompleted = false;
4798
+ let nativeTurnModelId = modelId;
4526
4799
  let nativeFrameCount = 0;
4527
4800
  let finished = false;
4528
4801
  let connectTimer;
@@ -4542,10 +4815,24 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}`, type: "i
4542
4815
  if (finished) return;
4543
4816
  finished = true;
4544
4817
  nativeActive = false;
4818
+ nativeSendTurn = void 0;
4545
4819
  if (nativeUpstream === upstream) nativeUpstream = void 0;
4546
4820
  clearTimers();
4547
4821
  if (debug && message) {
4548
- log14(`WS native upstream failed: model=${modelId} opened=${nativeOpened} frames=${nativeFrameCount} message=${message}`);
4822
+ log14(`WS native upstream failed: model=${nativeTurnModelId} opened=${nativeOpened} frames=${nativeFrameCount} message=${message}`);
4823
+ }
4824
+ if (message && !nativeCompleted) {
4825
+ audit({
4826
+ transport: "ws",
4827
+ requestedModel: nativeTurnModelId,
4828
+ dispatch: "native",
4829
+ phase: "complete",
4830
+ provider: "openai-native",
4831
+ routeModel: nativeTurnModelId,
4832
+ upstreamModel: nativeTurnModelId,
4833
+ outcome: "error",
4834
+ status: "upstream-failed"
4835
+ });
4549
4836
  }
4550
4837
  if (message && !nativeCompleted) sendNativeError(message);
4551
4838
  try {
@@ -4554,20 +4841,31 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}`, type: "i
4554
4841
  }
4555
4842
  closeSocket(closeCode);
4556
4843
  };
4844
+ const sendNativeTurn = (turnBody, turnModelId) => {
4845
+ if (!upstream || upstream.readyState !== WebSocket.OPEN) {
4846
+ if (debug) log14(`WS native cannot send turn: model=${turnModelId} upstream_state=${upstream?.readyState ?? "missing"}`);
4847
+ return;
4848
+ }
4849
+ nativeTurnModelId = turnModelId;
4850
+ nativeCompleted = false;
4851
+ if (firstFrameTimer) clearTimeout(firstFrameTimer);
4852
+ upstream.send(JSON.stringify({ type: "response.create", ...turnBody }));
4853
+ firstFrameTimer = setTimeout(() => closeBoth("Native Codex WebSocket response timed out"), 6e4);
4854
+ };
4557
4855
  try {
4558
4856
  if (debug) {
4559
4857
  log14(`WS native connecting: model=${modelId} url=${target.url} headers=[${Object.keys(target.headers).join(",")}]`);
4560
4858
  }
4561
4859
  upstream = new WebSocket(target.url, { headers: target.headers });
4562
4860
  nativeUpstream = upstream;
4861
+ nativeSendTurn = sendNativeTurn;
4563
4862
  nativeActive = true;
4564
4863
  connectTimer = setTimeout(() => closeBoth("Native Codex WebSocket connection timed out"), 15e3);
4565
4864
  upstream.once("open", () => {
4566
4865
  nativeOpened = true;
4567
4866
  if (connectTimer) clearTimeout(connectTimer);
4568
4867
  if (debug) log14(`WS native upstream open: model=${modelId}`);
4569
- upstream?.send(JSON.stringify({ type: "response.create", ...body }));
4570
- firstFrameTimer = setTimeout(() => closeBoth("Native Codex WebSocket response timed out"), 6e4);
4868
+ sendNativeTurn(nativeBody, modelId);
4571
4869
  });
4572
4870
  upstream.once("unexpected-response", (_request, response) => {
4573
4871
  if (debug) log14(`WS native upstream HTTP rejection: model=${modelId} status=${response.statusCode}`);
@@ -4585,6 +4883,17 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}`, type: "i
4585
4883
  if (typeof parsed.type === "string") eventType = parsed.type;
4586
4884
  if (eventType === "response.completed" || eventType === "response.failed" || eventType === "response.incomplete") {
4587
4885
  nativeCompleted = true;
4886
+ audit({
4887
+ transport: "ws",
4888
+ requestedModel: modelId,
4889
+ dispatch: "native",
4890
+ phase: "complete",
4891
+ provider: "openai-native",
4892
+ routeModel: modelId,
4893
+ upstreamModel: modelId,
4894
+ outcome: eventType === "response.completed" ? "ok" : "error",
4895
+ status: eventType
4896
+ });
4588
4897
  }
4589
4898
  } catch {
4590
4899
  }
@@ -4605,6 +4914,7 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}`, type: "i
4605
4914
  if (debug) log14(`WS native downstream close: model=${modelId} frames=${nativeFrameCount} completed=${nativeCompleted}`);
4606
4915
  finished = true;
4607
4916
  nativeActive = false;
4917
+ nativeSendTurn = void 0;
4608
4918
  if (nativeUpstream === upstream) nativeUpstream = void 0;
4609
4919
  clearTimers();
4610
4920
  try {
@@ -4619,6 +4929,7 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}`, type: "i
4619
4929
  }
4620
4930
  }
4621
4931
  }
4932
+ externalActive = true;
4622
4933
  let resolved = subagentRoute ? resolveModel(routes, models, subagentRoute.modelId) : resolveModel(routes, models, modelId);
4623
4934
  if (!resolved) {
4624
4935
  const fb = routes[0];
@@ -4637,13 +4948,35 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}` } })}
4637
4948
  }
4638
4949
  }
4639
4950
  const { route, languageModel } = resolved;
4951
+ const relayDispatch = markedSubagent ? "relay-subagent" : "relay";
4952
+ audit({
4953
+ transport: "ws",
4954
+ requestedModel: modelId,
4955
+ dispatch: relayDispatch,
4956
+ phase: "dispatch",
4957
+ provider: route.providerId ?? "relay",
4958
+ routeModel: route.modelId,
4959
+ upstreamModel: route.auditUpstreamModelId ?? route.upstreamModelId
4960
+ });
4961
+ currentExternalCompletedResponse = void 0;
4962
+ currentExternalStateInput = void 0;
4963
+ currentExternalConsumedResponseId = void 0;
4964
+ const continuation = resolveExternalContinuation(body);
4965
+ if (continuation.orphanedResponseId) {
4966
+ if (debug) log14(`WS continuation rejected: unknown previous_response_id=${continuation.orphanedResponseId}`);
4967
+ writeResponsesErrorStream(modelId, "Unknown or expired previous_response_id", sendWsEvent, 400);
4968
+ externalActive = false;
4969
+ return;
4970
+ }
4640
4971
  try {
4641
- const routedBody = await prepareExternalCodexBody(body, {
4972
+ const routedBody = await prepareExternalCodexBody(continuation.body, {
4642
4973
  relay: nativePayloadRelay,
4643
4974
  mixedNative,
4644
4975
  headers: req.headers
4645
4976
  });
4646
- let params = applyClaudeCodeOAuthIdentity(route, translateResponsesRequest(
4977
+ currentExternalStateInput = responsesInputItems(routedBody.input);
4978
+ currentExternalConsumedResponseId = continuation.consumedResponseId;
4979
+ let params = applyClaudeCodeOAuthIdentity(route, applyExternalCodexRuntimeIdentity(translateResponsesRequest(
4647
4980
  routedBody,
4648
4981
  route.npm,
4649
4982
  {
@@ -4655,7 +4988,7 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}` } })}
4655
4988
  upstreamModelId: route.upstreamModelId
4656
4989
  },
4657
4990
  { maxTools: maxToolsForNpm(route.npm) }
4658
- ));
4991
+ ), route));
4659
4992
  if (route.contextWindow && route.contextWindow > 0) {
4660
4993
  const before = params.messages.length;
4661
4994
  const estimatedChars = estimateCodexRequestChars(params);
@@ -4688,9 +5021,37 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}` } })}
4688
5021
  log14(`WS response progress: model=${route.modelId} elapsedMs=${progress.elapsedMs} reasoningChars=${progress.reasoningChars} textChars=${progress.textChars} toolCalls=${progress.toolCallCount} reasoningTail=${JSON.stringify(progress.reasoningTail)}`);
4689
5022
  }
4690
5023
  });
5024
+ if (currentExternalCompletedResponse && currentExternalStateInput) {
5025
+ if (currentExternalConsumedResponseId) {
5026
+ externalResponseStates.delete(currentExternalConsumedResponseId);
5027
+ }
5028
+ rememberExternalResponse(currentExternalCompletedResponse, currentExternalStateInput);
5029
+ }
5030
+ audit({
5031
+ transport: "ws",
5032
+ requestedModel: modelId,
5033
+ dispatch: relayDispatch,
5034
+ phase: "complete",
5035
+ provider: route.providerId ?? "relay",
5036
+ routeModel: route.modelId,
5037
+ upstreamModel: route.auditUpstreamModelId ?? route.upstreamModelId,
5038
+ outcome: "ok",
5039
+ status: "response.completed"
5040
+ });
4691
5041
  } catch (err) {
4692
5042
  const msg = formatUpstreamError(err);
4693
5043
  const status = upstreamHttpStatus(err, msg);
5044
+ audit({
5045
+ transport: "ws",
5046
+ requestedModel: modelId,
5047
+ dispatch: relayDispatch,
5048
+ phase: "complete",
5049
+ provider: route.providerId ?? "relay",
5050
+ routeModel: route.modelId,
5051
+ upstreamModel: route.auditUpstreamModelId ?? route.upstreamModelId,
5052
+ outcome: "error",
5053
+ status
5054
+ });
4694
5055
  if (debug) log14(`WS sdk error: ${route.modelId}: ${msg}`);
4695
5056
  if (status === 429) {
4696
5057
  writeResponsesRateLimitStream(modelId, msg, sendWsEvent);
@@ -4698,10 +5059,11 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}` } })}
4698
5059
  writeResponsesErrorStream(modelId, msg, sendWsEvent, status);
4699
5060
  }
4700
5061
  }
4701
- closeSocket();
5062
+ externalActive = false;
4702
5063
  })();
4703
5064
  };
4704
5065
  socket.on("error", () => socket.destroy());
5066
+ socket.once("close", () => externalResponseStates.clear());
4705
5067
  socket.on("data", onData);
4706
5068
  onData(head);
4707
5069
  });
@@ -4726,44 +5088,44 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}` } })}
4726
5088
  }
4727
5089
 
4728
5090
  // src/codex/profile.ts
4729
- import { join as join3 } from "path";
5091
+ import { join as join4 } from "path";
4730
5092
 
4731
5093
  // src/codex/session.ts
4732
5094
  import {
4733
5095
  copyFileSync,
4734
- chmodSync,
5096
+ chmodSync as chmodSync2,
4735
5097
  existsSync as existsSync3,
4736
- mkdirSync,
5098
+ mkdirSync as mkdirSync2,
4737
5099
  readdirSync,
4738
5100
  readFileSync as readFileSync2,
4739
5101
  renameSync,
4740
5102
  rmSync,
4741
5103
  statSync,
4742
5104
  unlinkSync,
4743
- writeFileSync
5105
+ writeFileSync as writeFileSync2
4744
5106
  } from "fs";
4745
5107
  import { homedir as homedir3 } from "os";
4746
- import { basename, dirname, join as join2 } from "path";
5108
+ import { basename, dirname, join as join3 } from "path";
4747
5109
  var CODEX_PROFILE_NAME = "relay-ai-launch";
4748
5110
  var STALE_SESSION_MS = 5 * 60 * 1e3;
4749
5111
  var MAX_BACKUPS = 5;
4750
5112
  function getCodexHome(env = process.env) {
4751
- return env["CODEX_HOME"] || join2(homedir3(), ".codex");
5113
+ return env["CODEX_HOME"] || join3(homedir3(), ".codex");
4752
5114
  }
4753
5115
  function getCodexProfilePath() {
4754
- return join2(getCodexHome(), `${CODEX_PROFILE_NAME}.config.toml`);
5116
+ return join3(getCodexHome(), `${CODEX_PROFILE_NAME}.config.toml`);
4755
5117
  }
4756
5118
  function getRelayAiCodexDir(env = process.env) {
4757
- return join2(getAppHome(env), "codex");
5119
+ return join3(getAppHome(env), "codex");
4758
5120
  }
4759
5121
  function getSessionLockPath(env = process.env) {
4760
- return join2(getRelayAiCodexDir(env), "session.json");
5122
+ return join3(getRelayAiCodexDir(env), "session.json");
4761
5123
  }
4762
5124
  function getBackupsDir(env = process.env) {
4763
- return join2(getRelayAiCodexDir(env), "backups");
5125
+ return join3(getRelayAiCodexDir(env), "backups");
4764
5126
  }
4765
5127
  function getCatalogPath(providerId, env = process.env) {
4766
- return join2(getRelayAiCodexDir(env), `models-${providerId}.json`);
5128
+ return join3(getRelayAiCodexDir(env), `models-${providerId}.json`);
4767
5129
  }
4768
5130
  function ownedOverlayPaths(env = process.env) {
4769
5131
  const paths = [getCodexProfilePath()];
@@ -4771,15 +5133,15 @@ function ownedOverlayPaths(env = process.env) {
4771
5133
  if (existsSync3(codexDir)) {
4772
5134
  for (const name of readdirSync(codexDir)) {
4773
5135
  if (name.startsWith("models-") && name.endsWith(".json")) {
4774
- paths.push(join2(codexDir, name));
5136
+ paths.push(join3(codexDir, name));
4775
5137
  }
4776
5138
  }
4777
5139
  }
4778
- const agentsDir = join2(getCodexHome(env), "agents");
5140
+ const agentsDir = join3(getCodexHome(env), "agents");
4779
5141
  if (existsSync3(agentsDir)) {
4780
5142
  for (const name of readdirSync(agentsDir)) {
4781
5143
  if (/^relay-model-[a-z0-9-]+\.toml$/i.test(name)) {
4782
- paths.push(join2(agentsDir, name));
5144
+ paths.push(join3(agentsDir, name));
4783
5145
  }
4784
5146
  }
4785
5147
  }
@@ -4787,27 +5149,27 @@ function ownedOverlayPaths(env = process.env) {
4787
5149
  return paths;
4788
5150
  }
4789
5151
  function atomicWriteFile(path3, content) {
4790
- mkdirSync(dirname(path3), { recursive: true });
5152
+ mkdirSync2(dirname(path3), { recursive: true });
4791
5153
  const tmp = `${path3}.tmp.${process.pid}`;
4792
- writeFileSync(tmp, content, { encoding: "utf8", mode: 384 });
5154
+ writeFileSync2(tmp, content, { encoding: "utf8", mode: 384 });
4793
5155
  renameSync(tmp, path3);
4794
5156
  try {
4795
- chmodSync(path3, 384);
5157
+ chmodSync2(path3, 384);
4796
5158
  } catch {
4797
5159
  }
4798
5160
  }
4799
5161
  function rotateBackups(filePath, env = process.env) {
4800
5162
  if (!existsSync3(filePath)) return;
4801
5163
  const backupsDir = getBackupsDir(env);
4802
- mkdirSync(backupsDir, { recursive: true });
5164
+ mkdirSync2(backupsDir, { recursive: true });
4803
5165
  const base = basename(filePath);
4804
5166
  const stamp = Date.now();
4805
- const backupPath = join2(backupsDir, `${base}.${stamp}.bak`);
5167
+ const backupPath = join3(backupsDir, `${base}.${stamp}.bak`);
4806
5168
  copyFileSync(filePath, backupPath);
4807
- const backups = readdirSync(backupsDir).filter((n) => n.startsWith(`${base}.`) && n.endsWith(".bak")).map((n) => ({ name: n, mtime: statSync(join2(backupsDir, n)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
5169
+ const backups = readdirSync(backupsDir).filter((n) => n.startsWith(`${base}.`) && n.endsWith(".bak")).map((n) => ({ name: n, mtime: statSync(join3(backupsDir, n)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
4808
5170
  for (const old of backups.slice(MAX_BACKUPS)) {
4809
5171
  try {
4810
- unlinkSync(join2(backupsDir, old.name));
5172
+ unlinkSync(join3(backupsDir, old.name));
4811
5173
  } catch {
4812
5174
  }
4813
5175
  }
@@ -4828,7 +5190,7 @@ function readSessionLock(env = process.env) {
4828
5190
  }
4829
5191
  function writeSessionLock(lock, env = process.env) {
4830
5192
  const path3 = getSessionLockPath(env);
4831
- mkdirSync(getRelayAiCodexDir(env), { recursive: true });
5193
+ mkdirSync2(getRelayAiCodexDir(env), { recursive: true });
4832
5194
  atomicWriteFile(path3, `${JSON.stringify(lock, null, 2)}
4833
5195
  `);
4834
5196
  }
@@ -4951,20 +5313,21 @@ function getCatalogOutputPath(providerId) {
4951
5313
  return getCatalogPath(providerId);
4952
5314
  }
4953
5315
  function getFavoritesCatalogPath() {
4954
- return join3(getRelayAiCodexDir(), "models-favorites.json");
5316
+ return join4(getRelayAiCodexDir(), "models-favorites.json");
4955
5317
  }
4956
5318
  function getFavoritesAppCatalogPath() {
4957
- return join3(getRelayAiCodexDir(), "app-models-favorites.json");
5319
+ return join4(getRelayAiCodexDir(), "app-models-favorites.json");
4958
5320
  }
4959
5321
  function profileName() {
4960
5322
  return CODEX_PROFILE_NAME;
4961
5323
  }
4962
5324
 
4963
5325
  // src/codex/launch.ts
4964
- import { execFileSync, execSync as execSync2, spawn as spawn2 } from "child_process";
5326
+ import { execSync as execSync2 } from "child_process";
5327
+ import spawn2 from "cross-spawn";
4965
5328
  import { existsSync as existsSync4 } from "fs";
4966
5329
  import { homedir as homedir4 } from "os";
4967
- import { join as join4 } from "path";
5330
+ import { join as join5 } from "path";
4968
5331
  var isWindows2 = process.platform === "win32";
4969
5332
  var CODEX_CI_ENV_VARS = [
4970
5333
  "CI",
@@ -4985,11 +5348,11 @@ function stripCodexInheritedEnv(env) {
4985
5348
  return out;
4986
5349
  }
4987
5350
  var CODEX_FALLBACK_PATHS = isWindows2 ? [
4988
- join4(process.env["APPDATA"] ?? homedir4(), "npm", "codex.cmd"),
4989
- join4(process.env["APPDATA"] ?? homedir4(), "npm", "codex")
5351
+ join5(process.env["APPDATA"] ?? homedir4(), "npm", "codex.cmd"),
5352
+ join5(process.env["APPDATA"] ?? homedir4(), "npm", "codex")
4990
5353
  ] : [
4991
- join4(homedir4(), ".local", "bin", "codex"),
4992
- join4(homedir4(), ".npm", "bin", "codex"),
5354
+ join5(homedir4(), ".local", "bin", "codex"),
5355
+ join5(homedir4(), ".npm", "bin", "codex"),
4993
5356
  "/usr/local/bin/codex",
4994
5357
  "/opt/homebrew/bin/codex"
4995
5358
  ];
@@ -5023,12 +5386,7 @@ function selectCodexBinary(candidates, exists, canRun) {
5023
5386
  }
5024
5387
  function canRunCodexBinary(path3) {
5025
5388
  try {
5026
- execFileSync(path3, ["--version"], {
5027
- encoding: "utf8",
5028
- stdio: ["ignore", "pipe", "pipe"],
5029
- timeout: 5e3,
5030
- shell: isWindows2
5031
- });
5389
+ runCodexCommandSync(path3, ["--version"], { timeout: 5e3 });
5032
5390
  return true;
5033
5391
  } catch {
5034
5392
  return false;
@@ -5067,8 +5425,7 @@ function launchCodex(modelId, env, extraArgs) {
5067
5425
  const args = ["--profile", profileName(), "-m", modelId, ...ensureCodexSandboxArgs(extraArgs)];
5068
5426
  const child = spawn2(codexPath, args, {
5069
5427
  stdio: "inherit",
5070
- env,
5071
- shell: isWindows2
5428
+ env
5072
5429
  });
5073
5430
  const forward = (signal) => {
5074
5431
  child.kill(signal);
@@ -5498,14 +5855,12 @@ async function resolveCodexMixedModels(input) {
5498
5855
  subagents: subagentResult.resolved,
5499
5856
  all,
5500
5857
  providersById: new Map(input.compatible.map((provider) => [provider.id, provider])),
5501
- dropped: [...visibleResult.droppedFavorites, ...subagentResult.droppedFavorites]
5858
+ dropped: [...visibleResult.droppedFavorites, ...subagentResult.droppedFavorites],
5859
+ capacitySkipped: [...visibleResult.capacitySkippedFavorites, ...subagentResult.capacitySkippedFavorites]
5502
5860
  };
5503
5861
  }
5504
5862
 
5505
5863
  // src/codex/native-catalog.ts
5506
- import { execFile } from "child_process";
5507
- import { promisify } from "util";
5508
- var execFileAsync = promisify(execFile);
5509
5864
  function isCatalogModel(value) {
5510
5865
  if (!value || typeof value !== "object") return false;
5511
5866
  const model = value;
@@ -5522,7 +5877,7 @@ function validateNativeCodexCatalog(value) {
5522
5877
  }
5523
5878
  async function captureNativeCodexCatalog(options) {
5524
5879
  const run = options.run ?? (async (args) => {
5525
- const result = await execFileAsync(options.binaryPath, args, { encoding: "utf8", maxBuffer: 16 * 1024 * 1024 });
5880
+ const result = await runCodexCommand(options.binaryPath, args, { maxBuffer: 16 * 1024 * 1024 });
5526
5881
  return result.stdout;
5527
5882
  });
5528
5883
  const stdout = await run(options.bundled ? ["debug", "models", "--bundled"] : ["debug", "models"]);
@@ -5545,6 +5900,22 @@ async function captureNativeCodexCatalog(options) {
5545
5900
  }
5546
5901
 
5547
5902
  // src/codex/mixed-catalog.ts
5903
+ function externalInstructionValue(value) {
5904
+ if (typeof value === "string") {
5905
+ return value.replace(/^You are Codex,[^\n]*?\.\s*/i, "").replace(/\bAs Codex,\s+(\w)/g, (_match, nextChar) => nextChar.toUpperCase()).replace(/\s+as Codex\b/gi, "");
5906
+ }
5907
+ if (Array.isArray(value)) return value.map(externalInstructionValue);
5908
+ if (!value || typeof value !== "object") return value;
5909
+ return Object.fromEntries(
5910
+ Object.entries(value).map(([key, nested]) => [
5911
+ key,
5912
+ externalInstructionValue(nested)
5913
+ ])
5914
+ );
5915
+ }
5916
+ function externalModelMessages(templateMessages) {
5917
+ return externalInstructionValue(templateMessages);
5918
+ }
5548
5919
  function externalCatalogEntryFromTemplate(template, entry, priority, visibility, multiAgentVersion) {
5549
5920
  const resolvedModel = entry.resolved.model;
5550
5921
  const generated = catalogEntryFromModel(
@@ -5554,7 +5925,7 @@ function externalCatalogEntryFromTemplate(template, entry, priority, visibility,
5554
5925
  false,
5555
5926
  entry.slug
5556
5927
  );
5557
- return {
5928
+ const external = {
5558
5929
  ...template,
5559
5930
  ...generated,
5560
5931
  slug: entry.slug,
@@ -5562,6 +5933,9 @@ function externalCatalogEntryFromTemplate(template, entry, priority, visibility,
5562
5933
  visibility,
5563
5934
  multi_agent_version: multiAgentVersion
5564
5935
  };
5936
+ external.model_messages = externalModelMessages(template.model_messages);
5937
+ delete external.comp_hash;
5938
+ return external;
5565
5939
  }
5566
5940
  function composeMixedCodexCatalog(input) {
5567
5941
  const template = input.nativeModels.find((model) => model.slug === "gpt-5.5") ?? input.nativeModels.find((model) => model.visibility === "list") ?? input.nativeModels[0];
@@ -5682,6 +6056,7 @@ async function prepareCodexMixedRelayRoutes(models, trace = false) {
5682
6056
  apiKey: backend.token,
5683
6057
  baseURL: `http://127.0.0.1:${backend.port}`,
5684
6058
  upstreamModelId: proxyRoute.aliasId,
6059
+ auditUpstreamModelId: original.model.upstreamModelId || original.model.id,
5685
6060
  providerId: original.providerId,
5686
6061
  authType: "oauth",
5687
6062
  oauthAccountId: original.oauthAccountId,
@@ -6012,7 +6387,7 @@ async function writeFavoritesLaunchArtifacts(resolved, starting, proxyPort) {
6012
6387
  return { profilePath, catalogPath };
6013
6388
  }
6014
6389
  async function writeMixedLaunchArtifacts(plan, proxyPort) {
6015
- const catalogPath = join5(getRelayAiCodexDir(), "models-mixed.json");
6390
+ const catalogPath = join6(getRelayAiCodexDir(), "models-mixed.json");
6016
6391
  writeOverlayFile(catalogPath, serializeCatalog(plan.catalog));
6017
6392
  const profilePath = getProfileOutputPath();
6018
6393
  writeOverlayFile(profilePath, buildCodexMixedProfileToml({
@@ -6136,7 +6511,7 @@ async function runCodexVertexLaunch(passthroughArgs, trace) {
6136
6511
  restoreCodexOverlay();
6137
6512
  }
6138
6513
  }
6139
- async function runCodexCommand(codexArgs, trace = false, launch = {}) {
6514
+ async function runCodexCommand2(codexArgs, trace = false, launch = {}) {
6140
6515
  if (codexArgs.includes("--help") || codexArgs.includes("-h")) {
6141
6516
  console.log(codexHelpText());
6142
6517
  return 0;
@@ -6334,7 +6709,7 @@ Error: ${launchPlan.error}
6334
6709
  let mixedPlan = null;
6335
6710
  if (mixedMode) {
6336
6711
  try {
6337
- const version = execFileSync2(codexPath, ["--version"], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
6712
+ const version = runCodexCommandSync(codexPath, ["--version"]).stdout.trim();
6338
6713
  const mixedModels = await resolveCodexMixedModels({
6339
6714
  activeProvider,
6340
6715
  selectedModel,
@@ -6597,17 +6972,17 @@ import * as p10 from "@clack/prompts";
6597
6972
 
6598
6973
  // src/gemini/launch.ts
6599
6974
  import { spawn as spawn3 } from "child_process";
6600
- import { existsSync as existsSync5, mkdirSync as mkdirSync2, mkdtempSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
6975
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, mkdtempSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
6601
6976
  import { homedir as homedir5, tmpdir } from "os";
6602
- import { join as join6 } from "path";
6977
+ import { join as join7 } from "path";
6603
6978
  var isWindows3 = process.platform === "win32";
6604
6979
  var GEMINI_API_KEY_AUTH_TYPE = "gemini-api-key";
6605
6980
  var GEMINI_FALLBACK_PATHS = isWindows3 ? [
6606
- join6(process.env["APPDATA"] ?? homedir5(), "npm", "gemini.cmd"),
6607
- join6(process.env["APPDATA"] ?? homedir5(), "npm", "gemini")
6981
+ join7(process.env["APPDATA"] ?? homedir5(), "npm", "gemini.cmd"),
6982
+ join7(process.env["APPDATA"] ?? homedir5(), "npm", "gemini")
6608
6983
  ] : [
6609
- join6(homedir5(), ".local", "bin", "gemini"),
6610
- join6(homedir5(), ".npm", "bin", "gemini"),
6984
+ join7(homedir5(), ".local", "bin", "gemini"),
6985
+ join7(homedir5(), ".npm", "bin", "gemini"),
6611
6986
  "/usr/local/bin/gemini",
6612
6987
  "/opt/homebrew/bin/gemini"
6613
6988
  ];
@@ -6628,7 +7003,7 @@ function buildGeminiChildEnv(proxyPort, proxyToken) {
6628
7003
  return env;
6629
7004
  }
6630
7005
  function createGeminiCliHomeOverlay() {
6631
- const cliHome = mkdtempSync(join6(tmpdir(), "relay-ai-gemini-"));
7006
+ const cliHome = mkdtempSync(join7(tmpdir(), "relay-ai-gemini-"));
6632
7007
  const settings = {
6633
7008
  security: {
6634
7009
  auth: {
@@ -6636,9 +7011,9 @@ function createGeminiCliHomeOverlay() {
6636
7011
  }
6637
7012
  }
6638
7013
  };
6639
- const geminiDir = join6(cliHome, ".gemini");
6640
- mkdirSync2(geminiDir);
6641
- writeFileSync2(join6(geminiDir, "settings.json"), `${JSON.stringify(settings, null, 2)}
7014
+ const geminiDir = join7(cliHome, ".gemini");
7015
+ mkdirSync3(geminiDir);
7016
+ writeFileSync3(join7(geminiDir, "settings.json"), `${JSON.stringify(settings, null, 2)}
6642
7017
  `, {
6643
7018
  encoding: "utf8",
6644
7019
  mode: 384
@@ -7738,6 +8113,267 @@ import * as p11 from "@clack/prompts";
7738
8113
  import http from "http";
7739
8114
  import { streamText as streamText3, generateText as generateText3 } from "ai";
7740
8115
 
8116
+ // src/antigravity/request-adapter.ts
8117
+ import { randomUUID as randomUUID2 } from "crypto";
8118
+ import { tool as tool3, jsonSchema as jsonSchema3 } from "ai";
8119
+ var UNSUPPORTED_VOICE_MESSAGE = "Voice transcription isn\u2019t supported by Relay AI yet. Please type your message. Your coding session remains active.";
8120
+ var OMITTED_VOICE_TEXT = "[Voice recording omitted because transcription is not supported by Relay AI.]";
8121
+ function isSupportedImage(part) {
8122
+ return part.inlineData?.mimeType.toLowerCase().startsWith("image/") ?? false;
8123
+ }
8124
+ function isUnsupportedInlineData(part) {
8125
+ return !!part.inlineData && !isSupportedImage(part);
8126
+ }
8127
+ function sanitizeUnsupportedInlineData(ccReq) {
8128
+ const contents = ccReq.request?.contents ?? [];
8129
+ let latestUserIndex = -1;
8130
+ for (let i = contents.length - 1; i >= 0; i--) {
8131
+ if (contents[i].role === "user") {
8132
+ latestUserIndex = i;
8133
+ break;
8134
+ }
8135
+ }
8136
+ let latestUserTurnHasUnsupportedMedia = false;
8137
+ const sanitizedContents = contents.map((message, index) => ({
8138
+ ...message,
8139
+ parts: message.parts.map((part) => {
8140
+ if (!isUnsupportedInlineData(part)) return part;
8141
+ if (index === latestUserIndex) latestUserTurnHasUnsupportedMedia = true;
8142
+ return { text: OMITTED_VOICE_TEXT };
8143
+ })
8144
+ }));
8145
+ return {
8146
+ request: {
8147
+ ...ccReq,
8148
+ request: {
8149
+ ...ccReq.request,
8150
+ contents: sanitizedContents
8151
+ }
8152
+ },
8153
+ latestUserTurnHasUnsupportedMedia
8154
+ };
8155
+ }
8156
+ function tracePartChars(part) {
8157
+ if (typeof part.text === "string") return part.text.length;
8158
+ if (part.type !== "tool-result") return void 0;
8159
+ const output = part.output;
8160
+ if (typeof output === "string") return output.length;
8161
+ if (output && typeof output === "object" && typeof output.value === "string") {
8162
+ return output.value.length;
8163
+ }
8164
+ try {
8165
+ return output === void 0 ? void 0 : JSON.stringify(output).length;
8166
+ } catch {
8167
+ return void 0;
8168
+ }
8169
+ }
8170
+ function summarizeSdkRequestForTrace(request2) {
8171
+ const messages = request2.messages.map((message) => {
8172
+ const content = message.content;
8173
+ if (typeof content === "string") {
8174
+ return { role: message.role, parts: [{ type: "text", chars: content.length }] };
8175
+ }
8176
+ const parts = Array.isArray(content) ? content.map((rawPart) => {
8177
+ const part = rawPart;
8178
+ const summary = {
8179
+ type: typeof part.type === "string" ? part.type : typeof rawPart
8180
+ };
8181
+ const chars = tracePartChars(part);
8182
+ if (chars !== void 0) summary.chars = chars;
8183
+ if (typeof part.toolName === "string") summary.toolName = part.toolName;
8184
+ if (typeof part.toolCallId === "string") summary.toolCallId = part.toolCallId;
8185
+ return summary;
8186
+ }) : [{ type: typeof content }];
8187
+ return { role: message.role, parts };
8188
+ });
8189
+ return {
8190
+ systemChars: request2.system?.length ?? 0,
8191
+ messages,
8192
+ toolNames: Object.keys(request2.tools ?? {}),
8193
+ ...request2.toolChoice ? { toolChoice: request2.toolChoice } : {}
8194
+ };
8195
+ }
8196
+ var JSON_SCHEMA_TYPES = /* @__PURE__ */ new Map([
8197
+ ["ARRAY", "array"],
8198
+ ["BOOLEAN", "boolean"],
8199
+ ["INTEGER", "integer"],
8200
+ ["NULL", "null"],
8201
+ ["NUMBER", "number"],
8202
+ ["OBJECT", "object"],
8203
+ ["STRING", "string"]
8204
+ ]);
8205
+ function expandTextWithThinking(text5) {
8206
+ if (!text5.includes("<thinking>")) {
8207
+ return [{ type: "text", text: text5 }];
8208
+ }
8209
+ const out = [];
8210
+ const tokens = text5.split(/<thinking>([\s\S]*?)<\/thinking>/);
8211
+ for (let i = 0; i < tokens.length; i++) {
8212
+ const token = tokens[i] ?? "";
8213
+ if (!token.trim()) continue;
8214
+ out.push({ type: i % 2 === 1 ? "reasoning" : "text", text: token });
8215
+ }
8216
+ return out.length > 0 ? out : [{ type: "text", text: text5 }];
8217
+ }
8218
+ function normalizeSchemaType(value) {
8219
+ if (typeof value === "string") {
8220
+ return JSON_SCHEMA_TYPES.get(value) ?? value;
8221
+ }
8222
+ if (Array.isArray(value)) {
8223
+ return value.map(normalizeSchemaType);
8224
+ }
8225
+ return value;
8226
+ }
8227
+ function normalizeJsonSchema(value) {
8228
+ if (Array.isArray(value)) {
8229
+ return value.map(normalizeJsonSchema);
8230
+ }
8231
+ if (!value || typeof value !== "object") {
8232
+ return value;
8233
+ }
8234
+ return Object.fromEntries(
8235
+ Object.entries(value).map(([key, child]) => [
8236
+ key,
8237
+ key === "type" ? normalizeSchemaType(child) : normalizeJsonSchema(child)
8238
+ ])
8239
+ );
8240
+ }
8241
+ function translateTools(ccTools, options = {}) {
8242
+ if (!ccTools?.length) return void 0;
8243
+ const tools = {};
8244
+ let toolCount = 0;
8245
+ for (const t of ccTools) {
8246
+ if (t.functionDeclarations) {
8247
+ for (const fd of t.functionDeclarations) {
8248
+ if (options.maxTools !== void 0 && toolCount >= options.maxTools) break;
8249
+ tools[fd.name] = tool3({
8250
+ description: fd.description || "",
8251
+ inputSchema: jsonSchema3(
8252
+ normalizeJsonSchema(fd.parameters || { type: "object", properties: {} })
8253
+ )
8254
+ });
8255
+ toolCount++;
8256
+ }
8257
+ }
8258
+ }
8259
+ return Object.keys(tools).length > 0 ? tools : void 0;
8260
+ }
8261
+ function translateRequest(ccReq, options = {}) {
8262
+ const systemInstructions = [];
8263
+ const sdkMessages = [];
8264
+ const nameToIdList = /* @__PURE__ */ new Map();
8265
+ const fallbackAssistantReasoning = [...options.fallbackAssistantReasoning ?? []];
8266
+ const request2 = ccReq.request || {};
8267
+ if (request2.systemInstruction?.parts) {
8268
+ for (const part of request2.systemInstruction.parts) {
8269
+ if (part.text) {
8270
+ systemInstructions.push(part.text);
8271
+ }
8272
+ }
8273
+ }
8274
+ const contents = request2.contents || [];
8275
+ for (const msg of contents) {
8276
+ const role = msg.role;
8277
+ if (role === "system") {
8278
+ for (const part of msg.parts) {
8279
+ if (part.text) {
8280
+ systemInstructions.push(part.text);
8281
+ }
8282
+ }
8283
+ continue;
8284
+ }
8285
+ const sdkRole = role === "model" ? "assistant" : "user";
8286
+ const hasFunctionCall = msg.parts.some((p15) => p15.functionCall);
8287
+ const hasAssistantReasoning = role === "model" && msg.parts.some((p15) => p15.thought || p15.text?.includes("<thinking>"));
8288
+ const hasComplexParts = msg.parts.some((p15) => p15.thought || p15.inlineData || p15.functionCall || p15.functionResponse);
8289
+ const singleText = msg.parts.length === 1 ? msg.parts[0]?.text : void 0;
8290
+ if (!hasComplexParts && singleText !== void 0 && !singleText.includes("<thinking>")) {
8291
+ sdkMessages.push({
8292
+ role: sdkRole,
8293
+ content: singleText
8294
+ });
8295
+ continue;
8296
+ }
8297
+ const contentParts = [];
8298
+ const toolResults = [];
8299
+ if (role === "model" && hasFunctionCall && !hasAssistantReasoning) {
8300
+ const fallback = fallbackAssistantReasoning.shift();
8301
+ if (fallback?.trim()) {
8302
+ contentParts.push({ type: "reasoning", text: fallback });
8303
+ }
8304
+ }
8305
+ for (const part of msg.parts) {
8306
+ if (part.text !== void 0) {
8307
+ if (part.thought) {
8308
+ contentParts.push({ type: "reasoning", text: part.text });
8309
+ } else {
8310
+ for (const piece of expandTextWithThinking(part.text)) {
8311
+ contentParts.push(piece);
8312
+ }
8313
+ }
8314
+ } else if (part.inlineData) {
8315
+ if (isSupportedImage(part)) {
8316
+ contentParts.push({
8317
+ type: "image",
8318
+ image: part.inlineData.data,
8319
+ mimeType: part.inlineData.mimeType
8320
+ });
8321
+ } else {
8322
+ contentParts.push({ type: "text", text: OMITTED_VOICE_TEXT });
8323
+ }
8324
+ } else if (part.functionCall) {
8325
+ const id = "call_" + randomUUID2().replace(/-/g, "");
8326
+ const name = part.functionCall.name;
8327
+ if (!nameToIdList.has(name)) nameToIdList.set(name, []);
8328
+ nameToIdList.get(name).push(id);
8329
+ contentParts.push({
8330
+ type: "tool-call",
8331
+ toolCallId: id,
8332
+ toolName: name,
8333
+ input: part.functionCall.args || {}
8334
+ });
8335
+ } else if (part.functionResponse) {
8336
+ const name = part.functionResponse.name;
8337
+ const idList = nameToIdList.get(name) || [];
8338
+ const id = idList.shift() || "call_" + randomUUID2().replace(/-/g, "");
8339
+ toolResults.push({
8340
+ type: "tool-result",
8341
+ toolCallId: id,
8342
+ toolName: name,
8343
+ output: { type: "text", value: serializeToolResultContent(part.functionResponse.response) }
8344
+ });
8345
+ }
8346
+ }
8347
+ if (toolResults.length > 0) {
8348
+ sdkMessages.push({
8349
+ role: "tool",
8350
+ content: toolResults
8351
+ });
8352
+ }
8353
+ if (contentParts.length > 0) {
8354
+ sdkMessages.push({
8355
+ role: sdkRole,
8356
+ content: contentParts
8357
+ });
8358
+ }
8359
+ }
8360
+ const system = systemInstructions.length > 0 ? systemInstructions.join("\n\n") : void 0;
8361
+ const tools = translateTools(request2.tools, options);
8362
+ let toolChoice;
8363
+ const mode = request2.toolConfig?.functionCallingConfig?.mode;
8364
+ if (mode === "ANY") {
8365
+ toolChoice = "required";
8366
+ } else if (mode === "AUTO" || tools) {
8367
+ toolChoice = "auto";
8368
+ }
8369
+ return {
8370
+ system,
8371
+ messages: sdkMessages,
8372
+ tools,
8373
+ toolChoice
8374
+ };
8375
+ }
8376
+
7741
8377
  // src/antigravity/response-adapter.ts
7742
8378
  function normalizeFunctionCallArgs(args) {
7743
8379
  const out = {};
@@ -9615,19 +10251,19 @@ async function resolveAntigravityLaunchRoutes(opts) {
9615
10251
  }
9616
10252
 
9617
10253
  // src/antigravity/launch-cli.ts
9618
- import { execFileSync as execFileSync3, execSync as execSync3 } from "child_process";
10254
+ import { execFileSync, execSync as execSync3 } from "child_process";
9619
10255
  import spawn4 from "cross-spawn";
9620
10256
  import { existsSync as existsSync6 } from "fs";
9621
10257
  import { homedir as homedir6 } from "os";
9622
- import { join as join7 } from "path";
10258
+ import { join as join8 } from "path";
9623
10259
  var isWindows4 = process.platform === "win32";
9624
10260
  var FALLBACK_PATHS = isWindows4 ? [
9625
- join7(process.env["APPDATA"] ?? homedir6(), "npm", "agy.cmd"),
9626
- join7(process.env["APPDATA"] ?? homedir6(), "npm", "agy"),
9627
- join7(homedir6(), "AppData", "Roaming", "npm", "agy.cmd")
10261
+ join8(process.env["APPDATA"] ?? homedir6(), "npm", "agy.cmd"),
10262
+ join8(process.env["APPDATA"] ?? homedir6(), "npm", "agy"),
10263
+ join8(homedir6(), "AppData", "Roaming", "npm", "agy.cmd")
9628
10264
  ] : [
9629
- join7(homedir6(), ".local", "bin", "agy"),
9630
- join7(homedir6(), ".npm", "bin", "agy"),
10265
+ join8(homedir6(), ".local", "bin", "agy"),
10266
+ join8(homedir6(), ".npm", "bin", "agy"),
9631
10267
  "/usr/local/bin/agy",
9632
10268
  "/opt/homebrew/bin/agy"
9633
10269
  ];
@@ -9653,7 +10289,7 @@ function readAntigravityCliVersion(binaryPath = findAntigravityCliBinary() ?? vo
9653
10289
  return { version: null, error: 'Antigravity CLI binary "agy" not found' };
9654
10290
  }
9655
10291
  try {
9656
- const raw = execFileSync3(binaryPath, ["--version"], {
10292
+ const raw = execFileSync(binaryPath, ["--version"], {
9657
10293
  encoding: "utf8",
9658
10294
  stdio: ["ignore", "pipe", "pipe"]
9659
10295
  }).trim();
@@ -9702,10 +10338,10 @@ function launchAntigravityCli(env, extraArgs) {
9702
10338
  }
9703
10339
 
9704
10340
  // src/antigravity/launch-ide.ts
9705
- import { execFileSync as execFileSync4, execSync as execSync4, spawn as spawn5 } from "child_process";
10341
+ import { execFileSync as execFileSync2, execSync as execSync4, spawn as spawn5 } from "child_process";
9706
10342
  import { existsSync as existsSync7 } from "fs";
9707
10343
  import { homedir as homedir7 } from "os";
9708
- import { join as join8 } from "path";
10344
+ import { join as join9 } from "path";
9709
10345
 
9710
10346
  // src/antigravity/ide-profile.ts
9711
10347
  import fs from "fs";
@@ -9739,8 +10375,8 @@ function prepareIdeProfile(profileDir, gatewayUrl) {
9739
10375
  }
9740
10376
 
9741
10377
  // src/antigravity/launch-ide.ts
9742
- var LINUX_APP_PROFILE_DIR = join8(homedir7(), ".relay-ai", "antigravity", "app-profile");
9743
- var LINUX_IDE_PROFILE_DIR = join8(homedir7(), ".relay-ai", "antigravity", "profile");
10378
+ var LINUX_APP_PROFILE_DIR = join9(homedir7(), ".relay-ai", "antigravity", "app-profile");
10379
+ var LINUX_IDE_PROFILE_DIR = join9(homedir7(), ".relay-ai", "antigravity", "profile");
9744
10380
  function sleep(ms) {
9745
10381
  return new Promise((resolve2) => setTimeout(resolve2, ms));
9746
10382
  }
@@ -9748,7 +10384,7 @@ function linuxAntigravityBinary() {
9748
10384
  const candidates = [
9749
10385
  "/usr/share/antigravity/antigravity",
9750
10386
  "/opt/antigravity/antigravity",
9751
- join8(homedir7(), ".local", "share", "antigravity", "antigravity")
10387
+ join9(homedir7(), ".local", "share", "antigravity", "antigravity")
9752
10388
  ];
9753
10389
  for (const candidate of candidates) {
9754
10390
  if (existsSync7(candidate)) return candidate;
@@ -9806,7 +10442,7 @@ function defaultProcessList() {
9806
10442
  const psArgs = process.platform === "linux" ? ["-eo", "pid=,args="] : ["-axo", "pid=,command="];
9807
10443
  if (process.platform !== "darwin" && process.platform !== "linux") return "";
9808
10444
  try {
9809
- return execFileSync4("ps", psArgs, {
10445
+ return execFileSync2("ps", psArgs, {
9810
10446
  encoding: "utf8",
9811
10447
  stdio: ["ignore", "pipe", "ignore"],
9812
10448
  maxBuffer: 1024 * 1024 * 4
@@ -9870,11 +10506,11 @@ function quitAntigravityIdeGracefully() {
9870
10506
  }
9871
10507
  if (process.platform !== "darwin") return;
9872
10508
  try {
9873
- execFileSync4("osascript", ["-e", 'tell application "Antigravity IDE" to quit'], {
10509
+ execFileSync2("osascript", ["-e", 'tell application "Antigravity IDE" to quit'], {
9874
10510
  stdio: ["ignore", "pipe", "pipe"]
9875
10511
  });
9876
10512
  } catch {
9877
- execFileSync4("osascript", ["-e", 'tell application id "com.google.antigravity-ide" to quit'], {
10513
+ execFileSync2("osascript", ["-e", 'tell application id "com.google.antigravity-ide" to quit'], {
9878
10514
  stdio: ["ignore", "pipe", "pipe"]
9879
10515
  });
9880
10516
  }
@@ -9890,11 +10526,11 @@ function quitAntigravityAppGracefully() {
9890
10526
  }
9891
10527
  if (process.platform !== "darwin") return;
9892
10528
  try {
9893
- execFileSync4("osascript", ["-e", 'tell application "Antigravity" to quit'], {
10529
+ execFileSync2("osascript", ["-e", 'tell application "Antigravity" to quit'], {
9894
10530
  stdio: ["ignore", "pipe", "pipe"]
9895
10531
  });
9896
10532
  } catch {
9897
- execFileSync4("osascript", ["-e", 'tell application id "com.google.antigravity" to quit'], {
10533
+ execFileSync2("osascript", ["-e", 'tell application id "com.google.antigravity" to quit'], {
9898
10534
  stdio: ["ignore", "pipe", "pipe"]
9899
10535
  });
9900
10536
  }
@@ -9903,15 +10539,15 @@ function findAntigravityAppBinary() {
9903
10539
  const override = getAppPathOverride("antigravity");
9904
10540
  if (override) return existsSync7(override) ? override : null;
9905
10541
  if (process.platform === "win32") {
9906
- const localAppData = process.env["LOCALAPPDATA"] ?? join8(homedir7(), "AppData", "Local");
9907
- const winPath = join8(localAppData, "Programs", "Antigravity", "Antigravity.exe");
10542
+ const localAppData = process.env["LOCALAPPDATA"] ?? join9(homedir7(), "AppData", "Local");
10543
+ const winPath = join9(localAppData, "Programs", "Antigravity", "Antigravity.exe");
9908
10544
  return existsSync7(winPath) ? winPath : null;
9909
10545
  }
9910
10546
  if (process.platform === "linux") return linuxAntigravityBinary();
9911
10547
  if (process.platform !== "darwin") return null;
9912
10548
  const defaultPath = "/Applications/Antigravity.app/Contents/MacOS/Antigravity";
9913
10549
  if (existsSync7(defaultPath)) return defaultPath;
9914
- const homePath = join8(homedir7(), "Applications", "Antigravity.app", "Contents", "MacOS", "Antigravity");
10550
+ const homePath = join9(homedir7(), "Applications", "Antigravity.app", "Contents", "MacOS", "Antigravity");
9915
10551
  if (existsSync7(homePath)) return homePath;
9916
10552
  return null;
9917
10553
  }
@@ -9919,15 +10555,15 @@ function findAntigravityIdeBinary() {
9919
10555
  const override = getAppPathOverride("antigravity-ide");
9920
10556
  if (override) return existsSync7(override) ? override : null;
9921
10557
  if (process.platform === "win32") {
9922
- const localAppData = process.env["LOCALAPPDATA"] ?? join8(homedir7(), "AppData", "Local");
9923
- const winPath = join8(localAppData, "Programs", "Antigravity IDE", "Antigravity IDE.exe");
10558
+ const localAppData = process.env["LOCALAPPDATA"] ?? join9(homedir7(), "AppData", "Local");
10559
+ const winPath = join9(localAppData, "Programs", "Antigravity IDE", "Antigravity IDE.exe");
9924
10560
  return existsSync7(winPath) ? winPath : null;
9925
10561
  }
9926
10562
  if (process.platform === "linux") return linuxAntigravityBinary();
9927
10563
  if (process.platform !== "darwin") return null;
9928
10564
  const defaultPath = "/Applications/Antigravity IDE.app/Contents/Resources/app/bin/antigravity-ide";
9929
10565
  if (existsSync7(defaultPath)) return defaultPath;
9930
- const homePath = join8(homedir7(), "Applications", "Antigravity IDE.app", "Contents", "Resources", "app", "bin", "antigravity-ide");
10566
+ const homePath = join9(homedir7(), "Applications", "Antigravity IDE.app", "Contents", "Resources", "app", "bin", "antigravity-ide");
9931
10567
  if (existsSync7(homePath)) return homePath;
9932
10568
  return null;
9933
10569
  }
@@ -9988,7 +10624,7 @@ function launchAntigravityIde(env, profileDir, gatewayUrl, extraArgs) {
9988
10624
  return;
9989
10625
  }
9990
10626
  prepareIdeProfile(profileDir, gatewayUrl);
9991
- const relayExtensionsDir = join8(homedir7(), ".relay-ai", "antigravity", "extensions");
10627
+ const relayExtensionsDir = join9(homedir7(), ".relay-ai", "antigravity", "extensions");
9992
10628
  const args = [
9993
10629
  `--user-data-dir=${profileDir}`,
9994
10630
  `--extensions-dir=${relayExtensionsDir}`,
@@ -10018,7 +10654,7 @@ function launchAntigravityIde(env, profileDir, gatewayUrl, extraArgs) {
10018
10654
 
10019
10655
  // src/antigravity.ts
10020
10656
  import { homedir as homedir8 } from "os";
10021
- import { join as join9 } from "path";
10657
+ import { join as join10 } from "path";
10022
10658
  var SHUTDOWN_DRAIN_MS = 500;
10023
10659
  var AGY_FAVORITES_PROVIDER_ID = "__relay_agy_favorites__";
10024
10660
  var AGY_FAVORITES_PROVIDER_LABEL = "\u2605 Antigravity CLI Favorites";
@@ -10312,7 +10948,7 @@ async function runAntigravityAppCommand(childArgs, trace = false, boot) {
10312
10948
  trace,
10313
10949
  boot,
10314
10950
  async (env, _routes, gatewayHandle) => {
10315
- const profileDir = join9(homedir8(), ".relay-ai", "antigravity", "app-profile");
10951
+ const profileDir = join10(homedir8(), ".relay-ai", "antigravity", "app-profile");
10316
10952
  if (isAntigravityAppRunning(profileDir)) {
10317
10953
  const restart = await p11.confirm({
10318
10954
  message: "Restart Antigravity to apply this Relay gateway?",
@@ -10360,7 +10996,7 @@ async function runAntigravityIdeCommand(childArgs, trace = false, boot) {
10360
10996
  trace,
10361
10997
  boot,
10362
10998
  async (env, _routes, gatewayHandle) => {
10363
- const profileDir = join9(homedir8(), ".relay-ai", "antigravity", "profile");
10999
+ const profileDir = join10(homedir8(), ".relay-ai", "antigravity", "profile");
10364
11000
  if (isAntigravityIdeRunning(profileDir)) {
10365
11001
  const restart = await p11.confirm({
10366
11002
  message: "Restart Antigravity IDE to apply this Relay gateway?",
@@ -10405,8 +11041,7 @@ async function runAntigravityIdeCommand(childArgs, trace = false, boot) {
10405
11041
  // src/codex-app.ts
10406
11042
  import pc10 from "picocolors";
10407
11043
  import * as p12 from "@clack/prompts";
10408
- import { execFileSync as execFileSync5 } from "child_process";
10409
- import { join as join12 } from "path";
11044
+ import { join as join13 } from "path";
10410
11045
 
10411
11046
  // src/codex/app-provider-routes.ts
10412
11047
  function codexRouteToProxyRoute(provider, model, apiKey) {
@@ -10495,14 +11130,14 @@ async function buildCodexAppProviderCatalogRoutes(provider, apiKey, selectedMode
10495
11130
  }
10496
11131
 
10497
11132
  // src/codex/app-config.ts
10498
- import { existsSync as existsSync8, readFileSync as readFileSync3, rmSync as rmSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
10499
- import { dirname as dirname2, join as join10 } from "path";
11133
+ import { existsSync as existsSync8, readFileSync as readFileSync3, rmSync as rmSync3, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4 } from "fs";
11134
+ import { dirname as dirname2, join as join11 } from "path";
10500
11135
  import { parse, stringify } from "smol-toml";
10501
11136
  function getCodexConfigPath() {
10502
- return join10(getCodexHome(), "config.toml");
11137
+ return join11(getCodexHome(), "config.toml");
10503
11138
  }
10504
11139
  function getCodexAppSidecarProfilePath() {
10505
- return join10(getCodexHome(), `${CODEX_APP_PROVIDER_ID}.config.toml`);
11140
+ return join11(getCodexHome(), `${CODEX_APP_PROVIDER_ID}.config.toml`);
10506
11141
  }
10507
11142
  function asRecord(value) {
10508
11143
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
@@ -10677,8 +11312,13 @@ function applyAppConfigPatch(spec, configPath = getCodexConfigPath()) {
10677
11312
  const text5 = `${stringify(merged)}
10678
11313
  `;
10679
11314
  validateAppConfigText(text5, spec);
10680
- mkdirSync3(dirname2(configPath), { recursive: true });
10681
- writeFileSync3(configPath, text5, "utf8");
11315
+ mkdirSync4(dirname2(configPath), { recursive: true });
11316
+ atomicWriteFile(configPath, text5);
11317
+ const written = readCodexConfigText(configPath);
11318
+ if (written !== text5) {
11319
+ throw new Error(`Codex config readback mismatch at ${configPath}`);
11320
+ }
11321
+ validateAppConfigText(written, spec);
10682
11322
  return text5;
10683
11323
  }
10684
11324
  function applyRestoreKey(config, key, had, value) {
@@ -10734,7 +11374,7 @@ function restoreConfigFromState(state, configPath = getCodexConfigPath()) {
10734
11374
  rmSync3(configPath, { force: true });
10735
11375
  return true;
10736
11376
  }
10737
- writeFileSync3(configPath, `${stringify(config)}
11377
+ writeFileSync4(configPath, `${stringify(config)}
10738
11378
  `, "utf8");
10739
11379
  return true;
10740
11380
  }
@@ -10745,31 +11385,69 @@ function previewAppConfigToml(spec) {
10745
11385
  return text5;
10746
11386
  }
10747
11387
 
11388
+ // src/codex/app-readiness.ts
11389
+ import { readFileSync as readFileSync4 } from "fs";
11390
+ function proxyRoot(spec) {
11391
+ const base = spec.proxyBaseUrl ?? `http://127.0.0.1:${spec.proxyPort}/v1`;
11392
+ if (!base.endsWith("/v1")) throw new Error("Codex App proxy base URL must end in /v1");
11393
+ return base.slice(0, -3);
11394
+ }
11395
+ async function checkedJson(url, fetchImpl) {
11396
+ const response = await fetchImpl(url);
11397
+ if (!response.ok) throw new Error(`Relay readiness check failed: GET ${url} returned HTTP ${response.status}`);
11398
+ return response.json();
11399
+ }
11400
+ async function verifyCodexAppReadiness(spec, options = {}) {
11401
+ const fetchImpl = options.fetchImpl ?? fetch;
11402
+ const root = proxyRoot(spec);
11403
+ const health = await checkedJson(`${root}/health`, fetchImpl);
11404
+ if (health.ok !== true) throw new Error("Relay proxy health check did not report ready");
11405
+ const catalog = JSON.parse(readFileSync4(spec.catalogPath, "utf8"));
11406
+ if (!Array.isArray(catalog.models) || catalog.models.length === 0) {
11407
+ throw new Error("Relay Codex model catalog is empty or invalid");
11408
+ }
11409
+ const catalogIds = catalog.models.map((model) => model?.slug).filter((id) => typeof id === "string" && id.length > 0);
11410
+ if (catalogIds.length !== catalog.models.length) throw new Error("Relay Codex model catalog contains an invalid model slug");
11411
+ if (!catalogIds.includes(spec.route.modelId)) {
11412
+ throw new Error(`Relay Codex model catalog is missing selected model ${spec.route.modelId}`);
11413
+ }
11414
+ const advertised = await checkedJson(`${root}/v1/models`, fetchImpl);
11415
+ const advertisedIds = new Set((advertised.data ?? []).map((model) => model.id).filter((id) => typeof id === "string"));
11416
+ for (const id of catalogIds) {
11417
+ if (!advertisedIds.has(id)) throw new Error(`Relay proxy does not advertise catalog model ${id}`);
11418
+ }
11419
+ validateAppConfigText(readCodexConfigText(options.configPath), spec);
11420
+ }
11421
+
10748
11422
  // src/codex/app-session.ts
10749
11423
  import {
10750
11424
  copyFileSync as copyFileSync2,
10751
11425
  existsSync as existsSync9,
10752
- mkdirSync as mkdirSync4,
11426
+ mkdirSync as mkdirSync5,
10753
11427
  readdirSync as readdirSync2,
10754
- readFileSync as readFileSync4,
11428
+ readFileSync as readFileSync5,
10755
11429
  rmSync as rmSync4,
10756
11430
  statSync as statSync2
10757
11431
  } from "fs";
10758
- import { basename as basename2, join as join11 } from "path";
11432
+ import { basename as basename2, join as join12 } from "path";
11433
+ import { createHash as createHash3 } from "crypto";
10759
11434
  function getAppSessionLockPath(env = process.env) {
10760
- return join11(getRelayAiCodexDir(env), "session-app.json");
11435
+ return join12(getRelayAiCodexDir(env), "session-app.json");
10761
11436
  }
10762
11437
  function getAppRestoreStatePath(env = process.env) {
10763
- return join11(getRelayAiCodexDir(env), "app-restore-state.json");
11438
+ return join12(getRelayAiCodexDir(env), "app-restore-state.json");
10764
11439
  }
10765
11440
  function getAppCatalogPath(providerId, env = process.env) {
10766
- return join11(getRelayAiCodexDir(env), `app-models-${providerId}.json`);
11441
+ return join12(getRelayAiCodexDir(env), `app-models-${providerId}.json`);
11442
+ }
11443
+ function fileSha256(path3) {
11444
+ return createHash3("sha256").update(readFileSync5(path3)).digest("hex");
10767
11445
  }
10768
11446
  function readAppSessionLock(env = process.env) {
10769
11447
  const path3 = getAppSessionLockPath(env);
10770
11448
  if (!existsSync9(path3)) return null;
10771
11449
  try {
10772
- const parsed = JSON.parse(readFileSync4(path3, "utf8"));
11450
+ const parsed = JSON.parse(readFileSync5(path3, "utf8"));
10773
11451
  if (typeof parsed.pid === "number" && typeof parsed.startedAt === "string") return parsed;
10774
11452
  } catch {
10775
11453
  }
@@ -10787,7 +11465,7 @@ function readAppRestoreState(env = process.env) {
10787
11465
  const path3 = getAppRestoreStatePath(env);
10788
11466
  if (!existsSync9(path3)) return null;
10789
11467
  try {
10790
- return JSON.parse(readFileSync4(path3, "utf8"));
11468
+ return JSON.parse(readFileSync5(path3, "utf8"));
10791
11469
  } catch {
10792
11470
  return null;
10793
11471
  }
@@ -10806,9 +11484,9 @@ function backupConfigToml(env = process.env) {
10806
11484
  if (!existsSync9(configPath)) return void 0;
10807
11485
  rotateBackups(configPath, env);
10808
11486
  const backupsDir = getBackupsDir(env);
10809
- mkdirSync4(backupsDir, { recursive: true });
11487
+ mkdirSync5(backupsDir, { recursive: true });
10810
11488
  const base = basename2(configPath);
10811
- const backupPath = join11(backupsDir, `${base}.${Date.now()}.bak`);
11489
+ const backupPath = join12(backupsDir, `${base}.${Date.now()}.bak`);
10812
11490
  copyFileSync2(configPath, backupPath);
10813
11491
  return backupPath;
10814
11492
  }
@@ -10825,7 +11503,7 @@ function saveAppRestoreStateBeforePatch(env = process.env) {
10825
11503
  function ownedAppCatalogPaths(env = process.env) {
10826
11504
  const codexDir = getRelayAiCodexDir(env);
10827
11505
  if (!existsSync9(codexDir)) return [];
10828
- return readdirSync2(codexDir).filter((n) => n.startsWith("app-models-") && n.endsWith(".json")).map((n) => join11(codexDir, n));
11506
+ return readdirSync2(codexDir).filter((n) => n.startsWith("app-models-") && n.endsWith(".json")).map((n) => join12(codexDir, n));
10829
11507
  }
10830
11508
  function removeAppCatalogs(env = process.env) {
10831
11509
  const removed = [];
@@ -10843,7 +11521,7 @@ function newestConfigBackup(env = process.env) {
10843
11521
  if (!existsSync9(backupDir)) return null;
10844
11522
  const configBase = basename2(getCodexConfigPath());
10845
11523
  const candidates = readdirSync2(backupDir).filter((name) => name.startsWith(`${configBase}.`) && name.endsWith(".bak")).map((name) => {
10846
- const path3 = join11(backupDir, name);
11524
+ const path3 = join12(backupDir, name);
10847
11525
  try {
10848
11526
  return { path: path3, mtimeMs: statSync2(path3).mtimeMs };
10849
11527
  } catch {
@@ -10869,7 +11547,12 @@ function restoreCodexAppOverlay(env = process.env) {
10869
11547
  clearAppSessionLock(env);
10870
11548
  return { restored: false, message: "Nothing to restore." };
10871
11549
  }
10872
- if (restoreState) {
11550
+ const exactBackupIsSafe = Boolean(
11551
+ managed && lock?.backupPath && existsSync9(lock.backupPath) && lock.patchedConfigSha256 && lock.originalConfigSha256 && fileSha256(getCodexConfigPath()) === lock.patchedConfigSha256 && fileSha256(lock.backupPath) === lock.originalConfigSha256
11552
+ );
11553
+ if (exactBackupIsSafe) {
11554
+ copyFileSync2(lock.backupPath, getCodexConfigPath());
11555
+ } else if (restoreState) {
10873
11556
  restoreConfigFromState(restoreState);
10874
11557
  } else if (lock?.backupPath && existsSync9(lock.backupPath)) {
10875
11558
  copyFileSync2(lock.backupPath, getCodexConfigPath());
@@ -10945,10 +11628,15 @@ function codexProxyRouteToCodexRoute(route, fallbackProviderId) {
10945
11628
  refreshToken: route.refreshToken
10946
11629
  };
10947
11630
  }
10948
- async function waitForShutdownWithConfirm() {
11631
+ function codexAppUsesExplicitSelection(configOnly, launchProvider, launchModel) {
11632
+ void configOnly;
11633
+ return Boolean(launchProvider && launchModel);
11634
+ }
11635
+ async function waitForShutdownWithConfirm(assumeYes = false) {
10949
11636
  while (true) {
10950
11637
  const signal = await waitForShutdown2();
10951
- if (signal !== "sigint") break;
11638
+ if (signal !== "sigint") return signal;
11639
+ if (assumeYes) return signal;
10952
11640
  console.log("");
10953
11641
  const choice = await p12.select({
10954
11642
  message: "Close ChatGPT Desktop and restore your Codex config?",
@@ -10957,11 +11645,19 @@ async function waitForShutdownWithConfirm() {
10957
11645
  { value: "no", label: "No, keep session running" }
10958
11646
  ]
10959
11647
  });
10960
- if (p12.isCancel(choice) || choice === "yes") break;
11648
+ if (p12.isCancel(choice) || choice === "yes") return signal;
10961
11649
  }
10962
11650
  }
10963
- async function maybeCloseRunningCodexApp() {
11651
+ function unattendedShutdownClosesApp(assumeYes, signal) {
11652
+ return assumeYes && signal !== "sigint";
11653
+ }
11654
+ async function maybeCloseRunningCodexApp(assumeYes = false) {
10964
11655
  if (!isCodexAppRunning()) return;
11656
+ if (assumeYes) {
11657
+ p12.log.step("Stopping ChatGPT Desktop...");
11658
+ quitCodexAppGracefully();
11659
+ return;
11660
+ }
10965
11661
  const shouldClose = await p12.confirm({ message: "ChatGPT Desktop is still running. Close it?" });
10966
11662
  if (shouldClose && !p12.isCancel(shouldClose)) {
10967
11663
  p12.log.step("Stopping ChatGPT Desktop...");
@@ -10985,6 +11681,7 @@ ${pc10.bold("Options:")}
10985
11681
  --vertex Use Claude models through Google Vertex AI
10986
11682
  --with-native Load native Codex models beside Relay models for this launch
10987
11683
  --relay-only Keep the current Relay-only launch behavior
11684
+ --yes, -y Approve a fully specified launch/restart without prompting
10988
11685
  --restore Restore Codex config after an interrupted app session
10989
11686
  --config Preview the generated Codex app configuration without launching
10990
11687
  --trace Write proxy debug logs to ~/.relay-ai/logs/ and show errors on exit
@@ -11011,6 +11708,7 @@ ${pc10.bold("Preview (no writes):")}
11011
11708
  ${pc10.bold("Examples:")}
11012
11709
  relay-ai codex-app
11013
11710
  relay-ai codex-app --vertex
11711
+ relay-ai codex-app --provider antigravity --model gemini-3.1-pro-high --with-native --yes
11014
11712
  relay-ai codex-app --config
11015
11713
  relay-ai codex-app --restore
11016
11714
 
@@ -11119,8 +11817,10 @@ async function runCodexAppVertexLaunch(configOnly, trace = false) {
11119
11817
  catalogPath
11120
11818
  };
11121
11819
  saveAppRestoreStateBeforePatch();
11820
+ sessionActive = true;
11122
11821
  const backupPath = backupConfigToml();
11123
11822
  applyAppConfigPatch(spec);
11823
+ await verifyCodexAppReadiness(spec);
11124
11824
  writeAppSessionLock({
11125
11825
  pid: process.pid,
11126
11826
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -11128,9 +11828,10 @@ async function runCodexAppVertexLaunch(configOnly, trace = false) {
11128
11828
  catalogPaths: [catalogPath],
11129
11829
  restoreStatePath: getAppRestoreStatePath(),
11130
11830
  backupPath,
11131
- proxyPort
11831
+ proxyPort,
11832
+ patchedConfigSha256: fileSha256(getCodexConfigPath()),
11833
+ ...backupPath ? { originalConfigSha256: fileSha256(backupPath) } : {}
11132
11834
  });
11133
- sessionActive = true;
11134
11835
  p12.log.info(`Vertex AI \xB7 ${selectedEntry.display_name} \u2014 project: ${config.project} / location: ${config.location}`);
11135
11836
  logProxy(proxyPort);
11136
11837
  logActiveModel(selectedEntry.display_name, selectedEntry.id);
@@ -11139,6 +11840,7 @@ async function runCodexAppVertexLaunch(configOnly, trace = false) {
11139
11840
  } catch (err) {
11140
11841
  p12.log.warn(String(err instanceof Error ? err.message : err));
11141
11842
  p12.log.info(codexAppInstallHint());
11843
+ throw err;
11142
11844
  }
11143
11845
  printCodexAppSessionPanel({
11144
11846
  modelLabel: selectedEntry.display_name,
@@ -11170,6 +11872,13 @@ async function runCodexAppCommand(args, opts = {}) {
11170
11872
  console.log(result.message);
11171
11873
  return result.liveSession ? 1 : 0;
11172
11874
  }
11875
+ const configOnly = args.includes("--config");
11876
+ if (opts.assumeYes && !configOnly) {
11877
+ if (opts.vertex || !opts.launchProvider || !opts.launchModel || !opts.codexLaunchMode) {
11878
+ console.error(pc10.red("--yes requires --provider, --model, and either --with-native or --relay-only."));
11879
+ return 1;
11880
+ }
11881
+ }
11173
11882
  try {
11174
11883
  codexAppSupported();
11175
11884
  } catch (err) {
@@ -11177,7 +11886,6 @@ async function runCodexAppCommand(args, opts = {}) {
11177
11886
  return 1;
11178
11887
  }
11179
11888
  const interrupted = recoverInterruptedCodexAppSession();
11180
- const configOnly = args.includes("--config");
11181
11889
  const trace = args.includes("--trace");
11182
11890
  const debugLogPath = getCodexProxyDebugLogPath();
11183
11891
  if (trace && !configOnly) {
@@ -11185,7 +11893,7 @@ async function runCodexAppCommand(args, opts = {}) {
11185
11893
  }
11186
11894
  const isTty = Boolean(process.stdin.isTTY);
11187
11895
  if (!configOnly) {
11188
- const sessionCheck = checkAppSessionLock(isTty);
11896
+ const sessionCheck = checkAppSessionLock(isTty || Boolean(opts.assumeYes));
11189
11897
  if (!sessionCheck.ok) {
11190
11898
  if (sessionCheck.reason === "non_tty") {
11191
11899
  console.error(pc10.red("relay-ai codex-app requires an interactive terminal."));
@@ -11243,7 +11951,7 @@ async function runCodexAppCommand(args, opts = {}) {
11243
11951
  compatible.find((lp) => lp.id === prefs.lastCodexProvider) ?? compatible[0]
11244
11952
  );
11245
11953
  let selectedModel = activeProvider.models.find((m) => m.id === prefs.lastCodexModel) ?? activeProvider.models[0];
11246
- if (!configOnly && opts.launchProvider && opts.launchModel) {
11954
+ if (codexAppUsesExplicitSelection(configOnly, opts.launchProvider, opts.launchModel)) {
11247
11955
  const bootSelection = resolveBootSelection(
11248
11956
  compatible,
11249
11957
  opts.launchProvider,
@@ -11314,7 +12022,7 @@ async function runCodexAppCommand(args, opts = {}) {
11314
12022
  try {
11315
12023
  const embeddedBinary = findEmbeddedCodexBinary();
11316
12024
  if (!embeddedBinary) throw new Error("Embedded ChatGPT/Codex runtime was not found; mixed Desktop mode is unavailable on this installation");
11317
- const version = execFileSync5(embeddedBinary, ["--version"], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
12025
+ const version = runCodexCommandSync(embeddedBinary, ["--version"]).stdout.trim();
11318
12026
  const mixedModels = await resolveCodexMixedModels({
11319
12027
  activeProvider,
11320
12028
  selectedModel,
@@ -11322,6 +12030,11 @@ async function runCodexAppCommand(args, opts = {}) {
11322
12030
  generalFavorites: favorites,
11323
12031
  subagentFavorites: prefs.codexSubagentModels ?? []
11324
12032
  });
12033
+ if (mixedModels.capacitySkipped.length > 0) {
12034
+ p12.log.warn(
12035
+ `Skipped ${mixedModels.capacitySkipped.length} favorite(s) because the mixed catalog is full: ` + mixedModels.capacitySkipped.map((f) => `${f.providerId}:${f.modelId}`).join(", ")
12036
+ );
12037
+ }
11325
12038
  assertConfiguredCodexSubagentsResolved(prefs.codexSubagentModels ?? [], mixedModels);
11326
12039
  const multiAgentV2Supported = mixedModels.subagents.length === 0 || supportsMultiAgentV2(embeddedBinary);
11327
12040
  if (!multiAgentV2Supported) {
@@ -11345,7 +12058,7 @@ Mixed Codex App mode is unavailable: ${err instanceof Error ? err.message : err}
11345
12058
  return 1;
11346
12059
  }
11347
12060
  }
11348
- if (!configOnly) {
12061
+ if (!configOnly && !opts.assumeYes) {
11349
12062
  const modelLabel = formatCodexModelLabel(selectedModel);
11350
12063
  const confirmed = await confirmCodexLaunch(
11351
12064
  activeProvider.name,
@@ -11361,7 +12074,7 @@ Mixed Codex App mode is unavailable: ${err instanceof Error ? err.message : err}
11361
12074
  let proxyHandle = null;
11362
12075
  let sessionActive = false;
11363
12076
  try {
11364
- const catalogPath = mixedPlan ? join12(getRelayAiCodexDir(), "app-models-mixed.json") : favoritesActive && resolvedFavorites.length > 0 ? getFavoritesAppCatalogPath() : getAppCatalogPath(route.providerId);
12077
+ const catalogPath = mixedPlan ? join13(getRelayAiCodexDir(), "app-models-mixed.json") : favoritesActive && resolvedFavorites.length > 0 ? getFavoritesAppCatalogPath() : getAppCatalogPath(route.providerId);
11365
12078
  const activeRoute = mixedPlan ? {
11366
12079
  tier: "proxy",
11367
12080
  modelId: mixedPlan.selectedSlug,
@@ -11424,10 +12137,12 @@ Mixed Codex App mode is unavailable: ${err instanceof Error ? err.message : err}
11424
12137
  return 0;
11425
12138
  }
11426
12139
  let proxyPort;
12140
+ const routeAuditPath = mixedPlan ? prepareCodexRouteAuditLog() : void 0;
11427
12141
  if (mixedPlan) {
11428
12142
  proxyHandle = await startCodexProxy(mixedPlan.relayRoutes, {
11429
12143
  requireAuth: false,
11430
12144
  debug: trace,
12145
+ routeAuditPath,
11431
12146
  mixedNative: {
11432
12147
  nativeModelIds: mixedPlan.nativeModelIds,
11433
12148
  subagentRouteModelId: mixedPlan.subagentRouteModelId,
@@ -11436,6 +12151,7 @@ Mixed Codex App mode is unavailable: ${err instanceof Error ? err.message : err}
11436
12151
  }
11437
12152
  });
11438
12153
  proxyPort = proxyHandle.port;
12154
+ p12.log.info(`Route audit (metadata only): ${routeAuditPath}`);
11439
12155
  } else if (favoritesActive && resolvedFavorites.length > 0) {
11440
12156
  const needsBackend = (r) => {
11441
12157
  const m = r.model;
@@ -11495,8 +12211,10 @@ Mixed Codex App mode is unavailable: ${err instanceof Error ? err.message : err}
11495
12211
  ...mixedPlan ? { proxyBaseUrl: `${mixedProxyBaseUrl(proxyPort, mixedPlan.capability)}/v1` } : {}
11496
12212
  };
11497
12213
  saveAppRestoreStateBeforePatch();
12214
+ sessionActive = true;
11498
12215
  const backupPath = backupConfigToml();
11499
12216
  applyAppConfigPatch(spec);
12217
+ await verifyCodexAppReadiness(spec);
11500
12218
  writeAppSessionLock({
11501
12219
  pid: process.pid,
11502
12220
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -11504,9 +12222,10 @@ Mixed Codex App mode is unavailable: ${err instanceof Error ? err.message : err}
11504
12222
  catalogPaths: [catalogPath],
11505
12223
  restoreStatePath: getAppRestoreStatePath(),
11506
12224
  backupPath,
11507
- proxyPort
12225
+ proxyPort,
12226
+ patchedConfigSha256: fileSha256(getCodexConfigPath()),
12227
+ ...backupPath ? { originalConfigSha256: fileSha256(backupPath) } : {}
11508
12228
  });
11509
- sessionActive = true;
11510
12229
  const prevRecent = prefs.recentModelsByProvider?.[activeProvider.id] ?? [];
11511
12230
  const updatedRecent = [selectedModel.id, ...prevRecent.filter((id) => id !== selectedModel.id)].slice(0, 3);
11512
12231
  savePreferences({
@@ -11517,10 +12236,11 @@ Mixed Codex App mode is unavailable: ${err instanceof Error ? err.message : err}
11517
12236
  logProxy(proxyPort);
11518
12237
  logActiveModel(modelLabel, selectedModel.id);
11519
12238
  try {
11520
- await launchOrRestartCodexApp();
12239
+ await launchOrRestartCodexApp(void 0, opts.assumeYes);
11521
12240
  } catch (err) {
11522
12241
  p12.log.warn(String(err instanceof Error ? err.message : err));
11523
12242
  p12.log.info(codexAppInstallHint());
12243
+ throw err;
11524
12244
  }
11525
12245
  printCodexAppSessionPanel({
11526
12246
  modelLabel,
@@ -11529,14 +12249,21 @@ Mixed Codex App mode is unavailable: ${err instanceof Error ? err.message : err}
11529
12249
  restoreCommand: "relay-ai codex-app --restore"
11530
12250
  });
11531
12251
  codexAppOutro(modelLabel);
11532
- await waitForShutdownWithConfirm();
12252
+ const shutdownSignal = await waitForShutdownWithConfirm(opts.assumeYes);
11533
12253
  if (trace) printTraceLog(debugLogPath);
11534
12254
  console.log("");
11535
12255
  if (sessionActive) {
11536
12256
  restoreCodexAppOverlay();
11537
12257
  sessionActive = false;
11538
12258
  }
11539
- await maybeCloseRunningCodexApp();
12259
+ if (unattendedShutdownClosesApp(Boolean(opts.assumeYes), shutdownSignal)) {
12260
+ if (isCodexAppRunning()) {
12261
+ p12.log.step("Stopping ChatGPT Desktop after unattended Relay shutdown...");
12262
+ quitCodexAppGracefully();
12263
+ }
12264
+ } else {
12265
+ await maybeCloseRunningCodexApp(opts.assumeYes);
12266
+ }
11540
12267
  return 0;
11541
12268
  } finally {
11542
12269
  proxyHandle?.close();
@@ -11555,38 +12282,38 @@ import pc11 from "picocolors";
11555
12282
  import * as p13 from "@clack/prompts";
11556
12283
 
11557
12284
  // src/claude-desktop/app-config.ts
11558
- import { existsSync as existsSync10, readFileSync as readFileSync5, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5 } from "fs";
12285
+ import { existsSync as existsSync10, readFileSync as readFileSync6, writeFileSync as writeFileSync5, mkdirSync as mkdirSync6 } from "fs";
11559
12286
  import { homedir as homedir9 } from "os";
11560
- import { join as join13, dirname as dirname3 } from "path";
11561
- import { randomUUID as randomUUID2 } from "crypto";
12287
+ import { join as join14, dirname as dirname3 } from "path";
12288
+ import { randomUUID as randomUUID3 } from "crypto";
11562
12289
  function getClaudeDesktopHome() {
11563
12290
  if (process.platform === "win32") {
11564
- return join13(process.env.LOCALAPPDATA || join13(homedir9(), "AppData", "Local"), "Claude-3p");
12291
+ return join14(process.env.LOCALAPPDATA || join14(homedir9(), "AppData", "Local"), "Claude-3p");
11565
12292
  }
11566
12293
  if (process.platform === "linux") {
11567
- return join13(process.env.XDG_CONFIG_HOME || join13(homedir9(), ".config"), "Claude-3p");
12294
+ return join14(process.env.XDG_CONFIG_HOME || join14(homedir9(), ".config"), "Claude-3p");
11568
12295
  }
11569
- return join13(homedir9(), "Library", "Application Support", "Claude-3p");
12296
+ return join14(homedir9(), "Library", "Application Support", "Claude-3p");
11570
12297
  }
11571
12298
  function getConfigLibraryPath() {
11572
- return join13(getClaudeDesktopHome(), "configLibrary");
12299
+ return join14(getClaudeDesktopHome(), "configLibrary");
11573
12300
  }
11574
12301
  function getMetaJsonPath() {
11575
- return join13(getConfigLibraryPath(), "_meta.json");
12302
+ return join14(getConfigLibraryPath(), "_meta.json");
11576
12303
  }
11577
12304
  function readMetaJson() {
11578
12305
  const metaPath = getMetaJsonPath();
11579
12306
  if (!existsSync10(metaPath)) return null;
11580
12307
  try {
11581
- return JSON.parse(readFileSync5(metaPath, "utf8"));
12308
+ return JSON.parse(readFileSync6(metaPath, "utf8"));
11582
12309
  } catch {
11583
12310
  return null;
11584
12311
  }
11585
12312
  }
11586
12313
  function writeMetaJson(meta) {
11587
12314
  const metaPath = getMetaJsonPath();
11588
- mkdirSync5(dirname3(metaPath), { recursive: true });
11589
- writeFileSync4(metaPath, `${JSON.stringify(meta, null, 2)}
12315
+ mkdirSync6(dirname3(metaPath), { recursive: true });
12316
+ writeFileSync5(metaPath, `${JSON.stringify(meta, null, 2)}
11590
12317
  `, "utf8");
11591
12318
  }
11592
12319
  function buildRelayAiConfig(proxyPort) {
@@ -11599,11 +12326,11 @@ function buildRelayAiConfig(proxyPort) {
11599
12326
  };
11600
12327
  }
11601
12328
  function writeRelayAiConfig(proxyPort) {
11602
- const uuid = randomUUID2();
11603
- const configPath = join13(getConfigLibraryPath(), `${uuid}.json`);
12329
+ const uuid = randomUUID3();
12330
+ const configPath = join14(getConfigLibraryPath(), `${uuid}.json`);
11604
12331
  const config = buildRelayAiConfig(proxyPort);
11605
- mkdirSync5(dirname3(configPath), { recursive: true });
11606
- writeFileSync4(configPath, `${JSON.stringify(config, null, 2)}
12332
+ mkdirSync6(dirname3(configPath), { recursive: true });
12333
+ writeFileSync5(configPath, `${JSON.stringify(config, null, 2)}
11607
12334
  `, "utf8");
11608
12335
  const meta = readMetaJson() || { appliedId: "", entries: [] };
11609
12336
  meta.appliedId = uuid;
@@ -11758,22 +12485,22 @@ async function buildClaudeAppServerCatalog(entries, providersById, trace) {
11758
12485
  import {
11759
12486
  copyFileSync as copyFileSync3,
11760
12487
  existsSync as existsSync11,
11761
- mkdirSync as mkdirSync6,
11762
- readFileSync as readFileSync6,
12488
+ mkdirSync as mkdirSync7,
12489
+ readFileSync as readFileSync7,
11763
12490
  renameSync as renameSync2,
11764
12491
  rmSync as rmSync5,
11765
12492
  unlinkSync as unlinkSync2,
11766
- writeFileSync as writeFileSync5
12493
+ writeFileSync as writeFileSync6
11767
12494
  } from "fs";
11768
- import { dirname as dirname4, join as join14 } from "path";
12495
+ import { dirname as dirname4, join as join15 } from "path";
11769
12496
  function getSessionLockPath2() {
11770
- return join14(getClaudeDesktopHome(), ".relay-ai.lock");
12497
+ return join15(getClaudeDesktopHome(), ".relay-ai.lock");
11771
12498
  }
11772
12499
  function inspectSessionLock() {
11773
12500
  const path3 = getSessionLockPath2();
11774
12501
  if (!existsSync11(path3)) return { status: "missing" };
11775
12502
  try {
11776
- const parsed = JSON.parse(readFileSync6(path3, "utf8"));
12503
+ const parsed = JSON.parse(readFileSync7(path3, "utf8"));
11777
12504
  if (typeof parsed.pid === "number" && typeof parsed.startedAt === "string" && typeof parsed.uuid === "string" && typeof parsed.proxyPort === "number") {
11778
12505
  return { status: "valid", lock: parsed };
11779
12506
  }
@@ -11784,9 +12511,9 @@ function inspectSessionLock() {
11784
12511
  function writeSessionLock2(lock) {
11785
12512
  const path3 = getSessionLockPath2();
11786
12513
  const tempPath = `${path3}.tmp.${process.pid}`;
11787
- mkdirSync6(dirname4(path3), { recursive: true });
12514
+ mkdirSync7(dirname4(path3), { recursive: true });
11788
12515
  try {
11789
- writeFileSync5(tempPath, `${JSON.stringify(lock, null, 2)}
12516
+ writeFileSync6(tempPath, `${JSON.stringify(lock, null, 2)}
11790
12517
  `, "utf8");
11791
12518
  renameSync2(tempPath, path3);
11792
12519
  } finally {
@@ -11821,7 +12548,7 @@ function restoreMetaJson() {
11821
12548
  }
11822
12549
  }
11823
12550
  function removeRelayAiConfig(uuid) {
11824
- const configPath = join14(getConfigLibraryPath(), `${uuid}.json`);
12551
+ const configPath = join15(getConfigLibraryPath(), `${uuid}.json`);
11825
12552
  if (existsSync11(configPath)) {
11826
12553
  try {
11827
12554
  rmSync5(configPath, { force: true });
@@ -12139,17 +12866,17 @@ ${pc11.bold("Claude Desktop 3P Mode Active")}`);
12139
12866
  }
12140
12867
 
12141
12868
  // src/ai-doc.ts
12142
- import { existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
12869
+ import { existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "fs";
12143
12870
  import { homedir as homedir10 } from "os";
12144
- import { join as join15 } from "path";
12871
+ import { join as join16 } from "path";
12145
12872
  var SKILL_DIR_NAME = "relay-ai-cli";
12146
12873
  var SKILL_INSTALL_DIRS = [
12147
- join15(getAppHome(), "skills"),
12148
- join15(homedir10(), ".claude", "skills"),
12149
- join15(homedir10(), ".agents", "skills"),
12150
- join15(homedir10(), ".codex", "skills"),
12151
- join15(homedir10(), ".cursor", "skills"),
12152
- join15(homedir10(), ".cursor", "skills-cursor")
12874
+ join16(getAppHome(), "skills"),
12875
+ join16(homedir10(), ".claude", "skills"),
12876
+ join16(homedir10(), ".agents", "skills"),
12877
+ join16(homedir10(), ".codex", "skills"),
12878
+ join16(homedir10(), ".cursor", "skills"),
12879
+ join16(homedir10(), ".cursor", "skills-cursor")
12153
12880
  ];
12154
12881
  function parseSkillVersion(content) {
12155
12882
  const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
@@ -12163,10 +12890,10 @@ function parseSkillVersion(content) {
12163
12890
  return null;
12164
12891
  }
12165
12892
  function readInstalledSkillVersion(skillDir) {
12166
- const skillPath = join15(skillDir, "SKILL.md");
12893
+ const skillPath = join16(skillDir, "SKILL.md");
12167
12894
  if (!existsSync12(skillPath)) return null;
12168
12895
  try {
12169
- const head = readFileSync7(skillPath, "utf-8").slice(0, 1024);
12896
+ const head = readFileSync8(skillPath, "utf-8").slice(0, 1024);
12170
12897
  return parseSkillVersion(head.includes("---", 4) ? head : `${head}
12171
12898
  ---
12172
12899
  `);
@@ -12176,8 +12903,8 @@ function readInstalledSkillVersion(skillDir) {
12176
12903
  }
12177
12904
  function skillInstallTargets() {
12178
12905
  return SKILL_INSTALL_DIRS.map((dir) => {
12179
- const skillDir = join15(dir, SKILL_DIR_NAME);
12180
- return { skillDir, skillPath: join15(skillDir, "SKILL.md") };
12906
+ const skillDir = join16(dir, SKILL_DIR_NAME);
12907
+ return { skillDir, skillPath: join16(skillDir, "SKILL.md") };
12181
12908
  });
12182
12909
  }
12183
12910
  function formatProviderModels(provider) {
@@ -12681,8 +13408,8 @@ function installAiDoc(opts = {}) {
12681
13408
  result.skipped.push(skillPath);
12682
13409
  continue;
12683
13410
  }
12684
- mkdirSync7(skillDir, { recursive: true });
12685
- writeFileSync6(skillPath, doc, "utf-8");
13411
+ mkdirSync8(skillDir, { recursive: true });
13412
+ writeFileSync7(skillPath, doc, "utf-8");
12686
13413
  if (previous) {
12687
13414
  result.updated.push({ path: skillPath, fromVersion: previous });
12688
13415
  } else {
@@ -12840,18 +13567,18 @@ function buildHttpProxyChildEnv(baseEnv, proxyUrl, caCertPath) {
12840
13567
  }
12841
13568
 
12842
13569
  // src/http-proxy/ca.ts
12843
- import { randomBytes as randomBytes2, randomUUID as randomUUID3 } from "crypto";
13570
+ import { randomBytes as randomBytes2, randomUUID as randomUUID4 } from "crypto";
12844
13571
  import {
12845
- chmodSync as chmodSync2,
13572
+ chmodSync as chmodSync3,
12846
13573
  existsSync as existsSync13,
12847
- mkdirSync as mkdirSync9,
12848
- readFileSync as readFileSync8,
13574
+ mkdirSync as mkdirSync10,
13575
+ readFileSync as readFileSync9,
12849
13576
  readdirSync as readdirSync3,
12850
13577
  rmSync as rmSync6,
12851
13578
  statSync as statSync3,
12852
- writeFileSync as writeFileSync8
13579
+ writeFileSync as writeFileSync9
12853
13580
  } from "fs";
12854
- import { dirname as dirname6, join as join17, resolve } from "path";
13581
+ import { dirname as dirname6, join as join18, resolve } from "path";
12855
13582
  import forge from "node-forge";
12856
13583
  var SESSION_ROOT = "http-proxy-sessions";
12857
13584
  var OWNER_FILE = "owner.pid";
@@ -12871,22 +13598,22 @@ function processIsRunning(pid) {
12871
13598
  }
12872
13599
  }
12873
13600
  function cleanupStaleHttpProxySessions(appHome = getAppHome()) {
12874
- const root = join17(appHome, SESSION_ROOT);
13601
+ const root = join18(appHome, SESSION_ROOT);
12875
13602
  if (!existsSync13(root)) return;
12876
13603
  const now = Date.now();
12877
13604
  for (const name of readdirSync3(root)) {
12878
- const sessionDir = join17(root, name);
13605
+ const sessionDir = join18(root, name);
12879
13606
  try {
12880
13607
  const stat = statSync3(sessionDir);
12881
13608
  if (!stat.isDirectory()) continue;
12882
- const ownerPath = join17(sessionDir, OWNER_FILE);
13609
+ const ownerPath = join18(sessionDir, OWNER_FILE);
12883
13610
  if (!existsSync13(ownerPath)) {
12884
13611
  if (now - stat.mtimeMs > MID_CREATION_GRACE_MS) {
12885
13612
  rmSync6(sessionDir, { recursive: true, force: true });
12886
13613
  }
12887
13614
  continue;
12888
13615
  }
12889
- const pid = Number(readFileSync8(ownerPath, "utf8").trim());
13616
+ const pid = Number(readFileSync9(ownerPath, "utf8").trim());
12890
13617
  if (!Number.isSafeInteger(pid) || pid <= 0) {
12891
13618
  const ownerStat = statSync3(ownerPath);
12892
13619
  const newestMtimeMs = Math.max(stat.mtimeMs, ownerStat.mtimeMs);
@@ -12902,13 +13629,13 @@ function cleanupStaleHttpProxySessions(appHome = getAppHome()) {
12902
13629
  }
12903
13630
  function createHttpProxyCertificates(appHome = getAppHome()) {
12904
13631
  cleanupStaleHttpProxySessions(appHome);
12905
- const root = join17(appHome, SESSION_ROOT);
12906
- mkdirSync9(root, { recursive: true, mode: 448 });
12907
- chmodSync2(root, 448);
12908
- const sessionDir = join17(root, randomUUID3());
12909
- mkdirSync9(sessionDir, { mode: 448 });
12910
- chmodSync2(sessionDir, 448);
12911
- writeFileSync8(join17(sessionDir, OWNER_FILE), `${process.pid}
13632
+ const root = join18(appHome, SESSION_ROOT);
13633
+ mkdirSync10(root, { recursive: true, mode: 448 });
13634
+ chmodSync3(root, 448);
13635
+ const sessionDir = join18(root, randomUUID4());
13636
+ mkdirSync10(sessionDir, { mode: 448 });
13637
+ chmodSync3(sessionDir, 448);
13638
+ writeFileSync9(join18(sessionDir, OWNER_FILE), `${process.pid}
12912
13639
  `, { mode: 384 });
12913
13640
  try {
12914
13641
  const caKeys = forge.pki.rsa.generateKeyPair(2048);
@@ -12943,9 +13670,9 @@ function createHttpProxyCertificates(appHome = getAppHome()) {
12943
13670
  ]);
12944
13671
  server.sign(caKeys.privateKey, forge.md.sha256.create());
12945
13672
  const caCert = forge.pki.certificateToPem(ca);
12946
- const caCertPath = join17(sessionDir, "relay-ai-ca.pem");
12947
- writeFileSync8(caCertPath, caCert, { encoding: "utf8", mode: 384 });
12948
- chmodSync2(caCertPath, 384);
13673
+ const caCertPath = join18(sessionDir, "relay-ai-ca.pem");
13674
+ writeFileSync9(caCertPath, caCert, { encoding: "utf8", mode: 384 });
13675
+ chmodSync3(caCertPath, 384);
12949
13676
  let cleaned = false;
12950
13677
  const cleanupOnExit = () => {
12951
13678
  if (cleaned) return;
@@ -12986,18 +13713,18 @@ function createHttpProxyCaBundle(relayCaCertPath, additionalCaCertPath) {
12986
13713
  if (resolve(additionalCaCertPath) === resolve(relayCaCertPath)) {
12987
13714
  return relayCaCertPath;
12988
13715
  }
12989
- const relayCa = readFileSync8(relayCaCertPath, "utf8").trimEnd();
12990
- const additionalCa = readFileSync8(additionalCaCertPath, "utf8").trim();
13716
+ const relayCa = readFileSync9(relayCaCertPath, "utf8").trimEnd();
13717
+ const additionalCa = readFileSync9(additionalCaCertPath, "utf8").trim();
12991
13718
  if (!additionalCa) return relayCaCertPath;
12992
- const combinedPath = join17(dirname6(relayCaCertPath), "combined-ca.pem");
12993
- writeFileSync8(
13719
+ const combinedPath = join18(dirname6(relayCaCertPath), "combined-ca.pem");
13720
+ writeFileSync9(
12994
13721
  combinedPath,
12995
13722
  `${relayCa}
12996
13723
  ${additionalCa}
12997
13724
  `,
12998
13725
  { encoding: "utf8", mode: 384 }
12999
13726
  );
13000
- chmodSync2(combinedPath, 384);
13727
+ chmodSync3(combinedPath, 384);
13001
13728
  return combinedPath;
13002
13729
  }
13003
13730
 
@@ -13761,6 +14488,10 @@ function parseArgs(args) {
13761
14488
  parsed2.vertex = true;
13762
14489
  continue;
13763
14490
  }
14491
+ if (arg === "--yes" || arg === "-y") {
14492
+ parsed2.assumeYes = true;
14493
+ continue;
14494
+ }
13764
14495
  if (arg === "--with-native") {
13765
14496
  if (parsed2.codexLaunchMode === "relay-only") parsed2.error = "--with-native and --relay-only cannot be used together";
13766
14497
  parsed2.codexLaunchMode = "mixed";
@@ -15008,7 +15739,7 @@ Options:
15008
15739
  --trace Write debug logs under ~/.relay-ai/logs/`);
15009
15740
  return 0;
15010
15741
  }
15011
- const { runUiCommand } = await import("./ui-command-OIY4243G.js");
15742
+ const { runUiCommand } = await import("./ui-command-NYYLGTCD.js");
15012
15743
  return runUiCommand({ trace: parsed.trace, serverMode: parsed.uiServerMode });
15013
15744
  }
15014
15745
  if (parsed.command === "models") {
@@ -15051,7 +15782,7 @@ Options:
15051
15782
  console.log(codexAppHelpText());
15052
15783
  return 0;
15053
15784
  }
15054
- return runCodexAppCommand(parsed.claudeArgs, { vertex: parsed.vertex, launchProvider: parsed.launchProvider, launchModel: parsed.launchModel, codexLaunchMode: parsed.codexLaunchMode });
15785
+ return runCodexAppCommand(parsed.claudeArgs, { vertex: parsed.vertex, launchProvider: parsed.launchProvider, launchModel: parsed.launchModel, codexLaunchMode: parsed.codexLaunchMode, assumeYes: parsed.assumeYes });
15055
15786
  }
15056
15787
  if (parsed.command === "claude-app") {
15057
15788
  if (parsed.showVersion) {
@@ -15073,7 +15804,7 @@ Options:
15073
15804
  console.log(codexHelpText());
15074
15805
  return 0;
15075
15806
  }
15076
- return runCodexCommand(parsed.claudeArgs, parsed.trace, {
15807
+ return runCodexCommand2(parsed.claudeArgs, parsed.trace, {
15077
15808
  launchProvider: parsed.launchProvider,
15078
15809
  launchModel: parsed.launchModel,
15079
15810
  vertex: parsed.vertex,