@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.
- package/README.AI.md +5 -4
- package/README.CN.md +9 -8
- package/README.md +9 -8
- package/dist/app/add-provider.js +10 -16
- package/dist/app/bridge.js +8 -13
- package/dist/app/get-status.js +15 -12
- package/dist/app/run-doctor.js +17 -18
- package/dist/app/switch-provider.js +6 -11
- package/dist/commands/handlers.js +32 -69
- package/dist/domain/providers.js +9 -9
- package/dist/runtime/copilot-adapter.js +126 -1
- package/dist/runtime/copilot-bridge-worker.js +3 -0
- package/dist/runtime/copilot-bridge.js +198 -56
- package/dist/runtime/copilot-http-bridge-worker.js +228 -0
- package/dist/runtime/copilot-token.js +294 -0
- package/docs/Design/codex-switch-v0.1.5-design.md +17 -0
- package/docs/Design/codex-switch-v0.2.0-design.md +56 -0
- package/docs/PRD/codex-switch-prd-v0.1.5.md +42 -0
- package/docs/Tests/testing.md +2 -2
- package/docs/cli-usage.md +11 -7
- package/docs/codex-switch-product-overview.md +24 -23
- package/docs/codex-switch-technical-architecture.md +2 -2
- package/package.json +2 -2
|
@@ -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
|
-
|
|
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;
|
|
@@ -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
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
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
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
text
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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.
|
|
935
|
+
if (!response.writableEnded) {
|
|
936
|
+
response.write(": keep-alive\n\n");
|
|
937
|
+
}
|
|
796
938
|
}, 15000);
|
|
797
939
|
}
|
|
798
940
|
function getChatCompletionText(payload) {
|