@jeffreycao/copilot-api 2.8.1 → 2.8.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.
- package/dist/auth-BFV7bzJo.js +1 -0
- package/dist/{auth-B9pYwLYA.js → auth-b8dJfIDO.js} +4 -4
- package/dist/{auth-B9pYwLYA.js.map → auth-b8dJfIDO.js.map} +1 -1
- package/dist/{config-BvACNGIp.js → config-B55PwJzQ.js} +44 -3
- package/dist/config-B55PwJzQ.js.map +1 -0
- package/dist/{config-store-BLr5xjym.js → config-store-DFcv4AY0.js} +2 -2
- package/dist/{config-store-BLr5xjym.js.map → config-store-DFcv4AY0.js.map} +1 -1
- package/dist/{copilot-runtime-DvmA6ALD.js → copilot-runtime-DNKNavLK.js} +4 -4
- package/dist/{copilot-runtime-DvmA6ALD.js.map → copilot-runtime-DNKNavLK.js.map} +1 -1
- package/dist/{debug-C_uR3s4n.js → debug-1bQIZTcG.js} +3 -3
- package/dist/{debug-C_uR3s4n.js.map → debug-1bQIZTcG.js.map} +1 -1
- package/dist/main.js +4 -4
- package/dist/{provider-_0MixRky.js → provider-B8UJ434o.js} +2 -2
- package/dist/{provider-_0MixRky.js.map → provider-B8UJ434o.js.map} +1 -1
- package/dist/{provider-management-C8AWr-Gk.js → provider-management-C6ZgkTJ7.js} +2 -2
- package/dist/{provider-management-C8AWr-Gk.js.map → provider-management-C6ZgkTJ7.js.map} +1 -1
- package/dist/{server-CwSW3Oz8.js → server-Cw9qn5Gt.js} +1479 -222
- package/dist/server-Cw9qn5Gt.js.map +1 -0
- package/dist/{start-D5tt7WWi.js → start-CPoEuVzr.js} +7 -7
- package/dist/{start-D5tt7WWi.js.map → start-CPoEuVzr.js.map} +1 -1
- package/dist/{token-D3nRiw9B.js → token-DFlvOMh2.js} +12 -9
- package/dist/token-DFlvOMh2.js.map +1 -0
- package/package.json +3 -1
- package/dist/auth-dswtyrvJ.js +0 -1
- package/dist/config-BvACNGIp.js.map +0 -1
- package/dist/server-CwSW3Oz8.js.map +0 -1
- package/dist/token-D3nRiw9B.js.map +0 -1
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { D as
|
|
2
|
-
import { A as
|
|
3
|
-
import { $ as buildCodexRequestHeaders, B as getXaiAccessToken, C as copilotHeaders, D as prepareForCompact, E as isOpencodeOauthApp, F as compactTextOnlyGuard, I as createAuthMiddleware, J as readGitHubTokenFromEnv, L as getConfiguredAdminApiKeys, M as compactMessageSections, O as prepareInteractionHeaders, P as compactSystemPromptStarts, Q as CODEX_API_BASE_URL, S as copilotBaseUrl, T as copilotWebSocketHeaders, U as readXaiCredentials, Y as XAI_API_BASE_URL, _ as isAsyncIterable, at as
|
|
1
|
+
import { D as isGitHubCopilotEnabled, E as isGitHubCopilotAvailable, M as forwardError, O as state, S as PATHS, T as assertGitHubCopilotEnabled, a as getAnthropicApiKey, c as getConfig, d as getUpstreamTransportConfig, g as isResponsesApiWebSocketEnabled, h as isResponsesApiWebSearchEnabled, i as getAlphaSearchModel, k as HTTPError, l as getMessageApiWebSearchModel, m as isMessagesApiEnabled, o as getClaudeAutoModel, p as isAlphaSearchCodexPriorityEnabled, s as getClaudeTokenMultiplier, u as getOpencodeModelContextWindow, w as GITHUB_COPILOT_PROVIDER, y as reloadConfig } from "./config-store-DFcv4AY0.js";
|
|
2
|
+
import { A as getSmallModelForProvider, D as getModelResponsesApiCompactThreshold$1, E as getModelMappings, F as resolveMappedModel, I as setModelMappings, M as isContextManagementEnabledForResponses, N as isGpt56OrAbove, O as getReasoningEffortForModel, P as isOutputEncryptedContentDisabled, S as getModelsDevModelPricing, T as getExtraPromptForModel, _ as DEFAULT_IMAGE_MODELS, a as normalizeProviderBaseUrl, b as getModelsDevModel, d as isProviderModelVisible, f as resolveProviderModelsDevId, g as getBuiltinProviderModelRecords, h as getBuiltinProviderImageModelRecords, i as listEnabledProviders, j as isContextManagementEnabledForMessages, k as getSmallModel, l as getLocalProviderModelRecords, m as builtinProviderModelRegistry, n as getRawProviderConfig, o as resolveEffectiveProviderType, p as usesModelsDevModelCatalog, s as resolveProviderConfigForModel, t as getProviderConfig, u as getProviderModelConfig, v as IMAGE_PROVIDER_PRIORITY, x as getModelsDevModelMaxOutputTokens, y as getDefaultImageModel } from "./config-B55PwJzQ.js";
|
|
3
|
+
import { $ as buildCodexRequestHeaders, B as getXaiAccessToken, C as copilotHeaders, D as prepareForCompact, E as isOpencodeOauthApp, F as compactTextOnlyGuard, I as createAuthMiddleware, J as readGitHubTokenFromEnv, L as getConfiguredAdminApiKeys, M as compactMessageSections, O as prepareInteractionHeaders, P as compactSystemPromptStarts, Q as CODEX_API_BASE_URL, S as copilotBaseUrl, T as copilotWebSocketHeaders, U as readXaiCredentials, Y as XAI_API_BASE_URL, _ as isAsyncIterable, at as setPluginRequestActualModel, b as parseUserIdMetadata, ct as createResponsesSafeStream, d as generateRequestIdFromPayload, dt as createPooledWebSocketStream, et as forwardCodexResponses, f as getPromptCacheKey, ft as createWebSocketUrl, g as getUUID, h as getSessionAffinityFromHeaders, it as resolveTraceId$1, j as compactAutoContinuePromptStarts, k as prepareMessageProxyHeaders, lt as encodePoolKeyPart, m as getRootSessionIdFromHeaders, mt as stripInternalRequestHeaders, n as invalidateCodexRuntime, nt as generateTraceId, ot as fetchUpstreamWithLifecycle, p as getRootSessionId, q as readGitHubToken, rt as requestContext, s as setupCodexToken, st as createResponsesHttpEventStream, tt as formatPluginActualModel, u as stopCopilotRefreshLoop, ut as getResponsesStreamTerminalDisposition, v as isNullish, x as getCopilotUsage, y as isResponsesStream } from "./token-DFlvOMh2.js";
|
|
4
4
|
import { a as isDeferredToolName, c as parseMcpToolSearchSentinel, d as shouldEnableResponsesToolSearch, i as isBridgeToolSearchName, l as resolveBridgeToolSearchName, o as listDeferredToolNames, r as formatToolSearchBridgeArguments, s as normalizeToolSearchBridgeArguments, t as BRIDGE_TOOL_SEARCH_NAME, u as selectDeferredToolsByNames } from "./tool-search-DzipwtCX.js";
|
|
5
|
-
import { n as isProviderAgentModelVisible, t as getProviderAgentModels } from "./provider-management-
|
|
6
|
-
import { i as registerProcessCleanup, n as stopModelsRefreshLoop, r as EventBus, t as setupCopilotRuntime } from "./copilot-runtime-
|
|
5
|
+
import { n as isProviderAgentModelVisible, t as getProviderAgentModels } from "./provider-management-C6ZgkTJ7.js";
|
|
6
|
+
import { i as registerProcessCleanup, n as stopModelsRefreshLoop, r as EventBus, t as setupCopilotRuntime } from "./copilot-runtime-DNKNavLK.js";
|
|
7
7
|
import consola from "consola";
|
|
8
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
9
|
-
import fs, { createWriteStream, readFileSync, rmSync } from "node:fs";
|
|
8
|
+
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
9
|
+
import fs, { createReadStream, createWriteStream, readFileSync, rmSync } from "node:fs";
|
|
10
10
|
import path, { join } from "node:path";
|
|
11
|
-
import fs$1, { mkdtemp, rm } from "node:fs/promises";
|
|
11
|
+
import fs$1, { mkdtemp, open, rm } from "node:fs/promises";
|
|
12
12
|
import os, { tmpdir } from "node:os";
|
|
13
13
|
import { fetch as fetch$1, getGlobalDispatcher } from "undici";
|
|
14
14
|
import { events } from "fetch-event-stream";
|
|
@@ -21,6 +21,9 @@ import { streamSSE } from "hono/streaming";
|
|
|
21
21
|
import { Busboy } from "@fastify/busboy";
|
|
22
22
|
import { Readable, Transform } from "node:stream";
|
|
23
23
|
import { pipeline } from "node:stream/promises";
|
|
24
|
+
import { none } from "stream-chain/core";
|
|
25
|
+
import { jsonParser } from "stream-json/core/parser.js";
|
|
26
|
+
import stringer from "stream-json/core/stringer.js";
|
|
24
27
|
import { decompress } from "fzstd";
|
|
25
28
|
// src/lib/trace.ts
|
|
26
29
|
const traceIdMiddleware = async (c, next) => {
|
|
@@ -175,10 +178,15 @@ function initializeTokenUsageDb(db) {
|
|
|
175
178
|
ensureColumn(db, "total_cost_nanos", "INTEGER");
|
|
176
179
|
ensureColumn(db, "cost_source", "TEXT");
|
|
177
180
|
ensureColumn(db, "request_id", "TEXT");
|
|
181
|
+
ensureColumn(db, "upstream_request_id", "TEXT");
|
|
178
182
|
ensureColumn(db, "first_token_ms", "REAL");
|
|
179
183
|
ensureColumn(db, "duration_ms", "REAL");
|
|
180
184
|
ensureColumn(db, "request_outcome", "TEXT");
|
|
181
185
|
db.exec(`
|
|
186
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_token_usage_events_upstream_request
|
|
187
|
+
ON token_usage_events(source, provider_name, endpoint, upstream_request_id)
|
|
188
|
+
`);
|
|
189
|
+
db.exec(`
|
|
182
190
|
CREATE INDEX IF NOT EXISTS idx_token_usage_events_request_id
|
|
183
191
|
ON token_usage_events(request_id)
|
|
184
192
|
`);
|
|
@@ -243,12 +251,14 @@ async function writeTokenUsageEvent(event) {
|
|
|
243
251
|
total_cost_nanos,
|
|
244
252
|
cost_source,
|
|
245
253
|
request_id,
|
|
254
|
+
upstream_request_id,
|
|
246
255
|
first_token_ms,
|
|
247
256
|
duration_ms,
|
|
248
257
|
request_outcome,
|
|
249
258
|
user_agent
|
|
250
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
251
|
-
|
|
259
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
260
|
+
ON CONFLICT(source, provider_name, endpoint, upstream_request_id) DO NOTHING
|
|
261
|
+
`).run(event.created_at_ms, event.created_at_utc, event.trace_id, event.session_id, event.user_id, event.source, event.endpoint, event.provider_name, event.model, event.input_tokens, event.output_tokens, event.cache_read_input_tokens, event.cache_creation_input_tokens, event.total_tokens, event.total_nano_aiu, event.cost_currency, event.total_cost_nanos, event.cost_source, event.request_id, event.upstream_request_id ?? null, event.first_token_ms, event.duration_ms, event.request_outcome, event.user_agent);
|
|
252
262
|
}
|
|
253
263
|
function enqueueTokenUsageWrite(event) {
|
|
254
264
|
if (!isTokenUsageStorageEnabled()) return;
|
|
@@ -906,14 +916,17 @@ async function closeUsageStore() {
|
|
|
906
916
|
registerProcessCleanup(closeUsageStore);
|
|
907
917
|
// src/lib/request-events.ts
|
|
908
918
|
const TRACKED = /\/(chat\/completions|responses|messages|alpha\/search)$/;
|
|
919
|
+
const IMAGE_REQUEST = /\/images\/(generations|edits)$/u;
|
|
909
920
|
function setPluginRequestModel(model) {
|
|
910
921
|
requestContext.getStore()?.pluginRequest?.setModel(model);
|
|
911
922
|
}
|
|
912
923
|
function requestEventsMiddleware(plugins, port) {
|
|
913
924
|
return async (c, next) => {
|
|
914
|
-
|
|
925
|
+
const isImageRequest = IMAGE_REQUEST.test(c.req.path);
|
|
926
|
+
if (c.req.method !== "POST" || !TRACKED.test(c.req.path) && !isImageRequest) return await next();
|
|
915
927
|
const store = requestContext.getStore();
|
|
916
928
|
if (!store) return await next();
|
|
929
|
+
const requestPlugins = isImageRequest ? void 0 : plugins;
|
|
917
930
|
const started = performance.now();
|
|
918
931
|
const info = {
|
|
919
932
|
id: randomUUID(),
|
|
@@ -937,7 +950,7 @@ function requestEventsMiddleware(plugins, port) {
|
|
|
937
950
|
recordFirstToken: () => {
|
|
938
951
|
if (finished || timing.firstTokenMs !== null) return;
|
|
939
952
|
timing.firstTokenMs = performance.now() - started;
|
|
940
|
-
|
|
953
|
+
requestPlugins?.publish("request.first_token", {
|
|
941
954
|
...info,
|
|
942
955
|
firstTokenMs: timing.firstTokenMs
|
|
943
956
|
});
|
|
@@ -951,7 +964,7 @@ function requestEventsMiddleware(plugins, port) {
|
|
|
951
964
|
timing.durationMs = performance.now() - started;
|
|
952
965
|
timing.outcome = outcome;
|
|
953
966
|
if (timing.hasUsage) enqueueTokenUsageTiming(timing);
|
|
954
|
-
|
|
967
|
+
requestPlugins?.publish("request.end", {
|
|
955
968
|
...info,
|
|
956
969
|
durationMs: timing.durationMs,
|
|
957
970
|
firstTokenMs: timing.firstTokenMs,
|
|
@@ -961,33 +974,33 @@ function requestEventsMiddleware(plugins, port) {
|
|
|
961
974
|
signal.removeEventListener("abort", abort);
|
|
962
975
|
};
|
|
963
976
|
const abort = () => finish("cancelled");
|
|
964
|
-
if (
|
|
977
|
+
if (requestPlugins?.enabled) store.pluginRequest = {
|
|
965
978
|
id: info.id,
|
|
966
979
|
setModel: (model) => {
|
|
967
980
|
if (finished || typeof model !== "string" || !model || info.model === model) return;
|
|
968
981
|
info.model = model;
|
|
969
|
-
|
|
982
|
+
requestPlugins.publish("request.model", info);
|
|
970
983
|
},
|
|
971
984
|
setActualModel: (model) => {
|
|
972
985
|
if (finished || typeof model !== "string" || !model || info.actualModel === model) return;
|
|
973
986
|
info.actualModel = model;
|
|
974
|
-
|
|
987
|
+
requestPlugins.publish("request.actual_model", info);
|
|
975
988
|
},
|
|
976
989
|
setSessionId: (sessionId) => {
|
|
977
990
|
if (!sessionId || info.sessionId === sessionId) return;
|
|
978
991
|
info.sessionId = sessionId;
|
|
979
|
-
if (!finished)
|
|
992
|
+
if (!finished) requestPlugins.publish("request.session", info);
|
|
980
993
|
},
|
|
981
994
|
recordFirstToken: timing.recordFirstToken,
|
|
982
|
-
recordUsage: (event) =>
|
|
995
|
+
recordUsage: (event) => requestPlugins.publish("usage.recorded", {
|
|
983
996
|
...event,
|
|
984
997
|
requestId: info.id,
|
|
985
998
|
userAgent: info.userAgent,
|
|
986
999
|
sessionId: event.session_id || null,
|
|
987
|
-
actualModel: event.model
|
|
1000
|
+
actualModel: formatPluginActualModel(event.model, event.provider_name)
|
|
988
1001
|
})
|
|
989
1002
|
};
|
|
990
|
-
|
|
1003
|
+
requestPlugins?.publish("request.start", info);
|
|
991
1004
|
signal.addEventListener("abort", abort, { once: true });
|
|
992
1005
|
if (signal.aborted) abort();
|
|
993
1006
|
try {
|
|
@@ -1389,6 +1402,54 @@ async function ensureConfiguredProviderModelAlias(alias, resolveConfig = resolve
|
|
|
1389
1402
|
async function resolveConfiguredProviderModelAlias(model, resolveConfig = resolveProviderConfig) {
|
|
1390
1403
|
return ensureConfiguredProviderModelAlias(parseProviderModelAlias(model), resolveConfig);
|
|
1391
1404
|
}
|
|
1405
|
+
// src/lib/token-usage/image-pricing.ts
|
|
1406
|
+
const IMAGE_PRICES = {
|
|
1407
|
+
textInput: 5,
|
|
1408
|
+
imageInput: 8,
|
|
1409
|
+
imageOutput: 30
|
|
1410
|
+
};
|
|
1411
|
+
const IMAGE_MODELS = /* @__PURE__ */ new Set([
|
|
1412
|
+
"gpt-image-2",
|
|
1413
|
+
"gpt-image-2.5",
|
|
1414
|
+
"gpt-image-2.5-sunburst",
|
|
1415
|
+
"gpt-image-2.5-flare"
|
|
1416
|
+
]);
|
|
1417
|
+
function splitTokens(total, details, defaultModality) {
|
|
1418
|
+
const tokens = normalizeToken(total);
|
|
1419
|
+
if (details?.image_tokens !== void 0) {
|
|
1420
|
+
const image = Math.min(tokens, normalizeToken(details.image_tokens));
|
|
1421
|
+
return {
|
|
1422
|
+
image,
|
|
1423
|
+
text: tokens - image
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1426
|
+
if (details?.text_tokens !== void 0) {
|
|
1427
|
+
const text = Math.min(tokens, normalizeToken(details.text_tokens));
|
|
1428
|
+
return {
|
|
1429
|
+
image: tokens - text,
|
|
1430
|
+
text
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1433
|
+
return defaultModality === "image" ? {
|
|
1434
|
+
image: tokens,
|
|
1435
|
+
text: 0
|
|
1436
|
+
} : {
|
|
1437
|
+
image: 0,
|
|
1438
|
+
text: tokens
|
|
1439
|
+
};
|
|
1440
|
+
}
|
|
1441
|
+
function resolveBuiltinImageCost(input) {
|
|
1442
|
+
const model = input.model.trim().toLowerCase().replace(/-\d{4}-\d{2}-\d{2}$/u, "");
|
|
1443
|
+
if (!IMAGE_MODELS.has(model)) return null;
|
|
1444
|
+
const prompt = splitTokens(input.input_tokens, input.input_tokens_details, "text");
|
|
1445
|
+
const output = splitTokens(input.output_tokens, input.output_tokens_details, "image");
|
|
1446
|
+
const totalCostNanos = Math.round((prompt.text * IMAGE_PRICES.textInput + prompt.image * IMAGE_PRICES.imageInput + output.image * IMAGE_PRICES.imageOutput) * 1e3);
|
|
1447
|
+
return totalCostNanos > 0 ? {
|
|
1448
|
+
currency: "USD",
|
|
1449
|
+
source: "builtin",
|
|
1450
|
+
total_cost_nanos: totalCostNanos
|
|
1451
|
+
} : null;
|
|
1452
|
+
}
|
|
1392
1453
|
// src/lib/token-usage/pricing.ts
|
|
1393
1454
|
const COST_NANOS_PER_UNIT = 1e9;
|
|
1394
1455
|
const COST_NANOS_PER_TOKEN_AT_ONE_PER_MILLION = 1e3;
|
|
@@ -1401,13 +1462,14 @@ const BUILTIN_PROVIDER_CURRENCIES = {
|
|
|
1401
1462
|
"opencode-go": "USD"
|
|
1402
1463
|
};
|
|
1403
1464
|
function resolveTokenUsageCost(input) {
|
|
1404
|
-
if (input.source === "provider" && input.providerName?.trim().toLowerCase()
|
|
1465
|
+
if (input.source === "provider" && ["openrouter", "xai"].includes(input.providerName?.trim().toLowerCase() ?? "")) {
|
|
1405
1466
|
const reportedCost = resolveReportedProviderCost(input);
|
|
1406
1467
|
if (reportedCost) return reportedCost;
|
|
1407
1468
|
}
|
|
1408
1469
|
if (input.source === "copilot") return resolveCopilotCost(input);
|
|
1409
1470
|
const providerName = input.providerName?.trim();
|
|
1410
1471
|
if (!providerName) return null;
|
|
1472
|
+
if (providerName.toLowerCase() === "codex" && !input.pricing && (input.endpoint === "images_generations" || input.endpoint === "images_edits")) return resolveBuiltinImageCost(input);
|
|
1411
1473
|
const resolvedPricing = resolveProviderPricing(providerName, input.model, input.pricing);
|
|
1412
1474
|
if (!resolvedPricing) return null;
|
|
1413
1475
|
const pricing = resolvePricingTier(resolvedPricing.pricing, getInputTokenTotal(input), resolvePricingTime(input.at));
|
|
@@ -1446,6 +1508,9 @@ function resolveReportedProviderCost(input) {
|
|
|
1446
1508
|
total_cost_nanos: totalCostNanos
|
|
1447
1509
|
};
|
|
1448
1510
|
}
|
|
1511
|
+
function xaiUsdTicksToCost(value) {
|
|
1512
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value / 1e10 : void 0;
|
|
1513
|
+
}
|
|
1449
1514
|
function resolveCopilotCost(input) {
|
|
1450
1515
|
const totalNanoAiu = normalizeToken(input.total_nano_aiu);
|
|
1451
1516
|
if (totalNanoAiu <= 0) return null;
|
|
@@ -1551,13 +1616,13 @@ function resolveUserId(input) {
|
|
|
1551
1616
|
return state.userName?.trim() || "";
|
|
1552
1617
|
}
|
|
1553
1618
|
function toPersistedEvent(input) {
|
|
1554
|
-
if (!hasAnyToken(input)) return null;
|
|
1555
1619
|
const now = /* @__PURE__ */ new Date();
|
|
1556
|
-
const timing = requestContext.getStore()?.requestTiming;
|
|
1557
1620
|
const cost = resolveTokenUsageCost({
|
|
1558
1621
|
...input,
|
|
1559
1622
|
at: now
|
|
1560
1623
|
});
|
|
1624
|
+
if (!hasAnyToken(input) && !cost) return null;
|
|
1625
|
+
const timing = requestContext.getStore()?.requestTiming;
|
|
1561
1626
|
return {
|
|
1562
1627
|
request_id: timing?.id ?? null,
|
|
1563
1628
|
first_token_ms: timing?.firstTokenMs ?? null,
|
|
@@ -1581,7 +1646,8 @@ function toPersistedEvent(input) {
|
|
|
1581
1646
|
total_tokens: resolveTotalTokens(input),
|
|
1582
1647
|
trace_id: resolveTraceId(input.traceId),
|
|
1583
1648
|
user_agent: (requestContext.getStore()?.userAgent ?? "").slice(0, USER_AGENT_MAX_LENGTH),
|
|
1584
|
-
user_id: resolveUserId(input)
|
|
1649
|
+
user_id: resolveUserId(input),
|
|
1650
|
+
upstream_request_id: input.upstreamRequestId?.trim() || null
|
|
1585
1651
|
};
|
|
1586
1652
|
}
|
|
1587
1653
|
tokenUsageEventBus.subscribe("token_usage.recorded", enqueueTokenUsageWrite);
|
|
@@ -2230,7 +2296,7 @@ async function forwardProviderMessages(providerConfig, payload, requestHeaders,
|
|
|
2230
2296
|
const headers = buildProviderUpstreamHeaders(providerConfig, requestHeaders);
|
|
2231
2297
|
applyOpencodeSessionHeader(providerConfig, headers, resolveOpencodeMessagesSession(payload));
|
|
2232
2298
|
const transportConfig = getUpstreamTransportConfig();
|
|
2233
|
-
setPluginRequestActualModel(payload.model);
|
|
2299
|
+
setPluginRequestActualModel(payload.model, providerConfig.name);
|
|
2234
2300
|
return await fetchUpstreamWithLifecycle(resolveProviderEndpointUrl(providerConfig, "messages"), {
|
|
2235
2301
|
method: "POST",
|
|
2236
2302
|
headers,
|
|
@@ -2246,7 +2312,7 @@ async function forwardProviderChatCompletions(providerConfig, payload, requestHe
|
|
|
2246
2312
|
const headers = buildProviderUpstreamHeaders(providerConfig, requestHeaders);
|
|
2247
2313
|
applyOpencodeSessionHeader(providerConfig, headers, payload.prompt_cache_key?.trim() || void 0);
|
|
2248
2314
|
const transportConfig = getUpstreamTransportConfig();
|
|
2249
|
-
setPluginRequestActualModel(payload.model);
|
|
2315
|
+
setPluginRequestActualModel(payload.model, providerConfig.name);
|
|
2250
2316
|
return await fetchUpstreamWithLifecycle(resolveProviderEndpointUrl(providerConfig, "chat/completions"), {
|
|
2251
2317
|
method: "POST",
|
|
2252
2318
|
headers,
|
|
@@ -2263,7 +2329,7 @@ async function forwardProviderResponses(providerConfig, payload, requestHeaders,
|
|
|
2263
2329
|
const headers = buildProviderUpstreamHeaders(providerConfig, requestHeaders);
|
|
2264
2330
|
applyOpencodeSessionHeader(providerConfig, headers, payload.prompt_cache_key?.trim() || void 0);
|
|
2265
2331
|
await applyXaiGrokBuildHeaders(providerConfig, headers, payload);
|
|
2266
|
-
setPluginRequestActualModel(payload.model);
|
|
2332
|
+
setPluginRequestActualModel(payload.model, providerConfig.name);
|
|
2267
2333
|
return await fetchUpstreamWithLifecycle(resolveProviderEndpointUrl(providerConfig, "responses"), {
|
|
2268
2334
|
method: "POST",
|
|
2269
2335
|
headers,
|
|
@@ -2887,7 +2953,7 @@ function parseDebugBody(body) {
|
|
|
2887
2953
|
}
|
|
2888
2954
|
async function forwardCodexAlphaSearchRequest(request, model) {
|
|
2889
2955
|
await debugJsonAsync(logger$19, "alpha_search.codex.request", async () => ({ body: parseDebugBody(await request.clone().text()) }));
|
|
2890
|
-
if (model) setPluginRequestActualModel(model);
|
|
2956
|
+
if (model) setPluginRequestActualModel(model, "codex");
|
|
2891
2957
|
const upstreamResponse = await alphaSearchRouteDependencies.forwardCodexAlphaSearch(request);
|
|
2892
2958
|
await debugJsonAsync(logger$19, "alpha_search.codex.response", async () => ({
|
|
2893
2959
|
body: parseDebugBody(await upstreamResponse.clone().text()),
|
|
@@ -3821,7 +3887,7 @@ var XaiImagesRequestError = class extends Error {
|
|
|
3821
3887
|
this.name = "XaiImagesRequestError";
|
|
3822
3888
|
}
|
|
3823
3889
|
};
|
|
3824
|
-
function isRecord$
|
|
3890
|
+
function isRecord$5(value) {
|
|
3825
3891
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3826
3892
|
}
|
|
3827
3893
|
function readPrompt(value) {
|
|
@@ -3930,7 +3996,7 @@ function readImageReference(value) {
|
|
|
3930
3996
|
type: "image_url",
|
|
3931
3997
|
url: normalizeImageUrl(value)
|
|
3932
3998
|
};
|
|
3933
|
-
if (!isRecord$
|
|
3999
|
+
if (!isRecord$5(value)) throw new XaiImagesRequestError("image must be a URL or an image object");
|
|
3934
4000
|
if (typeof value.url === "string") return {
|
|
3935
4001
|
type: "image_url",
|
|
3936
4002
|
url: normalizeImageUrl(value.url)
|
|
@@ -3975,7 +4041,7 @@ function buildEditPayload(model, prompt, images, source) {
|
|
|
3975
4041
|
* request. The configured model replaces the client model.
|
|
3976
4042
|
*/
|
|
3977
4043
|
function translateXaiImageGeneration(payload, model) {
|
|
3978
|
-
if (!isRecord$
|
|
4044
|
+
if (!isRecord$5(payload)) throw new XaiImagesRequestError("Image generation body must be a JSON object");
|
|
3979
4045
|
const translated = {
|
|
3980
4046
|
model,
|
|
3981
4047
|
prompt: readPrompt(payload.prompt)
|
|
@@ -3987,7 +4053,7 @@ function translateXaiImageGeneration(payload, model) {
|
|
|
3987
4053
|
* Translates an OpenAI JSON image edit body into an xAI Imagine edit request.
|
|
3988
4054
|
*/
|
|
3989
4055
|
function translateXaiImageEditJson(payload, model) {
|
|
3990
|
-
if (!isRecord$
|
|
4056
|
+
if (!isRecord$5(payload)) throw new XaiImagesRequestError("Image edit body must be a JSON object");
|
|
3991
4057
|
return buildEditPayload(model, readPrompt(payload.prompt), collectJsonImages(payload).map((image) => readImageReference(image)), payload);
|
|
3992
4058
|
}
|
|
3993
4059
|
const MAX_RESPONSE_IMAGE_BYTES = 33554432;
|
|
@@ -4080,15 +4146,15 @@ async function ensureImageResponseB64Json(response, fetchImage = globalThis.fetc
|
|
|
4080
4146
|
} catch {
|
|
4081
4147
|
return rebuildJsonResponse(response, body);
|
|
4082
4148
|
}
|
|
4083
|
-
if (!isRecord$
|
|
4149
|
+
if (!isRecord$5(payload) || !Array.isArray(payload.data)) return rebuildJsonResponse(response, body);
|
|
4084
4150
|
let changed = false;
|
|
4085
4151
|
const data = [];
|
|
4086
4152
|
for (const item of payload.data) {
|
|
4087
|
-
if (isRecord$
|
|
4153
|
+
if (isRecord$5(item) && onImageUrl) {
|
|
4088
4154
|
const outputUrl = imageOutputUrlForLog(item.url);
|
|
4089
4155
|
if (outputUrl) onImageUrl(outputUrl);
|
|
4090
4156
|
}
|
|
4091
|
-
if (!isRecord$
|
|
4157
|
+
if (!isRecord$5(item) || responseFormat === "url" || responseHasB64Json(item.b64_json)) {
|
|
4092
4158
|
data.push(item);
|
|
4093
4159
|
continue;
|
|
4094
4160
|
}
|
|
@@ -4223,7 +4289,7 @@ async function parseMultipartImageEdit(request) {
|
|
|
4223
4289
|
const headers = new Headers(requestHeaders);
|
|
4224
4290
|
headers.delete("content-length");
|
|
4225
4291
|
let body;
|
|
4226
|
-
if (provider === "codex" || provider === "xai") {
|
|
4292
|
+
if (provider === "codex" || provider === "xai" || provider === "github-copilot") {
|
|
4227
4293
|
const payload = {
|
|
4228
4294
|
...await readPayload(),
|
|
4229
4295
|
...model === void 0 ? {} : { model }
|
|
@@ -4246,6 +4312,603 @@ async function parseMultipartImageEdit(request) {
|
|
|
4246
4312
|
}
|
|
4247
4313
|
};
|
|
4248
4314
|
}
|
|
4315
|
+
// src/lib/image-json-files.ts
|
|
4316
|
+
const IMAGE_JSON_MAX_BYTES = 536870912;
|
|
4317
|
+
function imageJsonLimitError() {
|
|
4318
|
+
return new HTTPError("Image JSON upload exceeds the staging limit", Response.json({ error: {
|
|
4319
|
+
message: "Image JSON upload exceeds the staging limit",
|
|
4320
|
+
type: "invalid_request_error"
|
|
4321
|
+
} }, { status: 413 }));
|
|
4322
|
+
}
|
|
4323
|
+
/** Owns only an mkdtemp-created directory; callers never supply deletion paths. */
|
|
4324
|
+
async function createImageJsonFiles(ttlMs = 9e5) {
|
|
4325
|
+
const directory = await mkdtemp(join(tmpdir(), "copilot-image-json-"));
|
|
4326
|
+
let index = 0;
|
|
4327
|
+
let timer;
|
|
4328
|
+
let cleanupPromise;
|
|
4329
|
+
const unregister = registerProcessCleanup(() => {
|
|
4330
|
+
if (timer) clearTimeout(timer);
|
|
4331
|
+
rmSync(directory, {
|
|
4332
|
+
recursive: true,
|
|
4333
|
+
force: true
|
|
4334
|
+
});
|
|
4335
|
+
});
|
|
4336
|
+
const cleanup = () => {
|
|
4337
|
+
cleanupPromise ??= (async () => {
|
|
4338
|
+
if (timer) clearTimeout(timer);
|
|
4339
|
+
await rm(directory, {
|
|
4340
|
+
recursive: true,
|
|
4341
|
+
force: true,
|
|
4342
|
+
maxRetries: 3,
|
|
4343
|
+
retryDelay: 20
|
|
4344
|
+
});
|
|
4345
|
+
unregister();
|
|
4346
|
+
})();
|
|
4347
|
+
return cleanupPromise;
|
|
4348
|
+
};
|
|
4349
|
+
return {
|
|
4350
|
+
directory,
|
|
4351
|
+
cleanup,
|
|
4352
|
+
scheduleCleanup() {
|
|
4353
|
+
if (timer || cleanupPromise) return;
|
|
4354
|
+
timer = setTimeout(() => void cleanup().catch(() => {}), ttlMs);
|
|
4355
|
+
timer.unref();
|
|
4356
|
+
},
|
|
4357
|
+
async create() {
|
|
4358
|
+
const path = join(directory, String(index++));
|
|
4359
|
+
return {
|
|
4360
|
+
path,
|
|
4361
|
+
handle: await open(path, "wx")
|
|
4362
|
+
};
|
|
4363
|
+
}
|
|
4364
|
+
};
|
|
4365
|
+
}
|
|
4366
|
+
async function* readImageJsonFile(path, signal) {
|
|
4367
|
+
const stream = createReadStream(path, {
|
|
4368
|
+
highWaterMark: 65536,
|
|
4369
|
+
signal
|
|
4370
|
+
});
|
|
4371
|
+
try {
|
|
4372
|
+
for await (const bytes of stream) yield bytes;
|
|
4373
|
+
} finally {
|
|
4374
|
+
stream.destroy();
|
|
4375
|
+
}
|
|
4376
|
+
}
|
|
4377
|
+
// src/lib/streaming-json.ts
|
|
4378
|
+
const JSON_CHUNK_BYTES = 65536;
|
|
4379
|
+
/** Never ask the tokenizer to accumulate untrusted strings, keys, or numbers. */
|
|
4380
|
+
var StreamingJsonReader = class {
|
|
4381
|
+
decode = new TextDecoder("utf-8", { fatal: true });
|
|
4382
|
+
parse = jsonParser({ packValues: false });
|
|
4383
|
+
depth = 0;
|
|
4384
|
+
scalarLength = 0;
|
|
4385
|
+
boundedScalar = false;
|
|
4386
|
+
read(bytes) {
|
|
4387
|
+
const text = this.decode.decode(bytes, { stream: bytes !== void 0 });
|
|
4388
|
+
const result = this.parse(text);
|
|
4389
|
+
const tokens = result === none ? [] : result.values;
|
|
4390
|
+
if (bytes === void 0) {
|
|
4391
|
+
const last = this.parse(none);
|
|
4392
|
+
if (last !== none) tokens.push(...last.values);
|
|
4393
|
+
}
|
|
4394
|
+
for (const token of tokens) switch (token.name) {
|
|
4395
|
+
case "startObject":
|
|
4396
|
+
case "startArray":
|
|
4397
|
+
if (++this.depth > 64) throw new SyntaxError("JSON nesting exceeds 64");
|
|
4398
|
+
break;
|
|
4399
|
+
case "endObject":
|
|
4400
|
+
case "endArray":
|
|
4401
|
+
this.depth--;
|
|
4402
|
+
break;
|
|
4403
|
+
case "startKey":
|
|
4404
|
+
case "startNumber":
|
|
4405
|
+
this.scalarLength = 0;
|
|
4406
|
+
this.boundedScalar = true;
|
|
4407
|
+
break;
|
|
4408
|
+
case "endKey":
|
|
4409
|
+
case "endNumber":
|
|
4410
|
+
this.boundedScalar = false;
|
|
4411
|
+
break;
|
|
4412
|
+
case "stringChunk":
|
|
4413
|
+
case "numberChunk": if (this.boundedScalar) {
|
|
4414
|
+
this.scalarLength += token.value.length;
|
|
4415
|
+
if (this.scalarLength > 65536) throw new SyntaxError("JSON key or number is too large");
|
|
4416
|
+
}
|
|
4417
|
+
}
|
|
4418
|
+
return tokens;
|
|
4419
|
+
}
|
|
4420
|
+
};
|
|
4421
|
+
/** Splitting also bounds tokenizer allocations when a runtime supplies a huge chunk. */
|
|
4422
|
+
async function* readJsonBytes(body, signal) {
|
|
4423
|
+
if (!body) return;
|
|
4424
|
+
const reader = body.getReader();
|
|
4425
|
+
const abort = () => void reader.cancel(signal?.reason).catch(() => {});
|
|
4426
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
4427
|
+
let complete = false;
|
|
4428
|
+
try {
|
|
4429
|
+
while (true) {
|
|
4430
|
+
signal?.throwIfAborted();
|
|
4431
|
+
const { done, value } = await reader.read();
|
|
4432
|
+
signal?.throwIfAborted();
|
|
4433
|
+
if (done) {
|
|
4434
|
+
complete = true;
|
|
4435
|
+
return;
|
|
4436
|
+
}
|
|
4437
|
+
for (let offset = 0; offset < value.byteLength; offset += JSON_CHUNK_BYTES) yield value.subarray(offset, offset + JSON_CHUNK_BYTES);
|
|
4438
|
+
}
|
|
4439
|
+
} finally {
|
|
4440
|
+
signal?.removeEventListener("abort", abort);
|
|
4441
|
+
if (!complete) await reader.cancel().catch(() => {});
|
|
4442
|
+
reader.releaseLock();
|
|
4443
|
+
}
|
|
4444
|
+
}
|
|
4445
|
+
function createJsonByteSource(body, signal) {
|
|
4446
|
+
const controller = new AbortController();
|
|
4447
|
+
const bytes = readJsonBytes(body, AbortSignal.any([signal, controller.signal]));
|
|
4448
|
+
return {
|
|
4449
|
+
bytes,
|
|
4450
|
+
async cancel(reason) {
|
|
4451
|
+
controller.abort(reason);
|
|
4452
|
+
await bytes.return(void 0).catch(() => {});
|
|
4453
|
+
if (body && !body.locked) await body.cancel(reason).catch(() => {});
|
|
4454
|
+
}
|
|
4455
|
+
};
|
|
4456
|
+
}
|
|
4457
|
+
/** Lazy streams allow a route to choose the original or rebuilt body without teeing. */
|
|
4458
|
+
function jsonByteStream(iterator, cancel) {
|
|
4459
|
+
return new ReadableStream({
|
|
4460
|
+
async pull(controller) {
|
|
4461
|
+
try {
|
|
4462
|
+
const { done, value } = await iterator.next();
|
|
4463
|
+
if (done) controller.close();
|
|
4464
|
+
else controller.enqueue(value);
|
|
4465
|
+
} catch (error) {
|
|
4466
|
+
controller.error(error);
|
|
4467
|
+
await iterator.return(void 0).catch(() => {});
|
|
4468
|
+
}
|
|
4469
|
+
},
|
|
4470
|
+
async cancel(reason) {
|
|
4471
|
+
await cancel?.(reason);
|
|
4472
|
+
await iterator.return(void 0);
|
|
4473
|
+
}
|
|
4474
|
+
}, { highWaterMark: 0 });
|
|
4475
|
+
}
|
|
4476
|
+
var DuplicateImageModelError = class extends SyntaxError {
|
|
4477
|
+
constructor() {
|
|
4478
|
+
super("Duplicate top-level image model");
|
|
4479
|
+
}
|
|
4480
|
+
};
|
|
4481
|
+
/** Keeps only the top-level model; all image string chunks are discarded. */
|
|
4482
|
+
var ImageModelReader = class {
|
|
4483
|
+
model;
|
|
4484
|
+
object = false;
|
|
4485
|
+
complete = false;
|
|
4486
|
+
invalidModel = false;
|
|
4487
|
+
depth = 0;
|
|
4488
|
+
key = "";
|
|
4489
|
+
inKey = false;
|
|
4490
|
+
readingModel = false;
|
|
4491
|
+
modelText = "";
|
|
4492
|
+
seenModel = false;
|
|
4493
|
+
read(tokens) {
|
|
4494
|
+
for (const token of tokens) {
|
|
4495
|
+
if (token.name === "startKey") {
|
|
4496
|
+
this.inKey = true;
|
|
4497
|
+
this.key = "";
|
|
4498
|
+
} else if (token.name === "endKey") {
|
|
4499
|
+
this.inKey = false;
|
|
4500
|
+
if (this.depth === 1 && this.key === "model") {
|
|
4501
|
+
if (this.seenModel) throw new DuplicateImageModelError();
|
|
4502
|
+
this.seenModel = true;
|
|
4503
|
+
}
|
|
4504
|
+
} else if (token.name === "stringChunk") {
|
|
4505
|
+
if (this.inKey) this.key += token.value;
|
|
4506
|
+
else if (this.readingModel) {
|
|
4507
|
+
this.modelText += token.value;
|
|
4508
|
+
if (this.modelText.length > 4096) {
|
|
4509
|
+
this.invalidModel = true;
|
|
4510
|
+
this.readingModel = false;
|
|
4511
|
+
this.modelText = "";
|
|
4512
|
+
}
|
|
4513
|
+
}
|
|
4514
|
+
} else if (token.name === "startString") this.readingModel = this.depth === 1 && this.key === "model";
|
|
4515
|
+
else if (token.name === "endString") {
|
|
4516
|
+
if (this.readingModel) {
|
|
4517
|
+
this.model = this.modelText;
|
|
4518
|
+
this.modelText = "";
|
|
4519
|
+
this.readingModel = false;
|
|
4520
|
+
}
|
|
4521
|
+
} else if (this.depth === 1 && this.key === "model" && [
|
|
4522
|
+
"startObject",
|
|
4523
|
+
"startArray",
|
|
4524
|
+
"startNumber",
|
|
4525
|
+
"nullValue",
|
|
4526
|
+
"trueValue",
|
|
4527
|
+
"falseValue"
|
|
4528
|
+
].includes(token.name)) this.invalidModel = true;
|
|
4529
|
+
if (token.name === "startObject" || token.name === "startArray") {
|
|
4530
|
+
if (this.depth === 0) this.object = token.name === "startObject";
|
|
4531
|
+
this.depth++;
|
|
4532
|
+
} else if (token.name === "endObject" || token.name === "endArray") {
|
|
4533
|
+
if (--this.depth === 0) this.complete = true;
|
|
4534
|
+
}
|
|
4535
|
+
}
|
|
4536
|
+
}
|
|
4537
|
+
};
|
|
4538
|
+
/** Re-encodes bounded chunks, replacing or inserting the top-level model. */
|
|
4539
|
+
async function* rewriteImageModel(input, model, insertModel = false) {
|
|
4540
|
+
const parser = new StreamingJsonReader();
|
|
4541
|
+
const encode = new TextEncoder();
|
|
4542
|
+
const stringify = stringer();
|
|
4543
|
+
let depth = 0;
|
|
4544
|
+
let key = "";
|
|
4545
|
+
let inKey = false;
|
|
4546
|
+
let replacing = false;
|
|
4547
|
+
const process = (tokens) => {
|
|
4548
|
+
const parts = [];
|
|
4549
|
+
const emit = (token) => {
|
|
4550
|
+
const text = stringify(token);
|
|
4551
|
+
if (text !== none) parts.push(text);
|
|
4552
|
+
};
|
|
4553
|
+
for (const token of tokens) {
|
|
4554
|
+
if (insertModel && depth === 0 && token.name === "startObject") {
|
|
4555
|
+
emit(token);
|
|
4556
|
+
emit({ name: "startKey" });
|
|
4557
|
+
emit({
|
|
4558
|
+
name: "stringChunk",
|
|
4559
|
+
value: "model"
|
|
4560
|
+
});
|
|
4561
|
+
emit({ name: "endKey" });
|
|
4562
|
+
emit({ name: "startString" });
|
|
4563
|
+
emit({
|
|
4564
|
+
name: "stringChunk",
|
|
4565
|
+
value: model
|
|
4566
|
+
});
|
|
4567
|
+
emit({ name: "endString" });
|
|
4568
|
+
depth++;
|
|
4569
|
+
continue;
|
|
4570
|
+
}
|
|
4571
|
+
if (replacing) {
|
|
4572
|
+
if (token.name === "endString") replacing = false;
|
|
4573
|
+
continue;
|
|
4574
|
+
}
|
|
4575
|
+
if (token.name === "startKey") {
|
|
4576
|
+
inKey = true;
|
|
4577
|
+
key = "";
|
|
4578
|
+
} else if (token.name === "endKey") inKey = false;
|
|
4579
|
+
else if (token.name === "stringChunk" && inKey) key += token.value;
|
|
4580
|
+
if (token.name === "startString" && depth === 1 && key === "model") {
|
|
4581
|
+
emit(token);
|
|
4582
|
+
emit({
|
|
4583
|
+
name: "stringChunk",
|
|
4584
|
+
value: model
|
|
4585
|
+
});
|
|
4586
|
+
emit({ name: "endString" });
|
|
4587
|
+
replacing = true;
|
|
4588
|
+
continue;
|
|
4589
|
+
}
|
|
4590
|
+
emit(token);
|
|
4591
|
+
if (token.name === "startObject" || token.name === "startArray") depth++;
|
|
4592
|
+
else if (token.name === "endObject" || token.name === "endArray") depth--;
|
|
4593
|
+
}
|
|
4594
|
+
return encode.encode(parts.join(""));
|
|
4595
|
+
};
|
|
4596
|
+
for await (const bytes of input) yield process(parser.read(bytes));
|
|
4597
|
+
const tail = process(parser.read());
|
|
4598
|
+
if (tail.byteLength) yield tail;
|
|
4599
|
+
}
|
|
4600
|
+
// src/lib/image-json-request.ts
|
|
4601
|
+
function rebuild(request, body, changed = false) {
|
|
4602
|
+
const headers = new Headers(request.headers);
|
|
4603
|
+
if (changed) headers.delete("content-length");
|
|
4604
|
+
return new Request(request.url, {
|
|
4605
|
+
method: request.method,
|
|
4606
|
+
headers,
|
|
4607
|
+
body,
|
|
4608
|
+
signal: request.signal,
|
|
4609
|
+
duplex: "half"
|
|
4610
|
+
});
|
|
4611
|
+
}
|
|
4612
|
+
/** Read at most 64 KiB before forwarding a model-first request. Late models use disk. */
|
|
4613
|
+
async function prepareImageJsonRequest(request, maxStagedBytes = IMAGE_JSON_MAX_BYTES) {
|
|
4614
|
+
const forwardingAbort = new AbortController();
|
|
4615
|
+
const signal = AbortSignal.any([request.signal, forwardingAbort.signal]);
|
|
4616
|
+
const input = createJsonByteSource(request.body, signal);
|
|
4617
|
+
const bytes = input.bytes;
|
|
4618
|
+
const parser = new StreamingJsonReader();
|
|
4619
|
+
const metadata = new ImageModelReader();
|
|
4620
|
+
let valid = true;
|
|
4621
|
+
const scan = (chunk) => {
|
|
4622
|
+
if (!valid) return;
|
|
4623
|
+
try {
|
|
4624
|
+
metadata.read(parser.read(chunk));
|
|
4625
|
+
} catch (error) {
|
|
4626
|
+
if (error instanceof DuplicateImageModelError) throw error;
|
|
4627
|
+
valid = false;
|
|
4628
|
+
}
|
|
4629
|
+
};
|
|
4630
|
+
const prefix = [];
|
|
4631
|
+
let length = 0;
|
|
4632
|
+
let eof = false;
|
|
4633
|
+
let pending;
|
|
4634
|
+
async function* remaining() {
|
|
4635
|
+
if (pending) {
|
|
4636
|
+
const chunk = pending;
|
|
4637
|
+
pending = void 0;
|
|
4638
|
+
yield chunk;
|
|
4639
|
+
}
|
|
4640
|
+
yield* bytes;
|
|
4641
|
+
}
|
|
4642
|
+
let files;
|
|
4643
|
+
let stagedPath;
|
|
4644
|
+
try {
|
|
4645
|
+
while (length < JSON_CHUNK_BYTES) {
|
|
4646
|
+
const next = await bytes.next();
|
|
4647
|
+
if (next.done) {
|
|
4648
|
+
eof = true;
|
|
4649
|
+
scan();
|
|
4650
|
+
break;
|
|
4651
|
+
}
|
|
4652
|
+
const chunk = next.value.subarray(0, JSON_CHUNK_BYTES - length);
|
|
4653
|
+
pending = chunk.byteLength < next.value.byteLength ? next.value.subarray(chunk.byteLength) : void 0;
|
|
4654
|
+
prefix.push(chunk);
|
|
4655
|
+
length += chunk.byteLength;
|
|
4656
|
+
scan(chunk);
|
|
4657
|
+
if (!valid || metadata.model !== void 0 || metadata.invalidModel || metadata.complete) break;
|
|
4658
|
+
}
|
|
4659
|
+
if (valid && !eof && !metadata.complete && metadata.model === void 0 && !metadata.invalidModel) {
|
|
4660
|
+
files = await createImageJsonFiles();
|
|
4661
|
+
const file = await files.create();
|
|
4662
|
+
stagedPath = file.path;
|
|
4663
|
+
try {
|
|
4664
|
+
for (const chunk of prefix) {
|
|
4665
|
+
if (length > maxStagedBytes) throw imageJsonLimitError();
|
|
4666
|
+
await file.handle.writeFile(chunk);
|
|
4667
|
+
}
|
|
4668
|
+
prefix.length = 0;
|
|
4669
|
+
for await (const chunk of remaining()) {
|
|
4670
|
+
length += chunk.byteLength;
|
|
4671
|
+
if (length > maxStagedBytes) throw imageJsonLimitError();
|
|
4672
|
+
scan(chunk);
|
|
4673
|
+
await file.handle.writeFile(chunk);
|
|
4674
|
+
}
|
|
4675
|
+
eof = true;
|
|
4676
|
+
scan();
|
|
4677
|
+
} finally {
|
|
4678
|
+
await file.handle.close();
|
|
4679
|
+
}
|
|
4680
|
+
files.scheduleCleanup();
|
|
4681
|
+
}
|
|
4682
|
+
} catch (error) {
|
|
4683
|
+
await input.cancel(error);
|
|
4684
|
+
await files?.cleanup();
|
|
4685
|
+
if (error instanceof DuplicateImageModelError) throw new HTTPError(error.message, Response.json({ error: {
|
|
4686
|
+
message: error.message,
|
|
4687
|
+
type: "invalid_request_error"
|
|
4688
|
+
} }, { status: 400 }));
|
|
4689
|
+
throw error;
|
|
4690
|
+
}
|
|
4691
|
+
let claimed = false;
|
|
4692
|
+
const abort = () => {
|
|
4693
|
+
input.cancel().catch(() => {});
|
|
4694
|
+
files?.cleanup().catch(() => {});
|
|
4695
|
+
};
|
|
4696
|
+
request.signal.addEventListener("abort", abort, { once: true });
|
|
4697
|
+
async function* source() {
|
|
4698
|
+
if (claimed) throw new Error("Image request body already forwarded");
|
|
4699
|
+
claimed = true;
|
|
4700
|
+
try {
|
|
4701
|
+
signal.throwIfAborted();
|
|
4702
|
+
if (stagedPath) yield* readImageJsonFile(stagedPath, signal);
|
|
4703
|
+
else {
|
|
4704
|
+
yield* prefix;
|
|
4705
|
+
prefix.length = 0;
|
|
4706
|
+
if (!eof) {
|
|
4707
|
+
for await (const chunk of remaining()) {
|
|
4708
|
+
scan(chunk);
|
|
4709
|
+
yield chunk;
|
|
4710
|
+
}
|
|
4711
|
+
scan();
|
|
4712
|
+
}
|
|
4713
|
+
}
|
|
4714
|
+
} finally {
|
|
4715
|
+
request.signal.removeEventListener("abort", abort);
|
|
4716
|
+
await bytes.return(void 0).catch(() => {});
|
|
4717
|
+
await files?.cleanup();
|
|
4718
|
+
}
|
|
4719
|
+
}
|
|
4720
|
+
const cancel = async (reason) => {
|
|
4721
|
+
forwardingAbort.abort(reason);
|
|
4722
|
+
request.signal.removeEventListener("abort", abort);
|
|
4723
|
+
await input.cancel(reason);
|
|
4724
|
+
await files?.cleanup();
|
|
4725
|
+
};
|
|
4726
|
+
return {
|
|
4727
|
+
valid: valid && metadata.object && !metadata.invalidModel,
|
|
4728
|
+
model: metadata.model,
|
|
4729
|
+
originalRequest: rebuild(request, jsonByteStream(source(), cancel)),
|
|
4730
|
+
createRequest: (model) => rebuild(request, jsonByteStream(rewriteImageModel(source(), model, metadata.model === void 0), cancel), true),
|
|
4731
|
+
cleanup: cancel,
|
|
4732
|
+
stagedDirectory: files?.directory
|
|
4733
|
+
};
|
|
4734
|
+
}
|
|
4735
|
+
// src/lib/image-json-metadata.ts
|
|
4736
|
+
const MAX_METADATA_LENGTH = 65536;
|
|
4737
|
+
const OMITTED_FIELDS = /* @__PURE__ */ new Set([
|
|
4738
|
+
"data",
|
|
4739
|
+
"b64_json",
|
|
4740
|
+
"partial_image_b64"
|
|
4741
|
+
]);
|
|
4742
|
+
/** Reads JSON/SSE object metadata without retaining image payloads. */
|
|
4743
|
+
var ImageJsonMetadataReader = class {
|
|
4744
|
+
depth = 0;
|
|
4745
|
+
inString = false;
|
|
4746
|
+
escaped = false;
|
|
4747
|
+
phase = "key";
|
|
4748
|
+
keyText = "";
|
|
4749
|
+
key = "";
|
|
4750
|
+
value = null;
|
|
4751
|
+
fields = /* @__PURE__ */ new Map();
|
|
4752
|
+
metadataLength = 0;
|
|
4753
|
+
onMetadata;
|
|
4754
|
+
constructor(onMetadata) {
|
|
4755
|
+
this.onMetadata = onMetadata;
|
|
4756
|
+
}
|
|
4757
|
+
write(text) {
|
|
4758
|
+
for (const char of text) this.readCharacter(char);
|
|
4759
|
+
}
|
|
4760
|
+
readCharacter(char) {
|
|
4761
|
+
if (this.depth === 0) {
|
|
4762
|
+
if (char === "{") {
|
|
4763
|
+
this.depth = 1;
|
|
4764
|
+
this.phase = "key";
|
|
4765
|
+
this.fields.clear();
|
|
4766
|
+
this.metadataLength = 0;
|
|
4767
|
+
}
|
|
4768
|
+
return;
|
|
4769
|
+
}
|
|
4770
|
+
if (!this.inString && this.depth === 1) {
|
|
4771
|
+
if (char === "}") {
|
|
4772
|
+
if (this.phase === "value") this.finishField();
|
|
4773
|
+
this.depth = 0;
|
|
4774
|
+
this.onMetadata(Object.fromEntries(this.fields));
|
|
4775
|
+
return;
|
|
4776
|
+
}
|
|
4777
|
+
if (this.phase === "key" && char === "\"") {
|
|
4778
|
+
this.keyText = "\"";
|
|
4779
|
+
this.phase = "colon";
|
|
4780
|
+
this.inString = true;
|
|
4781
|
+
return;
|
|
4782
|
+
}
|
|
4783
|
+
if (this.phase === "colon" && char === ":") {
|
|
4784
|
+
try {
|
|
4785
|
+
this.key = JSON.parse(this.keyText);
|
|
4786
|
+
} catch {
|
|
4787
|
+
this.key = "";
|
|
4788
|
+
}
|
|
4789
|
+
this.value = OMITTED_FIELDS.has(this.key) ? null : "";
|
|
4790
|
+
this.phase = "value";
|
|
4791
|
+
return;
|
|
4792
|
+
}
|
|
4793
|
+
if (this.phase === "value" && char === ",") {
|
|
4794
|
+
this.finishField();
|
|
4795
|
+
this.phase = "key";
|
|
4796
|
+
return;
|
|
4797
|
+
}
|
|
4798
|
+
}
|
|
4799
|
+
if (this.phase === "colon" && this.keyText.length < MAX_METADATA_LENGTH) this.keyText += char;
|
|
4800
|
+
else if (this.phase === "value" && this.value !== null) {
|
|
4801
|
+
if (this.metadataLength + this.value.length < MAX_METADATA_LENGTH) this.value += char;
|
|
4802
|
+
else this.value = null;
|
|
4803
|
+
}
|
|
4804
|
+
if (this.inString) {
|
|
4805
|
+
if (this.escaped) this.escaped = false;
|
|
4806
|
+
else if (char === "\\") this.escaped = true;
|
|
4807
|
+
else if (char === "\"") this.inString = false;
|
|
4808
|
+
} else if (char === "\"") this.inString = true;
|
|
4809
|
+
else if (char === "{" || char === "[") this.depth++;
|
|
4810
|
+
else if (char === "}" || char === "]") this.depth--;
|
|
4811
|
+
}
|
|
4812
|
+
finishField() {
|
|
4813
|
+
if (this.value === null) return;
|
|
4814
|
+
try {
|
|
4815
|
+
const value = JSON.parse(this.value);
|
|
4816
|
+
this.fields.set(this.key, value);
|
|
4817
|
+
this.metadataLength += this.key.length + this.value.length;
|
|
4818
|
+
} catch {}
|
|
4819
|
+
this.value = null;
|
|
4820
|
+
}
|
|
4821
|
+
};
|
|
4822
|
+
// src/lib/token-usage/images.ts
|
|
4823
|
+
function isRecord$4(value) {
|
|
4824
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
4825
|
+
}
|
|
4826
|
+
function token(value) {
|
|
4827
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? normalizeToken(value) : void 0;
|
|
4828
|
+
}
|
|
4829
|
+
function tokenDetails(value) {
|
|
4830
|
+
if (!isRecord$4(value)) return void 0;
|
|
4831
|
+
return {
|
|
4832
|
+
image_tokens: token(value.image_tokens),
|
|
4833
|
+
text_tokens: token(value.text_tokens)
|
|
4834
|
+
};
|
|
4835
|
+
}
|
|
4836
|
+
function normalizeImagesUsage(usage, provider) {
|
|
4837
|
+
if (!isRecord$4(usage)) return {};
|
|
4838
|
+
const inputDetails = tokenDetails(usage.input_tokens_details);
|
|
4839
|
+
const outputDetails = tokenDetails(usage.output_tokens_details);
|
|
4840
|
+
return {
|
|
4841
|
+
cost: provider.toLowerCase() === "xai" ? xaiUsdTicksToCost(usage.cost_in_usd_ticks) : void 0,
|
|
4842
|
+
input_tokens: token(usage.input_tokens) ?? (inputDetails?.image_tokens ?? 0) + (inputDetails?.text_tokens ?? 0),
|
|
4843
|
+
input_tokens_details: inputDetails,
|
|
4844
|
+
output_tokens: token(usage.output_tokens) ?? (outputDetails?.image_tokens ?? 0) + (outputDetails?.text_tokens ?? 0),
|
|
4845
|
+
output_tokens_details: outputDetails,
|
|
4846
|
+
total_tokens: token(usage.total_tokens)
|
|
4847
|
+
};
|
|
4848
|
+
}
|
|
4849
|
+
/** Observes bytes as the client reads them; image data is never buffered here. */
|
|
4850
|
+
function observeImagesResponse(response, options) {
|
|
4851
|
+
if (!response.body) return createProviderProxyResponse(response);
|
|
4852
|
+
const context = requestContext.getStore();
|
|
4853
|
+
const provider = options.providerConfig;
|
|
4854
|
+
const recordUsage = createTokenUsageRecorder({
|
|
4855
|
+
source: provider.name === "github-copilot" ? "copilot" : "provider",
|
|
4856
|
+
endpoint: options.operation === "edits" ? "images_edits" : "images_generations",
|
|
4857
|
+
model: options.model,
|
|
4858
|
+
pricing: provider.models?.[options.model]?.pricing,
|
|
4859
|
+
pricingCurrency: provider.pricingCurrency,
|
|
4860
|
+
providerName: provider.name
|
|
4861
|
+
});
|
|
4862
|
+
let usage = {};
|
|
4863
|
+
let finished = false;
|
|
4864
|
+
const decoder = new TextDecoder();
|
|
4865
|
+
const metadataReader = new ImageJsonMetadataReader((metadata) => {
|
|
4866
|
+
if (context) requestContext.run(context, () => options.onMetadata(metadata));
|
|
4867
|
+
else options.onMetadata(metadata);
|
|
4868
|
+
if (response.ok) {
|
|
4869
|
+
if (isRecord$4(metadata.usage)) usage = {
|
|
4870
|
+
...usage,
|
|
4871
|
+
...normalizeImagesUsage(metadata.usage, provider.name)
|
|
4872
|
+
};
|
|
4873
|
+
if (provider.name === "github-copilot" && isRecord$4(metadata.copilot_usage)) {
|
|
4874
|
+
const totalNanoAiu = token(metadata.copilot_usage.total_nano_aiu);
|
|
4875
|
+
if (totalNanoAiu !== void 0) usage.total_nano_aiu = totalNanoAiu;
|
|
4876
|
+
}
|
|
4877
|
+
}
|
|
4878
|
+
});
|
|
4879
|
+
const finish = () => {
|
|
4880
|
+
if (finished) return;
|
|
4881
|
+
finished = true;
|
|
4882
|
+
if (context) requestContext.run(context, () => recordUsage(usage));
|
|
4883
|
+
else recordUsage(usage);
|
|
4884
|
+
};
|
|
4885
|
+
const reader = response.body.getReader();
|
|
4886
|
+
return createProviderProxyResponse(response, new ReadableStream({
|
|
4887
|
+
async pull(controller) {
|
|
4888
|
+
try {
|
|
4889
|
+
const next = await reader.read();
|
|
4890
|
+
if (next.done) {
|
|
4891
|
+
metadataReader.write(decoder.decode());
|
|
4892
|
+
finish();
|
|
4893
|
+
reader.releaseLock();
|
|
4894
|
+
controller.close();
|
|
4895
|
+
} else {
|
|
4896
|
+
metadataReader.write(decoder.decode(next.value, { stream: true }));
|
|
4897
|
+
controller.enqueue(next.value);
|
|
4898
|
+
}
|
|
4899
|
+
} catch (error) {
|
|
4900
|
+
finish();
|
|
4901
|
+
reader.releaseLock();
|
|
4902
|
+
controller.error(error);
|
|
4903
|
+
}
|
|
4904
|
+
},
|
|
4905
|
+
async cancel(reason) {
|
|
4906
|
+
finish();
|
|
4907
|
+
await reader.cancel(reason);
|
|
4908
|
+
reader.releaseLock();
|
|
4909
|
+
}
|
|
4910
|
+
}));
|
|
4911
|
+
}
|
|
4249
4912
|
// src/lib/xai-image-model.ts
|
|
4250
4913
|
/**
|
|
4251
4914
|
* Resolves one image-route model setting. Empty values keep the OpenAI
|
|
@@ -4275,32 +4938,482 @@ function getImageGenerationModel() {
|
|
|
4275
4938
|
function getImageEditModel() {
|
|
4276
4939
|
return resolveConfiguredXaiImageModel(getConfig().imageEditModel, "imageEditModel");
|
|
4277
4940
|
}
|
|
4941
|
+
// src/lib/image-json-value.ts
|
|
4942
|
+
const STRING_LIMIT = 65536;
|
|
4943
|
+
const METADATA_LIMIT = 1048576;
|
|
4944
|
+
/** Builds bounded metadata, optionally storing large decoded strings on disk. */
|
|
4945
|
+
var ImageJsonValueReader = class {
|
|
4946
|
+
value;
|
|
4947
|
+
external = [];
|
|
4948
|
+
stack = [];
|
|
4949
|
+
key = "";
|
|
4950
|
+
inKey = false;
|
|
4951
|
+
number = "";
|
|
4952
|
+
string;
|
|
4953
|
+
metadataSize = 0;
|
|
4954
|
+
files;
|
|
4955
|
+
externalize;
|
|
4956
|
+
constructor(externalize = false) {
|
|
4957
|
+
this.externalize = externalize;
|
|
4958
|
+
}
|
|
4959
|
+
charge(size) {
|
|
4960
|
+
this.metadataSize += size;
|
|
4961
|
+
if (this.metadataSize > METADATA_LIMIT) throw new SyntaxError("Image JSON metadata exceeds 1 MiB");
|
|
4962
|
+
}
|
|
4963
|
+
add(value) {
|
|
4964
|
+
this.charge(16);
|
|
4965
|
+
const frame = this.stack.at(-1);
|
|
4966
|
+
if (!frame) this.value = value;
|
|
4967
|
+
else if (Array.isArray(frame.value)) frame.value.push(value);
|
|
4968
|
+
else frame.value[frame.key] = value;
|
|
4969
|
+
}
|
|
4970
|
+
field() {
|
|
4971
|
+
const frame = this.stack.at(-1);
|
|
4972
|
+
return frame ? Array.isArray(frame.value) ? frame.field : frame.key : "";
|
|
4973
|
+
}
|
|
4974
|
+
async append(text) {
|
|
4975
|
+
const value = this.string;
|
|
4976
|
+
if (!value.external && value.text.length + text.length <= STRING_LIMIT) {
|
|
4977
|
+
value.text += text;
|
|
4978
|
+
return;
|
|
4979
|
+
}
|
|
4980
|
+
if (!this.externalize) {
|
|
4981
|
+
value.text = "[large string omitted]";
|
|
4982
|
+
value.external ??= {
|
|
4983
|
+
placeholder: value.text,
|
|
4984
|
+
path: "",
|
|
4985
|
+
mode: value.mode,
|
|
4986
|
+
first: 0,
|
|
4987
|
+
end: 0,
|
|
4988
|
+
length: 0,
|
|
4989
|
+
prefix: "",
|
|
4990
|
+
invalidBase64: false,
|
|
4991
|
+
padding: 0
|
|
4992
|
+
};
|
|
4993
|
+
return;
|
|
4994
|
+
}
|
|
4995
|
+
if (!value.external) {
|
|
4996
|
+
if (this.external.length >= 64) throw new SyntaxError("Too many large image JSON strings");
|
|
4997
|
+
this.files ??= await createImageJsonFiles();
|
|
4998
|
+
const file = await this.files.create();
|
|
4999
|
+
value.external = {
|
|
5000
|
+
placeholder: randomBytes(24).toString("hex"),
|
|
5001
|
+
path: file.path,
|
|
5002
|
+
mode: value.mode,
|
|
5003
|
+
first: -1,
|
|
5004
|
+
end: 0,
|
|
5005
|
+
length: 0,
|
|
5006
|
+
prefix: "",
|
|
5007
|
+
invalidBase64: false,
|
|
5008
|
+
padding: 0
|
|
5009
|
+
};
|
|
5010
|
+
value.handle = file.handle;
|
|
5011
|
+
this.external.push(value.external);
|
|
5012
|
+
this.track(value.text);
|
|
5013
|
+
value.pending.push(value.text);
|
|
5014
|
+
value.text = "";
|
|
5015
|
+
}
|
|
5016
|
+
this.track(text);
|
|
5017
|
+
value.pending.push(text);
|
|
5018
|
+
}
|
|
5019
|
+
track(text) {
|
|
5020
|
+
const value = this.string.external;
|
|
5021
|
+
const first = text.search(/\S/u);
|
|
5022
|
+
if (first !== -1) {
|
|
5023
|
+
if (value.first === -1) value.first = value.length + first;
|
|
5024
|
+
value.end = value.length + text.trimEnd().length;
|
|
5025
|
+
}
|
|
5026
|
+
value.length += text.length;
|
|
5027
|
+
const normalized = value.mode === "compact" ? text.replaceAll(/\s+/gu, "") : value.prefix ? text : text.trimStart();
|
|
5028
|
+
if (value.prefix.length < 256) value.prefix = (value.prefix + normalized).slice(0, 256);
|
|
5029
|
+
if (value.mode === "compact") {
|
|
5030
|
+
if (value.padding && /[^=]/u.test(normalized)) value.invalidBase64 = true;
|
|
5031
|
+
if (!/^[a-z0-9+/]*={0,2}$/iu.test(normalized)) value.invalidBase64 = true;
|
|
5032
|
+
value.padding += normalized.length - normalized.replaceAll("=", "").length;
|
|
5033
|
+
if (value.padding > 2) value.invalidBase64 = true;
|
|
5034
|
+
}
|
|
5035
|
+
}
|
|
5036
|
+
async flushString() {
|
|
5037
|
+
const value = this.string;
|
|
5038
|
+
if (value?.handle && value.pending.length) {
|
|
5039
|
+
await value.handle.writeFile(Buffer.from(value.pending.join(""), "utf16le"));
|
|
5040
|
+
value.pending = [];
|
|
5041
|
+
}
|
|
5042
|
+
}
|
|
5043
|
+
async read(tokens) {
|
|
5044
|
+
for (const token of tokens) switch (token.name) {
|
|
5045
|
+
case "startObject":
|
|
5046
|
+
case "startArray": {
|
|
5047
|
+
const field = this.field();
|
|
5048
|
+
const value = token.name === "startArray" ? [] : Object.create(null);
|
|
5049
|
+
this.add(value);
|
|
5050
|
+
this.stack.push({
|
|
5051
|
+
value,
|
|
5052
|
+
key: "",
|
|
5053
|
+
field
|
|
5054
|
+
});
|
|
5055
|
+
break;
|
|
5056
|
+
}
|
|
5057
|
+
case "endObject":
|
|
5058
|
+
case "endArray":
|
|
5059
|
+
this.stack.pop();
|
|
5060
|
+
break;
|
|
5061
|
+
case "startKey":
|
|
5062
|
+
this.inKey = true;
|
|
5063
|
+
this.key = "";
|
|
5064
|
+
break;
|
|
5065
|
+
case "endKey":
|
|
5066
|
+
this.inKey = false;
|
|
5067
|
+
this.charge(this.key.length);
|
|
5068
|
+
this.stack.at(-1).key = this.key;
|
|
5069
|
+
break;
|
|
5070
|
+
case "startString": {
|
|
5071
|
+
const field = this.field();
|
|
5072
|
+
this.string = {
|
|
5073
|
+
text: "",
|
|
5074
|
+
pending: [],
|
|
5075
|
+
mode: field === "b64_json" ? "compact" : [
|
|
5076
|
+
"image",
|
|
5077
|
+
"images",
|
|
5078
|
+
"url",
|
|
5079
|
+
"image_url"
|
|
5080
|
+
].includes(field) ? "trim" : "identity"
|
|
5081
|
+
};
|
|
5082
|
+
break;
|
|
5083
|
+
}
|
|
5084
|
+
case "stringChunk":
|
|
5085
|
+
if (this.inKey) this.key += token.value;
|
|
5086
|
+
else await this.append(token.value);
|
|
5087
|
+
break;
|
|
5088
|
+
case "endString": {
|
|
5089
|
+
await this.flushString();
|
|
5090
|
+
const value = this.string;
|
|
5091
|
+
await value.handle?.close();
|
|
5092
|
+
value.handle = void 0;
|
|
5093
|
+
if (value.external && this.externalize) {
|
|
5094
|
+
const external = value.external;
|
|
5095
|
+
external.placeholder = (/^(?:https?:\/\/|data:image\/[a-z0-9.+-]+;base64,)/iu.exec(external.prefix)?.[0] ?? "") + external.placeholder;
|
|
5096
|
+
this.add(external.first === -1 ? "" : external.placeholder);
|
|
5097
|
+
} else {
|
|
5098
|
+
this.charge(value.text.length);
|
|
5099
|
+
this.add(value.text);
|
|
5100
|
+
}
|
|
5101
|
+
this.string = void 0;
|
|
5102
|
+
break;
|
|
5103
|
+
}
|
|
5104
|
+
case "startNumber":
|
|
5105
|
+
this.number = "";
|
|
5106
|
+
break;
|
|
5107
|
+
case "numberChunk":
|
|
5108
|
+
this.number += token.value;
|
|
5109
|
+
break;
|
|
5110
|
+
case "endNumber":
|
|
5111
|
+
this.add(Number(this.number));
|
|
5112
|
+
break;
|
|
5113
|
+
case "trueValue":
|
|
5114
|
+
this.add(true);
|
|
5115
|
+
break;
|
|
5116
|
+
case "falseValue":
|
|
5117
|
+
this.add(false);
|
|
5118
|
+
break;
|
|
5119
|
+
case "nullValue": this.add(null);
|
|
5120
|
+
}
|
|
5121
|
+
await this.flushString();
|
|
5122
|
+
}
|
|
5123
|
+
async cleanup() {
|
|
5124
|
+
await this.string?.handle?.close();
|
|
5125
|
+
if (this.string) this.string.handle = void 0;
|
|
5126
|
+
await this.files?.cleanup();
|
|
5127
|
+
}
|
|
5128
|
+
scheduleCleanup() {
|
|
5129
|
+
this.files?.scheduleCleanup();
|
|
5130
|
+
}
|
|
5131
|
+
};
|
|
5132
|
+
/** Reads xAI input into a small skeleton, never assembling large image strings. */
|
|
5133
|
+
async function readExternalImageJson(request, maxBytes = IMAGE_JSON_MAX_BYTES) {
|
|
5134
|
+
const reader = new ImageJsonValueReader(true);
|
|
5135
|
+
const parser = new StreamingJsonReader();
|
|
5136
|
+
let length = 0;
|
|
5137
|
+
try {
|
|
5138
|
+
for await (const chunk of readJsonBytes(request.body, request.signal)) {
|
|
5139
|
+
length += chunk.byteLength;
|
|
5140
|
+
if (length > maxBytes) throw imageJsonLimitError();
|
|
5141
|
+
await reader.read(parser.read(chunk));
|
|
5142
|
+
}
|
|
5143
|
+
await reader.read(parser.read());
|
|
5144
|
+
reader.scheduleCleanup();
|
|
5145
|
+
return reader;
|
|
5146
|
+
} catch (error) {
|
|
5147
|
+
await reader.cleanup();
|
|
5148
|
+
throw error;
|
|
5149
|
+
}
|
|
5150
|
+
}
|
|
5151
|
+
function assertExternalImageBase64(reader, payload) {
|
|
5152
|
+
const text = JSON.stringify(payload);
|
|
5153
|
+
for (const value of reader.external) if (text.includes(value.placeholder) && value.mode === "compact" && !value.prefix.startsWith("data:image/") && value.invalidBase64) throw new SyntaxError("image b64_json is not valid base64");
|
|
5154
|
+
}
|
|
5155
|
+
/** Serializes the translated skeleton, expanding only strings retained by xAI. */
|
|
5156
|
+
function externalImageJsonStream(reader, payload, signal) {
|
|
5157
|
+
const template = JSON.stringify(payload);
|
|
5158
|
+
const pattern = new RegExp(reader.external.map((value) => value.placeholder.replaceAll(/[.*+?^${}()|[\]\\]/gu, "\\$&")).join("|"), "gu");
|
|
5159
|
+
const encode = new TextEncoder();
|
|
5160
|
+
let activeStream;
|
|
5161
|
+
async function* serialize() {
|
|
5162
|
+
try {
|
|
5163
|
+
signal.throwIfAborted();
|
|
5164
|
+
let offset = 0;
|
|
5165
|
+
for (const match of reader.external.length ? template.matchAll(pattern) : []) {
|
|
5166
|
+
signal.throwIfAborted();
|
|
5167
|
+
yield encode.encode(template.slice(offset, match.index));
|
|
5168
|
+
const value = reader.external.find((item) => item.placeholder === match[0]);
|
|
5169
|
+
const trim = value.mode !== "identity";
|
|
5170
|
+
const stream = createReadStream(value.path, {
|
|
5171
|
+
encoding: "utf16le",
|
|
5172
|
+
highWaterMark: 65536,
|
|
5173
|
+
start: trim ? value.first * 2 : 0,
|
|
5174
|
+
end: trim ? value.end * 2 - 1 : void 0
|
|
5175
|
+
});
|
|
5176
|
+
activeStream = stream;
|
|
5177
|
+
try {
|
|
5178
|
+
for await (const chunk of stream) {
|
|
5179
|
+
signal.throwIfAborted();
|
|
5180
|
+
const text = String(chunk);
|
|
5181
|
+
yield encode.encode(JSON.stringify(value.mode === "compact" ? text.replaceAll(/\s+/gu, "") : text).slice(1, -1));
|
|
5182
|
+
}
|
|
5183
|
+
} finally {
|
|
5184
|
+
stream.destroy();
|
|
5185
|
+
activeStream = void 0;
|
|
5186
|
+
}
|
|
5187
|
+
offset = match.index + match[0].length;
|
|
5188
|
+
}
|
|
5189
|
+
signal.throwIfAborted();
|
|
5190
|
+
yield encode.encode(template.slice(offset));
|
|
5191
|
+
} catch (error) {
|
|
5192
|
+
signal.throwIfAborted();
|
|
5193
|
+
throw error;
|
|
5194
|
+
} finally {
|
|
5195
|
+
await reader.cleanup();
|
|
5196
|
+
}
|
|
5197
|
+
}
|
|
5198
|
+
const abort = () => {
|
|
5199
|
+
activeStream?.destroy();
|
|
5200
|
+
reader.cleanup().catch(() => {});
|
|
5201
|
+
};
|
|
5202
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
5203
|
+
if (signal.aborted) abort();
|
|
5204
|
+
async function* body() {
|
|
5205
|
+
try {
|
|
5206
|
+
yield* serialize();
|
|
5207
|
+
} finally {
|
|
5208
|
+
signal.removeEventListener("abort", abort);
|
|
5209
|
+
}
|
|
5210
|
+
}
|
|
5211
|
+
return jsonByteStream(body(), async () => {
|
|
5212
|
+
signal.removeEventListener("abort", abort);
|
|
5213
|
+
activeStream?.destroy();
|
|
5214
|
+
await reader.cleanup();
|
|
5215
|
+
});
|
|
5216
|
+
}
|
|
5217
|
+
// src/routes/images/request-logging.ts
|
|
5218
|
+
function redactImageBase64(value, key = "") {
|
|
5219
|
+
if ([
|
|
5220
|
+
"b64_json",
|
|
5221
|
+
"base64",
|
|
5222
|
+
"image_base64"
|
|
5223
|
+
].includes(key)) return "[image base64 omitted]";
|
|
5224
|
+
if (typeof value === "string") return /^data:image\/[^,]+;base64,/iu.test(value) ? "[image base64 omitted]" : value;
|
|
5225
|
+
if (Array.isArray(value)) return value.map((item) => redactImageBase64(item));
|
|
5226
|
+
if (value !== null && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([field, item]) => [field, redactImageBase64(item, field)]));
|
|
5227
|
+
return value;
|
|
5228
|
+
}
|
|
5229
|
+
async function editRequestBody(request) {
|
|
5230
|
+
try {
|
|
5231
|
+
const cloned = request.clone();
|
|
5232
|
+
if (request.headers.get("content-type")?.includes("multipart/form-data")) {
|
|
5233
|
+
const fields = [];
|
|
5234
|
+
for (const [name, value] of await cloned.formData()) fields.push({
|
|
5235
|
+
name,
|
|
5236
|
+
value: typeof value === "string" ? redactImageBase64(value, name) : {
|
|
5237
|
+
filename: value.name,
|
|
5238
|
+
mime_type: value.type,
|
|
5239
|
+
size: value.size
|
|
5240
|
+
}
|
|
5241
|
+
});
|
|
5242
|
+
return fields;
|
|
5243
|
+
}
|
|
5244
|
+
return redactImageBase64(await cloned.json());
|
|
5245
|
+
} catch {
|
|
5246
|
+
return "[unavailable JSON body]";
|
|
5247
|
+
}
|
|
5248
|
+
}
|
|
5249
|
+
async function logImageEditRequest(logger, label, request, extra = {}, debug = debugJsonAsync) {
|
|
5250
|
+
if (!state.verbose) return request;
|
|
5251
|
+
if (!request.headers.get("content-type")?.includes("multipart/form-data")) return logImageJsonRequest(logger, label, request, extra, debug);
|
|
5252
|
+
await debug(logger, label, async () => ({
|
|
5253
|
+
contentType: request.headers.get("content-type"),
|
|
5254
|
+
contentLength: request.headers.get("content-length"),
|
|
5255
|
+
...extra,
|
|
5256
|
+
body: await editRequestBody(request)
|
|
5257
|
+
}));
|
|
5258
|
+
return request;
|
|
5259
|
+
}
|
|
5260
|
+
/** Observes the same chunks sent upstream; debug logging never tees JSON bodies. */
|
|
5261
|
+
function logImageJsonRequest(logger, label, request, extra, debug) {
|
|
5262
|
+
const value = new ImageJsonValueReader();
|
|
5263
|
+
const parser = new StreamingJsonReader();
|
|
5264
|
+
const input = createJsonByteSource(request.body, request.signal);
|
|
5265
|
+
let valid = true;
|
|
5266
|
+
async function* observe() {
|
|
5267
|
+
for await (const chunk of input.bytes) {
|
|
5268
|
+
if (valid) try {
|
|
5269
|
+
await value.read(parser.read(chunk));
|
|
5270
|
+
} catch {
|
|
5271
|
+
valid = false;
|
|
5272
|
+
}
|
|
5273
|
+
yield chunk;
|
|
5274
|
+
}
|
|
5275
|
+
if (valid) try {
|
|
5276
|
+
await value.read(parser.read());
|
|
5277
|
+
} catch {
|
|
5278
|
+
valid = false;
|
|
5279
|
+
}
|
|
5280
|
+
await debug(logger, label, () => Promise.resolve({
|
|
5281
|
+
contentType: request.headers.get("content-type"),
|
|
5282
|
+
contentLength: request.headers.get("content-length"),
|
|
5283
|
+
...extra,
|
|
5284
|
+
body: valid ? redactImageBase64(value.value) : "[unavailable JSON body]"
|
|
5285
|
+
}));
|
|
5286
|
+
}
|
|
5287
|
+
return new Request(request.url, {
|
|
5288
|
+
method: request.method,
|
|
5289
|
+
headers: request.headers,
|
|
5290
|
+
signal: request.signal,
|
|
5291
|
+
body: jsonByteStream(observe(), (reason) => input.cancel(reason)),
|
|
5292
|
+
duplex: "half"
|
|
5293
|
+
});
|
|
5294
|
+
}
|
|
5295
|
+
DEFAULT_IMAGE_MODELS["github-copilot"];
|
|
5296
|
+
const COPILOT_CLIENT_VERSION = "copilot/1.0.103-unstable.r40031404408.gba6c003";
|
|
5297
|
+
const COPILOT_IMAGES_TIMEOUT_MS = 9e5;
|
|
5298
|
+
const copilotImagesDispatcher = createTimeoutDispatcher(COPILOT_IMAGES_TIMEOUT_MS);
|
|
5299
|
+
function resolveCopilotImagesUrl(requestUrl, operation) {
|
|
5300
|
+
const upstreamUrl = new URL(`${copilotBaseUrl(state).replace(/\/+$/u, "")}/v1/images/${operation}`);
|
|
5301
|
+
if (upstreamUrl.hostname === "api.githubcopilot.com") upstreamUrl.hostname = `api.${state.accountType}.githubcopilot.com`;
|
|
5302
|
+
upstreamUrl.search = new URL(requestUrl).search;
|
|
5303
|
+
return upstreamUrl.toString();
|
|
5304
|
+
}
|
|
5305
|
+
async function forwardCopilotImages(request, operation) {
|
|
5306
|
+
assertGitHubCopilotEnabled();
|
|
5307
|
+
if (!state.githubToken) throw new HTTPError("GitHub token not found", Response.json({ error: {
|
|
5308
|
+
message: "GitHub token not found",
|
|
5309
|
+
type: "authentication_error"
|
|
5310
|
+
} }, { status: 401 }));
|
|
5311
|
+
const init = {
|
|
5312
|
+
method: "POST",
|
|
5313
|
+
headers: {
|
|
5314
|
+
authorization: `Bearer ${state.githubToken}`,
|
|
5315
|
+
"content-type": "application/json",
|
|
5316
|
+
accept: request.headers.get("accept") ?? "application/json",
|
|
5317
|
+
"copilot-integration-id": "vscode-chat",
|
|
5318
|
+
"copilot-harness-id": "copilot-sdk",
|
|
5319
|
+
"editor-version": COPILOT_CLIENT_VERSION,
|
|
5320
|
+
"user-agent": `${COPILOT_CLIENT_VERSION} (${process.platform} unknown) term/unknown client/vscode-agent-host`,
|
|
5321
|
+
"openai-intent": "conversation-agent",
|
|
5322
|
+
"x-initiator": "agent",
|
|
5323
|
+
"x-github-api-version": "2026-08-01",
|
|
5324
|
+
"x-client-machine-id": state.vsCodeDeviceId,
|
|
5325
|
+
"x-interaction-id": randomUUID()
|
|
5326
|
+
},
|
|
5327
|
+
body: request.body,
|
|
5328
|
+
duplex: "half",
|
|
5329
|
+
signal: AbortSignal.any([request.signal, AbortSignal.timeout(COPILOT_IMAGES_TIMEOUT_MS)])
|
|
5330
|
+
};
|
|
5331
|
+
const upstreamUrl = resolveCopilotImagesUrl(request.url, operation);
|
|
5332
|
+
const response = typeof Bun !== "undefined" ? await fetch(upstreamUrl, init) : await fetch$1(upstreamUrl, {
|
|
5333
|
+
...init,
|
|
5334
|
+
dispatcher: copilotImagesDispatcher
|
|
5335
|
+
});
|
|
5336
|
+
logCopilotRateLimits(response.headers);
|
|
5337
|
+
return response;
|
|
5338
|
+
}
|
|
4278
5339
|
// src/routes/images/forward-provider-images.ts
|
|
4279
5340
|
/**
|
|
4280
5341
|
* Forwards an images request to a configured provider with debug logging.
|
|
4281
5342
|
* Shared by the top-level images routes (model mapping) and the
|
|
4282
5343
|
* provider-scoped images routes so both log and proxy identically.
|
|
4283
5344
|
*/
|
|
4284
|
-
async function forwardProviderImagesWithLogging(providerConfig, request, operation, { logger, provider }) {
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
5345
|
+
async function forwardProviderImagesWithLogging(providerConfig, request, operation, { logger, provider, model }) {
|
|
5346
|
+
let actualModel = model;
|
|
5347
|
+
if (actualModel === void 0 && !request.headers.get("content-type")?.includes("multipart/form-data")) {
|
|
5348
|
+
const parsed = await prepareImageJsonRequest(request);
|
|
5349
|
+
actualModel = parsed.valid ? parsed.model : "unknown";
|
|
5350
|
+
request = parsed.originalRequest;
|
|
5351
|
+
}
|
|
5352
|
+
actualModel ??= "unknown";
|
|
5353
|
+
request = await logImageEditRequest(logger, `provider.images.${operation}.request`, request, { provider });
|
|
5354
|
+
let upstreamResponse;
|
|
5355
|
+
try {
|
|
5356
|
+
upstreamResponse = providerConfig.name === "github-copilot" ? await forwardCopilotImages(request, operation) : await forwardProviderImages(providerConfig, request, operation);
|
|
5357
|
+
} catch (error) {
|
|
5358
|
+
await request.body?.cancel().catch(() => {});
|
|
5359
|
+
throw error;
|
|
5360
|
+
}
|
|
4295
5361
|
debugJson(logger, `provider.images.${operation}.response`, {
|
|
4296
5362
|
contentType: upstreamResponse.headers.get("content-type"),
|
|
4297
5363
|
contentLength: upstreamResponse.headers.get("content-length"),
|
|
4298
5364
|
provider,
|
|
4299
5365
|
statusCode: upstreamResponse.status
|
|
4300
5366
|
});
|
|
4301
|
-
return
|
|
5367
|
+
return observeImagesResponse(upstreamResponse, {
|
|
5368
|
+
model: actualModel,
|
|
5369
|
+
operation,
|
|
5370
|
+
providerConfig,
|
|
5371
|
+
onMetadata: (metadata) => debugJson(logger, `provider.images.${operation}.response.body`, {
|
|
5372
|
+
provider,
|
|
5373
|
+
...metadata
|
|
5374
|
+
})
|
|
5375
|
+
});
|
|
5376
|
+
}
|
|
5377
|
+
async function forwardCopilotImagesWithLogging(request, operation, context) {
|
|
5378
|
+
const config = getRawProviderConfig(GITHUB_COPILOT_PROVIDER);
|
|
5379
|
+
return await forwardProviderImagesWithLogging({
|
|
5380
|
+
name: GITHUB_COPILOT_PROVIDER,
|
|
5381
|
+
type: "openai-compatible",
|
|
5382
|
+
authType: "oauth2",
|
|
5383
|
+
apiKey: "",
|
|
5384
|
+
baseUrl: "",
|
|
5385
|
+
models: config?.models,
|
|
5386
|
+
pricingCurrency: config?.pricingCurrency
|
|
5387
|
+
}, request, operation, context);
|
|
5388
|
+
}
|
|
5389
|
+
/** Forwards translated xAI requests and normalizes OpenAI-compatible output. */
|
|
5390
|
+
async function forwardXaiImagesWithLogging(providerConfig, request, operation, payload, { logger, provider }, body) {
|
|
5391
|
+
return await ensureImageResponseB64Json(await forwardProviderImagesWithLogging(providerConfig, new Request(request.url, {
|
|
5392
|
+
body: body ?? new TextEncoder().encode(JSON.stringify(payload)),
|
|
5393
|
+
duplex: "half",
|
|
5394
|
+
headers: {
|
|
5395
|
+
accept: "application/json",
|
|
5396
|
+
"content-type": "application/json"
|
|
5397
|
+
},
|
|
5398
|
+
method: "POST",
|
|
5399
|
+
signal: request.signal
|
|
5400
|
+
}), operation, {
|
|
5401
|
+
logger,
|
|
5402
|
+
provider,
|
|
5403
|
+
model: typeof payload.model === "string" ? payload.model : "unknown"
|
|
5404
|
+
}), void 0, {
|
|
5405
|
+
clientSignal: request.signal,
|
|
5406
|
+
onImageUrl: (url) => {
|
|
5407
|
+
logger.info("images.xai.output.url", JSON.stringify({
|
|
5408
|
+
operation,
|
|
5409
|
+
url
|
|
5410
|
+
}));
|
|
5411
|
+
},
|
|
5412
|
+
responseFormat: payload.response_format === "url" ? "url" : "b64_json"
|
|
5413
|
+
});
|
|
4302
5414
|
}
|
|
4303
5415
|
// src/services/codex/images.ts
|
|
5416
|
+
const DEFAULT_CODEX_IMAGE_MODEL = DEFAULT_IMAGE_MODELS.codex;
|
|
4304
5417
|
const CODEX_IMAGES_TIMEOUT_MS = 9e5;
|
|
4305
5418
|
const codexImagesDispatcher = createTimeoutDispatcher(CODEX_IMAGES_TIMEOUT_MS);
|
|
4306
5419
|
function resolveCodexImagesUrl(requestUrl, operation) {
|
|
@@ -4326,12 +5439,66 @@ async function forwardCodexImages(request, operation) {
|
|
|
4326
5439
|
dispatcher: codexImagesDispatcher
|
|
4327
5440
|
});
|
|
4328
5441
|
}
|
|
5442
|
+
// src/routes/images/xai-json-request.ts
|
|
5443
|
+
/** Existing translators validate a bounded skeleton; large strings stay on disk. */
|
|
5444
|
+
async function translateXaiImageJsonRequest(request, operation, model) {
|
|
5445
|
+
let json;
|
|
5446
|
+
try {
|
|
5447
|
+
json = await readExternalImageJson(request);
|
|
5448
|
+
} catch (error) {
|
|
5449
|
+
if (error instanceof HTTPError || request.signal.aborted) throw error;
|
|
5450
|
+
throw new XaiImagesRequestError(operation === "edits" ? "Image edit body must be JSON" : "Image generation body must be JSON");
|
|
5451
|
+
}
|
|
5452
|
+
try {
|
|
5453
|
+
const payload = operation === "edits" ? translateXaiImageEditJson(json.value, model) : translateXaiImageGeneration(json.value, model);
|
|
5454
|
+
try {
|
|
5455
|
+
assertExternalImageBase64(json, payload);
|
|
5456
|
+
} catch {
|
|
5457
|
+
throw new XaiImagesRequestError("image b64_json is not valid base64");
|
|
5458
|
+
}
|
|
5459
|
+
const input = json.value;
|
|
5460
|
+
return {
|
|
5461
|
+
payload,
|
|
5462
|
+
body: externalImageJsonStream(json, payload, request.signal),
|
|
5463
|
+
requestedModel: typeof input.model === "string" ? input.model : model
|
|
5464
|
+
};
|
|
5465
|
+
} catch (error) {
|
|
5466
|
+
await json.cleanup();
|
|
5467
|
+
throw error;
|
|
5468
|
+
}
|
|
5469
|
+
}
|
|
5470
|
+
// src/routes/images/default-model.ts
|
|
5471
|
+
/** Resolve the three supported image models before sending the request. */
|
|
5472
|
+
async function resolveImageRequestModel(requestedModel, options = {}) {
|
|
5473
|
+
if (options.provider) {
|
|
5474
|
+
const model = getDefaultImageModel(options.provider);
|
|
5475
|
+
if (model) return model;
|
|
5476
|
+
}
|
|
5477
|
+
const availability = /* @__PURE__ */ new Map();
|
|
5478
|
+
const isAvailable = (provider) => {
|
|
5479
|
+
let pending = availability.get(provider);
|
|
5480
|
+
if (!pending) {
|
|
5481
|
+
pending = provider === "github-copilot" ? Promise.resolve((options.isGitHubCopilotAvailable ?? isGitHubCopilotAvailable)()) : (options.resolveProviderConfig ?? resolveProviderConfig)(provider).then((config) => config !== null);
|
|
5482
|
+
availability.set(provider, pending);
|
|
5483
|
+
}
|
|
5484
|
+
return pending;
|
|
5485
|
+
};
|
|
5486
|
+
const requested = requestedModel?.trim();
|
|
5487
|
+
const selected = IMAGE_PROVIDER_PRIORITY.find((provider) => requested === DEFAULT_IMAGE_MODELS[provider] || requested === `${provider}/${DEFAULT_IMAGE_MODELS[provider]}`);
|
|
5488
|
+
if (selected && await isAvailable(selected)) return `${selected}/${DEFAULT_IMAGE_MODELS[selected]}`;
|
|
5489
|
+
for (const provider of IMAGE_PROVIDER_PRIORITY) if (await isAvailable(provider)) return `${provider}/${DEFAULT_IMAGE_MODELS[provider]}`;
|
|
5490
|
+
throw new HTTPError("No enabled image provider is available", Response.json({ error: {
|
|
5491
|
+
message: "No enabled image provider is available",
|
|
5492
|
+
type: "invalid_request_error"
|
|
5493
|
+
} }, { status: 404 }));
|
|
5494
|
+
}
|
|
4329
5495
|
// src/routes/images/shared.ts
|
|
4330
5496
|
const logger$15 = createHandlerLogger("images-handler");
|
|
4331
5497
|
const imageRouteDependencies = {
|
|
4332
5498
|
debugJsonAsync,
|
|
4333
5499
|
getImageEditModel,
|
|
4334
5500
|
getImageGenerationModel,
|
|
5501
|
+
isGitHubCopilotAvailable,
|
|
4335
5502
|
resolveMappedModel,
|
|
4336
5503
|
resolveProviderConfig
|
|
4337
5504
|
};
|
|
@@ -4350,106 +5517,91 @@ function getContentMetadata(headers) {
|
|
|
4350
5517
|
};
|
|
4351
5518
|
}
|
|
4352
5519
|
/**
|
|
4353
|
-
* Snapshots generated request headers before consuming the body.
|
|
4354
|
-
*/
|
|
4355
|
-
function snapshotRequestHeaders(request) {
|
|
4356
|
-
return new Headers(request.headers);
|
|
4357
|
-
}
|
|
4358
|
-
/**
|
|
4359
|
-
* Rebuilds a request from primitives instead of cloning: under Node runtimes
|
|
4360
|
-
* srvx wraps incoming requests in a proxy class whose prototype chain
|
|
4361
|
-
* satisfies `instanceof Request` without the native internals, so
|
|
4362
|
-
* `new Request(request)` throws.
|
|
4363
|
-
*/
|
|
4364
|
-
function createForwardRequest(request, requestHeaders, body) {
|
|
4365
|
-
const init = {
|
|
4366
|
-
body,
|
|
4367
|
-
duplex: "half",
|
|
4368
|
-
headers: requestHeaders,
|
|
4369
|
-
method: request.method,
|
|
4370
|
-
signal: request.signal
|
|
4371
|
-
};
|
|
4372
|
-
return new Request(request.url, init);
|
|
4373
|
-
}
|
|
4374
|
-
function createJsonImagesRequest(request, requestHeaders, payload, model) {
|
|
4375
|
-
const headers = new Headers(requestHeaders);
|
|
4376
|
-
headers.delete("content-length");
|
|
4377
|
-
return createForwardRequest(request, headers, new TextEncoder().encode(JSON.stringify({
|
|
4378
|
-
...payload,
|
|
4379
|
-
model
|
|
4380
|
-
})));
|
|
4381
|
-
}
|
|
4382
|
-
/**
|
|
4383
5520
|
* Parses an image request body as JSON, retaining the original bytes for
|
|
4384
5521
|
* unchanged forwarding while allowing model mapping to rebuild the payload.
|
|
4385
5522
|
* Rebuilt bodies stay binary so Node does not synthesize a text/plain content type.
|
|
4386
5523
|
*/
|
|
4387
5524
|
async function parseJsonImagesRequest(request) {
|
|
4388
|
-
const
|
|
4389
|
-
|
|
4390
|
-
const originalRequest = createForwardRequest(request, requestHeaders, body);
|
|
4391
|
-
let payload;
|
|
4392
|
-
try {
|
|
4393
|
-
const bodyText = new TextDecoder("utf-8", { fatal: true }).decode(body);
|
|
4394
|
-
payload = JSON.parse(bodyText);
|
|
4395
|
-
} catch {
|
|
4396
|
-
return originalRequest;
|
|
4397
|
-
}
|
|
4398
|
-
if (payload === null || typeof payload !== "object" || Array.isArray(payload) || typeof payload.model !== "string") return originalRequest;
|
|
4399
|
-
return {
|
|
4400
|
-
createRequest: (mappedModel) => createJsonImagesRequest(request, requestHeaders, payload, mappedModel),
|
|
4401
|
-
model: payload.model,
|
|
4402
|
-
originalRequest
|
|
4403
|
-
};
|
|
5525
|
+
const parsed = await prepareImageJsonRequest(request);
|
|
5526
|
+
return parsed.valid ? parsed : parsed.originalRequest;
|
|
4404
5527
|
}
|
|
4405
5528
|
/**
|
|
4406
5529
|
* Handles Codex images proxying. Pass a resolvedProviderConfig when the
|
|
4407
5530
|
* caller already resolved the codex provider to avoid a second resolve, and
|
|
4408
5531
|
* pass a request when model mapping rebuilt the request body.
|
|
4409
5532
|
*/
|
|
4410
|
-
async function handleCodexImages(c, operation, resolvedProviderConfig, request = c.req.raw) {
|
|
5533
|
+
async function handleCodexImages(c, operation, resolvedProviderConfig, request = c.req.raw, model) {
|
|
4411
5534
|
try {
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
5535
|
+
const codexProviderConfig = resolvedProviderConfig ?? await imageRouteDependencies.resolveProviderConfig("codex");
|
|
5536
|
+
if (!codexProviderConfig) {
|
|
5537
|
+
await request.body?.cancel().catch(() => {});
|
|
5538
|
+
return c.json({ error: {
|
|
5539
|
+
message: "Provider 'codex' not found or disabled",
|
|
5540
|
+
type: "invalid_request_error"
|
|
5541
|
+
} }, 404);
|
|
5542
|
+
}
|
|
5543
|
+
let actualModel = model;
|
|
5544
|
+
if (actualModel === void 0 && !request.headers.get("content-type")?.includes("multipart/form-data")) {
|
|
5545
|
+
const parsed = await prepareImageJsonRequest(request);
|
|
5546
|
+
actualModel = parsed.valid ? parsed.model ?? DEFAULT_CODEX_IMAGE_MODEL : "unknown";
|
|
5547
|
+
request = parsed.originalRequest;
|
|
5548
|
+
}
|
|
5549
|
+
actualModel ??= "unknown";
|
|
5550
|
+
request = await logImageEditRequest(logger$15, `images.${operation}.codex.request`, request, {}, imageRouteDependencies.debugJsonAsync);
|
|
4418
5551
|
const upstreamResponse = await forwardCodexImages(request, operation);
|
|
4419
5552
|
debugJson(logger$15, `images.${operation}.codex.response`, {
|
|
4420
5553
|
...getContentMetadata(upstreamResponse.headers),
|
|
4421
5554
|
statusCode: upstreamResponse.status
|
|
4422
5555
|
});
|
|
4423
|
-
return
|
|
5556
|
+
return observeImagesResponse(upstreamResponse, {
|
|
5557
|
+
model: actualModel,
|
|
5558
|
+
operation,
|
|
5559
|
+
providerConfig: codexProviderConfig,
|
|
5560
|
+
onMetadata: (metadata) => debugJson(logger$15, `images.${operation}.codex.response.body`, metadata)
|
|
5561
|
+
});
|
|
4424
5562
|
} catch (error) {
|
|
5563
|
+
await request.body?.cancel().catch(() => {});
|
|
4425
5564
|
logger$15.error(`images.${operation}.codex.error`, { error });
|
|
4426
5565
|
return await forwardError(c, error);
|
|
4427
5566
|
}
|
|
4428
5567
|
}
|
|
4429
5568
|
/**
|
|
4430
|
-
*
|
|
4431
|
-
*
|
|
5569
|
+
* Applies model mappings and resolves unknown or absent models to an available
|
|
5570
|
+
* provider's image default before dispatching.
|
|
4432
5571
|
*/
|
|
4433
5572
|
async function routeImagesRequest(c, operation, parsed) {
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
return await forwardProviderImagesWithLogging(providerConfig, request, operation, {
|
|
4444
|
-
logger: logger$15,
|
|
4445
|
-
provider: providerModelAlias.provider
|
|
4446
|
-
});
|
|
4447
|
-
}
|
|
4448
|
-
consola.debug(`Provider '${providerModelAlias.provider}' not found or disabled; forwarding the original model to Codex`);
|
|
4449
|
-
return await handleCodexImages(c, operation, void 0, parsed.originalRequest ?? await parsed.createRequest(requestedModel));
|
|
5573
|
+
let resolvedModel;
|
|
5574
|
+
try {
|
|
5575
|
+
resolvedModel = await resolveImageRequestModel(parsed.model?.trim() ? imageRouteDependencies.resolveMappedModel(parsed.model.trim()) : void 0, {
|
|
5576
|
+
resolveProviderConfig: imageRouteDependencies.resolveProviderConfig,
|
|
5577
|
+
isGitHubCopilotAvailable: imageRouteDependencies.isGitHubCopilotAvailable
|
|
5578
|
+
});
|
|
5579
|
+
} catch (error) {
|
|
5580
|
+
await parsed.originalRequest?.body?.cancel().catch(() => {});
|
|
5581
|
+
throw error;
|
|
4450
5582
|
}
|
|
4451
|
-
|
|
4452
|
-
|
|
5583
|
+
const alias = parseProviderModelAlias(resolvedModel);
|
|
5584
|
+
if (!alias) throw new Error("Invalid resolved image model");
|
|
5585
|
+
consola.debug(`Resolved image model: ${parsed.model ?? "(default)"} -> ${resolvedModel}`);
|
|
5586
|
+
const request = parsed.originalRequest && (alias.provider === "xai" || parsed.model === alias.model) ? parsed.originalRequest : await parsed.createRequest(alias.model, alias.provider);
|
|
5587
|
+
if (alias.provider === "github-copilot") return await forwardCopilotImagesWithLogging(request, operation, {
|
|
5588
|
+
logger: logger$15,
|
|
5589
|
+
...alias
|
|
5590
|
+
});
|
|
5591
|
+
const providerConfig = await imageRouteDependencies.resolveProviderConfig(alias.provider);
|
|
5592
|
+
if (!providerConfig) {
|
|
5593
|
+
await request.body?.cancel().catch(() => {});
|
|
5594
|
+
throw new HTTPError(`Provider '${alias.provider}' not found or disabled`, Response.json({ error: {
|
|
5595
|
+
message: `Provider '${alias.provider}' not found or disabled`,
|
|
5596
|
+
type: "invalid_request_error"
|
|
5597
|
+
} }, { status: 404 }));
|
|
5598
|
+
}
|
|
5599
|
+
if (alias.provider === "codex") return await handleCodexImages(c, operation, providerConfig, request, alias.model);
|
|
5600
|
+
const translated = await translateXaiImageJsonRequest(request, operation, alias.model);
|
|
5601
|
+
return await forwardXaiImagesWithLogging(providerConfig, request, operation, translated.payload, {
|
|
5602
|
+
logger: logger$15,
|
|
5603
|
+
...alias
|
|
5604
|
+
}, translated.body);
|
|
4453
5605
|
}
|
|
4454
5606
|
// src/routes/images/xai-handler.ts
|
|
4455
5607
|
function xaiImagesRequestErrorResponse(c, error) {
|
|
@@ -4459,50 +5611,28 @@ function xaiImagesRequestErrorResponse(c, error) {
|
|
|
4459
5611
|
type: "invalid_request_error"
|
|
4460
5612
|
} }, 400);
|
|
4461
5613
|
}
|
|
4462
|
-
async function
|
|
4463
|
-
try {
|
|
4464
|
-
const body = new TextDecoder("utf-8", { fatal: true }).decode(new Uint8Array(await request.arrayBuffer()));
|
|
4465
|
-
return JSON.parse(body);
|
|
4466
|
-
} catch {
|
|
4467
|
-
throw new XaiImagesRequestError(message);
|
|
4468
|
-
}
|
|
4469
|
-
}
|
|
4470
|
-
async function forwardTranslatedXaiImages(c, operation, payload) {
|
|
5614
|
+
async function forwardTranslatedXaiImages(c, operation, payload, body) {
|
|
4471
5615
|
const providerConfig = await imageRouteDependencies.resolveProviderConfig("xai");
|
|
4472
|
-
if (!providerConfig)
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
}
|
|
4478
|
-
|
|
4479
|
-
return await
|
|
4480
|
-
body: JSON.stringify(payload),
|
|
4481
|
-
headers: {
|
|
4482
|
-
accept: "application/json",
|
|
4483
|
-
"content-type": "application/json"
|
|
4484
|
-
},
|
|
4485
|
-
method: "POST",
|
|
4486
|
-
signal: request.signal
|
|
4487
|
-
}), operation, {
|
|
5616
|
+
if (!providerConfig) {
|
|
5617
|
+
await body?.cancel();
|
|
5618
|
+
return c.json({ error: {
|
|
5619
|
+
message: "Provider 'xai' not found or disabled",
|
|
5620
|
+
type: "invalid_request_error"
|
|
5621
|
+
} }, 404);
|
|
5622
|
+
}
|
|
5623
|
+
return await forwardXaiImagesWithLogging(providerConfig, c.req.raw, operation, payload, {
|
|
4488
5624
|
logger: logger$15,
|
|
4489
5625
|
provider: "xai"
|
|
4490
|
-
}
|
|
4491
|
-
clientSignal: request.signal,
|
|
4492
|
-
onImageUrl: (url) => {
|
|
4493
|
-
logger$15.info("images.xai.output.url", JSON.stringify({
|
|
4494
|
-
operation,
|
|
4495
|
-
url
|
|
4496
|
-
}));
|
|
4497
|
-
},
|
|
4498
|
-
responseFormat: payload.response_format === "url" ? "url" : "b64_json"
|
|
4499
|
-
});
|
|
5626
|
+
}, body);
|
|
4500
5627
|
}
|
|
4501
5628
|
async function handleXaiImageGeneration(c, model) {
|
|
4502
|
-
|
|
5629
|
+
const translated = await translateXaiImageJsonRequest(c.req.raw, "generations", model);
|
|
5630
|
+
return await forwardTranslatedXaiImages(c, "generations", translated.payload, translated.body);
|
|
4503
5631
|
}
|
|
4504
5632
|
async function handleXaiImageEdit(c, model, multipartPayload) {
|
|
4505
|
-
return await forwardTranslatedXaiImages(c, "edits", translateXaiImageEditJson(multipartPayload
|
|
5633
|
+
if (multipartPayload) return await forwardTranslatedXaiImages(c, "edits", translateXaiImageEditJson(multipartPayload, model));
|
|
5634
|
+
const translated = await translateXaiImageJsonRequest(c.req.raw, "edits", model);
|
|
5635
|
+
return await forwardTranslatedXaiImages(c, "edits", translated.payload, translated.body);
|
|
4506
5636
|
}
|
|
4507
5637
|
// src/routes/images/edits-handler.ts
|
|
4508
5638
|
async function handleImagesEdits(c) {
|
|
@@ -4517,7 +5647,6 @@ async function handleImagesEdits(c) {
|
|
|
4517
5647
|
const multipart = await parseMultipartImageEdit(c.req.raw);
|
|
4518
5648
|
if (multipart) try {
|
|
4519
5649
|
if (configuredModel.status === "xai") return await handleXaiImageEdit(c, configuredModel.model, await multipart.readPayload());
|
|
4520
|
-
if (multipart.model === void 0) return await handleCodexImages(c, "edits", void 0, await multipart.createRequest(void 0));
|
|
4521
5650
|
return await routeImagesRequest(c, "edits", {
|
|
4522
5651
|
model: multipart.model,
|
|
4523
5652
|
createRequest: (model, provider) => multipart.createRequest(model, provider)
|
|
@@ -4530,7 +5659,7 @@ async function handleImagesEdits(c) {
|
|
|
4530
5659
|
}
|
|
4531
5660
|
if (configuredModel.status === "xai") return await handleXaiImageEdit(c, configuredModel.model);
|
|
4532
5661
|
const parsed = await parseJsonImagesRequest(c.req.raw);
|
|
4533
|
-
if (parsed instanceof Request) return await handleCodexImages(c, "edits", void 0, parsed);
|
|
5662
|
+
if (parsed instanceof Request) return await handleCodexImages(c, "edits", void 0, parsed, "unknown");
|
|
4534
5663
|
return await routeImagesRequest(c, "edits", parsed);
|
|
4535
5664
|
} catch (error) {
|
|
4536
5665
|
const invalidRequest = imageEditRequestErrorResponse(c, error);
|
|
@@ -4549,7 +5678,7 @@ async function handleImagesGenerations(c) {
|
|
|
4549
5678
|
} }, 400);
|
|
4550
5679
|
if (configuredModel.status === "xai") return await handleXaiImageGeneration(c, configuredModel.model);
|
|
4551
5680
|
const parsed = await parseJsonImagesRequest(c.req.raw);
|
|
4552
|
-
if (parsed instanceof Request) return await handleCodexImages(c, "generations", void 0, parsed);
|
|
5681
|
+
if (parsed instanceof Request) return await handleCodexImages(c, "generations", void 0, parsed, "unknown");
|
|
4553
5682
|
return await routeImagesRequest(c, "generations", parsed);
|
|
4554
5683
|
} catch (error) {
|
|
4555
5684
|
const invalidRequest = xaiImagesRequestErrorResponse(c, error);
|
|
@@ -6705,9 +7834,9 @@ const mapResponsesUsage = (response) => {
|
|
|
6705
7834
|
...cacheWriteTokens !== void 0 && { cache_creation_input_tokens: cacheWriteTokens }
|
|
6706
7835
|
};
|
|
6707
7836
|
};
|
|
6708
|
-
const isRecord$
|
|
6709
|
-
const isResponseOutputText = (block) => isRecord$
|
|
6710
|
-
const isResponseOutputRefusal = (block) => isRecord$
|
|
7837
|
+
const isRecord$3 = (value) => typeof value === "object" && value !== null;
|
|
7838
|
+
const isResponseOutputText = (block) => isRecord$3(block) && "type" in block && block.type === "output_text";
|
|
7839
|
+
const isResponseOutputRefusal = (block) => isRecord$3(block) && "type" in block && block.type === "refusal";
|
|
6711
7840
|
const convertToolResultContent = (content) => {
|
|
6712
7841
|
if (typeof content === "string") return content;
|
|
6713
7842
|
if (Array.isArray(content)) {
|
|
@@ -7460,7 +8589,7 @@ function resolveToolDescriptor(registry, alias) {
|
|
|
7460
8589
|
};
|
|
7461
8590
|
}
|
|
7462
8591
|
function decodeCustomToolInput(input) {
|
|
7463
|
-
if (isRecord$
|
|
8592
|
+
if (isRecord$2(input) && typeof input.input === "string") return input.input;
|
|
7464
8593
|
return typeof input === "string" ? input : JSON.stringify(input ?? {});
|
|
7465
8594
|
}
|
|
7466
8595
|
function toResponseId(messageId) {
|
|
@@ -7541,7 +8670,7 @@ function isNamespaceTool(tool) {
|
|
|
7541
8670
|
return getStringField$1(tool, "type") === "namespace";
|
|
7542
8671
|
}
|
|
7543
8672
|
function registerTool(tool, registry, strictCustomTools, namespaces = []) {
|
|
7544
|
-
if (!isRecord$
|
|
8673
|
+
if (!isRecord$2(tool)) throw new ResponsesMessagesTranslationError("Invalid Responses tool definition");
|
|
7545
8674
|
const type = getStringField$1(tool, "type");
|
|
7546
8675
|
if (type === "namespace") {
|
|
7547
8676
|
const namespaceTool = tool;
|
|
@@ -7558,7 +8687,7 @@ function registerTool(tool, registry, strictCustomTools, namespaces = []) {
|
|
|
7558
8687
|
name,
|
|
7559
8688
|
namespace: namespaces.length > 0 ? namespaces.join(".") : void 0,
|
|
7560
8689
|
description: getOptionalStringField(tool, "description"),
|
|
7561
|
-
parameters: type === "function" && isRecord$
|
|
8690
|
+
parameters: type === "function" && isRecord$2(tool.parameters) ? tool.parameters : null
|
|
7562
8691
|
}, registry, strictCustomTools);
|
|
7563
8692
|
}
|
|
7564
8693
|
function registerMessagesTool(registration, registry, strictCustomTools) {
|
|
@@ -7656,7 +8785,7 @@ function translateInputItems(input, messages, system, registry) {
|
|
|
7656
8785
|
}
|
|
7657
8786
|
}
|
|
7658
8787
|
function translateInputMessage(item, messages, system, userMessageSeen) {
|
|
7659
|
-
if (!isRecord$
|
|
8788
|
+
if (!isRecord$2(item)) throw new ResponsesMessagesTranslationError("Invalid Responses message item");
|
|
7660
8789
|
const role = getStringField$1(item, "role");
|
|
7661
8790
|
if (role !== "user" && role !== "assistant" && role !== "system" && role !== "developer") throw new ResponsesMessagesTranslationError("Responses message items require a supported role");
|
|
7662
8791
|
if (role === "developer" && !userMessageSeen) return;
|
|
@@ -7696,7 +8825,7 @@ function appendDeveloperPrompts(system, input) {
|
|
|
7696
8825
|
if (type === "agent_message") break;
|
|
7697
8826
|
if ((type === void 0 || type === "message") && role === "user") break;
|
|
7698
8827
|
if (type !== void 0 && type !== "message" || role !== "developer") continue;
|
|
7699
|
-
prompts.push(translateSystemContent(isRecord$
|
|
8828
|
+
prompts.push(translateSystemContent(isRecord$2(item) ? item.content : void 0, "message.content"));
|
|
7700
8829
|
}
|
|
7701
8830
|
const [firstPrompt, ...remainingPrompts] = prompts;
|
|
7702
8831
|
if (firstPrompt) system.push({
|
|
@@ -7717,7 +8846,7 @@ function translateInputReasoning(item, messages) {
|
|
|
7717
8846
|
});
|
|
7718
8847
|
}
|
|
7719
8848
|
function translateInputToolCall(item, kind, messages, registry) {
|
|
7720
|
-
if (!isRecord$
|
|
8849
|
+
if (!isRecord$2(item)) throw new ResponsesMessagesTranslationError("Invalid Responses tool call item");
|
|
7721
8850
|
const name = requireStringField(item, "name", `${kind}_tool_call`);
|
|
7722
8851
|
const namespace = getOptionalStringField(item, "namespace") ?? void 0;
|
|
7723
8852
|
const toolIdentity = {
|
|
@@ -7748,7 +8877,7 @@ function translateAssistantContent(value, path) {
|
|
|
7748
8877
|
}] : [];
|
|
7749
8878
|
if (!Array.isArray(value)) throw new ResponsesMessagesTranslationError(`${path} must be text or an array`);
|
|
7750
8879
|
return value.map((part, index) => {
|
|
7751
|
-
if (!isRecord$
|
|
8880
|
+
if (!isRecord$2(part)) throw new ResponsesMessagesTranslationError(`${path}[${index}] must be an object`);
|
|
7752
8881
|
const type = getStringField$1(part, "type");
|
|
7753
8882
|
if (type !== "input_text" && type !== "output_text" && type !== "text") throw new ResponsesMessagesTranslationError(`${path}[${index}] has unsupported assistant content type '${type ?? "unknown"}'`);
|
|
7754
8883
|
return {
|
|
@@ -7762,14 +8891,14 @@ function translateSystemContent(value, path) {
|
|
|
7762
8891
|
if (typeof value === "string") return value;
|
|
7763
8892
|
if (!Array.isArray(value)) throw new ResponsesMessagesTranslationError(`${path} must be text or an array`);
|
|
7764
8893
|
return value.map((part, index) => {
|
|
7765
|
-
if (!isRecord$
|
|
8894
|
+
if (!isRecord$2(part)) throw new ResponsesMessagesTranslationError(`${path}[${index}] must be an object`);
|
|
7766
8895
|
const type = getStringField$1(part, "type");
|
|
7767
8896
|
if (type !== "input_text" && type !== "output_text" && type !== "text") throw new ResponsesMessagesTranslationError(`${path}[${index}] has unsupported system content type '${type ?? "unknown"}'`);
|
|
7768
8897
|
return requireStringField(part, "text", `${path}[${index}]`);
|
|
7769
8898
|
}).join("\n\n");
|
|
7770
8899
|
}
|
|
7771
8900
|
function translateUserContentPart(part, path) {
|
|
7772
|
-
if (!isRecord$
|
|
8901
|
+
if (!isRecord$2(part)) throw new ResponsesMessagesTranslationError(`${path} must be an object`);
|
|
7773
8902
|
const type = getStringField$1(part, "type");
|
|
7774
8903
|
if (type === "input_text" || type === "output_text" || type === "text") return {
|
|
7775
8904
|
type: "text",
|
|
@@ -7791,7 +8920,7 @@ function translateToolResultContent(value, path) {
|
|
|
7791
8920
|
return blocks.length > 0 ? blocks : "";
|
|
7792
8921
|
}
|
|
7793
8922
|
function isEmptyTextContentPart(part) {
|
|
7794
|
-
if (!isRecord$
|
|
8923
|
+
if (!isRecord$2(part)) return false;
|
|
7795
8924
|
const type = part.type;
|
|
7796
8925
|
return (type === "input_text" || type === "output_text" || type === "text") && part.text === "";
|
|
7797
8926
|
}
|
|
@@ -7976,7 +9105,7 @@ function appendUserBlock(messages, block) {
|
|
|
7976
9105
|
function parseFunctionArguments(value, _path) {
|
|
7977
9106
|
try {
|
|
7978
9107
|
const parsed = JSON.parse(value);
|
|
7979
|
-
if (isRecord$
|
|
9108
|
+
if (isRecord$2(parsed)) return parsed;
|
|
7980
9109
|
} catch {
|
|
7981
9110
|
return {};
|
|
7982
9111
|
}
|
|
@@ -8019,15 +9148,15 @@ function createStableHash(value) {
|
|
|
8019
9148
|
return createHash("sha256").update(value).digest("hex").slice(0, 24);
|
|
8020
9149
|
}
|
|
8021
9150
|
function getItemType(value) {
|
|
8022
|
-
return isRecord$
|
|
9151
|
+
return isRecord$2(value) && typeof value.type === "string" ? value.type : void 0;
|
|
8023
9152
|
}
|
|
8024
9153
|
function getStringField$1(value, field) {
|
|
8025
|
-
if (!isRecord$
|
|
9154
|
+
if (!isRecord$2(value)) return void 0;
|
|
8026
9155
|
const fieldValue = value[field];
|
|
8027
9156
|
return typeof fieldValue === "string" ? fieldValue : void 0;
|
|
8028
9157
|
}
|
|
8029
9158
|
function getOptionalStringField(value, field) {
|
|
8030
|
-
if (!isRecord$
|
|
9159
|
+
if (!isRecord$2(value)) return void 0;
|
|
8031
9160
|
const fieldValue = value[field];
|
|
8032
9161
|
return typeof fieldValue === "string" || fieldValue === null ? fieldValue : void 0;
|
|
8033
9162
|
}
|
|
@@ -8037,10 +9166,10 @@ function requireStringField(value, field, path) {
|
|
|
8037
9166
|
return result;
|
|
8038
9167
|
}
|
|
8039
9168
|
function getArrayField(value, field) {
|
|
8040
|
-
if (!isRecord$
|
|
9169
|
+
if (!isRecord$2(value)) return [];
|
|
8041
9170
|
return Array.isArray(value[field]) ? value[field] : [];
|
|
8042
9171
|
}
|
|
8043
|
-
function isRecord$
|
|
9172
|
+
function isRecord$2(value) {
|
|
8044
9173
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8045
9174
|
}
|
|
8046
9175
|
const DEFAULT_RESPONSES_COMPACT_THRESHOLD_RATIO = .85;
|
|
@@ -9197,7 +10326,7 @@ async function forwardGoogleGenerateContent(provider, model, payload, requestHea
|
|
|
9197
10326
|
applyOpencodeSessionHeader(provider, headers, options.sessionId);
|
|
9198
10327
|
headers.accept = options.stream ? "text/event-stream" : "application/json";
|
|
9199
10328
|
const transport = getUpstreamTransportConfig();
|
|
9200
|
-
setPluginRequestActualModel(model.replace(/^models\//u, ""));
|
|
10329
|
+
setPluginRequestActualModel(model.replace(/^models\//u, ""), provider.name);
|
|
9201
10330
|
return await fetchUpstreamWithLifecycle(url, {
|
|
9202
10331
|
method: "POST",
|
|
9203
10332
|
headers,
|
|
@@ -10498,14 +11627,14 @@ function withModelDisplayName(model, provider) {
|
|
|
10498
11627
|
// src/routes/models/model-discovery.ts
|
|
10499
11628
|
const logger$10 = createHandlerLogger("models-handler");
|
|
10500
11629
|
const EPOCH_ISO = (/* @__PURE__ */ new Date(0)).toISOString();
|
|
10501
|
-
function isRecord(value) {
|
|
11630
|
+
function isRecord$1(value) {
|
|
10502
11631
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10503
11632
|
}
|
|
10504
11633
|
function firstPositiveInteger(...values) {
|
|
10505
11634
|
for (const value of values) if (typeof value === "number" && Number.isFinite(value) && value >= 1) return Math.floor(value);
|
|
10506
11635
|
}
|
|
10507
11636
|
function normalizeModelCapabilities(model) {
|
|
10508
|
-
if (!isRecord(model.capabilities)) return model;
|
|
11637
|
+
if (!isRecord$1(model.capabilities)) return model;
|
|
10509
11638
|
const limits = getRecordField(model.capabilities, "limits");
|
|
10510
11639
|
const supports = getRecordField(model.capabilities, "supports");
|
|
10511
11640
|
return {
|
|
@@ -10536,13 +11665,21 @@ function getStringField(model, field) {
|
|
|
10536
11665
|
}
|
|
10537
11666
|
function getRecordField(model, field) {
|
|
10538
11667
|
const value = model?.[field];
|
|
10539
|
-
return isRecord(value) ? value : void 0;
|
|
11668
|
+
return isRecord$1(value) ? value : void 0;
|
|
10540
11669
|
}
|
|
10541
11670
|
function getStringList(value) {
|
|
10542
11671
|
if (!Array.isArray(value)) return void 0;
|
|
10543
11672
|
const strings = [...new Set(value.filter((item) => typeof item === "string"))];
|
|
10544
11673
|
return strings.length > 0 ? strings : void 0;
|
|
10545
11674
|
}
|
|
11675
|
+
function isImageOnlyModel(provider, modelId, model, config) {
|
|
11676
|
+
const record = isRecord$1(model) ? model : void 0;
|
|
11677
|
+
const catalog = getModelsDevModel(resolveProviderModelsDevId(provider, config), modelId);
|
|
11678
|
+
const builtin = getBuiltinProviderImageModelRecords(provider).find((image) => image.id === modelId);
|
|
11679
|
+
const output = getStringList(record?.output_modalities) ?? getStringList(getRecordField(record, "architecture")?.output_modalities) ?? getStringList(catalog?.modalities?.output) ?? getStringList(builtin?.output_modalities);
|
|
11680
|
+
if (output) return output.includes("image") && !output.includes("text");
|
|
11681
|
+
return record?.type === "image" || getRecordField(record, "capabilities")?.type === "image";
|
|
11682
|
+
}
|
|
10546
11683
|
function getFallbackProviderModelRecords(provider, reason, details = {}) {
|
|
10547
11684
|
const fallbackModels = getLocalProviderModelRecords(provider, getRawProviderConfig(provider)) ?? getBuiltinProviderModelRecords(provider);
|
|
10548
11685
|
logger$10.warn(`models.provider.fallback_${reason}`, {
|
|
@@ -10552,10 +11689,10 @@ function getFallbackProviderModelRecords(provider, reason, details = {}) {
|
|
|
10552
11689
|
});
|
|
10553
11690
|
return fallbackModels;
|
|
10554
11691
|
}
|
|
10555
|
-
function normalizeProviderModel(provider, model, config) {
|
|
10556
|
-
if (!isRecord(model)) return null;
|
|
11692
|
+
function normalizeProviderModel(provider, model, config, includeImageModels = false) {
|
|
11693
|
+
if (!isRecord$1(model)) return null;
|
|
10557
11694
|
const rawId = getStringField(model, "id");
|
|
10558
|
-
if (!rawId || !isProviderModelVisible(provider, rawId, config)) return null;
|
|
11695
|
+
if (!rawId || !isProviderModelVisible(provider, rawId, config) || !includeImageModels && isImageOnlyModel(provider, rawId, model, config)) return null;
|
|
10559
11696
|
const id = `${provider}/${rawId}`;
|
|
10560
11697
|
const ownedBy = getStringField(model, "owned_by") ?? getStringField(model, "vendor") ?? provider;
|
|
10561
11698
|
return {
|
|
@@ -10568,8 +11705,8 @@ function normalizeProviderModel(provider, model, config) {
|
|
|
10568
11705
|
owned_by: ownedBy
|
|
10569
11706
|
};
|
|
10570
11707
|
}
|
|
10571
|
-
function normalizeProviderModels(provider, models, config) {
|
|
10572
|
-
return models.map((model) => normalizeProviderModel(provider, model, config)).filter((model) => model !== null);
|
|
11708
|
+
function normalizeProviderModels(provider, models, config, includeImageModels = false) {
|
|
11709
|
+
return models.map((model) => normalizeProviderModel(provider, model, config, includeImageModels)).filter((model) => model !== null);
|
|
10573
11710
|
}
|
|
10574
11711
|
async function getProviderModelRecords(providerConfig, requestHeaders) {
|
|
10575
11712
|
const localModels = getLocalProviderModelRecords(providerConfig.name, providerConfig);
|
|
@@ -10578,8 +11715,8 @@ async function getProviderModelRecords(providerConfig, requestHeaders) {
|
|
|
10578
11715
|
const response = await forwardProviderModels(providerConfig, requestHeaders);
|
|
10579
11716
|
if (!response.ok) return getFallbackProviderModelRecords(providerConfig.name, "non_ok", { statusCode: response.status });
|
|
10580
11717
|
const body = await response.json();
|
|
10581
|
-
if (!isRecord(body) || !Array.isArray(body.data)) return getFallbackProviderModelRecords(providerConfig.name, "invalid_body");
|
|
10582
|
-
return body.data.filter(isRecord).map(normalizeModelCapabilities);
|
|
11718
|
+
if (!isRecord$1(body) || !Array.isArray(body.data)) return getFallbackProviderModelRecords(providerConfig.name, "invalid_body");
|
|
11719
|
+
return body.data.filter(isRecord$1).map(normalizeModelCapabilities);
|
|
10583
11720
|
} catch (error) {
|
|
10584
11721
|
return getFallbackProviderModelRecords(providerConfig.name, "error", { error });
|
|
10585
11722
|
}
|
|
@@ -10589,7 +11726,7 @@ function getCopilotModelRecords() {
|
|
|
10589
11726
|
}
|
|
10590
11727
|
function getModelsById(models) {
|
|
10591
11728
|
return new Map(models.flatMap((model) => {
|
|
10592
|
-
if (!isRecord(model)) return [];
|
|
11729
|
+
if (!isRecord$1(model)) return [];
|
|
10593
11730
|
const id = getStringField(model, "id");
|
|
10594
11731
|
return id ? [[id, model]] : [];
|
|
10595
11732
|
}));
|
|
@@ -10603,12 +11740,22 @@ function deduplicateModels(models, getId) {
|
|
|
10603
11740
|
return true;
|
|
10604
11741
|
});
|
|
10605
11742
|
}
|
|
10606
|
-
|
|
11743
|
+
function withProviderImageModels(provider, models) {
|
|
11744
|
+
const imageModels = getModelsById(getBuiltinProviderImageModelRecords(provider));
|
|
11745
|
+
if (imageModels.size === 0) return models;
|
|
11746
|
+
const listedModels = getModelsById(models);
|
|
11747
|
+
return [...models.map((model) => isRecord$1(model) ? {
|
|
11748
|
+
...imageModels.get(getStringField(model, "id") ?? ""),
|
|
11749
|
+
...model
|
|
11750
|
+
} : model), ...[...imageModels.values()].filter((model) => !listedModels.has(getStringField(model, "id") ?? ""))];
|
|
11751
|
+
}
|
|
11752
|
+
async function getProviderModels(provider, requestHeaders, selectModels, includeImageModels = false) {
|
|
10607
11753
|
try {
|
|
10608
11754
|
const providerConfig = await resolveProviderConfig(provider);
|
|
10609
11755
|
if (!providerConfig) return [];
|
|
10610
|
-
const
|
|
10611
|
-
|
|
11756
|
+
const upstreamModels = providerConfig.name === "codex" ? getModels().data.map(normalizeModelCapabilities) : await getProviderModelRecords(providerConfig, requestHeaders);
|
|
11757
|
+
const models = includeImageModels ? withProviderImageModels(providerConfig.name, upstreamModels) : upstreamModels;
|
|
11758
|
+
return normalizeProviderModels(providerConfig.name, selectModels?.(providerConfig, models) ?? models, providerConfig, includeImageModels);
|
|
10612
11759
|
} catch (error) {
|
|
10613
11760
|
if (provider === "codex") {
|
|
10614
11761
|
logger$10.warn("models.provider.skip_error", {
|
|
@@ -10619,14 +11766,22 @@ async function getProviderModels(provider, requestHeaders, selectModels) {
|
|
|
10619
11766
|
}
|
|
10620
11767
|
const fallbackModels = getFallbackProviderModelRecords(provider, "error", { error });
|
|
10621
11768
|
const providerConfig = getRawProviderConfig(provider);
|
|
10622
|
-
return normalizeProviderModels(provider, selectModels?.(providerConfig, fallbackModels) ?? fallbackModels, providerConfig);
|
|
11769
|
+
return normalizeProviderModels(provider, selectModels?.(providerConfig, fallbackModels) ?? fallbackModels, providerConfig, includeImageModels);
|
|
10623
11770
|
}
|
|
10624
11771
|
}
|
|
10625
11772
|
async function getAggregatedModels(requestHeaders, selectors = {}) {
|
|
10626
11773
|
const rawCopilotModels = getCopilotModelRecords();
|
|
10627
|
-
const
|
|
10628
|
-
const
|
|
10629
|
-
|
|
11774
|
+
const copilotConfig = getRawProviderConfig(GITHUB_COPILOT_PROVIDER);
|
|
11775
|
+
const isCopilotImage = (model) => isImageOnlyModel(GITHUB_COPILOT_PROVIDER, model.id, model, copilotConfig);
|
|
11776
|
+
const copilotModels = (selectors.selectCopilotModels?.(rawCopilotModels) ?? rawCopilotModels).filter((model) => !isCopilotImage(model)).map(normalizeCopilotModel);
|
|
11777
|
+
const rawCopilotImages = selectors.includeImageModels && isGitHubCopilotAvailable() ? withProviderImageModels(GITHUB_COPILOT_PROVIDER, rawCopilotModels.filter(isCopilotImage)) : [];
|
|
11778
|
+
const copilotImages = selectors.includeImageModels && isGitHubCopilotAvailable() ? normalizeProviderModels(GITHUB_COPILOT_PROVIDER, (selectors.selectProviderModels?.(copilotConfig, rawCopilotImages) ?? rawCopilotImages).filter((model) => isRecord$1(model) && isImageOnlyModel("github-copilot", getStringField(model, "id") ?? "", model, copilotConfig)), copilotConfig, true) : [];
|
|
11779
|
+
const providerModelsByProvider = await Promise.all(listEnabledProviders().map((provider) => getProviderModels(provider, requestHeaders, selectors.selectProviderModels, selectors.includeImageModels)));
|
|
11780
|
+
return deduplicateModels([
|
|
11781
|
+
...copilotModels,
|
|
11782
|
+
...copilotImages,
|
|
11783
|
+
...providerModelsByProvider.flat()
|
|
11784
|
+
], (model) => model.id);
|
|
10630
11785
|
}
|
|
10631
11786
|
function selectAgentCopilotModels(models) {
|
|
10632
11787
|
const selection = getProviderAgentModels(getRawProviderConfig(GITHUB_COPILOT_PROVIDER));
|
|
@@ -10639,8 +11794,9 @@ function selectAgentProviderModels(providerConfig, models) {
|
|
|
10639
11794
|
const modelsById = getModelsById(models);
|
|
10640
11795
|
return selection.map((id) => modelsById.get(id) ?? { id });
|
|
10641
11796
|
}
|
|
10642
|
-
async function getAgentModels(requestHeaders) {
|
|
11797
|
+
async function getAgentModels(requestHeaders, options = {}) {
|
|
10643
11798
|
return getAggregatedModels(requestHeaders, {
|
|
11799
|
+
includeImageModels: options.includeImageModels,
|
|
10644
11800
|
selectCopilotModels: selectAgentCopilotModels,
|
|
10645
11801
|
selectProviderModels: selectAgentProviderModels
|
|
10646
11802
|
});
|
|
@@ -10804,7 +11960,8 @@ function getCopilotCodexCandidates() {
|
|
|
10804
11960
|
return candidates;
|
|
10805
11961
|
}
|
|
10806
11962
|
function isCopilotCodexCandidate(model) {
|
|
10807
|
-
|
|
11963
|
+
const endpoints = model.supported_endpoints ?? [];
|
|
11964
|
+
return !isImageOnlyModel("github-copilot", model.id, model) && endpoints.some((endpoint) => endpoint === MESSAGES_ENDPOINT || endpoint === CHAT_COMPLETIONS_ENDPOINT || RESPONSES_ENDPOINTS.has(endpoint)) && model.capabilities.supports.tool_calls !== false;
|
|
10808
11965
|
}
|
|
10809
11966
|
function createCopilotCodexCandidate(model) {
|
|
10810
11967
|
const reasoningEfforts = normalizeReasoningEfforts(model.capabilities.supports.reasoning_effort);
|
|
@@ -10834,9 +11991,11 @@ async function getProviderCodexCandidates(provider, requestHeaders) {
|
|
|
10834
11991
|
const candidates = [];
|
|
10835
11992
|
for (const modelId of modelIds) {
|
|
10836
11993
|
if (!isProviderModelVisible(provider, modelId, providerConfig)) continue;
|
|
11994
|
+
const remoteModel = modelsById.get(modelId);
|
|
11995
|
+
if (isImageOnlyModel(provider, modelId, remoteModel, providerConfig)) continue;
|
|
10837
11996
|
const effectiveType = resolveEffectiveProviderType(providerConfig, modelId);
|
|
10838
11997
|
if (!isMessagesFallbackProviderType(effectiveType) && effectiveType !== "openai-responses") continue;
|
|
10839
|
-
candidates.push(createProviderCodexCandidate(providerConfig, modelId,
|
|
11998
|
+
candidates.push(createProviderCodexCandidate(providerConfig, modelId, remoteModel, effectiveType));
|
|
10840
11999
|
}
|
|
10841
12000
|
return candidates;
|
|
10842
12001
|
} catch (error) {
|
|
@@ -12623,7 +13782,7 @@ async function handleCodexModelsProxy(c, resolvedProviderConfig) {
|
|
|
12623
13782
|
const providerConfig = getRawProviderConfig("codex");
|
|
12624
13783
|
return createCodexCatalogResponse(c, {
|
|
12625
13784
|
...catalog,
|
|
12626
|
-
models: catalog.models.filter((model) => isProviderAgentModelVisible(providerConfig, model.slug))
|
|
13785
|
+
models: catalog.models.filter((model) => !isImageOnlyModel("codex", model.slug, model, providerConfig) && isProviderAgentModelVisible(providerConfig, model.slug))
|
|
12627
13786
|
}, new Set(getProviderAgentModels(providerConfig)), FALLBACK_CODEX_MODELS[0].model_messages);
|
|
12628
13787
|
}
|
|
12629
13788
|
async function handleCodexModels(c) {
|
|
@@ -12640,10 +13799,10 @@ async function handleMergedCodexModels(c, candidatesRequest, options = {}) {
|
|
|
12640
13799
|
return [];
|
|
12641
13800
|
})]);
|
|
12642
13801
|
const upstreamCatalog = upstreamCatalogResult?.catalog;
|
|
12643
|
-
const
|
|
13802
|
+
const codexConfig = getRawProviderConfig("codex");
|
|
13803
|
+
const upstreamModels = (upstreamCatalog?.models ?? FALLBACK_CODEX_MODELS).filter((model) => !isImageOnlyModel("codex", model.slug, model, codexConfig));
|
|
12644
13804
|
const template = selectTemplate(upstreamModels);
|
|
12645
13805
|
const catalogModelsBySlug = new Map(upstreamModels.map((model) => [model.slug, model]));
|
|
12646
|
-
const codexConfig = getRawProviderConfig("codex");
|
|
12647
13806
|
const copilotConfig = getRawProviderConfig("github-copilot");
|
|
12648
13807
|
const visibleUpstreamModels = upstreamModels.filter((model) => isProviderAgentModelVisible(codexConfig, model.slug) && (upstreamCatalog !== void 0 || codexConfig !== null || isProviderAgentModelVisible(copilotConfig, model.slug)));
|
|
12649
13808
|
const explicitSlugs = /* @__PURE__ */ new Set();
|
|
@@ -12814,15 +13973,17 @@ function getCandidatePriorityBase(candidate) {
|
|
|
12814
13973
|
return PROVIDER_PRIORITY_BASE;
|
|
12815
13974
|
}
|
|
12816
13975
|
function isCodexModelsResponse(value) {
|
|
12817
|
-
if (!isRecord(value) || !Array.isArray(value.models)) return false;
|
|
12818
|
-
return value.models.every((model) => isRecord(model) && typeof model.slug === "string");
|
|
13976
|
+
if (!isRecord$1(value) || !Array.isArray(value.models)) return false;
|
|
13977
|
+
return value.models.every((model) => isRecord$1(model) && typeof model.slug === "string");
|
|
12819
13978
|
}
|
|
12820
13979
|
// src/routes/models/default-models.ts
|
|
12821
13980
|
async function handleDefaultModels(c) {
|
|
12822
|
-
|
|
13981
|
+
const isDeepSeekClient = c.req.header("user-agent")?.toLowerCase().startsWith("deepseek") ?? false;
|
|
13982
|
+
return createModelListResponse(c, await (isDeepSeekClient ? getAgentModels : getAggregatedModels)(stripInternalRequestHeaders(c.req.raw.headers), { includeImageModels: !isDeepSeekClient }));
|
|
12823
13983
|
}
|
|
12824
13984
|
// src/routes/models/opencode-models.ts
|
|
12825
13985
|
const RESPONSES_PACKAGE = "@opencode/ai/providers/openai/responses";
|
|
13986
|
+
const IMAGES_PACKAGE = "@opencode/ai/providers/openai/images";
|
|
12826
13987
|
const CNY_PER_USD = 6.7;
|
|
12827
13988
|
const COST_DECIMALS = 6;
|
|
12828
13989
|
function isOpencodeUserAgent(userAgent) {
|
|
@@ -12891,10 +14052,12 @@ function toOpencodeModel(model, contextWindow) {
|
|
|
12891
14052
|
const config = getRawProviderConfig(provider);
|
|
12892
14053
|
const metadata = getModelMetadata(provider, rawId, model, config);
|
|
12893
14054
|
const { modelConfig, defaults, catalog, capabilities, supports, limits } = metadata;
|
|
14055
|
+
const output = getStringList(model.output_modalities) ?? getStringList(catalog?.modalities?.output) ?? (model.type === "image" ? ["image"] : ["text"]);
|
|
14056
|
+
const isImage = output.includes("image") && !output.includes("text");
|
|
12894
14057
|
const context = metadata.contextWindow ?? 256e3;
|
|
12895
14058
|
const inputLimit = metadata.inputLimit ?? 0;
|
|
12896
14059
|
const supportedMediaTypes = getStringList(getRecordField(limits, "vision")?.supported_media_types);
|
|
12897
|
-
const supportPdf = resolveModelPdfSupport(provider, {
|
|
14060
|
+
const supportPdf = !isImage && resolveModelPdfSupport(provider, {
|
|
12898
14061
|
supportPdf: modelConfig?.supportPdf,
|
|
12899
14062
|
supportedMediaTypes,
|
|
12900
14063
|
catalogInputModalities: catalog?.modalities?.input,
|
|
@@ -12911,15 +14074,16 @@ function toOpencodeModel(model, contextWindow) {
|
|
|
12911
14074
|
id: model.id,
|
|
12912
14075
|
modelID: model.id,
|
|
12913
14076
|
providerID: "local",
|
|
14077
|
+
...isImage && { type: "image" },
|
|
12914
14078
|
name: getStringField(model, "display_name") ?? catalog?.name ?? model.id,
|
|
12915
|
-
family: catalog?.family ?? getStringField(capabilities ?? {}, "family"),
|
|
12916
|
-
package: RESPONSES_PACKAGE,
|
|
14079
|
+
family: catalog?.family ?? getStringField(model, "family") ?? getStringField(capabilities ?? {}, "family"),
|
|
14080
|
+
package: isImage ? IMAGES_PACKAGE : RESPONSES_PACKAGE,
|
|
12917
14081
|
capabilities: {
|
|
12918
|
-
tools: typeof supports?.tool_calls === "boolean" ? supports.tool_calls : catalog?.tool_call ?? true,
|
|
14082
|
+
tools: isImage ? false : typeof supports?.tool_calls === "boolean" ? supports.tool_calls : catalog?.tool_call ?? true,
|
|
12919
14083
|
input: [...input],
|
|
12920
|
-
output
|
|
14084
|
+
output
|
|
12921
14085
|
},
|
|
12922
|
-
variants: efforts.map((effort) => ({
|
|
14086
|
+
variants: isImage ? [] : efforts.map((effort) => ({
|
|
12923
14087
|
id: effort,
|
|
12924
14088
|
body: { reasoning: { effort } }
|
|
12925
14089
|
})),
|
|
@@ -12927,7 +14091,10 @@ function toOpencodeModel(model, contextWindow) {
|
|
|
12927
14091
|
cost,
|
|
12928
14092
|
status: catalog?.status ?? (model.preview ? "beta" : "active"),
|
|
12929
14093
|
enabled: true,
|
|
12930
|
-
limit: {
|
|
14094
|
+
limit: isImage ? {
|
|
14095
|
+
context: 0,
|
|
14096
|
+
output: 0
|
|
14097
|
+
} : {
|
|
12931
14098
|
context: Math.min(context, contextWindow),
|
|
12932
14099
|
...inputLimit > 0 && { input: Math.min(inputLimit, context, contextWindow) },
|
|
12933
14100
|
output: Math.min(context, metadata.maxOutputTokens ?? 32e3)
|
|
@@ -12935,7 +14102,7 @@ function toOpencodeModel(model, contextWindow) {
|
|
|
12935
14102
|
};
|
|
12936
14103
|
}
|
|
12937
14104
|
async function handleOpencodeModels(c) {
|
|
12938
|
-
const models = await getAgentModels(stripInternalRequestHeaders(c.req.raw.headers));
|
|
14105
|
+
const models = await getAgentModels(stripInternalRequestHeaders(c.req.raw.headers), { includeImageModels: true });
|
|
12939
14106
|
const contextWindow = getOpencodeModelContextWindow();
|
|
12940
14107
|
c.header("Cache-Control", "private, no-store");
|
|
12941
14108
|
c.header("Vary", "User-Agent");
|
|
@@ -12997,6 +14164,7 @@ const providerImageRoutes = new Hono();
|
|
|
12997
14164
|
async function handleProviderImages(c, operation) {
|
|
12998
14165
|
const provider = c.req.param("provider") ?? "";
|
|
12999
14166
|
try {
|
|
14167
|
+
if (provider === "github-copilot") return await handleCopilotImages(c, operation);
|
|
13000
14168
|
const providerConfig = await resolveProviderConfig(provider);
|
|
13001
14169
|
if (!providerConfig) return c.json({ error: {
|
|
13002
14170
|
message: `Provider '${provider}' not found or disabled`,
|
|
@@ -13007,17 +14175,27 @@ async function handleProviderImages(c, operation) {
|
|
|
13007
14175
|
if (contentTypeError) return contentTypeError;
|
|
13008
14176
|
const multipart = await parseMultipartImageEdit(c.req.raw);
|
|
13009
14177
|
if (multipart) try {
|
|
13010
|
-
const
|
|
13011
|
-
|
|
14178
|
+
const model = await resolveImageRequestModel(multipart.model, { provider: providerConfig.name });
|
|
14179
|
+
const request = await multipart.createRequest(model, providerConfig.name);
|
|
14180
|
+
if (providerConfig.name === "codex") return await handleCodexImages(c, operation, providerConfig, request, model);
|
|
13012
14181
|
return await forwardProviderImagesWithLogging(providerConfig, request, operation, {
|
|
13013
14182
|
logger: logger$6,
|
|
13014
|
-
provider
|
|
14183
|
+
provider,
|
|
14184
|
+
model
|
|
13015
14185
|
});
|
|
13016
14186
|
} finally {
|
|
13017
14187
|
await multipart.cleanup();
|
|
13018
14188
|
}
|
|
13019
14189
|
}
|
|
13020
|
-
if (providerConfig.name
|
|
14190
|
+
if (getDefaultImageModel(providerConfig.name)) {
|
|
14191
|
+
const { request, model } = await prepareProviderJsonImages(c.req.raw, providerConfig.name);
|
|
14192
|
+
if (providerConfig.name === "codex") return await handleCodexImages(c, operation, providerConfig, request, model);
|
|
14193
|
+
return await forwardProviderImagesWithLogging(providerConfig, request, operation, {
|
|
14194
|
+
logger: logger$6,
|
|
14195
|
+
provider,
|
|
14196
|
+
model
|
|
14197
|
+
});
|
|
14198
|
+
}
|
|
13021
14199
|
return await forwardProviderImagesWithLogging(providerConfig, c.req.raw, operation, {
|
|
13022
14200
|
logger: logger$6,
|
|
13023
14201
|
provider
|
|
@@ -13032,6 +14210,46 @@ async function handleProviderImages(c, operation) {
|
|
|
13032
14210
|
return await forwardError(c, error);
|
|
13033
14211
|
}
|
|
13034
14212
|
}
|
|
14213
|
+
async function handleCopilotImages(c, operation) {
|
|
14214
|
+
if (operation === "edits") {
|
|
14215
|
+
const contentTypeError = imageEditContentTypeErrorResponse(c);
|
|
14216
|
+
if (contentTypeError) return contentTypeError;
|
|
14217
|
+
const multipart = await parseMultipartImageEdit(c.req.raw);
|
|
14218
|
+
if (multipart) try {
|
|
14219
|
+
const model = await resolveImageRequestModel(multipart.model, { provider: GITHUB_COPILOT_PROVIDER });
|
|
14220
|
+
return await forwardCopilotImagesWithLogging(await multipart.createRequest(model, GITHUB_COPILOT_PROVIDER), operation, {
|
|
14221
|
+
logger: logger$6,
|
|
14222
|
+
provider: GITHUB_COPILOT_PROVIDER,
|
|
14223
|
+
model
|
|
14224
|
+
});
|
|
14225
|
+
} finally {
|
|
14226
|
+
await multipart.cleanup();
|
|
14227
|
+
}
|
|
14228
|
+
}
|
|
14229
|
+
const { request, model } = await prepareProviderJsonImages(c.req.raw, GITHUB_COPILOT_PROVIDER);
|
|
14230
|
+
return await forwardCopilotImagesWithLogging(request, operation, {
|
|
14231
|
+
logger: logger$6,
|
|
14232
|
+
provider: GITHUB_COPILOT_PROVIDER,
|
|
14233
|
+
model
|
|
14234
|
+
});
|
|
14235
|
+
}
|
|
14236
|
+
async function prepareProviderJsonImages(request, provider) {
|
|
14237
|
+
const parsed = await parseJsonImagesRequest(request);
|
|
14238
|
+
if (parsed instanceof Request) return {
|
|
14239
|
+
request: parsed,
|
|
14240
|
+
model: "unknown"
|
|
14241
|
+
};
|
|
14242
|
+
try {
|
|
14243
|
+
const model = await resolveImageRequestModel(parsed.model, { provider });
|
|
14244
|
+
return {
|
|
14245
|
+
model,
|
|
14246
|
+
request: parsed.model === model && parsed.originalRequest ? parsed.originalRequest : await parsed.createRequest(model)
|
|
14247
|
+
};
|
|
14248
|
+
} catch (error) {
|
|
14249
|
+
await parsed.originalRequest?.body?.cancel().catch(() => {});
|
|
14250
|
+
throw error;
|
|
14251
|
+
}
|
|
14252
|
+
}
|
|
13035
14253
|
providerImageRoutes.post("/generations", (c) => handleProviderImages(c, "generations"));
|
|
13036
14254
|
providerImageRoutes.post("/edits", (c) => handleProviderImages(c, "edits"));
|
|
13037
14255
|
// src/routes/provider/messages/route.ts
|
|
@@ -14862,6 +16080,38 @@ usageRoute.get("/", async (c) => {
|
|
|
14862
16080
|
return c.json({ error: "Failed to fetch Copilot usage" }, 500);
|
|
14863
16081
|
}
|
|
14864
16082
|
});
|
|
16083
|
+
// src/lib/token-usage/videos.ts
|
|
16084
|
+
function isRecord(value) {
|
|
16085
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
16086
|
+
}
|
|
16087
|
+
/** Reads the finite result JSON once for usage accounting and debug output. */
|
|
16088
|
+
async function recordVideoResultUsage(response, options) {
|
|
16089
|
+
let body;
|
|
16090
|
+
try {
|
|
16091
|
+
body = await response.clone().text();
|
|
16092
|
+
} catch {
|
|
16093
|
+
return;
|
|
16094
|
+
}
|
|
16095
|
+
options.onBody(body);
|
|
16096
|
+
if (!response.ok) return;
|
|
16097
|
+
let result;
|
|
16098
|
+
try {
|
|
16099
|
+
result = JSON.parse(body);
|
|
16100
|
+
} catch {
|
|
16101
|
+
return;
|
|
16102
|
+
}
|
|
16103
|
+
if (!isRecord(result) || result.status !== "done" || !isRecord(result.usage)) return;
|
|
16104
|
+
const cost = xaiUsdTicksToCost(result.usage.cost_in_usd_ticks);
|
|
16105
|
+
if (cost === void 0) return;
|
|
16106
|
+
recordTokenUsageEvent({
|
|
16107
|
+
source: "provider",
|
|
16108
|
+
providerName: options.providerConfig.name,
|
|
16109
|
+
endpoint: "videos_generations",
|
|
16110
|
+
model: typeof result.model === "string" && result.model.trim() ? result.model : options.defaultModel,
|
|
16111
|
+
cost,
|
|
16112
|
+
upstreamRequestId: options.requestId
|
|
16113
|
+
});
|
|
16114
|
+
}
|
|
14865
16115
|
// src/services/xai/videos.ts
|
|
14866
16116
|
var XaiVideosRequestError = class extends Error {
|
|
14867
16117
|
constructor(message) {
|
|
@@ -14894,6 +16144,7 @@ async function forwardXaiVideos(providerConfig, request, videoRequest) {
|
|
|
14894
16144
|
const logger$1 = createHandlerLogger("videos-handler");
|
|
14895
16145
|
const SUPPORTED_VIDEO_MODEL = "grok-imagine-video-1.5";
|
|
14896
16146
|
const videoRouteDependencies = {
|
|
16147
|
+
debugJson,
|
|
14897
16148
|
resolveMappedModel,
|
|
14898
16149
|
resolveProviderConfig
|
|
14899
16150
|
};
|
|
@@ -14932,6 +16183,12 @@ async function handleVideos(c, operation) {
|
|
|
14932
16183
|
} }, 404);
|
|
14933
16184
|
const upstreamResponse = await forwardXaiVideos(providerConfig, c.req.raw, videoRequest);
|
|
14934
16185
|
debugJson(logger$1, `videos.${operation}.response`, { statusCode: upstreamResponse.status });
|
|
16186
|
+
if (videoRequest.operation === "result") await recordVideoResultUsage(upstreamResponse, {
|
|
16187
|
+
providerConfig,
|
|
16188
|
+
requestId: videoRequest.requestId,
|
|
16189
|
+
defaultModel: SUPPORTED_VIDEO_MODEL,
|
|
16190
|
+
onBody: (body) => videoRouteDependencies.debugJson(logger$1, "videos.result.response.body", { body })
|
|
16191
|
+
});
|
|
14935
16192
|
return createProviderProxyResponse(upstreamResponse);
|
|
14936
16193
|
} catch (error) {
|
|
14937
16194
|
if (error instanceof XaiVideosRequestError && !c.req.raw.signal.aborted) return c.json({ error: {
|
|
@@ -15013,4 +16270,4 @@ function createServer(options = {}) {
|
|
|
15013
16270
|
createServer();
|
|
15014
16271
|
export { createServer };
|
|
15015
16272
|
|
|
15016
|
-
//# sourceMappingURL=server-
|
|
16273
|
+
//# sourceMappingURL=server-Cw9qn5Gt.js.map
|