@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/README.md +6 -4
- package/dist/{chunk-VSXPAZX4.js → chunk-UTPRIJFE.js} +172 -28
- package/dist/chunk-UTPRIJFE.js.map +1 -0
- package/dist/cli.js +401 -54
- package/dist/cli.js.map +1 -1
- package/dist/ui/public/app.js +50 -0
- package/dist/ui/public/index.html +18 -1
- package/dist/ui/public/style.css +82 -1
- package/dist/{ui-command-GEO7ACPW.js → ui-command-A2WDJOSZ.js} +46 -9
- package/dist/ui-command-A2WDJOSZ.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-VSXPAZX4.js.map +0 -1
- package/dist/ui-command-GEO7ACPW.js.map +0 -1
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-
|
|
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,
|
|
2179
|
+
const addTool = (name, description, parameters) => {
|
|
2027
2180
|
if (options.maxTools !== void 0 && toolCount >= options.maxTools) return;
|
|
2028
2181
|
out[name] = tool({
|
|
2029
|
-
description:
|
|
2030
|
-
inputSchema: jsonSchema(
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
-
|
|
2537
|
-
|
|
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) =>
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
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
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
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-
|
|
12699
|
+
const { runUiCommand } = await import("./ui-command-A2WDJOSZ.js");
|
|
12353
12700
|
return runUiCommand({ trace: parsed.trace });
|
|
12354
12701
|
}
|
|
12355
12702
|
if (parsed.command === "models") {
|