@jacobbd/relay-ai 0.11.1 → 0.12.1

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-UQKYSNDM.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-OYE6YZYP.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-7ASGPECN.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
@@ -2986,7 +2990,8 @@ async function writeResponsesStream(fullStream, modelId, write, onDone, onProgre
2986
2990
  toolCallCount: toolStates.length,
2987
2991
  toolNames: toolStates.map((t) => t.name),
2988
2992
  loopDetected,
2989
- errorMessage: msg
2993
+ errorMessage: msg,
2994
+ errorStatus: part.error?.statusCode
2990
2995
  });
2991
2996
  if (is429) {
2992
2997
  writeResponsesRateLimitStream(modelId, msg, write);
@@ -3430,6 +3435,14 @@ function classifyCodexDispatch(modelId, relayRoutes, nativeModelIds) {
3430
3435
  if (route) return { kind: "relay", route };
3431
3436
  return { kind: "unknown", modelId };
3432
3437
  }
3438
+ function classifyCodexMixedDispatch(input) {
3439
+ if (input.subagentRoute) return { kind: "relay", route: input.subagentRoute };
3440
+ const dispatch = classifyCodexDispatch(input.modelId, input.relayRoutes, input.nativeModelIds);
3441
+ if (dispatch.kind === "unknown" && input.markedSubagent) {
3442
+ return { kind: "native", modelId: input.modelId };
3443
+ }
3444
+ return dispatch;
3445
+ }
3433
3446
  function createMixedProxyCapability() {
3434
3447
  return randomBytes(32).toString("base64url");
3435
3448
  }
@@ -4046,6 +4059,13 @@ function isCodexV2CompactionRequest(body) {
4046
4059
  );
4047
4060
  }
4048
4061
  var COMPACTION_MAX_OUTPUT_TOKENS = 4e3;
4062
+ function streamOutcome(failure, okStatus) {
4063
+ if (!failure) return { outcome: "ok", status: okStatus };
4064
+ return {
4065
+ outcome: "error",
4066
+ status: failure.errorStatus ?? (failure.errorMessage ? upstreamHttpStatus(void 0, failure.errorMessage) : "stream-aborted")
4067
+ };
4068
+ }
4049
4069
  function protectCodexCompactionParams(body, params, contextWindow) {
4050
4070
  if (!isLikelyCodexCompactionRequest(body)) {
4051
4071
  return trimToContextLimit(params, contextWindow);
@@ -4345,74 +4365,68 @@ async function startCodexProxy(routes, options = {}) {
4345
4365
  if (debug && markedSubagent) {
4346
4366
  log15(`subagent dispatch: requested=${modelId} route=${subagentRoute?.modelId ?? "(none)"}`);
4347
4367
  }
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
4368
  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") {
4369
+ const dispatch = classifyCodexMixedDispatch({
4370
+ modelId,
4371
+ markedSubagent,
4372
+ subagentRoute,
4373
+ relayRoutes: routes,
4374
+ nativeModelIds: mixedNative.nativeModelIds
4375
+ });
4376
+ if (dispatch.kind === "unknown") {
4377
+ audit({ transport: "http", requestedModel: modelId, dispatch: "unknown", phase: "complete", outcome: "error", status: 404 });
4378
+ sendJson(res, 404, { error: { message: `Unknown model: ${modelId}`, type: "invalid_request_error" } });
4379
+ return;
4380
+ }
4381
+ if (dispatch.kind === "native") {
4382
+ audit({
4383
+ transport: "http",
4384
+ requestedModel: modelId,
4385
+ dispatch: "native",
4386
+ phase: "dispatch",
4387
+ provider: "openai-native",
4388
+ routeModel: modelId,
4389
+ upstreamModel: modelId
4390
+ });
4391
+ const controller = new AbortController();
4392
+ req.once("aborted", () => controller.abort());
4393
+ try {
4394
+ const nativeResponse = await forwardNativeCodexHttp({
4395
+ body: rawBody,
4396
+ inboundHeaders: req.headers,
4397
+ nativeUrl: mixedNative.nativeBaseUrl ? `${mixedNative.nativeBaseUrl.replace(/\/$/, "")}/responses` : NATIVE_CODEX_RESPONSES_URL,
4398
+ signal: controller.signal,
4399
+ fetchImpl: mixedNative.nativeFetchImpl
4400
+ });
4401
+ const contentType = nativeResponse.headers.get("content-type");
4402
+ res.writeHead(nativeResponse.status, contentType ? { "content-type": contentType } : void 0);
4403
+ res.end(Buffer.from(await nativeResponse.arrayBuffer()));
4367
4404
  audit({
4368
4405
  transport: "http",
4369
4406
  requestedModel: modelId,
4370
4407
  dispatch: "native",
4371
- phase: "dispatch",
4408
+ phase: "complete",
4372
4409
  provider: "openai-native",
4373
4410
  routeModel: modelId,
4374
- upstreamModel: modelId
4411
+ upstreamModel: modelId,
4412
+ outcome: nativeResponse.ok ? "ok" : "error",
4413
+ status: nativeResponse.status
4375
4414
  });
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;
4415
+ } catch (err) {
4416
+ audit({
4417
+ transport: "http",
4418
+ requestedModel: modelId,
4419
+ dispatch: "native",
4420
+ phase: "complete",
4421
+ provider: "openai-native",
4422
+ routeModel: modelId,
4423
+ upstreamModel: modelId,
4424
+ outcome: "error",
4425
+ status: "forward-failed"
4426
+ });
4427
+ if (!res.writableEnded) sendJson(res, 502, { error: { message: "Native Codex request failed", type: "upstream_error" } });
4415
4428
  }
4429
+ return;
4416
4430
  }
4417
4431
  }
4418
4432
  let resolved = subagentRoute ? resolveModel(routes, models, subagentRoute.modelId) : resolveModel(routes, models, modelId);
@@ -4511,11 +4525,13 @@ async function startCodexProxy(routes, options = {}) {
4511
4525
  }
4512
4526
  }
4513
4527
  };
4528
+ let streamFailure;
4514
4529
  try {
4515
4530
  if (v2Compaction) {
4516
4531
  await streamCompactionResponse(languageModel, params, modelId, write);
4517
4532
  } else
4518
4533
  await streamResponsesResponse(languageModel, params, modelId, write, (summary) => {
4534
+ if (summary.errorMessage || summary.aborted) streamFailure = summary;
4519
4535
  if (debug) {
4520
4536
  const failure = `${summary.aborted ? " aborted=yes" : ""}${summary.errorMessage ? ` error=${JSON.stringify(summary.errorMessage)}` : ""}`;
4521
4537
  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 +4549,7 @@ async function startCodexProxy(routes, options = {}) {
4533
4549
  provider: route.providerId ?? "relay",
4534
4550
  routeModel: route.modelId,
4535
4551
  upstreamModel: route.auditUpstreamModelId ?? route.upstreamModelId,
4536
- outcome: "ok",
4537
- status: 200
4552
+ ...streamOutcome(streamFailure, 200)
4538
4553
  });
4539
4554
  } catch (err) {
4540
4555
  const msg = formatUpstreamError(err);
@@ -4877,190 +4892,182 @@ data: ${JSON.stringify({ error: { message: "Invalid JSON", type: "invalid_reques
4877
4892
  if (debug && markedSubagent) {
4878
4893
  log15(`WS subagent dispatch: requested=${modelId} route=${subagentRoute?.modelId ?? "(none)"}`);
4879
4894
  }
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
4895
  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
4896
+ const dispatch = classifyCodexMixedDispatch({
4897
+ modelId,
4898
+ markedSubagent,
4899
+ subagentRoute,
4900
+ relayRoutes: routes,
4901
+ nativeModelIds: mixedNative.nativeModelIds
4902
+ });
4903
+ if (dispatch.kind === "unknown") {
4904
+ audit({ transport: "ws", requestedModel: modelId, dispatch: "unknown", phase: "complete", outcome: "error", status: 404 });
4905
+ sendWsEvent(`event: error
4898
4906
  data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}`, type: "invalid_request_error" } })}
4899
4907
 
4900
4908
  `);
4901
- closeSocket();
4909
+ closeSocket();
4910
+ return;
4911
+ }
4912
+ if (dispatch.kind === "native") {
4913
+ audit({
4914
+ transport: "ws",
4915
+ requestedModel: modelId,
4916
+ dispatch: "native",
4917
+ phase: "dispatch",
4918
+ provider: "openai-native",
4919
+ routeModel: modelId,
4920
+ upstreamModel: modelId
4921
+ });
4922
+ const nativeBody = prepareNativeCodexBody(body);
4923
+ if (debug && nativeBody !== body) {
4924
+ log15(`WS native history normalized: model=${modelId} converted Relay compaction for native verification`);
4925
+ }
4926
+ if (nativeActive && nativeUpstream) {
4927
+ if (nativeUpstream.readyState === WebSocket.OPEN) {
4928
+ if (debug) log15(`WS native forwarding next turn: model=${modelId}`);
4929
+ nativeSendTurn?.(nativeBody, modelId);
4930
+ } else if (debug) {
4931
+ log15(`WS native cannot forward next turn: upstream_state=${nativeUpstream.readyState}`);
4932
+ }
4902
4933
  return;
4903
4934
  }
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`);
4935
+ const wsTarget = mixedNative.nativeBaseUrl ? `${mixedNative.nativeBaseUrl.replace(/^http:/, "ws:").replace(/^https:/, "wss:").replace(/\/$/, "")}/responses` : void 0;
4936
+ const target = nativeResponsesWebSocketOptions({ headers: req.headers, wsUrl: wsTarget });
4937
+ let upstream;
4938
+ let nativeOpened = false;
4939
+ let nativeCompleted = false;
4940
+ let nativeTurnModelId = modelId;
4941
+ let nativeFrameCount = 0;
4942
+ let finished = false;
4943
+ let connectTimer;
4944
+ let firstFrameTimer;
4945
+ const clearTimers = () => {
4946
+ if (connectTimer) clearTimeout(connectTimer);
4947
+ if (firstFrameTimer) clearTimeout(firstFrameTimer);
4948
+ };
4949
+ const sendNativeError = (message) => {
4950
+ if (socket.destroyed) return;
4951
+ socket.write(wsEncodeTextFrame(JSON.stringify({
4952
+ type: "error",
4953
+ error: { type: "upstream_error", message }
4954
+ })));
4955
+ };
4956
+ const closeBoth = (message, closeCode = 1011) => {
4957
+ if (finished) return;
4958
+ finished = true;
4959
+ nativeActive = false;
4960
+ nativeSendTurn = void 0;
4961
+ if (nativeUpstream === upstream) nativeUpstream = void 0;
4962
+ clearTimers();
4963
+ if (debug && message) {
4964
+ log15(`WS native upstream failed: model=${nativeTurnModelId} opened=${nativeOpened} frames=${nativeFrameCount} message=${message}`);
4917
4965
  }
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
- }
4966
+ if (message && !nativeCompleted) {
4967
+ audit({
4968
+ transport: "ws",
4969
+ requestedModel: nativeTurnModelId,
4970
+ dispatch: "native",
4971
+ phase: "complete",
4972
+ provider: "openai-native",
4973
+ routeModel: nativeTurnModelId,
4974
+ upstreamModel: nativeTurnModelId,
4975
+ outcome: "error",
4976
+ status: "upstream-failed"
4977
+ });
4978
+ }
4979
+ if (message && !nativeCompleted) sendNativeError(message);
4980
+ try {
4981
+ upstream?.close();
4982
+ } catch {
4983
+ }
4984
+ closeSocket(closeCode);
4985
+ };
4986
+ const sendNativeTurn = (turnBody, turnModelId) => {
4987
+ if (!upstream || upstream.readyState !== WebSocket.OPEN) {
4988
+ if (debug) log15(`WS native cannot send turn: model=${turnModelId} upstream_state=${upstream?.readyState ?? "missing"}`);
4925
4989
  return;
4926
4990
  }
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 = () => {
4991
+ nativeTurnModelId = turnModelId;
4992
+ nativeCompleted = false;
4993
+ if (firstFrameTimer) clearTimeout(firstFrameTimer);
4994
+ upstream.send(JSON.stringify({ type: "response.create", ...turnBody }));
4995
+ firstFrameTimer = setTimeout(() => closeBoth("Native Codex WebSocket response timed out"), 6e4);
4996
+ };
4997
+ try {
4998
+ if (debug) {
4999
+ log15(`WS native connecting: model=${modelId} url=${target.url} headers=[${Object.keys(target.headers).join(",")}]`);
5000
+ }
5001
+ upstream = new WebSocket(target.url, { headers: target.headers });
5002
+ nativeUpstream = upstream;
5003
+ nativeSendTurn = sendNativeTurn;
5004
+ nativeActive = true;
5005
+ connectTimer = setTimeout(() => closeBoth("Native Codex WebSocket connection timed out"), 15e3);
5006
+ upstream.once("open", () => {
5007
+ nativeOpened = true;
4938
5008
  if (connectTimer) clearTimeout(connectTimer);
4939
- if (firstFrameTimer) clearTimeout(firstFrameTimer);
4940
- };
4941
- const sendNativeError = (message) => {
5009
+ if (debug) log15(`WS native upstream open: model=${modelId}`);
5010
+ sendNativeTurn(nativeBody, modelId);
5011
+ });
5012
+ upstream.once("unexpected-response", (_request, response) => {
5013
+ if (debug) log15(`WS native upstream HTTP rejection: model=${modelId} status=${response.statusCode}`);
5014
+ response.resume();
5015
+ closeBoth(`Native Codex WebSocket rejected (${response.statusCode})`);
5016
+ });
5017
+ upstream.on("message", (data) => {
4942
5018
  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;
5019
+ nativeFrameCount += 1;
5020
+ if (firstFrameTimer) clearTimeout(firstFrameTimer);
5021
+ const text6 = Array.isArray(data) ? Buffer.concat(data).toString("utf8") : data.toString("utf8");
5022
+ let eventType = "non-json";
5023
+ try {
5024
+ const parsed = JSON.parse(text6);
5025
+ if (typeof parsed.type === "string") eventType = parsed.type;
5026
+ if (eventType === "response.completed" || eventType === "response.failed" || eventType === "response.incomplete") {
5027
+ nativeCompleted = true;
5028
+ audit({
5029
+ transport: "ws",
5030
+ requestedModel: modelId,
5031
+ dispatch: "native",
5032
+ phase: "complete",
5033
+ provider: "openai-native",
5034
+ routeModel: modelId,
5035
+ upstreamModel: modelId,
5036
+ outcome: eventType === "response.completed" ? "ok" : "error",
5037
+ status: eventType
5038
+ });
5039
+ }
5040
+ } catch {
5041
+ }
5042
+ if (debug && (nativeFrameCount <= 3 || nativeCompleted || eventType === "error" || nativeFrameCount % 25 === 0)) {
5043
+ log15(`WS native frame#${nativeFrameCount}: model=${modelId} type=${eventType} bytes=${text6.length}`);
5044
+ }
5045
+ socket.write(wsEncodeTextFrame(text6));
5046
+ });
5047
+ upstream.once("error", (err) => closeBoth(`Native Codex WebSocket error: ${err.message}`));
5048
+ upstream.once("close", (code, reason) => {
5049
+ const detail = reason?.length ? ` reason=${reason.toString("utf8").slice(0, 200)}` : "";
5050
+ if (debug) log15(`WS native upstream close: model=${modelId} code=${code}${detail} frames=${nativeFrameCount}`);
5051
+ if (nativeUpstream === upstream) nativeUpstream = void 0;
5052
+ nativeActive = false;
5053
+ if (!finished) closeBoth(nativeCompleted ? void 0 : `Native Codex WebSocket closed before completion (${code})`);
5054
+ });
5055
+ socket.once("close", () => {
5056
+ if (debug) log15(`WS native downstream close: model=${modelId} frames=${nativeFrameCount} completed=${nativeCompleted}`);
4950
5057
  finished = true;
4951
5058
  nativeActive = false;
4952
5059
  nativeSendTurn = void 0;
4953
5060
  if (nativeUpstream === upstream) nativeUpstream = void 0;
4954
5061
  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
5062
  try {
4973
5063
  upstream?.close();
4974
5064
  } catch {
4975
5065
  }
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;
5066
+ });
5067
+ } catch (err) {
5068
+ closeBoth(`Native Codex WebSocket setup failed: ${err instanceof Error ? err.message : String(err)}`);
5063
5069
  }
5070
+ return;
5064
5071
  }
5065
5072
  }
5066
5073
  externalActive = true;
@@ -5095,6 +5102,7 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}` } })}
5095
5102
  currentExternalCompletedResponse = void 0;
5096
5103
  currentExternalStateInput = void 0;
5097
5104
  currentExternalConsumedResponseId = void 0;
5105
+ let streamFailure;
5098
5106
  const continuation = resolveExternalContinuation(body);
5099
5107
  if (continuation.orphanedResponseId) {
5100
5108
  if (debug) log15(`WS continuation rejected: unknown previous_response_id=${continuation.orphanedResponseId}`);
@@ -5155,6 +5163,7 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}` } })}
5155
5163
  await streamCompactionResponse(languageModel, params, modelId, sendWsEvent);
5156
5164
  } else
5157
5165
  await streamResponsesResponse(languageModel, params, modelId, sendWsEvent, (summary) => {
5166
+ if (summary.errorMessage || summary.aborted) streamFailure = summary;
5158
5167
  if (debug) {
5159
5168
  const failure = `${summary.aborted ? " aborted=yes" : ""}${summary.errorMessage ? ` error=${JSON.stringify(summary.errorMessage)}` : ""}`;
5160
5169
  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 +5187,7 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}` } })}
5178
5187
  provider: route.providerId ?? "relay",
5179
5188
  routeModel: route.modelId,
5180
5189
  upstreamModel: route.auditUpstreamModelId ?? route.upstreamModelId,
5181
- outcome: "ok",
5182
- status: "response.completed"
5190
+ ...streamOutcome(streamFailure, "response.completed")
5183
5191
  });
5184
5192
  } catch (err) {
5185
5193
  const msg = formatUpstreamError(err);
@@ -15973,7 +15981,7 @@ Options:
15973
15981
  --trace Write debug logs under ~/.relay-ai/logs/`);
15974
15982
  return 0;
15975
15983
  }
15976
- const { runUiCommand } = await import("./ui-command-WKPJI2CU.js");
15984
+ const { runUiCommand } = await import("./ui-command-QFXKL6HH.js");
15977
15985
  return runUiCommand({ trace: parsed.trace, serverMode: parsed.uiServerMode });
15978
15986
  }
15979
15987
  if (parsed.command === "models") {