@jacobbd/relay-ai 0.4.3 → 0.4.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
@@ -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,
@@ -25,6 +26,7 @@ import {
25
26
  buildVertexRuntimeConfig,
26
27
  cachedModelToLocal,
27
28
  catalogEntryFromModel,
29
+ checkForUpdates,
28
30
  claudeAppSupported,
29
31
  claudeCodeClientModelId,
30
32
  codexAppInstallHint,
@@ -57,6 +59,7 @@ import {
57
59
  fmtUrl,
58
60
  formatCodexModelLabel,
59
61
  formatRegistryAuthLabel,
62
+ formatUpdateNotification,
60
63
  formatUpstreamError,
61
64
  getAppHome,
62
65
  getAppPathOverride,
@@ -127,6 +130,7 @@ import {
127
130
  relayIntro,
128
131
  relayOutro,
129
132
  removeProviderFromRegistry,
133
+ resetCodexBodyDumpLog,
130
134
  resolveApiKey,
131
135
  resolveContextWindow,
132
136
  resolveLocalProviderApiKey,
@@ -162,7 +166,7 @@ import {
162
166
  validateCustomEndpointUrl,
163
167
  writeSecureLogLine,
164
168
  zenRegistryStub
165
- } from "./chunk-VSXPAZX4.js";
169
+ } from "./chunk-UTPRIJFE.js";
166
170
  import {
167
171
  filterTemplates,
168
172
  init_provider_templates,
@@ -1901,6 +1905,52 @@ function applyClaudeCodeOAuthIdentity(input, sdkParams) {
1901
1905
 
1902
1906
  // src/codex-responses-adapter.ts
1903
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
+ }
1904
1954
  function messageText(content) {
1905
1955
  if (typeof content === "string") return content;
1906
1956
  return (content ?? []).map((p15) => p15.type === "output_text" || p15.type === "input_text" || p15.type === "text" ? p15.text ?? "" : "").join("");
@@ -1925,12 +1975,40 @@ function annotateToolNamesFromCalls(items) {
1925
1975
  const nameByCallId = /* @__PURE__ */ new Map();
1926
1976
  for (const item of items) {
1927
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") {
1928
1984
  const { rawId } = splitToolUseId(item.call_id);
1929
1985
  nameByCallId.set(rawId, item.name);
1930
1986
  }
1931
1987
  }
1932
1988
  return nameByCallId;
1933
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
+ }
1934
2012
  function mergeConsecutiveMessages(messages) {
1935
2013
  if (messages.length <= 1) return messages;
1936
2014
  const out = [];
@@ -1963,16 +2041,18 @@ function makeReasoningOutputItem(id, text4) {
1963
2041
  summary: text4.trim() ? [{ type: "summary_text", text: text4 }] : []
1964
2042
  };
1965
2043
  }
1966
- function translateResponsesInput(input, instructions, npm) {
2044
+ function translateResponsesInput(input, instructions, npm, toolContext = createCodexToolContext()) {
1967
2045
  if (typeof input === "string") {
1968
2046
  return {
1969
2047
  system: instructions?.trim() || void 0,
1970
- messages: [{ role: "user", content: [{ type: "text", text: input }] }]
2048
+ messages: [{ role: "user", content: [{ type: "text", text: input }] }],
2049
+ deferredTools: []
1971
2050
  };
1972
2051
  }
1973
2052
  const { system, remaining } = extractDeveloperAndInstructions(input, instructions);
1974
2053
  const toolNames = annotateToolNamesFromCalls(remaining);
1975
2054
  const messages = [];
2055
+ const deferredTools = [];
1976
2056
  let pendingReasoning = "";
1977
2057
  for (const item of remaining) {
1978
2058
  if (item.type === "reasoning") {
@@ -1989,7 +2069,7 @@ function translateResponsesInput(input, instructions, npm) {
1989
2069
  const toolPart = {
1990
2070
  type: "tool-call",
1991
2071
  toolCallId: rawId,
1992
- toolName: item.name,
2072
+ toolName: item.namespace ? flatNamespaceName(item.namespace, item.name) : item.name,
1993
2073
  input: parseToolArguments(item.arguments)
1994
2074
  };
1995
2075
  if (thoughtSignature && npm === "@ai-sdk/google") {
@@ -2008,6 +2088,62 @@ function translateResponsesInput(input, instructions, npm) {
2008
2088
  output: { type: "text", value: serializeToolResultContent(item.output) }
2009
2089
  }]
2010
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
+ }
2011
2147
  } else if ("role" in item) {
2012
2148
  const role = item.role === "assistant" ? "assistant" : "user";
2013
2149
  const text4 = messageText(item.content);
@@ -2016,46 +2152,83 @@ function translateResponsesInput(input, instructions, npm) {
2016
2152
  }
2017
2153
  return {
2018
2154
  system,
2019
- messages: ensureUserFirst(mergeConsecutiveMessages(messages))
2155
+ messages: ensureUserFirst(mergeConsecutiveMessages(messages)),
2156
+ deferredTools
2020
2157
  };
2021
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
+ };
2022
2175
  function translateResponsesTools(tools, options = {}) {
2023
2176
  if (!tools?.length) return void 0;
2024
2177
  const out = {};
2025
2178
  let toolCount = 0;
2026
- const addTool = (name, definition) => {
2179
+ const addTool = (name, description, parameters) => {
2027
2180
  if (options.maxTools !== void 0 && toolCount >= options.maxTools) return;
2028
2181
  out[name] = tool({
2029
- description: definition.description ?? "",
2030
- inputSchema: jsonSchema(definition.parameters ?? { type: "object", properties: {} })
2182
+ description: description ?? "",
2183
+ inputSchema: jsonSchema(parameters ?? { type: "object", properties: {} })
2031
2184
  });
2032
2185
  toolCount++;
2033
2186
  };
2034
2187
  for (const t of tools) {
2188
+ if (!t || typeof t !== "object") continue;
2035
2189
  if (t.type === "namespace") {
2036
2190
  for (const nested of t.tools ?? []) {
2037
2191
  if (nested.type !== "function" || !nested.name) continue;
2038
- addTool(`${t.name}__${nested.name}`, nested);
2192
+ addTool(flatNamespaceName(t.name, nested.name), nested.description, nested.parameters);
2039
2193
  }
2040
2194
  continue;
2041
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
+ }
2042
2205
  if (t.type !== "function" || !t.name) continue;
2043
- addTool(t.name, t);
2206
+ addTool(t.name, t.description, t.parameters);
2044
2207
  }
2045
2208
  return Object.keys(out).length ? out : void 0;
2046
2209
  }
2047
2210
  function translateResponsesRequest(body, npm, metadata, options = {}) {
2048
- 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);
2049
2221
  const effort = body.reasoning?.effort;
2050
2222
  const providerOptions = deepMergeProviderOptions(
2051
2223
  thinkingProviderOptions(npm),
2052
2224
  effortProviderOptions(npm, effort, metadata?.upstreamModelId ?? body.model, metadata)
2053
2225
  );
2054
- const tools = translateResponsesTools(body.tools, options);
2226
+ const tools = translateResponsesTools([...effectiveTools, ...deferredTools], options);
2055
2227
  return {
2056
2228
  system,
2057
2229
  messages,
2058
2230
  tools,
2231
+ toolContext,
2059
2232
  maxOutputTokens: body.max_output_tokens,
2060
2233
  temperature: body.temperature,
2061
2234
  providerOptions
@@ -2072,6 +2245,29 @@ function usageFromPart(part) {
2072
2245
  const output = part.totalUsage?.outputTokens ?? 0;
2073
2246
  return { input_tokens: input, output_tokens: output, total_tokens: input + output };
2074
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
+ }
2075
2271
  var PROGRESS_INTERVAL_MS = 3e3;
2076
2272
  var REPEAT_TAIL_CHARS = 200;
2077
2273
  var REPEAT_STREAK_LIMIT = 3;
@@ -2434,14 +2630,7 @@ async function writeResponsesStream(fullStream, modelId, write, onDone, onProgre
2434
2630
  output_index: tool3.outputIndex,
2435
2631
  arguments: tool3.args
2436
2632
  });
2437
- const fcItem = {
2438
- type: "function_call",
2439
- id: tool3.itemId,
2440
- call_id: tool3.callId,
2441
- name: tool3.name,
2442
- arguments: tool3.args,
2443
- status: "completed"
2444
- };
2633
+ const fcItem = buildFinalToolItem(resolveOutputKind(tool3.name, options?.toolContext), tool3.name, tool3.callId, tool3.itemId, tool3.args);
2445
2634
  emit("response.output_item.done", {
2446
2635
  type: "response.output_item.done",
2447
2636
  output_index: tool3.outputIndex,
@@ -2482,7 +2671,8 @@ async function streamResponsesResponse(model, params, modelId, write, onDone, on
2482
2671
  () => abort.abort(new Error(`no data received from provider for ${Math.round(idleTimeoutMs / 1e3)}s`)),
2483
2672
  idleTimeoutMs
2484
2673
  );
2485
- const result = streamText({ model, ...params, abortSignal: abort.signal, onError: () => {
2674
+ const { toolContext, ...sdkParams } = params;
2675
+ const result = streamText({ model, ...sdkParams, abortSignal: abort.signal, onError: () => {
2486
2676
  } });
2487
2677
  Promise.resolve(result.text).catch(() => {
2488
2678
  });
@@ -2511,11 +2701,13 @@ async function streamResponsesResponse(model, params, modelId, write, onDone, on
2511
2701
  }
2512
2702
  })();
2513
2703
  await writeResponsesStream(watchedStream, modelId, write, onDone, onProgress, {
2514
- onForceStop: (reason) => abort.abort(new Error(reason))
2704
+ onForceStop: (reason) => abort.abort(new Error(reason)),
2705
+ toolContext
2515
2706
  });
2516
2707
  }
2517
2708
  async function generateResponsesResponse(model, params, modelId) {
2518
- const r = await generateText({ model, ...params });
2709
+ const { toolContext, ...sdkParams } = params;
2710
+ const r = await generateText({ model, ...sdkParams });
2519
2711
  const createdAt = Math.floor(Date.now() / 1e3);
2520
2712
  const responseId = newResponseId();
2521
2713
  const output = [];
@@ -2533,14 +2725,8 @@ async function generateResponsesResponse(model, params, modelId) {
2533
2725
  }
2534
2726
  for (const tc of r.toolCalls) {
2535
2727
  const encodedId = encodeToolUseId(tc.toolCallId, grabRoundTripSignature(tc), false);
2536
- output.push({
2537
- type: "function_call",
2538
- id: tc.toolCallId,
2539
- call_id: encodedId,
2540
- name: tc.toolName,
2541
- arguments: JSON.stringify(tc.input ?? {}),
2542
- status: "completed"
2543
- });
2728
+ const argsStr = JSON.stringify(tc.input ?? {});
2729
+ output.push(buildFinalToolItem(resolveOutputKind(tc.toolName, toolContext), tc.toolName, encodedId, tc.toolCallId, argsStr));
2544
2730
  }
2545
2731
  if (output.length === 0) {
2546
2732
  output.push({ id: newItemId("msg"), type: "message", role: "assistant", status: "completed", content: [{ type: "output_text", text: "(conversation context was too large to summarize)" }] });
@@ -2561,6 +2747,70 @@ async function generateResponsesResponse(model, params, modelId) {
2561
2747
  }
2562
2748
  };
2563
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
+ }
2564
2814
  function responsesErrorBody(modelId, message, statusCode = 401) {
2565
2815
  return {
2566
2816
  id: newResponseId(),
@@ -2658,6 +2908,17 @@ function responsesRateLimitBody(modelId, message) {
2658
2908
  }
2659
2909
 
2660
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
+ }
2661
2922
  function estimateCodexRequestChars(params) {
2662
2923
  let chars = (params.system ?? "").length;
2663
2924
  for (const msg of params.messages) {
@@ -2748,6 +3009,12 @@ function isLikelyCodexCompactionRequest(body) {
2748
3009
  }
2749
3010
  return false;
2750
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
+ }
2751
3018
  var COMPACTION_MAX_OUTPUT_TOKENS = 4e3;
2752
3019
  function protectCodexCompactionParams(body, params, contextWindow) {
2753
3020
  if (!isLikelyCodexCompactionRequest(body)) {
@@ -2821,6 +3088,7 @@ async function startCodexProxy(routes, options = {}) {
2821
3088
  return new Promise((resolve, reject2) => {
2822
3089
  const log14 = debug ? makeTraceLogger(getCodexProxyDebugLogPath()) : () => {
2823
3090
  };
3091
+ if (debug) resetCodexBodyDumpLog();
2824
3092
  const onRejection = (reason) => {
2825
3093
  if (debug) log14(`unhandled-rejection: ${formatUpstreamError(reason)}`);
2826
3094
  };
@@ -2928,6 +3196,15 @@ async function startCodexProxy(routes, options = {}) {
2928
3196
  const tools = Array.isArray(body.tools) ? body.tools : [];
2929
3197
  const toolNames = tools.map((t) => t && typeof t === "object" && "name" in t ? t.name : "?").join(",");
2930
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
+ });
2931
3208
  const mcpTools = tools.filter((t) => t && typeof t === "object" && "name" in t && String(t.name).startsWith("mcp__"));
2932
3209
  for (const t of mcpTools) {
2933
3210
  const mt = t;
@@ -2978,6 +3255,11 @@ async function startCodexProxy(routes, options = {}) {
2978
3255
  log14(`context trim: model=${route.modelId} window=${route.contextWindow} kept=${params.messages.length}/${before} messages`);
2979
3256
  }
2980
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
+ }
2981
3263
  if (debug) {
2982
3264
  const effort = body.reasoning?.effort;
2983
3265
  log14(`model=${route.modelId} effort=${effort ?? "(none)"} providerOptions=${JSON.stringify(params.providerOptions)}`);
@@ -2988,18 +3270,35 @@ async function startCodexProxy(routes, options = {}) {
2988
3270
  "Cache-Control": "no-cache",
2989
3271
  Connection: "keep-alive"
2990
3272
  });
2991
- const write = (chunk) => res.write(chunk);
2992
- try {
2993
- await streamResponsesResponse(languageModel, params, modelId, write, (summary) => {
2994
- if (debug) {
2995
- const failure = `${summary.aborted ? " aborted=yes" : ""}${summary.errorMessage ? ` error=${JSON.stringify(summary.errorMessage)}` : ""}`;
2996
- 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)}`);
2997
- }
2998
- }, (progress) => {
2999
- if (debug) {
3000
- 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
+ });
3001
3285
  }
3002
- });
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
+ });
3003
3302
  } catch (err) {
3004
3303
  const msg = formatUpstreamError(err);
3005
3304
  const status = upstreamHttpStatus(err, msg);
@@ -3013,7 +3312,16 @@ async function startCodexProxy(routes, options = {}) {
3013
3312
  res.end();
3014
3313
  } else {
3015
3314
  try {
3016
- 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
+ }
3017
3325
  sendJson(res, 200, response);
3018
3326
  } catch (err) {
3019
3327
  const msg = formatUpstreamError(err);
@@ -3120,6 +3428,7 @@ Sec-WebSocket-Accept: ${wsAcceptKey(clientKey)}\r
3120
3428
  );
3121
3429
  let frameBuf = Buffer.alloc(0);
3122
3430
  let handled = false;
3431
+ let currentRequestModel = "";
3123
3432
  const closeSocket = () => {
3124
3433
  if (!socket.destroyed) {
3125
3434
  socket.write(wsCloseFrame());
@@ -3128,6 +3437,18 @@ Sec-WebSocket-Accept: ${wsAcceptKey(clientKey)}\r
3128
3437
  };
3129
3438
  const sendWsEvent = (sseChunk2) => {
3130
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
+ }
3131
3452
  for (const line of sseChunk2.split("\n")) {
3132
3453
  if (line.startsWith("data: ")) {
3133
3454
  socket.write(wsEncodeTextFrame(line.slice(6)));
@@ -3160,8 +3481,18 @@ data: ${JSON.stringify({ error: { message: "Invalid JSON", type: "invalid_reques
3160
3481
  const tools = Array.isArray(body.tools) ? body.tools : [];
3161
3482
  const toolNames = tools.map((t) => t && typeof t === "object" && "name" in t ? t.name : "?").join(",");
3162
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
+ });
3163
3493
  }
3164
3494
  const modelId = String(body.model ?? "");
3495
+ currentRequestModel = modelId;
3165
3496
  let resolved = resolveModel(routes, models, modelId);
3166
3497
  if (!resolved) {
3167
3498
  const fb = routes[0];
@@ -3204,20 +3535,28 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}` } })}
3204
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}`);
3205
3536
  }
3206
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
+ }
3207
3543
  if (debug) {
3208
3544
  const effort = body.reasoning?.effort;
3209
3545
  log14(`WS model=${route.modelId} effort=${effort ?? "(none)"} providerOptions=${JSON.stringify(params.providerOptions)}`);
3210
3546
  }
3211
- await streamResponsesResponse(languageModel, params, modelId, sendWsEvent, (summary) => {
3212
- if (debug) {
3213
- const failure = `${summary.aborted ? " aborted=yes" : ""}${summary.errorMessage ? ` error=${JSON.stringify(summary.errorMessage)}` : ""}`;
3214
- 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)}`);
3215
- }
3216
- }, (progress) => {
3217
- if (debug) {
3218
- log14(`WS response progress: model=${route.modelId} elapsedMs=${progress.elapsedMs} reasoningChars=${progress.reasoningChars} textChars=${progress.textChars} toolCalls=${progress.toolCallCount} reasoningTail=${JSON.stringify(progress.reasoningTail)}`);
3219
- }
3220
- });
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
+ });
3221
3560
  } catch (err) {
3222
3561
  const msg = formatUpstreamError(err);
3223
3562
  const status = upstreamHttpStatus(err, msg);
@@ -12295,6 +12634,14 @@ Error: ${launchPlan.error}
12295
12634
  }
12296
12635
  async function main(args = process.argv.slice(2)) {
12297
12636
  const parsed = parseArgs(args);
12637
+ if (process.stdout.isTTY) {
12638
+ const update = await checkForUpdates();
12639
+ if (update.updateAvailable && update.latestVersion) {
12640
+ console.log(`
12641
+ ${formatUpdateNotification(update.currentVersion, update.latestVersion)}
12642
+ `);
12643
+ }
12644
+ }
12298
12645
  if (parsed.error) {
12299
12646
  console.error(pc12.red(`
12300
12647
  Error: ${parsed.error}
@@ -12349,7 +12696,7 @@ Error: ${parsed.error}
12349
12696
  console.log("Usage: relay-ai ui [--trace]\n\nOpen the settings UI in your browser.");
12350
12697
  return 0;
12351
12698
  }
12352
- const { runUiCommand } = await import("./ui-command-GEO7ACPW.js");
12699
+ const { runUiCommand } = await import("./ui-command-A2WDJOSZ.js");
12353
12700
  return runUiCommand({ trace: parsed.trace });
12354
12701
  }
12355
12702
  if (parsed.command === "models") {