@minniexcode/codex-switch 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.AI.md CHANGED
@@ -6,7 +6,7 @@ This file summarizes the current operational contract for AI agents, automation
6
6
 
7
7
  - Package: `@minniexcode/codex-switch`
8
8
  - CLI name: `codexs`
9
- - Current repository version: `0.1.4`
9
+ - Current repository version: `0.1.5`
10
10
  - Version status: development line
11
11
  - Runtime contract target: Codex `0.134.0+`
12
12
 
@@ -22,7 +22,7 @@ Direct provider workflow:
22
22
 
23
23
  ```bash
24
24
  codexs init
25
- codexs add <provider> --model <model> --api-key <key> [--base-url <url>]
25
+ codexs add <provider> --profile <model-provider-id> --model <model> --api-key <key> [--base-url <url>]
26
26
  codexs switch <provider>
27
27
  codexs status
28
28
  codexs doctor
@@ -33,7 +33,7 @@ GitHub Copilot workflow:
33
33
  ```bash
34
34
  codexs init
35
35
  codexs login copilot
36
- codexs add <provider> --copilot --model <model>
36
+ codexs add <provider> --copilot --profile <model-provider-id> --model <model>
37
37
  codexs switch <provider>
38
38
  codexs status
39
39
  codexs doctor
@@ -130,10 +130,11 @@ Important behavioral constraints:
130
130
  - `login copilot` requires a real TTY and does not support `--json`.
131
131
  - `login copilot` currently installs the local Copilot SDK when needed, tries the bundled runtime CLI first, falls back to `PATH` when necessary, and rechecks auth readiness before reporting success.
132
132
  - `add --copilot` assumes SDK install and upstream Copilot auth are already ready.
133
+ - Non-interactive automation should pass `--profile` explicitly. In TTY mode, `add` and `edit` can prompt for missing required fields.
133
134
  - `migrate` remains interactive when provider adoption requires human input.
134
135
  - `status` is the main dual-path summary command.
135
136
  - `doctor` is the deeper repair-oriented diagnostic command.
136
- - The current `0.1.4` line focuses on bridge stability, surfaced runtime log metadata, and stricter release-hygiene verification rather than command-surface expansion.
137
+ - The current `0.1.5` line focuses on Copilot Bridge process visibility, Responses commentary/reasoning stream events, defensive SDK-event normalization, and unknown-event redaction hardening rather than command-surface expansion.
137
138
 
138
139
  ## Safety Notes
139
140
 
package/README.CN.md CHANGED
@@ -6,9 +6,9 @@
6
6
 
7
7
  ## 版本定位
8
8
 
9
- 当前包版本:`0.1.4`
9
+ 当前包版本:`0.1.5`
10
10
 
11
- 这是当前仓库开发线。`0.1.4` 聚焦于 bridge 稳定性与可观测性,包括 bridge 复用探测加固、运行态日志元数据外显,以及更严格的 release hygiene 门禁,同时不扩展 provider 命令面。
11
+ 这是当前仓库开发线。`0.1.5` Copilot Bridge 过程可见性补丁,聚焦于 commentary/reasoning 流式信号、SDK 事件防御性归一化,以及未知运行态事件的更安全脱敏,同时不扩展 provider 命令面。
12
12
 
13
13
  ## 安装
14
14
 
@@ -34,7 +34,7 @@ Direct provider 主路径:
34
34
 
35
35
  ```bash
36
36
  codexs init
37
- codexs add my-provider --model gpt-5.5 --base-url https://gateway.example.com/v1 --api-key sk-xxx
37
+ codexs add my-provider --profile my-provider --model gpt-5.5 --base-url https://gateway.example.com/v1 --api-key sk-xxx
38
38
  codexs switch my-provider
39
39
  codexs status
40
40
  codexs doctor
@@ -45,7 +45,7 @@ GitHub Copilot 主路径:
45
45
  ```bash
46
46
  codexs init
47
47
  codexs login copilot
48
- codexs add copilot-main --copilot --model gpt-4.1
48
+ codexs add copilot-main --copilot --profile copilot-main --model gpt-4.1
49
49
  codexs switch copilot-main
50
50
  codexs status
51
51
  codexs doctor
@@ -56,6 +56,7 @@ codexs doctor
56
56
  - `init` 负责初始化 `codex-switch` 的 tool home 与受管状态文件。
57
57
  - `login copilot` 负责上游 Copilot onboarding 和登录可用性检查。
58
58
  - `add --copilot` 不负责替你登录,它假设上游 Copilot 已经 ready。
59
+ - 非交互调用请显式传入 `--profile`;在 TTY 模式下,`add` 和 `edit` 可以补问缺失的必填项。
59
60
  - `switch` 会把选中的 provider 投影到目标 Codex runtime 的顶层 `model` 与 `model_provider`。
60
61
  - `status` 是切换后的主读取命令。
61
62
  - `doctor` 是主诊断命令,用于解释问题和下一步修复动作。
@@ -116,8 +117,8 @@ codexs current
116
117
  codexs status
117
118
  codexs config show [profile]
118
119
  codexs config list-profiles
119
- codexs add <provider> --model <model> --api-key <key> [--base-url <url>]
120
- codexs add <provider> --copilot --model <model>
120
+ codexs add <provider> --profile <model-provider-id> --model <model> --api-key <key> [--base-url <url>]
121
+ codexs add <provider> --copilot --profile <model-provider-id> --model <model>
121
122
  codexs edit <provider>
122
123
  codexs switch <provider>
123
124
  codexs remove <provider> [--force] [--switch-to <provider>]
@@ -209,8 +210,8 @@ npm pack --dry-run
209
210
  - [Design 0.1.1](./docs/Design/codex-switch-v0.1.1-design.md)
210
211
  - [PRD 0.1.2](./docs/PRD/codex-switch-prd-v0.1.2.md)
211
212
  - [Design 0.1.2](./docs/Design/codex-switch-v0.1.2-design.md)
212
- - [PRD 0.1.4](./docs/PRD/codex-switch-prd-v0.1.4.md)
213
- - [Design 0.1.4](./docs/Design/codex-switch-v0.1.4-design.md)
213
+ - [PRD 0.1.5](./docs/PRD/codex-switch-prd-v0.1.5.md)
214
+ - [Design 0.1.5](./docs/Design/codex-switch-v0.1.5-design.md)
214
215
 
215
216
  ## License
216
217
 
package/README.md CHANGED
@@ -8,9 +8,9 @@ Chinese version: [README.CN.md](./README.CN.md)
8
8
 
9
9
  ## Version
10
10
 
11
- Current package version: `0.1.4`
11
+ Current package version: `0.1.5`
12
12
 
13
- This is the current repository development line. `0.1.4` is the bridge stability and observability line, focused on bridge reuse hardening, surfaced runtime log metadata, and a stricter release-hygiene gate while keeping the provider surface unchanged.
13
+ This is the current repository development line. `0.1.5` is a Copilot Bridge process-visibility patch, focused on streaming commentary/reasoning signals, defensive SDK-event normalization, and safer redaction for unknown runtime events while keeping the provider surface unchanged.
14
14
 
15
15
  ## Install
16
16
 
@@ -36,7 +36,7 @@ Direct provider workflow:
36
36
 
37
37
  ```bash
38
38
  codexs init
39
- codexs add my-provider --model gpt-5.5 --base-url https://gateway.example.com/v1 --api-key sk-xxx
39
+ codexs add my-provider --profile my-provider --model gpt-5.5 --base-url https://gateway.example.com/v1 --api-key sk-xxx
40
40
  codexs switch my-provider
41
41
  codexs status
42
42
  codexs doctor
@@ -47,7 +47,7 @@ GitHub Copilot workflow:
47
47
  ```bash
48
48
  codexs init
49
49
  codexs login copilot
50
- codexs add copilot-main --copilot --model gpt-4.1
50
+ codexs add copilot-main --copilot --profile copilot-main --model gpt-4.1
51
51
  codexs switch copilot-main
52
52
  codexs status
53
53
  codexs doctor
@@ -58,6 +58,7 @@ Notes:
58
58
  - `init` prepares the `codex-switch` tool home and managed state.
59
59
  - `login copilot` handles upstream Copilot onboarding and auth readiness.
60
60
  - `add --copilot` does not perform login for you; it assumes Copilot login is already ready.
61
+ - For non-interactive use, pass `--profile` explicitly. In TTY mode, `add` and `edit` can prompt for missing required fields.
61
62
  - Copilot support is an experimental local bridge. The managed installer defaults to `@github/copilot-sdk@1.0.2`, Copilot runtime paths require Node.js `>=20`, and runtime checks separately reject older or prerelease SDK installs while validating API shape when the client or session is used.
62
63
  - `switch` projects the selected provider into the target Codex runtime as top-level `model` plus `model_provider`.
63
64
  - `status` is the main read command after switching.
@@ -125,8 +126,8 @@ codexs current
125
126
  codexs status
126
127
  codexs config show [profile]
127
128
  codexs config list-profiles
128
- codexs add <provider> --model <model> --api-key <key> [--base-url <url>]
129
- codexs add <provider> --copilot --model <model>
129
+ codexs add <provider> --profile <model-provider-id> --model <model> --api-key <key> [--base-url <url>]
130
+ codexs add <provider> --copilot --profile <model-provider-id> --model <model>
130
131
  codexs edit <provider>
131
132
  codexs switch <provider>
132
133
  codexs remove <provider> [--force] [--switch-to <provider>]
@@ -218,10 +219,10 @@ npm pack --dry-run
218
219
  - [PRD 0.1.1](./docs/PRD/codex-switch-prd-v0.1.1.md)
219
220
  - [PRD 0.1.2](./docs/PRD/codex-switch-prd-v0.1.2.md)
220
221
  - [PRD 0.1.3](./docs/PRD/codex-switch-prd-v0.1.3.md)
221
- - [PRD 0.1.4](./docs/PRD/codex-switch-prd-v0.1.4.md)
222
+ - [PRD 0.1.5](./docs/PRD/codex-switch-prd-v0.1.5.md)
222
223
  - [Design 0.1.2](./docs/Design/codex-switch-v0.1.2-design.md)
223
224
  - [Design 0.1.3](./docs/Design/codex-switch-v0.1.3-design.md)
224
- - [Design 0.1.4](./docs/Design/codex-switch-v0.1.4-design.md)
225
+ - [Design 0.1.5](./docs/Design/codex-switch-v0.1.5-design.md)
225
226
 
226
227
  ## License
227
228
 
@@ -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);
@@ -395,6 +406,115 @@ function extractDelta(event) {
395
406
  }
396
407
  return null;
397
408
  }
409
+ /**
410
+ * Maps SDK session events into the bridge's stable process-event contract.
411
+ */
412
+ function mapCopilotRuntimeEvent(event) {
413
+ if (!event || typeof event !== "object") {
414
+ return [];
415
+ }
416
+ const record = event;
417
+ const sdkType = readString(record, ["type", "event", "name", "eventName"]) ?? "unknown";
418
+ const normalizedType = sdkType.replace(/_/g, ".").toLowerCase();
419
+ const text = readString(record, ["text", "delta", "content", "message", "summary", "description"]);
420
+ const name = readString(record, ["toolName", "tool", "name"]);
421
+ const requestId = readString(record, ["requestId", "id", "callId"]);
422
+ const kind = readString(record, ["kind", "permission", "permissionKind"]);
423
+ const success = readBoolean(record, ["success", "ok"]);
424
+ const approved = readBoolean(record, ["approved", "allowed", "accepted"]);
425
+ const summary = truncateForBridgeLog(text ?? summarizeUnknownObject(record), 600);
426
+ if (normalizedType === "assistant.intent") {
427
+ return [{ type: "assistant.intent", text: summary }];
428
+ }
429
+ if (normalizedType === "assistant.message.delta" || normalizedType === "assistant.message_delta") {
430
+ return [{ type: "assistant.message_delta", text: text ?? "" }];
431
+ }
432
+ if (normalizedType === "assistant.reasoning.delta" || normalizedType === "assistant.reasoning_delta" || normalizedType === "reasoning.delta") {
433
+ return [{ type: "assistant.reasoning_delta", text: summary }];
434
+ }
435
+ if (normalizedType === "tool.execution.start" || normalizedType === "tool.execution_start") {
436
+ return [{ type: "tool.execution_start", name, requestId, summary: summary || `Tool started: ${name ?? "unknown"}` }];
437
+ }
438
+ if (normalizedType === "tool.execution.progress" || normalizedType === "tool.execution_progress") {
439
+ return [{ type: "tool.execution_progress", name, requestId, summary }];
440
+ }
441
+ if (normalizedType === "tool.execution.partial.result" || normalizedType === "tool.execution.partial_result") {
442
+ return [{ type: "tool.execution_partial_result", name, requestId, summary }];
443
+ }
444
+ if (normalizedType === "tool.execution.complete" || normalizedType === "tool.execution_complete") {
445
+ return [{ type: "tool.execution_complete", name, requestId, success, summary: summary || `Tool completed: ${name ?? "unknown"}` }];
446
+ }
447
+ if (normalizedType === "permission.requested" || normalizedType === "permission.request") {
448
+ return [{ type: "permission.requested", kind, requestId, summary: summary || `Copilot requested permission: ${kind ?? "unknown"}` }];
449
+ }
450
+ if (normalizedType === "permission.completed" || normalizedType === "permission.complete") {
451
+ return [{ type: "permission.completed", kind, requestId, approved, summary }];
452
+ }
453
+ if (normalizedType === "user.input.requested" || normalizedType === "user.input_request" || normalizedType === "user_input.requested") {
454
+ return [{ type: "user_input.requested", requestId, summary }];
455
+ }
456
+ if (normalizedType === "exit.plan.mode.requested" || normalizedType === "exit_plan_mode.requested") {
457
+ return [{ type: "exit_plan_mode.requested", requestId, summary }];
458
+ }
459
+ if (normalizedType === "session.error" || normalizedType === "error") {
460
+ return [{ type: "session.error", summary }];
461
+ }
462
+ if (normalizedType === "session.idle" || normalizedType === "idle") {
463
+ return [{ type: "session.idle", summary: summary || "Copilot session is idle." }];
464
+ }
465
+ return [{ type: "session.unknown", sdkType, summary }];
466
+ }
467
+ function readString(record, keys) {
468
+ for (const key of keys) {
469
+ const value = record[key];
470
+ if (typeof value === "string" && value.length > 0) {
471
+ return value;
472
+ }
473
+ if (value && typeof value === "object") {
474
+ const nested = value;
475
+ for (const nestedKey of ["name", "id", "text", "content", "message", "summary"]) {
476
+ if (typeof nested[nestedKey] === "string" && nested[nestedKey].length > 0) {
477
+ return nested[nestedKey];
478
+ }
479
+ }
480
+ }
481
+ }
482
+ return undefined;
483
+ }
484
+ function readBoolean(record, keys) {
485
+ for (const key of keys) {
486
+ if (typeof record[key] === "boolean") {
487
+ return record[key];
488
+ }
489
+ }
490
+ return undefined;
491
+ }
492
+ function summarizeUnknownObject(record) {
493
+ return JSON.stringify(record, (key, value) => {
494
+ if (isSensitiveKey(key)) {
495
+ return "[redacted]";
496
+ }
497
+ if (typeof value === "string") {
498
+ return redactSensitiveText(value);
499
+ }
500
+ return value;
501
+ });
502
+ }
503
+ function redactSensitiveText(value) {
504
+ if (/api[_-]?key|token|authorization|bearer\s+|sk-[a-z0-9_-]+/i.test(value)) {
505
+ return "[redacted]";
506
+ }
507
+ return value;
508
+ }
509
+ function isSensitiveKey(key) {
510
+ return /^(api[_-]?key|token|access[_-]?token|refresh[_-]?token|authorization|secret|password)$/i.test(key);
511
+ }
512
+ function truncateForBridgeLog(value, maxLength) {
513
+ if (value.length <= maxLength) {
514
+ return value;
515
+ }
516
+ return `${value.slice(0, maxLength)}... [truncated]`;
517
+ }
398
518
  function isAuthReady(status) {
399
519
  if (status === true) {
400
520
  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
  }
@@ -403,6 +403,9 @@ function createCopilotBridgeRequestHandler(context) {
403
403
  writeResponsesTextDelta(response, messageId, doneText);
404
404
  }
405
405
  },
406
+ onRuntimeEvent: (event) => {
407
+ writeResponsesRuntimeEvent(response, responseId, event);
408
+ },
406
409
  });
407
410
  clearInterval(heartbeat);
408
411
  const outputText = text || getChatCompletionText(payload);
@@ -790,6 +793,112 @@ function writeResponsesStreamDone(response, responseId, model, messageId, output
790
793
  },
791
794
  });
792
795
  }
796
+ function writeResponsesRuntimeEvent(response, responseId, event) {
797
+ if (event.type === "assistant.message_delta") {
798
+ return;
799
+ }
800
+ if (event.type === "assistant.reasoning_delta") {
801
+ writeResponsesReasoningDelta(response, responseId, formatRuntimeEventText(event));
802
+ return;
803
+ }
804
+ if (event.type === "session.unknown") {
805
+ process.stderr.write(`[${new Date().toISOString()}] bridge runtime event ignored type=${event.sdkType} summary=${truncateBridgeText(event.summary, 240)}\n`);
806
+ return;
807
+ }
808
+ const text = formatRuntimeEventText(event);
809
+ if (text.length === 0) {
810
+ return;
811
+ }
812
+ writeResponsesCommentaryItem(response, responseId, text);
813
+ process.stderr.write(`[${new Date().toISOString()}] bridge runtime event type=${event.type} summary=${truncateBridgeText(text, 240)}\n`);
814
+ }
815
+ function writeResponsesReasoningDelta(response, responseId, text) {
816
+ const reasoningId = `${responseId}_rs_0`;
817
+ writeSseEvent(response, "response.reasoning_summary_part.added", {
818
+ type: "response.reasoning_summary_part.added",
819
+ item_id: reasoningId,
820
+ output_index: 0,
821
+ summary_index: 0,
822
+ part: {
823
+ type: "summary_text",
824
+ text: "",
825
+ },
826
+ });
827
+ writeSseEvent(response, "response.reasoning_summary_text.delta", {
828
+ type: "response.reasoning_summary_text.delta",
829
+ item_id: reasoningId,
830
+ output_index: 0,
831
+ summary_index: 0,
832
+ delta: text,
833
+ });
834
+ }
835
+ function writeResponsesCommentaryItem(response, responseId, text) {
836
+ const itemId = `${responseId}_commentary_${Date.now()}_${Math.floor(Math.random() * 100000)}`;
837
+ writeSseEvent(response, "response.output_item.done", {
838
+ type: "response.output_item.done",
839
+ output_index: 0,
840
+ item: {
841
+ id: itemId,
842
+ type: "message",
843
+ status: "completed",
844
+ role: "assistant",
845
+ phase: "commentary",
846
+ content: [
847
+ {
848
+ type: "output_text",
849
+ text,
850
+ annotations: [],
851
+ },
852
+ ],
853
+ },
854
+ });
855
+ }
856
+ function formatRuntimeEventText(event) {
857
+ switch (event.type) {
858
+ case "assistant.intent":
859
+ return truncateBridgeText(event.text, 600);
860
+ case "assistant.reasoning_delta":
861
+ return truncateBridgeText(event.text, 600);
862
+ case "tool.execution_start":
863
+ return truncateBridgeText(`Copilot started ${formatToolName(event.name)}${formatRequestId(event.requestId)}${formatSummarySuffix(event.summary)}`, 600);
864
+ case "tool.execution_progress":
865
+ return truncateBridgeText(`Copilot progress ${formatToolName(event.name)}${formatRequestId(event.requestId)}${formatSummarySuffix(event.summary)}`, 600);
866
+ case "tool.execution_partial_result":
867
+ return truncateBridgeText(`Copilot partial result ${formatToolName(event.name)}${formatRequestId(event.requestId)}${formatSummarySuffix(event.summary)}`, 600);
868
+ case "tool.execution_complete":
869
+ return truncateBridgeText(`Copilot ${event.success === false ? "failed" : "completed"} ${formatToolName(event.name)}${formatRequestId(event.requestId)}${formatSummarySuffix(event.summary)}`, 600);
870
+ case "permission.requested":
871
+ return truncateBridgeText(`Copilot requested permission${event.kind ? `: ${event.kind}` : ""}${formatRequestId(event.requestId)}${formatSummarySuffix(event.summary)}`, 600);
872
+ case "permission.completed":
873
+ return truncateBridgeText(`Copilot permission ${event.approved === false ? "denied" : "approved"}${event.kind ? `: ${event.kind}` : ""}${formatRequestId(event.requestId)}${formatSummarySuffix(event.summary)}`, 600);
874
+ case "user_input.requested":
875
+ return truncateBridgeText(`Copilot requested user input${formatRequestId(event.requestId)}${formatSummarySuffix(event.summary)}`, 600);
876
+ case "exit_plan_mode.requested":
877
+ return truncateBridgeText(`Copilot requested to exit plan mode${formatRequestId(event.requestId)}${formatSummarySuffix(event.summary)}`, 600);
878
+ case "session.error":
879
+ return truncateBridgeText(`Copilot session error${formatSummarySuffix(event.summary)}`, 600);
880
+ case "session.idle":
881
+ return truncateBridgeText(event.summary, 600);
882
+ case "assistant.message_delta":
883
+ case "session.unknown":
884
+ return "";
885
+ }
886
+ }
887
+ function formatToolName(name) {
888
+ return name ? `tool ${name}` : "tool";
889
+ }
890
+ function formatRequestId(requestId) {
891
+ return requestId ? ` (${requestId})` : "";
892
+ }
893
+ function formatSummarySuffix(summary) {
894
+ return summary.length > 0 ? `: ${summary}` : "";
895
+ }
896
+ function truncateBridgeText(value, maxLength) {
897
+ if (value.length <= maxLength) {
898
+ return value;
899
+ }
900
+ return `${value.slice(0, maxLength)}... [truncated]`;
901
+ }
793
902
  function startSseHeartbeat(response) {
794
903
  return setInterval(() => {
795
904
  response.write(": keep-alive\n\n");
@@ -0,0 +1,17 @@
1
+ # codex-switch v0.1.5 Design
2
+
3
+ `0.1.5` is a Copilot Bridge process-visibility and redaction patch release.
4
+
5
+ ## Design Notes
6
+
7
+ - `CopilotBridgeRuntimeEvent` is the stable internal boundary between Copilot SDK session events and the OpenAI-compatible bridge surface.
8
+ - Copilot sessions are created with `streaming: true` and register the generic `session.on("event", handler)` listener alongside the existing `data`, `message`, and `delta` text compatibility listeners.
9
+ - The adapter maps known SDK event names into bridge runtime events for assistant intent, assistant message deltas, assistant reasoning deltas, tool lifecycle, permission lifecycle, user-input requests, exit-plan-mode requests, session idle, and session errors.
10
+ - `assistant.message_delta` is also forwarded as a normal text delta so final response streaming remains compatible with existing Chat Completions and Responses text paths.
11
+ - The bridge worker forwards adapter runtime events through `onRuntimeEvent`, separate from `onTextDelta` and `onTextDone`.
12
+ - Responses streaming projects non-text runtime process events as completed assistant message items with `phase: "commentary"`.
13
+ - Reasoning deltas project as `response.reasoning_summary_part.added` followed by `response.reasoning_summary_text.delta`.
14
+ - Unknown SDK events become `session.unknown`, are omitted from the UI projection path, and are logged with bounded summaries.
15
+ - Unknown summaries redact sensitive key names and obvious token/API-key-like values before truncation.
16
+ - Chat Completions streaming only wires text deltas, so Responses-only commentary events stay out of Chat Completions streams.
17
+ - Internal Copilot tool lifecycle events remain process visibility signals only; they are not emitted as Codex/OpenAI function calls or tool calls.
@@ -0,0 +1,42 @@
1
+ # codex-switch v0.1.5 PRD
2
+
3
+ ## Version
4
+
5
+ - Version line: `0.1.5`
6
+ - Target repository package version: `0.1.5`
7
+
8
+ ## Summary
9
+
10
+ `0.1.5` is a Copilot Bridge process-visibility and redaction patch release. It surfaces assistant progress, reasoning summaries, tool lifecycle, permission, user-input, and exit-plan-mode signals through the existing bridge stream while keeping the provider command surface unchanged.
11
+
12
+ ## Required Outcome
13
+
14
+ - Copilot SDK streaming sessions must subscribe to the generic session event channel and preserve existing text-delta compatibility listeners.
15
+ - Known SDK process events must map into a stable bridge runtime-event contract instead of leaking SDK-specific shapes.
16
+ - Responses streaming must surface process/status updates as commentary items and reasoning/progress updates as reasoning summary events.
17
+ - Chat Completions streaming must continue to receive text only and must not receive Responses-only commentary events.
18
+ - Unknown SDK events must be ignored by the UI projection path while logging bounded, redacted summaries for diagnostics.
19
+ - Adapter-level tests must cover raw SDK session event normalization, not only bridge-server projection of already-normalized events.
20
+
21
+ ## Release Scope
22
+
23
+ - Copilot adapter runtime event contract and SDK event normalization.
24
+ - Bridge worker forwarding of adapter runtime events into request handlers.
25
+ - Responses streaming projection for commentary and reasoning summary events.
26
+ - Unknown-event truncation and redaction hardening.
27
+ - Focused regression coverage for raw SDK event mapping and bridge stream projection.
28
+
29
+ ## Non-Goals
30
+
31
+ - No new provider families.
32
+ - No migration or backward-compatibility shims.
33
+ - No expansion of direct-provider workflows.
34
+ - No conversion of internal Copilot tool lifecycle events into Codex/OpenAI function-call or tool-call payloads.
35
+
36
+ ## Release Acceptance
37
+
38
+ - `npm test` passes with adapter-level raw event normalization coverage.
39
+ - Responses streaming includes commentary and reasoning summary process events.
40
+ - Final assistant text still streams normally.
41
+ - Unknown events are redacted, truncated, logged for diagnostics, and ignored by the UI projection path.
42
+ - Chat Completions streaming does not emit Responses-only commentary events.
@@ -2,7 +2,7 @@
2
2
 
3
3
  This guide records the current `0.1.x` verification contract for release and review work.
4
4
 
5
- The current repository line is `0.1.4` and remains an unreleased development line until an explicit release task says otherwise.
5
+ The current repository line is `0.1.5` and remains an unreleased development line until an explicit release task says otherwise.
6
6
 
7
7
  ## Required checks
8
8
 
@@ -25,7 +25,7 @@ node dist/cli.js --version
25
25
  - `--json` envelope: top-level `ok`, `command`, `data`, `warnings`, and `error` must remain stable
26
26
  - `migrate`: advanced adopt helper only
27
27
  - `setup`: deprecated entry only
28
- - Release hygiene: `package.json`, `package-lock.json`, current-line docs, changelog top entry, and current PRD/Design fact sources must agree on the `0.1.4` development line
28
+ - Release hygiene: `package.json`, `package-lock.json`, current-line docs, changelog top entry, and current PRD/Design fact sources must agree on the `0.1.5` development line
29
29
 
30
30
  ## Fixture guidance
31
31
 
package/docs/cli-usage.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # codex-switch CLI Usage
2
2
 
3
- This document describes the current `0.1.4` repository development-line CLI contract for `@minniexcode/codex-switch`, including the bridge stability and observability boundary.
3
+ This document describes the current `0.1.5` repository development-line CLI contract for `@minniexcode/codex-switch`, including the Copilot Bridge process-visibility boundary.
4
4
 
5
5
  Executable command name:
6
6
 
@@ -10,9 +10,9 @@ codexs
10
10
 
11
11
  ## 1. Version Context
12
12
 
13
- The current package version in this repository is `0.1.4`.
13
+ The current package version in this repository is `0.1.5`.
14
14
 
15
- This release line targets Codex `0.134.0+`. The public contract assumes runtime routing is selected by top-level `model` plus `model_provider`, while legacy `profile` and `[profiles.*]` remain inspect-and-adopt inputs instead of the recommended runtime path. The current `0.1.4` development line tightens bridge reliability and release-hygiene verification without expanding the provider command surface.
15
+ This release line targets Codex `0.134.0+`. The public contract assumes runtime routing is selected by top-level `model` plus `model_provider`, while legacy `profile` and `[profiles.*]` remain inspect-and-adopt inputs instead of the recommended runtime path. The current `0.1.5` development line adds Copilot Bridge process visibility, defensive SDK-event normalization, and unknown-event redaction hardening without expanding the provider command surface.
16
16
 
17
17
  ## 2. Primary Workflows
18
18
 
@@ -20,7 +20,7 @@ This release line targets Codex `0.134.0+`. The public contract assumes runtime
20
20
 
21
21
  ```bash
22
22
  codexs init
23
- codexs add <provider> --model <model> --api-key <key> [--base-url <url>]
23
+ codexs add <provider> --profile <model-provider-id> --model <model> --api-key <key> [--base-url <url>]
24
24
  codexs switch <provider>
25
25
  codexs status
26
26
  codexs doctor
@@ -30,6 +30,7 @@ Intent:
30
30
 
31
31
  - `init` prepares the `codex-switch` tool home.
32
32
  - `add` creates a managed provider record with a target model and provider route identity.
33
+ - Non-interactive runs should pass `--profile` explicitly; TTY mode can prompt for missing required fields.
33
34
  - `switch` projects the selected provider into the target Codex runtime.
34
35
  - `status` summarizes tool-home, runtime, provider, and health state.
35
36
  - `doctor` gives deeper repair-oriented diagnostics.
@@ -39,7 +40,7 @@ Intent:
39
40
  ```bash
40
41
  codexs init
41
42
  codexs login copilot
42
- codexs add <provider> --copilot --model <model>
43
+ codexs add <provider> --copilot --profile <model-provider-id> --model <model>
43
44
  codexs switch <provider>
44
45
  codexs status
45
46
  codexs doctor
@@ -51,6 +52,7 @@ Important notes:
51
52
  - The current implementation prefers the bundled Copilot CLI from the managed runtime and falls back to `PATH` when needed.
52
53
  - `login copilot` succeeds only after auth readiness is rechecked.
53
54
  - `add --copilot` does not install or log in to Copilot for you.
55
+ - Non-interactive Copilot adds should also pass `--profile` explicitly; TTY mode can still collect missing required fields.
54
56
  - Copilot runtime paths require Node.js `>=20`; direct providers remain supported on Node.js `>=18`.
55
57
  - The Copilot bridge is experimental and targets simple text-oriented turns through the local OpenAI-compatible bridge.
56
58
 
@@ -82,6 +84,7 @@ Copilot bridge projection also writes:
82
84
  Compatibility notes:
83
85
 
84
86
  - `--profile` is accepted as an alias for the managed `model_provider` id.
87
+ - automation should still pass `--profile` explicitly instead of relying on prompts
85
88
  - legacy top-level `profile` and `[profiles.*]` may still appear in existing runtime state
86
89
  - `migrate`, `config show`, `config list-profiles`, and `doctor` can still inspect those legacy structures
87
90
  - new managed runtime projection should be described as route-first, not profile-first
@@ -262,6 +265,7 @@ codexs rollback [backup-id]
262
265
 
263
266
  - Create or update managed provider records rather than editing runtime files directly.
264
267
  - Treat `--profile` only as an alias for the managed `model_provider` id.
268
+ - Require explicit `--profile` in non-interactive usage; allow TTY prompts to fill it when omitted.
265
269
  - Clean old `env_key` and `env_key_instructions` fields from managed projection during subsequent switching.
266
270
 
267
271
  ### `migrate`
@@ -288,7 +292,7 @@ codexs rollback [backup-id]
288
292
  - [PRD 0.1.1](./PRD/codex-switch-prd-v0.1.1.md)
289
293
  - [PRD 0.1.2](./PRD/codex-switch-prd-v0.1.2.md)
290
294
  - [PRD 0.1.3](./PRD/codex-switch-prd-v0.1.3.md)
291
- - [PRD 0.1.4](./PRD/codex-switch-prd-v0.1.4.md)
295
+ - [PRD 0.1.5](./PRD/codex-switch-prd-v0.1.5.md)
292
296
  - [Design 0.1.2](./Design/codex-switch-v0.1.2-design.md)
293
297
  - [Design 0.1.3](./Design/codex-switch-v0.1.3-design.md)
294
- - [Design 0.1.4](./Design/codex-switch-v0.1.4-design.md)
298
+ - [Design 0.1.5](./Design/codex-switch-v0.1.5-design.md)
@@ -4,21 +4,21 @@
4
4
 
5
5
  这份文档介绍当前活跃产品事实源下的 `codex-switch` 产品定位。
6
6
 
7
- 当前仓库开发线 fact source 以这些文档为准:
8
-
9
- - [`cli-usage.md`](./cli-usage.md)
10
- - [`PRD/codex-switch-prd-v0.1.0.md`](./PRD/codex-switch-prd-v0.1.0.md)
11
- - [`PRD/codex-switch-prd-v0.1.1.md`](./PRD/codex-switch-prd-v0.1.1.md)
12
- - [`PRD/codex-switch-prd-v0.1.2.md`](./PRD/codex-switch-prd-v0.1.2.md)
13
- - [`PRD/codex-switch-prd-v0.1.3.md`](./PRD/codex-switch-prd-v0.1.3.md)
14
- - [`PRD/codex-switch-prd-v0.1.4.md`](./PRD/codex-switch-prd-v0.1.4.md)
15
- - [`Design/codex-switch-v0.1.2-design.md`](./Design/codex-switch-v0.1.2-design.md)
16
- - [`Design/codex-switch-v0.1.3-design.md`](./Design/codex-switch-v0.1.3-design.md)
17
- - [`Design/codex-switch-v0.1.4-design.md`](./Design/codex-switch-v0.1.4-design.md)
7
+ 当前仓库开发线 fact source 以这些文档为准:
8
+
9
+ - [`cli-usage.md`](./cli-usage.md)
10
+ - [`PRD/codex-switch-prd-v0.1.0.md`](./PRD/codex-switch-prd-v0.1.0.md)
11
+ - [`PRD/codex-switch-prd-v0.1.1.md`](./PRD/codex-switch-prd-v0.1.1.md)
12
+ - [`PRD/codex-switch-prd-v0.1.2.md`](./PRD/codex-switch-prd-v0.1.2.md)
13
+ - [`PRD/codex-switch-prd-v0.1.3.md`](./PRD/codex-switch-prd-v0.1.3.md)
14
+ - [`PRD/codex-switch-prd-v0.1.5.md`](./PRD/codex-switch-prd-v0.1.5.md)
15
+ - [`Design/codex-switch-v0.1.2-design.md`](./Design/codex-switch-v0.1.2-design.md)
16
+ - [`Design/codex-switch-v0.1.3-design.md`](./Design/codex-switch-v0.1.3-design.md)
17
+ - [`Design/codex-switch-v0.1.5-design.md`](./Design/codex-switch-v0.1.5-design.md)
18
18
 
19
19
  ## 产品概述
20
20
 
21
- `codex-switch` 是一个本地 provider 管理 CLI,用于管理和切换目标 Codex runtime 的 provider/model-provider 路由配置,同时把工具自己的管理态保存在独立 tool home 下。
21
+ `codex-switch` 是一个本地 provider 管理 CLI,用于管理和切换目标 Codex runtime 的 provider/model-provider 路由配置,同时把工具自己的管理态保存在独立 tool home 下。
22
22
 
23
23
  它不是旧 `setup` 小工具,也不是围绕单目录 `~/.codex` 组织全部状态的脚本集合。
24
24
 
@@ -54,11 +54,11 @@ target Codex runtime:
54
54
 
55
55
  ## 核心使用流程
56
56
 
57
- Direct 主路径:
57
+ Direct 主路径:
58
58
 
59
59
  ```bash
60
60
  codexs init
61
- codexs add <provider> --model <model> --api-key <key> [--base-url <url>]
61
+ codexs add <provider> --profile <model-provider-id> --model <model> --api-key <key> [--base-url <url>]
62
62
  codexs switch <provider>
63
63
  codexs status
64
64
  codexs doctor
@@ -69,7 +69,7 @@ Copilot 主路径:
69
69
  ```bash
70
70
  codexs init
71
71
  codexs login copilot
72
- codexs add <provider> --copilot --model <model>
72
+ codexs add <provider> --copilot --profile <model-provider-id> --model <model>
73
73
  codexs switch <provider>
74
74
  codexs status
75
75
  codexs doctor
@@ -84,12 +84,13 @@ codexs migrate
84
84
 
85
85
  ## 当前产品判断
86
86
 
87
- `0.1.1` 的重点不是再加新命令,而是让用户在 README、help 和输出第一屏就能理解:
87
+ `0.1.1` 的重点不是再加新命令,而是让用户在 README、help 和输出第一屏就能理解:
88
88
 
89
- - fresh install 应先走什么
90
- - Copilot 路径和 direct 路径有什么区别
91
- - `migrate` 何时才该使用
92
- - `status` / `doctor` 如何帮助定位下一步
93
- - 当前运行态是用顶层 `model` `model_provider` 选择活动路由
94
-
95
- `0.1.4` 是当前仓库开发线,重点不是扩展 provider 面,而是把已有 Copilot bridge 实验路径变得更稳、更可诊断,并把 release hygiene 拉进真实门禁。当前实现边界仍然是:Copilot 路径要求 Node.js `>=20`,受管安装默认固定到 `@github/copilot-sdk@1.0.2`,本地 bridge 仍然只是面向 simple text-oriented turns 的 experimental bridge;Direct provider 路径继续支持 Node.js `>=18`。
89
+ - fresh install 应先走什么
90
+ - Copilot 路径和 direct 路径有什么区别
91
+ - 非交互命令为什么应显式传 `--profile`,以及 TTY 模式下哪些缺失项可以补问
92
+ - `migrate` 何时才该使用
93
+ - `status` / `doctor` 如何帮助定位下一步
94
+ - 当前运行态是用顶层 `model` 与 `model_provider` 选择活动路由
95
+
96
+ `0.1.5` 是当前仓库开发线,重点不是扩展 provider 面,而是让已有 Copilot bridge 实验路径具备过程可见性:SDK 事件被归一化为稳定运行态事件,Responses 流可以显示 commentary/reasoning 信号,未知事件摘要会被脱敏和截断。当前实现边界仍然是:Copilot 路径要求 Node.js `>=20`,受管安装默认固定到 `@github/copilot-sdk@1.0.2`,本地 bridge 仍然只是面向 simple text-oriented turns 的 experimental bridge;Direct provider 路径继续支持 Node.js `>=18`。
@@ -7,12 +7,12 @@
7
7
  - [`PRD/codex-switch-prd-v0.1.1.md`](./PRD/codex-switch-prd-v0.1.1.md)
8
8
  - [`PRD/codex-switch-prd-v0.1.2.md`](./PRD/codex-switch-prd-v0.1.2.md)
9
9
  - [`PRD/codex-switch-prd-v0.1.3.md`](./PRD/codex-switch-prd-v0.1.3.md)
10
- - [`PRD/codex-switch-prd-v0.1.4.md`](./PRD/codex-switch-prd-v0.1.4.md)
10
+ - [`PRD/codex-switch-prd-v0.1.5.md`](./PRD/codex-switch-prd-v0.1.5.md)
11
11
  - [`Design/codex-switch-v0.1.0-design.md`](./Design/codex-switch-v0.1.0-design.md)
12
12
  - [`Design/codex-switch-v0.1.1-design.md`](./Design/codex-switch-v0.1.1-design.md)
13
13
  - [`Design/codex-switch-v0.1.2-design.md`](./Design/codex-switch-v0.1.2-design.md)
14
14
  - [`Design/codex-switch-v0.1.3-design.md`](./Design/codex-switch-v0.1.3-design.md)
15
- - [`Design/codex-switch-v0.1.4-design.md`](./Design/codex-switch-v0.1.4-design.md)
15
+ - [`Design/codex-switch-v0.1.5-design.md`](./Design/codex-switch-v0.1.5-design.md)
16
16
 
17
17
  ## Layers
18
18
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@minniexcode/codex-switch",
3
- "version": "0.1.4",
4
- "description": "Local-first CLI for managing and switching Codex provider/model-provider routing.",
3
+ "version": "0.1.5",
4
+ "description": "Local-first CLI for managing and switching Codex provider/model-provider routing.",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
7
7
  "bin": {