@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/README.md +3 -1
- package/dist/{chunk-XCB2K4GI.js → chunk-YM6G7BHE.js} +23 -3
- package/dist/chunk-YM6G7BHE.js.map +1 -0
- package/dist/cli.js +391 -54
- package/dist/cli.js.map +1 -1
- package/dist/ui/public/app.js +154 -5
- package/dist/ui/public/index.html +10 -2
- package/dist/ui/public/provider-model-browser.js +29 -0
- package/dist/ui/public/style.css +114 -0
- package/dist/{ui-command-JG3BFVSL.js → ui-command-2HNQX7U3.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-XCB2K4GI.js.map +0 -1
- /package/dist/{ui-command-JG3BFVSL.js.map → ui-command-2HNQX7U3.js.map} +0 -0
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-
|
|
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,
|
|
2179
|
+
const addTool = (name, description, parameters) => {
|
|
2029
2180
|
if (options.maxTools !== void 0 && toolCount >= options.maxTools) return;
|
|
2030
2181
|
out[name] = tool({
|
|
2031
|
-
description:
|
|
2032
|
-
inputSchema: jsonSchema(
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
-
|
|
2539
|
-
|
|
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) =>
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
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
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
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-
|
|
12699
|
+
const { runUiCommand } = await import("./ui-command-2HNQX7U3.js");
|
|
12363
12700
|
return runUiCommand({ trace: parsed.trace });
|
|
12364
12701
|
}
|
|
12365
12702
|
if (parsed.command === "models") {
|