@jeffreycao/copilot-api 1.10.29 → 1.10.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/README.zh-CN.md +2 -2
- package/dist/main.js +1 -1
- package/dist/{server-BDu1gVes.js → server-81bhucad.js} +229 -138
- package/dist/server-81bhucad.js.map +1 -0
- package/dist/{start-BDPLLHaw.js → start-CJzLIzWp.js} +2 -2
- package/dist/{start-BDPLLHaw.js.map → start-CJzLIzWp.js.map} +1 -1
- package/package.json +1 -1
- package/dist/server-BDu1gVes.js.map +0 -1
package/README.md
CHANGED
|
@@ -244,7 +244,7 @@ The following command line options are available for the `start` command:
|
|
|
244
244
|
- **auth.adminApiKey:** Single admin key used only for `/admin/*` routes. If missing, the server generates a random key at startup and writes it back to `config.json`. Requests use the same `x-api-key` or `Authorization: Bearer` headers, but regular `auth.apiKeys` never grant access to `/admin/*`.
|
|
245
245
|
- **modelMappings:** Exact `sourceModel -> targetModel` rewrites shared by top-level `POST /v1/messages`, `POST /v1/messages/count_tokens`, `POST /v1/responses`, and `POST /v1/chat/completions` requests. Omit it or leave it as `{}` to disable rewrites. Both the source and target must be non-empty strings. Targets can be regular model IDs or `provider/model` aliases such as `dashscope/qwen3.6-plus`, and the rewrite happens before provider alias parsing. These mappings are not split per interface. The admin endpoints `GET/POST /admin/config/model-mappings` read and update only this field.
|
|
246
246
|
- **extraPrompts:** Map of `model -> prompt` appended to the first system prompt when translating Anthropic-style requests to Copilot. Use this to inject guardrails or guidance per model. Missing default entries are auto-added without overwriting your custom prompts. The built-in prompts for `gpt-5.3-codex` and `gpt-5.4` enable phase-aware commentary, which lets the model emit a short user-facing progress update before tools or deeper reasoning.
|
|
247
|
-
- **providers:** Global upstream provider map. Each provider key (for example `dashscope`) becomes a route prefix (`/dashscope/v1/messages`). Supports `type: "anthropic"`, `type: "openai-compatible"`, and `type: "openai-responses"`. Top-level clients can also use `model: "dashscope/model-id"` with `/v1/messages`, `/v1/messages/count_tokens`, and `/v1/
|
|
247
|
+
- **providers:** Global upstream provider map. Each provider key (for example `dashscope`) becomes a route prefix (`/dashscope/v1/messages`). Supports `type: "anthropic"`, `type: "openai-compatible"`, and `type: "openai-responses"`. Top-level clients can also use `model: "dashscope/model-id"` with `/v1/messages`, `/v1/messages/count_tokens`, `/v1/responses`, and `/v1/chat/completions`; the gateway strips the `dashscope/` prefix before forwarding upstream. `GET /v1/models` does not aggregate provider models; use `GET /dashscope/v1/models` for provider model lists.
|
|
248
248
|
- `enabled` defaults to `true` if omitted.
|
|
249
249
|
- `baseUrl` should be provider API base URL without the final endpoint. For Anthropic providers, omit `/v1/messages`; for OpenAI-compatible providers, omit `/v1/chat/completions`; for OpenAI Responses providers, omit `/v1/responses`.
|
|
250
250
|
- `apiKey` is used as the upstream credential value and is required for regular providers.
|
|
@@ -304,7 +304,7 @@ These endpoints mimic the OpenAI API structure.
|
|
|
304
304
|
| Endpoint | Method | Description |
|
|
305
305
|
| --------------------------- | ------ | ---------------------------------------------------------------- |
|
|
306
306
|
| `POST /v1/responses` | `POST` | OpenAI Most advanced interface for generating model responses. Supports `provider/model` aliases for `openai-responses` providers. |
|
|
307
|
-
| `POST /v1/chat/completions` | `POST` | Creates a model response for the given chat conversation.
|
|
307
|
+
| `POST /v1/chat/completions` | `POST` | Creates a model response for the given chat conversation. Supports `provider/model` aliases for `openai-compatible` providers. |
|
|
308
308
|
| `GET /v1/models` | `GET` | Lists the currently available models. |
|
|
309
309
|
| `POST /v1/embeddings` | `POST` | Creates an embedding vector representing the input text. |
|
|
310
310
|
|
package/README.zh-CN.md
CHANGED
|
@@ -246,7 +246,7 @@ Copilot API 现在使用子命令结构,主要命令包括:
|
|
|
246
246
|
- **auth.adminApiKey:** 仅用于 `/admin/*` 路由的单个 admin key。若未配置,服务会在启动时自动生成一个随机 key,并回写到 `config.json`。它同样使用 `x-api-key` 或 `Authorization: Bearer` 这两种头,但普通 `auth.apiKeys` 不能访问 `/admin/*`。
|
|
247
247
|
- **modelMappings:** 用于顶层 `POST /v1/messages`、`POST /v1/messages/count_tokens`、`POST /v1/responses` 和 `POST /v1/chat/completions` 请求的精确 `sourceModel -> targetModel` 重写映射,这几类接口共用同一份规则。省略该字段或保留为 `{}` 时,不会做模型重写。`source` 和 `target` 都必须是非空字符串。`target` 可以是普通模型 ID,也可以是 `provider/model` 形式的别名,例如 `dashscope/qwen3.6-plus`;重写发生在 provider alias 解析之前。这些映射不再按接口区分。`GET/POST /admin/config/model-mappings` 管理接口读写的也只有这个字段。
|
|
248
248
|
- **extraPrompts:** `model -> prompt` 的映射。把 Anthropic 风格请求翻译给 Copilot 时,会将其附加到第一条 system prompt 后面。你可以借此为不同模型注入护栏或指引。缺失的默认项会自动补齐,但不会覆盖你自定义的 prompt。内置的 `gpt-5.3-codex` 和 `gpt-5.4` prompt 会启用带阶段感知的 commentary,让模型在工具调用或更深层推理前先发出简短的用户可见进度说明。
|
|
249
|
-
- **providers:** 全局上游 provider 映射。每个 provider key(例如 `dashscope`)都会变成一个路由前缀(`/dashscope/v1/messages`)。支持 `type: "anthropic"`、`type: "openai-compatible"` 和 `type: "openai-responses"`。顶层客户端也可以在 `/v1/messages`、`/v1/messages/count_tokens` 和 `/v1/
|
|
249
|
+
- **providers:** 全局上游 provider 映射。每个 provider key(例如 `dashscope`)都会变成一个路由前缀(`/dashscope/v1/messages`)。支持 `type: "anthropic"`、`type: "openai-compatible"` 和 `type: "openai-responses"`。顶层客户端也可以在 `/v1/messages`、`/v1/messages/count_tokens`、`/v1/responses` 和 `/v1/chat/completions` 中使用 `model: "dashscope/model-id"`;AI gateway 会在转发上游前移除 `dashscope/` 前缀。`GET /v1/models` 不聚合 provider 模型;provider 模型列表请使用 `GET /dashscope/v1/models`。
|
|
250
250
|
- `enabled`:可选,若省略则默认为 `true`。
|
|
251
251
|
- `baseUrl`:provider API 的基础 URL,不要带结尾的 endpoint。Anthropic provider 不要带 `/v1/messages`;OpenAI 兼容 provider 不要带 `/v1/chat/completions`;OpenAI Responses provider 不要带 `/v1/responses`。
|
|
252
252
|
- `apiKey`:作为上游凭据值使用;普通 provider 必须配置。
|
|
@@ -306,7 +306,7 @@ curl http://localhost:4141/admin/config/model-mappings \
|
|
|
306
306
|
| 端点 | 方法 | 说明 |
|
|
307
307
|
| --- | --- | --- |
|
|
308
308
|
| `POST /v1/responses` | `POST` | OpenAI 中用于生成模型响应的高级接口。支持 `openai-responses` provider 的 `provider/model` 别名。 |
|
|
309
|
-
| `POST /v1/chat/completions` | `POST` |
|
|
309
|
+
| `POST /v1/chat/completions` | `POST` | 为给定聊天对话创建模型响应。支持 `openai-compatible` provider 的 `provider/model` 别名。 |
|
|
310
310
|
| `GET /v1/models` | `GET` | 列出当前可用模型。 |
|
|
311
311
|
| `POST /v1/embeddings` | `POST` | 创建表示输入文本的向量嵌入。 |
|
|
312
312
|
|
package/dist/main.js
CHANGED
|
@@ -43,7 +43,7 @@ const { auth } = await import("./auth-aKVIYJgv.js");
|
|
|
43
43
|
const { checkUsage } = await import("./check-usage-VAhq_UvP.js");
|
|
44
44
|
const { debug } = await import("./debug-9GrBfKqc.js");
|
|
45
45
|
const { mcp } = await import("./mcp-DZgcvqQY.js");
|
|
46
|
-
const { start } = await import("./start-
|
|
46
|
+
const { start } = await import("./start-CJzLIzWp.js");
|
|
47
47
|
await runMain(defineCommand({
|
|
48
48
|
meta: {
|
|
49
49
|
name: "copilot-api",
|
|
@@ -273,6 +273,36 @@ const createHandlerLogger = (name) => {
|
|
|
273
273
|
return instance;
|
|
274
274
|
};
|
|
275
275
|
//#endregion
|
|
276
|
+
//#region src/lib/provider-model.ts
|
|
277
|
+
const parseProviderModelAlias = (model) => {
|
|
278
|
+
const separatorIndex = model.indexOf("/");
|
|
279
|
+
if (separatorIndex <= 0 || separatorIndex === model.length - 1) return null;
|
|
280
|
+
const provider = model.slice(0, separatorIndex).trim();
|
|
281
|
+
const providerModel = model.slice(separatorIndex + 1).trim();
|
|
282
|
+
if (!provider || !providerModel) return null;
|
|
283
|
+
return {
|
|
284
|
+
model: providerModel,
|
|
285
|
+
provider
|
|
286
|
+
};
|
|
287
|
+
};
|
|
288
|
+
const createFallbackModel = (modelId) => ({
|
|
289
|
+
capabilities: {
|
|
290
|
+
family: "provider",
|
|
291
|
+
limits: {},
|
|
292
|
+
object: "model_capabilities",
|
|
293
|
+
supports: {},
|
|
294
|
+
tokenizer: "o200k_base",
|
|
295
|
+
type: "chat"
|
|
296
|
+
},
|
|
297
|
+
id: modelId,
|
|
298
|
+
model_picker_enabled: false,
|
|
299
|
+
name: modelId,
|
|
300
|
+
object: "model",
|
|
301
|
+
preview: false,
|
|
302
|
+
vendor: "provider",
|
|
303
|
+
version: "unknown"
|
|
304
|
+
});
|
|
305
|
+
//#endregion
|
|
276
306
|
//#region src/lib/rate-limit.ts
|
|
277
307
|
async function checkRateLimit(state) {
|
|
278
308
|
if (state.rateLimitSeconds === void 0) return;
|
|
@@ -920,6 +950,188 @@ function mergeAnthropicUsage(current, next) {
|
|
|
920
950
|
};
|
|
921
951
|
}
|
|
922
952
|
//#endregion
|
|
953
|
+
//#region src/lib/provider-resolver.ts
|
|
954
|
+
function isMissingCodexCredentialsError(error) {
|
|
955
|
+
return error instanceof Error && error.message === "Codex credentials not found. Run `copilot-api auth login --provider codex` first.";
|
|
956
|
+
}
|
|
957
|
+
async function resolveProviderConfig(providerName) {
|
|
958
|
+
const normalizedProviderName = providerName.trim();
|
|
959
|
+
if (!normalizedProviderName) return null;
|
|
960
|
+
if (normalizedProviderName === "codex") {
|
|
961
|
+
if (getRawProviderConfig(normalizedProviderName)?.enabled === false) return null;
|
|
962
|
+
try {
|
|
963
|
+
await setupCodexToken();
|
|
964
|
+
} catch (error) {
|
|
965
|
+
if (isMissingCodexCredentialsError(error)) return null;
|
|
966
|
+
throw error;
|
|
967
|
+
}
|
|
968
|
+
const providerConfig = getProviderConfig(normalizedProviderName);
|
|
969
|
+
if (!providerConfig) return null;
|
|
970
|
+
return {
|
|
971
|
+
...providerConfig,
|
|
972
|
+
apiKey: state.codexAccessToken ?? providerConfig.apiKey
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
return getProviderConfig(normalizedProviderName);
|
|
976
|
+
}
|
|
977
|
+
//#endregion
|
|
978
|
+
//#region src/services/providers/provider-proxy.ts
|
|
979
|
+
const SHARED_FORWARDABLE_HEADERS = ["accept", "user-agent"];
|
|
980
|
+
const ANTHROPIC_FORWARDABLE_HEADERS = ["anthropic-version", "anthropic-beta"];
|
|
981
|
+
const STRIPPED_RESPONSE_HEADERS = [
|
|
982
|
+
"connection",
|
|
983
|
+
"content-encoding",
|
|
984
|
+
"content-length",
|
|
985
|
+
"keep-alive",
|
|
986
|
+
"proxy-authenticate",
|
|
987
|
+
"proxy-authorization",
|
|
988
|
+
"te",
|
|
989
|
+
"trailer",
|
|
990
|
+
"transfer-encoding",
|
|
991
|
+
"upgrade"
|
|
992
|
+
];
|
|
993
|
+
function buildProviderUpstreamHeaders(providerConfig, requestHeaders) {
|
|
994
|
+
const authHeaders = {};
|
|
995
|
+
if (providerConfig.authType === "x-api-key") authHeaders["x-api-key"] = providerConfig.apiKey;
|
|
996
|
+
else authHeaders.authorization = `Bearer ${providerConfig.apiKey}`;
|
|
997
|
+
const headers = {
|
|
998
|
+
"content-type": "application/json",
|
|
999
|
+
accept: "application/json",
|
|
1000
|
+
...authHeaders
|
|
1001
|
+
};
|
|
1002
|
+
for (const headerName of SHARED_FORWARDABLE_HEADERS) {
|
|
1003
|
+
const headerValue = requestHeaders.get(headerName);
|
|
1004
|
+
if (headerValue) headers[headerName] = headerValue;
|
|
1005
|
+
}
|
|
1006
|
+
if (providerConfig.type !== "anthropic") return headers;
|
|
1007
|
+
for (const headerName of ANTHROPIC_FORWARDABLE_HEADERS) {
|
|
1008
|
+
const headerValue = requestHeaders.get(headerName);
|
|
1009
|
+
if (headerValue) headers[headerName] = headerValue;
|
|
1010
|
+
}
|
|
1011
|
+
return headers;
|
|
1012
|
+
}
|
|
1013
|
+
function createProviderProxyResponse(upstreamResponse, body) {
|
|
1014
|
+
const headers = new Headers(upstreamResponse.headers);
|
|
1015
|
+
for (const headerName of STRIPPED_RESPONSE_HEADERS) headers.delete(headerName);
|
|
1016
|
+
return new Response(body ?? upstreamResponse.body, {
|
|
1017
|
+
headers,
|
|
1018
|
+
status: upstreamResponse.status,
|
|
1019
|
+
statusText: upstreamResponse.statusText
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
async function forwardProviderMessages(providerConfig, payload, requestHeaders) {
|
|
1023
|
+
return await fetch(`${providerConfig.baseUrl}/v1/messages`, {
|
|
1024
|
+
method: "POST",
|
|
1025
|
+
headers: buildProviderUpstreamHeaders(providerConfig, requestHeaders),
|
|
1026
|
+
body: JSON.stringify(payload)
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
async function forwardProviderChatCompletions(providerConfig, payload, requestHeaders) {
|
|
1030
|
+
return await fetch(`${providerConfig.baseUrl}/v1/chat/completions`, {
|
|
1031
|
+
method: "POST",
|
|
1032
|
+
headers: buildProviderUpstreamHeaders(providerConfig, requestHeaders),
|
|
1033
|
+
body: JSON.stringify(payload)
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
async function forwardProviderResponses(providerConfig, payload, requestHeaders) {
|
|
1037
|
+
return await fetch(`${providerConfig.baseUrl}/v1/responses`, {
|
|
1038
|
+
method: "POST",
|
|
1039
|
+
headers: buildProviderUpstreamHeaders(providerConfig, requestHeaders),
|
|
1040
|
+
body: JSON.stringify(payload)
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
async function forwardProviderModels(providerConfig, requestHeaders) {
|
|
1044
|
+
return await fetch(`${providerConfig.baseUrl}/v1/models`, {
|
|
1045
|
+
method: "GET",
|
|
1046
|
+
headers: buildProviderUpstreamHeaders(providerConfig, requestHeaders)
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
//#endregion
|
|
1050
|
+
//#region src/routes/provider/chat-completions/handler.ts
|
|
1051
|
+
const logger$8 = createHandlerLogger("provider-chat-completions-handler");
|
|
1052
|
+
async function handleProviderChatCompletionsForProvider(c, options) {
|
|
1053
|
+
const { payload, provider } = options;
|
|
1054
|
+
const providerConfig = await resolveProviderConfig(provider);
|
|
1055
|
+
if (providerConfig?.type !== "openai-compatible") return c.json({ error: {
|
|
1056
|
+
message: `Provider '${provider}' does not support the /v1/chat/completions endpoint`,
|
|
1057
|
+
type: "invalid_request_error"
|
|
1058
|
+
} }, 400);
|
|
1059
|
+
const modelConfig = providerConfig.models?.[payload.model];
|
|
1060
|
+
applyProviderModelDefaults(payload, modelConfig);
|
|
1061
|
+
applyMissingExtraBody$1(payload, { extraBody: modelConfig?.extraBody });
|
|
1062
|
+
applyProviderStreamOptions(payload);
|
|
1063
|
+
debugJson(logger$8, "provider.chat_completions.request", {
|
|
1064
|
+
payload,
|
|
1065
|
+
provider
|
|
1066
|
+
});
|
|
1067
|
+
const upstreamResponse = await forwardProviderChatCompletions(providerConfig, payload, c.req.raw.headers);
|
|
1068
|
+
if (!upstreamResponse.ok) {
|
|
1069
|
+
logger$8.error("Failed to create provider chat completions", {
|
|
1070
|
+
provider,
|
|
1071
|
+
statusCode: upstreamResponse.status
|
|
1072
|
+
});
|
|
1073
|
+
throw new HTTPError(`Failed to create ${provider} chat completions`, upstreamResponse);
|
|
1074
|
+
}
|
|
1075
|
+
const recordUsage = createProviderChatCompletionsUsageRecorder(payload, provider);
|
|
1076
|
+
const contentType = upstreamResponse.headers.get("content-type") ?? "";
|
|
1077
|
+
if (Boolean(payload.stream) && contentType.includes("text/event-stream")) return streamProviderChatCompletions(c, upstreamResponse, {
|
|
1078
|
+
provider,
|
|
1079
|
+
recordUsage
|
|
1080
|
+
});
|
|
1081
|
+
const responseBody = await upstreamResponse.clone().json();
|
|
1082
|
+
recordUsage(normalizeOpenAIUsage(responseBody.usage));
|
|
1083
|
+
debugJson(logger$8, "provider.chat_completions.response", responseBody);
|
|
1084
|
+
return createProviderProxyResponse(upstreamResponse);
|
|
1085
|
+
}
|
|
1086
|
+
const applyProviderModelDefaults = (payload, modelConfig) => {
|
|
1087
|
+
payload.temperature ??= modelConfig?.temperature;
|
|
1088
|
+
payload.top_p ??= modelConfig?.topP;
|
|
1089
|
+
payload.top_k ??= modelConfig?.topK;
|
|
1090
|
+
};
|
|
1091
|
+
const applyMissingExtraBody$1 = (payload, options) => {
|
|
1092
|
+
for (const [key, value] of Object.entries(options.extraBody ?? {})) if (!Object.hasOwn(payload, key)) payload[key] = value;
|
|
1093
|
+
};
|
|
1094
|
+
const applyProviderStreamOptions = (payload) => {
|
|
1095
|
+
if (!payload.stream) return;
|
|
1096
|
+
payload.stream_options = {
|
|
1097
|
+
...payload.stream_options ?? {},
|
|
1098
|
+
include_usage: true
|
|
1099
|
+
};
|
|
1100
|
+
};
|
|
1101
|
+
const createProviderChatCompletionsUsageRecorder = (payload, provider) => createProviderTokenUsageRecorder({
|
|
1102
|
+
endpoint: "chat_completions",
|
|
1103
|
+
model: payload.model,
|
|
1104
|
+
providerName: provider
|
|
1105
|
+
});
|
|
1106
|
+
const streamProviderChatCompletions = (c, upstreamResponse, options) => {
|
|
1107
|
+
logger$8.debug("provider.chat_completions.streaming", { provider: options.provider });
|
|
1108
|
+
return streamSSE(c, async (stream) => {
|
|
1109
|
+
let usage = {};
|
|
1110
|
+
try {
|
|
1111
|
+
for await (const chunk of events(upstreamResponse)) {
|
|
1112
|
+
debugJson(logger$8, "provider.chat_completions.stream_chunk", chunk);
|
|
1113
|
+
if (chunk.data && chunk.data !== "[DONE]") {
|
|
1114
|
+
const parsedChunk = parseChatCompletionChunkData(chunk.data);
|
|
1115
|
+
if (parsedChunk?.usage) usage = normalizeOpenAIUsage(parsedChunk.usage);
|
|
1116
|
+
}
|
|
1117
|
+
await stream.writeSSE({
|
|
1118
|
+
event: chunk.event,
|
|
1119
|
+
data: chunk.data ?? ""
|
|
1120
|
+
});
|
|
1121
|
+
}
|
|
1122
|
+
} finally {
|
|
1123
|
+
options.recordUsage(usage);
|
|
1124
|
+
}
|
|
1125
|
+
});
|
|
1126
|
+
};
|
|
1127
|
+
const parseChatCompletionChunkData = (data) => {
|
|
1128
|
+
try {
|
|
1129
|
+
return JSON.parse(data);
|
|
1130
|
+
} catch {
|
|
1131
|
+
return null;
|
|
1132
|
+
}
|
|
1133
|
+
};
|
|
1134
|
+
//#endregion
|
|
923
1135
|
//#region src/lib/copilot-rate-limit.ts
|
|
924
1136
|
const copilotRateLimitTypes = ["session", "weekly"];
|
|
925
1137
|
const copilotRateLimitHeaders = {
|
|
@@ -1029,12 +1241,17 @@ async function handleCompletion$1(c) {
|
|
|
1029
1241
|
let payload = await c.req.json();
|
|
1030
1242
|
const requestedModel = payload.model;
|
|
1031
1243
|
payload.model = resolveMappedModel(payload.model);
|
|
1032
|
-
if (payload.model !== requestedModel)
|
|
1244
|
+
if (payload.model !== requestedModel) consola.debug(`Resolved model mapping: ${requestedModel} -> ${payload.model}`);
|
|
1245
|
+
const providerModelAlias = parseProviderModelAlias(payload.model);
|
|
1246
|
+
if (providerModelAlias) {
|
|
1247
|
+
payload.model = providerModelAlias.model;
|
|
1248
|
+
return await handleProviderChatCompletionsForProvider(c, {
|
|
1249
|
+
payload,
|
|
1250
|
+
provider: providerModelAlias.provider
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
1033
1253
|
await checkRateLimit(state);
|
|
1034
|
-
|
|
1035
|
-
value: payload,
|
|
1036
|
-
tailLength: 400
|
|
1037
|
-
});
|
|
1254
|
+
debugJson(logger$7, "Request payload:", payload);
|
|
1038
1255
|
const selectedModel = state.models?.data.find((model) => model.id === payload.model);
|
|
1039
1256
|
if (selectedModel?.id === "gpt-5.4") return c.json({ error: {
|
|
1040
1257
|
message: "Please use `/v1/responses` or `/v1/messages` API",
|
|
@@ -1156,36 +1373,6 @@ embeddingRoutes.post("/", async (c) => {
|
|
|
1156
1373
|
}
|
|
1157
1374
|
});
|
|
1158
1375
|
//#endregion
|
|
1159
|
-
//#region src/lib/provider-model.ts
|
|
1160
|
-
const parseProviderModelAlias = (model) => {
|
|
1161
|
-
const separatorIndex = model.indexOf("/");
|
|
1162
|
-
if (separatorIndex <= 0 || separatorIndex === model.length - 1) return null;
|
|
1163
|
-
const provider = model.slice(0, separatorIndex).trim();
|
|
1164
|
-
const providerModel = model.slice(separatorIndex + 1).trim();
|
|
1165
|
-
if (!provider || !providerModel) return null;
|
|
1166
|
-
return {
|
|
1167
|
-
model: providerModel,
|
|
1168
|
-
provider
|
|
1169
|
-
};
|
|
1170
|
-
};
|
|
1171
|
-
const createFallbackModel = (modelId) => ({
|
|
1172
|
-
capabilities: {
|
|
1173
|
-
family: "provider",
|
|
1174
|
-
limits: {},
|
|
1175
|
-
object: "model_capabilities",
|
|
1176
|
-
supports: {},
|
|
1177
|
-
tokenizer: "o200k_base",
|
|
1178
|
-
type: "chat"
|
|
1179
|
-
},
|
|
1180
|
-
id: modelId,
|
|
1181
|
-
model_picker_enabled: false,
|
|
1182
|
-
name: modelId,
|
|
1183
|
-
object: "model",
|
|
1184
|
-
preview: false,
|
|
1185
|
-
vendor: "provider",
|
|
1186
|
-
version: "unknown"
|
|
1187
|
-
});
|
|
1188
|
-
//#endregion
|
|
1189
1376
|
//#region src/lib/tokenizer.ts
|
|
1190
1377
|
const ENCODING_MAP = {
|
|
1191
1378
|
o200k_base: () => import("gpt-tokenizer/encoding/o200k_base"),
|
|
@@ -1403,31 +1590,6 @@ const getTokenCount = async (payload, model) => {
|
|
|
1403
1590
|
};
|
|
1404
1591
|
};
|
|
1405
1592
|
//#endregion
|
|
1406
|
-
//#region src/lib/provider-resolver.ts
|
|
1407
|
-
function isMissingCodexCredentialsError(error) {
|
|
1408
|
-
return error instanceof Error && error.message === "Codex credentials not found. Run `copilot-api auth login --provider codex` first.";
|
|
1409
|
-
}
|
|
1410
|
-
async function resolveProviderConfig(providerName) {
|
|
1411
|
-
const normalizedProviderName = providerName.trim();
|
|
1412
|
-
if (!normalizedProviderName) return null;
|
|
1413
|
-
if (normalizedProviderName === "codex") {
|
|
1414
|
-
if (getRawProviderConfig(normalizedProviderName)?.enabled === false) return null;
|
|
1415
|
-
try {
|
|
1416
|
-
await setupCodexToken();
|
|
1417
|
-
} catch (error) {
|
|
1418
|
-
if (isMissingCodexCredentialsError(error)) return null;
|
|
1419
|
-
throw error;
|
|
1420
|
-
}
|
|
1421
|
-
const providerConfig = getProviderConfig(normalizedProviderName);
|
|
1422
|
-
if (!providerConfig) return null;
|
|
1423
|
-
return {
|
|
1424
|
-
...providerConfig,
|
|
1425
|
-
apiKey: state.codexAccessToken ?? providerConfig.apiKey
|
|
1426
|
-
};
|
|
1427
|
-
}
|
|
1428
|
-
return getProviderConfig(normalizedProviderName);
|
|
1429
|
-
}
|
|
1430
|
-
//#endregion
|
|
1431
1593
|
//#region src/routes/messages/utils.ts
|
|
1432
1594
|
function mapOpenAIStopReasonToAnthropic(finishReason) {
|
|
1433
1595
|
if (finishReason === null) return null;
|
|
@@ -4022,78 +4184,6 @@ function getModels() {
|
|
|
4022
4184
|
};
|
|
4023
4185
|
}
|
|
4024
4186
|
//#endregion
|
|
4025
|
-
//#region src/services/providers/provider-proxy.ts
|
|
4026
|
-
const SHARED_FORWARDABLE_HEADERS = ["accept", "user-agent"];
|
|
4027
|
-
const ANTHROPIC_FORWARDABLE_HEADERS = ["anthropic-version", "anthropic-beta"];
|
|
4028
|
-
const STRIPPED_RESPONSE_HEADERS = [
|
|
4029
|
-
"connection",
|
|
4030
|
-
"content-encoding",
|
|
4031
|
-
"content-length",
|
|
4032
|
-
"keep-alive",
|
|
4033
|
-
"proxy-authenticate",
|
|
4034
|
-
"proxy-authorization",
|
|
4035
|
-
"te",
|
|
4036
|
-
"trailer",
|
|
4037
|
-
"transfer-encoding",
|
|
4038
|
-
"upgrade"
|
|
4039
|
-
];
|
|
4040
|
-
function buildProviderUpstreamHeaders(providerConfig, requestHeaders) {
|
|
4041
|
-
const authHeaders = {};
|
|
4042
|
-
if (providerConfig.authType === "x-api-key") authHeaders["x-api-key"] = providerConfig.apiKey;
|
|
4043
|
-
else authHeaders.authorization = `Bearer ${providerConfig.apiKey}`;
|
|
4044
|
-
const headers = {
|
|
4045
|
-
"content-type": "application/json",
|
|
4046
|
-
accept: "application/json",
|
|
4047
|
-
...authHeaders
|
|
4048
|
-
};
|
|
4049
|
-
for (const headerName of SHARED_FORWARDABLE_HEADERS) {
|
|
4050
|
-
const headerValue = requestHeaders.get(headerName);
|
|
4051
|
-
if (headerValue) headers[headerName] = headerValue;
|
|
4052
|
-
}
|
|
4053
|
-
if (providerConfig.type !== "anthropic") return headers;
|
|
4054
|
-
for (const headerName of ANTHROPIC_FORWARDABLE_HEADERS) {
|
|
4055
|
-
const headerValue = requestHeaders.get(headerName);
|
|
4056
|
-
if (headerValue) headers[headerName] = headerValue;
|
|
4057
|
-
}
|
|
4058
|
-
return headers;
|
|
4059
|
-
}
|
|
4060
|
-
function createProviderProxyResponse(upstreamResponse, body) {
|
|
4061
|
-
const headers = new Headers(upstreamResponse.headers);
|
|
4062
|
-
for (const headerName of STRIPPED_RESPONSE_HEADERS) headers.delete(headerName);
|
|
4063
|
-
return new Response(body ?? upstreamResponse.body, {
|
|
4064
|
-
headers,
|
|
4065
|
-
status: upstreamResponse.status,
|
|
4066
|
-
statusText: upstreamResponse.statusText
|
|
4067
|
-
});
|
|
4068
|
-
}
|
|
4069
|
-
async function forwardProviderMessages(providerConfig, payload, requestHeaders) {
|
|
4070
|
-
return await fetch(`${providerConfig.baseUrl}/v1/messages`, {
|
|
4071
|
-
method: "POST",
|
|
4072
|
-
headers: buildProviderUpstreamHeaders(providerConfig, requestHeaders),
|
|
4073
|
-
body: JSON.stringify(payload)
|
|
4074
|
-
});
|
|
4075
|
-
}
|
|
4076
|
-
async function forwardProviderChatCompletions(providerConfig, payload, requestHeaders) {
|
|
4077
|
-
return await fetch(`${providerConfig.baseUrl}/v1/chat/completions`, {
|
|
4078
|
-
method: "POST",
|
|
4079
|
-
headers: buildProviderUpstreamHeaders(providerConfig, requestHeaders),
|
|
4080
|
-
body: JSON.stringify(payload)
|
|
4081
|
-
});
|
|
4082
|
-
}
|
|
4083
|
-
async function forwardProviderResponses(providerConfig, payload, requestHeaders) {
|
|
4084
|
-
return await fetch(`${providerConfig.baseUrl}/v1/responses`, {
|
|
4085
|
-
method: "POST",
|
|
4086
|
-
headers: buildProviderUpstreamHeaders(providerConfig, requestHeaders),
|
|
4087
|
-
body: JSON.stringify(payload)
|
|
4088
|
-
});
|
|
4089
|
-
}
|
|
4090
|
-
async function forwardProviderModels(providerConfig, requestHeaders) {
|
|
4091
|
-
return await fetch(`${providerConfig.baseUrl}/v1/models`, {
|
|
4092
|
-
method: "GET",
|
|
4093
|
-
headers: buildProviderUpstreamHeaders(providerConfig, requestHeaders)
|
|
4094
|
-
});
|
|
4095
|
-
}
|
|
4096
|
-
//#endregion
|
|
4097
4187
|
//#region src/routes/provider/messages/handler.ts
|
|
4098
4188
|
const logger$5 = createHandlerLogger("provider-messages-handler");
|
|
4099
4189
|
const OPENAI_COMPATIBLE_CONTEXT_CACHE_MARKER_LIMIT = 4;
|
|
@@ -4877,7 +4967,7 @@ async function handleCompletion(c) {
|
|
|
4877
4967
|
const anthropicPayload = await c.req.json();
|
|
4878
4968
|
const requestedModel = anthropicPayload.model;
|
|
4879
4969
|
anthropicPayload.model = resolveMappedModel(anthropicPayload.model);
|
|
4880
|
-
if (anthropicPayload.model !== requestedModel)
|
|
4970
|
+
if (anthropicPayload.model !== requestedModel) consola.debug(`Resolved model mapping: ${requestedModel} -> ${anthropicPayload.model}`);
|
|
4881
4971
|
const providerModelAlias = parseProviderModelAlias(anthropicPayload.model);
|
|
4882
4972
|
if (providerModelAlias) {
|
|
4883
4973
|
anthropicPayload.model = providerModelAlias.model;
|
|
@@ -4892,8 +4982,7 @@ async function handleCompletion(c) {
|
|
|
4892
4982
|
sanitizeIdeTools(anthropicPayload);
|
|
4893
4983
|
const subagentMarker = parseSubagentMarkerFromFirstUser(anthropicPayload);
|
|
4894
4984
|
if (subagentMarker) debugJson(logger$4, "Detected Subagent marker:", subagentMarker);
|
|
4895
|
-
|
|
4896
|
-
logger$4.debug("Extracted session ID:", sessionId);
|
|
4985
|
+
let sessionId = getRootSessionId(anthropicPayload, c);
|
|
4897
4986
|
const compactType = getCompactType(anthropicPayload);
|
|
4898
4987
|
const anthropicBeta = c.req.header("anthropic-beta");
|
|
4899
4988
|
logger$4.debug("Anthropic Beta header:", anthropicBeta);
|
|
@@ -4906,6 +4995,8 @@ async function handleCompletion(c) {
|
|
|
4906
4995
|
applyLastMessageCacheControl(anthropicPayload, lastMessageCacheControl);
|
|
4907
4996
|
const requestId = generateRequestIdFromPayload(anthropicPayload, sessionId);
|
|
4908
4997
|
logger$4.debug("Generated request ID:", requestId);
|
|
4998
|
+
if (!sessionId) sessionId = getUUID(requestId);
|
|
4999
|
+
logger$4.debug("Extracted session ID:", sessionId);
|
|
4909
5000
|
if (state.manualApprove) await awaitApproval();
|
|
4910
5001
|
const selectedModel = findEndpointModel(anthropicPayload.model);
|
|
4911
5002
|
anthropicPayload.model = selectedModel?.id ?? anthropicPayload.model;
|
|
@@ -5194,7 +5285,7 @@ const handleResponses = async (c) => {
|
|
|
5194
5285
|
const payload = await c.req.json();
|
|
5195
5286
|
const requestedModel = payload.model;
|
|
5196
5287
|
payload.model = resolveMappedModel(payload.model);
|
|
5197
|
-
if (payload.model !== requestedModel)
|
|
5288
|
+
if (payload.model !== requestedModel) consola.debug(`Resolved model mapping: ${requestedModel} -> ${payload.model}`);
|
|
5198
5289
|
const providerModelAlias = parseProviderModelAlias(payload.model);
|
|
5199
5290
|
if (providerModelAlias) {
|
|
5200
5291
|
payload.model = providerModelAlias.model;
|
|
@@ -5207,7 +5298,7 @@ const handleResponses = async (c) => {
|
|
|
5207
5298
|
await responsesHandlerDependencies.checkRateLimit(state);
|
|
5208
5299
|
const subagentMarker = getCodexResponsesSubagentMarker(c);
|
|
5209
5300
|
if (subagentMarker) debugJson(logger$1, "Detected Codex subagent headers:", subagentMarker);
|
|
5210
|
-
const incomingSessionId =
|
|
5301
|
+
const incomingSessionId = getIncomingResponsesSessionId(c);
|
|
5211
5302
|
const sessionId = incomingSessionId ? getUUID(incomingSessionId) : void 0;
|
|
5212
5303
|
const requestId = generateRequestIdFromPayload({ messages: payload.input }, sessionId);
|
|
5213
5304
|
logger$1.debug("Generated request ID:", requestId);
|
|
@@ -5436,4 +5527,4 @@ server.route("/:provider/v1/models", providerModelRoutes);
|
|
|
5436
5527
|
//#endregion
|
|
5437
5528
|
export { server };
|
|
5438
5529
|
|
|
5439
|
-
//# sourceMappingURL=server-
|
|
5530
|
+
//# sourceMappingURL=server-81bhucad.js.map
|