@jeffreycao/copilot-api 2.7.15 → 2.7.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -2
- package/README.zh-CN.md +14 -2
- package/dist/auth-DuGyJdjW.js +2 -0
- package/dist/{auth--x4CIFvT.js → auth-sKq0qru1.js} +2 -2
- package/dist/{auth--x4CIFvT.js.map → auth-sKq0qru1.js.map} +1 -1
- package/dist/config-store-CC5TNG7V.js.map +1 -1
- package/dist/{copilot-runtime-CW4zUahm.js → copilot-runtime-XKDhY-sV.js} +2 -2
- package/dist/{copilot-runtime-CW4zUahm.js.map → copilot-runtime-XKDhY-sV.js.map} +1 -1
- package/dist/main.js +2 -2
- package/dist/{server-CeyKV5sQ.js → server-04fZfBz1.js} +563 -4
- package/dist/server-04fZfBz1.js.map +1 -0
- package/dist/{start-COhhmcdU.js → start-qcGeG0TU.js} +5 -5
- package/dist/{start-COhhmcdU.js.map → start-qcGeG0TU.js.map} +1 -1
- package/dist/{token-BdwhXP3l.js → token-CWFRUUIv.js} +2 -2
- package/dist/token-CWFRUUIv.js.map +1 -0
- package/package.json +1 -1
- package/dist/auth-1hGXVwvW.js +0 -2
- package/dist/server-CeyKV5sQ.js.map +0 -1
- package/dist/token-BdwhXP3l.js.map +0 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { D as state, E as isGitHubCopilotEnabled, O as HTTPError, S as PATHS, a as getAnthropicApiKey, c as getConfig, d as getUpstreamTransportConfig, g as isResponsesApiWebSocketEnabled, h as isResponsesApiWebSearchEnabled, i as getAlphaSearchModel, j as forwardError, 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-CC5TNG7V.js";
|
|
2
2
|
import { A as resolveMappedModel, C as getModelResponsesApiCompactThreshold$1, D as isContextManagementEnabledForMessages, E as getSmallModelForProvider, O as isContextManagementEnabledForResponses, S as getModelMappings, T as getSmallModel, _ as getModelsDevModelMaxOutputTokens, d as isProviderModelVisible, f as resolveProviderModelsDevId, g as getModelsDevModel, h as getBuiltinProviderModelRecords, i as listEnabledProviders, j as setModelMappings, k as isGpt56OrAbove, 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 getModelsDevModelPricing, w as getReasoningEffortForModel, x as getExtraPromptForModel } from "./config-BKA4Y1t8.js";
|
|
3
|
-
import { $ as resolveTraceId$1, C as isOpencodeOauthApp, E as prepareMessageProxyHeaders, G as XAI_API_BASE_URL, J as CODEX_API_BASE_URL, L as getXaiAccessToken, M as compactTextOnlyGuard, N as createAuthMiddleware, O as compactAutoContinuePromptStarts, P as getConfiguredAdminApiKeys, Q as requestContext, S as copilotWebSocketHeaders, T as prepareInteractionHeaders, U as readGitHubToken, W as readGitHubTokenFromEnv, X as forwardCodexResponses, Y as buildCodexRequestHeaders, Z as generateTraceId, _ as parseUserIdMetadata, at as createPooledWebSocketStream, b as copilotHeaders, ct as stripInternalRequestHeaders, d as generateRequestIdFromPayload, et as fetchUpstreamWithLifecycle, f as getRootSessionId, g as isResponsesStream, h as isNullish, it as getResponsesStreamTerminalDisposition, j as compactSystemPromptStarts, k as compactMessageSections, m as isAsyncIterable, n as invalidateCodexRuntime, nt as createResponsesSafeStream, ot as createWebSocketUrl, p as getUUID, rt as encodePoolKeyPart, s as setupCodexToken, tt as createResponsesHttpEventStream, u as stopCopilotRefreshLoop, v as getCopilotUsage, w as prepareForCompact, y as copilotBaseUrl } from "./token-
|
|
3
|
+
import { $ as resolveTraceId$1, C as isOpencodeOauthApp, E as prepareMessageProxyHeaders, G as XAI_API_BASE_URL, J as CODEX_API_BASE_URL, L as getXaiAccessToken, M as compactTextOnlyGuard, N as createAuthMiddleware, O as compactAutoContinuePromptStarts, P as getConfiguredAdminApiKeys, Q as requestContext, S as copilotWebSocketHeaders, T as prepareInteractionHeaders, U as readGitHubToken, W as readGitHubTokenFromEnv, X as forwardCodexResponses, Y as buildCodexRequestHeaders, Z as generateTraceId, _ as parseUserIdMetadata, at as createPooledWebSocketStream, b as copilotHeaders, ct as stripInternalRequestHeaders, d as generateRequestIdFromPayload, et as fetchUpstreamWithLifecycle, f as getRootSessionId, g as isResponsesStream, h as isNullish, it as getResponsesStreamTerminalDisposition, j as compactSystemPromptStarts, k as compactMessageSections, m as isAsyncIterable, n as invalidateCodexRuntime, nt as createResponsesSafeStream, ot as createWebSocketUrl, p as getUUID, rt as encodePoolKeyPart, s as setupCodexToken, tt as createResponsesHttpEventStream, u as stopCopilotRefreshLoop, v as getCopilotUsage, w as prepareForCompact, y as copilotBaseUrl } from "./token-CWFRUUIv.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-CcvaqM03.js";
|
|
5
5
|
import { n as isProviderAgentModelVisible, t as getProviderAgentModels } from "./provider-management-CBrc2nDg.js";
|
|
6
|
-
import { n as stopModelsRefreshLoop, t as setupCopilotRuntime } from "./copilot-runtime-
|
|
6
|
+
import { n as stopModelsRefreshLoop, t as setupCopilotRuntime } from "./copilot-runtime-XKDhY-sV.js";
|
|
7
7
|
import consola from "consola";
|
|
8
8
|
import { createHash, randomUUID } from "node:crypto";
|
|
9
9
|
import fs, { createWriteStream, readFileSync, rmSync } from "node:fs";
|
|
@@ -2131,7 +2131,7 @@ async function forwardProviderImages(providerConfig, request, operation) {
|
|
|
2131
2131
|
headers,
|
|
2132
2132
|
body: request.body,
|
|
2133
2133
|
duplex: "half",
|
|
2134
|
-
signal: AbortSignal.timeout(PROVIDER_IMAGES_TIMEOUT_MS)
|
|
2134
|
+
signal: AbortSignal.any([request.signal, AbortSignal.timeout(PROVIDER_IMAGES_TIMEOUT_MS)])
|
|
2135
2135
|
};
|
|
2136
2136
|
const upstreamUrl = resolveProviderRequestUrl(providerConfig, request.url, `/v1/images/${operation}`);
|
|
2137
2137
|
if (typeof Bun !== "undefined") return await fetch(upstreamUrl, init);
|
|
@@ -3256,6 +3256,36 @@ embeddingRoutes.post("/", async (c) => {
|
|
|
3256
3256
|
}
|
|
3257
3257
|
});
|
|
3258
3258
|
//#endregion
|
|
3259
|
+
//#region src/lib/xai-image-model.ts
|
|
3260
|
+
/**
|
|
3261
|
+
* Resolves one image-route model setting. Empty values keep the OpenAI
|
|
3262
|
+
* protocol. Only an xai/model alias selects the xAI Imagine API.
|
|
3263
|
+
*/
|
|
3264
|
+
function resolveConfiguredXaiImageModel(configured, settingName) {
|
|
3265
|
+
if (configured === void 0 || configured === null) return { status: "disabled" };
|
|
3266
|
+
if (typeof configured !== "string") return {
|
|
3267
|
+
message: `${settingName} must be an xai/model alias`,
|
|
3268
|
+
status: "invalid"
|
|
3269
|
+
};
|
|
3270
|
+
const trimmed = configured.trim();
|
|
3271
|
+
if (!trimmed) return { status: "disabled" };
|
|
3272
|
+
const alias = parseProviderModelAlias(trimmed);
|
|
3273
|
+
if (alias?.provider.toLowerCase() !== "xai") return {
|
|
3274
|
+
message: `${settingName} only supports an xai/model alias, received '${trimmed}'`,
|
|
3275
|
+
status: "invalid"
|
|
3276
|
+
};
|
|
3277
|
+
return {
|
|
3278
|
+
model: alias.model,
|
|
3279
|
+
status: "xai"
|
|
3280
|
+
};
|
|
3281
|
+
}
|
|
3282
|
+
function getImageGenerationModel() {
|
|
3283
|
+
return resolveConfiguredXaiImageModel(getConfig().imageGenerationModel, "imageGenerationModel");
|
|
3284
|
+
}
|
|
3285
|
+
function getImageEditModel() {
|
|
3286
|
+
return resolveConfiguredXaiImageModel(getConfig().imageEditModel, "imageEditModel");
|
|
3287
|
+
}
|
|
3288
|
+
//#endregion
|
|
3259
3289
|
//#region src/routes/images/forward-provider-images.ts
|
|
3260
3290
|
/**
|
|
3261
3291
|
* Forwards an images request to a configured provider with debug logging.
|
|
@@ -3313,6 +3343,8 @@ async function forwardCodexImages(request, operation) {
|
|
|
3313
3343
|
const logger$14 = createHandlerLogger("images-handler");
|
|
3314
3344
|
const imageRouteDependencies = {
|
|
3315
3345
|
debugJsonAsync,
|
|
3346
|
+
getImageEditModel,
|
|
3347
|
+
getImageGenerationModel,
|
|
3316
3348
|
resolveMappedModel,
|
|
3317
3349
|
resolveProviderConfig
|
|
3318
3350
|
};
|
|
@@ -3395,6 +3427,517 @@ async function routeImagesRequest(c, operation, parsed) {
|
|
|
3395
3427
|
return await handleCodexImages(c, operation, void 0, parsed.createRequest(mappedModel));
|
|
3396
3428
|
}
|
|
3397
3429
|
//#endregion
|
|
3430
|
+
//#region src/services/xai/images.ts
|
|
3431
|
+
const XAI_ASPECT_RATIOS = [
|
|
3432
|
+
{
|
|
3433
|
+
label: "1:1",
|
|
3434
|
+
width: 1,
|
|
3435
|
+
height: 1
|
|
3436
|
+
},
|
|
3437
|
+
{
|
|
3438
|
+
label: "16:9",
|
|
3439
|
+
width: 16,
|
|
3440
|
+
height: 9
|
|
3441
|
+
},
|
|
3442
|
+
{
|
|
3443
|
+
label: "9:16",
|
|
3444
|
+
width: 9,
|
|
3445
|
+
height: 16
|
|
3446
|
+
},
|
|
3447
|
+
{
|
|
3448
|
+
label: "4:3",
|
|
3449
|
+
width: 4,
|
|
3450
|
+
height: 3
|
|
3451
|
+
},
|
|
3452
|
+
{
|
|
3453
|
+
label: "3:4",
|
|
3454
|
+
width: 3,
|
|
3455
|
+
height: 4
|
|
3456
|
+
},
|
|
3457
|
+
{
|
|
3458
|
+
label: "3:2",
|
|
3459
|
+
width: 3,
|
|
3460
|
+
height: 2
|
|
3461
|
+
},
|
|
3462
|
+
{
|
|
3463
|
+
label: "2:3",
|
|
3464
|
+
width: 2,
|
|
3465
|
+
height: 3
|
|
3466
|
+
},
|
|
3467
|
+
{
|
|
3468
|
+
label: "2:1",
|
|
3469
|
+
width: 2,
|
|
3470
|
+
height: 1
|
|
3471
|
+
},
|
|
3472
|
+
{
|
|
3473
|
+
label: "1:2",
|
|
3474
|
+
width: 1,
|
|
3475
|
+
height: 2
|
|
3476
|
+
},
|
|
3477
|
+
{
|
|
3478
|
+
label: "19.5:9",
|
|
3479
|
+
width: 19.5,
|
|
3480
|
+
height: 9
|
|
3481
|
+
},
|
|
3482
|
+
{
|
|
3483
|
+
label: "9:19.5",
|
|
3484
|
+
width: 9,
|
|
3485
|
+
height: 19.5
|
|
3486
|
+
},
|
|
3487
|
+
{
|
|
3488
|
+
label: "20:9",
|
|
3489
|
+
width: 20,
|
|
3490
|
+
height: 9
|
|
3491
|
+
},
|
|
3492
|
+
{
|
|
3493
|
+
label: "9:20",
|
|
3494
|
+
width: 9,
|
|
3495
|
+
height: 20
|
|
3496
|
+
}
|
|
3497
|
+
];
|
|
3498
|
+
const XAI_ASPECT_RATIO_LABELS = /* @__PURE__ */ new Set([...XAI_ASPECT_RATIOS.map((ratio) => ratio.label), "auto"]);
|
|
3499
|
+
const XAI_RESPONSE_FORMATS = /* @__PURE__ */ new Set(["url", "b64_json"]);
|
|
3500
|
+
const XAI_RESOLUTIONS = /* @__PURE__ */ new Set(["1k", "2k"]);
|
|
3501
|
+
const IMAGE_FIELD_NAMES = /* @__PURE__ */ new Set([
|
|
3502
|
+
"image",
|
|
3503
|
+
"image[]",
|
|
3504
|
+
"images",
|
|
3505
|
+
"images[]"
|
|
3506
|
+
]);
|
|
3507
|
+
const MAX_EDIT_IMAGES = 5;
|
|
3508
|
+
const MAX_IMAGE_COUNT = 10;
|
|
3509
|
+
const HIGH_RESOLUTION_EDGE = 1536;
|
|
3510
|
+
var XaiImagesRequestError = class extends Error {
|
|
3511
|
+
constructor(message) {
|
|
3512
|
+
super(message);
|
|
3513
|
+
this.name = "XaiImagesRequestError";
|
|
3514
|
+
}
|
|
3515
|
+
};
|
|
3516
|
+
function isRecord$3(value) {
|
|
3517
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3518
|
+
}
|
|
3519
|
+
function readPrompt(value) {
|
|
3520
|
+
if (typeof value !== "string" || value.trim() === "") throw new XaiImagesRequestError("prompt is required");
|
|
3521
|
+
return value;
|
|
3522
|
+
}
|
|
3523
|
+
function assertNotStreaming(value) {
|
|
3524
|
+
if (value === true || value === "true") throw new XaiImagesRequestError("xAI image requests do not support streaming");
|
|
3525
|
+
}
|
|
3526
|
+
function readCount(value) {
|
|
3527
|
+
if (value === void 0 || value === null) return void 0;
|
|
3528
|
+
const parsed = typeof value === "number" ? value : typeof value === "string" ? Number(value.trim()) : NaN;
|
|
3529
|
+
if (!Number.isInteger(parsed) || parsed < 1 || parsed > MAX_IMAGE_COUNT) throw new XaiImagesRequestError(`n must be an integer from 1 to ${MAX_IMAGE_COUNT}`);
|
|
3530
|
+
return parsed;
|
|
3531
|
+
}
|
|
3532
|
+
function readQuality(value) {
|
|
3533
|
+
if (typeof value !== "string") return void 0;
|
|
3534
|
+
switch (value.trim().toLowerCase()) {
|
|
3535
|
+
case "low": return "low";
|
|
3536
|
+
case "medium":
|
|
3537
|
+
case "standard":
|
|
3538
|
+
case "high":
|
|
3539
|
+
case "hd": return "medium";
|
|
3540
|
+
case "auto": return "auto";
|
|
3541
|
+
default: return;
|
|
3542
|
+
}
|
|
3543
|
+
}
|
|
3544
|
+
function readChoice(value, allowed, name, normalize = (item) => item) {
|
|
3545
|
+
if (value === void 0 || value === null) return void 0;
|
|
3546
|
+
if (typeof value !== "string") throw new XaiImagesRequestError(`${name} is invalid`);
|
|
3547
|
+
const trimmed = value.trim();
|
|
3548
|
+
if (!trimmed) return void 0;
|
|
3549
|
+
const normalized = normalize(trimmed);
|
|
3550
|
+
if (!allowed.has(normalized)) throw new XaiImagesRequestError(`${name} must be one of ${Array.from(allowed).join(", ")}`);
|
|
3551
|
+
return normalized;
|
|
3552
|
+
}
|
|
3553
|
+
function readAspectRatio(value) {
|
|
3554
|
+
if (value === void 0) return void 0;
|
|
3555
|
+
if (typeof value !== "string") throw new XaiImagesRequestError("aspect_ratio is invalid");
|
|
3556
|
+
const trimmed = value.trim();
|
|
3557
|
+
if (!trimmed) return void 0;
|
|
3558
|
+
const normalized = trimmed.toLowerCase() === "auto" ? "auto" : trimmed;
|
|
3559
|
+
if (!XAI_ASPECT_RATIO_LABELS.has(normalized)) throw new XaiImagesRequestError("aspect_ratio must be a supported xAI ratio such as 1:1, 16:9, 9:16, or auto");
|
|
3560
|
+
return normalized;
|
|
3561
|
+
}
|
|
3562
|
+
function readSize(value) {
|
|
3563
|
+
if (typeof value !== "string") return void 0;
|
|
3564
|
+
const trimmed = value.trim().toLowerCase();
|
|
3565
|
+
if (!trimmed) return void 0;
|
|
3566
|
+
if (trimmed === "auto") return "auto";
|
|
3567
|
+
const match = /^(\d+)x(\d+)$/u.exec(trimmed);
|
|
3568
|
+
if (!match?.[1] || !match[2]) return void 0;
|
|
3569
|
+
const width = Number(match[1]);
|
|
3570
|
+
const height = Number(match[2]);
|
|
3571
|
+
if (!Number.isSafeInteger(width) || !Number.isSafeInteger(height) || width <= 0 || height <= 0) return;
|
|
3572
|
+
return {
|
|
3573
|
+
height,
|
|
3574
|
+
width
|
|
3575
|
+
};
|
|
3576
|
+
}
|
|
3577
|
+
function aspectRatioFromSize(size) {
|
|
3578
|
+
if (!size) return void 0;
|
|
3579
|
+
if (size === "auto") return "auto";
|
|
3580
|
+
let best = XAI_ASPECT_RATIOS[0].label;
|
|
3581
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
3582
|
+
const ratio = Math.log(size.width / size.height);
|
|
3583
|
+
for (const candidate of XAI_ASPECT_RATIOS) {
|
|
3584
|
+
const distance = Math.abs(ratio - Math.log(candidate.width / candidate.height));
|
|
3585
|
+
if (distance < bestDistance) {
|
|
3586
|
+
best = candidate.label;
|
|
3587
|
+
bestDistance = distance;
|
|
3588
|
+
}
|
|
3589
|
+
}
|
|
3590
|
+
return best;
|
|
3591
|
+
}
|
|
3592
|
+
function resolutionFromSize(size) {
|
|
3593
|
+
if (!size || size === "auto") return void 0;
|
|
3594
|
+
return Math.max(size.width, size.height) >= HIGH_RESOLUTION_EDGE ? "2k" : "1k";
|
|
3595
|
+
}
|
|
3596
|
+
function applyCommonImageFields(target, source) {
|
|
3597
|
+
assertNotStreaming(source.stream);
|
|
3598
|
+
const count = readCount(source.n);
|
|
3599
|
+
const quality = readQuality(source.quality);
|
|
3600
|
+
const responseFormat = readChoice(source.response_format, XAI_RESPONSE_FORMATS, "response_format");
|
|
3601
|
+
const aspectRatio = readAspectRatio(source.aspect_ratio) ?? aspectRatioFromSize(readSize(source.size));
|
|
3602
|
+
const resolution = readChoice(source.resolution, XAI_RESOLUTIONS, "resolution", (value) => value.toLowerCase()) ?? resolutionFromSize(readSize(source.size));
|
|
3603
|
+
if (count !== void 0) target.n = count;
|
|
3604
|
+
if (quality && target.model === "grok-imagine-image-2.0") target.quality = quality;
|
|
3605
|
+
target.response_format = responseFormat ?? "b64_json";
|
|
3606
|
+
if (aspectRatio) target.aspect_ratio = aspectRatio;
|
|
3607
|
+
if (resolution) target.resolution = resolution;
|
|
3608
|
+
}
|
|
3609
|
+
function normalizeImageUrl(value) {
|
|
3610
|
+
const trimmed = value.trim();
|
|
3611
|
+
if (/^https?:\/\//iu.test(trimmed) || /^data:image\/[a-z0-9.+-]+;base64,/iu.test(trimmed)) return trimmed;
|
|
3612
|
+
throw new XaiImagesRequestError("image must be an http(s) URL or a base64 data URL");
|
|
3613
|
+
}
|
|
3614
|
+
function base64ToDataUrl(value, mime) {
|
|
3615
|
+
const compact = value.trim().replaceAll(/\s+/gu, "");
|
|
3616
|
+
if (compact.startsWith("data:image/")) return normalizeImageUrl(compact);
|
|
3617
|
+
if (!/^[a-z0-9+/]+={0,2}$/iu.test(compact)) throw new XaiImagesRequestError("image b64_json is not valid base64");
|
|
3618
|
+
return `data:${typeof mime === "string" && mime.trim().toLowerCase().startsWith("image/") ? mime.trim().toLowerCase() : "image/png"};base64,${compact}`;
|
|
3619
|
+
}
|
|
3620
|
+
function readImageReference(value) {
|
|
3621
|
+
if (typeof value === "string") return {
|
|
3622
|
+
type: "image_url",
|
|
3623
|
+
url: normalizeImageUrl(value)
|
|
3624
|
+
};
|
|
3625
|
+
if (!isRecord$3(value)) throw new XaiImagesRequestError("image must be a URL or an image object");
|
|
3626
|
+
if (typeof value.url === "string") return {
|
|
3627
|
+
type: "image_url",
|
|
3628
|
+
url: normalizeImageUrl(value.url)
|
|
3629
|
+
};
|
|
3630
|
+
if (typeof value.image_url === "string") return {
|
|
3631
|
+
type: "image_url",
|
|
3632
|
+
url: normalizeImageUrl(value.image_url)
|
|
3633
|
+
};
|
|
3634
|
+
if (typeof value.b64_json === "string") return {
|
|
3635
|
+
type: "image_url",
|
|
3636
|
+
url: base64ToDataUrl(value.b64_json, value.mime_type ?? value.media_type)
|
|
3637
|
+
};
|
|
3638
|
+
if (value.file_id !== void 0) throw new XaiImagesRequestError("file_id is not supported for xAI image edits; provide an image_url instead");
|
|
3639
|
+
throw new XaiImagesRequestError("image must include a url, image_url, or b64_json");
|
|
3640
|
+
}
|
|
3641
|
+
function detectImageMime(bytes, fileName) {
|
|
3642
|
+
if (bytes.length >= 8 && bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71) return "image/png";
|
|
3643
|
+
if (bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216) return "image/jpeg";
|
|
3644
|
+
if (bytes.length >= 12 && bytes[0] === 82 && bytes[1] === 73 && bytes[2] === 70 && bytes[3] === 70 && bytes[8] === 87 && bytes[9] === 69 && bytes[10] === 66 && bytes[11] === 80) return "image/webp";
|
|
3645
|
+
if (bytes.length >= 6 && bytes[0] === 71 && bytes[1] === 73 && bytes[2] === 70) return "image/gif";
|
|
3646
|
+
const extension = fileName?.split(".").pop()?.toLowerCase();
|
|
3647
|
+
if (extension === "jpg" || extension === "jpeg") return "image/jpeg";
|
|
3648
|
+
if (extension === "webp") return "image/webp";
|
|
3649
|
+
if (extension === "gif") return "image/gif";
|
|
3650
|
+
return "image/png";
|
|
3651
|
+
}
|
|
3652
|
+
async function blobToImageReference(blob) {
|
|
3653
|
+
const bytes = new Uint8Array(await blob.arrayBuffer());
|
|
3654
|
+
if (bytes.byteLength === 0) throw new XaiImagesRequestError("image is empty");
|
|
3655
|
+
const fileName = blob instanceof File ? blob.name : void 0;
|
|
3656
|
+
const declared = blob.type.trim().toLowerCase();
|
|
3657
|
+
return {
|
|
3658
|
+
type: "image_url",
|
|
3659
|
+
url: `data:${declared.startsWith("image/") ? declared : detectImageMime(bytes, fileName)};base64,${Buffer.from(bytes).toString("base64")}`
|
|
3660
|
+
};
|
|
3661
|
+
}
|
|
3662
|
+
async function formValueToImageReference(value) {
|
|
3663
|
+
if (typeof value === "string") return readImageReference(value);
|
|
3664
|
+
if (!(value instanceof Blob)) throw new XaiImagesRequestError("image must be a file or a URL");
|
|
3665
|
+
return await blobToImageReference(value);
|
|
3666
|
+
}
|
|
3667
|
+
function collectJsonImages(payload) {
|
|
3668
|
+
const values = [];
|
|
3669
|
+
if (isUnknownArray(payload.image)) values.push(...payload.image);
|
|
3670
|
+
else if (payload.image !== void 0) values.push(payload.image);
|
|
3671
|
+
if (isUnknownArray(payload.images)) values.push(...payload.images);
|
|
3672
|
+
if (payload.image_url !== void 0) values.push(payload.image_url);
|
|
3673
|
+
return values;
|
|
3674
|
+
}
|
|
3675
|
+
function isUnknownArray(value) {
|
|
3676
|
+
return Array.isArray(value);
|
|
3677
|
+
}
|
|
3678
|
+
function buildEditPayload(model, prompt, images, source) {
|
|
3679
|
+
if (source.mask !== void 0) throw new XaiImagesRequestError("xAI image edits do not support mask");
|
|
3680
|
+
if (images.length === 0) throw new XaiImagesRequestError("image is required");
|
|
3681
|
+
if (images.length > MAX_EDIT_IMAGES) throw new XaiImagesRequestError(`xAI image edits accept at most ${MAX_EDIT_IMAGES} images`);
|
|
3682
|
+
const payload = {
|
|
3683
|
+
model,
|
|
3684
|
+
prompt
|
|
3685
|
+
};
|
|
3686
|
+
applyCommonImageFields(payload, source);
|
|
3687
|
+
if (images.length === 1) payload.image = images[0];
|
|
3688
|
+
else payload.images = images;
|
|
3689
|
+
return payload;
|
|
3690
|
+
}
|
|
3691
|
+
function formOptions(formData) {
|
|
3692
|
+
const source = {};
|
|
3693
|
+
for (const key of [
|
|
3694
|
+
"n",
|
|
3695
|
+
"quality",
|
|
3696
|
+
"response_format",
|
|
3697
|
+
"aspect_ratio",
|
|
3698
|
+
"resolution",
|
|
3699
|
+
"size",
|
|
3700
|
+
"stream",
|
|
3701
|
+
"mask"
|
|
3702
|
+
]) {
|
|
3703
|
+
const value = formData.get(key);
|
|
3704
|
+
if (typeof value === "string") source[key] = value;
|
|
3705
|
+
}
|
|
3706
|
+
if (formData.get("mask") !== null || formData.get("mask[]") !== null) source.mask = true;
|
|
3707
|
+
return source;
|
|
3708
|
+
}
|
|
3709
|
+
/**
|
|
3710
|
+
* Translates an OpenAI image generation body into an xAI Imagine generation
|
|
3711
|
+
* request. The configured model replaces the client model.
|
|
3712
|
+
*/
|
|
3713
|
+
function translateXaiImageGeneration(payload, model) {
|
|
3714
|
+
if (!isRecord$3(payload)) throw new XaiImagesRequestError("Image generation body must be a JSON object");
|
|
3715
|
+
const translated = {
|
|
3716
|
+
model,
|
|
3717
|
+
prompt: readPrompt(payload.prompt)
|
|
3718
|
+
};
|
|
3719
|
+
applyCommonImageFields(translated, payload);
|
|
3720
|
+
return translated;
|
|
3721
|
+
}
|
|
3722
|
+
/**
|
|
3723
|
+
* Translates an OpenAI JSON image edit body into an xAI Imagine edit request.
|
|
3724
|
+
*/
|
|
3725
|
+
function translateXaiImageEditJson(payload, model) {
|
|
3726
|
+
if (!isRecord$3(payload)) throw new XaiImagesRequestError("Image edit body must be a JSON object");
|
|
3727
|
+
return buildEditPayload(model, readPrompt(payload.prompt), collectJsonImages(payload).map((image) => readImageReference(image)), payload);
|
|
3728
|
+
}
|
|
3729
|
+
/**
|
|
3730
|
+
* Translates an OpenAI multipart image edit into an xAI Imagine edit request.
|
|
3731
|
+
* File parts become base64 data URLs. A mask cannot be represented and is
|
|
3732
|
+
* rejected.
|
|
3733
|
+
*/
|
|
3734
|
+
async function translateXaiImageEditForm(formData, model) {
|
|
3735
|
+
const images = [];
|
|
3736
|
+
for (const [name, value] of formData.entries()) {
|
|
3737
|
+
if (!IMAGE_FIELD_NAMES.has(name)) continue;
|
|
3738
|
+
images.push(await formValueToImageReference(value));
|
|
3739
|
+
}
|
|
3740
|
+
return buildEditPayload(model, readPrompt(formData.get("prompt")), images, formOptions(formData));
|
|
3741
|
+
}
|
|
3742
|
+
const MAX_RESPONSE_IMAGE_BYTES = 33554432;
|
|
3743
|
+
function responseHasB64Json(value) {
|
|
3744
|
+
return typeof value === "string" && value.trim() !== "";
|
|
3745
|
+
}
|
|
3746
|
+
function imageCreatedTimestamp(value) {
|
|
3747
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) return Math.trunc(value);
|
|
3748
|
+
return Math.floor(Date.now() / 1e3);
|
|
3749
|
+
}
|
|
3750
|
+
function trustedImageUrl(value) {
|
|
3751
|
+
if (typeof value !== "string" || !value.trim()) return null;
|
|
3752
|
+
try {
|
|
3753
|
+
const url = new URL(value.trim());
|
|
3754
|
+
if (url.protocol !== "https:" || url.hostname !== "imgen.x.ai" || url.port || url.username || url.password) return null;
|
|
3755
|
+
return url.toString();
|
|
3756
|
+
} catch {
|
|
3757
|
+
return null;
|
|
3758
|
+
}
|
|
3759
|
+
}
|
|
3760
|
+
async function readImageBytes(response) {
|
|
3761
|
+
const declaredLength = Number(response.headers.get("content-length"));
|
|
3762
|
+
if (Number.isFinite(declaredLength) && declaredLength > MAX_RESPONSE_IMAGE_BYTES) {
|
|
3763
|
+
await response.body?.cancel().catch(() => {});
|
|
3764
|
+
return null;
|
|
3765
|
+
}
|
|
3766
|
+
if (!response.body) return null;
|
|
3767
|
+
const reader = response.body.getReader();
|
|
3768
|
+
const chunks = [];
|
|
3769
|
+
let length = 0;
|
|
3770
|
+
let complete = false;
|
|
3771
|
+
try {
|
|
3772
|
+
while (true) {
|
|
3773
|
+
const { done, value } = await reader.read();
|
|
3774
|
+
if (done) {
|
|
3775
|
+
complete = true;
|
|
3776
|
+
return length > 0 ? Buffer.concat(chunks, length) : null;
|
|
3777
|
+
}
|
|
3778
|
+
length += value.byteLength;
|
|
3779
|
+
if (length > MAX_RESPONSE_IMAGE_BYTES) return null;
|
|
3780
|
+
chunks.push(value);
|
|
3781
|
+
}
|
|
3782
|
+
} finally {
|
|
3783
|
+
if (!complete) await reader.cancel().catch(() => {});
|
|
3784
|
+
reader.releaseLock();
|
|
3785
|
+
}
|
|
3786
|
+
}
|
|
3787
|
+
function imageDownloadErrorResponse() {
|
|
3788
|
+
return Response.json({ error: {
|
|
3789
|
+
message: "Failed to retrieve xAI image output as b64_json",
|
|
3790
|
+
type: "upstream_error"
|
|
3791
|
+
} }, { status: 502 });
|
|
3792
|
+
}
|
|
3793
|
+
function imageOutputUrlForLog(value) {
|
|
3794
|
+
if (typeof value !== "string" || !value.trim()) return null;
|
|
3795
|
+
try {
|
|
3796
|
+
const url = new URL(value.trim());
|
|
3797
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") return null;
|
|
3798
|
+
url.username = "";
|
|
3799
|
+
url.password = "";
|
|
3800
|
+
for (const key of url.searchParams.keys()) if (/token|secret|signature|credential|password|authorization|api[_-]?key|^key$|^sig$/iu.test(key)) url.searchParams.set(key, "[REDACTED]");
|
|
3801
|
+
return url.toString();
|
|
3802
|
+
} catch {
|
|
3803
|
+
return null;
|
|
3804
|
+
}
|
|
3805
|
+
}
|
|
3806
|
+
function rebuildJsonResponse(response, body) {
|
|
3807
|
+
const headers = new Headers(response.headers);
|
|
3808
|
+
headers.delete("content-length");
|
|
3809
|
+
if (!headers.get("content-type")) headers.set("content-type", "application/json");
|
|
3810
|
+
return new Response(body, {
|
|
3811
|
+
headers,
|
|
3812
|
+
status: response.status,
|
|
3813
|
+
statusText: response.statusText
|
|
3814
|
+
});
|
|
3815
|
+
}
|
|
3816
|
+
/**
|
|
3817
|
+
* Adapts an xAI image body to the OpenAI Images response.
|
|
3818
|
+
* Generation returns created: null, and edits omit created. OpenAI clients
|
|
3819
|
+
* require a unix timestamp. When b64_json is requested, a URL-only result
|
|
3820
|
+
* is downloaded from the xAI CDN with redirects disabled and a byte limit.
|
|
3821
|
+
*/
|
|
3822
|
+
async function ensureImageResponseB64Json(response, fetchImage = globalThis.fetch, { clientSignal, onImageUrl, responseFormat = "b64_json" } = {}) {
|
|
3823
|
+
if (!response.ok) return response;
|
|
3824
|
+
clientSignal?.throwIfAborted();
|
|
3825
|
+
const body = await response.text();
|
|
3826
|
+
let payload;
|
|
3827
|
+
try {
|
|
3828
|
+
payload = JSON.parse(body);
|
|
3829
|
+
} catch {
|
|
3830
|
+
return rebuildJsonResponse(response, body);
|
|
3831
|
+
}
|
|
3832
|
+
if (!isRecord$3(payload) || !Array.isArray(payload.data)) return rebuildJsonResponse(response, body);
|
|
3833
|
+
let changed = false;
|
|
3834
|
+
const data = [];
|
|
3835
|
+
for (const item of payload.data) {
|
|
3836
|
+
if (isRecord$3(item) && onImageUrl) {
|
|
3837
|
+
const outputUrl = imageOutputUrlForLog(item.url);
|
|
3838
|
+
if (outputUrl) onImageUrl(outputUrl);
|
|
3839
|
+
}
|
|
3840
|
+
if (!isRecord$3(item) || responseFormat === "url" || responseHasB64Json(item.b64_json)) {
|
|
3841
|
+
data.push(item);
|
|
3842
|
+
continue;
|
|
3843
|
+
}
|
|
3844
|
+
const url = trustedImageUrl(item.url);
|
|
3845
|
+
if (!url) return imageDownloadErrorResponse();
|
|
3846
|
+
try {
|
|
3847
|
+
clientSignal?.throwIfAborted();
|
|
3848
|
+
const timeoutSignal = AbortSignal.timeout(6e4);
|
|
3849
|
+
const imageResponse = await fetchImage(url, {
|
|
3850
|
+
method: "GET",
|
|
3851
|
+
redirect: "error",
|
|
3852
|
+
signal: clientSignal ? AbortSignal.any([clientSignal, timeoutSignal]) : timeoutSignal
|
|
3853
|
+
});
|
|
3854
|
+
if (!imageResponse.ok) {
|
|
3855
|
+
await imageResponse.body?.cancel().catch(() => {});
|
|
3856
|
+
return imageDownloadErrorResponse();
|
|
3857
|
+
}
|
|
3858
|
+
const bytes = await readImageBytes(imageResponse);
|
|
3859
|
+
if (!bytes) return imageDownloadErrorResponse();
|
|
3860
|
+
data.push({
|
|
3861
|
+
...item,
|
|
3862
|
+
b64_json: bytes.toString("base64")
|
|
3863
|
+
});
|
|
3864
|
+
changed = true;
|
|
3865
|
+
} catch {
|
|
3866
|
+
clientSignal?.throwIfAborted();
|
|
3867
|
+
return imageDownloadErrorResponse();
|
|
3868
|
+
}
|
|
3869
|
+
}
|
|
3870
|
+
const created = imageCreatedTimestamp(payload.created);
|
|
3871
|
+
if (!changed && payload.created === created) return rebuildJsonResponse(response, body);
|
|
3872
|
+
return rebuildJsonResponse(response, JSON.stringify({
|
|
3873
|
+
...payload,
|
|
3874
|
+
created,
|
|
3875
|
+
data
|
|
3876
|
+
}));
|
|
3877
|
+
}
|
|
3878
|
+
//#endregion
|
|
3879
|
+
//#region src/routes/images/xai-handler.ts
|
|
3880
|
+
function xaiImagesRequestErrorResponse(c, error) {
|
|
3881
|
+
if (!(error instanceof XaiImagesRequestError)) return null;
|
|
3882
|
+
return c.json({ error: {
|
|
3883
|
+
message: error.message,
|
|
3884
|
+
type: "invalid_request_error"
|
|
3885
|
+
} }, 400);
|
|
3886
|
+
}
|
|
3887
|
+
async function readJsonBody(request, message) {
|
|
3888
|
+
try {
|
|
3889
|
+
const body = new TextDecoder("utf-8", { fatal: true }).decode(new Uint8Array(await request.arrayBuffer()));
|
|
3890
|
+
return JSON.parse(body);
|
|
3891
|
+
} catch {
|
|
3892
|
+
throw new XaiImagesRequestError(message);
|
|
3893
|
+
}
|
|
3894
|
+
}
|
|
3895
|
+
async function forwardTranslatedXaiImages(c, operation, payload) {
|
|
3896
|
+
const providerConfig = await imageRouteDependencies.resolveProviderConfig("xai");
|
|
3897
|
+
if (!providerConfig) return c.json({ error: {
|
|
3898
|
+
message: "Provider 'xai' not found or disabled",
|
|
3899
|
+
type: "invalid_request_error"
|
|
3900
|
+
} }, 404);
|
|
3901
|
+
return await forwardXaiImages(providerConfig, c.req.raw, operation, payload);
|
|
3902
|
+
}
|
|
3903
|
+
async function forwardXaiImages(providerConfig, request, operation, payload) {
|
|
3904
|
+
return await ensureImageResponseB64Json(await forwardProviderImagesWithLogging(providerConfig, new Request(request.url, {
|
|
3905
|
+
body: JSON.stringify(payload),
|
|
3906
|
+
headers: {
|
|
3907
|
+
accept: "application/json",
|
|
3908
|
+
"content-type": "application/json"
|
|
3909
|
+
},
|
|
3910
|
+
method: "POST",
|
|
3911
|
+
signal: request.signal
|
|
3912
|
+
}), operation, {
|
|
3913
|
+
logger: logger$14,
|
|
3914
|
+
provider: "xai"
|
|
3915
|
+
}), void 0, {
|
|
3916
|
+
clientSignal: request.signal,
|
|
3917
|
+
onImageUrl: (url) => {
|
|
3918
|
+
logger$14.info("images.xai.output.url", JSON.stringify({
|
|
3919
|
+
operation,
|
|
3920
|
+
url
|
|
3921
|
+
}));
|
|
3922
|
+
},
|
|
3923
|
+
responseFormat: payload.response_format === "url" ? "url" : "b64_json"
|
|
3924
|
+
});
|
|
3925
|
+
}
|
|
3926
|
+
async function handleXaiImageGeneration(c, model) {
|
|
3927
|
+
return await forwardTranslatedXaiImages(c, "generations", translateXaiImageGeneration(await readJsonBody(c.req.raw, "Image generation body must be JSON"), model));
|
|
3928
|
+
}
|
|
3929
|
+
async function handleXaiImageEdit(c, model, parsed) {
|
|
3930
|
+
if (parsed instanceof Request) return await forwardTranslatedXaiImages(c, "edits", translateXaiImageEditJson(await readJsonBody(parsed, "Image edit body must be JSON"), model));
|
|
3931
|
+
const { staged } = parsed;
|
|
3932
|
+
let payload;
|
|
3933
|
+
try {
|
|
3934
|
+
payload = await translateXaiImageEditForm(staged.formData, model);
|
|
3935
|
+
} finally {
|
|
3936
|
+
await staged.cleanup();
|
|
3937
|
+
}
|
|
3938
|
+
return await forwardTranslatedXaiImages(c, "edits", payload);
|
|
3939
|
+
}
|
|
3940
|
+
//#endregion
|
|
3398
3941
|
//#region src/routes/images/temp-form-data.ts
|
|
3399
3942
|
const logger$13 = createHandlerLogger("images-temp-form-data");
|
|
3400
3943
|
const multipartStagingDependencies = { createWriteStream };
|
|
@@ -3664,7 +4207,13 @@ async function parseEditsRequest(request) {
|
|
|
3664
4207
|
}
|
|
3665
4208
|
async function handleImagesEdits(c) {
|
|
3666
4209
|
try {
|
|
4210
|
+
const configuredModel = imageRouteDependencies.getImageEditModel();
|
|
4211
|
+
if (configuredModel.status === "invalid") return c.json({ error: {
|
|
4212
|
+
message: configuredModel.message,
|
|
4213
|
+
type: "invalid_request_error"
|
|
4214
|
+
} }, 400);
|
|
3667
4215
|
const parsed = await parseEditsRequest(c.req.raw);
|
|
4216
|
+
if (configuredModel.status === "xai") return await handleXaiImageEdit(c, configuredModel.model, parsed);
|
|
3668
4217
|
if (parsed instanceof Request) return await handleCodexImages(c, "edits", void 0, parsed);
|
|
3669
4218
|
const { model, requestHeaders, staged } = parsed;
|
|
3670
4219
|
try {
|
|
@@ -3683,6 +4232,8 @@ async function handleImagesEdits(c) {
|
|
|
3683
4232
|
message: error.message,
|
|
3684
4233
|
type: "invalid_request_error"
|
|
3685
4234
|
} }, error instanceof MultipartBodyTooLargeError ? 413 : 400);
|
|
4235
|
+
const invalidRequest = xaiImagesRequestErrorResponse(c, error);
|
|
4236
|
+
if (invalidRequest) return invalidRequest;
|
|
3686
4237
|
logger$14.error("images.edits.error", { error });
|
|
3687
4238
|
return await forwardError(c, error);
|
|
3688
4239
|
}
|
|
@@ -3723,10 +4274,18 @@ async function parseGenerationsRequest(request) {
|
|
|
3723
4274
|
}
|
|
3724
4275
|
async function handleImagesGenerations(c) {
|
|
3725
4276
|
try {
|
|
4277
|
+
const configuredModel = imageRouteDependencies.getImageGenerationModel();
|
|
4278
|
+
if (configuredModel.status === "invalid") return c.json({ error: {
|
|
4279
|
+
message: configuredModel.message,
|
|
4280
|
+
type: "invalid_request_error"
|
|
4281
|
+
} }, 400);
|
|
4282
|
+
if (configuredModel.status === "xai") return await handleXaiImageGeneration(c, configuredModel.model);
|
|
3726
4283
|
const parsed = await parseGenerationsRequest(c.req.raw);
|
|
3727
4284
|
if (parsed instanceof Request) return await handleCodexImages(c, "generations", void 0, parsed);
|
|
3728
4285
|
return await routeImagesRequest(c, "generations", parsed);
|
|
3729
4286
|
} catch (error) {
|
|
4287
|
+
const invalidRequest = xaiImagesRequestErrorResponse(c, error);
|
|
4288
|
+
if (invalidRequest) return invalidRequest;
|
|
3730
4289
|
logger$14.error("images.generations.error", { error });
|
|
3731
4290
|
return await forwardError(c, error);
|
|
3732
4291
|
}
|
|
@@ -13074,4 +13633,4 @@ createServer();
|
|
|
13074
13633
|
//#endregion
|
|
13075
13634
|
export { createServer };
|
|
13076
13635
|
|
|
13077
|
-
//# sourceMappingURL=server-
|
|
13636
|
+
//# sourceMappingURL=server-04fZfBz1.js.map
|