@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.
- package/README.AI.md +141 -110
- package/README.CN.md +215 -179
- package/README.md +224 -183
- package/dist/app/add-provider.js +16 -23
- package/dist/app/bridge.js +2 -1
- package/dist/app/edit-provider.js +30 -65
- package/dist/app/get-current-profile.js +15 -3
- package/dist/app/get-status.js +11 -8
- package/dist/app/list-config-profiles.js +3 -1
- package/dist/app/list-providers.js +10 -4
- package/dist/app/remove-provider.js +52 -19
- package/dist/app/run-doctor.js +26 -29
- package/dist/app/setup-codex.js +3 -3
- package/dist/app/show-config.js +3 -1
- package/dist/app/switch-provider.js +38 -6
- package/dist/cli/output.js +29 -19
- package/dist/commands/handlers.js +3 -2
- package/dist/commands/help.js +3 -3
- package/dist/commands/registry.js +29 -29
- package/dist/domain/config.js +293 -209
- package/dist/domain/providers.js +8 -0
- package/dist/domain/runtime-state.js +15 -15
- package/dist/domain/setup.js +3 -1
- package/dist/interaction/interactive.js +2 -2
- package/dist/runtime/codex-version.js +7 -0
- package/dist/runtime/copilot-adapter.js +326 -70
- package/dist/runtime/copilot-bridge-worker.js +27 -2
- package/dist/runtime/copilot-bridge.js +192 -10
- package/dist/runtime/copilot-cli.js +7 -0
- package/dist/runtime/copilot-installer.js +59 -1
- package/dist/runtime/copilot-sdk-loader.js +4 -1
- package/dist/storage/config-repo.js +6 -14
- package/docs/Design/codex-switch-v0.1.0-design.md +32 -152
- package/docs/Design/codex-switch-v0.1.1-design.md +22 -0
- package/docs/Design/codex-switch-v0.1.2-design.md +65 -0
- package/docs/PRD/codex-switch-prd-v0.1.0.md +65 -217
- package/docs/PRD/codex-switch-prd-v0.1.1.md +26 -0
- package/docs/PRD/codex-switch-prd-v0.1.2.md +41 -0
- package/docs/Reference/codex-config-reference.md +41 -0
- package/docs/Reference/codex-config-reference.zh-CN.md +41 -0
- package/docs/Tests/testing.md +1 -1
- package/docs/cli-usage.md +290 -223
- package/docs/codex-switch-command-design.md +2 -2
- package/docs/codex-switch-product-overview.md +18 -13
- package/docs/codex-switch-product-research.md +2 -2
- package/docs/codex-switch-technical-architecture.md +84 -1115
- package/package.json +2 -2
- package/docs/Design/codex-switch-copilot-integration-design.md +0 -517
- package/docs/Design/codex-switch-v0.0.10-design.md +0 -669
- package/docs/Design/codex-switch-v0.0.11-design.md +0 -824
- package/docs/Design/codex-switch-v0.0.12-design.md +0 -343
- package/docs/Design/codex-switch-v0.0.4-design.md +0 -874
- package/docs/Design/codex-switch-v0.0.5-design.md +0 -932
- package/docs/Design/codex-switch-v0.0.6-design.md +0 -708
- package/docs/Design/codex-switch-v0.0.7-design.md +0 -862
- package/docs/Design/codex-switch-v0.0.8-design.md +0 -132
- package/docs/Design/codex-switch-v0.0.9-design.md +0 -182
- package/docs/Design/codex-switch-v0.0.9-to-v0.0.12-roadmap.md +0 -413
- package/docs/PRD/codex-switch-prd-v0.0.10.md +0 -406
- package/docs/PRD/codex-switch-prd-v0.0.11.md +0 -577
- package/docs/PRD/codex-switch-prd-v0.0.12.md +0 -279
- package/docs/PRD/codex-switch-prd-v0.0.5-to-v0.1.0.md +0 -446
- package/docs/PRD/codex-switch-prd-v0.0.8.md +0 -62
- package/docs/PRD/codex-switch-prd-v0.0.9.md +0 -166
- package/docs/PRD/codex-switch-prd.md +0 -650
- package/docs/Tests/test-report-0.0.5.md +0 -163
- package/docs/Tests/test-report-0.0.7.md +0 -118
- package/docs/Tests/testing-bridge-v0.0.9.md +0 -367
package/dist/domain/providers.js
CHANGED
|
@@ -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
|
|
93
|
+
* Compares the live active model_provider against managed providers to detect drift.
|
|
94
94
|
*/
|
|
95
|
-
function inspectLiveStateDrift(
|
|
96
|
-
if (
|
|
95
|
+
function inspectLiveStateDrift(currentModelProvider, providers) {
|
|
96
|
+
if (currentModelProvider === null) {
|
|
97
97
|
return {
|
|
98
|
-
|
|
98
|
+
currentModelProvider,
|
|
99
99
|
mappedProvider: null,
|
|
100
100
|
mappedProviders: [],
|
|
101
|
-
|
|
101
|
+
modelProviderMapped: false,
|
|
102
102
|
providerResolvable: false,
|
|
103
103
|
canBackfillActiveProvider: false,
|
|
104
|
-
reason: providers ? "
|
|
104
|
+
reason: providers ? "model-provider-missing" : "config-missing",
|
|
105
105
|
};
|
|
106
106
|
}
|
|
107
107
|
if (!providers) {
|
|
108
108
|
return {
|
|
109
|
-
|
|
109
|
+
currentModelProvider,
|
|
110
110
|
mappedProvider: null,
|
|
111
111
|
mappedProviders: [],
|
|
112
|
-
|
|
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 ===
|
|
120
|
+
if (provider.profile === currentModelProvider) {
|
|
121
121
|
mappedProviders.push(name);
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
if (mappedProviders.length === 1) {
|
|
125
125
|
return {
|
|
126
|
-
|
|
126
|
+
currentModelProvider,
|
|
127
127
|
mappedProvider: mappedProviders[0],
|
|
128
128
|
mappedProviders,
|
|
129
|
-
|
|
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
|
-
|
|
137
|
+
currentModelProvider,
|
|
138
138
|
mappedProvider: null,
|
|
139
139
|
mappedProviders,
|
|
140
|
-
|
|
140
|
+
modelProviderMapped: true,
|
|
141
141
|
providerResolvable: false,
|
|
142
142
|
canBackfillActiveProvider: false,
|
|
143
143
|
reason: "shared-profile",
|
|
144
144
|
};
|
|
145
145
|
}
|
|
146
146
|
return {
|
|
147
|
-
|
|
147
|
+
currentModelProvider,
|
|
148
148
|
mappedProvider: null,
|
|
149
149
|
mappedProviders: [],
|
|
150
|
-
|
|
150
|
+
modelProviderMapped: false,
|
|
151
151
|
providerResolvable: false,
|
|
152
152
|
canBackfillActiveProvider: true,
|
|
153
153
|
reason: "provider-unmapped",
|
package/dist/domain/setup.js
CHANGED
|
@@ -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
|
|
74
|
-
const liveState = (0, runtime_state_1.inspectLiveStateDrift)(
|
|
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]) => {
|
|
@@ -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
|
|
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
|
|
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)("
|
|
76
|
+
throw (0, errors_1.cliError)(runtime.reason === "unsupported" ? "COPILOT_SDK_VERSION_UNSUPPORTED" : "COPILOT_SDK_MISSING", runtime.cause, runtime.details);
|
|
50
77
|
}
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
*
|
|
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
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
|
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
|
-
|
|
161
|
+
if (session) {
|
|
162
|
+
await disconnectCopilotSession(session);
|
|
163
|
+
}
|
|
164
|
+
if (ownsClient) {
|
|
165
|
+
await stopCopilotRuntimeClient(runtimeClient);
|
|
166
|
+
}
|
|
107
167
|
}
|
|
108
168
|
}
|
|
109
|
-
async function createCopilotSession(
|
|
110
|
-
const
|
|
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)("
|
|
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 =
|
|
118
|
-
|
|
119
|
-
sdk,
|
|
120
|
-
|
|
121
|
-
|
|
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)("
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)("
|
|
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
|
-
|
|
165
|
-
|
|
315
|
+
const stop = resolveCallable(client, "stop");
|
|
316
|
+
if (stop) {
|
|
317
|
+
await Promise.resolve(stop());
|
|
166
318
|
}
|
|
167
319
|
}
|
|
168
|
-
|
|
169
|
-
|
|
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
|
|
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
|
|
213
|
-
return nestedDefault
|
|
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
|
+
}
|