@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
|
@@ -2,19 +2,44 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const copilot_bridge_1 = require("./copilot-bridge");
|
|
4
4
|
const copilot_adapter_1 = require("./copilot-adapter");
|
|
5
|
+
let requestQueue = Promise.resolve();
|
|
6
|
+
function enqueueRequest(task) {
|
|
7
|
+
const run = requestQueue.then(task, task);
|
|
8
|
+
requestQueue = run.catch(() => undefined);
|
|
9
|
+
return run;
|
|
10
|
+
}
|
|
5
11
|
async function main() {
|
|
6
12
|
const provider = process.env.CODEX_SWITCH_BRIDGE_PROVIDER ?? "copilot";
|
|
7
13
|
const host = process.env.CODEX_SWITCH_BRIDGE_HOST ?? "127.0.0.1";
|
|
8
14
|
const port = Number(process.env.CODEX_SWITCH_BRIDGE_PORT ?? "41415");
|
|
9
15
|
const apiKey = process.env.CODEX_SWITCH_BRIDGE_API_KEY ?? "";
|
|
16
|
+
const runtimesDir = process.env.CODEX_SWITCH_RUNTIMES_DIR || undefined;
|
|
17
|
+
const runtimeClient = await (0, copilot_adapter_1.createCopilotRuntimeClient)(runtimesDir);
|
|
18
|
+
await (0, copilot_adapter_1.startCopilotRuntimeClient)(runtimeClient);
|
|
19
|
+
const stopRuntime = () => {
|
|
20
|
+
void (0, copilot_adapter_1.stopCopilotRuntimeClient)(runtimeClient).finally(() => process.exit(0));
|
|
21
|
+
};
|
|
22
|
+
process.once("SIGINT", stopRuntime);
|
|
23
|
+
process.once("SIGTERM", stopRuntime);
|
|
10
24
|
await (0, copilot_bridge_1.startCopilotBridgeServer)({
|
|
11
25
|
host,
|
|
12
26
|
port,
|
|
13
27
|
apiKey,
|
|
14
|
-
executeChatCompletion: async (payload) => (0, copilot_adapter_1.sendCopilotChatCompletion)({
|
|
28
|
+
executeChatCompletion: async (payload, options) => enqueueRequest(() => (0, copilot_adapter_1.sendCopilotChatCompletion)({
|
|
15
29
|
provider,
|
|
16
30
|
payload,
|
|
17
|
-
|
|
31
|
+
runtimesDir,
|
|
32
|
+
runtimeClient,
|
|
33
|
+
timeoutMs: options?.timeoutMs,
|
|
34
|
+
onStreamEvent: (event) => {
|
|
35
|
+
if (event.type === "delta") {
|
|
36
|
+
options?.onTextDelta?.(event.delta);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
options?.onTextDone?.(event.text);
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
})),
|
|
18
43
|
});
|
|
19
44
|
}
|
|
20
45
|
if (require.main === module) {
|
|
@@ -138,13 +138,13 @@ async function probeCopilotBridgeRuntime(provider, persistedState, runtimeDir) {
|
|
|
138
138
|
/**
|
|
139
139
|
* Starts or reuses a Copilot bridge worker, then verifies its health before returning.
|
|
140
140
|
*/
|
|
141
|
-
async function ensureCopilotBridge(providerName, provider, runtimeDir) {
|
|
142
|
-
return startOrReuseCopilotBridge(providerName, provider, runtimeDir);
|
|
141
|
+
async function ensureCopilotBridge(providerName, provider, runtimeDir, runtimesDir) {
|
|
142
|
+
return startOrReuseCopilotBridge(providerName, provider, runtimeDir, runtimesDir);
|
|
143
143
|
}
|
|
144
144
|
/**
|
|
145
145
|
* Starts or reuses a Copilot bridge worker and reports the chosen port.
|
|
146
146
|
*/
|
|
147
|
-
async function startOrReuseCopilotBridge(providerName, provider, runtimeDir) {
|
|
147
|
+
async function startOrReuseCopilotBridge(providerName, provider, runtimeDir, runtimesDir) {
|
|
148
148
|
if (!(0, providers_1.isCopilotBridgeProvider)(provider)) {
|
|
149
149
|
throw (0, errors_1.cliError)("RUNTIME_PROVIDER_INVALID", "Provider is not backed by a Copilot bridge runtime.", {
|
|
150
150
|
provider: providerName,
|
|
@@ -208,6 +208,8 @@ async function startOrReuseCopilotBridge(providerName, provider, runtimeDir) {
|
|
|
208
208
|
CODEX_SWITCH_BRIDGE_PORT: String(selectedPort),
|
|
209
209
|
CODEX_SWITCH_BRIDGE_API_KEY: provider.apiKey,
|
|
210
210
|
CODEX_SWITCH_BRIDGE_BASE_URL: selectedBaseUrl,
|
|
211
|
+
CODEX_SWITCH_RUNTIME_DIR: runtimeDir ?? "",
|
|
212
|
+
CODEX_SWITCH_RUNTIMES_DIR: runtimesDir ?? "",
|
|
211
213
|
},
|
|
212
214
|
});
|
|
213
215
|
}
|
|
@@ -285,19 +287,35 @@ function createCopilotBridgeRequestHandler(context) {
|
|
|
285
287
|
}
|
|
286
288
|
if (method === "POST" && url === "/v1/chat/completions") {
|
|
287
289
|
const body = await readJsonBody(request);
|
|
290
|
+
const timeoutMs = parseBridgeRequestTimeoutMs(body, "/v1/chat/completions");
|
|
288
291
|
const stream = Boolean(body.stream);
|
|
289
|
-
const payload = await context.executeChatCompletion(body);
|
|
290
292
|
if (stream) {
|
|
291
293
|
response.writeHead(200, {
|
|
292
294
|
"content-type": "text/event-stream",
|
|
293
295
|
"cache-control": "no-cache",
|
|
294
296
|
connection: "keep-alive",
|
|
295
297
|
});
|
|
296
|
-
|
|
298
|
+
const heartbeat = startSseHeartbeat(response);
|
|
299
|
+
const payload = await context.executeChatCompletion(body, {
|
|
300
|
+
timeoutMs,
|
|
301
|
+
onTextDelta: (delta) => {
|
|
302
|
+
response.write(`data: ${JSON.stringify({
|
|
303
|
+
choices: [
|
|
304
|
+
{
|
|
305
|
+
index: 0,
|
|
306
|
+
delta: { content: delta },
|
|
307
|
+
finish_reason: null,
|
|
308
|
+
},
|
|
309
|
+
],
|
|
310
|
+
})}\n\n`);
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
clearInterval(heartbeat);
|
|
297
314
|
response.write("data: [DONE]\n\n");
|
|
298
315
|
response.end();
|
|
299
316
|
return;
|
|
300
317
|
}
|
|
318
|
+
const payload = await context.executeChatCompletion(body, { timeoutMs });
|
|
301
319
|
response.writeHead(200, { "content-type": "application/json" });
|
|
302
320
|
response.end(JSON.stringify(payload));
|
|
303
321
|
return;
|
|
@@ -305,20 +323,43 @@ function createCopilotBridgeRequestHandler(context) {
|
|
|
305
323
|
if (method === "POST" && url === "/v1/responses") {
|
|
306
324
|
const body = await readJsonBody(request);
|
|
307
325
|
const normalized = normalizeResponsesRequest(body);
|
|
308
|
-
const
|
|
326
|
+
const chatPayload = {
|
|
309
327
|
model: normalized.model,
|
|
310
328
|
messages: normalized.messages,
|
|
311
|
-
}
|
|
329
|
+
};
|
|
312
330
|
if (normalized.stream) {
|
|
313
331
|
response.writeHead(200, {
|
|
314
332
|
"content-type": "text/event-stream",
|
|
315
333
|
"cache-control": "no-cache",
|
|
316
334
|
connection: "keep-alive",
|
|
317
335
|
});
|
|
318
|
-
|
|
336
|
+
const responseId = `resp_${Date.now()}`;
|
|
337
|
+
const messageId = buildResponsesMessageId(responseId);
|
|
338
|
+
writeResponsesStreamStart(response, responseId, normalized.model, messageId);
|
|
339
|
+
const heartbeat = startSseHeartbeat(response);
|
|
340
|
+
let text = "";
|
|
341
|
+
const payload = await context.executeChatCompletion(chatPayload, {
|
|
342
|
+
timeoutMs: normalized.timeoutMs,
|
|
343
|
+
onTextDelta: (delta) => {
|
|
344
|
+
text += delta;
|
|
345
|
+
writeResponsesTextDelta(response, messageId, delta);
|
|
346
|
+
},
|
|
347
|
+
onTextDone: (doneText) => {
|
|
348
|
+
if (text.length === 0) {
|
|
349
|
+
text = doneText;
|
|
350
|
+
writeResponsesTextDelta(response, messageId, doneText);
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
});
|
|
354
|
+
clearInterval(heartbeat);
|
|
355
|
+
const outputText = text || getChatCompletionText(payload);
|
|
356
|
+
writeResponsesStreamDone(response, responseId, normalized.model, messageId, outputText);
|
|
319
357
|
response.end();
|
|
320
358
|
return;
|
|
321
359
|
}
|
|
360
|
+
const payload = await context.executeChatCompletion(chatPayload, {
|
|
361
|
+
timeoutMs: normalized.timeoutMs,
|
|
362
|
+
});
|
|
322
363
|
response.writeHead(200, { "content-type": "application/json" });
|
|
323
364
|
response.end(JSON.stringify(buildResponsesPayload(payload)));
|
|
324
365
|
return;
|
|
@@ -332,9 +373,9 @@ function createCopilotBridgeRequestHandler(context) {
|
|
|
332
373
|
response.end(JSON.stringify({ error: { message: "Not found" } }));
|
|
333
374
|
}
|
|
334
375
|
catch (error) {
|
|
335
|
-
const statusCode =
|
|
376
|
+
const statusCode = mapBridgeErrorStatus(error);
|
|
336
377
|
response.writeHead(statusCode, { "content-type": "application/json" });
|
|
337
|
-
response.end(JSON.stringify({ error: { message: error instanceof Error ? error.message : String(error) } }));
|
|
378
|
+
response.end(JSON.stringify({ error: { message: error instanceof Error ? error.message : String(error), code: isCliError(error) ? error.code : "BRIDGE_RUNTIME_FAILURE" } }));
|
|
338
379
|
}
|
|
339
380
|
};
|
|
340
381
|
}
|
|
@@ -354,8 +395,22 @@ function normalizeResponsesRequest(body) {
|
|
|
354
395
|
model: payload.model,
|
|
355
396
|
messages,
|
|
356
397
|
stream: payload.stream === true,
|
|
398
|
+
timeoutMs: parseBridgeRequestTimeoutMs(body, "/v1/responses"),
|
|
357
399
|
};
|
|
358
400
|
}
|
|
401
|
+
/**
|
|
402
|
+
* Extracts one optional request timeout for bridge-backed completions.
|
|
403
|
+
*/
|
|
404
|
+
function parseBridgeRequestTimeoutMs(body, endpoint) {
|
|
405
|
+
const timeoutMsValue = body.timeout_ms ?? body.timeoutMs;
|
|
406
|
+
if (timeoutMsValue === undefined) {
|
|
407
|
+
return undefined;
|
|
408
|
+
}
|
|
409
|
+
if (typeof timeoutMsValue !== "number" || !Number.isFinite(timeoutMsValue) || timeoutMsValue <= 0) {
|
|
410
|
+
throw (0, errors_1.cliError)("BRIDGE_UNSUPPORTED_REQUEST", `Copilot bridge ${endpoint} timeout must be a positive number when provided.`);
|
|
411
|
+
}
|
|
412
|
+
return timeoutMsValue;
|
|
413
|
+
}
|
|
359
414
|
function normalizeResponsesInput(input) {
|
|
360
415
|
if (typeof input === "string") {
|
|
361
416
|
return [{ role: "user", content: input }];
|
|
@@ -575,6 +630,118 @@ function writeResponsesStream(response, payload) {
|
|
|
575
630
|
},
|
|
576
631
|
});
|
|
577
632
|
}
|
|
633
|
+
function writeResponsesStreamStart(response, responseId, model, messageId) {
|
|
634
|
+
const createdAt = Math.floor(Date.now() / 1000);
|
|
635
|
+
const inProgressResponse = {
|
|
636
|
+
id: responseId,
|
|
637
|
+
object: "response",
|
|
638
|
+
created_at: createdAt,
|
|
639
|
+
model,
|
|
640
|
+
status: "in_progress",
|
|
641
|
+
output: [],
|
|
642
|
+
output_text: "",
|
|
643
|
+
};
|
|
644
|
+
writeSseEvent(response, "response.created", {
|
|
645
|
+
type: "response.created",
|
|
646
|
+
response: inProgressResponse,
|
|
647
|
+
});
|
|
648
|
+
writeSseEvent(response, "response.in_progress", {
|
|
649
|
+
type: "response.in_progress",
|
|
650
|
+
response: inProgressResponse,
|
|
651
|
+
});
|
|
652
|
+
writeSseEvent(response, "response.output_item.added", {
|
|
653
|
+
type: "response.output_item.added",
|
|
654
|
+
output_index: 0,
|
|
655
|
+
item: {
|
|
656
|
+
id: messageId,
|
|
657
|
+
type: "message",
|
|
658
|
+
status: "in_progress",
|
|
659
|
+
role: "assistant",
|
|
660
|
+
content: [],
|
|
661
|
+
},
|
|
662
|
+
});
|
|
663
|
+
writeSseEvent(response, "response.content_part.added", {
|
|
664
|
+
type: "response.content_part.added",
|
|
665
|
+
item_id: messageId,
|
|
666
|
+
output_index: 0,
|
|
667
|
+
content_index: 0,
|
|
668
|
+
part: {
|
|
669
|
+
type: "output_text",
|
|
670
|
+
text: "",
|
|
671
|
+
annotations: [],
|
|
672
|
+
},
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
function writeResponsesTextDelta(response, messageId, delta) {
|
|
676
|
+
if (delta.length === 0) {
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
writeSseEvent(response, "response.output_text.delta", {
|
|
680
|
+
type: "response.output_text.delta",
|
|
681
|
+
item_id: messageId,
|
|
682
|
+
output_index: 0,
|
|
683
|
+
content_index: 0,
|
|
684
|
+
delta,
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
function writeResponsesStreamDone(response, responseId, model, messageId, outputText) {
|
|
688
|
+
const completedMessage = {
|
|
689
|
+
id: messageId,
|
|
690
|
+
type: "message",
|
|
691
|
+
status: "completed",
|
|
692
|
+
role: "assistant",
|
|
693
|
+
content: [
|
|
694
|
+
{
|
|
695
|
+
type: "output_text",
|
|
696
|
+
text: outputText,
|
|
697
|
+
annotations: [],
|
|
698
|
+
},
|
|
699
|
+
],
|
|
700
|
+
};
|
|
701
|
+
writeSseEvent(response, "response.output_text.done", {
|
|
702
|
+
type: "response.output_text.done",
|
|
703
|
+
item_id: messageId,
|
|
704
|
+
output_index: 0,
|
|
705
|
+
content_index: 0,
|
|
706
|
+
text: outputText,
|
|
707
|
+
});
|
|
708
|
+
writeSseEvent(response, "response.content_part.done", {
|
|
709
|
+
type: "response.content_part.done",
|
|
710
|
+
item_id: messageId,
|
|
711
|
+
output_index: 0,
|
|
712
|
+
content_index: 0,
|
|
713
|
+
part: {
|
|
714
|
+
type: "output_text",
|
|
715
|
+
text: outputText,
|
|
716
|
+
annotations: [],
|
|
717
|
+
},
|
|
718
|
+
});
|
|
719
|
+
writeSseEvent(response, "response.output_item.done", {
|
|
720
|
+
type: "response.output_item.done",
|
|
721
|
+
output_index: 0,
|
|
722
|
+
item: completedMessage,
|
|
723
|
+
});
|
|
724
|
+
writeSseEvent(response, "response.completed", {
|
|
725
|
+
type: "response.completed",
|
|
726
|
+
response: {
|
|
727
|
+
id: responseId,
|
|
728
|
+
object: "response",
|
|
729
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
730
|
+
model,
|
|
731
|
+
status: "completed",
|
|
732
|
+
output: [completedMessage],
|
|
733
|
+
output_text: outputText,
|
|
734
|
+
},
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
function startSseHeartbeat(response) {
|
|
738
|
+
return setInterval(() => {
|
|
739
|
+
response.write(": keep-alive\n\n");
|
|
740
|
+
}, 15000);
|
|
741
|
+
}
|
|
742
|
+
function getChatCompletionText(payload) {
|
|
743
|
+
return payload.choices?.[0]?.message?.content ?? "";
|
|
744
|
+
}
|
|
578
745
|
/**
|
|
579
746
|
* Formats and writes one server-sent event frame.
|
|
580
747
|
*/
|
|
@@ -594,6 +761,21 @@ function buildResponsesMessageId(responseId) {
|
|
|
594
761
|
function isCliError(error) {
|
|
595
762
|
return Boolean(error && typeof error === "object" && typeof error.code === "string");
|
|
596
763
|
}
|
|
764
|
+
function mapBridgeErrorStatus(error) {
|
|
765
|
+
if (!isCliError(error)) {
|
|
766
|
+
return 500;
|
|
767
|
+
}
|
|
768
|
+
if (error.code === "BRIDGE_UNSUPPORTED_REQUEST") {
|
|
769
|
+
return 400;
|
|
770
|
+
}
|
|
771
|
+
if (error.code === "COPILOT_AUTH_REQUIRED") {
|
|
772
|
+
return 401;
|
|
773
|
+
}
|
|
774
|
+
if (error.code === "BRIDGE_UPSTREAM_TIMEOUT") {
|
|
775
|
+
return 504;
|
|
776
|
+
}
|
|
777
|
+
return 500;
|
|
778
|
+
}
|
|
597
779
|
/**
|
|
598
780
|
* Returns a stable build identifier for the compiled bridge worker bundle.
|
|
599
781
|
*/
|
|
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.setCopilotCliSpawnImplementation = setCopilotCliSpawnImplementation;
|
|
37
37
|
exports.resetCopilotCliSpawnImplementation = resetCopilotCliSpawnImplementation;
|
|
38
38
|
exports.checkCopilotCliAvailable = checkCopilotCliAvailable;
|
|
39
|
+
exports.resolveCopilotCliInvocation = resolveCopilotCliInvocation;
|
|
39
40
|
exports.runCopilotLogin = runCopilotLogin;
|
|
40
41
|
const fs = __importStar(require("node:fs"));
|
|
41
42
|
const path = __importStar(require("node:path"));
|
|
@@ -78,6 +79,12 @@ function checkCopilotCliAvailable(runtimesDir) {
|
|
|
78
79
|
command: formatInvocation(invocation),
|
|
79
80
|
};
|
|
80
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* Resolves the Copilot CLI invocation used by SDK clients and command probes.
|
|
84
|
+
*/
|
|
85
|
+
function resolveCopilotCliInvocation(args = [], runtimesDir) {
|
|
86
|
+
return getCopilotInvocation(args, runtimesDir);
|
|
87
|
+
}
|
|
81
88
|
/**
|
|
82
89
|
* Launches the official `copilot login` flow in the current terminal.
|
|
83
90
|
*/
|
|
@@ -37,6 +37,10 @@ exports.setCopilotInstallerSpawnImplementation = setCopilotInstallerSpawnImpleme
|
|
|
37
37
|
exports.resetCopilotInstallerSpawnImplementation = resetCopilotInstallerSpawnImplementation;
|
|
38
38
|
exports.getCopilotRuntimeInstallDir = getCopilotRuntimeInstallDir;
|
|
39
39
|
exports.getCopilotSdkPackageName = getCopilotSdkPackageName;
|
|
40
|
+
exports.getSupportedCopilotSdkVersion = getSupportedCopilotSdkVersion;
|
|
41
|
+
exports.getCopilotNodeRuntimeStatus = getCopilotNodeRuntimeStatus;
|
|
42
|
+
exports.assertCopilotNodeRuntimeSupported = assertCopilotNodeRuntimeSupported;
|
|
43
|
+
exports.isSupportedCopilotSdkVersion = isSupportedCopilotSdkVersion;
|
|
40
44
|
exports.probeCopilotSdkInstall = probeCopilotSdkInstall;
|
|
41
45
|
exports.installCopilotSdk = installCopilotSdk;
|
|
42
46
|
const fs = __importStar(require("node:fs"));
|
|
@@ -45,7 +49,8 @@ const node_child_process_1 = require("node:child_process");
|
|
|
45
49
|
const errors_1 = require("../domain/errors");
|
|
46
50
|
const codex_paths_1 = require("../storage/codex-paths");
|
|
47
51
|
const COPILOT_SDK_PACKAGE = "@github/copilot-sdk";
|
|
48
|
-
const COPILOT_SDK_VERSION = "
|
|
52
|
+
const COPILOT_SDK_VERSION = "1.0.2";
|
|
53
|
+
const COPILOT_MIN_NODE_MAJOR = 20;
|
|
49
54
|
let spawnImplementation = node_child_process_1.spawnSync;
|
|
50
55
|
/**
|
|
51
56
|
* Overrides the spawn implementation for runtime installer tests.
|
|
@@ -76,6 +81,47 @@ function getCopilotRuntimeInstallDir(runtimesDir) {
|
|
|
76
81
|
function getCopilotSdkPackageName() {
|
|
77
82
|
return COPILOT_SDK_PACKAGE;
|
|
78
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Returns the supported Copilot SDK package version installed by this release.
|
|
86
|
+
*/
|
|
87
|
+
function getSupportedCopilotSdkVersion() {
|
|
88
|
+
return COPILOT_SDK_VERSION;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Returns whether the active Node.js runtime can run the Copilot SDK path.
|
|
92
|
+
*/
|
|
93
|
+
function getCopilotNodeRuntimeStatus(version = process.versions.node) {
|
|
94
|
+
const major = Number(version.split(".")[0]);
|
|
95
|
+
if (Number.isInteger(major) && major >= COPILOT_MIN_NODE_MAJOR) {
|
|
96
|
+
return { ok: true, version };
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
ok: false,
|
|
100
|
+
version,
|
|
101
|
+
required: `>=${String(COPILOT_MIN_NODE_MAJOR)}`,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Fails early when a command path requires the Copilot SDK runtime under Node.js <20.
|
|
106
|
+
*/
|
|
107
|
+
function assertCopilotNodeRuntimeSupported(version = process.versions.node) {
|
|
108
|
+
const status = getCopilotNodeRuntimeStatus(version);
|
|
109
|
+
if (!status.ok) {
|
|
110
|
+
throw (0, errors_1.cliError)("COPILOT_RUNTIME_NODE_UNSUPPORTED", "Copilot runtime support requires Node.js >=20. Direct providers continue to support Node.js >=18.", {
|
|
111
|
+
nodeVersion: status.version,
|
|
112
|
+
requiredNode: status.required,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Returns whether an installed Copilot SDK version is supported by this release.
|
|
118
|
+
*/
|
|
119
|
+
function isSupportedCopilotSdkVersion(version) {
|
|
120
|
+
if (!version || version.includes("-")) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
return compareSemver(version, COPILOT_SDK_VERSION) >= 0;
|
|
124
|
+
}
|
|
79
125
|
/**
|
|
80
126
|
* Reports whether the optional Copilot SDK runtime is currently installed.
|
|
81
127
|
*/
|
|
@@ -153,6 +199,18 @@ function resolveNpmInstallCommand() {
|
|
|
153
199
|
args: installArgs,
|
|
154
200
|
};
|
|
155
201
|
}
|
|
202
|
+
function compareSemver(left, right) {
|
|
203
|
+
const leftParts = left.split(".").map((part) => Number(part));
|
|
204
|
+
const rightParts = right.split(".").map((part) => Number(part));
|
|
205
|
+
for (let index = 0; index < 3; index += 1) {
|
|
206
|
+
const leftPart = Number.isFinite(leftParts[index]) ? leftParts[index] : 0;
|
|
207
|
+
const rightPart = Number.isFinite(rightParts[index]) ? rightParts[index] : 0;
|
|
208
|
+
if (leftPart !== rightPart) {
|
|
209
|
+
return leftPart > rightPart ? 1 : -1;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return 0;
|
|
213
|
+
}
|
|
156
214
|
/**
|
|
157
215
|
* Finds a locally available npm CLI script near the active Node runtime.
|
|
158
216
|
*/
|
|
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.getCopilotSdkEntrypoint = getCopilotSdkEntrypoint;
|
|
37
37
|
exports.loadCopilotSdk = loadCopilotSdk;
|
|
38
38
|
const path = __importStar(require("node:path"));
|
|
39
|
+
const node_module_1 = require("node:module");
|
|
39
40
|
const errors_1 = require("../domain/errors");
|
|
40
41
|
const copilot_installer_1 = require("./copilot-installer");
|
|
41
42
|
/**
|
|
@@ -55,5 +56,7 @@ async function loadCopilotSdk(runtimesDir) {
|
|
|
55
56
|
packageName: status.packageName,
|
|
56
57
|
});
|
|
57
58
|
}
|
|
58
|
-
|
|
59
|
+
const runtimePackageJson = path.join(status.installDir, "package.json");
|
|
60
|
+
const runtimeRequire = (0, node_module_1.createRequire)(runtimePackageJson);
|
|
61
|
+
return runtimeRequire("@github/copilot-sdk");
|
|
59
62
|
}
|
|
@@ -73,12 +73,12 @@ function readStructuredConfig(configPath) {
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
/**
|
|
76
|
-
* Reads the active top-level
|
|
76
|
+
* Reads the active top-level model_provider route from config.toml.
|
|
77
77
|
*/
|
|
78
78
|
function readCurrentProfile(configPath) {
|
|
79
|
-
const profile = readStructuredConfig(configPath).
|
|
79
|
+
const profile = readStructuredConfig(configPath).currentModelProvider;
|
|
80
80
|
if (!profile) {
|
|
81
|
-
throw (0, errors_1.cliError)("PROFILE_NOT_FOUND", "No top-level
|
|
81
|
+
throw (0, errors_1.cliError)("PROFILE_NOT_FOUND", "No top-level model_provider is set in config.toml.", {
|
|
82
82
|
file: configPath,
|
|
83
83
|
});
|
|
84
84
|
}
|
|
@@ -91,18 +91,10 @@ function listConfigProfiles(configPath) {
|
|
|
91
91
|
return new Set(readStructuredConfig(configPath).profiles.map((profile) => profile.name));
|
|
92
92
|
}
|
|
93
93
|
/**
|
|
94
|
-
*
|
|
94
|
+
* Loads config.toml for commands that project one model_provider route.
|
|
95
95
|
*/
|
|
96
96
|
function ensureProfileExists(configPath, profile, provider) {
|
|
97
|
-
|
|
98
|
-
if (!document.profiles.some((entry) => entry.name === profile)) {
|
|
99
|
-
throw (0, errors_1.cliError)("PROFILE_NOT_FOUND", `Profile "${profile}" does not exist in config.toml.`, {
|
|
100
|
-
file: configPath,
|
|
101
|
-
provider,
|
|
102
|
-
profile,
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
return document;
|
|
97
|
+
return readStructuredConfig(configPath);
|
|
106
98
|
}
|
|
107
99
|
/**
|
|
108
100
|
* Resolves one profile view and enforces the managed model_provider contract.
|
|
@@ -166,7 +158,7 @@ function requireModelProviderRuntimeSection(document, profile) {
|
|
|
166
158
|
*/
|
|
167
159
|
function updateTopLevelProfile(configPath, configContent, profile) {
|
|
168
160
|
(0, fs_utils_1.writeTextFileAtomic)(configPath, (0, config_1.applyPatchOperations)(configContent, (0, config_1.planConfigMutation)((0, config_1.parseStructuredConfig)(configContent), {
|
|
169
|
-
|
|
161
|
+
setLegacyProfile: profile,
|
|
170
162
|
}).operations));
|
|
171
163
|
}
|
|
172
164
|
/**
|