@minniexcode/codex-switch 0.1.0 → 0.1.2

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.
Files changed (68) hide show
  1. package/README.AI.md +141 -110
  2. package/README.CN.md +215 -179
  3. package/README.md +224 -183
  4. package/dist/app/add-provider.js +16 -23
  5. package/dist/app/bridge.js +2 -1
  6. package/dist/app/edit-provider.js +30 -65
  7. package/dist/app/get-current-profile.js +15 -3
  8. package/dist/app/get-status.js +11 -8
  9. package/dist/app/list-config-profiles.js +3 -1
  10. package/dist/app/list-providers.js +10 -4
  11. package/dist/app/remove-provider.js +52 -19
  12. package/dist/app/run-doctor.js +26 -29
  13. package/dist/app/setup-codex.js +3 -3
  14. package/dist/app/show-config.js +3 -1
  15. package/dist/app/switch-provider.js +38 -6
  16. package/dist/cli/output.js +29 -19
  17. package/dist/commands/handlers.js +3 -2
  18. package/dist/commands/help.js +3 -3
  19. package/dist/commands/registry.js +29 -29
  20. package/dist/domain/config.js +293 -209
  21. package/dist/domain/providers.js +8 -0
  22. package/dist/domain/runtime-state.js +15 -15
  23. package/dist/domain/setup.js +3 -1
  24. package/dist/interaction/interactive.js +2 -2
  25. package/dist/runtime/codex-version.js +7 -0
  26. package/dist/runtime/copilot-adapter.js +326 -70
  27. package/dist/runtime/copilot-bridge-worker.js +27 -2
  28. package/dist/runtime/copilot-bridge.js +192 -10
  29. package/dist/runtime/copilot-cli.js +7 -0
  30. package/dist/runtime/copilot-installer.js +59 -1
  31. package/dist/runtime/copilot-sdk-loader.js +4 -1
  32. package/dist/storage/config-repo.js +6 -14
  33. package/docs/Design/codex-switch-v0.1.0-design.md +32 -152
  34. package/docs/Design/codex-switch-v0.1.1-design.md +22 -0
  35. package/docs/Design/codex-switch-v0.1.2-design.md +65 -0
  36. package/docs/PRD/codex-switch-prd-v0.1.0.md +65 -217
  37. package/docs/PRD/codex-switch-prd-v0.1.1.md +26 -0
  38. package/docs/PRD/codex-switch-prd-v0.1.2.md +41 -0
  39. package/docs/Reference/codex-config-reference.md +41 -0
  40. package/docs/Reference/codex-config-reference.zh-CN.md +41 -0
  41. package/docs/Tests/testing.md +1 -1
  42. package/docs/cli-usage.md +290 -223
  43. package/docs/codex-switch-command-design.md +2 -2
  44. package/docs/codex-switch-product-overview.md +18 -13
  45. package/docs/codex-switch-product-research.md +2 -2
  46. package/docs/codex-switch-technical-architecture.md +84 -1115
  47. package/package.json +2 -2
  48. package/docs/Design/codex-switch-copilot-integration-design.md +0 -517
  49. package/docs/Design/codex-switch-v0.0.10-design.md +0 -669
  50. package/docs/Design/codex-switch-v0.0.11-design.md +0 -824
  51. package/docs/Design/codex-switch-v0.0.12-design.md +0 -343
  52. package/docs/Design/codex-switch-v0.0.4-design.md +0 -874
  53. package/docs/Design/codex-switch-v0.0.5-design.md +0 -932
  54. package/docs/Design/codex-switch-v0.0.6-design.md +0 -708
  55. package/docs/Design/codex-switch-v0.0.7-design.md +0 -862
  56. package/docs/Design/codex-switch-v0.0.8-design.md +0 -132
  57. package/docs/Design/codex-switch-v0.0.9-design.md +0 -182
  58. package/docs/Design/codex-switch-v0.0.9-to-v0.0.12-roadmap.md +0 -413
  59. package/docs/PRD/codex-switch-prd-v0.0.10.md +0 -406
  60. package/docs/PRD/codex-switch-prd-v0.0.11.md +0 -577
  61. package/docs/PRD/codex-switch-prd-v0.0.12.md +0 -279
  62. package/docs/PRD/codex-switch-prd-v0.0.5-to-v0.1.0.md +0 -446
  63. package/docs/PRD/codex-switch-prd-v0.0.8.md +0 -62
  64. package/docs/PRD/codex-switch-prd-v0.0.9.md +0 -166
  65. package/docs/PRD/codex-switch-prd.md +0 -650
  66. package/docs/Tests/test-report-0.0.5.md +0 -163
  67. package/docs/Tests/test-report-0.0.7.md +0 -118
  68. package/docs/Tests/testing-bridge-v0.0.9.md +0 -367
@@ -34,6 +34,9 @@ function validateProvidersShape(input) {
34
34
  if (typeof provider.apiKey !== "string" || provider.apiKey.trim() === "") {
35
35
  throw new Error(`Provider "${name}" is missing a valid apiKey.`);
36
36
  }
37
+ if (provider.model !== undefined && typeof provider.model !== "string") {
38
+ throw new Error(`Provider "${name}" has an invalid model.`);
39
+ }
37
40
  if (provider.baseUrl !== undefined && typeof provider.baseUrl !== "string") {
38
41
  throw new Error(`Provider "${name}" has an invalid baseUrl.`);
39
42
  }
@@ -55,6 +58,7 @@ function validateProvidersShape(input) {
55
58
  providers[name] = cleanProviderRecord({
56
59
  profile: provider.profile,
57
60
  apiKey: provider.apiKey,
61
+ model: provider.model,
58
62
  baseUrl: provider.baseUrl,
59
63
  note: provider.note,
60
64
  tags: provider.tags,
@@ -71,6 +75,9 @@ function cleanProviderRecord(record) {
71
75
  profile: record.profile.trim(),
72
76
  apiKey: record.apiKey.trim(),
73
77
  };
78
+ if (record.model && record.model.trim() !== "") {
79
+ next.model = record.model.trim();
80
+ }
74
81
  if (record.baseUrl && record.baseUrl.trim() !== "") {
75
82
  next.baseUrl = record.baseUrl.trim();
76
83
  }
@@ -161,6 +168,7 @@ function buildCopilotModelProviderProjection(runtime) {
161
168
  name: "copilot",
162
169
  requiresOpenAiAuth: true,
163
170
  wireApi: "responses",
171
+ streamIdleTimeoutMs: 300000,
164
172
  };
165
173
  }
166
174
  /**
@@ -90,26 +90,26 @@ function getStorageRoles(args) {
90
90
  };
91
91
  }
92
92
  /**
93
- * Compares the live active profile against managed providers to detect drift.
93
+ * Compares the live active model_provider against managed providers to detect drift.
94
94
  */
95
- function inspectLiveStateDrift(currentProfile, providers) {
96
- if (currentProfile === null) {
95
+ function inspectLiveStateDrift(currentModelProvider, providers) {
96
+ if (currentModelProvider === null) {
97
97
  return {
98
- currentProfile,
98
+ currentModelProvider,
99
99
  mappedProvider: null,
100
100
  mappedProviders: [],
101
- profileMapped: false,
101
+ modelProviderMapped: false,
102
102
  providerResolvable: false,
103
103
  canBackfillActiveProvider: false,
104
- reason: providers ? "profile-missing" : "config-missing",
104
+ reason: providers ? "model-provider-missing" : "config-missing",
105
105
  };
106
106
  }
107
107
  if (!providers) {
108
108
  return {
109
- currentProfile,
109
+ currentModelProvider,
110
110
  mappedProvider: null,
111
111
  mappedProviders: [],
112
- profileMapped: false,
112
+ modelProviderMapped: false,
113
113
  providerResolvable: false,
114
114
  canBackfillActiveProvider: false,
115
115
  reason: "providers-missing",
@@ -117,16 +117,16 @@ function inspectLiveStateDrift(currentProfile, providers) {
117
117
  }
118
118
  const mappedProviders = [];
119
119
  for (const [name, provider] of Object.entries(providers.providers)) {
120
- if (provider.profile === currentProfile) {
120
+ if (provider.profile === currentModelProvider) {
121
121
  mappedProviders.push(name);
122
122
  }
123
123
  }
124
124
  if (mappedProviders.length === 1) {
125
125
  return {
126
- currentProfile,
126
+ currentModelProvider,
127
127
  mappedProvider: mappedProviders[0],
128
128
  mappedProviders,
129
- profileMapped: true,
129
+ modelProviderMapped: true,
130
130
  providerResolvable: true,
131
131
  canBackfillActiveProvider: false,
132
132
  reason: "ok",
@@ -134,20 +134,20 @@ function inspectLiveStateDrift(currentProfile, providers) {
134
134
  }
135
135
  if (mappedProviders.length > 1) {
136
136
  return {
137
- currentProfile,
137
+ currentModelProvider,
138
138
  mappedProvider: null,
139
139
  mappedProviders,
140
- profileMapped: true,
140
+ modelProviderMapped: true,
141
141
  providerResolvable: false,
142
142
  canBackfillActiveProvider: false,
143
143
  reason: "shared-profile",
144
144
  };
145
145
  }
146
146
  return {
147
- currentProfile,
147
+ currentModelProvider,
148
148
  mappedProvider: null,
149
149
  mappedProviders: [],
150
- profileMapped: false,
150
+ modelProviderMapped: false,
151
151
  providerResolvable: false,
152
152
  canBackfillActiveProvider: true,
153
153
  reason: "provider-unmapped",
@@ -16,8 +16,9 @@ function buildSetupDrafts(profiles, detailsByProfile, runtimeByProfile) {
16
16
  return {
17
17
  providerName,
18
18
  record: (0, providers_1.cleanProviderRecord)({
19
- profile,
19
+ profile: runtime?.modelProvider ?? profile,
20
20
  apiKey: detail.apiKey ?? "",
21
+ model: runtime?.model,
21
22
  baseUrl: detail.baseUrl ?? runtime?.baseUrl,
22
23
  note: detail.note,
23
24
  tags: detail.tags,
@@ -71,6 +72,7 @@ function collectMigrateAdoptability(document, providers) {
71
72
  adoptableProfileDetails.push({
72
73
  name: view.name,
73
74
  model: view.model,
75
+ modelProvider: view.modelProvider,
74
76
  baseUrl: view.baseUrl,
75
77
  });
76
78
  continue;
@@ -70,8 +70,8 @@ function canPrompt(runtime, jsonMode) {
70
70
  */
71
71
  async function promptForProviderSelection(runtime, providersPath, configPath, message) {
72
72
  const providers = (0, providers_repo_1.readProvidersFile)(providersPath);
73
- const currentProfile = fs.existsSync(configPath) ? (0, config_repo_1.readStructuredConfig)(configPath).activeProfile : null;
74
- const liveState = (0, runtime_state_1.inspectLiveStateDrift)(currentProfile, providers);
73
+ const currentModelProvider = fs.existsSync(configPath) ? (0, config_repo_1.readStructuredConfig)(configPath).currentModelProvider : null;
74
+ const liveState = (0, runtime_state_1.inspectLiveStateDrift)(currentModelProvider, providers);
75
75
  const choices = Object.entries(providers.providers)
76
76
  .sort(([left], [right]) => left.localeCompare(right))
77
77
  .map(([providerName, provider]) => {
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MIN_SUPPORTED_CODEX_VERSION = void 0;
4
+ /**
5
+ * Minimum Codex CLI version supported by the managed runtime workflow.
6
+ */
7
+ exports.MIN_SUPPORTED_CODEX_VERSION = "0.134.0";
@@ -1,16 +1,28 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EventEmitter = void 0;
3
4
  exports.probeCopilotSdkRuntime = probeCopilotSdkRuntime;
4
5
  exports.requireCopilotSdk = requireCopilotSdk;
5
6
  exports.readCopilotAuthState = readCopilotAuthState;
7
+ exports.createCopilotRuntimeClient = createCopilotRuntimeClient;
8
+ exports.startCopilotRuntimeClient = startCopilotRuntimeClient;
9
+ exports.stopCopilotRuntimeClient = stopCopilotRuntimeClient;
6
10
  exports.sendCopilotChatCompletion = sendCopilotChatCompletion;
11
+ const node_events_1 = require("node:events");
12
+ Object.defineProperty(exports, "EventEmitter", { enumerable: true, get: function () { return node_events_1.EventEmitter; } });
7
13
  const errors_1 = require("../domain/errors");
8
14
  const copilot_sdk_loader_1 = require("./copilot-sdk-loader");
9
15
  const copilot_installer_1 = require("./copilot-installer");
16
+ const copilot_cli_1 = require("./copilot-cli");
17
+ const DEFAULT_UPSTREAM_TIMEOUT_MS = 300000;
10
18
  /**
11
- * Probes whether the optional Copilot SDK runtime is installed and loadable.
19
+ * Probes whether the optional Copilot SDK runtime is installed, supported, and shaped correctly.
12
20
  */
13
21
  function probeCopilotSdkRuntime(runtimesDir) {
22
+ const nodeStatus = safeCopilotNodeRuntimeStatus();
23
+ if (!nodeStatus.ok) {
24
+ return nodeStatus.availability;
25
+ }
14
26
  const status = (0, copilot_installer_1.probeCopilotSdkInstall)(runtimesDir);
15
27
  if (!status.installed) {
16
28
  return {
@@ -24,6 +36,21 @@ function probeCopilotSdkRuntime(runtimesDir) {
24
36
  },
25
37
  };
26
38
  }
39
+ if (!(0, copilot_installer_1.isSupportedCopilotSdkVersion)(status.packageVersion)) {
40
+ return {
41
+ ok: false,
42
+ runtime: "copilot-sdk",
43
+ reason: "unsupported",
44
+ version: status.packageVersion ?? undefined,
45
+ cause: `The installed Copilot SDK version is unsupported. Install ${status.packageName}@${(0, copilot_installer_1.getSupportedCopilotSdkVersion)()}.`,
46
+ details: {
47
+ installDir: status.installDir,
48
+ packageName: status.packageName,
49
+ packageVersion: status.packageVersion,
50
+ supportedVersion: (0, copilot_installer_1.getSupportedCopilotSdkVersion)(),
51
+ },
52
+ };
53
+ }
27
54
  return {
28
55
  ok: true,
29
56
  runtime: "copilot-sdk",
@@ -41,55 +68,83 @@ async function requireCopilotSdk(runtimesDir) {
41
68
  return (0, copilot_sdk_loader_1.loadCopilotSdk)(runtimesDir);
42
69
  }
43
70
  /**
44
- * Probes whether the lazily installed Copilot SDK can create a usable session.
71
+ * Probes Copilot auth readiness through the SDK client status endpoint.
45
72
  */
46
73
  async function readCopilotAuthState(runtimesDir) {
47
74
  const runtime = probeCopilotSdkRuntime(runtimesDir);
48
75
  if (!runtime.ok) {
49
- throw (0, errors_1.cliError)("COPILOT_SDK_MISSING", "The optional Copilot SDK runtime is not installed.", runtime.details);
76
+ throw (0, errors_1.cliError)(runtime.reason === "unsupported" ? "COPILOT_SDK_VERSION_UNSUPPORTED" : "COPILOT_SDK_MISSING", runtime.cause, runtime.details);
50
77
  }
51
- const { client, session } = await createCopilotSession(runtimesDir);
52
- await stopCopilotClient(client);
53
- return {
54
- ready: Boolean(session),
55
- source: "official-sdk",
56
- mode: "session",
57
- };
78
+ const runtimeClient = await createCopilotRuntimeClient(runtimesDir);
79
+ try {
80
+ await startCopilotClient(runtimeClient.client);
81
+ const getAuthStatus = resolveCallable(runtimeClient.client, "getAuthStatus");
82
+ if (!getAuthStatus) {
83
+ throw (0, errors_1.cliError)("COPILOT_SDK_API_UNSUPPORTED", "The installed Copilot SDK does not expose CopilotClient.getAuthStatus().", {});
84
+ }
85
+ const status = await Promise.resolve(getAuthStatus());
86
+ if (!isAuthReady(status)) {
87
+ throw (0, errors_1.cliError)("COPILOT_AUTH_REQUIRED", "Copilot authentication is required before the local bridge can be used.", {
88
+ status,
89
+ });
90
+ }
91
+ return {
92
+ ready: true,
93
+ source: "official-sdk",
94
+ mode: "auth-status",
95
+ };
96
+ }
97
+ finally {
98
+ await stopCopilotClient(runtimeClient.client);
99
+ }
100
+ }
101
+ /**
102
+ * Creates one long-lived Copilot SDK client for bridge workers.
103
+ */
104
+ async function createCopilotRuntimeClient(runtimesDir) {
105
+ (0, copilot_installer_1.assertCopilotNodeRuntimeSupported)();
106
+ const runtime = probeCopilotSdkRuntime(runtimesDir);
107
+ if (!runtime.ok) {
108
+ throw (0, errors_1.cliError)(runtime.reason === "unsupported" ? "COPILOT_SDK_VERSION_UNSUPPORTED" : "COPILOT_SDK_MISSING", runtime.cause, runtime.details);
109
+ }
110
+ const sdk = (await requireCopilotSdk(runtimesDir));
111
+ const client = createCopilotClient(sdk, runtimesDir);
112
+ assertCopilotSdkApiContract(sdk, client);
113
+ return { sdk, client };
114
+ }
115
+ /**
116
+ * Starts a Copilot SDK client if the installed SDK exposes an explicit start hook.
117
+ */
118
+ async function startCopilotRuntimeClient(runtimeClient) {
119
+ await startCopilotClient(runtimeClient.client);
58
120
  }
59
121
  /**
60
- * Executes a single chat-completions style request through the optional Copilot SDK when available.
122
+ * Stops a Copilot SDK client.
123
+ */
124
+ async function stopCopilotRuntimeClient(runtimeClient) {
125
+ await stopCopilotClient(runtimeClient.client);
126
+ }
127
+ /**
128
+ * Executes one OpenAI-compatible request through a fresh Copilot session.
61
129
  */
62
130
  async function sendCopilotChatCompletion(args) {
63
- const { client, session, sdk } = await createCopilotSession(args.runtimesDir);
131
+ const ownsClient = !args.runtimeClient;
132
+ const runtimeClient = args.runtimeClient ?? await createCopilotRuntimeClient(args.runtimesDir);
133
+ if (ownsClient) {
134
+ await startCopilotRuntimeClient(runtimeClient);
135
+ }
136
+ let session = null;
64
137
  try {
65
- const sendAndWait = resolveCallable(session, "sendAndWait") ?? resolveCallable(sdk, "sendAndWait");
66
- if (!sendAndWait) {
67
- throw (0, errors_1.cliError)("COPILOT_SDK_UNSUPPORTED", "The installed Copilot SDK does not expose a supported sendAndWait API.", {
68
- provider: args.provider,
69
- });
70
- }
71
- const prompt = Array.isArray(args.payload.messages)
72
- ? args.payload.messages
73
- .map((entry) => {
74
- const message = entry;
75
- return `${String(message.role ?? "user")}: ${String(message.content ?? "")}`;
76
- })
77
- .join("\n")
78
- : "";
79
- const result = await Promise.resolve(sendAndWait({ model: args.payload.model, prompt }));
80
- const content = typeof result === "string"
81
- ? result
82
- : typeof result?.content === "string"
83
- ? String(result.content)
84
- : typeof result?.data === "object" &&
85
- typeof result.data.content === "string"
86
- ? String(result.data.content)
87
- : JSON.stringify(result);
138
+ session = await createCopilotSession(runtimeClient, args.payload);
139
+ const prompt = buildPrompt(args.payload);
140
+ const timeoutMs = args.timeoutMs ?? DEFAULT_UPSTREAM_TIMEOUT_MS;
141
+ const result = await sendSessionRequest(session, prompt, timeoutMs, args.onStreamEvent);
142
+ const content = extractCopilotContent(result);
88
143
  return {
89
144
  id: `copilot-${Date.now()}`,
90
145
  object: "chat.completion",
91
146
  created: Math.floor(Date.now() / 1000),
92
- model: args.payload.model ?? "copilot",
147
+ model: typeof args.payload.model === "string" ? args.payload.model : "copilot",
93
148
  choices: [
94
149
  {
95
150
  index: 0,
@@ -103,27 +158,36 @@ async function sendCopilotChatCompletion(args) {
103
158
  };
104
159
  }
105
160
  finally {
106
- await stopCopilotClient(client);
161
+ if (session) {
162
+ await disconnectCopilotSession(session);
163
+ }
164
+ if (ownsClient) {
165
+ await stopCopilotRuntimeClient(runtimeClient);
166
+ }
107
167
  }
108
168
  }
109
- async function createCopilotSession(runtimesDir) {
110
- const sdk = (await requireCopilotSdk(runtimesDir));
111
- const client = createCopilotClient(sdk);
112
- const createSession = resolveCallable(client ? client : null, "createSession") ?? resolveCallable(sdk, "createSession");
169
+ async function createCopilotSession(runtimeClient, payload) {
170
+ const createSession = resolveCallable(runtimeClient.client, "createSession");
113
171
  if (!createSession) {
114
- throw (0, errors_1.cliError)("COPILOT_SDK_UNSUPPORTED", "The installed Copilot SDK does not expose a supported createSession API.", {});
172
+ throw (0, errors_1.cliError)("COPILOT_SDK_API_UNSUPPORTED", "The installed Copilot SDK does not expose CopilotClient.createSession().", {});
115
173
  }
116
174
  try {
117
- const session = (await Promise.resolve(createSession(createSessionOptions(sdk))));
118
- return {
119
- sdk,
120
- client,
121
- session,
122
- };
175
+ const session = await Promise.resolve(createSession({
176
+ model: typeof payload.model === "string" ? payload.model : undefined,
177
+ ...createSessionOptions(runtimeClient.sdk),
178
+ }));
179
+ if (!session || typeof session !== "object") {
180
+ throw (0, errors_1.cliError)("COPILOT_SDK_API_UNSUPPORTED", "The installed Copilot SDK returned an invalid CopilotSession.", {});
181
+ }
182
+ assertCopilotSessionContract(session);
183
+ return session;
123
184
  }
124
185
  catch (error) {
186
+ if (isCliError(error)) {
187
+ throw error;
188
+ }
125
189
  if (classifyCopilotSessionError(error) === "unsupported") {
126
- throw (0, errors_1.cliError)("COPILOT_SDK_UNSUPPORTED", "The installed Copilot SDK does not expose a compatible permission-handling session API.", {
190
+ throw (0, errors_1.cliError)("COPILOT_SDK_API_UNSUPPORTED", "The installed Copilot SDK does not expose a compatible session API.", {
127
191
  cause: error instanceof Error ? error.message : String(error),
128
192
  });
129
193
  }
@@ -132,9 +196,61 @@ async function createCopilotSession(runtimesDir) {
132
196
  });
133
197
  }
134
198
  }
135
- /**
136
- * Builds the session options used consistently across auth probes and request execution.
137
- */
199
+ async function sendSessionRequest(session, prompt, timeoutMs, onStreamEvent) {
200
+ const sendAndWait = resolveCallable(session, "sendAndWait");
201
+ const send = resolveCallable(session, "send");
202
+ if (!sendAndWait && !send) {
203
+ throw (0, errors_1.cliError)("COPILOT_SDK_API_UNSUPPORTED", "The installed Copilot SDK session does not expose sendAndWait() or send().", {});
204
+ }
205
+ const deltaHandler = (event) => {
206
+ const delta = extractDelta(event);
207
+ if (delta) {
208
+ onStreamEvent?.({ type: "delta", delta });
209
+ }
210
+ };
211
+ if (onStreamEvent && session.on) {
212
+ session.on("data", deltaHandler);
213
+ session.on("message", deltaHandler);
214
+ session.on("delta", deltaHandler);
215
+ }
216
+ try {
217
+ const sendPromise = Promise.resolve(sendAndWait ? sendAndWait({ prompt }, timeoutMs) : send({ prompt }));
218
+ const result = await withTimeout(sendPromise, timeoutMs, async () => {
219
+ await abortCopilotSession(session);
220
+ });
221
+ if (onStreamEvent) {
222
+ onStreamEvent({ type: "done", text: extractCopilotContent(result) });
223
+ }
224
+ return result;
225
+ }
226
+ finally {
227
+ if (onStreamEvent && session.off) {
228
+ session.off("data", deltaHandler);
229
+ session.off("message", deltaHandler);
230
+ session.off("delta", deltaHandler);
231
+ }
232
+ }
233
+ }
234
+ async function withTimeout(promise, timeoutMs, onTimeout) {
235
+ let timeout = null;
236
+ try {
237
+ return await Promise.race([
238
+ promise,
239
+ new Promise((_resolve, reject) => {
240
+ timeout = setTimeout(() => {
241
+ void onTimeout().finally(() => {
242
+ reject((0, errors_1.cliError)("BRIDGE_UPSTREAM_TIMEOUT", "Copilot upstream request timed out.", { timeoutMs }));
243
+ });
244
+ }, timeoutMs);
245
+ }),
246
+ ]);
247
+ }
248
+ finally {
249
+ if (timeout) {
250
+ clearTimeout(timeout);
251
+ }
252
+ }
253
+ }
138
254
  function createSessionOptions(sdk) {
139
255
  const approveAll = resolveApproveAll(sdk);
140
256
  if (approveAll) {
@@ -142,35 +258,153 @@ function createSessionOptions(sdk) {
142
258
  onPermissionRequest: (request) => approveAll(request),
143
259
  };
144
260
  }
145
- return {
146
- onPermissionRequest: () => true,
147
- };
261
+ throw (0, errors_1.cliError)("COPILOT_SDK_API_UNSUPPORTED", "The installed Copilot SDK does not expose an approveAll permission handler.", {});
148
262
  }
149
- function createCopilotClient(sdk) {
263
+ function createCopilotClient(sdk, runtimesDir) {
150
264
  const ClientCtor = resolveConstructor(sdk, "CopilotClient");
151
265
  if (!ClientCtor) {
152
- return null;
266
+ throw (0, errors_1.cliError)("COPILOT_SDK_API_UNSUPPORTED", "The installed Copilot SDK does not expose CopilotClient.", {});
153
267
  }
268
+ const invocation = (0, copilot_cli_1.resolveCopilotCliInvocation)([], runtimesDir);
269
+ const clientOptions = {
270
+ copilotCommand: invocation.command,
271
+ command: invocation.command,
272
+ executable: invocation.command,
273
+ };
154
274
  try {
155
- return new ClientCtor();
275
+ return new ClientCtor(clientOptions);
156
276
  }
157
277
  catch (error) {
158
- throw (0, errors_1.cliError)("COPILOT_SDK_UNSUPPORTED", "The installed Copilot SDK CopilotClient could not be constructed.", {
278
+ throw (0, errors_1.cliError)("COPILOT_SDK_API_UNSUPPORTED", "The installed Copilot SDK CopilotClient could not be constructed.", {
159
279
  cause: error instanceof Error ? error.message : String(error),
160
280
  });
161
281
  }
162
282
  }
283
+ function assertCopilotSdkApiContract(sdk, client) {
284
+ const createSession = resolveCallable(client, "createSession");
285
+ const getAuthStatus = resolveCallable(client, "getAuthStatus");
286
+ if (!createSession || !getAuthStatus) {
287
+ throw (0, errors_1.cliError)("COPILOT_SDK_API_UNSUPPORTED", "The installed Copilot SDK does not expose the required CopilotClient API.", {
288
+ hasCreateSession: Boolean(createSession),
289
+ hasGetAuthStatus: Boolean(getAuthStatus),
290
+ });
291
+ }
292
+ if (!resolveApproveAll(sdk)) {
293
+ throw (0, errors_1.cliError)("COPILOT_SDK_API_UNSUPPORTED", "The installed Copilot SDK does not expose a supported permission handler.", {});
294
+ }
295
+ }
296
+ function assertCopilotSessionContract(session) {
297
+ const hasSendAndWait = typeof session.sendAndWait === "function";
298
+ const hasSend = typeof session.send === "function";
299
+ const hasAbort = typeof session.abort === "function";
300
+ if ((!hasSendAndWait && !hasSend) || !hasAbort) {
301
+ throw (0, errors_1.cliError)("COPILOT_SDK_API_UNSUPPORTED", "The installed Copilot SDK session does not expose the required request API.", {
302
+ hasSendAndWait,
303
+ hasSend,
304
+ hasAbort,
305
+ });
306
+ }
307
+ }
308
+ async function startCopilotClient(client) {
309
+ const start = resolveCallable(client, "start");
310
+ if (start) {
311
+ await Promise.resolve(start());
312
+ }
313
+ }
163
314
  async function stopCopilotClient(client) {
164
- if (client && typeof client.stop === "function") {
165
- await Promise.resolve(client.stop());
315
+ const stop = resolveCallable(client, "stop");
316
+ if (stop) {
317
+ await Promise.resolve(stop());
166
318
  }
167
319
  }
168
- /**
169
- * Distinguishes true auth failures from SDK API-shape mismatches.
170
- */
320
+ async function abortCopilotSession(session) {
321
+ const abort = resolveCallable(session, "abort");
322
+ if (abort) {
323
+ await Promise.resolve(abort());
324
+ }
325
+ }
326
+ async function disconnectCopilotSession(session) {
327
+ const disconnect = resolveCallable(session, "disconnect");
328
+ if (disconnect) {
329
+ await Promise.resolve(disconnect());
330
+ }
331
+ }
332
+ function buildPrompt(payload) {
333
+ if (Array.isArray(payload.messages)) {
334
+ return payload.messages
335
+ .map((entry) => {
336
+ const message = entry;
337
+ return `${String(message.role ?? "user")}: ${String(message.content ?? "")}`;
338
+ })
339
+ .join("\n");
340
+ }
341
+ if (typeof payload.prompt === "string") {
342
+ return payload.prompt;
343
+ }
344
+ return "";
345
+ }
346
+ function extractCopilotContent(result) {
347
+ if (typeof result === "string") {
348
+ return result;
349
+ }
350
+ if (!result || typeof result !== "object") {
351
+ return JSON.stringify(result);
352
+ }
353
+ const record = result;
354
+ if (typeof record.content === "string") {
355
+ return record.content;
356
+ }
357
+ if (typeof record.text === "string") {
358
+ return record.text;
359
+ }
360
+ if (typeof record.message === "string") {
361
+ return record.message;
362
+ }
363
+ if (record.data && typeof record.data === "object") {
364
+ const data = record.data;
365
+ if (typeof data.content === "string") {
366
+ return data.content;
367
+ }
368
+ if (typeof data.text === "string") {
369
+ return data.text;
370
+ }
371
+ }
372
+ return JSON.stringify(result);
373
+ }
374
+ function extractDelta(event) {
375
+ if (typeof event === "string") {
376
+ return event;
377
+ }
378
+ if (!event || typeof event !== "object") {
379
+ return null;
380
+ }
381
+ const record = event;
382
+ for (const key of ["delta", "text", "content"]) {
383
+ if (typeof record[key] === "string") {
384
+ return record[key];
385
+ }
386
+ }
387
+ return null;
388
+ }
389
+ function isAuthReady(status) {
390
+ if (status === true) {
391
+ return true;
392
+ }
393
+ if (!status || typeof status !== "object") {
394
+ return false;
395
+ }
396
+ const record = status;
397
+ if (record.ready === true || record.isAuthenticated === true || record.authenticated === true) {
398
+ return true;
399
+ }
400
+ if (typeof record.status === "string" && /^(ok|ready|authenticated|logged_in)$/i.test(record.status)) {
401
+ return true;
402
+ }
403
+ return false;
404
+ }
171
405
  function classifyCopilotSessionError(error) {
172
406
  const message = error instanceof Error ? error.message : String(error);
173
- if (/onPermissionRequest/i.test(message) || /permission/i.test(message)) {
407
+ if (/onPermissionRequest|permission|sendAndWait|createSession|unsupported/i.test(message)) {
174
408
  return "unsupported";
175
409
  }
176
410
  return "auth";
@@ -200,17 +434,39 @@ function resolveConstructor(target, name) {
200
434
  }
201
435
  return null;
202
436
  }
203
- /**
204
- * Resolves the SDK-provided permission helper when available.
205
- */
206
437
  function resolveApproveAll(target) {
207
438
  const direct = target.approveAll;
208
439
  if (typeof direct === "function") {
209
440
  return direct;
210
441
  }
442
+ const permissionHandler = target.PermissionHandler;
443
+ if (permissionHandler && typeof permissionHandler === "object" && typeof permissionHandler.approveAll === "function") {
444
+ return permissionHandler.approveAll;
445
+ }
211
446
  const nestedDefault = target.default;
212
- if (nestedDefault && typeof nestedDefault.approveAll === "function") {
213
- return nestedDefault.approveAll;
447
+ if (nestedDefault) {
448
+ return resolveApproveAll(nestedDefault);
214
449
  }
215
450
  return null;
216
451
  }
452
+ function isCliError(error) {
453
+ return Boolean(error && typeof error === "object" && typeof error.code === "string");
454
+ }
455
+ function safeCopilotNodeRuntimeStatus() {
456
+ try {
457
+ (0, copilot_installer_1.assertCopilotNodeRuntimeSupported)();
458
+ return { ok: true };
459
+ }
460
+ catch (error) {
461
+ return {
462
+ ok: false,
463
+ availability: {
464
+ ok: false,
465
+ runtime: "copilot-sdk",
466
+ reason: "unsupported",
467
+ cause: error instanceof Error ? error.message : String(error),
468
+ details: isCliError(error) ? error.details : undefined,
469
+ },
470
+ };
471
+ }
472
+ }