@nick3/copilot-api 1.0.8 → 1.1.3

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,4 +1,4 @@
1
- import { HTTPError, PATHS, accountFromState, accountsManager, copilotBaseUrl, copilotHeaders, forwardError, getAliasTargetSet, getConfig, getCopilotUsage, getExtraPromptForModel, getModelAliases, getModelAliasesInfo, getModelRefreshIntervalMs, getReasoningEffortForModel, getSmallModel, isForceAgentEnabled, isFreeModelLoadBalancingEnabled, isMessageStartInputTokensFallbackEnabled, isNullish, listAccountsFromRegistry, mergeConfigWithDefaults, shouldCompactUseSmallModel, sleep, state } from "./accounts-manager-CJtqVqDx.js";
1
+ import { HTTPError, PATHS, accountFromState, accountsManager, copilotBaseUrl, copilotHeaders, forwardError, getAliasTargetSet, getConfig, getCopilotUsage, getExtraPromptForModel, getModelAliases, getModelAliasesInfo, getModelRefreshIntervalMs, getReasoningEffortForModel, getSmallModel, isForceAgentEnabled, isFreeModelLoadBalancingEnabled, isMessageStartInputTokensFallbackEnabled, isNullish, listAccountsFromRegistry, mergeConfigWithDefaults, shouldCompactUseSmallModel, sleep, state } from "./accounts-manager-CfBWqxBl.js";
2
2
  import consola from "consola";
3
3
  import fs, { readFile } from "node:fs/promises";
4
4
  import * as path$1 from "node:path";
@@ -14,27 +14,56 @@ import { streamSSE } from "hono/streaming";
14
14
  import util from "node:util";
15
15
  import { events } from "fetch-event-stream";
16
16
 
17
- //#region src/lib/api-key-auth.ts
18
- const API_KEY_ENV_VAR = "COPILOT_API_KEY";
19
- function resolveConfiguredApiKey() {
20
- const envKey = process.env[API_KEY_ENV_VAR]?.trim();
21
- if (envKey) return envKey;
22
- const configKey = getConfig().apiKey?.trim();
23
- if (configKey) return configKey;
24
- }
25
- function parseBearerToken(value) {
26
- const trimmed = value.trim();
27
- if (!trimmed.toLowerCase().startsWith("bearer ")) return void 0;
28
- return trimmed.slice(7).trim() || void 0;
29
- }
30
- function extractRequestApiKey(headers) {
31
- const headerToken = headers.get("x-api-key")?.trim();
32
- if (headerToken) return headerToken;
33
- const bearer = headers.get("authorization");
34
- if (bearer) {
35
- const token = parseBearerToken(bearer);
36
- if (token) return token;
17
+ //#region src/lib/request-auth.ts
18
+ const LEGACY_API_KEY_ENV_VAR = "COPILOT_API_KEY";
19
+ let warnedLegacyEnvFallback = false;
20
+ let warnedLegacyConfigFallback = false;
21
+ function normalizeApiKeys(apiKeys) {
22
+ if (!Array.isArray(apiKeys)) {
23
+ if (apiKeys !== void 0) consola.warn("Invalid auth.apiKeys config. Expected an array of strings.");
24
+ return [];
25
+ }
26
+ const normalizedKeys = apiKeys.filter((key) => typeof key === "string").map((key) => key.trim()).filter((key) => key.length > 0);
27
+ if (normalizedKeys.length !== apiKeys.length) consola.warn("Invalid auth.apiKeys entries found. Only non-empty strings are allowed.");
28
+ return [...new Set(normalizedKeys)];
29
+ }
30
+ function getConfiguredApiKeys() {
31
+ const config = getConfig();
32
+ const configuredApiKeys = normalizeApiKeys(config.auth?.apiKeys);
33
+ if (configuredApiKeys.length > 0) return configuredApiKeys;
34
+ const envApiKey = process.env[LEGACY_API_KEY_ENV_VAR]?.trim();
35
+ if (envApiKey) {
36
+ if (!warnedLegacyEnvFallback) {
37
+ warnedLegacyEnvFallback = true;
38
+ consola.warn(`Using legacy ${LEGACY_API_KEY_ENV_VAR}. Please migrate to config.auth.apiKeys.`);
39
+ }
40
+ return [envApiKey];
37
41
  }
42
+ const legacyConfigApiKey = config.apiKey?.trim();
43
+ if (legacyConfigApiKey) {
44
+ if (!warnedLegacyConfigFallback) {
45
+ warnedLegacyConfigFallback = true;
46
+ consola.warn("Using deprecated config.apiKey. Please migrate to config.auth.apiKeys.");
47
+ }
48
+ return [legacyConfigApiKey];
49
+ }
50
+ return configuredApiKeys;
51
+ }
52
+ function extractRequestApiKey(c) {
53
+ const xApiKey = c.req.header("x-api-key")?.trim();
54
+ if (xApiKey) return xApiKey;
55
+ const authorization = c.req.header("authorization");
56
+ if (!authorization) return null;
57
+ const [scheme, ...rest] = authorization.trim().split(/\s+/);
58
+ if (scheme.toLowerCase() !== "bearer") return null;
59
+ return rest.join(" ").trim() || null;
60
+ }
61
+ function createUnauthorizedResponse(c) {
62
+ c.header("WWW-Authenticate", "Bearer realm=\"copilot-api\"");
63
+ return c.json({ error: {
64
+ message: "Unauthorized. Provide Authorization: Bearer <key> or x-api-key.",
65
+ type: "unauthorized"
66
+ } }, 401);
38
67
  }
39
68
  function normalizePathname(pathname) {
40
69
  if (pathname.length > 1 && pathname.endsWith("/")) return pathname.slice(0, -1);
@@ -43,10 +72,6 @@ function normalizePathname(pathname) {
43
72
  function hasPrefixBoundary(pathname, prefix) {
44
73
  return pathname === prefix || pathname.startsWith(`${prefix}/`);
45
74
  }
46
- function isProtectedPath(pathnameRaw) {
47
- const pathname = normalizePathname(pathnameRaw);
48
- return hasPrefixBoundary(pathname, "/v1") || hasPrefixBoundary(pathname, "/token") || hasPrefixBoundary(pathname, "/usage") || hasPrefixBoundary(pathname, "/chat/completions") || hasPrefixBoundary(pathname, "/embeddings") || hasPrefixBoundary(pathname, "/models") || hasPrefixBoundary(pathname, "/responses");
49
- }
50
75
  function timingSafeKeyCompare(a, b) {
51
76
  try {
52
77
  const aBuf = Buffer.from(a);
@@ -57,30 +82,21 @@ function timingSafeKeyCompare(a, b) {
57
82
  return false;
58
83
  }
59
84
  }
60
- function createApiKeyAuthMiddleware(options = {}) {
61
- const getConfiguredKey = options.getConfiguredApiKey ?? resolveConfiguredApiKey;
62
- const isPathProtected = options.isProtectedPath ?? isProtectedPath;
85
+ function createAuthMiddleware(options = {}) {
86
+ const getApiKeys = options.getApiKeys ?? getConfiguredApiKeys;
87
+ const allowUnauthenticatedPaths = new Set((options.allowUnauthenticatedPaths ?? ["/"]).map((path$2) => normalizePathname(path$2)));
88
+ const allowUnauthenticatedPathPrefixes = (options.allowUnauthenticatedPathPrefixes ?? []).map((path$2) => normalizePathname(path$2));
89
+ const allowOptionsBypass = options.allowOptionsBypass ?? true;
63
90
  return async (c, next) => {
64
- if (c.req.method === "OPTIONS") {
65
- await next();
66
- return;
67
- }
68
- const url = new URL(c.req.url, "http://local");
69
- if (!isPathProtected(url.pathname)) {
70
- await next();
71
- return;
72
- }
73
- const configuredKey = getConfiguredKey();
74
- if (!configuredKey) {
75
- await next();
76
- return;
77
- }
78
- const providedKey = extractRequestApiKey(c.req.raw.headers);
79
- if (!providedKey || !timingSafeKeyCompare(providedKey, configuredKey)) return c.json({ error: {
80
- message: "Unauthorized. Provide Authorization: Bearer <key> or x-api-key.",
81
- type: "unauthorized"
82
- } }, 401);
83
- await next();
91
+ if (allowOptionsBypass && c.req.method === "OPTIONS") return next();
92
+ const pathname = normalizePathname(new URL(c.req.url, "http://local").pathname);
93
+ if (allowUnauthenticatedPaths.has(pathname)) return next();
94
+ if (allowUnauthenticatedPathPrefixes.some((prefix) => hasPrefixBoundary(pathname, prefix))) return next();
95
+ const apiKeys = getApiKeys();
96
+ if (apiKeys.length === 0) return next();
97
+ const requestApiKey = extractRequestApiKey(c);
98
+ if (!(requestApiKey ? apiKeys.some((apiKey) => timingSafeKeyCompare(requestApiKey, apiKey)) : false)) return createUnauthorizedResponse(c);
99
+ return next();
84
100
  };
85
101
  }
86
102
 
@@ -705,6 +721,7 @@ function parseTriStateBool(value) {
705
721
  if (value === "0") return false;
706
722
  }
707
723
  const CONFIG_KEYS = new Set([
724
+ "auth",
708
725
  "extraPrompts",
709
726
  "smallModel",
710
727
  "freeModelLoadBalancing",
@@ -755,6 +772,16 @@ function parseOptionalNonNegativeNumber(value, field) {
755
772
  if (!Number.isFinite(value) || value < 0) return { error: `${field} must be a non-negative number` };
756
773
  return { value };
757
774
  }
775
+ function parseAuthConfig(value) {
776
+ if (value === null || value === void 0) return { clear: true };
777
+ if (!isPlainObject(value)) return { error: "auth must be an object" };
778
+ for (const key of Object.keys(value)) if (key !== "apiKeys") return { error: `auth.${key} is not supported` };
779
+ if (!("apiKeys" in value) || value.apiKeys === null || value.apiKeys === void 0) return { value: { apiKeys: [] } };
780
+ if (!Array.isArray(value.apiKeys)) return { error: "auth.apiKeys must be an array of strings" };
781
+ const normalizedApiKeys = value.apiKeys.filter((item) => typeof item === "string").map((item) => item.trim()).filter((item) => item.length > 0);
782
+ if (normalizedApiKeys.length !== value.apiKeys.length) return { error: "auth.apiKeys must contain non-empty strings only" };
783
+ return { value: { apiKeys: [...new Set(normalizedApiKeys)] } };
784
+ }
758
785
  function parseStringRecord(value, field) {
759
786
  if (value === null || value === void 0) return { clear: true };
760
787
  if (!isPlainObject(value)) return { error: `${field} must be an object with string values` };
@@ -830,6 +857,15 @@ function applyOptionalString(next, key, value) {
830
857
  }
831
858
  next[key] = parsed.value;
832
859
  }
860
+ function applyAuthConfig(next, value) {
861
+ const parsed = parseAuthConfig(value);
862
+ if ("error" in parsed) return parsed.error;
863
+ if ("clear" in parsed) {
864
+ delete next.auth;
865
+ return;
866
+ }
867
+ next.auth = parsed.value;
868
+ }
833
869
  function applyOptionalBoolean(next, key, value) {
834
870
  const parsed = parseOptionalBoolean(value, key);
835
871
  if ("error" in parsed) return parsed.error;
@@ -875,51 +911,29 @@ function applyModelAliases(next, value) {
875
911
  }
876
912
  next.modelAliases = parsed.value;
877
913
  }
914
+ const CONFIG_PATCH_HANDLERS = {
915
+ auth: applyAuthConfig,
916
+ extraPrompts: applyExtraPrompts,
917
+ smallModel: (next, value) => applyOptionalString(next, "smallModel", value),
918
+ freeModelLoadBalancing: (next, value) => applyOptionalBoolean(next, "freeModelLoadBalancing", value),
919
+ apiKey: (next, value) => applyOptionalString(next, "apiKey", value),
920
+ modelReasoningEfforts: applyReasoningEfforts,
921
+ modelAliases: applyModelAliases,
922
+ allowOriginalModelNamesForAliases: (next, value) => applyOptionalBoolean(next, "allowOriginalModelNamesForAliases", value),
923
+ useFunctionApplyPatch: (next, value) => applyOptionalBoolean(next, "useFunctionApplyPatch", value),
924
+ forceAgent: (next, value) => applyOptionalBoolean(next, "forceAgent", value),
925
+ compactUseSmallModel: (next, value) => applyOptionalBoolean(next, "compactUseSmallModel", value),
926
+ messageStartInputTokensFallback: (next, value) => applyOptionalBoolean(next, "messageStartInputTokensFallback", value),
927
+ modelRefreshIntervalHours: (next, value) => applyOptionalNumber(next, "modelRefreshIntervalHours", value)
928
+ };
878
929
  function applyConfigPatch(base, input) {
879
930
  const next = { ...base };
880
931
  for (const [rawKey, value] of Object.entries(input)) {
881
932
  const key = rawKey;
882
933
  if (!CONFIG_KEYS.has(key)) return { error: `Unknown config key: ${rawKey}` };
883
- let error;
884
- switch (rawKey) {
885
- case "extraPrompts":
886
- error = applyExtraPrompts(next, value);
887
- break;
888
- case "smallModel":
889
- error = applyOptionalString(next, "smallModel", value);
890
- break;
891
- case "freeModelLoadBalancing":
892
- error = applyOptionalBoolean(next, "freeModelLoadBalancing", value);
893
- break;
894
- case "apiKey":
895
- error = applyOptionalString(next, "apiKey", value);
896
- break;
897
- case "modelReasoningEfforts":
898
- error = applyReasoningEfforts(next, value);
899
- break;
900
- case "modelAliases":
901
- error = applyModelAliases(next, value);
902
- break;
903
- case "allowOriginalModelNamesForAliases":
904
- error = applyOptionalBoolean(next, "allowOriginalModelNamesForAliases", value);
905
- break;
906
- case "useFunctionApplyPatch":
907
- error = applyOptionalBoolean(next, "useFunctionApplyPatch", value);
908
- break;
909
- case "forceAgent":
910
- error = applyOptionalBoolean(next, "forceAgent", value);
911
- break;
912
- case "compactUseSmallModel":
913
- error = applyOptionalBoolean(next, "compactUseSmallModel", value);
914
- break;
915
- case "messageStartInputTokensFallback":
916
- error = applyOptionalBoolean(next, "messageStartInputTokensFallback", value);
917
- break;
918
- case "modelRefreshIntervalHours":
919
- error = applyOptionalNumber(next, "modelRefreshIntervalHours", value);
920
- break;
921
- default: return { error: `Unsupported config key: ${rawKey}` };
922
- }
934
+ const handler = CONFIG_PATCH_HANDLERS[rawKey];
935
+ if (!handler) return { error: `Unsupported config key: ${rawKey}` };
936
+ const error = handler(next, value);
923
937
  if (error) return { error };
924
938
  }
925
939
  return { config: next };
@@ -2237,11 +2251,12 @@ const createResponses = async (payload, { vision, initiator, upstreamRequestId }
2237
2251
  //#endregion
2238
2252
  //#region src/routes/messages/responses-translation.ts
2239
2253
  const MESSAGE_TYPE = "message";
2254
+ const CODEX_PHASE_MODEL = "gpt-5.3-codex";
2240
2255
  const THINKING_TEXT$1 = "Thinking...";
2241
2256
  const translateAnthropicMessagesToResponsesPayload = (payload, modelOverride) => {
2242
2257
  const model = modelOverride ?? payload.model;
2243
2258
  const input = [];
2244
- for (const message of payload.messages) input.push(...translateMessage(message));
2259
+ for (const message of payload.messages) input.push(...translateMessage(message, payload.model));
2245
2260
  const translatedTools = convertAnthropicTools(payload.tools);
2246
2261
  const toolChoice = convertAnthropicToolChoice(payload.tool_choice);
2247
2262
  const { safetyIdentifier, promptCacheKey } = parseUserId(payload.metadata?.user_id);
@@ -2267,9 +2282,9 @@ const translateAnthropicMessagesToResponsesPayload = (payload, modelOverride) =>
2267
2282
  include: ["reasoning.encrypted_content"]
2268
2283
  };
2269
2284
  };
2270
- const translateMessage = (message) => {
2285
+ const translateMessage = (message, model) => {
2271
2286
  if (message.role === "user") return translateUserMessage(message);
2272
- return translateAssistantMessage(message);
2287
+ return translateAssistantMessage(message, model);
2273
2288
  };
2274
2289
  const translateUserMessage = (message) => {
2275
2290
  if (typeof message.content === "string") return [createMessage("user", message.content)];
@@ -2278,36 +2293,46 @@ const translateUserMessage = (message) => {
2278
2293
  const pendingContent = [];
2279
2294
  for (const block of message.content) {
2280
2295
  if (block.type === "tool_result") {
2281
- flushPendingContent("user", pendingContent, items);
2296
+ flushPendingContent(pendingContent, items, { role: "user" });
2282
2297
  items.push(createFunctionCallOutput(block));
2283
2298
  continue;
2284
2299
  }
2285
2300
  const converted = translateUserContentBlock(block);
2286
2301
  if (converted) pendingContent.push(converted);
2287
2302
  }
2288
- flushPendingContent("user", pendingContent, items);
2303
+ flushPendingContent(pendingContent, items, { role: "user" });
2289
2304
  return items;
2290
2305
  };
2291
- const translateAssistantMessage = (message) => {
2292
- if (typeof message.content === "string") return [createMessage("assistant", message.content)];
2306
+ const translateAssistantMessage = (message, model) => {
2307
+ const assistantPhase = resolveAssistantPhase(model, message.content);
2308
+ if (typeof message.content === "string") return [createMessage("assistant", message.content, assistantPhase)];
2293
2309
  if (!Array.isArray(message.content)) return [];
2294
2310
  const items = [];
2295
2311
  const pendingContent = [];
2296
2312
  for (const block of message.content) {
2297
2313
  if (block.type === "tool_use") {
2298
- flushPendingContent("assistant", pendingContent, items);
2314
+ flushPendingContent(pendingContent, items, {
2315
+ role: "assistant",
2316
+ phase: assistantPhase
2317
+ });
2299
2318
  items.push(createFunctionToolCall(block));
2300
2319
  continue;
2301
2320
  }
2302
2321
  if (block.type === "thinking" && block.signature && block.signature.includes("@")) {
2303
- flushPendingContent("assistant", pendingContent, items);
2322
+ flushPendingContent(pendingContent, items, {
2323
+ role: "assistant",
2324
+ phase: assistantPhase
2325
+ });
2304
2326
  items.push(createReasoningContent(block));
2305
2327
  continue;
2306
2328
  }
2307
2329
  const converted = translateAssistantContentBlock(block);
2308
2330
  if (converted) pendingContent.push(converted);
2309
2331
  }
2310
- flushPendingContent("assistant", pendingContent, items);
2332
+ flushPendingContent(pendingContent, items, {
2333
+ role: "assistant",
2334
+ phase: assistantPhase
2335
+ });
2311
2336
  return items;
2312
2337
  };
2313
2338
  const translateUserContentBlock = (block) => {
@@ -2323,17 +2348,26 @@ const translateAssistantContentBlock = (block) => {
2323
2348
  default: return;
2324
2349
  }
2325
2350
  };
2326
- const flushPendingContent = (role, pendingContent, target) => {
2351
+ const flushPendingContent = (pendingContent, target, message) => {
2327
2352
  if (pendingContent.length === 0) return;
2328
2353
  const messageContent = [...pendingContent];
2329
- target.push(createMessage(role, messageContent));
2354
+ target.push(createMessage(message.role, messageContent, message.phase));
2330
2355
  pendingContent.length = 0;
2331
2356
  };
2332
- const createMessage = (role, content) => ({
2357
+ const createMessage = (role, content, phase) => ({
2333
2358
  type: MESSAGE_TYPE,
2334
2359
  role,
2335
- content
2360
+ content,
2361
+ ...role === "assistant" && phase ? { phase } : {}
2336
2362
  });
2363
+ const resolveAssistantPhase = (model, content) => {
2364
+ if (!shouldApplyCodexPhase(model)) return;
2365
+ if (typeof content === "string") return "final_answer";
2366
+ if (!Array.isArray(content)) return;
2367
+ if (!content.some((block) => block.type === "text")) return;
2368
+ return content.some((block) => block.type === "tool_use") ? "commentary" : "final_answer";
2369
+ };
2370
+ const shouldApplyCodexPhase = (model) => model === CODEX_PHASE_MODEL;
2337
2371
  const createTextContent = (text) => ({
2338
2372
  type: "input_text",
2339
2373
  text
@@ -5635,7 +5669,7 @@ usageRoute.get("/:accountIndex", async (c) => {
5635
5669
  const server = new Hono();
5636
5670
  server.use(logger());
5637
5671
  server.use(cors());
5638
- server.use("*", createApiKeyAuthMiddleware());
5672
+ server.use("*", createAuthMiddleware({ allowUnauthenticatedPathPrefixes: ["/admin", "/api/admin"] }));
5639
5673
  server.get("/", (c) => c.text("Server running"));
5640
5674
  server.route("/chat/completions", completionRoutes);
5641
5675
  server.route("/models", modelRoutes);
@@ -5653,4 +5687,4 @@ server.route("/v1/messages", messageRoutes);
5653
5687
 
5654
5688
  //#endregion
5655
5689
  export { server };
5656
- //# sourceMappingURL=server-BnJIteDi.js.map
5690
+ //# sourceMappingURL=server-2YTgFCW0.js.map