@jacobbd/relay-ai 0.4.4 → 0.4.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
@@ -14,6 +14,7 @@ import {
14
14
  addProviderFromTemplate,
15
15
  addZenRegistryStub,
16
16
  aliasModelId,
17
+ appendCodexBodyDump,
17
18
  authenticateProvider,
18
19
  buildAntigravityChildEnv,
19
20
  buildAppCatalogFile,
@@ -129,6 +130,7 @@ import {
129
130
  relayIntro,
130
131
  relayOutro,
131
132
  removeProviderFromRegistry,
133
+ resetCodexBodyDumpLog,
132
134
  resolveApiKey,
133
135
  resolveContextWindow,
134
136
  resolveLocalProviderApiKey,
@@ -164,7 +166,7 @@ import {
164
166
  validateCustomEndpointUrl,
165
167
  writeSecureLogLine,
166
168
  zenRegistryStub
167
- } from "./chunk-XCB2K4GI.js";
169
+ } from "./chunk-YM6G7BHE.js";
168
170
  import {
169
171
  filterTemplates,
170
172
  init_provider_templates,
@@ -1903,6 +1905,52 @@ function applyClaudeCodeOAuthIdentity(input, sdkParams) {
1903
1905
 
1904
1906
  // src/codex-responses-adapter.ts
1905
1907
  import { streamText, generateText, tool, jsonSchema } from "ai";
1908
+ function createCodexToolContext() {
1909
+ return { namespaceByFlatName: /* @__PURE__ */ new Map(), customToolNames: /* @__PURE__ */ new Set() };
1910
+ }
1911
+ var TOOL_SEARCH_NAME = "tool_search";
1912
+ function flatNamespaceName(namespace, name) {
1913
+ return `${namespace}__${name}`;
1914
+ }
1915
+ function ingestToolDefs(tools, ctx) {
1916
+ for (const t of tools ?? []) {
1917
+ if (!t || typeof t !== "object") continue;
1918
+ if (t.type === "namespace") {
1919
+ for (const sub of t.tools ?? []) {
1920
+ if (sub?.name) {
1921
+ ctx.namespaceByFlatName.set(flatNamespaceName(t.name, sub.name), {
1922
+ namespace: t.name,
1923
+ name: sub.name,
1924
+ parameters: sub.parameters
1925
+ });
1926
+ }
1927
+ }
1928
+ } else if (t.type === "custom" && t.name) {
1929
+ ctx.customToolNames.add(t.name);
1930
+ }
1931
+ }
1932
+ }
1933
+ function flattenNamespaceTools(ns) {
1934
+ return (ns.tools ?? []).filter((sub) => sub?.type === "function" && !!sub.name).map((sub) => ({ ...sub, name: flatNamespaceName(ns.name, sub.name) }));
1935
+ }
1936
+ function liftAdditionalToolsInput(input, tools) {
1937
+ let lifted = [];
1938
+ const keptInput = [];
1939
+ let changed = false;
1940
+ for (const item of input) {
1941
+ if (item && item.type === "additional_tools") {
1942
+ changed = true;
1943
+ if (Array.isArray(item.tools)) lifted = lifted.concat(item.tools);
1944
+ if (item.content !== void 0) {
1945
+ keptInput.push({ role: item.role ?? "developer", content: item.content });
1946
+ }
1947
+ continue;
1948
+ }
1949
+ keptInput.push(item);
1950
+ }
1951
+ if (!changed) return { input, tools };
1952
+ return { input: keptInput, tools: lifted.length ? [...tools, ...lifted] : tools };
1953
+ }
1906
1954
  function messageText(content) {
1907
1955
  if (typeof content === "string") return content;
1908
1956
  return (content ?? []).map((p15) => p15.type === "output_text" || p15.type === "input_text" || p15.type === "text" ? p15.text ?? "" : "").join("");
@@ -1927,12 +1975,40 @@ function annotateToolNamesFromCalls(items) {
1927
1975
  const nameByCallId = /* @__PURE__ */ new Map();
1928
1976
  for (const item of items) {
1929
1977
  if (item.type === "function_call") {
1978
+ const { rawId } = splitToolUseId(item.call_id);
1979
+ nameByCallId.set(rawId, item.namespace ? flatNamespaceName(item.namespace, item.name) : item.name);
1980
+ } else if (item.type === "tool_search_call") {
1981
+ const { rawId } = splitToolUseId(item.call_id);
1982
+ nameByCallId.set(rawId, TOOL_SEARCH_NAME);
1983
+ } else if (item.type === "custom_tool_call") {
1930
1984
  const { rawId } = splitToolUseId(item.call_id);
1931
1985
  nameByCallId.set(rawId, item.name);
1932
1986
  }
1933
1987
  }
1934
1988
  return nameByCallId;
1935
1989
  }
1990
+ function customToolInputFromArgs(name, args) {
1991
+ if (typeof args === "string") {
1992
+ const trimmed = args.trim();
1993
+ if (name === "apply_patch" && trimmed.startsWith("*** Begin Patch")) return args;
1994
+ try {
1995
+ return customToolInputFromArgs(name, JSON.parse(trimmed));
1996
+ } catch {
1997
+ return args;
1998
+ }
1999
+ }
2000
+ if (args && typeof args === "object") {
2001
+ const obj = args;
2002
+ if (Array.isArray(obj.command) && obj.command[0] === "apply_patch" && typeof obj.command[1] === "string") {
2003
+ return obj.command[1];
2004
+ }
2005
+ if (typeof obj.input === "string") return obj.input;
2006
+ for (const v of Object.values(obj)) {
2007
+ if (typeof v === "string") return v;
2008
+ }
2009
+ }
2010
+ return serializeToolResultContent(args);
2011
+ }
1936
2012
  function mergeConsecutiveMessages(messages) {
1937
2013
  if (messages.length <= 1) return messages;
1938
2014
  const out = [];
@@ -1965,16 +2041,18 @@ function makeReasoningOutputItem(id, text4) {
1965
2041
  summary: text4.trim() ? [{ type: "summary_text", text: text4 }] : []
1966
2042
  };
1967
2043
  }
1968
- function translateResponsesInput(input, instructions, npm) {
2044
+ function translateResponsesInput(input, instructions, npm, toolContext = createCodexToolContext()) {
1969
2045
  if (typeof input === "string") {
1970
2046
  return {
1971
2047
  system: instructions?.trim() || void 0,
1972
- messages: [{ role: "user", content: [{ type: "text", text: input }] }]
2048
+ messages: [{ role: "user", content: [{ type: "text", text: input }] }],
2049
+ deferredTools: []
1973
2050
  };
1974
2051
  }
1975
2052
  const { system, remaining } = extractDeveloperAndInstructions(input, instructions);
1976
2053
  const toolNames = annotateToolNamesFromCalls(remaining);
1977
2054
  const messages = [];
2055
+ const deferredTools = [];
1978
2056
  let pendingReasoning = "";
1979
2057
  for (const item of remaining) {
1980
2058
  if (item.type === "reasoning") {
@@ -1991,7 +2069,7 @@ function translateResponsesInput(input, instructions, npm) {
1991
2069
  const toolPart = {
1992
2070
  type: "tool-call",
1993
2071
  toolCallId: rawId,
1994
- toolName: item.name,
2072
+ toolName: item.namespace ? flatNamespaceName(item.namespace, item.name) : item.name,
1995
2073
  input: parseToolArguments(item.arguments)
1996
2074
  };
1997
2075
  if (thoughtSignature && npm === "@ai-sdk/google") {
@@ -2010,6 +2088,62 @@ function translateResponsesInput(input, instructions, npm) {
2010
2088
  output: { type: "text", value: serializeToolResultContent(item.output) }
2011
2089
  }]
2012
2090
  });
2091
+ } else if (item.type === "tool_search_call") {
2092
+ const { rawId } = splitToolUseId(item.call_id);
2093
+ messages.push({
2094
+ role: "assistant",
2095
+ content: [{
2096
+ type: "tool-call",
2097
+ toolCallId: rawId,
2098
+ toolName: TOOL_SEARCH_NAME,
2099
+ input: parseToolArguments(item.arguments)
2100
+ }]
2101
+ });
2102
+ } else if (item.type === "tool_search_output") {
2103
+ const { rawId } = splitToolUseId(item.call_id);
2104
+ const surfacedTools = item.tools ?? [];
2105
+ ingestToolDefs(surfacedTools, toolContext);
2106
+ for (const t of surfacedTools) {
2107
+ if (t.type === "namespace") deferredTools.push(...flattenNamespaceTools(t));
2108
+ else if (t.type === "function") deferredTools.push(t);
2109
+ }
2110
+ messages.push({
2111
+ role: "tool",
2112
+ content: [{
2113
+ type: "tool-result",
2114
+ toolCallId: rawId,
2115
+ toolName: TOOL_SEARCH_NAME,
2116
+ output: { type: "text", value: serializeToolResultContent(surfacedTools) }
2117
+ }]
2118
+ });
2119
+ } else if (item.type === "custom_tool_call") {
2120
+ const { rawId } = splitToolUseId(item.call_id);
2121
+ messages.push({
2122
+ role: "assistant",
2123
+ content: [{
2124
+ type: "tool-call",
2125
+ toolCallId: rawId,
2126
+ toolName: item.name,
2127
+ input: { input: typeof item.input === "string" ? item.input : serializeToolResultContent(item.input) }
2128
+ }]
2129
+ });
2130
+ } else if (item.type === "custom_tool_call_output") {
2131
+ const { rawId } = splitToolUseId(item.call_id);
2132
+ messages.push({
2133
+ role: "tool",
2134
+ content: [{
2135
+ type: "tool-result",
2136
+ toolCallId: rawId,
2137
+ toolName: toolNames.get(rawId) ?? "unknown",
2138
+ output: { type: "text", value: serializeToolResultContent(item.output) }
2139
+ }]
2140
+ });
2141
+ } else if (item.type === "compaction" || item.type === "context_compaction") {
2142
+ const summary = decodeCompactionContent(item.encrypted_content) ?? "";
2143
+ if (summary.trim()) {
2144
+ messages.push({ role: "user", content: [{ type: "text", text: `[Summary of earlier conversation]
2145
+ ${summary}` }] });
2146
+ }
2013
2147
  } else if ("role" in item) {
2014
2148
  const role = item.role === "assistant" ? "assistant" : "user";
2015
2149
  const text4 = messageText(item.content);
@@ -2018,46 +2152,83 @@ function translateResponsesInput(input, instructions, npm) {
2018
2152
  }
2019
2153
  return {
2020
2154
  system,
2021
- messages: ensureUserFirst(mergeConsecutiveMessages(messages))
2155
+ messages: ensureUserFirst(mergeConsecutiveMessages(messages)),
2156
+ deferredTools
2022
2157
  };
2023
2158
  }
2159
+ var TOOL_SEARCH_DESCRIPTION = "Search the available deferred Codex tools, plugin tools, MCP namespaces, and connectors by query. Use this when a needed tool is not already present in the current tool list. Returns matching tool definitions for a follow-up call.";
2160
+ var TOOL_SEARCH_PARAMETERS = {
2161
+ type: "object",
2162
+ properties: {
2163
+ query: { type: "string", description: "Search query describing the tool or capability needed." },
2164
+ limit: { type: "number", description: "Maximum number of matching tools to return. Defaults to 8." }
2165
+ },
2166
+ required: ["query"],
2167
+ additionalProperties: false
2168
+ };
2169
+ var CUSTOM_TOOL_INPUT_SCHEMA = {
2170
+ type: "object",
2171
+ properties: { input: { type: "string", description: "Freeform input for this custom tool (e.g. a patch body)." } },
2172
+ required: ["input"],
2173
+ additionalProperties: false
2174
+ };
2024
2175
  function translateResponsesTools(tools, options = {}) {
2025
2176
  if (!tools?.length) return void 0;
2026
2177
  const out = {};
2027
2178
  let toolCount = 0;
2028
- const addTool = (name, definition) => {
2179
+ const addTool = (name, description, parameters) => {
2029
2180
  if (options.maxTools !== void 0 && toolCount >= options.maxTools) return;
2030
2181
  out[name] = tool({
2031
- description: definition.description ?? "",
2032
- inputSchema: jsonSchema(definition.parameters ?? { type: "object", properties: {} })
2182
+ description: description ?? "",
2183
+ inputSchema: jsonSchema(parameters ?? { type: "object", properties: {} })
2033
2184
  });
2034
2185
  toolCount++;
2035
2186
  };
2036
2187
  for (const t of tools) {
2188
+ if (!t || typeof t !== "object") continue;
2037
2189
  if (t.type === "namespace") {
2038
2190
  for (const nested of t.tools ?? []) {
2039
2191
  if (nested.type !== "function" || !nested.name) continue;
2040
- addTool(`${t.name}__${nested.name}`, nested);
2192
+ addTool(flatNamespaceName(t.name, nested.name), nested.description, nested.parameters);
2041
2193
  }
2042
2194
  continue;
2043
2195
  }
2196
+ if (t.type === "custom") {
2197
+ if (!t.name) continue;
2198
+ addTool(t.name, t.description, CUSTOM_TOOL_INPUT_SCHEMA);
2199
+ continue;
2200
+ }
2201
+ if (t.type === "tool_search") {
2202
+ addTool(TOOL_SEARCH_NAME, TOOL_SEARCH_DESCRIPTION, TOOL_SEARCH_PARAMETERS);
2203
+ continue;
2204
+ }
2044
2205
  if (t.type !== "function" || !t.name) continue;
2045
- addTool(t.name, t);
2206
+ addTool(t.name, t.description, t.parameters);
2046
2207
  }
2047
2208
  return Object.keys(out).length ? out : void 0;
2048
2209
  }
2049
2210
  function translateResponsesRequest(body, npm, metadata, options = {}) {
2050
- const { system, messages } = translateResponsesInput(body.input, body.instructions, npm);
2211
+ const toolContext = createCodexToolContext();
2212
+ let effectiveTools = body.tools ?? [];
2213
+ let effectiveInput = body.input;
2214
+ if (Array.isArray(effectiveInput)) {
2215
+ const lifted = liftAdditionalToolsInput(effectiveInput, effectiveTools);
2216
+ effectiveInput = lifted.input;
2217
+ effectiveTools = lifted.tools;
2218
+ }
2219
+ ingestToolDefs(effectiveTools, toolContext);
2220
+ const { system, messages, deferredTools } = translateResponsesInput(effectiveInput, body.instructions, npm, toolContext);
2051
2221
  const effort = body.reasoning?.effort;
2052
2222
  const providerOptions = deepMergeProviderOptions(
2053
2223
  thinkingProviderOptions(npm),
2054
2224
  effortProviderOptions(npm, effort, metadata?.upstreamModelId ?? body.model, metadata)
2055
2225
  );
2056
- const tools = translateResponsesTools(body.tools, options);
2226
+ const tools = translateResponsesTools([...effectiveTools, ...deferredTools], options);
2057
2227
  return {
2058
2228
  system,
2059
2229
  messages,
2060
2230
  tools,
2231
+ toolContext,
2061
2232
  maxOutputTokens: body.max_output_tokens,
2062
2233
  temperature: body.temperature,
2063
2234
  providerOptions
@@ -2074,6 +2245,29 @@ function usageFromPart(part) {
2074
2245
  const output = part.totalUsage?.outputTokens ?? 0;
2075
2246
  return { input_tokens: input, output_tokens: output, total_tokens: input + output };
2076
2247
  }
2248
+ function resolveOutputKind(flatName, ctx) {
2249
+ if (!ctx) return { kind: "plain" };
2250
+ if (flatName === TOOL_SEARCH_NAME) return { kind: "tool_search" };
2251
+ if (ctx.customToolNames.has(flatName)) return { kind: "custom" };
2252
+ const ns = ctx.namespaceByFlatName.get(flatName);
2253
+ if (ns) return { kind: "namespace", namespace: ns.namespace, name: ns.name };
2254
+ return { kind: "plain" };
2255
+ }
2256
+ function buildFinalToolItem(kind, flatName, callId, itemId, argsStr) {
2257
+ switch (kind.kind) {
2258
+ case "namespace":
2259
+ return { type: "function_call", id: itemId, call_id: callId, namespace: kind.namespace, name: kind.name, arguments: argsStr, status: "completed" };
2260
+ case "tool_search": {
2261
+ const args = parseToolArguments(argsStr);
2262
+ if (typeof args.limit === "string" && /^-?\d+$/.test(args.limit)) args.limit = Number(args.limit);
2263
+ return { type: "tool_search_call", id: itemId, call_id: callId, execution: "client", arguments: args, status: "completed" };
2264
+ }
2265
+ case "custom":
2266
+ return { type: "custom_tool_call", id: itemId, call_id: callId, name: flatName, input: customToolInputFromArgs(flatName, parseToolArguments(argsStr)), status: "completed" };
2267
+ default:
2268
+ return { type: "function_call", id: itemId, call_id: callId, name: flatName, arguments: argsStr, status: "completed" };
2269
+ }
2270
+ }
2077
2271
  var PROGRESS_INTERVAL_MS = 3e3;
2078
2272
  var REPEAT_TAIL_CHARS = 200;
2079
2273
  var REPEAT_STREAK_LIMIT = 3;
@@ -2436,14 +2630,7 @@ async function writeResponsesStream(fullStream, modelId, write, onDone, onProgre
2436
2630
  output_index: tool3.outputIndex,
2437
2631
  arguments: tool3.args
2438
2632
  });
2439
- const fcItem = {
2440
- type: "function_call",
2441
- id: tool3.itemId,
2442
- call_id: tool3.callId,
2443
- name: tool3.name,
2444
- arguments: tool3.args,
2445
- status: "completed"
2446
- };
2633
+ const fcItem = buildFinalToolItem(resolveOutputKind(tool3.name, options?.toolContext), tool3.name, tool3.callId, tool3.itemId, tool3.args);
2447
2634
  emit("response.output_item.done", {
2448
2635
  type: "response.output_item.done",
2449
2636
  output_index: tool3.outputIndex,
@@ -2484,7 +2671,8 @@ async function streamResponsesResponse(model, params, modelId, write, onDone, on
2484
2671
  () => abort.abort(new Error(`no data received from provider for ${Math.round(idleTimeoutMs / 1e3)}s`)),
2485
2672
  idleTimeoutMs
2486
2673
  );
2487
- const result = streamText({ model, ...params, abortSignal: abort.signal, onError: () => {
2674
+ const { toolContext, ...sdkParams } = params;
2675
+ const result = streamText({ model, ...sdkParams, abortSignal: abort.signal, onError: () => {
2488
2676
  } });
2489
2677
  Promise.resolve(result.text).catch(() => {
2490
2678
  });
@@ -2513,11 +2701,13 @@ async function streamResponsesResponse(model, params, modelId, write, onDone, on
2513
2701
  }
2514
2702
  })();
2515
2703
  await writeResponsesStream(watchedStream, modelId, write, onDone, onProgress, {
2516
- onForceStop: (reason) => abort.abort(new Error(reason))
2704
+ onForceStop: (reason) => abort.abort(new Error(reason)),
2705
+ toolContext
2517
2706
  });
2518
2707
  }
2519
2708
  async function generateResponsesResponse(model, params, modelId) {
2520
- const r = await generateText({ model, ...params });
2709
+ const { toolContext, ...sdkParams } = params;
2710
+ const r = await generateText({ model, ...sdkParams });
2521
2711
  const createdAt = Math.floor(Date.now() / 1e3);
2522
2712
  const responseId = newResponseId();
2523
2713
  const output = [];
@@ -2535,14 +2725,8 @@ async function generateResponsesResponse(model, params, modelId) {
2535
2725
  }
2536
2726
  for (const tc of r.toolCalls) {
2537
2727
  const encodedId = encodeToolUseId(tc.toolCallId, grabRoundTripSignature(tc), false);
2538
- output.push({
2539
- type: "function_call",
2540
- id: tc.toolCallId,
2541
- call_id: encodedId,
2542
- name: tc.toolName,
2543
- arguments: JSON.stringify(tc.input ?? {}),
2544
- status: "completed"
2545
- });
2728
+ const argsStr = JSON.stringify(tc.input ?? {});
2729
+ output.push(buildFinalToolItem(resolveOutputKind(tc.toolName, toolContext), tc.toolName, encodedId, tc.toolCallId, argsStr));
2546
2730
  }
2547
2731
  if (output.length === 0) {
2548
2732
  output.push({ id: newItemId("msg"), type: "message", role: "assistant", status: "completed", content: [{ type: "output_text", text: "(conversation context was too large to summarize)" }] });
@@ -2563,6 +2747,70 @@ async function generateResponsesResponse(model, params, modelId) {
2563
2747
  }
2564
2748
  };
2565
2749
  }
2750
+ var COMPACTION_SUMMARY_INSTRUCTION = "You are performing a CONTEXT CHECKPOINT COMPACTION. Summarize the conversation so far into a concise but complete summary that preserves the user's goals, key decisions and facts, the current state of the work, and any pending or in-progress tasks. Output only the summary text.";
2751
+ function encodeCompactionContent(summary) {
2752
+ return Buffer.from(JSON.stringify({ v: 1, summary }), "utf8").toString("base64");
2753
+ }
2754
+ function decodeCompactionContent(encrypted) {
2755
+ if (!encrypted) return null;
2756
+ try {
2757
+ const obj = JSON.parse(Buffer.from(encrypted, "base64").toString("utf8"));
2758
+ return typeof obj?.summary === "string" ? obj.summary : null;
2759
+ } catch {
2760
+ return null;
2761
+ }
2762
+ }
2763
+ function makeCompactionItem(summary) {
2764
+ return { type: "compaction", id: newItemId("cmp"), encrypted_content: encodeCompactionContent(summary) };
2765
+ }
2766
+ function appendCompactionInstruction(params) {
2767
+ return {
2768
+ ...params,
2769
+ tools: void 0,
2770
+ messages: [
2771
+ ...params.messages,
2772
+ { role: "user", content: [{ type: "text", text: COMPACTION_SUMMARY_INSTRUCTION }] }
2773
+ ]
2774
+ };
2775
+ }
2776
+ function buildCompactionResponseBody(summary, modelId) {
2777
+ return {
2778
+ id: newResponseId(),
2779
+ object: "response",
2780
+ model: modelId,
2781
+ created_at: Math.floor(Date.now() / 1e3),
2782
+ status: "completed",
2783
+ output: [makeCompactionItem(summary)]
2784
+ };
2785
+ }
2786
+ function writeCompactionSse(summary, modelId, write) {
2787
+ const emit = (type, data) => write(sseChunk(type, data));
2788
+ const responseId = newResponseId();
2789
+ const createdAt = Math.floor(Date.now() / 1e3);
2790
+ const item = makeCompactionItem(summary);
2791
+ emit("response.created", {
2792
+ type: "response.created",
2793
+ response: { id: responseId, object: "response", model: modelId, created_at: createdAt, status: "in_progress", output: [] }
2794
+ });
2795
+ emit("response.output_item.added", { type: "response.output_item.added", output_index: 0, item });
2796
+ emit("response.output_item.done", { type: "response.output_item.done", output_index: 0, item });
2797
+ emit("response.completed", {
2798
+ type: "response.completed",
2799
+ response: { id: responseId, object: "response", model: modelId, created_at: createdAt, status: "completed", output: [item] }
2800
+ });
2801
+ }
2802
+ async function generateCompactionResponse(model, params, modelId) {
2803
+ const { toolContext: _toolContext, ...sdkParams } = params;
2804
+ void _toolContext;
2805
+ const r = await generateText({ model, ...sdkParams });
2806
+ return buildCompactionResponseBody((r.text ?? "").trim() || "(no summary produced)", modelId);
2807
+ }
2808
+ async function streamCompactionResponse(model, params, modelId, write) {
2809
+ const { toolContext: _toolContext, ...sdkParams } = params;
2810
+ void _toolContext;
2811
+ const r = await generateText({ model, ...sdkParams });
2812
+ writeCompactionSse((r.text ?? "").trim() || "(no summary produced)", modelId, write);
2813
+ }
2566
2814
  function responsesErrorBody(modelId, message, statusCode = 401) {
2567
2815
  return {
2568
2816
  id: newResponseId(),
@@ -2660,6 +2908,17 @@ function responsesRateLimitBody(modelId, message) {
2660
2908
  }
2661
2909
 
2662
2910
  // src/codex-proxy.ts
2911
+ function captureCompletedResponse(sseText) {
2912
+ if (!sseText.includes("response.completed")) return void 0;
2913
+ const dataLine = sseText.split("\n").find((l) => l.startsWith("data:"));
2914
+ if (!dataLine) return void 0;
2915
+ try {
2916
+ const obj = JSON.parse(dataLine.slice(5).trim());
2917
+ if (obj && obj.type === "response.completed") return obj.response;
2918
+ } catch {
2919
+ }
2920
+ return void 0;
2921
+ }
2663
2922
  function estimateCodexRequestChars(params) {
2664
2923
  let chars = (params.system ?? "").length;
2665
2924
  for (const msg of params.messages) {
@@ -2750,6 +3009,12 @@ function isLikelyCodexCompactionRequest(body) {
2750
3009
  }
2751
3010
  return false;
2752
3011
  }
3012
+ function isCodexV2CompactionRequest(body) {
3013
+ if (!Array.isArray(body.input)) return false;
3014
+ return body.input.some(
3015
+ (item) => item && typeof item === "object" && item.type === "compaction_trigger"
3016
+ );
3017
+ }
2753
3018
  var COMPACTION_MAX_OUTPUT_TOKENS = 4e3;
2754
3019
  function protectCodexCompactionParams(body, params, contextWindow) {
2755
3020
  if (!isLikelyCodexCompactionRequest(body)) {
@@ -2823,6 +3088,7 @@ async function startCodexProxy(routes, options = {}) {
2823
3088
  return new Promise((resolve, reject2) => {
2824
3089
  const log14 = debug ? makeTraceLogger(getCodexProxyDebugLogPath()) : () => {
2825
3090
  };
3091
+ if (debug) resetCodexBodyDumpLog();
2826
3092
  const onRejection = (reason) => {
2827
3093
  if (debug) log14(`unhandled-rejection: ${formatUpstreamError(reason)}`);
2828
3094
  };
@@ -2930,6 +3196,15 @@ async function startCodexProxy(routes, options = {}) {
2930
3196
  const tools = Array.isArray(body.tools) ? body.tools : [];
2931
3197
  const toolNames = tools.map((t) => t && typeof t === "object" && "name" in t ? t.name : "?").join(",");
2932
3198
  log14(`request: model=${String(body.model ?? "")} previous_response_id=${prevId ?? "(none)"} input_items=${inputItems} body_bytes=${rawBody.length} tools=[${toolNames || "none"}]`);
3199
+ appendCodexBodyDump({
3200
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
3201
+ transport: "http",
3202
+ direction: "request",
3203
+ model: String(body.model ?? ""),
3204
+ previous_response_id: prevId,
3205
+ tools: body.tools,
3206
+ input: body.input
3207
+ });
2933
3208
  const mcpTools = tools.filter((t) => t && typeof t === "object" && "name" in t && String(t.name).startsWith("mcp__"));
2934
3209
  for (const t of mcpTools) {
2935
3210
  const mt = t;
@@ -2980,6 +3255,11 @@ async function startCodexProxy(routes, options = {}) {
2980
3255
  log14(`context trim: model=${route.modelId} window=${route.contextWindow} kept=${params.messages.length}/${before} messages`);
2981
3256
  }
2982
3257
  }
3258
+ const v2Compaction = isCodexV2CompactionRequest(body);
3259
+ if (v2Compaction) {
3260
+ params = appendCompactionInstruction(params);
3261
+ if (debug) log14(`compaction v2: synthesizing single compaction item for model=${route.modelId}`);
3262
+ }
2983
3263
  if (debug) {
2984
3264
  const effort = body.reasoning?.effort;
2985
3265
  log14(`model=${route.modelId} effort=${effort ?? "(none)"} providerOptions=${JSON.stringify(params.providerOptions)}`);
@@ -2990,18 +3270,35 @@ async function startCodexProxy(routes, options = {}) {
2990
3270
  "Cache-Control": "no-cache",
2991
3271
  Connection: "keep-alive"
2992
3272
  });
2993
- const write = (chunk) => res.write(chunk);
2994
- try {
2995
- await streamResponsesResponse(languageModel, params, modelId, write, (summary) => {
2996
- if (debug) {
2997
- const failure = `${summary.aborted ? " aborted=yes" : ""}${summary.errorMessage ? ` error=${JSON.stringify(summary.errorMessage)}` : ""}`;
2998
- log14(`response done: model=${route.modelId} reasoningChars=${summary.reasoningChars} textChars=${summary.textChars} toolCalls=${summary.toolCallCount} toolNames=[${summary.toolNames.join(",")}] loopDetected=${summary.loopDetected ?? "no"} dsmlRecovered=${summary.dsmlToolCallsRecovered ?? 0}${failure} reasoningPreview=${JSON.stringify(summary.reasoningPreview)}`);
2999
- }
3000
- }, (progress) => {
3001
- if (debug) {
3002
- log14(`response progress: model=${route.modelId} elapsedMs=${progress.elapsedMs} reasoningChars=${progress.reasoningChars} textChars=${progress.textChars} toolCalls=${progress.toolCallCount} reasoningTail=${JSON.stringify(progress.reasoningTail)}`);
3273
+ const write = (chunk) => {
3274
+ res.write(chunk);
3275
+ if (debug) {
3276
+ const completed = captureCompletedResponse(chunk);
3277
+ if (completed) {
3278
+ appendCodexBodyDump({
3279
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
3280
+ transport: "http",
3281
+ direction: "response",
3282
+ model: route.modelId,
3283
+ response: completed
3284
+ });
3003
3285
  }
3004
- });
3286
+ }
3287
+ };
3288
+ try {
3289
+ if (v2Compaction) {
3290
+ await streamCompactionResponse(languageModel, params, modelId, write);
3291
+ } else
3292
+ await streamResponsesResponse(languageModel, params, modelId, write, (summary) => {
3293
+ if (debug) {
3294
+ const failure = `${summary.aborted ? " aborted=yes" : ""}${summary.errorMessage ? ` error=${JSON.stringify(summary.errorMessage)}` : ""}`;
3295
+ log14(`response done: model=${route.modelId} reasoningChars=${summary.reasoningChars} textChars=${summary.textChars} toolCalls=${summary.toolCallCount} toolNames=[${summary.toolNames.join(",")}] loopDetected=${summary.loopDetected ?? "no"} dsmlRecovered=${summary.dsmlToolCallsRecovered ?? 0}${failure} reasoningPreview=${JSON.stringify(summary.reasoningPreview)}`);
3296
+ }
3297
+ }, (progress) => {
3298
+ if (debug) {
3299
+ log14(`response progress: model=${route.modelId} elapsedMs=${progress.elapsedMs} reasoningChars=${progress.reasoningChars} textChars=${progress.textChars} toolCalls=${progress.toolCallCount} reasoningTail=${JSON.stringify(progress.reasoningTail)}`);
3300
+ }
3301
+ });
3005
3302
  } catch (err) {
3006
3303
  const msg = formatUpstreamError(err);
3007
3304
  const status = upstreamHttpStatus(err, msg);
@@ -3015,7 +3312,16 @@ async function startCodexProxy(routes, options = {}) {
3015
3312
  res.end();
3016
3313
  } else {
3017
3314
  try {
3018
- const response = await generateResponsesResponse(languageModel, params, modelId);
3315
+ const response = v2Compaction ? await generateCompactionResponse(languageModel, params, modelId) : await generateResponsesResponse(languageModel, params, modelId);
3316
+ if (debug) {
3317
+ appendCodexBodyDump({
3318
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
3319
+ transport: "http",
3320
+ direction: "response",
3321
+ model: route.modelId,
3322
+ response
3323
+ });
3324
+ }
3019
3325
  sendJson(res, 200, response);
3020
3326
  } catch (err) {
3021
3327
  const msg = formatUpstreamError(err);
@@ -3122,6 +3428,7 @@ Sec-WebSocket-Accept: ${wsAcceptKey(clientKey)}\r
3122
3428
  );
3123
3429
  let frameBuf = Buffer.alloc(0);
3124
3430
  let handled = false;
3431
+ let currentRequestModel = "";
3125
3432
  const closeSocket = () => {
3126
3433
  if (!socket.destroyed) {
3127
3434
  socket.write(wsCloseFrame());
@@ -3130,6 +3437,18 @@ Sec-WebSocket-Accept: ${wsAcceptKey(clientKey)}\r
3130
3437
  };
3131
3438
  const sendWsEvent = (sseChunk2) => {
3132
3439
  if (socket.destroyed) return;
3440
+ if (debug) {
3441
+ const completed = captureCompletedResponse(sseChunk2);
3442
+ if (completed) {
3443
+ appendCodexBodyDump({
3444
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
3445
+ transport: "ws",
3446
+ direction: "response",
3447
+ model: currentRequestModel,
3448
+ response: completed
3449
+ });
3450
+ }
3451
+ }
3133
3452
  for (const line of sseChunk2.split("\n")) {
3134
3453
  if (line.startsWith("data: ")) {
3135
3454
  socket.write(wsEncodeTextFrame(line.slice(6)));
@@ -3162,8 +3481,18 @@ data: ${JSON.stringify({ error: { message: "Invalid JSON", type: "invalid_reques
3162
3481
  const tools = Array.isArray(body.tools) ? body.tools : [];
3163
3482
  const toolNames = tools.map((t) => t && typeof t === "object" && "name" in t ? t.name : "?").join(",");
3164
3483
  log14(`WS request: model=${String(body.model ?? "")} previous_response_id=${prevId ?? "(none)"} input_items=${inputItems} body_bytes=${frame.text.length} tools=[${toolNames || "none"}]`);
3484
+ appendCodexBodyDump({
3485
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
3486
+ transport: "ws",
3487
+ direction: "request",
3488
+ model: String(body.model ?? ""),
3489
+ previous_response_id: prevId,
3490
+ tools: body.tools,
3491
+ input: body.input
3492
+ });
3165
3493
  }
3166
3494
  const modelId = String(body.model ?? "");
3495
+ currentRequestModel = modelId;
3167
3496
  let resolved = resolveModel(routes, models, modelId);
3168
3497
  if (!resolved) {
3169
3498
  const fb = routes[0];
@@ -3206,20 +3535,28 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}` } })}
3206
3535
  log14(`WS context trim: model=${route.modelId} window=${route.contextWindow} kept=${params.messages.length}/${before} messages tools=${params.tools ? Object.keys(params.tools).length : 0}`);
3207
3536
  }
3208
3537
  }
3538
+ const v2Compaction = isCodexV2CompactionRequest(body);
3539
+ if (v2Compaction) {
3540
+ params = appendCompactionInstruction(params);
3541
+ if (debug) log14(`WS compaction v2: synthesizing single compaction item for model=${route.modelId}`);
3542
+ }
3209
3543
  if (debug) {
3210
3544
  const effort = body.reasoning?.effort;
3211
3545
  log14(`WS model=${route.modelId} effort=${effort ?? "(none)"} providerOptions=${JSON.stringify(params.providerOptions)}`);
3212
3546
  }
3213
- await streamResponsesResponse(languageModel, params, modelId, sendWsEvent, (summary) => {
3214
- if (debug) {
3215
- const failure = `${summary.aborted ? " aborted=yes" : ""}${summary.errorMessage ? ` error=${JSON.stringify(summary.errorMessage)}` : ""}`;
3216
- log14(`WS response done: model=${route.modelId} reasoningChars=${summary.reasoningChars} textChars=${summary.textChars} toolCalls=${summary.toolCallCount} toolNames=[${summary.toolNames.join(",")}] loopDetected=${summary.loopDetected ?? "no"} dsmlRecovered=${summary.dsmlToolCallsRecovered ?? 0}${failure} reasoningPreview=${JSON.stringify(summary.reasoningPreview)}`);
3217
- }
3218
- }, (progress) => {
3219
- if (debug) {
3220
- log14(`WS response progress: model=${route.modelId} elapsedMs=${progress.elapsedMs} reasoningChars=${progress.reasoningChars} textChars=${progress.textChars} toolCalls=${progress.toolCallCount} reasoningTail=${JSON.stringify(progress.reasoningTail)}`);
3221
- }
3222
- });
3547
+ if (v2Compaction) {
3548
+ await streamCompactionResponse(languageModel, params, modelId, sendWsEvent);
3549
+ } else
3550
+ await streamResponsesResponse(languageModel, params, modelId, sendWsEvent, (summary) => {
3551
+ if (debug) {
3552
+ const failure = `${summary.aborted ? " aborted=yes" : ""}${summary.errorMessage ? ` error=${JSON.stringify(summary.errorMessage)}` : ""}`;
3553
+ log14(`WS response done: model=${route.modelId} reasoningChars=${summary.reasoningChars} textChars=${summary.textChars} toolCalls=${summary.toolCallCount} toolNames=[${summary.toolNames.join(",")}] loopDetected=${summary.loopDetected ?? "no"} dsmlRecovered=${summary.dsmlToolCallsRecovered ?? 0}${failure} reasoningPreview=${JSON.stringify(summary.reasoningPreview)}`);
3554
+ }
3555
+ }, (progress) => {
3556
+ if (debug) {
3557
+ log14(`WS response progress: model=${route.modelId} elapsedMs=${progress.elapsedMs} reasoningChars=${progress.reasoningChars} textChars=${progress.textChars} toolCalls=${progress.toolCallCount} reasoningTail=${JSON.stringify(progress.reasoningTail)}`);
3558
+ }
3559
+ });
3223
3560
  } catch (err) {
3224
3561
  const msg = formatUpstreamError(err);
3225
3562
  const status = upstreamHttpStatus(err, msg);
@@ -12359,7 +12696,7 @@ Error: ${parsed.error}
12359
12696
  console.log("Usage: relay-ai ui [--trace]\n\nOpen the settings UI in your browser.");
12360
12697
  return 0;
12361
12698
  }
12362
- const { runUiCommand } = await import("./ui-command-JG3BFVSL.js");
12699
+ const { runUiCommand } = await import("./ui-command-2HNQX7U3.js");
12363
12700
  return runUiCommand({ trace: parsed.trace });
12364
12701
  }
12365
12702
  if (parsed.command === "models") {