@jacobbd/relay-ai 0.9.3 → 0.9.5

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,
@@ -169,8 +168,9 @@ import {
169
168
  resolveRelayCatalogSlots,
170
169
  routableModelsForTarget,
171
170
  routeLookupIds,
171
+ runCodexCommand,
172
+ runCodexCommandSync,
172
173
  runServerCommand,
173
- sanitizeUnsupportedInlineData,
174
174
  savePreferences,
175
175
  saveProviderCredential,
176
176
  saveRegistry,
@@ -186,20 +186,18 @@ import {
186
186
  startProxy,
187
187
  startProxyCatalog,
188
188
  startServer,
189
- summarizeSdkRequestForTrace,
190
189
  supportsClaudeTransparentMode,
191
190
  supportsMultiAgentV2,
192
191
  supportsNativeOAuth,
193
192
  syntheticTemplate,
194
193
  thinkingProviderOptions,
195
194
  toggleProviderEnabled,
196
- translateRequest,
197
195
  updateCustomEndpointProvider,
198
196
  upstreamHttpStatus,
199
197
  validateCustomEndpointUrl,
200
198
  writeSecureLogLine,
201
199
  zenRegistryStub
202
- } from "./chunk-PVGAE7HA.js";
200
+ } from "./chunk-SCW2TYSG.js";
203
201
  import {
204
202
  filterTemplates,
205
203
  getTemplateById,
@@ -2235,7 +2233,6 @@ async function runProvidersCommand(args) {
2235
2233
  // src/codex.ts
2236
2234
  import pc7 from "picocolors";
2237
2235
  import * as p8 from "@clack/prompts";
2238
- import { execFileSync as execFileSync2 } from "child_process";
2239
2236
  import { join as join5 } from "path";
2240
2237
 
2241
2238
  // src/codex-proxy.ts
@@ -3041,18 +3038,18 @@ async function writeResponsesStream(fullStream, modelId, write, onDone, onProgre
3041
3038
  });
3042
3039
  outputItems.unshift(reasoningItem);
3043
3040
  }
3044
- for (const tool3 of toolStates) {
3045
- const normalizedArgs = normalizeCodexSubagentArguments(tool3.name, tool3.args);
3041
+ for (const tool4 of toolStates) {
3042
+ const normalizedArgs = normalizeCodexSubagentArguments(tool4.name, tool4.args);
3046
3043
  emit("response.function_call_arguments.done", {
3047
3044
  type: "response.function_call_arguments.done",
3048
- item_id: tool3.itemId,
3049
- output_index: tool3.outputIndex,
3045
+ item_id: tool4.itemId,
3046
+ output_index: tool4.outputIndex,
3050
3047
  arguments: normalizedArgs
3051
3048
  });
3052
- const fcItem = buildFinalToolItem(resolveOutputKind(tool3.name, options?.toolContext), tool3.name, tool3.callId, tool3.itemId, normalizedArgs);
3049
+ const fcItem = buildFinalToolItem(resolveOutputKind(tool4.name, options?.toolContext), tool4.name, tool4.callId, tool4.itemId, normalizedArgs);
3053
3050
  emit("response.output_item.done", {
3054
3051
  type: "response.output_item.done",
3055
- output_index: tool3.outputIndex,
3052
+ output_index: tool4.outputIndex,
3056
3053
  item: fcItem
3057
3054
  });
3058
3055
  outputItems.push(fcItem);
@@ -3180,7 +3177,7 @@ function decodeCompactionContent(encrypted) {
3180
3177
  if (!encrypted) return null;
3181
3178
  try {
3182
3179
  const obj = JSON.parse(Buffer.from(encrypted, "base64").toString("utf8"));
3183
- return typeof obj?.summary === "string" ? obj.summary : null;
3180
+ return obj?.v === 1 && typeof obj.summary === "string" ? obj.summary : null;
3184
3181
  } catch {
3185
3182
  return null;
3186
3183
  }
@@ -3464,6 +3461,41 @@ function allowlistedNativeHeaders(inboundHeaders) {
3464
3461
  }
3465
3462
  return out;
3466
3463
  }
3464
+ function prepareNativeCodexBody(body) {
3465
+ if (!Array.isArray(body.input)) return body;
3466
+ let changed = false;
3467
+ const input = body.input.map((item) => {
3468
+ if (!item || typeof item !== "object" || Array.isArray(item)) return item;
3469
+ const record = item;
3470
+ if (record.type !== "compaction" && record.type !== "context_compaction") return item;
3471
+ const summary = decodeCompactionContent(
3472
+ typeof record.encrypted_content === "string" ? record.encrypted_content : void 0
3473
+ );
3474
+ if (summary === null) return item;
3475
+ changed = true;
3476
+ return {
3477
+ type: "message",
3478
+ role: "user",
3479
+ content: [{
3480
+ type: "input_text",
3481
+ text: `[Summary of earlier conversation]
3482
+ ${summary}`
3483
+ }]
3484
+ };
3485
+ });
3486
+ return changed ? { ...body, input } : body;
3487
+ }
3488
+ function prepareNativeHttpBody(body) {
3489
+ const text5 = typeof body === "string" ? body : Buffer.from(body).toString("utf8");
3490
+ try {
3491
+ const parsed = JSON.parse(text5);
3492
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return body;
3493
+ const prepared = prepareNativeCodexBody(parsed);
3494
+ return prepared === parsed ? body : JSON.stringify(prepared);
3495
+ } catch {
3496
+ return body;
3497
+ }
3498
+ }
3467
3499
  async function forwardNativeCodexHttp(options) {
3468
3500
  const fetchImpl = options.fetchImpl ?? fetch;
3469
3501
  const headers = allowlistedNativeHeaders(options.inboundHeaders);
@@ -3471,7 +3503,7 @@ async function forwardNativeCodexHttp(options) {
3471
3503
  return fetchImpl(options.nativeUrl ?? NATIVE_CODEX_RESPONSES_URL, {
3472
3504
  method: "POST",
3473
3505
  headers,
3474
- body: options.body,
3506
+ body: prepareNativeHttpBody(options.body),
3475
3507
  signal: options.signal,
3476
3508
  redirect: "manual"
3477
3509
  });
@@ -3549,17 +3581,17 @@ var COLLABORATION_TOOL_NAMES = /* @__PURE__ */ new Set([
3549
3581
  ]);
3550
3582
  function isCollaborationTool(value) {
3551
3583
  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";
3584
+ const tool4 = value;
3585
+ const name = typeof tool4.name === "string" ? tool4.name : "";
3586
+ if (tool4.type === "namespace") return name === "collaboration" || name === "multi_agent_v1";
3555
3587
  return COLLABORATION_TOOL_NAMES.has(name) || name.startsWith("collaboration__") || name.startsWith("multi_agent_v1__");
3556
3588
  }
3557
3589
  function stripCollaborationToolList(value) {
3558
3590
  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;
3591
+ return value.filter((tool4) => !isCollaborationTool(tool4)).map((tool4) => {
3592
+ if (!tool4 || typeof tool4 !== "object") return tool4;
3593
+ const record = tool4;
3594
+ if (!Array.isArray(record.tools)) return tool4;
3563
3595
  return { ...record, tools: stripCollaborationToolList(record.tools) };
3564
3596
  });
3565
3597
  }
@@ -3881,6 +3913,18 @@ function codexRouteLookupIds(requestedModel) {
3881
3913
  return [...new Set(ids)];
3882
3914
  }
3883
3915
  function findCodexProxyRoute(routes, requestedModel) {
3916
+ const bareRequestedModel = parseCodexAppModelSlug(requestedModel);
3917
+ const providerSeparator = bareRequestedModel.indexOf("__");
3918
+ if (providerSeparator > 0) {
3919
+ const requestedProvider = bareRequestedModel.slice(0, providerSeparator);
3920
+ const requestedIds = codexRouteLookupIds(bareRequestedModel.slice(providerSeparator + 2));
3921
+ const providerRoute = routes.find((route) => {
3922
+ if (route.providerId !== requestedProvider) return false;
3923
+ const routeIds = codexRouteLookupIds(route.modelId);
3924
+ return requestedIds.some((id) => routeIds.includes(id));
3925
+ });
3926
+ if (providerRoute) return providerRoute;
3927
+ }
3884
3928
  const ids = codexRouteLookupIds(requestedModel);
3885
3929
  for (const id of ids) {
3886
3930
  const route = routes.find(
@@ -4509,10 +4553,14 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}`, type: "i
4509
4553
  return;
4510
4554
  }
4511
4555
  if (dispatch.kind === "native") {
4556
+ const nativeBody = prepareNativeCodexBody(body);
4557
+ if (debug && nativeBody !== body) {
4558
+ log14(`WS native history normalized: model=${modelId} converted Relay compaction for native verification`);
4559
+ }
4512
4560
  if (nativeActive && nativeUpstream) {
4513
4561
  if (nativeUpstream.readyState === WebSocket.OPEN) {
4514
4562
  if (debug) log14(`WS native forwarding next turn: model=${modelId}`);
4515
- nativeUpstream.send(JSON.stringify({ type: "response.create", ...body }));
4563
+ nativeUpstream.send(JSON.stringify({ type: "response.create", ...nativeBody }));
4516
4564
  } else if (debug) {
4517
4565
  log14(`WS native cannot forward next turn: upstream_state=${nativeUpstream.readyState}`);
4518
4566
  }
@@ -4566,7 +4614,7 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}`, type: "i
4566
4614
  nativeOpened = true;
4567
4615
  if (connectTimer) clearTimeout(connectTimer);
4568
4616
  if (debug) log14(`WS native upstream open: model=${modelId}`);
4569
- upstream?.send(JSON.stringify({ type: "response.create", ...body }));
4617
+ upstream?.send(JSON.stringify({ type: "response.create", ...nativeBody }));
4570
4618
  firstFrameTimer = setTimeout(() => closeBoth("Native Codex WebSocket response timed out"), 6e4);
4571
4619
  });
4572
4620
  upstream.once("unexpected-response", (_request, response) => {
@@ -4961,7 +5009,8 @@ function profileName() {
4961
5009
  }
4962
5010
 
4963
5011
  // src/codex/launch.ts
4964
- import { execFileSync, execSync as execSync2, spawn as spawn2 } from "child_process";
5012
+ import { execSync as execSync2 } from "child_process";
5013
+ import spawn2 from "cross-spawn";
4965
5014
  import { existsSync as existsSync4 } from "fs";
4966
5015
  import { homedir as homedir4 } from "os";
4967
5016
  import { join as join4 } from "path";
@@ -5023,12 +5072,7 @@ function selectCodexBinary(candidates, exists, canRun) {
5023
5072
  }
5024
5073
  function canRunCodexBinary(path3) {
5025
5074
  try {
5026
- execFileSync(path3, ["--version"], {
5027
- encoding: "utf8",
5028
- stdio: ["ignore", "pipe", "pipe"],
5029
- timeout: 5e3,
5030
- shell: isWindows2
5031
- });
5075
+ runCodexCommandSync(path3, ["--version"], { timeout: 5e3 });
5032
5076
  return true;
5033
5077
  } catch {
5034
5078
  return false;
@@ -5067,8 +5111,7 @@ function launchCodex(modelId, env, extraArgs) {
5067
5111
  const args = ["--profile", profileName(), "-m", modelId, ...ensureCodexSandboxArgs(extraArgs)];
5068
5112
  const child = spawn2(codexPath, args, {
5069
5113
  stdio: "inherit",
5070
- env,
5071
- shell: isWindows2
5114
+ env
5072
5115
  });
5073
5116
  const forward = (signal) => {
5074
5117
  child.kill(signal);
@@ -5503,9 +5546,6 @@ async function resolveCodexMixedModels(input) {
5503
5546
  }
5504
5547
 
5505
5548
  // src/codex/native-catalog.ts
5506
- import { execFile } from "child_process";
5507
- import { promisify } from "util";
5508
- var execFileAsync = promisify(execFile);
5509
5549
  function isCatalogModel(value) {
5510
5550
  if (!value || typeof value !== "object") return false;
5511
5551
  const model = value;
@@ -5522,7 +5562,7 @@ function validateNativeCodexCatalog(value) {
5522
5562
  }
5523
5563
  async function captureNativeCodexCatalog(options) {
5524
5564
  const run = options.run ?? (async (args) => {
5525
- const result = await execFileAsync(options.binaryPath, args, { encoding: "utf8", maxBuffer: 16 * 1024 * 1024 });
5565
+ const result = await runCodexCommand(options.binaryPath, args, { maxBuffer: 16 * 1024 * 1024 });
5526
5566
  return result.stdout;
5527
5567
  });
5528
5568
  const stdout = await run(options.bundled ? ["debug", "models", "--bundled"] : ["debug", "models"]);
@@ -5545,6 +5585,22 @@ async function captureNativeCodexCatalog(options) {
5545
5585
  }
5546
5586
 
5547
5587
  // src/codex/mixed-catalog.ts
5588
+ function externalInstructionValue(value) {
5589
+ if (typeof value === "string") {
5590
+ 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, "");
5591
+ }
5592
+ if (Array.isArray(value)) return value.map(externalInstructionValue);
5593
+ if (!value || typeof value !== "object") return value;
5594
+ return Object.fromEntries(
5595
+ Object.entries(value).map(([key, nested]) => [
5596
+ key,
5597
+ externalInstructionValue(nested)
5598
+ ])
5599
+ );
5600
+ }
5601
+ function externalModelMessages(templateMessages) {
5602
+ return externalInstructionValue(templateMessages);
5603
+ }
5548
5604
  function externalCatalogEntryFromTemplate(template, entry, priority, visibility, multiAgentVersion) {
5549
5605
  const resolvedModel = entry.resolved.model;
5550
5606
  const generated = catalogEntryFromModel(
@@ -5554,7 +5610,7 @@ function externalCatalogEntryFromTemplate(template, entry, priority, visibility,
5554
5610
  false,
5555
5611
  entry.slug
5556
5612
  );
5557
- return {
5613
+ const external = {
5558
5614
  ...template,
5559
5615
  ...generated,
5560
5616
  slug: entry.slug,
@@ -5562,6 +5618,9 @@ function externalCatalogEntryFromTemplate(template, entry, priority, visibility,
5562
5618
  visibility,
5563
5619
  multi_agent_version: multiAgentVersion
5564
5620
  };
5621
+ external.model_messages = externalModelMessages(template.model_messages);
5622
+ delete external.comp_hash;
5623
+ return external;
5565
5624
  }
5566
5625
  function composeMixedCodexCatalog(input) {
5567
5626
  const template = input.nativeModels.find((model) => model.slug === "gpt-5.5") ?? input.nativeModels.find((model) => model.visibility === "list") ?? input.nativeModels[0];
@@ -6136,7 +6195,7 @@ async function runCodexVertexLaunch(passthroughArgs, trace) {
6136
6195
  restoreCodexOverlay();
6137
6196
  }
6138
6197
  }
6139
- async function runCodexCommand(codexArgs, trace = false, launch = {}) {
6198
+ async function runCodexCommand2(codexArgs, trace = false, launch = {}) {
6140
6199
  if (codexArgs.includes("--help") || codexArgs.includes("-h")) {
6141
6200
  console.log(codexHelpText());
6142
6201
  return 0;
@@ -6334,7 +6393,7 @@ Error: ${launchPlan.error}
6334
6393
  let mixedPlan = null;
6335
6394
  if (mixedMode) {
6336
6395
  try {
6337
- const version = execFileSync2(codexPath, ["--version"], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
6396
+ const version = runCodexCommandSync(codexPath, ["--version"]).stdout.trim();
6338
6397
  const mixedModels = await resolveCodexMixedModels({
6339
6398
  activeProvider,
6340
6399
  selectedModel,
@@ -7738,6 +7797,267 @@ import * as p11 from "@clack/prompts";
7738
7797
  import http from "http";
7739
7798
  import { streamText as streamText3, generateText as generateText3 } from "ai";
7740
7799
 
7800
+ // src/antigravity/request-adapter.ts
7801
+ import { randomUUID as randomUUID2 } from "crypto";
7802
+ import { tool as tool3, jsonSchema as jsonSchema3 } from "ai";
7803
+ var UNSUPPORTED_VOICE_MESSAGE = "Voice transcription isn\u2019t supported by Relay AI yet. Please type your message. Your coding session remains active.";
7804
+ var OMITTED_VOICE_TEXT = "[Voice recording omitted because transcription is not supported by Relay AI.]";
7805
+ function isSupportedImage(part) {
7806
+ return part.inlineData?.mimeType.toLowerCase().startsWith("image/") ?? false;
7807
+ }
7808
+ function isUnsupportedInlineData(part) {
7809
+ return !!part.inlineData && !isSupportedImage(part);
7810
+ }
7811
+ function sanitizeUnsupportedInlineData(ccReq) {
7812
+ const contents = ccReq.request?.contents ?? [];
7813
+ let latestUserIndex = -1;
7814
+ for (let i = contents.length - 1; i >= 0; i--) {
7815
+ if (contents[i].role === "user") {
7816
+ latestUserIndex = i;
7817
+ break;
7818
+ }
7819
+ }
7820
+ let latestUserTurnHasUnsupportedMedia = false;
7821
+ const sanitizedContents = contents.map((message, index) => ({
7822
+ ...message,
7823
+ parts: message.parts.map((part) => {
7824
+ if (!isUnsupportedInlineData(part)) return part;
7825
+ if (index === latestUserIndex) latestUserTurnHasUnsupportedMedia = true;
7826
+ return { text: OMITTED_VOICE_TEXT };
7827
+ })
7828
+ }));
7829
+ return {
7830
+ request: {
7831
+ ...ccReq,
7832
+ request: {
7833
+ ...ccReq.request,
7834
+ contents: sanitizedContents
7835
+ }
7836
+ },
7837
+ latestUserTurnHasUnsupportedMedia
7838
+ };
7839
+ }
7840
+ function tracePartChars(part) {
7841
+ if (typeof part.text === "string") return part.text.length;
7842
+ if (part.type !== "tool-result") return void 0;
7843
+ const output = part.output;
7844
+ if (typeof output === "string") return output.length;
7845
+ if (output && typeof output === "object" && typeof output.value === "string") {
7846
+ return output.value.length;
7847
+ }
7848
+ try {
7849
+ return output === void 0 ? void 0 : JSON.stringify(output).length;
7850
+ } catch {
7851
+ return void 0;
7852
+ }
7853
+ }
7854
+ function summarizeSdkRequestForTrace(request2) {
7855
+ const messages = request2.messages.map((message) => {
7856
+ const content = message.content;
7857
+ if (typeof content === "string") {
7858
+ return { role: message.role, parts: [{ type: "text", chars: content.length }] };
7859
+ }
7860
+ const parts = Array.isArray(content) ? content.map((rawPart) => {
7861
+ const part = rawPart;
7862
+ const summary = {
7863
+ type: typeof part.type === "string" ? part.type : typeof rawPart
7864
+ };
7865
+ const chars = tracePartChars(part);
7866
+ if (chars !== void 0) summary.chars = chars;
7867
+ if (typeof part.toolName === "string") summary.toolName = part.toolName;
7868
+ if (typeof part.toolCallId === "string") summary.toolCallId = part.toolCallId;
7869
+ return summary;
7870
+ }) : [{ type: typeof content }];
7871
+ return { role: message.role, parts };
7872
+ });
7873
+ return {
7874
+ systemChars: request2.system?.length ?? 0,
7875
+ messages,
7876
+ toolNames: Object.keys(request2.tools ?? {}),
7877
+ ...request2.toolChoice ? { toolChoice: request2.toolChoice } : {}
7878
+ };
7879
+ }
7880
+ var JSON_SCHEMA_TYPES = /* @__PURE__ */ new Map([
7881
+ ["ARRAY", "array"],
7882
+ ["BOOLEAN", "boolean"],
7883
+ ["INTEGER", "integer"],
7884
+ ["NULL", "null"],
7885
+ ["NUMBER", "number"],
7886
+ ["OBJECT", "object"],
7887
+ ["STRING", "string"]
7888
+ ]);
7889
+ function expandTextWithThinking(text5) {
7890
+ if (!text5.includes("<thinking>")) {
7891
+ return [{ type: "text", text: text5 }];
7892
+ }
7893
+ const out = [];
7894
+ const tokens = text5.split(/<thinking>([\s\S]*?)<\/thinking>/);
7895
+ for (let i = 0; i < tokens.length; i++) {
7896
+ const token = tokens[i] ?? "";
7897
+ if (!token.trim()) continue;
7898
+ out.push({ type: i % 2 === 1 ? "reasoning" : "text", text: token });
7899
+ }
7900
+ return out.length > 0 ? out : [{ type: "text", text: text5 }];
7901
+ }
7902
+ function normalizeSchemaType(value) {
7903
+ if (typeof value === "string") {
7904
+ return JSON_SCHEMA_TYPES.get(value) ?? value;
7905
+ }
7906
+ if (Array.isArray(value)) {
7907
+ return value.map(normalizeSchemaType);
7908
+ }
7909
+ return value;
7910
+ }
7911
+ function normalizeJsonSchema(value) {
7912
+ if (Array.isArray(value)) {
7913
+ return value.map(normalizeJsonSchema);
7914
+ }
7915
+ if (!value || typeof value !== "object") {
7916
+ return value;
7917
+ }
7918
+ return Object.fromEntries(
7919
+ Object.entries(value).map(([key, child]) => [
7920
+ key,
7921
+ key === "type" ? normalizeSchemaType(child) : normalizeJsonSchema(child)
7922
+ ])
7923
+ );
7924
+ }
7925
+ function translateTools(ccTools, options = {}) {
7926
+ if (!ccTools?.length) return void 0;
7927
+ const tools = {};
7928
+ let toolCount = 0;
7929
+ for (const t of ccTools) {
7930
+ if (t.functionDeclarations) {
7931
+ for (const fd of t.functionDeclarations) {
7932
+ if (options.maxTools !== void 0 && toolCount >= options.maxTools) break;
7933
+ tools[fd.name] = tool3({
7934
+ description: fd.description || "",
7935
+ inputSchema: jsonSchema3(
7936
+ normalizeJsonSchema(fd.parameters || { type: "object", properties: {} })
7937
+ )
7938
+ });
7939
+ toolCount++;
7940
+ }
7941
+ }
7942
+ }
7943
+ return Object.keys(tools).length > 0 ? tools : void 0;
7944
+ }
7945
+ function translateRequest(ccReq, options = {}) {
7946
+ const systemInstructions = [];
7947
+ const sdkMessages = [];
7948
+ const nameToIdList = /* @__PURE__ */ new Map();
7949
+ const fallbackAssistantReasoning = [...options.fallbackAssistantReasoning ?? []];
7950
+ const request2 = ccReq.request || {};
7951
+ if (request2.systemInstruction?.parts) {
7952
+ for (const part of request2.systemInstruction.parts) {
7953
+ if (part.text) {
7954
+ systemInstructions.push(part.text);
7955
+ }
7956
+ }
7957
+ }
7958
+ const contents = request2.contents || [];
7959
+ for (const msg of contents) {
7960
+ const role = msg.role;
7961
+ if (role === "system") {
7962
+ for (const part of msg.parts) {
7963
+ if (part.text) {
7964
+ systemInstructions.push(part.text);
7965
+ }
7966
+ }
7967
+ continue;
7968
+ }
7969
+ const sdkRole = role === "model" ? "assistant" : "user";
7970
+ const hasFunctionCall = msg.parts.some((p15) => p15.functionCall);
7971
+ const hasAssistantReasoning = role === "model" && msg.parts.some((p15) => p15.thought || p15.text?.includes("<thinking>"));
7972
+ const hasComplexParts = msg.parts.some((p15) => p15.thought || p15.inlineData || p15.functionCall || p15.functionResponse);
7973
+ const singleText = msg.parts.length === 1 ? msg.parts[0]?.text : void 0;
7974
+ if (!hasComplexParts && singleText !== void 0 && !singleText.includes("<thinking>")) {
7975
+ sdkMessages.push({
7976
+ role: sdkRole,
7977
+ content: singleText
7978
+ });
7979
+ continue;
7980
+ }
7981
+ const contentParts = [];
7982
+ const toolResults = [];
7983
+ if (role === "model" && hasFunctionCall && !hasAssistantReasoning) {
7984
+ const fallback = fallbackAssistantReasoning.shift();
7985
+ if (fallback?.trim()) {
7986
+ contentParts.push({ type: "reasoning", text: fallback });
7987
+ }
7988
+ }
7989
+ for (const part of msg.parts) {
7990
+ if (part.text !== void 0) {
7991
+ if (part.thought) {
7992
+ contentParts.push({ type: "reasoning", text: part.text });
7993
+ } else {
7994
+ for (const piece of expandTextWithThinking(part.text)) {
7995
+ contentParts.push(piece);
7996
+ }
7997
+ }
7998
+ } else if (part.inlineData) {
7999
+ if (isSupportedImage(part)) {
8000
+ contentParts.push({
8001
+ type: "image",
8002
+ image: part.inlineData.data,
8003
+ mimeType: part.inlineData.mimeType
8004
+ });
8005
+ } else {
8006
+ contentParts.push({ type: "text", text: OMITTED_VOICE_TEXT });
8007
+ }
8008
+ } else if (part.functionCall) {
8009
+ const id = "call_" + randomUUID2().replace(/-/g, "");
8010
+ const name = part.functionCall.name;
8011
+ if (!nameToIdList.has(name)) nameToIdList.set(name, []);
8012
+ nameToIdList.get(name).push(id);
8013
+ contentParts.push({
8014
+ type: "tool-call",
8015
+ toolCallId: id,
8016
+ toolName: name,
8017
+ input: part.functionCall.args || {}
8018
+ });
8019
+ } else if (part.functionResponse) {
8020
+ const name = part.functionResponse.name;
8021
+ const idList = nameToIdList.get(name) || [];
8022
+ const id = idList.shift() || "call_" + randomUUID2().replace(/-/g, "");
8023
+ toolResults.push({
8024
+ type: "tool-result",
8025
+ toolCallId: id,
8026
+ toolName: name,
8027
+ output: { type: "text", value: serializeToolResultContent(part.functionResponse.response) }
8028
+ });
8029
+ }
8030
+ }
8031
+ if (toolResults.length > 0) {
8032
+ sdkMessages.push({
8033
+ role: "tool",
8034
+ content: toolResults
8035
+ });
8036
+ }
8037
+ if (contentParts.length > 0) {
8038
+ sdkMessages.push({
8039
+ role: sdkRole,
8040
+ content: contentParts
8041
+ });
8042
+ }
8043
+ }
8044
+ const system = systemInstructions.length > 0 ? systemInstructions.join("\n\n") : void 0;
8045
+ const tools = translateTools(request2.tools, options);
8046
+ let toolChoice;
8047
+ const mode = request2.toolConfig?.functionCallingConfig?.mode;
8048
+ if (mode === "ANY") {
8049
+ toolChoice = "required";
8050
+ } else if (mode === "AUTO" || tools) {
8051
+ toolChoice = "auto";
8052
+ }
8053
+ return {
8054
+ system,
8055
+ messages: sdkMessages,
8056
+ tools,
8057
+ toolChoice
8058
+ };
8059
+ }
8060
+
7741
8061
  // src/antigravity/response-adapter.ts
7742
8062
  function normalizeFunctionCallArgs(args) {
7743
8063
  const out = {};
@@ -9615,7 +9935,7 @@ async function resolveAntigravityLaunchRoutes(opts) {
9615
9935
  }
9616
9936
 
9617
9937
  // src/antigravity/launch-cli.ts
9618
- import { execFileSync as execFileSync3, execSync as execSync3 } from "child_process";
9938
+ import { execFileSync, execSync as execSync3 } from "child_process";
9619
9939
  import spawn4 from "cross-spawn";
9620
9940
  import { existsSync as existsSync6 } from "fs";
9621
9941
  import { homedir as homedir6 } from "os";
@@ -9653,7 +9973,7 @@ function readAntigravityCliVersion(binaryPath = findAntigravityCliBinary() ?? vo
9653
9973
  return { version: null, error: 'Antigravity CLI binary "agy" not found' };
9654
9974
  }
9655
9975
  try {
9656
- const raw = execFileSync3(binaryPath, ["--version"], {
9976
+ const raw = execFileSync(binaryPath, ["--version"], {
9657
9977
  encoding: "utf8",
9658
9978
  stdio: ["ignore", "pipe", "pipe"]
9659
9979
  }).trim();
@@ -9702,7 +10022,7 @@ function launchAntigravityCli(env, extraArgs) {
9702
10022
  }
9703
10023
 
9704
10024
  // src/antigravity/launch-ide.ts
9705
- import { execFileSync as execFileSync4, execSync as execSync4, spawn as spawn5 } from "child_process";
10025
+ import { execFileSync as execFileSync2, execSync as execSync4, spawn as spawn5 } from "child_process";
9706
10026
  import { existsSync as existsSync7 } from "fs";
9707
10027
  import { homedir as homedir7 } from "os";
9708
10028
  import { join as join8 } from "path";
@@ -9806,7 +10126,7 @@ function defaultProcessList() {
9806
10126
  const psArgs = process.platform === "linux" ? ["-eo", "pid=,args="] : ["-axo", "pid=,command="];
9807
10127
  if (process.platform !== "darwin" && process.platform !== "linux") return "";
9808
10128
  try {
9809
- return execFileSync4("ps", psArgs, {
10129
+ return execFileSync2("ps", psArgs, {
9810
10130
  encoding: "utf8",
9811
10131
  stdio: ["ignore", "pipe", "ignore"],
9812
10132
  maxBuffer: 1024 * 1024 * 4
@@ -9870,11 +10190,11 @@ function quitAntigravityIdeGracefully() {
9870
10190
  }
9871
10191
  if (process.platform !== "darwin") return;
9872
10192
  try {
9873
- execFileSync4("osascript", ["-e", 'tell application "Antigravity IDE" to quit'], {
10193
+ execFileSync2("osascript", ["-e", 'tell application "Antigravity IDE" to quit'], {
9874
10194
  stdio: ["ignore", "pipe", "pipe"]
9875
10195
  });
9876
10196
  } catch {
9877
- execFileSync4("osascript", ["-e", 'tell application id "com.google.antigravity-ide" to quit'], {
10197
+ execFileSync2("osascript", ["-e", 'tell application id "com.google.antigravity-ide" to quit'], {
9878
10198
  stdio: ["ignore", "pipe", "pipe"]
9879
10199
  });
9880
10200
  }
@@ -9890,11 +10210,11 @@ function quitAntigravityAppGracefully() {
9890
10210
  }
9891
10211
  if (process.platform !== "darwin") return;
9892
10212
  try {
9893
- execFileSync4("osascript", ["-e", 'tell application "Antigravity" to quit'], {
10213
+ execFileSync2("osascript", ["-e", 'tell application "Antigravity" to quit'], {
9894
10214
  stdio: ["ignore", "pipe", "pipe"]
9895
10215
  });
9896
10216
  } catch {
9897
- execFileSync4("osascript", ["-e", 'tell application id "com.google.antigravity" to quit'], {
10217
+ execFileSync2("osascript", ["-e", 'tell application id "com.google.antigravity" to quit'], {
9898
10218
  stdio: ["ignore", "pipe", "pipe"]
9899
10219
  });
9900
10220
  }
@@ -10405,7 +10725,6 @@ async function runAntigravityIdeCommand(childArgs, trace = false, boot) {
10405
10725
  // src/codex-app.ts
10406
10726
  import pc10 from "picocolors";
10407
10727
  import * as p12 from "@clack/prompts";
10408
- import { execFileSync as execFileSync5 } from "child_process";
10409
10728
  import { join as join12 } from "path";
10410
10729
 
10411
10730
  // src/codex/app-provider-routes.ts
@@ -10945,10 +11264,11 @@ function codexProxyRouteToCodexRoute(route, fallbackProviderId) {
10945
11264
  refreshToken: route.refreshToken
10946
11265
  };
10947
11266
  }
10948
- async function waitForShutdownWithConfirm() {
11267
+ async function waitForShutdownWithConfirm(assumeYes = false) {
10949
11268
  while (true) {
10950
11269
  const signal = await waitForShutdown2();
10951
11270
  if (signal !== "sigint") break;
11271
+ if (assumeYes) break;
10952
11272
  console.log("");
10953
11273
  const choice = await p12.select({
10954
11274
  message: "Close ChatGPT Desktop and restore your Codex config?",
@@ -10960,8 +11280,13 @@ async function waitForShutdownWithConfirm() {
10960
11280
  if (p12.isCancel(choice) || choice === "yes") break;
10961
11281
  }
10962
11282
  }
10963
- async function maybeCloseRunningCodexApp() {
11283
+ async function maybeCloseRunningCodexApp(assumeYes = false) {
10964
11284
  if (!isCodexAppRunning()) return;
11285
+ if (assumeYes) {
11286
+ p12.log.step("Stopping ChatGPT Desktop...");
11287
+ quitCodexAppGracefully();
11288
+ return;
11289
+ }
10965
11290
  const shouldClose = await p12.confirm({ message: "ChatGPT Desktop is still running. Close it?" });
10966
11291
  if (shouldClose && !p12.isCancel(shouldClose)) {
10967
11292
  p12.log.step("Stopping ChatGPT Desktop...");
@@ -10985,6 +11310,7 @@ ${pc10.bold("Options:")}
10985
11310
  --vertex Use Claude models through Google Vertex AI
10986
11311
  --with-native Load native Codex models beside Relay models for this launch
10987
11312
  --relay-only Keep the current Relay-only launch behavior
11313
+ --yes, -y Run a fully specified launch unattended (no launch/stop prompts)
10988
11314
  --restore Restore Codex config after an interrupted app session
10989
11315
  --config Preview the generated Codex app configuration without launching
10990
11316
  --trace Write proxy debug logs to ~/.relay-ai/logs/ and show errors on exit
@@ -11011,6 +11337,7 @@ ${pc10.bold("Preview (no writes):")}
11011
11337
  ${pc10.bold("Examples:")}
11012
11338
  relay-ai codex-app
11013
11339
  relay-ai codex-app --vertex
11340
+ relay-ai codex-app --provider antigravity --model gemini-3.1-pro-high --with-native --yes
11014
11341
  relay-ai codex-app --config
11015
11342
  relay-ai codex-app --restore
11016
11343
 
@@ -11170,6 +11497,13 @@ async function runCodexAppCommand(args, opts = {}) {
11170
11497
  console.log(result.message);
11171
11498
  return result.liveSession ? 1 : 0;
11172
11499
  }
11500
+ const configOnly = args.includes("--config");
11501
+ if (opts.assumeYes && !configOnly) {
11502
+ if (opts.vertex || !opts.launchProvider || !opts.launchModel || !opts.codexLaunchMode) {
11503
+ console.error(pc10.red("--yes requires --provider, --model, and either --with-native or --relay-only."));
11504
+ return 1;
11505
+ }
11506
+ }
11173
11507
  try {
11174
11508
  codexAppSupported();
11175
11509
  } catch (err) {
@@ -11177,7 +11511,6 @@ async function runCodexAppCommand(args, opts = {}) {
11177
11511
  return 1;
11178
11512
  }
11179
11513
  const interrupted = recoverInterruptedCodexAppSession();
11180
- const configOnly = args.includes("--config");
11181
11514
  const trace = args.includes("--trace");
11182
11515
  const debugLogPath = getCodexProxyDebugLogPath();
11183
11516
  if (trace && !configOnly) {
@@ -11185,7 +11518,7 @@ async function runCodexAppCommand(args, opts = {}) {
11185
11518
  }
11186
11519
  const isTty = Boolean(process.stdin.isTTY);
11187
11520
  if (!configOnly) {
11188
- const sessionCheck = checkAppSessionLock(isTty);
11521
+ const sessionCheck = checkAppSessionLock(isTty || Boolean(opts.assumeYes));
11189
11522
  if (!sessionCheck.ok) {
11190
11523
  if (sessionCheck.reason === "non_tty") {
11191
11524
  console.error(pc10.red("relay-ai codex-app requires an interactive terminal."));
@@ -11314,7 +11647,7 @@ async function runCodexAppCommand(args, opts = {}) {
11314
11647
  try {
11315
11648
  const embeddedBinary = findEmbeddedCodexBinary();
11316
11649
  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();
11650
+ const version = runCodexCommandSync(embeddedBinary, ["--version"]).stdout.trim();
11318
11651
  const mixedModels = await resolveCodexMixedModels({
11319
11652
  activeProvider,
11320
11653
  selectedModel,
@@ -11345,7 +11678,7 @@ Mixed Codex App mode is unavailable: ${err instanceof Error ? err.message : err}
11345
11678
  return 1;
11346
11679
  }
11347
11680
  }
11348
- if (!configOnly) {
11681
+ if (!configOnly && !opts.assumeYes) {
11349
11682
  const modelLabel = formatCodexModelLabel(selectedModel);
11350
11683
  const confirmed = await confirmCodexLaunch(
11351
11684
  activeProvider.name,
@@ -11517,7 +11850,7 @@ Mixed Codex App mode is unavailable: ${err instanceof Error ? err.message : err}
11517
11850
  logProxy(proxyPort);
11518
11851
  logActiveModel(modelLabel, selectedModel.id);
11519
11852
  try {
11520
- await launchOrRestartCodexApp();
11853
+ await launchOrRestartCodexApp(void 0, opts.assumeYes);
11521
11854
  } catch (err) {
11522
11855
  p12.log.warn(String(err instanceof Error ? err.message : err));
11523
11856
  p12.log.info(codexAppInstallHint());
@@ -11529,14 +11862,14 @@ Mixed Codex App mode is unavailable: ${err instanceof Error ? err.message : err}
11529
11862
  restoreCommand: "relay-ai codex-app --restore"
11530
11863
  });
11531
11864
  codexAppOutro(modelLabel);
11532
- await waitForShutdownWithConfirm();
11865
+ await waitForShutdownWithConfirm(opts.assumeYes);
11533
11866
  if (trace) printTraceLog(debugLogPath);
11534
11867
  console.log("");
11535
11868
  if (sessionActive) {
11536
11869
  restoreCodexAppOverlay();
11537
11870
  sessionActive = false;
11538
11871
  }
11539
- await maybeCloseRunningCodexApp();
11872
+ await maybeCloseRunningCodexApp(opts.assumeYes);
11540
11873
  return 0;
11541
11874
  } finally {
11542
11875
  proxyHandle?.close();
@@ -11558,7 +11891,7 @@ import * as p13 from "@clack/prompts";
11558
11891
  import { existsSync as existsSync10, readFileSync as readFileSync5, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5 } from "fs";
11559
11892
  import { homedir as homedir9 } from "os";
11560
11893
  import { join as join13, dirname as dirname3 } from "path";
11561
- import { randomUUID as randomUUID2 } from "crypto";
11894
+ import { randomUUID as randomUUID3 } from "crypto";
11562
11895
  function getClaudeDesktopHome() {
11563
11896
  if (process.platform === "win32") {
11564
11897
  return join13(process.env.LOCALAPPDATA || join13(homedir9(), "AppData", "Local"), "Claude-3p");
@@ -11599,7 +11932,7 @@ function buildRelayAiConfig(proxyPort) {
11599
11932
  };
11600
11933
  }
11601
11934
  function writeRelayAiConfig(proxyPort) {
11602
- const uuid = randomUUID2();
11935
+ const uuid = randomUUID3();
11603
11936
  const configPath = join13(getConfigLibraryPath(), `${uuid}.json`);
11604
11937
  const config = buildRelayAiConfig(proxyPort);
11605
11938
  mkdirSync5(dirname3(configPath), { recursive: true });
@@ -12840,7 +13173,7 @@ function buildHttpProxyChildEnv(baseEnv, proxyUrl, caCertPath) {
12840
13173
  }
12841
13174
 
12842
13175
  // src/http-proxy/ca.ts
12843
- import { randomBytes as randomBytes2, randomUUID as randomUUID3 } from "crypto";
13176
+ import { randomBytes as randomBytes2, randomUUID as randomUUID4 } from "crypto";
12844
13177
  import {
12845
13178
  chmodSync as chmodSync2,
12846
13179
  existsSync as existsSync13,
@@ -12905,7 +13238,7 @@ function createHttpProxyCertificates(appHome = getAppHome()) {
12905
13238
  const root = join17(appHome, SESSION_ROOT);
12906
13239
  mkdirSync9(root, { recursive: true, mode: 448 });
12907
13240
  chmodSync2(root, 448);
12908
- const sessionDir = join17(root, randomUUID3());
13241
+ const sessionDir = join17(root, randomUUID4());
12909
13242
  mkdirSync9(sessionDir, { mode: 448 });
12910
13243
  chmodSync2(sessionDir, 448);
12911
13244
  writeFileSync8(join17(sessionDir, OWNER_FILE), `${process.pid}
@@ -13761,6 +14094,10 @@ function parseArgs(args) {
13761
14094
  parsed2.vertex = true;
13762
14095
  continue;
13763
14096
  }
14097
+ if (arg === "--yes" || arg === "-y") {
14098
+ parsed2.assumeYes = true;
14099
+ continue;
14100
+ }
13764
14101
  if (arg === "--with-native") {
13765
14102
  if (parsed2.codexLaunchMode === "relay-only") parsed2.error = "--with-native and --relay-only cannot be used together";
13766
14103
  parsed2.codexLaunchMode = "mixed";
@@ -15008,7 +15345,7 @@ Options:
15008
15345
  --trace Write debug logs under ~/.relay-ai/logs/`);
15009
15346
  return 0;
15010
15347
  }
15011
- const { runUiCommand } = await import("./ui-command-JQPEZAMN.js");
15348
+ const { runUiCommand } = await import("./ui-command-Q6LBKVM3.js");
15012
15349
  return runUiCommand({ trace: parsed.trace, serverMode: parsed.uiServerMode });
15013
15350
  }
15014
15351
  if (parsed.command === "models") {
@@ -15051,7 +15388,7 @@ Options:
15051
15388
  console.log(codexAppHelpText());
15052
15389
  return 0;
15053
15390
  }
15054
- return runCodexAppCommand(parsed.claudeArgs, { vertex: parsed.vertex, launchProvider: parsed.launchProvider, launchModel: parsed.launchModel, codexLaunchMode: parsed.codexLaunchMode });
15391
+ return runCodexAppCommand(parsed.claudeArgs, { vertex: parsed.vertex, launchProvider: parsed.launchProvider, launchModel: parsed.launchModel, codexLaunchMode: parsed.codexLaunchMode, assumeYes: parsed.assumeYes });
15055
15392
  }
15056
15393
  if (parsed.command === "claude-app") {
15057
15394
  if (parsed.showVersion) {
@@ -15073,7 +15410,7 @@ Options:
15073
15410
  console.log(codexHelpText());
15074
15411
  return 0;
15075
15412
  }
15076
- return runCodexCommand(parsed.claudeArgs, parsed.trace, {
15413
+ return runCodexCommand2(parsed.claudeArgs, parsed.trace, {
15077
15414
  launchProvider: parsed.launchProvider,
15078
15415
  launchModel: parsed.launchModel,
15079
15416
  vertex: parsed.vertex,