@daomar/copilot-api 0.7.2 → 0.8.1

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