xiaodcs-copilot-api 2.3.9-public.3 → 2.5.1-public.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.
@@ -1,12 +1,13 @@
1
- import { F as ensurePaths, M as mergeConfigWithDefaults, i as listEnabledProviders } from "./config-SytZjLq8.js";
2
- import { K as state, S as initOpencodeVersion, _ as copilotModelsHeaders, h as copilotBaseUrl, i as setupCopilotToken, j as getMissingApiKeysMessage, m as readGitHubToken, q as HTTPError, t as logUser } from "./token-D9svRIYW.js";
3
- import { i as runProviderSetup } from "./auth-BiVetBYt.js";
4
- import { n as getLatestModelForFamily } from "./models-YMUf33c-.js";
1
+ import { I as mergeConfigWithDefaults, i as listEnabledProviders, t as getProviderConfig, u as isQoderSdkProvider, z as ensurePaths } from "./config-DnMKoKrg.js";
2
+ import { A as getConfiguredApiKeys, K as state, S as initOpencodeVersion, _ as copilotModelsHeaders, h as copilotBaseUrl, i as setupCopilotToken, j as getMissingApiKeysMessage, m as readGitHubToken, q as HTTPError, t as logUser } from "./token-CQftvMU4.js";
3
+ import { i as runProviderSetup } from "./auth-D-M3qCeV.js";
4
+ import { o as forwardProviderModels, u as getLatestModelForFamily } from "./provider-proxy-BbLwvtD_.js";
5
+ import { networkInterfaces } from "node:os";
6
+ import path from "node:path";
7
+ import { createHash, randomUUID } from "node:crypto";
8
+ import { isIP } from "node:net";
5
9
  import { defineCommand } from "citty";
6
10
  import consola from "consola";
7
- import { createHash, randomUUID } from "node:crypto";
8
- import path from "node:path";
9
- import { networkInterfaces } from "node:os";
10
11
  import { getProxyForUrl } from "proxy-from-env";
11
12
  import { Agent, ProxyAgent, setGlobalDispatcher } from "undici";
12
13
  import { execSync } from "node:child_process";
@@ -64,6 +65,65 @@ function initProxyFromEnv() {
64
65
  }
65
66
  }
66
67
  //#endregion
68
+ //#region src/lib/server-host-shared.ts
69
+ const INVALID_HOST_CHARACTERS = /[\s/?#]/;
70
+ const NORMALIZED_WILDCARD_HOSTS = new Set(["0.0.0.0", "::"]);
71
+ function stripIpv6Brackets(hostname) {
72
+ if (hostname.startsWith("[") && hostname.endsWith("]")) return hostname.slice(1, -1);
73
+ return hostname;
74
+ }
75
+ function normalizeHostnameBase(hostname) {
76
+ const normalized = stripIpv6Brackets(hostname.trim());
77
+ if (!normalized || INVALID_HOST_CHARACTERS.test(normalized)) throw new Error(`Invalid server host: ${JSON.stringify(hostname)}`);
78
+ return normalized;
79
+ }
80
+ function isWildcardHostname(hostname) {
81
+ return NORMALIZED_WILDCARD_HOSTS.has(stripIpv6Brackets(hostname.trim()));
82
+ }
83
+ //#endregion
84
+ //#region src/lib/server-host.ts
85
+ const DEFAULT_SERVER_HOST = "127.0.0.1";
86
+ function isLoopbackIpv4(hostname) {
87
+ return isIP(hostname) === 4 && hostname.split(".", 1)[0] === "127";
88
+ }
89
+ function isLoopbackMappedIpv4(hostname) {
90
+ try {
91
+ const mappedIpv4 = new URL(`http://[${hostname}]/`).hostname.slice(1, -1).match(/^::ffff:([\da-f]{1,4}):[\da-f]{1,4}$/);
92
+ if (!mappedIpv4) return false;
93
+ return (Number.parseInt(mappedIpv4[1], 16) & 65280) === 32512;
94
+ } catch {
95
+ return false;
96
+ }
97
+ }
98
+ function normalizeServerHostname(hostname) {
99
+ return normalizeHostnameBase(hostname);
100
+ }
101
+ function isLoopbackHostname(hostname) {
102
+ const normalized = normalizeServerHostname(hostname).toLowerCase();
103
+ if (normalized === "localhost" || normalized === "localhost.") return true;
104
+ if (isLoopbackIpv4(normalized)) return true;
105
+ if (isIP(normalized) !== 6) return false;
106
+ if (normalized === "::1" || normalized === "0:0:0:0:0:0:0:1") return true;
107
+ return isLoopbackMappedIpv4(normalized);
108
+ }
109
+ function resolveServerBinding(hostname, hasApiKeys) {
110
+ const normalizedHostname = normalizeServerHostname(hostname);
111
+ const networkExposed = !isLoopbackHostname(normalizedHostname);
112
+ if (networkExposed && !hasApiKeys) throw new Error(`Refusing to listen on non-loopback host ${JSON.stringify(normalizedHostname)} without gateway API keys. Run \`npx copilot-api auth keys --add <key>\` first, or use \`--host ${DEFAULT_SERVER_HOST}\`.`);
113
+ return {
114
+ hostname: normalizedHostname,
115
+ clientHostname: resolveClientHostname(normalizedHostname),
116
+ networkExposed
117
+ };
118
+ }
119
+ function resolveClientHostname(hostname) {
120
+ const normalizedHostname = normalizeServerHostname(hostname);
121
+ return isWildcardHostname(normalizedHostname) ? DEFAULT_SERVER_HOST : normalizedHostname.toLowerCase();
122
+ }
123
+ function formatServerUrl(hostname, port) {
124
+ return `http://${isIP(hostname) === 6 ? `[${hostname.replace("%", "%25")}]` : hostname}:${port}`;
125
+ }
126
+ //#endregion
67
127
  //#region src/lib/shell.ts
68
128
  function getShell() {
69
129
  const { platform, ppid, env } = process$1;
@@ -115,6 +175,33 @@ function generateEnvScript(envVars, commandToRun = "") {
115
175
  return commandBlock || commandToRun;
116
176
  }
117
177
  //#endregion
178
+ //#region src/services/qoder/startup-models.ts
179
+ async function loadQoderStartupModels(provider, fetchModels = forwardProviderModels, bunVersion = process.versions.bun) {
180
+ if (!provider || !isQoderSdkProvider(provider)) return { models: [] };
181
+ if (bunVersion) return {
182
+ models: [],
183
+ warning: "Qoder SDK Worker is not supported by this Bun startup path. Use `bun run start` (builds and runs with Node), or `node dist/main.js start`."
184
+ };
185
+ try {
186
+ const response = await fetchModels(provider, new Headers());
187
+ if (!response.ok) return {
188
+ models: [],
189
+ warning: `Qoder model discovery failed (HTTP ${response.status}). Check PAT and SDK runtime.`
190
+ };
191
+ const body = await response.json();
192
+ const models = Array.isArray(body.data) ? body.data.flatMap((m) => typeof m?.id === "string" && m.id.trim() ? [`qoder/${m.id}`] : []) : [];
193
+ return models.length ? { models } : {
194
+ models: [],
195
+ warning: "Qoder returned no available models."
196
+ };
197
+ } catch {
198
+ return {
199
+ models: [],
200
+ warning: "Qoder model discovery failed. Check PAT, SDK runtime and network settings."
201
+ };
202
+ }
203
+ }
204
+ //#endregion
118
205
  //#region src/services/copilot/get-models.ts
119
206
  const getModels = async () => {
120
207
  consola.info(`Fetching models from ${copilotBaseUrl(state)}/models`);
@@ -447,9 +534,11 @@ async function setupProviderMode(serverUrl, claudeCode) {
447
534
  consola.info(`Configured providers: ${providersAfterSetup.join(", ")}`);
448
535
  }
449
536
  async function runServer(options) {
450
- (await import("./tls-Aq1Dd8E2.js")).enableSystemCACompat();
537
+ (await import("./tls-B5VJAJJM.js")).enableSystemCACompat();
451
538
  consola.options.throttle = 0;
452
539
  mergeConfigWithDefaults();
540
+ const configuredApiKeys = getConfiguredApiKeys();
541
+ const binding = resolveServerBinding(options.host, configuredApiKeys.length > 0);
453
542
  const missingApiKeysMessage = getMissingApiKeysMessage();
454
543
  if (missingApiKeysMessage) consola.info(missingApiKeysMessage);
455
544
  await initOpencodeVersion();
@@ -461,14 +550,18 @@ async function runServer(options) {
461
550
  }
462
551
  state.showToken = options.showToken;
463
552
  await ensurePaths();
464
- const serverUrl = `http://localhost:${options.port}`;
553
+ const serverUrl = formatServerUrl(binding.clientHostname, options.port);
465
554
  const githubToken = options.githubToken || await readGitHubToken();
466
555
  if (githubToken) await setupCopilotMode(githubToken, Boolean(options.githubToken), serverUrl, options.claudeCode);
467
556
  else await setupProviderMode(serverUrl, options.claudeCode);
557
+ const qoder = await loadQoderStartupModels(getProviderConfig("qoder"));
558
+ if (qoder.warning) consola.warn(qoder.warning);
559
+ if (qoder.models.length) consola.info(`Qoder models (${qoder.models.length}):\n${qoder.models.map((model) => `- ${model}`).join("\n")}`);
468
560
  consola.box(`🌐 Usage Viewer: ${serverUrl}/usage-viewer?endpoint=${serverUrl}/usage`);
469
- const { server } = await import("./server-CFQmvoAJ.js");
561
+ const { createServer } = await import("./server-DmKNMBLd.js");
470
562
  serve({
471
- fetch: server.fetch,
563
+ fetch: createServer({ networkExposed: binding.networkExposed }).fetch,
564
+ hostname: binding.hostname,
472
565
  port: options.port,
473
566
  bun: { idleTimeout: 0 }
474
567
  });
@@ -479,6 +572,11 @@ const start = defineCommand({
479
572
  description: "Start the Copilot API server"
480
573
  },
481
574
  args: {
575
+ host: {
576
+ type: "string",
577
+ default: process.env.HOST?.trim() || "127.0.0.1",
578
+ description: "Host to listen on"
579
+ },
482
580
  port: {
483
581
  alias: "p",
484
582
  type: "string",
@@ -515,6 +613,7 @@ const start = defineCommand({
515
613
  },
516
614
  run({ args }) {
517
615
  return runServer({
616
+ host: args.host,
518
617
  port: Number.parseInt(args.port, 10),
519
618
  verbose: args.verbose,
520
619
  githubToken: args["github-token"],
@@ -1,8 +1,8 @@
1
- import { E as getResponsesTransportConfig, P as PATHS, c as setProviderConfig, j as isResponsesApiWebSocketEnabled, n as getRawProviderConfig, w as getConfig } from "./config-SytZjLq8.js";
2
- import consola from "consola";
3
- import { createHash, randomBytes, randomUUID } from "node:crypto";
4
- import path from "node:path";
1
+ import { A as isCodexResponsesApiWebSocketEnabled, B as writeFileAtomically, E as getConfig, O as getResponsesTransportConfig, R as PATHS, c as setProviderConfig, n as getRawProviderConfig } from "./config-DnMKoKrg.js";
5
2
  import fs, { readFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { createHash, randomBytes, randomUUID } from "node:crypto";
5
+ import consola from "consola";
6
6
  import { createServer } from "node:http";
7
7
  import { getProxyForUrl } from "proxy-from-env";
8
8
  import { WebSocket } from "undici";
@@ -142,6 +142,7 @@ const runPooledWebSocketRequest = async function* (request, options) {
142
142
  throwIfAborted(request.signal);
143
143
  messageStream = createWebSocketMessageStream(websocket, request.signal, options);
144
144
  messageStream.start();
145
+ options.onBeforeSend?.();
145
146
  websocket.send(JSON.stringify(request.payload));
146
147
  for await (const data of messageStream.iterable) {
147
148
  const chunk = options.createChunk(data);
@@ -785,7 +786,7 @@ function buildCodexRequestHeaders(requestHeaders) {
785
786
  return headers;
786
787
  }
787
788
  function resolveCodexResponsesTransport(transport) {
788
- return transport ?? (isResponsesApiWebSocketEnabled() ? "websocket" : "http");
789
+ return transport ?? (isCodexResponsesApiWebSocketEnabled() ? "websocket" : "http");
789
790
  }
790
791
  function buildCodexResponsesWebSocketHeaders(requestHeaders) {
791
792
  const headers = buildCodexResponsesHeaders(requestHeaders);
@@ -818,6 +819,11 @@ async function forwardCodexResponses(payload, requestHeaders, baseUrl = CODEX_AP
818
819
  consola.log(`<-- model: ${payload.model}`);
819
820
  const transport = resolveCodexResponsesTransport(options.transport);
820
821
  if (payload.stream && transport === "websocket") return forwardCodexResponsesOverWebSocket(payload, requestHeaders, baseUrl, options.signal);
822
+ const response = await fetchCodexResponsesOverHttp(payload, requestHeaders, baseUrl, options.signal);
823
+ if (payload.stream) return createResponsesSafeStream(createResponsesHttpEventStream(response, options.signal), { signal: options.signal });
824
+ return await response.json();
825
+ }
826
+ const fetchCodexResponsesOverHttp = async (payload, requestHeaders, baseUrl, signal) => {
821
827
  const normalizedPayload = normalizeCodexResponsesPayload(payload);
822
828
  const transportConfig = getResponsesTransportConfig();
823
829
  const response = await fetchResponsesWithLifecycle(resolveCodexResponsesUrl(baseUrl), {
@@ -826,13 +832,12 @@ async function forwardCodexResponses(payload, requestHeaders, baseUrl = CODEX_AP
826
832
  body: JSON.stringify(normalizedPayload)
827
833
  }, {
828
834
  headersTimeoutMs: transportConfig.headersTimeoutMs,
829
- signal: options.signal,
835
+ signal,
830
836
  streamInactivityTimeoutMs: transportConfig.streamInactivityTimeoutMs
831
837
  });
832
838
  if (!response.ok) throw new HTTPError("Failed to create codex responses", response);
833
- if (normalizedPayload.stream) return createResponsesSafeStream(createResponsesHttpEventStream(response, options.signal), { signal: options.signal });
834
- return await response.json();
835
- }
839
+ return response;
840
+ };
836
841
  const normalizeCodexResponsesPayload = (payload) => {
837
842
  const normalizedPayload = {
838
843
  ...payload,
@@ -897,22 +902,36 @@ const buildCodexResponsesWebSocketPoolKey = (payload, headers, baseUrl) => {
897
902
  ].map(encodePoolKeyPart).join("|");
898
903
  };
899
904
  const forwardCodexResponsesOverWebSocket = (payload, requestHeaders, baseUrl, signal) => {
900
- return createCodexResponsesWebSocketStream(prepareCodexResponsesWebSocketRequest(payload, requestHeaders, baseUrl, signal));
905
+ return createResponsesSafeStream(createCodexResponsesStreamWithFallback(prepareCodexResponsesWebSocketRequest(payload, requestHeaders, baseUrl, signal), payload, requestHeaders, baseUrl), { signal });
901
906
  };
902
- const createCodexResponsesWebSocketStream = (request) => {
907
+ const createCodexResponsesStreamWithFallback = async function* (request, payload, requestHeaders, baseUrl) {
908
+ let sendAttempted = false;
909
+ try {
910
+ yield* createCodexResponsesWebSocketStream(request, () => {
911
+ sendAttempted = true;
912
+ });
913
+ return;
914
+ } catch (error) {
915
+ if (sendAttempted || request.signal?.aborted || error instanceof Error && error.name === "AbortError" || payload.previous_response_id != null || payload.conversation != null) throw error;
916
+ consola.warn("Codex websocket failed before sending the request; falling back to HTTP once");
917
+ }
918
+ yield* createResponsesHttpEventStream(await fetchCodexResponsesOverHttp(payload, requestHeaders, baseUrl, request.signal), request.signal);
919
+ };
920
+ const createCodexResponsesWebSocketStream = (request, onBeforeSend) => {
903
921
  const transportConfig = getResponsesTransportConfig();
904
- return createResponsesSafeStream(createPooledWebSocketStream(request, {
922
+ return createPooledWebSocketStream(request, {
905
923
  createChunk: createCodexResponsesWebSocketStreamChunk,
906
924
  maxBufferedBytes: transportConfig.websocketMaxBufferedBytes,
907
925
  maxBufferedMessages: transportConfig.websocketMaxBufferedMessages,
908
926
  isTerminalChunk: isTerminalResponsesStreamChunk,
909
927
  openErrorMessage: "Failed to create codex responses websocket",
910
928
  openTimeoutMs: transportConfig.websocketOpenTimeoutMs,
929
+ onBeforeSend,
911
930
  poolIdleTimeoutMs: transportConfig.websocketPoolIdleTimeoutMs,
912
931
  streamInactivityTimeoutMs: transportConfig.streamInactivityTimeoutMs,
913
932
  streamErrorMessage: "Upstream connection lost, Codex responses websocket stream error",
914
933
  terminalChunkMissingMessage: "Codex responses websocket ended without a terminal response, retry your request."
915
- }), { signal: request.signal });
934
+ });
916
935
  };
917
936
  const createCodexResponsesWebSocketStreamChunk = (data) => {
918
937
  if (data === "[DONE]") return { data };
@@ -1080,7 +1099,7 @@ async function exchangeAuthorizationCode(code, verifier) {
1080
1099
  throw new Error(`Codex token exchange failed (${response.status}): ${details || response.statusText}`);
1081
1100
  }
1082
1101
  const payload = await response.json();
1083
- if (typeof payload.access_token !== "string" || typeof payload.refresh_token !== "string" || typeof payload.expires_in !== "number") throw new TypeError(`Codex token exchange response missing fields: ${JSON.stringify(payload)}`);
1102
+ if (typeof payload.access_token !== "string" || typeof payload.refresh_token !== "string" || typeof payload.expires_in !== "number") throw new TypeError("Codex token exchange response missing required fields");
1084
1103
  return {
1085
1104
  accessToken: payload.access_token,
1086
1105
  refreshToken: payload.refresh_token,
@@ -1102,7 +1121,7 @@ async function refreshAccessToken(refreshToken) {
1102
1121
  throw new Error(`Codex token refresh failed (${response.status}): ${details || response.statusText}`);
1103
1122
  }
1104
1123
  const payload = await response.json();
1105
- if (typeof payload.access_token !== "string" || typeof payload.refresh_token !== "string" || typeof payload.expires_in !== "number") throw new TypeError(`Codex token refresh response missing fields: ${JSON.stringify(payload)}`);
1124
+ if (typeof payload.access_token !== "string" || typeof payload.refresh_token !== "string" || typeof payload.expires_in !== "number") throw new TypeError("Codex token refresh response missing required fields");
1106
1125
  return {
1107
1126
  accessToken: payload.access_token,
1108
1127
  refreshToken: payload.refresh_token,
@@ -1565,14 +1584,8 @@ async function readOptionalFile(filePath) {
1565
1584
  throw error;
1566
1585
  }
1567
1586
  }
1568
- async function writeProtectedFile(filePath, content) {
1569
- await fs.mkdir(path.dirname(filePath), { recursive: true });
1570
- await fs.writeFile(filePath, content, "utf8");
1571
- try {
1572
- await fs.chmod(filePath, 384);
1573
- } catch {
1574
- return;
1575
- }
1587
+ function writeProtectedFile(filePath, content) {
1588
+ return Promise.resolve().then(() => writeFileAtomically(filePath, content));
1576
1589
  }
1577
1590
  function normalizeCodexCredentials(credentials) {
1578
1591
  if (!credentials || typeof credentials !== "object") return null;
@@ -1744,7 +1757,7 @@ async function pollAccessToken(deviceCode) {
1744
1757
  continue;
1745
1758
  }
1746
1759
  const json = await response.json();
1747
- consola.debug("Polling access token response:", json);
1760
+ consola.debug("Polling access token response received");
1748
1761
  const { access_token } = json;
1749
1762
  if (access_token) return access_token;
1750
1763
  else await sleep(sleepDuration);
@@ -1754,6 +1767,10 @@ async function pollAccessToken(deviceCode) {
1754
1767
  //#region src/lib/token.ts
1755
1768
  let copilotRefreshLoopController = null;
1756
1769
  let codexRefreshLoopController = null;
1770
+ const defaultCopilotTokenDependencies = {
1771
+ getCopilotToken,
1772
+ getCopilotUsage
1773
+ };
1757
1774
  const stopCopilotRefreshLoop = () => {
1758
1775
  if (!copilotRefreshLoopController) return;
1759
1776
  copilotRefreshLoopController.abort();
@@ -1801,7 +1818,7 @@ const applyCopilotTokenResponse = (response) => {
1801
1818
  state.copilotToken = response.token;
1802
1819
  if (response.endpoints?.api) state.copilotApiUrl = response.endpoints.api;
1803
1820
  };
1804
- const setupCopilotToken = async () => {
1821
+ const setupCopilotToken = async (dependencies = defaultCopilotTokenDependencies) => {
1805
1822
  if (isOpencodeOauthApp()) {
1806
1823
  if (!state.githubToken) throw new Error(`opencode token not found`);
1807
1824
  state.copilotToken = state.githubToken;
@@ -1810,14 +1827,14 @@ const setupCopilotToken = async () => {
1810
1827
  stopCopilotRefreshLoop();
1811
1828
  return;
1812
1829
  }
1813
- const response = await getCopilotToken();
1830
+ const response = await dependencies.getCopilotToken();
1814
1831
  applyCopilotTokenResponse(response);
1815
1832
  consola.debug("GitHub Copilot Token fetched successfully!");
1816
1833
  if (state.showToken) consola.info("Copilot token:", state.copilotToken);
1817
1834
  stopCopilotRefreshLoop();
1818
1835
  const controller = new AbortController();
1819
1836
  copilotRefreshLoopController = controller;
1820
- runCopilotRefreshLoop(response.refresh_in, controller.signal).catch(() => {
1837
+ runCopilotRefreshLoop(response.refresh_in, controller.signal, dependencies).catch(() => {
1821
1838
  consola.warn("Copilot token refresh loop stopped");
1822
1839
  }).finally(() => {
1823
1840
  if (copilotRefreshLoopController === controller) copilotRefreshLoopController = null;
@@ -1856,7 +1873,7 @@ const RETRY_REFRESH_JITTER_MS = 15e3;
1856
1873
  const MIN_REFRESH_DELAY_MS = 1e3;
1857
1874
  const getRefreshDeadlineMs = (refreshIn, nowMs = Date.now()) => nowMs + Math.max(refreshIn * 1e3 - EARLY_REFRESH_BUFFER_MS, MIN_REFRESH_DELAY_MS);
1858
1875
  const getRefreshPollDelayMs = (refreshAtMs, nowMs = Date.now()) => Math.min(Math.max(refreshAtMs - nowMs, 0), REFRESH_POLL_INTERVAL_MS);
1859
- const runCopilotRefreshLoop = async (refreshIn, signal) => {
1876
+ const runCopilotRefreshLoop = async (refreshIn, signal, dependencies) => {
1860
1877
  let refreshAtMs = getRefreshDeadlineMs(refreshIn);
1861
1878
  let retryDelayMs = RETRY_REFRESH_DELAY_MS;
1862
1879
  while (!signal.aborted) {
@@ -1867,7 +1884,7 @@ const runCopilotRefreshLoop = async (refreshIn, signal) => {
1867
1884
  }
1868
1885
  consola.debug("Refreshing Copilot token");
1869
1886
  try {
1870
- const response = await getCopilotToken();
1887
+ const response = await dependencies.getCopilotToken();
1871
1888
  applyCopilotTokenResponse(response);
1872
1889
  refreshAtMs = getRefreshDeadlineMs(response.refresh_in);
1873
1890
  retryDelayMs = RETRY_REFRESH_DELAY_MS;
@@ -1938,8 +1955,8 @@ async function setupGitHubToken(options) {
1938
1955
  throw error;
1939
1956
  }
1940
1957
  }
1941
- async function logUser() {
1942
- const copilotUser = await getCopilotUsage();
1958
+ async function logUser(dependencies = defaultCopilotTokenDependencies) {
1959
+ const copilotUser = await dependencies.getCopilotUsage();
1943
1960
  if (!copilotUser) throw new Error("GitHub token not found");
1944
1961
  state.userName = copilotUser.login;
1945
1962
  consola.info(`Logged in as ${copilotUser.login}`);
@@ -0,0 +1,252 @@
1
+ import { d as BridgeError, n as parseChat, r as publicError, t as completionIdentity } from "./protocol-BTFLbI8m.js";
2
+ import { randomBytes, timingSafeEqual } from "node:crypto";
3
+ import { Hono } from "hono";
4
+ import { streamSSE } from "hono/streaming";
5
+ import { bodyLimit } from "hono/body-limit";
6
+ //#region qoder-bridge/app.ts
7
+ function authorized(header, key) {
8
+ const actual = Buffer.from(header ?? "");
9
+ const expected = Buffer.from(`Bearer ${key}`);
10
+ return actual.length === expected.length && timingSafeEqual(actual, expected);
11
+ }
12
+ function abortable(promise, signal) {
13
+ return new Promise((resolve, reject) => {
14
+ const abort = () => reject(signal.reason instanceof Error ? signal.reason : new BridgeError("Request cancelled", 502, "cancelled"));
15
+ if (signal.aborted) {
16
+ abort();
17
+ return;
18
+ }
19
+ signal.addEventListener("abort", abort, { once: true });
20
+ promise.then(resolve, reject).finally(() => signal.removeEventListener("abort", abort)).catch(() => {});
21
+ });
22
+ }
23
+ function createBridge(backend, options) {
24
+ if (!options.apiKey.trim()) throw new Error("QODER_BRIDGE_API_KEY is required");
25
+ const app = new Hono();
26
+ let active = 0;
27
+ app.get("/health", (c) => c.json({
28
+ status: "ok",
29
+ service: "qoder-bridge",
30
+ capabilities: [
31
+ "text",
32
+ "image-input",
33
+ "chat-completions",
34
+ "client-tool-calls",
35
+ "streaming",
36
+ "models",
37
+ "credits"
38
+ ]
39
+ }));
40
+ app.use("*", async (c, next) => {
41
+ if (!authorized(c.req.header("authorization"), options.apiKey)) return c.json({ error: {
42
+ message: "Invalid bridge API key",
43
+ type: "authentication_error"
44
+ } }, 401);
45
+ await next();
46
+ });
47
+ app.use("*", bodyLimit({
48
+ maxSize: options.maxBodyBytes ?? 23068672,
49
+ onError: (c) => c.json({ error: {
50
+ message: "Request body too large",
51
+ type: "invalid_request_error"
52
+ } }, 413)
53
+ }));
54
+ app.onError((error, c) => {
55
+ const failure = publicError(error);
56
+ return c.json({ error: {
57
+ message: failure.message,
58
+ type: failure.code
59
+ } }, failure.status);
60
+ });
61
+ function lease(signal) {
62
+ if (active >= (options.maxConcurrent ?? 2)) throw new BridgeError("Qoder bridge is busy", 429, "rate_limit_error");
63
+ active++;
64
+ const controller = new AbortController();
65
+ const abort = () => controller.abort(signal.reason);
66
+ signal.addEventListener("abort", abort, { once: true });
67
+ if (signal.aborted) abort();
68
+ const timer = setTimeout(() => controller.abort(new BridgeError("Qoder request timed out", 504, "timeout")), options.timeoutMs ?? 12e4);
69
+ let released = false;
70
+ return {
71
+ signal: controller.signal,
72
+ abort: () => controller.abort(),
73
+ release: () => {
74
+ if (released) return;
75
+ released = true;
76
+ clearTimeout(timer);
77
+ signal.removeEventListener("abort", abort);
78
+ active--;
79
+ }
80
+ };
81
+ }
82
+ for (const path of ["/v1/models", "/usage"]) app.get(path, async (c) => {
83
+ const session = lease(c.req.raw.signal);
84
+ try {
85
+ const result = await abortable(backend.catalog(session.signal), session.signal);
86
+ if (path === "/usage") return c.json({
87
+ provider: "qoder",
88
+ usage: result.usage
89
+ });
90
+ return c.json({
91
+ object: "list",
92
+ data: result.models.map((m) => ({
93
+ ...m,
94
+ id: m.id,
95
+ object: "model",
96
+ created: 0,
97
+ owned_by: "qoder",
98
+ name: m.name ?? m.id
99
+ }))
100
+ });
101
+ } finally {
102
+ session.release();
103
+ }
104
+ });
105
+ app.post("/v1/chat/completions", async (c) => {
106
+ let json;
107
+ try {
108
+ json = await c.req.json();
109
+ } catch {
110
+ throw new BridgeError("Invalid JSON request");
111
+ }
112
+ const input = parseChat(json);
113
+ const session = lease(c.req.raw.signal);
114
+ const identity = completionIdentity(input.model);
115
+ const iterator = backend.turn(input, session.signal)[Symbol.asyncIterator]();
116
+ async function* consume() {
117
+ try {
118
+ let completed = false;
119
+ while (true) {
120
+ const item = await abortable(iterator.next(), session.signal);
121
+ if (item.done) break;
122
+ if (completed) throw new BridgeError("Qoder sent data after completion", 502, "invalid_stream");
123
+ completed = item.value.type === "complete";
124
+ yield item.value;
125
+ }
126
+ if (!completed) throw new BridgeError("Qoder stream ended before completion", 502, "incomplete_stream");
127
+ } finally {
128
+ session.abort();
129
+ iterator.return?.().catch(() => {});
130
+ session.release();
131
+ }
132
+ }
133
+ if (input.stream) return streamSSE(c, async (stream) => {
134
+ stream.onAbort(session.abort);
135
+ const chunk = (choices, extra = {}) => JSON.stringify({
136
+ ...identity,
137
+ object: "chat.completion.chunk",
138
+ choices,
139
+ ...extra
140
+ });
141
+ try {
142
+ await stream.writeSSE({ data: chunk([{
143
+ index: 0,
144
+ delta: { role: "assistant" },
145
+ finish_reason: null
146
+ }]) });
147
+ let info;
148
+ let hasTools = false;
149
+ for await (const event of consume()) if (event.type === "text") await stream.writeSSE({ data: chunk([{
150
+ index: 0,
151
+ delta: { content: event.text },
152
+ finish_reason: null
153
+ }]) });
154
+ else if (event.type === "tool_calls") {
155
+ hasTools = true;
156
+ await stream.writeSSE({ data: chunk([{
157
+ index: 0,
158
+ delta: { tool_calls: event.calls.map((call, index) => ({
159
+ ...call,
160
+ index
161
+ })) },
162
+ finish_reason: null
163
+ }]) });
164
+ } else info = event.info;
165
+ await stream.writeSSE({ data: chunk([{
166
+ index: 0,
167
+ delta: {},
168
+ finish_reason: hasTools ? "tool_calls" : "stop"
169
+ }], { qoder_usage: info?.qoder_usage }) });
170
+ if (input.includeUsage) await stream.writeSSE({ data: chunk([], {
171
+ usage: info?.usage ?? null,
172
+ qoder_usage: info?.qoder_usage
173
+ }) });
174
+ await stream.writeSSE({ data: "[DONE]" });
175
+ } catch (error) {
176
+ const failure = publicError(error);
177
+ if (!stream.aborted) await stream.writeSSE({ data: JSON.stringify({ error: {
178
+ message: failure.message,
179
+ type: failure.code
180
+ } }) });
181
+ } finally {
182
+ session.abort();
183
+ session.release();
184
+ }
185
+ });
186
+ let text = "";
187
+ let calls = [];
188
+ let info;
189
+ for await (const event of consume()) if (event.type === "text") text += event.text;
190
+ else if (event.type === "tool_calls") calls = event.calls;
191
+ else info = event.info;
192
+ return c.json({
193
+ ...identity,
194
+ object: "chat.completion",
195
+ choices: [{
196
+ index: 0,
197
+ message: {
198
+ role: "assistant",
199
+ content: text || null,
200
+ ...calls.length ? { tool_calls: calls } : {}
201
+ },
202
+ finish_reason: calls.length ? "tool_calls" : "stop"
203
+ }],
204
+ ...info
205
+ });
206
+ });
207
+ return app;
208
+ }
209
+ //#endregion
210
+ //#region src/services/qoder/transport.ts
211
+ async function sdkBackend(token) {
212
+ const { createBackend } = await import("./runtime-CWU5BNBf.js");
213
+ return createBackend(void 0, token);
214
+ }
215
+ function createQoderTransport(loadBackend = sdkBackend) {
216
+ let current;
217
+ const localKey = randomBytes(32).toString("hex");
218
+ return async (provider, path, init = {}) => {
219
+ const token = provider.apiKey.trim();
220
+ if (!token) return Response.json({ error: {
221
+ message: "Configure Qoder with auth login --provider qoder",
222
+ type: "authentication_error"
223
+ } }, { status: 401 });
224
+ if (!current || current.token !== token) {
225
+ const entry = {
226
+ token,
227
+ app: loadBackend(token).then((backend) => createBridge(backend, { apiKey: localKey }))
228
+ };
229
+ current = entry;
230
+ entry.app.catch(() => {
231
+ if (current === entry) current = void 0;
232
+ });
233
+ }
234
+ try {
235
+ const app = await current.app;
236
+ const headers = new Headers(init.headers);
237
+ headers.set("authorization", `Bearer ${localKey}`);
238
+ return await app.fetch(new Request(`http://qoder.internal${path}`, {
239
+ ...init,
240
+ headers
241
+ }));
242
+ } catch {
243
+ return Response.json({ error: {
244
+ message: "Qoder SDK could not start. Reinstall the package with npm install scripts enabled, then retry.",
245
+ type: "qoder_runtime_error"
246
+ } }, { status: 502 });
247
+ }
248
+ };
249
+ }
250
+ const forwardQoder = createQoderTransport();
251
+ //#endregion
252
+ export { forwardQoder as n, createQoderTransport as t };
@@ -0,0 +1,2 @@
1
+ import { n as forwardQoder } from "./transport-DE8Q0wU-.js";
2
+ export { forwardQoder };