@minniexcode/codex-switch 0.1.4 → 0.2.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.
@@ -174,6 +174,7 @@ async function createCopilotSession(runtimeClient, payload) {
174
174
  try {
175
175
  const session = await Promise.resolve(createSession({
176
176
  model: typeof payload.model === "string" ? payload.model : undefined,
177
+ streaming: true,
177
178
  ...createSessionOptions(runtimeClient.sdk),
178
179
  }));
179
180
  if (!session || typeof session !== "object") {
@@ -208,7 +209,16 @@ async function sendSessionRequest(session, prompt, timeoutMs, onStreamEvent) {
208
209
  onStreamEvent?.({ type: "delta", delta });
209
210
  }
210
211
  };
212
+ const runtimeHandler = (event) => {
213
+ for (const runtimeEvent of mapCopilotRuntimeEvent(event)) {
214
+ onStreamEvent?.({ type: "runtime", event: runtimeEvent });
215
+ if (runtimeEvent.type === "assistant.message_delta" && runtimeEvent.text.length > 0) {
216
+ onStreamEvent?.({ type: "delta", delta: runtimeEvent.text });
217
+ }
218
+ }
219
+ };
211
220
  if (onStreamEvent && session.on) {
221
+ session.on("event", runtimeHandler);
212
222
  session.on("data", deltaHandler);
213
223
  session.on("message", deltaHandler);
214
224
  session.on("delta", deltaHandler);
@@ -225,6 +235,7 @@ async function sendSessionRequest(session, prompt, timeoutMs, onStreamEvent) {
225
235
  }
226
236
  finally {
227
237
  if (onStreamEvent && session.off) {
238
+ session.off("event", runtimeHandler);
228
239
  session.off("data", deltaHandler);
229
240
  session.off("message", deltaHandler);
230
241
  session.off("delta", deltaHandler);
@@ -329,7 +340,12 @@ async function stopCopilotClient(client) {
329
340
  async function abortCopilotSession(session) {
330
341
  const abort = resolveCallable(session, "abort");
331
342
  if (abort) {
332
- await Promise.resolve(abort());
343
+ try {
344
+ await Promise.resolve(abort());
345
+ }
346
+ catch {
347
+ // Session may already be closed or unknown — safe to ignore
348
+ }
333
349
  }
334
350
  }
335
351
  async function disconnectCopilotSession(session) {
@@ -395,6 +411,115 @@ function extractDelta(event) {
395
411
  }
396
412
  return null;
397
413
  }
414
+ /**
415
+ * Maps SDK session events into the bridge's stable process-event contract.
416
+ */
417
+ function mapCopilotRuntimeEvent(event) {
418
+ if (!event || typeof event !== "object") {
419
+ return [];
420
+ }
421
+ const record = event;
422
+ const sdkType = readString(record, ["type", "event", "name", "eventName"]) ?? "unknown";
423
+ const normalizedType = sdkType.replace(/_/g, ".").toLowerCase();
424
+ const text = readString(record, ["text", "delta", "content", "message", "summary", "description"]);
425
+ const name = readString(record, ["toolName", "tool", "name"]);
426
+ const requestId = readString(record, ["requestId", "id", "callId"]);
427
+ const kind = readString(record, ["kind", "permission", "permissionKind"]);
428
+ const success = readBoolean(record, ["success", "ok"]);
429
+ const approved = readBoolean(record, ["approved", "allowed", "accepted"]);
430
+ const summary = truncateForBridgeLog(text ?? summarizeUnknownObject(record), 600);
431
+ if (normalizedType === "assistant.intent") {
432
+ return [{ type: "assistant.intent", text: summary }];
433
+ }
434
+ if (normalizedType === "assistant.message.delta" || normalizedType === "assistant.message_delta") {
435
+ return [{ type: "assistant.message_delta", text: text ?? "" }];
436
+ }
437
+ if (normalizedType === "assistant.reasoning.delta" || normalizedType === "assistant.reasoning_delta" || normalizedType === "reasoning.delta") {
438
+ return [{ type: "assistant.reasoning_delta", text: summary }];
439
+ }
440
+ if (normalizedType === "tool.execution.start" || normalizedType === "tool.execution_start") {
441
+ return [{ type: "tool.execution_start", name, requestId, summary: summary || `Tool started: ${name ?? "unknown"}` }];
442
+ }
443
+ if (normalizedType === "tool.execution.progress" || normalizedType === "tool.execution_progress") {
444
+ return [{ type: "tool.execution_progress", name, requestId, summary }];
445
+ }
446
+ if (normalizedType === "tool.execution.partial.result" || normalizedType === "tool.execution.partial_result") {
447
+ return [{ type: "tool.execution_partial_result", name, requestId, summary }];
448
+ }
449
+ if (normalizedType === "tool.execution.complete" || normalizedType === "tool.execution_complete") {
450
+ return [{ type: "tool.execution_complete", name, requestId, success, summary: summary || `Tool completed: ${name ?? "unknown"}` }];
451
+ }
452
+ if (normalizedType === "permission.requested" || normalizedType === "permission.request") {
453
+ return [{ type: "permission.requested", kind, requestId, summary: summary || `Copilot requested permission: ${kind ?? "unknown"}` }];
454
+ }
455
+ if (normalizedType === "permission.completed" || normalizedType === "permission.complete") {
456
+ return [{ type: "permission.completed", kind, requestId, approved, summary }];
457
+ }
458
+ if (normalizedType === "user.input.requested" || normalizedType === "user.input_request" || normalizedType === "user_input.requested") {
459
+ return [{ type: "user_input.requested", requestId, summary }];
460
+ }
461
+ if (normalizedType === "exit.plan.mode.requested" || normalizedType === "exit_plan_mode.requested") {
462
+ return [{ type: "exit_plan_mode.requested", requestId, summary }];
463
+ }
464
+ if (normalizedType === "session.error" || normalizedType === "error") {
465
+ return [{ type: "session.error", summary }];
466
+ }
467
+ if (normalizedType === "session.idle" || normalizedType === "idle") {
468
+ return [{ type: "session.idle", summary: summary || "Copilot session is idle." }];
469
+ }
470
+ return [{ type: "session.unknown", sdkType, summary }];
471
+ }
472
+ function readString(record, keys) {
473
+ for (const key of keys) {
474
+ const value = record[key];
475
+ if (typeof value === "string" && value.length > 0) {
476
+ return value;
477
+ }
478
+ if (value && typeof value === "object") {
479
+ const nested = value;
480
+ for (const nestedKey of ["name", "id", "text", "content", "message", "summary"]) {
481
+ if (typeof nested[nestedKey] === "string" && nested[nestedKey].length > 0) {
482
+ return nested[nestedKey];
483
+ }
484
+ }
485
+ }
486
+ }
487
+ return undefined;
488
+ }
489
+ function readBoolean(record, keys) {
490
+ for (const key of keys) {
491
+ if (typeof record[key] === "boolean") {
492
+ return record[key];
493
+ }
494
+ }
495
+ return undefined;
496
+ }
497
+ function summarizeUnknownObject(record) {
498
+ return JSON.stringify(record, (key, value) => {
499
+ if (isSensitiveKey(key)) {
500
+ return "[redacted]";
501
+ }
502
+ if (typeof value === "string") {
503
+ return redactSensitiveText(value);
504
+ }
505
+ return value;
506
+ });
507
+ }
508
+ function redactSensitiveText(value) {
509
+ if (/api[_-]?key|token|authorization|bearer\s+|sk-[a-z0-9_-]+/i.test(value)) {
510
+ return "[redacted]";
511
+ }
512
+ return value;
513
+ }
514
+ function isSensitiveKey(key) {
515
+ return /^(api[_-]?key|token|access[_-]?token|refresh[_-]?token|authorization|secret|password)$/i.test(key);
516
+ }
517
+ function truncateForBridgeLog(value, maxLength) {
518
+ if (value.length <= maxLength) {
519
+ return value;
520
+ }
521
+ return `${value.slice(0, maxLength)}... [truncated]`;
522
+ }
398
523
  function isAuthReady(status) {
399
524
  if (status === true) {
400
525
  return true;
@@ -43,6 +43,9 @@ async function main() {
43
43
  if (event.type === "delta") {
44
44
  options?.onTextDelta?.(event.delta);
45
45
  }
46
+ else if (event.type === "runtime") {
47
+ options?.onRuntimeEvent?.(event.event);
48
+ }
46
49
  else {
47
50
  options?.onTextDone?.(event.text);
48
51
  }
@@ -163,13 +163,13 @@ async function probeCopilotBridgeRuntime(provider, persistedState, runtimeDir) {
163
163
  /**
164
164
  * Starts or reuses a Copilot bridge worker, then verifies its health before returning.
165
165
  */
166
- async function ensureCopilotBridge(providerName, provider, runtimeDir, runtimesDir) {
167
- return startOrReuseCopilotBridge(providerName, provider, runtimeDir, runtimesDir);
166
+ async function ensureCopilotBridge(providerName, provider, runtimeDir, runtimesDir, toolHomeDir) {
167
+ return startOrReuseCopilotBridge(providerName, provider, runtimeDir, runtimesDir, toolHomeDir);
168
168
  }
169
169
  /**
170
170
  * Starts or reuses a Copilot bridge worker and reports the chosen port.
171
171
  */
172
- async function startOrReuseCopilotBridge(providerName, provider, runtimeDir, runtimesDir) {
172
+ async function startOrReuseCopilotBridge(providerName, provider, runtimeDir, runtimesDir, toolHomeDir) {
173
173
  if (!(0, providers_1.isCopilotBridgeProvider)(provider)) {
174
174
  throw (0, errors_1.cliError)("RUNTIME_PROVIDER_INVALID", "Provider is not backed by a Copilot bridge runtime.", {
175
175
  provider: providerName,
@@ -221,7 +221,7 @@ async function startOrReuseCopilotBridge(providerName, provider, runtimeDir, run
221
221
  }
222
222
  const selectedPort = await selectBridgePort(runtime.bridgeHost, runtime.bridgePort);
223
223
  const selectedBaseUrl = `http://${runtime.bridgeHost}:${selectedPort}${runtime.bridgePath}`;
224
- const workerPath = path.join(__dirname, "copilot-bridge-worker.js");
224
+ const workerPath = path.join(__dirname, "copilot-http-bridge-worker.js");
225
225
  ensureBridgeLogFile(logPath);
226
226
  appendBridgeLifecycleLog(logPath, `worker start provider=${providerName} host=${runtime.bridgeHost} port=${String(selectedPort)} replaced=${String(replaced)}`);
227
227
  let child;
@@ -239,6 +239,7 @@ async function startOrReuseCopilotBridge(providerName, provider, runtimeDir, run
239
239
  CODEX_SWITCH_RUNTIME_DIR: runtimeDir ?? "",
240
240
  CODEX_SWITCH_RUNTIMES_DIR: runtimesDir ?? "",
241
241
  CODEX_SWITCH_BRIDGE_LOG_PATH: logPath,
242
+ CODEX_SWITCH_TOOL_HOME_DIR: toolHomeDir ?? "",
242
243
  },
243
244
  });
244
245
  }
@@ -349,23 +350,27 @@ function createCopilotBridgeRequestHandler(context) {
349
350
  connection: "keep-alive",
350
351
  });
351
352
  const heartbeat = startSseHeartbeat(response);
352
- const payload = await context.executeChatCompletion(body, {
353
- timeoutMs,
354
- onTextDelta: (delta) => {
355
- response.write(`data: ${JSON.stringify({
356
- choices: [
357
- {
358
- index: 0,
359
- delta: { content: delta },
360
- finish_reason: null,
361
- },
362
- ],
363
- })}\n\n`);
364
- },
365
- });
366
- clearInterval(heartbeat);
367
- response.write("data: [DONE]\n\n");
368
- response.end();
353
+ try {
354
+ const payload = await context.executeChatCompletion(body, {
355
+ timeoutMs,
356
+ onTextDelta: (delta) => {
357
+ response.write(`data: ${JSON.stringify({
358
+ choices: [
359
+ {
360
+ index: 0,
361
+ delta: { content: delta },
362
+ finish_reason: null,
363
+ },
364
+ ],
365
+ })}\n\n`);
366
+ },
367
+ });
368
+ response.write("data: [DONE]\n\n");
369
+ response.end();
370
+ }
371
+ finally {
372
+ clearInterval(heartbeat);
373
+ }
369
374
  return;
370
375
  }
371
376
  const payload = await context.executeChatCompletion(body, { timeoutMs });
@@ -389,28 +394,36 @@ function createCopilotBridgeRequestHandler(context) {
389
394
  const responseId = `resp_${Date.now()}`;
390
395
  const messageId = buildResponsesMessageId(responseId);
391
396
  writeResponsesStreamStart(response, responseId, normalized.model, messageId);
397
+ writeResponsesReasoningPartAdded(response, responseId);
392
398
  const heartbeat = startSseHeartbeat(response);
393
399
  let text = "";
394
- const payload = await context.executeChatCompletion(chatPayload, {
395
- timeoutMs: normalized.timeoutMs,
396
- onTextDelta: (delta) => {
397
- text += delta;
398
- writeResponsesTextDelta(response, messageId, delta);
399
- },
400
- onTextDone: (doneText) => {
401
- if (text.length === 0) {
402
- text = doneText;
403
- writeResponsesTextDelta(response, messageId, doneText);
404
- }
405
- },
406
- });
407
- clearInterval(heartbeat);
408
- const outputText = text || getChatCompletionText(payload);
409
- if (text.length === 0 && outputText.length > 0) {
410
- writeResponsesTextDelta(response, messageId, outputText);
400
+ try {
401
+ const payload = await context.executeChatCompletion(chatPayload, {
402
+ timeoutMs: normalized.timeoutMs,
403
+ onTextDelta: (delta) => {
404
+ text += delta;
405
+ writeResponsesTextDelta(response, messageId, delta);
406
+ },
407
+ onTextDone: (doneText) => {
408
+ if (text.length === 0) {
409
+ text = doneText;
410
+ writeResponsesTextDelta(response, messageId, doneText);
411
+ }
412
+ },
413
+ onRuntimeEvent: (event) => {
414
+ writeResponsesRuntimeEvent(response, responseId, event);
415
+ },
416
+ });
417
+ const outputText = text || getChatCompletionText(payload);
418
+ if (text.length === 0 && outputText.length > 0) {
419
+ writeResponsesTextDelta(response, messageId, outputText);
420
+ }
421
+ writeResponsesStreamDone(response, responseId, normalized.model, messageId, outputText);
422
+ response.end();
423
+ }
424
+ finally {
425
+ clearInterval(heartbeat);
411
426
  }
412
- writeResponsesStreamDone(response, responseId, normalized.model, messageId, outputText);
413
- response.end();
414
427
  return;
415
428
  }
416
429
  const payload = await context.executeChatCompletion(chatPayload, {
@@ -429,9 +442,15 @@ function createCopilotBridgeRequestHandler(context) {
429
442
  response.end(JSON.stringify({ error: { message: "Not found" } }));
430
443
  }
431
444
  catch (error) {
432
- const statusCode = mapBridgeErrorStatus(error);
433
- response.writeHead(statusCode, { "content-type": "application/json" });
434
- response.end(JSON.stringify({ error: { message: error instanceof Error ? error.message : String(error), code: isCliError(error) ? error.code : "BRIDGE_RUNTIME_FAILURE" } }));
445
+ if (!response.headersSent) {
446
+ const statusCode = mapBridgeErrorStatus(error);
447
+ response.writeHead(statusCode, { "content-type": "application/json" });
448
+ response.end(JSON.stringify({ error: { message: error instanceof Error ? error.message : String(error), code: isCliError(error) ? error.code : "BRIDGE_RUNTIME_FAILURE" } }));
449
+ }
450
+ else if (!response.writableEnded) {
451
+ response.write(`data: ${JSON.stringify({ error: { message: error instanceof Error ? error.message : String(error), code: isCliError(error) ? error.code : "BRIDGE_RUNTIME_FAILURE" } })}\n\n`);
452
+ response.end();
453
+ }
435
454
  }
436
455
  };
437
456
  }
@@ -628,7 +647,7 @@ function writeResponsesStream(response, payload) {
628
647
  });
629
648
  writeSseEvent(response, "response.output_item.added", {
630
649
  type: "response.output_item.added",
631
- output_index: 0,
650
+ output_index: 1,
632
651
  item: {
633
652
  id: messageId,
634
653
  type: "message",
@@ -640,7 +659,7 @@ function writeResponsesStream(response, payload) {
640
659
  writeSseEvent(response, "response.content_part.added", {
641
660
  type: "response.content_part.added",
642
661
  item_id: messageId,
643
- output_index: 0,
662
+ output_index: 1,
644
663
  content_index: 0,
645
664
  part: {
646
665
  type: "output_text",
@@ -651,21 +670,21 @@ function writeResponsesStream(response, payload) {
651
670
  writeSseEvent(response, "response.output_text.delta", {
652
671
  type: "response.output_text.delta",
653
672
  item_id: messageId,
654
- output_index: 0,
673
+ output_index: 1,
655
674
  content_index: 0,
656
675
  delta: outputText,
657
676
  });
658
677
  writeSseEvent(response, "response.output_text.done", {
659
678
  type: "response.output_text.done",
660
679
  item_id: messageId,
661
- output_index: 0,
680
+ output_index: 1,
662
681
  content_index: 0,
663
682
  text: outputText,
664
683
  });
665
684
  writeSseEvent(response, "response.content_part.done", {
666
685
  type: "response.content_part.done",
667
686
  item_id: messageId,
668
- output_index: 0,
687
+ output_index: 1,
669
688
  content_index: 0,
670
689
  part: {
671
690
  type: "output_text",
@@ -675,7 +694,7 @@ function writeResponsesStream(response, payload) {
675
694
  });
676
695
  writeSseEvent(response, "response.output_item.done", {
677
696
  type: "response.output_item.done",
678
- output_index: 0,
697
+ output_index: 1,
679
698
  item: completedMessage,
680
699
  });
681
700
  writeSseEvent(response, "response.completed", {
@@ -707,7 +726,7 @@ function writeResponsesStreamStart(response, responseId, model, messageId) {
707
726
  });
708
727
  writeSseEvent(response, "response.output_item.added", {
709
728
  type: "response.output_item.added",
710
- output_index: 0,
729
+ output_index: 1,
711
730
  item: {
712
731
  id: messageId,
713
732
  type: "message",
@@ -719,7 +738,7 @@ function writeResponsesStreamStart(response, responseId, model, messageId) {
719
738
  writeSseEvent(response, "response.content_part.added", {
720
739
  type: "response.content_part.added",
721
740
  item_id: messageId,
722
- output_index: 0,
741
+ output_index: 1,
723
742
  content_index: 0,
724
743
  part: {
725
744
  type: "output_text",
@@ -735,7 +754,7 @@ function writeResponsesTextDelta(response, messageId, delta) {
735
754
  writeSseEvent(response, "response.output_text.delta", {
736
755
  type: "response.output_text.delta",
737
756
  item_id: messageId,
738
- output_index: 0,
757
+ output_index: 1,
739
758
  content_index: 0,
740
759
  delta,
741
760
  });
@@ -757,14 +776,14 @@ function writeResponsesStreamDone(response, responseId, model, messageId, output
757
776
  writeSseEvent(response, "response.output_text.done", {
758
777
  type: "response.output_text.done",
759
778
  item_id: messageId,
760
- output_index: 0,
779
+ output_index: 1,
761
780
  content_index: 0,
762
781
  text: outputText,
763
782
  });
764
783
  writeSseEvent(response, "response.content_part.done", {
765
784
  type: "response.content_part.done",
766
785
  item_id: messageId,
767
- output_index: 0,
786
+ output_index: 1,
768
787
  content_index: 0,
769
788
  part: {
770
789
  type: "output_text",
@@ -774,7 +793,7 @@ function writeResponsesStreamDone(response, responseId, model, messageId, output
774
793
  });
775
794
  writeSseEvent(response, "response.output_item.done", {
776
795
  type: "response.output_item.done",
777
- output_index: 0,
796
+ output_index: 1,
778
797
  item: completedMessage,
779
798
  });
780
799
  writeSseEvent(response, "response.completed", {
@@ -790,9 +809,132 @@ function writeResponsesStreamDone(response, responseId, model, messageId, output
790
809
  },
791
810
  });
792
811
  }
812
+ function writeResponsesRuntimeEvent(response, responseId, event) {
813
+ if (event.type === "assistant.message_delta") {
814
+ return;
815
+ }
816
+ if (event.type === "session.unknown") {
817
+ process.stderr.write(`[${new Date().toISOString()}] bridge runtime event ignored type=${event.sdkType ?? "unknown"} summary=${truncateBridgeText(event.summary ?? "", 240)}\n`);
818
+ return;
819
+ }
820
+ const text = formatRuntimeEventText(event);
821
+ if (text.length === 0) {
822
+ return;
823
+ }
824
+ writeResponsesReasoningDelta(response, responseId, text);
825
+ writeResponsesCommentaryItem(response, responseId, text);
826
+ process.stderr.write(`[${new Date().toISOString()}] bridge runtime event type=${event.type} summary=${truncateBridgeText(text, 240)}\n`);
827
+ }
828
+ function writeResponsesReasoningDelta(response, responseId, text) {
829
+ const reasoningId = `${responseId}_rs_0`;
830
+ writeSseEvent(response, "response.reasoning_summary_text.delta", {
831
+ type: "response.reasoning_summary_text.delta",
832
+ item_id: reasoningId,
833
+ output_index: 0,
834
+ summary_index: 0,
835
+ delta: text,
836
+ });
837
+ }
838
+ function writeResponsesReasoningPartAdded(response, responseId) {
839
+ const reasoningId = `${responseId}_rs_0`;
840
+ writeSseEvent(response, "response.output_item.added", {
841
+ type: "response.output_item.added",
842
+ output_index: 0,
843
+ item: {
844
+ id: reasoningId,
845
+ type: "reasoning",
846
+ status: "in_progress",
847
+ summary: [],
848
+ },
849
+ });
850
+ writeSseEvent(response, "response.reasoning_summary_part.added", {
851
+ type: "response.reasoning_summary_part.added",
852
+ item_id: reasoningId,
853
+ output_index: 0,
854
+ summary_index: 0,
855
+ part: {
856
+ type: "summary_text",
857
+ text: "",
858
+ },
859
+ });
860
+ }
861
+ function writeResponsesCommentaryItem(response, responseId, text) {
862
+ const itemId = `${responseId}_commentary_${Date.now()}_${Math.floor(Math.random() * 100000)}`;
863
+ writeSseEvent(response, "response.output_item.done", {
864
+ type: "response.output_item.done",
865
+ output_index: 1,
866
+ item: {
867
+ id: itemId,
868
+ type: "message",
869
+ status: "completed",
870
+ role: "assistant",
871
+ phase: "commentary",
872
+ content: [
873
+ {
874
+ type: "output_text",
875
+ text,
876
+ annotations: [],
877
+ },
878
+ ],
879
+ },
880
+ });
881
+ }
882
+ function formatRuntimeEventText(event) {
883
+ switch (event.type) {
884
+ case "assistant.intent":
885
+ return truncateBridgeText(event.text ?? "", 600);
886
+ case "assistant.reasoning_delta":
887
+ return truncateBridgeText(event.text ?? "", 600);
888
+ case "tool.execution_start":
889
+ return truncateBridgeText(`Copilot started ${formatToolName(event.name)}${formatRequestId(event.requestId)}${formatSummarySuffix(event.summary)}`, 600);
890
+ case "tool.execution_progress":
891
+ return truncateBridgeText(`Copilot progress ${formatToolName(event.name)}${formatRequestId(event.requestId)}${formatSummarySuffix(event.summary)}`, 600);
892
+ case "tool.execution_partial_result":
893
+ return truncateBridgeText(`Copilot partial result ${formatToolName(event.name)}${formatRequestId(event.requestId)}${formatSummarySuffix(event.summary)}`, 600);
894
+ case "tool.execution_complete":
895
+ return truncateBridgeText(`Copilot ${event.success === false ? "failed" : "completed"} ${formatToolName(event.name)}${formatRequestId(event.requestId)}${formatSummarySuffix(event.summary)}`, 600);
896
+ case "permission.requested":
897
+ return truncateBridgeText(`Copilot requested permission${event.kind ? `: ${event.kind}` : ""}${formatRequestId(event.requestId)}${formatSummarySuffix(event.summary)}`, 600);
898
+ case "permission.completed":
899
+ return truncateBridgeText(`Copilot permission ${event.approved === false ? "denied" : "approved"}${event.kind ? `: ${event.kind}` : ""}${formatRequestId(event.requestId)}${formatSummarySuffix(event.summary)}`, 600);
900
+ case "user_input.requested":
901
+ return truncateBridgeText(`Copilot requested user input${formatRequestId(event.requestId)}${formatSummarySuffix(event.summary)}`, 600);
902
+ case "exit_plan_mode.requested":
903
+ return truncateBridgeText(`Copilot requested to exit plan mode${formatRequestId(event.requestId)}${formatSummarySuffix(event.summary)}`, 600);
904
+ case "session.error":
905
+ return truncateBridgeText(`Copilot session error${formatSummarySuffix(event.summary)}`, 600);
906
+ case "session.idle":
907
+ return truncateBridgeText(event.summary ?? "", 600);
908
+ case "assistant.message_delta":
909
+ case "session.unknown":
910
+ return "";
911
+ default:
912
+ return "";
913
+ }
914
+ }
915
+ function formatToolName(name) {
916
+ return name ? `tool ${name}` : "tool";
917
+ }
918
+ function formatRequestId(requestId) {
919
+ return requestId ? ` (${requestId})` : "";
920
+ }
921
+ function formatSummarySuffix(summary) {
922
+ if (!summary || summary.length === 0) {
923
+ return "";
924
+ }
925
+ return `: ${summary}`;
926
+ }
927
+ function truncateBridgeText(value, maxLength) {
928
+ if (value.length <= maxLength) {
929
+ return value;
930
+ }
931
+ return `${value.slice(0, maxLength)}... [truncated]`;
932
+ }
793
933
  function startSseHeartbeat(response) {
794
934
  return setInterval(() => {
795
- response.write(": keep-alive\n\n");
935
+ if (!response.writableEnded) {
936
+ response.write(": keep-alive\n\n");
937
+ }
796
938
  }, 15000);
797
939
  }
798
940
  function getChatCompletionText(payload) {