@jacobbd/relay-ai 0.11.1 → 0.12.0

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
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  addManualModel,
4
4
  removeManualModel
5
- } from "./chunk-VXQ7ZVTW.js";
5
+ } from "./chunk-FD5EDSBM.js";
6
6
  import {
7
7
  CODEX_APP_AUTO_COMPACT_RATIO,
8
8
  CODEX_APP_PROVIDER_ID,
@@ -148,7 +148,7 @@ import {
148
148
  waitForCodexAppQuit,
149
149
  writeSecureLogLine,
150
150
  zenRegistryStub
151
- } from "./chunk-YD6A3ZB3.js";
151
+ } from "./chunk-HL3EZ4OU.js";
152
152
  import {
153
153
  filterTemplates,
154
154
  getTemplateById,
@@ -156,7 +156,7 @@ import {
156
156
  listAddableTemplates,
157
157
  listSupportedTemplates,
158
158
  listVisibleOAuthTemplates
159
- } from "./chunk-TRM2WGI6.js";
159
+ } from "./chunk-3R25QO5X.js";
160
160
  import {
161
161
  ANTIGRAVITY_BASE_URLS,
162
162
  BACKENDS,
@@ -196,6 +196,7 @@ import {
196
196
  migrateGlobalOpencodeCredential,
197
197
  migrateLegacyCloudProviders,
198
198
  modelIdError,
199
+ normalizeToolSchemaForNpm,
199
200
  oauthCredentialToKeychainJson,
200
201
  parseDsmlToolCalls,
201
202
  parseToolArguments,
@@ -221,7 +222,7 @@ import {
221
222
  thinkingProviderOptions,
222
223
  upstreamHttpStatus,
223
224
  validateCustomEndpointUrl
224
- } from "./chunk-O2XZFXHG.js";
225
+ } from "./chunk-Z5GL4ALA.js";
225
226
  import "./chunk-JIDIH7DS.js";
226
227
 
227
228
  // src/cli.ts
@@ -2661,7 +2662,7 @@ function translateResponsesTools(tools, options = {}) {
2661
2662
  if (options.maxTools !== void 0 && toolCount >= options.maxTools) return;
2662
2663
  out[name] = tool({
2663
2664
  description: description ?? "",
2664
- inputSchema: jsonSchema(parameters ?? { type: "object", properties: {} })
2665
+ inputSchema: jsonSchema(normalizeToolSchemaForNpm(parameters ?? { type: "object", properties: {} }, options.npm))
2665
2666
  });
2666
2667
  toolCount++;
2667
2668
  };
@@ -2688,6 +2689,9 @@ function translateResponsesTools(tools, options = {}) {
2688
2689
  }
2689
2690
  return Object.keys(out).length ? out : void 0;
2690
2691
  }
2692
+ function omitsMaxOutputTokens(metadata) {
2693
+ return metadata?.providerId === "openrouter" || (metadata?.apiBaseUrl?.includes("openrouter.ai") ?? false);
2694
+ }
2691
2695
  function translateResponsesRequest(body, npm, metadata, options = {}) {
2692
2696
  const toolContext = createCodexToolContext();
2693
2697
  let effectiveTools = body.tools ?? [];
@@ -2704,13 +2708,13 @@ function translateResponsesRequest(body, npm, metadata, options = {}) {
2704
2708
  thinkingProviderOptions(npm),
2705
2709
  effortProviderOptions(npm, effort, metadata?.upstreamModelId ?? body.model, metadata)
2706
2710
  );
2707
- const tools = translateResponsesTools([...effectiveTools, ...deferredTools], options);
2711
+ const tools = translateResponsesTools([...effectiveTools, ...deferredTools], { ...options, npm });
2708
2712
  return {
2709
2713
  instructions: system,
2710
2714
  messages,
2711
2715
  tools,
2712
2716
  toolContext,
2713
- maxOutputTokens: body.max_output_tokens,
2717
+ maxOutputTokens: omitsMaxOutputTokens(metadata) ? void 0 : body.max_output_tokens,
2714
2718
  temperature: body.temperature,
2715
2719
  providerOptions,
2716
2720
  headers: options.requestHeaders
@@ -3430,6 +3434,14 @@ function classifyCodexDispatch(modelId, relayRoutes, nativeModelIds) {
3430
3434
  if (route) return { kind: "relay", route };
3431
3435
  return { kind: "unknown", modelId };
3432
3436
  }
3437
+ function classifyCodexMixedDispatch(input) {
3438
+ if (input.subagentRoute) return { kind: "relay", route: input.subagentRoute };
3439
+ const dispatch = classifyCodexDispatch(input.modelId, input.relayRoutes, input.nativeModelIds);
3440
+ if (dispatch.kind === "unknown" && input.markedSubagent) {
3441
+ return { kind: "native", modelId: input.modelId };
3442
+ }
3443
+ return dispatch;
3444
+ }
3433
3445
  function createMixedProxyCapability() {
3434
3446
  return randomBytes(32).toString("base64url");
3435
3447
  }
@@ -4046,6 +4058,13 @@ function isCodexV2CompactionRequest(body) {
4046
4058
  );
4047
4059
  }
4048
4060
  var COMPACTION_MAX_OUTPUT_TOKENS = 4e3;
4061
+ function streamOutcome(failure, okStatus) {
4062
+ if (!failure) return { outcome: "ok", status: okStatus };
4063
+ return {
4064
+ outcome: "error",
4065
+ status: failure.errorMessage ? upstreamHttpStatus(void 0, failure.errorMessage) : "stream-aborted"
4066
+ };
4067
+ }
4049
4068
  function protectCodexCompactionParams(body, params, contextWindow) {
4050
4069
  if (!isLikelyCodexCompactionRequest(body)) {
4051
4070
  return trimToContextLimit(params, contextWindow);
@@ -4345,74 +4364,68 @@ async function startCodexProxy(routes, options = {}) {
4345
4364
  if (debug && markedSubagent) {
4346
4365
  log15(`subagent dispatch: requested=${modelId} route=${subagentRoute?.modelId ?? "(none)"}`);
4347
4366
  }
4348
- if (mixedNative && markedSubagent && !subagentRoute) {
4349
- audit({ transport: "http", requestedModel: modelId, dispatch: "relay-subagent", phase: "complete", outcome: "error", status: 503 });
4350
- sendJson(res, 503, {
4351
- error: {
4352
- message: "Codex marked this request as a Sub-agent, but no configured Codex Sub-agent route is available.",
4353
- type: "service_unavailable"
4354
- }
4355
- });
4356
- return;
4357
- }
4358
4367
  if (mixedNative) {
4359
- if (!markedSubagent) {
4360
- const dispatch = classifyCodexDispatch(modelId, routes, mixedNative.nativeModelIds);
4361
- if (dispatch.kind === "unknown") {
4362
- audit({ transport: "http", requestedModel: modelId, dispatch: "unknown", phase: "complete", outcome: "error", status: 404 });
4363
- sendJson(res, 404, { error: { message: `Unknown model: ${modelId}`, type: "invalid_request_error" } });
4364
- return;
4365
- }
4366
- if (dispatch.kind === "native") {
4368
+ const dispatch = classifyCodexMixedDispatch({
4369
+ modelId,
4370
+ markedSubagent,
4371
+ subagentRoute,
4372
+ relayRoutes: routes,
4373
+ nativeModelIds: mixedNative.nativeModelIds
4374
+ });
4375
+ if (dispatch.kind === "unknown") {
4376
+ audit({ transport: "http", requestedModel: modelId, dispatch: "unknown", phase: "complete", outcome: "error", status: 404 });
4377
+ sendJson(res, 404, { error: { message: `Unknown model: ${modelId}`, type: "invalid_request_error" } });
4378
+ return;
4379
+ }
4380
+ if (dispatch.kind === "native") {
4381
+ audit({
4382
+ transport: "http",
4383
+ requestedModel: modelId,
4384
+ dispatch: "native",
4385
+ phase: "dispatch",
4386
+ provider: "openai-native",
4387
+ routeModel: modelId,
4388
+ upstreamModel: modelId
4389
+ });
4390
+ const controller = new AbortController();
4391
+ req.once("aborted", () => controller.abort());
4392
+ try {
4393
+ const nativeResponse = await forwardNativeCodexHttp({
4394
+ body: rawBody,
4395
+ inboundHeaders: req.headers,
4396
+ nativeUrl: mixedNative.nativeBaseUrl ? `${mixedNative.nativeBaseUrl.replace(/\/$/, "")}/responses` : NATIVE_CODEX_RESPONSES_URL,
4397
+ signal: controller.signal,
4398
+ fetchImpl: mixedNative.nativeFetchImpl
4399
+ });
4400
+ const contentType = nativeResponse.headers.get("content-type");
4401
+ res.writeHead(nativeResponse.status, contentType ? { "content-type": contentType } : void 0);
4402
+ res.end(Buffer.from(await nativeResponse.arrayBuffer()));
4367
4403
  audit({
4368
4404
  transport: "http",
4369
4405
  requestedModel: modelId,
4370
4406
  dispatch: "native",
4371
- phase: "dispatch",
4407
+ phase: "complete",
4372
4408
  provider: "openai-native",
4373
4409
  routeModel: modelId,
4374
- upstreamModel: modelId
4410
+ upstreamModel: modelId,
4411
+ outcome: nativeResponse.ok ? "ok" : "error",
4412
+ status: nativeResponse.status
4375
4413
  });
4376
- const controller = new AbortController();
4377
- req.once("aborted", () => controller.abort());
4378
- try {
4379
- const nativeResponse = await forwardNativeCodexHttp({
4380
- body: rawBody,
4381
- inboundHeaders: req.headers,
4382
- nativeUrl: mixedNative.nativeBaseUrl ? `${mixedNative.nativeBaseUrl.replace(/\/$/, "")}/responses` : NATIVE_CODEX_RESPONSES_URL,
4383
- signal: controller.signal,
4384
- fetchImpl: mixedNative.nativeFetchImpl
4385
- });
4386
- const contentType = nativeResponse.headers.get("content-type");
4387
- res.writeHead(nativeResponse.status, contentType ? { "content-type": contentType } : void 0);
4388
- res.end(Buffer.from(await nativeResponse.arrayBuffer()));
4389
- audit({
4390
- transport: "http",
4391
- requestedModel: modelId,
4392
- dispatch: "native",
4393
- phase: "complete",
4394
- provider: "openai-native",
4395
- routeModel: modelId,
4396
- upstreamModel: modelId,
4397
- outcome: nativeResponse.ok ? "ok" : "error",
4398
- status: nativeResponse.status
4399
- });
4400
- } catch (err) {
4401
- audit({
4402
- transport: "http",
4403
- requestedModel: modelId,
4404
- dispatch: "native",
4405
- phase: "complete",
4406
- provider: "openai-native",
4407
- routeModel: modelId,
4408
- upstreamModel: modelId,
4409
- outcome: "error",
4410
- status: "forward-failed"
4411
- });
4412
- if (!res.writableEnded) sendJson(res, 502, { error: { message: "Native Codex request failed", type: "upstream_error" } });
4413
- }
4414
- return;
4414
+ } catch (err) {
4415
+ audit({
4416
+ transport: "http",
4417
+ requestedModel: modelId,
4418
+ dispatch: "native",
4419
+ phase: "complete",
4420
+ provider: "openai-native",
4421
+ routeModel: modelId,
4422
+ upstreamModel: modelId,
4423
+ outcome: "error",
4424
+ status: "forward-failed"
4425
+ });
4426
+ if (!res.writableEnded) sendJson(res, 502, { error: { message: "Native Codex request failed", type: "upstream_error" } });
4415
4427
  }
4428
+ return;
4416
4429
  }
4417
4430
  }
4418
4431
  let resolved = subagentRoute ? resolveModel(routes, models, subagentRoute.modelId) : resolveModel(routes, models, modelId);
@@ -4511,11 +4524,13 @@ async function startCodexProxy(routes, options = {}) {
4511
4524
  }
4512
4525
  }
4513
4526
  };
4527
+ let streamFailure;
4514
4528
  try {
4515
4529
  if (v2Compaction) {
4516
4530
  await streamCompactionResponse(languageModel, params, modelId, write);
4517
4531
  } else
4518
4532
  await streamResponsesResponse(languageModel, params, modelId, write, (summary) => {
4533
+ if (summary.errorMessage || summary.aborted) streamFailure = summary;
4519
4534
  if (debug) {
4520
4535
  const failure = `${summary.aborted ? " aborted=yes" : ""}${summary.errorMessage ? ` error=${JSON.stringify(summary.errorMessage)}` : ""}`;
4521
4536
  log15(`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)}`);
@@ -4533,8 +4548,7 @@ async function startCodexProxy(routes, options = {}) {
4533
4548
  provider: route.providerId ?? "relay",
4534
4549
  routeModel: route.modelId,
4535
4550
  upstreamModel: route.auditUpstreamModelId ?? route.upstreamModelId,
4536
- outcome: "ok",
4537
- status: 200
4551
+ ...streamOutcome(streamFailure, 200)
4538
4552
  });
4539
4553
  } catch (err) {
4540
4554
  const msg = formatUpstreamError(err);
@@ -4877,190 +4891,182 @@ data: ${JSON.stringify({ error: { message: "Invalid JSON", type: "invalid_reques
4877
4891
  if (debug && markedSubagent) {
4878
4892
  log15(`WS subagent dispatch: requested=${modelId} route=${subagentRoute?.modelId ?? "(none)"}`);
4879
4893
  }
4880
- if (mixedNative && markedSubagent && !subagentRoute) {
4881
- audit({ transport: "ws", requestedModel: modelId, dispatch: "relay-subagent", phase: "complete", outcome: "error", status: 503 });
4882
- sendWsEvent(`event: error
4883
- data: ${JSON.stringify({ error: {
4884
- message: "Codex marked this request as a Sub-agent, but no configured Codex Sub-agent route is available.",
4885
- type: "service_unavailable"
4886
- } })}
4887
-
4888
- `);
4889
- closeSocket();
4890
- return;
4891
- }
4892
4894
  if (mixedNative) {
4893
- if (!markedSubagent) {
4894
- const dispatch = classifyCodexDispatch(modelId, routes, mixedNative.nativeModelIds);
4895
- if (dispatch.kind === "unknown") {
4896
- audit({ transport: "ws", requestedModel: modelId, dispatch: "unknown", phase: "complete", outcome: "error", status: 404 });
4897
- sendWsEvent(`event: error
4895
+ const dispatch = classifyCodexMixedDispatch({
4896
+ modelId,
4897
+ markedSubagent,
4898
+ subagentRoute,
4899
+ relayRoutes: routes,
4900
+ nativeModelIds: mixedNative.nativeModelIds
4901
+ });
4902
+ if (dispatch.kind === "unknown") {
4903
+ audit({ transport: "ws", requestedModel: modelId, dispatch: "unknown", phase: "complete", outcome: "error", status: 404 });
4904
+ sendWsEvent(`event: error
4898
4905
  data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}`, type: "invalid_request_error" } })}
4899
4906
 
4900
4907
  `);
4901
- closeSocket();
4908
+ closeSocket();
4909
+ return;
4910
+ }
4911
+ if (dispatch.kind === "native") {
4912
+ audit({
4913
+ transport: "ws",
4914
+ requestedModel: modelId,
4915
+ dispatch: "native",
4916
+ phase: "dispatch",
4917
+ provider: "openai-native",
4918
+ routeModel: modelId,
4919
+ upstreamModel: modelId
4920
+ });
4921
+ const nativeBody = prepareNativeCodexBody(body);
4922
+ if (debug && nativeBody !== body) {
4923
+ log15(`WS native history normalized: model=${modelId} converted Relay compaction for native verification`);
4924
+ }
4925
+ if (nativeActive && nativeUpstream) {
4926
+ if (nativeUpstream.readyState === WebSocket.OPEN) {
4927
+ if (debug) log15(`WS native forwarding next turn: model=${modelId}`);
4928
+ nativeSendTurn?.(nativeBody, modelId);
4929
+ } else if (debug) {
4930
+ log15(`WS native cannot forward next turn: upstream_state=${nativeUpstream.readyState}`);
4931
+ }
4902
4932
  return;
4903
4933
  }
4904
- if (dispatch.kind === "native") {
4905
- audit({
4906
- transport: "ws",
4907
- requestedModel: modelId,
4908
- dispatch: "native",
4909
- phase: "dispatch",
4910
- provider: "openai-native",
4911
- routeModel: modelId,
4912
- upstreamModel: modelId
4913
- });
4914
- const nativeBody = prepareNativeCodexBody(body);
4915
- if (debug && nativeBody !== body) {
4916
- log15(`WS native history normalized: model=${modelId} converted Relay compaction for native verification`);
4934
+ const wsTarget = mixedNative.nativeBaseUrl ? `${mixedNative.nativeBaseUrl.replace(/^http:/, "ws:").replace(/^https:/, "wss:").replace(/\/$/, "")}/responses` : void 0;
4935
+ const target = nativeResponsesWebSocketOptions({ headers: req.headers, wsUrl: wsTarget });
4936
+ let upstream;
4937
+ let nativeOpened = false;
4938
+ let nativeCompleted = false;
4939
+ let nativeTurnModelId = modelId;
4940
+ let nativeFrameCount = 0;
4941
+ let finished = false;
4942
+ let connectTimer;
4943
+ let firstFrameTimer;
4944
+ const clearTimers = () => {
4945
+ if (connectTimer) clearTimeout(connectTimer);
4946
+ if (firstFrameTimer) clearTimeout(firstFrameTimer);
4947
+ };
4948
+ const sendNativeError = (message) => {
4949
+ if (socket.destroyed) return;
4950
+ socket.write(wsEncodeTextFrame(JSON.stringify({
4951
+ type: "error",
4952
+ error: { type: "upstream_error", message }
4953
+ })));
4954
+ };
4955
+ const closeBoth = (message, closeCode = 1011) => {
4956
+ if (finished) return;
4957
+ finished = true;
4958
+ nativeActive = false;
4959
+ nativeSendTurn = void 0;
4960
+ if (nativeUpstream === upstream) nativeUpstream = void 0;
4961
+ clearTimers();
4962
+ if (debug && message) {
4963
+ log15(`WS native upstream failed: model=${nativeTurnModelId} opened=${nativeOpened} frames=${nativeFrameCount} message=${message}`);
4917
4964
  }
4918
- if (nativeActive && nativeUpstream) {
4919
- if (nativeUpstream.readyState === WebSocket.OPEN) {
4920
- if (debug) log15(`WS native forwarding next turn: model=${modelId}`);
4921
- nativeSendTurn?.(nativeBody, modelId);
4922
- } else if (debug) {
4923
- log15(`WS native cannot forward next turn: upstream_state=${nativeUpstream.readyState}`);
4924
- }
4965
+ if (message && !nativeCompleted) {
4966
+ audit({
4967
+ transport: "ws",
4968
+ requestedModel: nativeTurnModelId,
4969
+ dispatch: "native",
4970
+ phase: "complete",
4971
+ provider: "openai-native",
4972
+ routeModel: nativeTurnModelId,
4973
+ upstreamModel: nativeTurnModelId,
4974
+ outcome: "error",
4975
+ status: "upstream-failed"
4976
+ });
4977
+ }
4978
+ if (message && !nativeCompleted) sendNativeError(message);
4979
+ try {
4980
+ upstream?.close();
4981
+ } catch {
4982
+ }
4983
+ closeSocket(closeCode);
4984
+ };
4985
+ const sendNativeTurn = (turnBody, turnModelId) => {
4986
+ if (!upstream || upstream.readyState !== WebSocket.OPEN) {
4987
+ if (debug) log15(`WS native cannot send turn: model=${turnModelId} upstream_state=${upstream?.readyState ?? "missing"}`);
4925
4988
  return;
4926
4989
  }
4927
- const wsTarget = mixedNative.nativeBaseUrl ? `${mixedNative.nativeBaseUrl.replace(/^http:/, "ws:").replace(/^https:/, "wss:").replace(/\/$/, "")}/responses` : void 0;
4928
- const target = nativeResponsesWebSocketOptions({ headers: req.headers, wsUrl: wsTarget });
4929
- let upstream;
4930
- let nativeOpened = false;
4931
- let nativeCompleted = false;
4932
- let nativeTurnModelId = modelId;
4933
- let nativeFrameCount = 0;
4934
- let finished = false;
4935
- let connectTimer;
4936
- let firstFrameTimer;
4937
- const clearTimers = () => {
4990
+ nativeTurnModelId = turnModelId;
4991
+ nativeCompleted = false;
4992
+ if (firstFrameTimer) clearTimeout(firstFrameTimer);
4993
+ upstream.send(JSON.stringify({ type: "response.create", ...turnBody }));
4994
+ firstFrameTimer = setTimeout(() => closeBoth("Native Codex WebSocket response timed out"), 6e4);
4995
+ };
4996
+ try {
4997
+ if (debug) {
4998
+ log15(`WS native connecting: model=${modelId} url=${target.url} headers=[${Object.keys(target.headers).join(",")}]`);
4999
+ }
5000
+ upstream = new WebSocket(target.url, { headers: target.headers });
5001
+ nativeUpstream = upstream;
5002
+ nativeSendTurn = sendNativeTurn;
5003
+ nativeActive = true;
5004
+ connectTimer = setTimeout(() => closeBoth("Native Codex WebSocket connection timed out"), 15e3);
5005
+ upstream.once("open", () => {
5006
+ nativeOpened = true;
4938
5007
  if (connectTimer) clearTimeout(connectTimer);
4939
- if (firstFrameTimer) clearTimeout(firstFrameTimer);
4940
- };
4941
- const sendNativeError = (message) => {
5008
+ if (debug) log15(`WS native upstream open: model=${modelId}`);
5009
+ sendNativeTurn(nativeBody, modelId);
5010
+ });
5011
+ upstream.once("unexpected-response", (_request, response) => {
5012
+ if (debug) log15(`WS native upstream HTTP rejection: model=${modelId} status=${response.statusCode}`);
5013
+ response.resume();
5014
+ closeBoth(`Native Codex WebSocket rejected (${response.statusCode})`);
5015
+ });
5016
+ upstream.on("message", (data) => {
4942
5017
  if (socket.destroyed) return;
4943
- socket.write(wsEncodeTextFrame(JSON.stringify({
4944
- type: "error",
4945
- error: { type: "upstream_error", message }
4946
- })));
4947
- };
4948
- const closeBoth = (message, closeCode = 1011) => {
4949
- if (finished) return;
5018
+ nativeFrameCount += 1;
5019
+ if (firstFrameTimer) clearTimeout(firstFrameTimer);
5020
+ const text6 = Array.isArray(data) ? Buffer.concat(data).toString("utf8") : data.toString("utf8");
5021
+ let eventType = "non-json";
5022
+ try {
5023
+ const parsed = JSON.parse(text6);
5024
+ if (typeof parsed.type === "string") eventType = parsed.type;
5025
+ if (eventType === "response.completed" || eventType === "response.failed" || eventType === "response.incomplete") {
5026
+ nativeCompleted = true;
5027
+ audit({
5028
+ transport: "ws",
5029
+ requestedModel: modelId,
5030
+ dispatch: "native",
5031
+ phase: "complete",
5032
+ provider: "openai-native",
5033
+ routeModel: modelId,
5034
+ upstreamModel: modelId,
5035
+ outcome: eventType === "response.completed" ? "ok" : "error",
5036
+ status: eventType
5037
+ });
5038
+ }
5039
+ } catch {
5040
+ }
5041
+ if (debug && (nativeFrameCount <= 3 || nativeCompleted || eventType === "error" || nativeFrameCount % 25 === 0)) {
5042
+ log15(`WS native frame#${nativeFrameCount}: model=${modelId} type=${eventType} bytes=${text6.length}`);
5043
+ }
5044
+ socket.write(wsEncodeTextFrame(text6));
5045
+ });
5046
+ upstream.once("error", (err) => closeBoth(`Native Codex WebSocket error: ${err.message}`));
5047
+ upstream.once("close", (code, reason) => {
5048
+ const detail = reason?.length ? ` reason=${reason.toString("utf8").slice(0, 200)}` : "";
5049
+ if (debug) log15(`WS native upstream close: model=${modelId} code=${code}${detail} frames=${nativeFrameCount}`);
5050
+ if (nativeUpstream === upstream) nativeUpstream = void 0;
5051
+ nativeActive = false;
5052
+ if (!finished) closeBoth(nativeCompleted ? void 0 : `Native Codex WebSocket closed before completion (${code})`);
5053
+ });
5054
+ socket.once("close", () => {
5055
+ if (debug) log15(`WS native downstream close: model=${modelId} frames=${nativeFrameCount} completed=${nativeCompleted}`);
4950
5056
  finished = true;
4951
5057
  nativeActive = false;
4952
5058
  nativeSendTurn = void 0;
4953
5059
  if (nativeUpstream === upstream) nativeUpstream = void 0;
4954
5060
  clearTimers();
4955
- if (debug && message) {
4956
- log15(`WS native upstream failed: model=${nativeTurnModelId} opened=${nativeOpened} frames=${nativeFrameCount} message=${message}`);
4957
- }
4958
- if (message && !nativeCompleted) {
4959
- audit({
4960
- transport: "ws",
4961
- requestedModel: nativeTurnModelId,
4962
- dispatch: "native",
4963
- phase: "complete",
4964
- provider: "openai-native",
4965
- routeModel: nativeTurnModelId,
4966
- upstreamModel: nativeTurnModelId,
4967
- outcome: "error",
4968
- status: "upstream-failed"
4969
- });
4970
- }
4971
- if (message && !nativeCompleted) sendNativeError(message);
4972
5061
  try {
4973
5062
  upstream?.close();
4974
5063
  } catch {
4975
5064
  }
4976
- closeSocket(closeCode);
4977
- };
4978
- const sendNativeTurn = (turnBody, turnModelId) => {
4979
- if (!upstream || upstream.readyState !== WebSocket.OPEN) {
4980
- if (debug) log15(`WS native cannot send turn: model=${turnModelId} upstream_state=${upstream?.readyState ?? "missing"}`);
4981
- return;
4982
- }
4983
- nativeTurnModelId = turnModelId;
4984
- nativeCompleted = false;
4985
- if (firstFrameTimer) clearTimeout(firstFrameTimer);
4986
- upstream.send(JSON.stringify({ type: "response.create", ...turnBody }));
4987
- firstFrameTimer = setTimeout(() => closeBoth("Native Codex WebSocket response timed out"), 6e4);
4988
- };
4989
- try {
4990
- if (debug) {
4991
- log15(`WS native connecting: model=${modelId} url=${target.url} headers=[${Object.keys(target.headers).join(",")}]`);
4992
- }
4993
- upstream = new WebSocket(target.url, { headers: target.headers });
4994
- nativeUpstream = upstream;
4995
- nativeSendTurn = sendNativeTurn;
4996
- nativeActive = true;
4997
- connectTimer = setTimeout(() => closeBoth("Native Codex WebSocket connection timed out"), 15e3);
4998
- upstream.once("open", () => {
4999
- nativeOpened = true;
5000
- if (connectTimer) clearTimeout(connectTimer);
5001
- if (debug) log15(`WS native upstream open: model=${modelId}`);
5002
- sendNativeTurn(nativeBody, modelId);
5003
- });
5004
- upstream.once("unexpected-response", (_request, response) => {
5005
- if (debug) log15(`WS native upstream HTTP rejection: model=${modelId} status=${response.statusCode}`);
5006
- response.resume();
5007
- closeBoth(`Native Codex WebSocket rejected (${response.statusCode})`);
5008
- });
5009
- upstream.on("message", (data) => {
5010
- if (socket.destroyed) return;
5011
- nativeFrameCount += 1;
5012
- if (firstFrameTimer) clearTimeout(firstFrameTimer);
5013
- const text6 = Array.isArray(data) ? Buffer.concat(data).toString("utf8") : data.toString("utf8");
5014
- let eventType = "non-json";
5015
- try {
5016
- const parsed = JSON.parse(text6);
5017
- if (typeof parsed.type === "string") eventType = parsed.type;
5018
- if (eventType === "response.completed" || eventType === "response.failed" || eventType === "response.incomplete") {
5019
- nativeCompleted = true;
5020
- audit({
5021
- transport: "ws",
5022
- requestedModel: modelId,
5023
- dispatch: "native",
5024
- phase: "complete",
5025
- provider: "openai-native",
5026
- routeModel: modelId,
5027
- upstreamModel: modelId,
5028
- outcome: eventType === "response.completed" ? "ok" : "error",
5029
- status: eventType
5030
- });
5031
- }
5032
- } catch {
5033
- }
5034
- if (debug && (nativeFrameCount <= 3 || nativeCompleted || eventType === "error" || nativeFrameCount % 25 === 0)) {
5035
- log15(`WS native frame#${nativeFrameCount}: model=${modelId} type=${eventType} bytes=${text6.length}`);
5036
- }
5037
- socket.write(wsEncodeTextFrame(text6));
5038
- });
5039
- upstream.once("error", (err) => closeBoth(`Native Codex WebSocket error: ${err.message}`));
5040
- upstream.once("close", (code, reason) => {
5041
- const detail = reason?.length ? ` reason=${reason.toString("utf8").slice(0, 200)}` : "";
5042
- if (debug) log15(`WS native upstream close: model=${modelId} code=${code}${detail} frames=${nativeFrameCount}`);
5043
- if (nativeUpstream === upstream) nativeUpstream = void 0;
5044
- nativeActive = false;
5045
- if (!finished) closeBoth(nativeCompleted ? void 0 : `Native Codex WebSocket closed before completion (${code})`);
5046
- });
5047
- socket.once("close", () => {
5048
- if (debug) log15(`WS native downstream close: model=${modelId} frames=${nativeFrameCount} completed=${nativeCompleted}`);
5049
- finished = true;
5050
- nativeActive = false;
5051
- nativeSendTurn = void 0;
5052
- if (nativeUpstream === upstream) nativeUpstream = void 0;
5053
- clearTimers();
5054
- try {
5055
- upstream?.close();
5056
- } catch {
5057
- }
5058
- });
5059
- } catch (err) {
5060
- closeBoth(`Native Codex WebSocket setup failed: ${err instanceof Error ? err.message : String(err)}`);
5061
- }
5062
- return;
5065
+ });
5066
+ } catch (err) {
5067
+ closeBoth(`Native Codex WebSocket setup failed: ${err instanceof Error ? err.message : String(err)}`);
5063
5068
  }
5069
+ return;
5064
5070
  }
5065
5071
  }
5066
5072
  externalActive = true;
@@ -5095,6 +5101,7 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}` } })}
5095
5101
  currentExternalCompletedResponse = void 0;
5096
5102
  currentExternalStateInput = void 0;
5097
5103
  currentExternalConsumedResponseId = void 0;
5104
+ let streamFailure;
5098
5105
  const continuation = resolveExternalContinuation(body);
5099
5106
  if (continuation.orphanedResponseId) {
5100
5107
  if (debug) log15(`WS continuation rejected: unknown previous_response_id=${continuation.orphanedResponseId}`);
@@ -5155,6 +5162,7 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}` } })}
5155
5162
  await streamCompactionResponse(languageModel, params, modelId, sendWsEvent);
5156
5163
  } else
5157
5164
  await streamResponsesResponse(languageModel, params, modelId, sendWsEvent, (summary) => {
5165
+ if (summary.errorMessage || summary.aborted) streamFailure = summary;
5158
5166
  if (debug) {
5159
5167
  const failure = `${summary.aborted ? " aborted=yes" : ""}${summary.errorMessage ? ` error=${JSON.stringify(summary.errorMessage)}` : ""}`;
5160
5168
  log15(`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)}`);
@@ -5178,8 +5186,7 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}` } })}
5178
5186
  provider: route.providerId ?? "relay",
5179
5187
  routeModel: route.modelId,
5180
5188
  upstreamModel: route.auditUpstreamModelId ?? route.upstreamModelId,
5181
- outcome: "ok",
5182
- status: "response.completed"
5189
+ ...streamOutcome(streamFailure, "response.completed")
5183
5190
  });
5184
5191
  } catch (err) {
5185
5192
  const msg = formatUpstreamError(err);
@@ -15973,7 +15980,7 @@ Options:
15973
15980
  --trace Write debug logs under ~/.relay-ai/logs/`);
15974
15981
  return 0;
15975
15982
  }
15976
- const { runUiCommand } = await import("./ui-command-WKPJI2CU.js");
15983
+ const { runUiCommand } = await import("./ui-command-J7PTTCM5.js");
15977
15984
  return runUiCommand({ trace: parsed.trace, serverMode: parsed.uiServerMode });
15978
15985
  }
15979
15986
  if (parsed.command === "models") {