@daomar/copilot-api 0.7.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -5,13 +5,14 @@ import fs from "node:fs/promises";
5
5
  import os from "node:os";
6
6
  import path from "node:path";
7
7
  import { randomUUID } from "node:crypto";
8
+ import fs$1 from "node:fs";
9
+ import process$1 from "node:process";
10
+ import { execFileSync, execSync } from "node:child_process";
8
11
  import clipboard from "clipboardy";
9
12
  import { serve } from "srvx";
10
13
  import invariant from "tiny-invariant";
11
14
  import { getProxyForUrl } from "proxy-from-env";
12
15
  import { Agent, ProxyAgent, setGlobalDispatcher } from "undici";
13
- import { execSync } from "node:child_process";
14
- import process$1 from "node:process";
15
16
  import { Hono } from "hono";
16
17
  import { cors } from "hono/cors";
17
18
  import { logger } from "hono/logger";
@@ -363,6 +364,266 @@ const checkUsage = defineCommand({
363
364
  }
364
365
  });
365
366
 
367
+ //#endregion
368
+ //#region src/lib/agent-config.ts
369
+ const CODEX_CONFIG_PATH = path.join(os.homedir(), ".codex", "config.toml");
370
+ const CLAUDE_SETTINGS_PATH = path.join(os.homedir(), ".claude", "settings.json");
371
+ const DEFAULT_CODEX_MODEL = "gpt-5.5";
372
+ const DEFAULT_CLAUDE_MODEL = "claude-sonnet-4.5";
373
+ const DEFAULT_CLAUDE_SMALL_MODEL = "claude-haiku-4.5";
374
+ const proxyBaseUrl = (host, port) => `http://${host}:${port}`;
375
+ function buildCodexProviderBlock(baseUrl) {
376
+ return [
377
+ `[model_providers.copilot]`,
378
+ `name = "GitHub Copilot"`,
379
+ `base_url = "${baseUrl}/v1"`,
380
+ `wire_api = "responses"`,
381
+ `requires_openai_auth = false`,
382
+ `http_headers = { "Openai-Intent" = "conversation-edits", "x-initiator" = "user" }`
383
+ ].join("\n");
384
+ }
385
+ const tableHeaderKey = (line) => {
386
+ const match = /^\s*\[([^[\]]+)\]\s*$/.exec(line);
387
+ return match ? match[1].trim() : null;
388
+ };
389
+ const isCopilotProviderTable = (key) => key === "model_providers.copilot" || key.startsWith("model_providers.copilot.");
390
+ const firstTableIndex = (lines) => {
391
+ const index = lines.findIndex((line) => tableHeaderKey(line) !== null);
392
+ return index === -1 ? lines.length : index;
393
+ };
394
+ function upsertTopLevelKey(content, update) {
395
+ const { key, value, override } = update;
396
+ const lines = content.split("\n");
397
+ const limit = firstTableIndex(lines);
398
+ const keyRegex = /* @__PURE__ */ new RegExp(`^\\s*${key}\\s*=`);
399
+ for (let i = 0; i < limit; i++) if (keyRegex.test(lines[i])) {
400
+ if (override) lines[i] = `${key} = ${value}`;
401
+ return lines.join("\n");
402
+ }
403
+ lines.splice(0, 0, `${key} = ${value}`);
404
+ return lines.join("\n");
405
+ }
406
+ function upsertCodexProvider(content, block) {
407
+ const lines = content.split("\n");
408
+ let start$1 = -1;
409
+ for (const [i, line] of lines.entries()) if (tableHeaderKey(line) === "model_providers.copilot") {
410
+ start$1 = i;
411
+ break;
412
+ }
413
+ if (start$1 === -1) {
414
+ const trimmed = content.replace(/\n*$/, "");
415
+ return (trimmed.length > 0 ? `${trimmed}\n\n` : "") + `${block}\n`;
416
+ }
417
+ let end = lines.length;
418
+ for (let j = start$1 + 1; j < lines.length; j++) {
419
+ const key = tableHeaderKey(lines[j]);
420
+ if (key !== null && !isCopilotProviderTable(key)) {
421
+ end = j;
422
+ break;
423
+ }
424
+ }
425
+ let realEnd = end;
426
+ if (end < lines.length) while (realEnd - 1 > start$1) {
427
+ const prev = lines[realEnd - 1].trim();
428
+ if (prev === "" || prev.startsWith("#")) realEnd--;
429
+ else break;
430
+ }
431
+ const beforeText = lines.slice(0, start$1).join("\n").replace(/\n*$/, "");
432
+ const afterText = lines.slice(realEnd).join("\n").replace(/^\n*/, "");
433
+ const parts = [];
434
+ if (beforeText.length > 0) parts.push(beforeText);
435
+ parts.push(block);
436
+ if (afterText.length > 0) parts.push(afterText);
437
+ return `${parts.join("\n\n")}\n`;
438
+ }
439
+ function buildCodexConfig(existing, options) {
440
+ let next = existing;
441
+ next = upsertTopLevelKey(next, {
442
+ key: "model",
443
+ value: `"${options.codexModel}"`,
444
+ override: false
445
+ });
446
+ next = upsertTopLevelKey(next, {
447
+ key: "model_provider",
448
+ value: `"copilot"`,
449
+ override: true
450
+ });
451
+ next = upsertCodexProvider(next, buildCodexProviderBlock(options.baseUrl));
452
+ return next;
453
+ }
454
+ function buildClaudeSettings(existing, options) {
455
+ const env = { ...existing.env };
456
+ env.ANTHROPIC_BASE_URL = options.baseUrl;
457
+ env.ANTHROPIC_AUTH_TOKEN = "dummy";
458
+ const defaults = {
459
+ ANTHROPIC_MODEL: options.claudeModel,
460
+ ANTHROPIC_DEFAULT_OPUS_MODEL: options.claudeModel,
461
+ ANTHROPIC_DEFAULT_SONNET_MODEL: options.claudeModel,
462
+ ANTHROPIC_SMALL_FAST_MODEL: options.claudeSmallModel,
463
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: options.claudeSmallModel,
464
+ DISABLE_NON_ESSENTIAL_MODEL_CALLS: "1",
465
+ CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1"
466
+ };
467
+ for (const [key, value] of Object.entries(defaults)) if (!Object.hasOwn(env, key)) env[key] = value;
468
+ return {
469
+ ...existing,
470
+ env
471
+ };
472
+ }
473
+ const readFileOr = async (filePath, fallback) => {
474
+ try {
475
+ return await fs.readFile(filePath, "utf8");
476
+ } catch {
477
+ return fallback;
478
+ }
479
+ };
480
+ const backupFile = async (filePath) => {
481
+ try {
482
+ await fs.access(filePath);
483
+ } catch {
484
+ return;
485
+ }
486
+ const backupPath = `${filePath}.bak`;
487
+ await fs.copyFile(filePath, backupPath);
488
+ consola.info(`Backed up existing config to ${backupPath}`);
489
+ };
490
+ async function writeCodexConfig(options) {
491
+ await fs.mkdir(path.dirname(CODEX_CONFIG_PATH), { recursive: true });
492
+ const existing = await readFileOr(CODEX_CONFIG_PATH, "");
493
+ const next = buildCodexConfig(existing, options);
494
+ if (next !== existing) await backupFile(CODEX_CONFIG_PATH);
495
+ await fs.writeFile(CODEX_CONFIG_PATH, next);
496
+ return CODEX_CONFIG_PATH;
497
+ }
498
+ async function writeClaudeSettings(options) {
499
+ await fs.mkdir(path.dirname(CLAUDE_SETTINGS_PATH), { recursive: true });
500
+ const raw = await readFileOr(CLAUDE_SETTINGS_PATH, "");
501
+ let existing = {};
502
+ if (raw.trim().length > 0) try {
503
+ existing = JSON.parse(raw);
504
+ } catch {
505
+ consola.warn(`Could not parse ${CLAUDE_SETTINGS_PATH}; it will be recreated.`);
506
+ }
507
+ const next = buildClaudeSettings(existing, options);
508
+ if (raw.trim().length > 0) await backupFile(CLAUDE_SETTINGS_PATH);
509
+ await fs.writeFile(CLAUDE_SETTINGS_PATH, `${JSON.stringify(next, null, 2)}\n`);
510
+ return CLAUDE_SETTINGS_PATH;
511
+ }
512
+
513
+ //#endregion
514
+ //#region src/config.ts
515
+ async function pickModel$1(message, fallback) {
516
+ const ids = state.models?.data.map((model) => model.id) ?? [];
517
+ if (ids.length === 0) return fallback;
518
+ return await consola.prompt(message, {
519
+ type: "select",
520
+ options: ids,
521
+ initial: ids.includes(fallback) ? fallback : ids[0]
522
+ });
523
+ }
524
+ async function runConfig(options) {
525
+ const doClaude = options.claude || !options.codex;
526
+ const doCodex = options.codex || !options.claude;
527
+ let { codexModel, claudeModel, claudeSmallModel } = options;
528
+ if (options.interactive) {
529
+ await ensurePaths();
530
+ await cacheVSCodeVersion();
531
+ await setupGitHubToken();
532
+ await setupCopilotToken();
533
+ await cacheModels();
534
+ if (doCodex) codexModel = await pickModel$1("Select a model for Codex CLI", codexModel);
535
+ if (doClaude) {
536
+ claudeModel = await pickModel$1("Select a model for Claude Code", claudeModel);
537
+ claudeSmallModel = await pickModel$1("Select a small/fast model for Claude Code", claudeSmallModel);
538
+ }
539
+ }
540
+ const baseUrl = proxyBaseUrl(options.host, options.port);
541
+ const configOptions = {
542
+ baseUrl,
543
+ codexModel,
544
+ claudeModel,
545
+ claudeSmallModel
546
+ };
547
+ if (doCodex) {
548
+ const written = await writeCodexConfig(configOptions);
549
+ consola.success(`Configured Codex CLI (model "${codexModel}") at ${written}`);
550
+ }
551
+ if (doClaude) {
552
+ const written = await writeClaudeSettings(configOptions);
553
+ consola.success(`Configured Claude Code at ${written}`);
554
+ }
555
+ consola.box([
556
+ `Both tools now route through ${baseUrl}.`,
557
+ ``,
558
+ `Next steps:`,
559
+ ` 1. Log in once on this machine: copilot-api auth`,
560
+ ` 2. Start the proxy: copilot-api start --port ${options.port}`,
561
+ ` 3. Run codex or claude as usual.`
562
+ ].join("\n"));
563
+ }
564
+ const config = defineCommand({
565
+ meta: {
566
+ name: "config",
567
+ description: "Write Codex CLI and Claude Code config files so they use this proxy"
568
+ },
569
+ args: {
570
+ port: {
571
+ alias: "p",
572
+ type: "string",
573
+ default: "4141",
574
+ description: "Port the proxy listens on"
575
+ },
576
+ host: {
577
+ type: "string",
578
+ default: "localhost",
579
+ description: "Host the proxy listens on"
580
+ },
581
+ claude: {
582
+ type: "boolean",
583
+ default: false,
584
+ description: "Only configure Claude Code (default: configure both)"
585
+ },
586
+ codex: {
587
+ type: "boolean",
588
+ default: false,
589
+ description: "Only configure Codex CLI (default: configure both)"
590
+ },
591
+ interactive: {
592
+ alias: "i",
593
+ type: "boolean",
594
+ default: false,
595
+ description: "Pick models from the live model list (requires GitHub login)"
596
+ },
597
+ "codex-model": {
598
+ type: "string",
599
+ default: DEFAULT_CODEX_MODEL,
600
+ description: "Model to use for Codex CLI"
601
+ },
602
+ "claude-model": {
603
+ type: "string",
604
+ default: DEFAULT_CLAUDE_MODEL,
605
+ description: "Main model to use for Claude Code"
606
+ },
607
+ "claude-small-model": {
608
+ type: "string",
609
+ default: DEFAULT_CLAUDE_SMALL_MODEL,
610
+ description: "Small/fast model to use for Claude Code"
611
+ }
612
+ },
613
+ run({ args }) {
614
+ return runConfig({
615
+ host: args.host,
616
+ port: Number.parseInt(args.port, 10),
617
+ codexModel: args["codex-model"],
618
+ claudeModel: args["claude-model"],
619
+ claudeSmallModel: args["claude-small-model"],
620
+ claude: args.claude,
621
+ codex: args.codex,
622
+ interactive: args.interactive
623
+ });
624
+ }
625
+ });
626
+
366
627
  //#endregion
367
628
  //#region src/debug.ts
368
629
  async function getPackageVersion() {
@@ -437,6 +698,406 @@ const debug = defineCommand({
437
698
  }
438
699
  });
439
700
 
701
+ //#endregion
702
+ //#region src/lib/service.ts
703
+ const SERVICE_NAME = "copilot-api";
704
+ const WINDOWS_TASK_NAME = "CopilotAPI";
705
+ const quote = (value) => `"${value.replaceAll("\"", String.raw`\"`)}"`;
706
+ const startCommandArgs = (target) => [
707
+ "start",
708
+ "--port",
709
+ String(target.port),
710
+ "--account-type",
711
+ target.accountType
712
+ ];
713
+ function buildSystemdUnit(target) {
714
+ return `[Unit]
715
+ Description=GitHub Copilot API proxy (copilot-api)
716
+ After=network-online.target
717
+ Wants=network-online.target
718
+
719
+ [Service]
720
+ Type=simple
721
+ ExecStart=${[
722
+ quote(target.runtimePath),
723
+ quote(target.scriptPath),
724
+ ...startCommandArgs(target)
725
+ ].join(" ")}
726
+ Restart=always
727
+ RestartSec=5
728
+ Environment=NODE_ENV=production
729
+
730
+ [Install]
731
+ WantedBy=default.target
732
+ `;
733
+ }
734
+ const systemdUnitPath = () => path.join(os.homedir(), ".config", "systemd", "user", `${SERVICE_NAME}.service`);
735
+ const runQuietly = (command, args) => {
736
+ try {
737
+ return {
738
+ ok: true,
739
+ output: execFileSync(command, args, {
740
+ stdio: "pipe",
741
+ encoding: "utf8"
742
+ })
743
+ };
744
+ } catch (error) {
745
+ const err = error;
746
+ return {
747
+ ok: false,
748
+ output: err.stderr ? err.stderr.toString() : err.message ?? "unknown error"
749
+ };
750
+ }
751
+ };
752
+ async function installSystemdService(target) {
753
+ const unitPath = systemdUnitPath();
754
+ await fs.mkdir(path.dirname(unitPath), { recursive: true });
755
+ await fs.writeFile(unitPath, buildSystemdUnit(target));
756
+ consola.info(`Wrote systemd unit to ${unitPath}`);
757
+ const reload = runQuietly("systemctl", ["--user", "daemon-reload"]);
758
+ if (!reload.ok) return {
759
+ installed: false,
760
+ manager: "systemd",
761
+ message: `Failed to reload systemd: ${reload.output.trim()}. Unit written to ${unitPath}.`
762
+ };
763
+ const enable = runQuietly("systemctl", [
764
+ "--user",
765
+ "enable",
766
+ "--now",
767
+ `${SERVICE_NAME}.service`
768
+ ]);
769
+ if (!enable.ok) return {
770
+ installed: false,
771
+ manager: "systemd",
772
+ message: `Failed to enable service: ${enable.output.trim()}. Try: systemctl --user enable --now ${SERVICE_NAME}.service`
773
+ };
774
+ if (!runQuietly("loginctl", ["enable-linger", os.userInfo().username]).ok) consola.warn(`Could not enable lingering (service may stop on logout). Run manually: sudo loginctl enable-linger ${os.userInfo().username}`);
775
+ return {
776
+ installed: true,
777
+ manager: "systemd",
778
+ message: `Service enabled. Manage with: systemctl --user status ${SERVICE_NAME}`
779
+ };
780
+ }
781
+ function buildWindowsTaskXml(target) {
782
+ const command = target.runtimePath;
783
+ const args = [quote(target.scriptPath), ...startCommandArgs(target)].join(" ");
784
+ return `<?xml version="1.0" encoding="UTF-16"?>
785
+ <Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
786
+ <RegistrationInfo>
787
+ <Description>GitHub Copilot API proxy (copilot-api)</Description>
788
+ </RegistrationInfo>
789
+ <Triggers>
790
+ <LogonTrigger>
791
+ <Enabled>true</Enabled>
792
+ </LogonTrigger>
793
+ </Triggers>
794
+ <Principals>
795
+ <Principal id="Author">
796
+ <LogonType>InteractiveToken</LogonType>
797
+ <RunLevel>LeastPrivilege</RunLevel>
798
+ </Principal>
799
+ </Principals>
800
+ <Settings>
801
+ <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
802
+ <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
803
+ <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
804
+ <AllowHardTerminate>true</AllowHardTerminate>
805
+ <StartWhenAvailable>true</StartWhenAvailable>
806
+ <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
807
+ <IdleSettings>
808
+ <StopOnIdleEnd>false</StopOnIdleEnd>
809
+ <RestartOnIdle>false</RestartOnIdle>
810
+ </IdleSettings>
811
+ <AllowStartOnDemand>true</AllowStartOnDemand>
812
+ <Enabled>true</Enabled>
813
+ <Hidden>false</Hidden>
814
+ <RunOnlyIfIdle>false</RunOnlyIfIdle>
815
+ <RestartOnFailure>
816
+ <Interval>PT1M</Interval>
817
+ <Count>999</Count>
818
+ </RestartOnFailure>
819
+ <ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
820
+ <Priority>7</Priority>
821
+ </Settings>
822
+ <Actions Context="Author">
823
+ <Exec>
824
+ <Command>${command}</Command>
825
+ <Arguments>${args}</Arguments>
826
+ </Exec>
827
+ </Actions>
828
+ </Task>
829
+ `;
830
+ }
831
+ async function installWindowsTask(target) {
832
+ const xml = buildWindowsTaskXml(target);
833
+ const xmlPath = path.join(os.tmpdir(), `${SERVICE_NAME}-task.xml`);
834
+ await fs.writeFile(xmlPath, `\uFEFF${xml}`, "utf16le");
835
+ const create = runQuietly("schtasks", [
836
+ "/Create",
837
+ "/TN",
838
+ WINDOWS_TASK_NAME,
839
+ "/XML",
840
+ xmlPath,
841
+ "/F"
842
+ ]);
843
+ if (!create.ok) return {
844
+ installed: false,
845
+ manager: "schtasks",
846
+ message: `Failed to register task: ${create.output.trim()}. XML written to ${xmlPath}.`
847
+ };
848
+ runQuietly("schtasks", [
849
+ "/Run",
850
+ "/TN",
851
+ WINDOWS_TASK_NAME
852
+ ]);
853
+ return {
854
+ installed: true,
855
+ manager: "schtasks",
856
+ message: `Scheduled task "${WINDOWS_TASK_NAME}" registered and started. Manage it in Task Scheduler.`
857
+ };
858
+ }
859
+ async function installService(target) {
860
+ const platform = os.platform();
861
+ if (platform === "linux") return installSystemdService(target);
862
+ if (platform === "win32") return installWindowsTask(target);
863
+ const exec = [
864
+ target.runtimePath,
865
+ target.scriptPath,
866
+ ...startCommandArgs(target)
867
+ ].join(" ");
868
+ return {
869
+ installed: false,
870
+ manager: "none",
871
+ message: `Automatic service install is not supported on "${platform}". Run this manually (e.g. via a launch agent):\n ${exec}`
872
+ };
873
+ }
874
+
875
+ //#endregion
876
+ //#region src/setup.ts
877
+ const ACCOUNT_TYPES = [
878
+ "individual",
879
+ "business",
880
+ "enterprise"
881
+ ];
882
+ const resolveRuntime = () => {
883
+ let scriptPath = process$1.argv[1] ?? "";
884
+ try {
885
+ scriptPath = fs$1.realpathSync(scriptPath);
886
+ } catch {}
887
+ return {
888
+ runtimePath: process$1.execPath,
889
+ scriptPath
890
+ };
891
+ };
892
+ async function loadModelIds(accountType) {
893
+ state.accountType = accountType;
894
+ await cacheVSCodeVersion();
895
+ const { token } = await getCopilotToken();
896
+ state.copilotToken = token;
897
+ await cacheModels();
898
+ return state.models?.data.map((model) => model.id) ?? [];
899
+ }
900
+ async function pickModel(message, ids, fallback) {
901
+ if (ids.length === 0) return fallback;
902
+ return await consola.prompt(message, {
903
+ type: "select",
904
+ options: ids,
905
+ initial: ids.includes(fallback) ? fallback : ids[0]
906
+ });
907
+ }
908
+ async function promptChoices(initial) {
909
+ const accountType = await consola.prompt("GitHub Copilot account type", {
910
+ type: "select",
911
+ options: ACCOUNT_TYPES,
912
+ initial: ACCOUNT_TYPES.includes(initial.accountType) ? initial.accountType : "individual"
913
+ });
914
+ const portInput = await consola.prompt("Port for the proxy", {
915
+ type: "text",
916
+ default: String(initial.port),
917
+ placeholder: String(initial.port)
918
+ });
919
+ const parsedPort = Number.parseInt(portInput, 10);
920
+ const doCodex = await consola.prompt("Configure Codex CLI?", {
921
+ type: "confirm",
922
+ initial: initial.doCodex
923
+ });
924
+ const doClaude = await consola.prompt("Configure Claude Code?", {
925
+ type: "confirm",
926
+ initial: initial.doClaude
927
+ });
928
+ return {
929
+ accountType,
930
+ port: Number.isNaN(parsedPort) ? initial.port : parsedPort,
931
+ doCodex,
932
+ doClaude
933
+ };
934
+ }
935
+ async function promptModels(choices, models) {
936
+ if (!choices.doCodex && !choices.doClaude) return models;
937
+ let { codexModel, claudeModel, claudeSmallModel } = models;
938
+ try {
939
+ const ids = await loadModelIds(choices.accountType);
940
+ if (choices.doCodex) codexModel = await pickModel("Model for Codex CLI", ids, codexModel);
941
+ if (choices.doClaude) {
942
+ claudeModel = await pickModel("Model for Claude Code", ids, claudeModel);
943
+ claudeSmallModel = await pickModel("Small/fast model for Claude Code", ids, claudeSmallModel);
944
+ }
945
+ } catch (error) {
946
+ consola.warn(`Could not load the model list, using defaults instead: ${String(error)}`);
947
+ }
948
+ return {
949
+ ...models,
950
+ codexModel,
951
+ claudeModel,
952
+ claudeSmallModel
953
+ };
954
+ }
955
+ async function writeConfigs(choices, configOptions) {
956
+ if (choices.doCodex) {
957
+ const written = await writeCodexConfig(configOptions);
958
+ consola.success(`Configured Codex CLI (model "${configOptions.codexModel}") at ${written}`);
959
+ }
960
+ if (choices.doClaude) {
961
+ const written = await writeClaudeSettings(configOptions);
962
+ consola.success(`Configured Claude Code at ${written}`);
963
+ }
964
+ }
965
+ async function maybeInstallService(options, choices) {
966
+ let doService = options.service;
967
+ if (!options.yes && options.service) doService = await consola.prompt(`Install an auto-start service on port ${choices.port}?`, {
968
+ type: "confirm",
969
+ initial: true
970
+ });
971
+ if (!doService) return;
972
+ const { runtimePath, scriptPath } = resolveRuntime();
973
+ const target = {
974
+ port: choices.port,
975
+ accountType: choices.accountType,
976
+ runtimePath,
977
+ scriptPath
978
+ };
979
+ const result = await installService(target);
980
+ if (result.installed) consola.success(result.message);
981
+ else consola.warn(result.message);
982
+ }
983
+ function printSummary(options, choices) {
984
+ const baseUrl = proxyBaseUrl(options.host, choices.port);
985
+ consola.box([
986
+ `Setup complete. Proxy URL: ${baseUrl}`,
987
+ ``,
988
+ options.service ? `The proxy is registered to run automatically on port ${choices.port}.` : `Start manually: copilot-api start --port ${choices.port} --account-type ${choices.accountType}`,
989
+ ``,
990
+ `Config files were written for Codex and/or Claude Code (created even if`,
991
+ `those tools are not installed yet), so they will work once installed.`
992
+ ].join("\n"));
993
+ }
994
+ async function runSetup(options) {
995
+ state.showToken = options.showToken;
996
+ consola.box("copilot-api setup");
997
+ let choices = {
998
+ port: options.port,
999
+ accountType: options.accountType,
1000
+ doClaude: options.claude || !options.codex,
1001
+ doCodex: options.codex || !options.claude
1002
+ };
1003
+ if (!options.yes) choices = await promptChoices(choices);
1004
+ await ensurePaths();
1005
+ consola.info("Checking GitHub authentication...");
1006
+ await setupGitHubToken();
1007
+ let configOptions = {
1008
+ baseUrl: proxyBaseUrl(options.host, choices.port),
1009
+ codexModel: options.codexModel,
1010
+ claudeModel: options.claudeModel,
1011
+ claudeSmallModel: options.claudeSmallModel
1012
+ };
1013
+ if (!options.yes) configOptions = await promptModels(choices, configOptions);
1014
+ await writeConfigs(choices, configOptions);
1015
+ await maybeInstallService(options, choices);
1016
+ printSummary(options, choices);
1017
+ process$1.exit(0);
1018
+ }
1019
+ const setup = defineCommand({
1020
+ meta: {
1021
+ name: "setup",
1022
+ description: "Guided setup: log in, configure Codex/Claude Code, and install an auto-start service"
1023
+ },
1024
+ args: {
1025
+ port: {
1026
+ alias: "p",
1027
+ type: "string",
1028
+ default: "4141",
1029
+ description: "Port the proxy listens on"
1030
+ },
1031
+ host: {
1032
+ type: "string",
1033
+ default: "localhost",
1034
+ description: "Host the config files point at"
1035
+ },
1036
+ "account-type": {
1037
+ alias: "a",
1038
+ type: "string",
1039
+ default: "individual",
1040
+ description: "Account type (individual, business, enterprise)"
1041
+ },
1042
+ claude: {
1043
+ type: "boolean",
1044
+ default: false,
1045
+ description: "Only configure Claude Code (default: configure both)"
1046
+ },
1047
+ codex: {
1048
+ type: "boolean",
1049
+ default: false,
1050
+ description: "Only configure Codex CLI (default: configure both)"
1051
+ },
1052
+ service: {
1053
+ type: "boolean",
1054
+ default: true,
1055
+ description: "Install an auto-start service (use --no-service to skip)"
1056
+ },
1057
+ yes: {
1058
+ alias: "y",
1059
+ type: "boolean",
1060
+ default: false,
1061
+ description: "Non-interactive: accept all defaults"
1062
+ },
1063
+ "codex-model": {
1064
+ type: "string",
1065
+ default: DEFAULT_CODEX_MODEL,
1066
+ description: "Model to use for Codex CLI"
1067
+ },
1068
+ "claude-model": {
1069
+ type: "string",
1070
+ default: DEFAULT_CLAUDE_MODEL,
1071
+ description: "Main model to use for Claude Code"
1072
+ },
1073
+ "claude-small-model": {
1074
+ type: "string",
1075
+ default: DEFAULT_CLAUDE_SMALL_MODEL,
1076
+ description: "Small/fast model to use for Claude Code"
1077
+ },
1078
+ "show-token": {
1079
+ type: "boolean",
1080
+ default: false,
1081
+ description: "Show GitHub and Copilot tokens during setup"
1082
+ }
1083
+ },
1084
+ run({ args }) {
1085
+ return runSetup({
1086
+ port: Number.parseInt(args.port, 10),
1087
+ host: args.host,
1088
+ accountType: args["account-type"],
1089
+ codexModel: args["codex-model"],
1090
+ claudeModel: args["claude-model"],
1091
+ claudeSmallModel: args["claude-small-model"],
1092
+ claude: args.claude,
1093
+ codex: args.codex,
1094
+ service: args.service,
1095
+ yes: args.yes,
1096
+ showToken: args["show-token"]
1097
+ });
1098
+ }
1099
+ });
1100
+
440
1101
  //#endregion
441
1102
  //#region src/lib/proxy.ts
442
1103
  function initProxyFromEnv() {
@@ -1583,6 +2244,62 @@ modelRoutes.get("/", async (c) => {
1583
2244
  }
1584
2245
  });
1585
2246
 
2247
+ //#endregion
2248
+ //#region src/services/copilot/create-responses.ts
2249
+ const createResponses = async (payload, extraHeaders = {}) => {
2250
+ if (!state.copilotToken) throw new Error("Copilot token not found");
2251
+ const headers = {
2252
+ ...copilotHeaders(state),
2253
+ "openai-intent": "conversation-edits",
2254
+ ...extraHeaders
2255
+ };
2256
+ const response = await fetch(`${copilotBaseUrl(state)}/responses`, {
2257
+ method: "POST",
2258
+ headers,
2259
+ body: JSON.stringify(payload)
2260
+ });
2261
+ if (!response.ok) {
2262
+ consola.error("Failed to create responses", response);
2263
+ throw new HTTPError("Failed to create responses", response);
2264
+ }
2265
+ return response;
2266
+ };
2267
+
2268
+ //#endregion
2269
+ //#region src/routes/responses/handler.ts
2270
+ async function handleResponses(c) {
2271
+ await checkRateLimit(state);
2272
+ const payload = await c.req.json();
2273
+ consola.debug("Responses request payload:", JSON.stringify(payload).slice(-400));
2274
+ if (state.manualApprove) await awaitApproval();
2275
+ const initiator = c.req.header("x-initiator");
2276
+ const response = await createResponses(payload, initiator ? { "x-initiator": initiator } : {});
2277
+ if ((response.headers.get("content-type") ?? "").includes("text/event-stream")) {
2278
+ consola.debug("Streaming responses response");
2279
+ return streamSSE(c, async (stream) => {
2280
+ for await (const event of events(response)) await stream.writeSSE({
2281
+ data: event.data ?? "",
2282
+ event: event.event,
2283
+ id: event.id === void 0 ? void 0 : String(event.id)
2284
+ });
2285
+ });
2286
+ }
2287
+ const json = await response.json();
2288
+ consola.debug("Non-streaming responses response");
2289
+ return c.json(json);
2290
+ }
2291
+
2292
+ //#endregion
2293
+ //#region src/routes/responses/route.ts
2294
+ const responsesRoutes = new Hono();
2295
+ responsesRoutes.post("/", async (c) => {
2296
+ try {
2297
+ return await handleResponses(c);
2298
+ } catch (error) {
2299
+ return await forwardError(c, error);
2300
+ }
2301
+ });
2302
+
1586
2303
  //#endregion
1587
2304
  //#region src/routes/token/route.ts
1588
2305
  const tokenRoute = new Hono();
@@ -1618,11 +2335,13 @@ server.use(logger());
1618
2335
  server.use(cors());
1619
2336
  server.get("/", (c) => c.text("Server running"));
1620
2337
  server.route("/chat/completions", completionRoutes);
2338
+ server.route("/responses", responsesRoutes);
1621
2339
  server.route("/models", modelRoutes);
1622
2340
  server.route("/embeddings", embeddingRoutes);
1623
2341
  server.route("/usage", usageRoute);
1624
2342
  server.route("/token", tokenRoute);
1625
2343
  server.route("/v1/chat/completions", completionRoutes);
2344
+ server.route("/v1/responses", responsesRoutes);
1626
2345
  server.route("/v1/models", modelRoutes);
1627
2346
  server.route("/v1/embeddings", embeddingRoutes);
1628
2347
  server.route("/v1/messages", messageRoutes);
@@ -1796,6 +2515,8 @@ const main = defineCommand({
1796
2515
  subCommands: {
1797
2516
  auth,
1798
2517
  start,
2518
+ setup,
2519
+ config,
1799
2520
  "check-usage": checkUsage,
1800
2521
  debug
1801
2522
  }