709 lines
22 KiB
TypeScript
709 lines
22 KiB
TypeScript
import {
|
|
BaseExecutor,
|
|
mergeAbortSignals,
|
|
mergeUpstreamExtraHeaders,
|
|
type ExecuteInput,
|
|
} from "./base.ts";
|
|
import { FETCH_TIMEOUT_MS } from "../config/constants.ts";
|
|
import { normalizeSessionCookieHeader } from "@/lib/providers/webCookieAuth";
|
|
import { prepareToolMessages, buildToolAwareResult } from "../translator/webTools.ts";
|
|
|
|
const BLACKBOX_CHAT_API = "https://app.blackbox.ai/api/chat";
|
|
const BLACKBOX_DEFAULT_COOKIE = "next-auth.session-token";
|
|
const BLACKBOX_USER_AGENT =
|
|
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36";
|
|
|
|
const SESSION_CACHE_TTL_MS = 5 * 60_000; // 5 minutes
|
|
|
|
/**
|
|
* Resolve the `validated` token for Blackbox `/api/chat` requests.
|
|
*
|
|
* Blackbox's web frontend ships a real validation token (exported as `tk` from
|
|
* its Next.js JavaScript chunks). If the value sent in `transformedBody.validated`
|
|
* does not match that token, the upstream returns HTTP 403 even when the session
|
|
* cookie and subscription are valid — see issue #2252.
|
|
*
|
|
* Resolution priority:
|
|
* 1. `BLACKBOX_WEB_VALIDATED_TOKEN` env var (operator-supplied, preferred)
|
|
* 2. Random UUID fallback (the original behavior; works only as long as
|
|
* Blackbox doesn't enforce a specific frontend `tk` value)
|
|
*
|
|
* We do NOT scrape Blackbox's Next.js chunks at runtime to extract `tk` — that
|
|
* coupling to their bundle hash is fragile and would silently break on every
|
|
* frontend deploy. The env-var override gives operators who have figured out
|
|
* the token a stable way to use it without patching code.
|
|
*/
|
|
export function resolveBlackboxValidatedToken(): string {
|
|
const explicit = (process.env.BLACKBOX_WEB_VALIDATED_TOKEN || "").trim();
|
|
if (explicit) return explicit;
|
|
return crypto.randomUUID();
|
|
}
|
|
|
|
/**
|
|
* Detect whether a Blackbox 403 response body indicates that the `validated`
|
|
* token is the problem (as opposed to a missing cookie or expired subscription).
|
|
* Surfaces the BLACKBOX_WEB_VALIDATED_TOKEN env var as the next step.
|
|
*/
|
|
function isBlackboxValidatedTokenError(responseText: string): boolean {
|
|
const lower = (responseText || "").toLowerCase();
|
|
return (
|
|
lower.includes("invalid validated token") ||
|
|
lower.includes("invalid validated") ||
|
|
lower.includes("validation token") ||
|
|
lower.includes("invalid token")
|
|
);
|
|
}
|
|
|
|
type CachedSession = {
|
|
sessionData: Record<string, unknown> | null;
|
|
subscriptionCache: Record<string, unknown> | null;
|
|
teamAccount: string;
|
|
fetchedAt: number;
|
|
};
|
|
|
|
const MAX_SESSIONS = 100;
|
|
const sessionCache = new Map<string, CachedSession>();
|
|
|
|
type BlackboxMessage = {
|
|
id: string;
|
|
role: "user" | "assistant";
|
|
content: string;
|
|
};
|
|
|
|
function extractMessageText(content: unknown): string {
|
|
if (typeof content === "string") {
|
|
return content.trim();
|
|
}
|
|
|
|
if (!Array.isArray(content)) {
|
|
return "";
|
|
}
|
|
|
|
return content
|
|
.map((part) => {
|
|
if (!part || typeof part !== "object") return "";
|
|
const item = part as Record<string, unknown>;
|
|
if (item.type === "text" && typeof item.text === "string") {
|
|
return item.text;
|
|
}
|
|
if (item.type === "input_text" && typeof item.text === "string") {
|
|
return item.text;
|
|
}
|
|
return "";
|
|
})
|
|
.filter((part) => part.trim().length > 0)
|
|
.join("\n")
|
|
.trim();
|
|
}
|
|
|
|
function parseOpenAIMessages(
|
|
messages: Array<Record<string, unknown>>,
|
|
chatId: string
|
|
): BlackboxMessage[] {
|
|
const systemParts: string[] = [];
|
|
const parsed: BlackboxMessage[] = [];
|
|
|
|
for (const message of messages) {
|
|
const role = String(message.role || "user");
|
|
const content = extractMessageText(message.content);
|
|
if (!content) continue;
|
|
|
|
if (role === "system" || role === "developer") {
|
|
systemParts.push(content);
|
|
continue;
|
|
}
|
|
|
|
if (role === "assistant" || role === "user") {
|
|
parsed.push({
|
|
id: role === "user" ? chatId : crypto.randomUUID(),
|
|
role,
|
|
content,
|
|
});
|
|
}
|
|
}
|
|
|
|
if (systemParts.length > 0) {
|
|
const prefix = `System instructions:\n${systemParts.join("\n\n")}`;
|
|
const firstUserIndex = parsed.findIndex((message) => message.role === "user");
|
|
if (firstUserIndex >= 0) {
|
|
parsed[firstUserIndex] = {
|
|
...parsed[firstUserIndex],
|
|
content: `${prefix}\n\n${parsed[firstUserIndex].content}`,
|
|
};
|
|
} else {
|
|
parsed.unshift({
|
|
id: chatId,
|
|
role: "user",
|
|
content: prefix,
|
|
});
|
|
}
|
|
}
|
|
|
|
return parsed;
|
|
}
|
|
|
|
function estimateTokens(text: string): number {
|
|
return Math.max(1, Math.ceil((text || "").length / 4));
|
|
}
|
|
|
|
function sseChunk(data: unknown): string {
|
|
return `data: ${JSON.stringify(data)}\n\n`;
|
|
}
|
|
|
|
async function readTextResponse(
|
|
body: ReadableStream<Uint8Array>,
|
|
signal?: AbortSignal | null
|
|
): Promise<string> {
|
|
const reader = body.getReader();
|
|
const decoder = new TextDecoder();
|
|
let text = "";
|
|
|
|
try {
|
|
while (true) {
|
|
if (signal?.aborted) {
|
|
throw signal.reason ?? new DOMException("Aborted", "AbortError");
|
|
}
|
|
|
|
const { value, done } = await reader.read();
|
|
if (done) break;
|
|
text += decoder.decode(value, { stream: true });
|
|
}
|
|
|
|
text += decoder.decode();
|
|
return text;
|
|
} finally {
|
|
reader.releaseLock();
|
|
}
|
|
}
|
|
|
|
function buildStreamingResponse(
|
|
responseText: string,
|
|
model: string,
|
|
id: string,
|
|
created: number
|
|
): ReadableStream<Uint8Array> {
|
|
const encoder = new TextEncoder();
|
|
|
|
return new ReadableStream(
|
|
{
|
|
start(controller) {
|
|
controller.enqueue(
|
|
encoder.encode(
|
|
sseChunk({
|
|
id,
|
|
object: "chat.completion.chunk",
|
|
created,
|
|
model,
|
|
system_fingerprint: null,
|
|
choices: [
|
|
{
|
|
index: 0,
|
|
delta: { role: "assistant" },
|
|
finish_reason: null,
|
|
logprobs: null,
|
|
},
|
|
],
|
|
})
|
|
)
|
|
);
|
|
|
|
if (responseText) {
|
|
controller.enqueue(
|
|
encoder.encode(
|
|
sseChunk({
|
|
id,
|
|
object: "chat.completion.chunk",
|
|
created,
|
|
model,
|
|
system_fingerprint: null,
|
|
choices: [
|
|
{
|
|
index: 0,
|
|
delta: { content: responseText },
|
|
finish_reason: null,
|
|
logprobs: null,
|
|
},
|
|
],
|
|
})
|
|
)
|
|
);
|
|
}
|
|
|
|
controller.enqueue(
|
|
encoder.encode(
|
|
sseChunk({
|
|
id,
|
|
object: "chat.completion.chunk",
|
|
created,
|
|
model,
|
|
system_fingerprint: null,
|
|
choices: [{ index: 0, delta: {}, finish_reason: "stop", logprobs: null }],
|
|
})
|
|
)
|
|
);
|
|
controller.enqueue(encoder.encode("data: [DONE]\n\n"));
|
|
controller.close();
|
|
},
|
|
},
|
|
{ highWaterMark: 16384 }
|
|
);
|
|
}
|
|
|
|
function buildNonStreamingResponse(
|
|
responseText: string,
|
|
model: string,
|
|
id: string,
|
|
created: number
|
|
) {
|
|
const completionTokens = estimateTokens(responseText);
|
|
|
|
return new Response(
|
|
JSON.stringify({
|
|
id,
|
|
object: "chat.completion",
|
|
created,
|
|
model,
|
|
system_fingerprint: null,
|
|
choices: [
|
|
{
|
|
index: 0,
|
|
message: { role: "assistant", content: responseText },
|
|
finish_reason: "stop",
|
|
logprobs: null,
|
|
},
|
|
],
|
|
usage: {
|
|
prompt_tokens: completionTokens,
|
|
completion_tokens: completionTokens,
|
|
total_tokens: completionTokens * 2,
|
|
},
|
|
}),
|
|
{
|
|
status: 200,
|
|
headers: { "Content-Type": "application/json" },
|
|
}
|
|
);
|
|
}
|
|
|
|
export function normalizeBlackboxCookieHeader(apiKey: string): string {
|
|
return normalizeSessionCookieHeader(apiKey, BLACKBOX_DEFAULT_COOKIE);
|
|
}
|
|
|
|
export class BlackboxWebExecutor extends BaseExecutor {
|
|
constructor() {
|
|
super("blackbox-web", { id: "blackbox-web", baseUrl: BLACKBOX_CHAT_API });
|
|
}
|
|
|
|
async execute({
|
|
model,
|
|
body,
|
|
stream,
|
|
credentials,
|
|
signal,
|
|
log,
|
|
upstreamExtraHeaders,
|
|
}: ExecuteInput) {
|
|
const bodyObj = (body || {}) as Record<string, unknown>;
|
|
const messages = bodyObj.messages as Array<Record<string, unknown>> | undefined;
|
|
if (!messages || !Array.isArray(messages) || messages.length === 0) {
|
|
const errorResponse = new Response(
|
|
JSON.stringify({
|
|
error: {
|
|
message: "Missing or empty messages array",
|
|
type: "invalid_request",
|
|
},
|
|
}),
|
|
{ status: 400, headers: { "Content-Type": "application/json" } }
|
|
);
|
|
return {
|
|
response: errorResponse,
|
|
url: BLACKBOX_CHAT_API,
|
|
headers: {},
|
|
transformedBody: body,
|
|
};
|
|
}
|
|
|
|
const { hasTools, requestedTools, effectiveMessages } = prepareToolMessages(
|
|
bodyObj,
|
|
messages as Array<{ role: string; content: unknown }>
|
|
);
|
|
const chatId = crypto.randomUUID().slice(0, 7);
|
|
const parsedMessages = parseOpenAIMessages(effectiveMessages, chatId);
|
|
if (parsedMessages.length === 0) {
|
|
const errorResponse = new Response(
|
|
JSON.stringify({
|
|
error: {
|
|
message: "Empty query after processing messages",
|
|
type: "invalid_request",
|
|
},
|
|
}),
|
|
{ status: 400, headers: { "Content-Type": "application/json" } }
|
|
);
|
|
return {
|
|
response: errorResponse,
|
|
url: BLACKBOX_CHAT_API,
|
|
headers: {},
|
|
transformedBody: body,
|
|
};
|
|
}
|
|
|
|
const cookieHeader = normalizeBlackboxCookieHeader(credentials.apiKey || "");
|
|
const baseHeaders: Record<string, string> = {
|
|
Accept: "application/json",
|
|
Cookie: cookieHeader,
|
|
Origin: "https://app.blackbox.ai",
|
|
"User-Agent": BLACKBOX_USER_AGENT,
|
|
};
|
|
|
|
// Fetch session + subscription — Blackbox requires these in the request body.
|
|
// Cached per cookie to avoid redundant round-trips on every request.
|
|
let sessionData: Record<string, unknown> | null = null;
|
|
let subscriptionCache: Record<string, unknown> | null = null;
|
|
let teamAccount = "";
|
|
|
|
const cacheKey = cookieHeader;
|
|
const cached = sessionCache.get(cacheKey);
|
|
if (cached && Date.now() - cached.fetchedAt < SESSION_CACHE_TTL_MS) {
|
|
sessionData = cached.sessionData;
|
|
subscriptionCache = cached.subscriptionCache;
|
|
teamAccount = cached.teamAccount;
|
|
log?.debug?.("BLACKBOX-WEB", `Session cache hit (${teamAccount || "no email"})`);
|
|
} else {
|
|
const sideSignal = signal
|
|
? mergeAbortSignals(signal, AbortSignal.timeout(10_000))
|
|
: AbortSignal.timeout(10_000);
|
|
|
|
try {
|
|
const sessionRes = await fetch("https://app.blackbox.ai/api/auth/session", {
|
|
method: "GET",
|
|
headers: { ...baseHeaders, Accept: "application/json" },
|
|
signal: sideSignal,
|
|
});
|
|
sessionData = sessionRes.ok ? ((await sessionRes.json()) as Record<string, unknown>) : null;
|
|
const email = (sessionData as any)?.user?.email as string | undefined;
|
|
teamAccount = email || "";
|
|
log?.debug?.("BLACKBOX-WEB", `Session email: ${email ?? "none"}`);
|
|
|
|
if (email) {
|
|
const subRes = await fetch("https://app.blackbox.ai/api/check-subscription", {
|
|
method: "POST",
|
|
headers: { ...baseHeaders, "Content-Type": "application/json" },
|
|
body: JSON.stringify({ email }),
|
|
signal: sideSignal,
|
|
});
|
|
const rawSub = subRes.ok ? ((await subRes.json()) as Record<string, unknown>) : null;
|
|
if (rawSub) {
|
|
subscriptionCache = {
|
|
status: rawSub.hasActiveSubscription ? "PREMIUM" : "FREE",
|
|
customerId: rawSub.customerId ?? null,
|
|
expiryTimestamp: rawSub.expiryTimestamp ?? null,
|
|
lastChecked: Date.now(),
|
|
isTrialSubscription: rawSub.isTrialSubscription ?? false,
|
|
hasPaymentVerificationFailure: false,
|
|
verificationFailureTimestamp: null,
|
|
requiresAuthentication: false,
|
|
isTeam: rawSub.isTeam ?? false,
|
|
numSeats: rawSub.numSeats ?? 1,
|
|
provider: rawSub.provider ?? null,
|
|
previouslySubscribed: rawSub.previouslySubscribed ?? false,
|
|
activeInsuffientCredits: rawSub.activeInsuffientCredits ?? false,
|
|
};
|
|
log?.debug?.("BLACKBOX-WEB", `Subscription: ${subscriptionCache.status}`);
|
|
}
|
|
}
|
|
|
|
sessionCache.set(cacheKey, {
|
|
sessionData,
|
|
subscriptionCache,
|
|
teamAccount,
|
|
fetchedAt: Date.now(),
|
|
});
|
|
while (sessionCache.size > MAX_SESSIONS) {
|
|
const oldestKey = sessionCache.keys().next().value;
|
|
if (oldestKey !== undefined) sessionCache.delete(oldestKey);
|
|
else break;
|
|
}
|
|
} catch (diagErr) {
|
|
log?.debug?.("BLACKBOX-WEB", `Session/subscription fetch failed (non-fatal): ${diagErr}`);
|
|
}
|
|
}
|
|
|
|
const headers: Record<string, string> = {
|
|
...baseHeaders,
|
|
Accept: "text/plain, */*",
|
|
"Content-Type": "application/json",
|
|
Referer: `https://app.blackbox.ai/chat/${chatId}`,
|
|
};
|
|
mergeUpstreamExtraHeaders(headers, upstreamExtraHeaders);
|
|
|
|
const transformedBody = {
|
|
messages: parsedMessages,
|
|
id: chatId,
|
|
previewToken: null,
|
|
userId: credentials.providerSpecificData?.userId ?? null,
|
|
codeModelMode: true,
|
|
trendingAgentMode: {},
|
|
isMicMode: false,
|
|
userSystemPrompt: null,
|
|
maxTokens: Number((body as Record<string, unknown>).max_tokens) || 1024,
|
|
playgroundTopP: null,
|
|
playgroundTemperature: null,
|
|
isChromeExt: false,
|
|
githubToken: "",
|
|
clickedAnswer2: false,
|
|
clickedAnswer3: false,
|
|
clickedForceWebSearch: false,
|
|
visitFromDelta: false,
|
|
isMemoryEnabled: false,
|
|
mobileClient: false,
|
|
userSelectedModel: model || null,
|
|
userSelectedAgent: "VscodeAgent",
|
|
// Issue #2252: prefer operator-supplied BLACKBOX_WEB_VALIDATED_TOKEN over
|
|
// a random UUID — Blackbox's `/api/chat` rejects mismatched tokens with 403.
|
|
validated: resolveBlackboxValidatedToken(),
|
|
imageGenerationMode: false,
|
|
imageGenMode: "autoMode",
|
|
webSearchModePrompt: false,
|
|
deepSearchMode: false,
|
|
promptSelection: "",
|
|
domains: null,
|
|
vscodeClient: false,
|
|
codeInterpreterMode: false,
|
|
customProfile: {
|
|
name: "",
|
|
occupation: "",
|
|
traits: [],
|
|
additionalInfo: "",
|
|
enableNewChats: false,
|
|
},
|
|
webSearchModeOption: {
|
|
autoMode: true,
|
|
webMode: false,
|
|
offlineMode: false,
|
|
},
|
|
session: sessionData,
|
|
isPremium: subscriptionCache
|
|
? subscriptionCache.status === "PREMIUM"
|
|
: (credentials.providerSpecificData?.isPremium ?? true),
|
|
teamAccount,
|
|
subscriptionCache,
|
|
beastMode: false,
|
|
reasoningMode: false,
|
|
designerMode: false,
|
|
workspaceId: "",
|
|
asyncMode: false,
|
|
integrations: {},
|
|
isTaskPersistent: false,
|
|
selectedElement: null,
|
|
};
|
|
|
|
const timeoutSignal = AbortSignal.timeout(FETCH_TIMEOUT_MS);
|
|
const combinedSignal = signal ? mergeAbortSignals(signal, timeoutSignal) : timeoutSignal;
|
|
|
|
let upstreamResponse: Response;
|
|
try {
|
|
upstreamResponse = await fetch(BLACKBOX_CHAT_API, {
|
|
method: "POST",
|
|
headers,
|
|
body: JSON.stringify(transformedBody),
|
|
signal: combinedSignal,
|
|
});
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
log?.error?.("BLACKBOX-WEB", `Fetch failed: ${message}`);
|
|
const errorResponse = new Response(
|
|
JSON.stringify({
|
|
error: {
|
|
message: `Blackbox Web connection failed: ${message}`,
|
|
type: "upstream_error",
|
|
},
|
|
}),
|
|
{ status: 502, headers: { "Content-Type": "application/json" } }
|
|
);
|
|
return {
|
|
response: errorResponse,
|
|
url: BLACKBOX_CHAT_API,
|
|
headers,
|
|
transformedBody,
|
|
};
|
|
}
|
|
|
|
if (!upstreamResponse.ok) {
|
|
const status = upstreamResponse.status;
|
|
let message = `Blackbox Web returned HTTP ${status}`;
|
|
// Issue #2252: distinguish "wrong validated token" from "expired cookie"
|
|
// when 403 carries a token-specific body — the fix is different in each case.
|
|
const errorBody = await upstreamResponse.text().catch(() => "");
|
|
if (status === 403 && isBlackboxValidatedTokenError(errorBody)) {
|
|
message =
|
|
"Blackbox Web rejected the request with an invalid `validated` token. " +
|
|
"If you have a valid frontend token (the `tk` value from app.blackbox.ai's " +
|
|
"Next.js bundle), set BLACKBOX_WEB_VALIDATED_TOKEN in your environment and restart.";
|
|
} else if (status === 401 || status === 403) {
|
|
message =
|
|
"Blackbox Web auth failed — your app.blackbox.ai session cookie may be missing or expired.";
|
|
} else if (status === 429) {
|
|
message = "Blackbox Web rate limited the session. Wait a moment and retry.";
|
|
}
|
|
const errorResponse = new Response(
|
|
JSON.stringify({
|
|
error: {
|
|
message,
|
|
type: "upstream_error",
|
|
code: `HTTP_${status}`,
|
|
},
|
|
}),
|
|
{ status, headers: { "Content-Type": "application/json" } }
|
|
);
|
|
return {
|
|
response: errorResponse,
|
|
url: BLACKBOX_CHAT_API,
|
|
headers,
|
|
transformedBody,
|
|
};
|
|
}
|
|
|
|
if (!upstreamResponse.body) {
|
|
const errorResponse = new Response(
|
|
JSON.stringify({
|
|
error: {
|
|
message: "Blackbox Web returned an empty response body",
|
|
type: "upstream_error",
|
|
},
|
|
}),
|
|
{ status: 502, headers: { "Content-Type": "application/json" } }
|
|
);
|
|
return {
|
|
response: errorResponse,
|
|
url: BLACKBOX_CHAT_API,
|
|
headers,
|
|
transformedBody,
|
|
};
|
|
}
|
|
|
|
const responseText = (await readTextResponse(upstreamResponse.body, signal)).trim();
|
|
|
|
log?.debug?.("BLACKBOX-WEB", `Response (first 300 chars): ${responseText.slice(0, 300)}`);
|
|
log?.debug?.("BLACKBOX-WEB", `userSelectedModel sent: ${transformedBody.userSelectedModel}`);
|
|
log?.debug?.("BLACKBOX-WEB", `isPremium sent: ${transformedBody.isPremium}`);
|
|
|
|
// Blackbox sometimes returns HTTP 200 with in-band error messages instead of proper HTTP status codes.
|
|
// Detect known error patterns and surface them as real errors.
|
|
const lowerText = responseText.toLowerCase();
|
|
const isSubscriptionError =
|
|
/not upgraded|upgrade to a premium plan|upgrade.required/i.test(responseText) ||
|
|
lowerText.includes("please upgrade");
|
|
const isAuthError =
|
|
/please login|login required|authentication required/i.test(responseText) &&
|
|
!isSubscriptionError;
|
|
const isRateLimit = /rate limit|too many requests/i.test(responseText) && !isSubscriptionError;
|
|
|
|
if (isSubscriptionError) {
|
|
log?.warn?.("BLACKBOX-WEB", "Blackbox returned subscription error in response body");
|
|
const errorResponse = new Response(
|
|
JSON.stringify({
|
|
error: {
|
|
message:
|
|
"Blackbox reports your account lacks a premium subscription. " +
|
|
"If you have a paid plan, re-paste your session cookie from app.blackbox.ai.",
|
|
type: "upstream_error",
|
|
code: "BLACKBOX_SUBSCRIPTION_REQUIRED",
|
|
},
|
|
}),
|
|
{ status: 402, headers: { "Content-Type": "application/json" } }
|
|
);
|
|
return { response: errorResponse, url: BLACKBOX_CHAT_API, headers, transformedBody };
|
|
}
|
|
|
|
if (isAuthError) {
|
|
log?.warn?.("BLACKBOX-WEB", "Blackbox returned auth error in response body");
|
|
const errorResponse = new Response(
|
|
JSON.stringify({
|
|
error: {
|
|
message:
|
|
"Blackbox session is not authenticated — re-paste next-auth.session-token from app.blackbox.ai",
|
|
type: "upstream_error",
|
|
code: "BLACKBOX_AUTH_REQUIRED",
|
|
},
|
|
}),
|
|
{ status: 401, headers: { "Content-Type": "application/json" } }
|
|
);
|
|
return { response: errorResponse, url: BLACKBOX_CHAT_API, headers, transformedBody };
|
|
}
|
|
|
|
if (isRateLimit) {
|
|
log?.warn?.("BLACKBOX-WEB", "Blackbox returned rate-limit error in response body");
|
|
const errorResponse = new Response(
|
|
JSON.stringify({
|
|
error: {
|
|
message: "Blackbox Web rate limited the session. Wait a moment and retry.",
|
|
type: "upstream_error",
|
|
code: "BLACKBOX_RATE_LIMIT",
|
|
},
|
|
}),
|
|
{ status: 429, headers: { "Content-Type": "application/json" } }
|
|
);
|
|
return { response: errorResponse, url: BLACKBOX_CHAT_API, headers, transformedBody };
|
|
}
|
|
|
|
const id = `chatcmpl-blackbox-${crypto.randomUUID().slice(0, 12)}`;
|
|
const created = Math.floor(Date.now() / 1000);
|
|
|
|
if (hasTools) {
|
|
const { content, toolCalls, finishReason } = buildToolAwareResult(
|
|
responseText,
|
|
requestedTools,
|
|
"bbx"
|
|
);
|
|
if (toolCalls) {
|
|
const toolResponse = new Response(
|
|
JSON.stringify({
|
|
id,
|
|
object: "chat.completion",
|
|
created,
|
|
model,
|
|
choices: [
|
|
{
|
|
index: 0,
|
|
message: { role: "assistant", content: null, tool_calls: toolCalls },
|
|
finish_reason: finishReason,
|
|
},
|
|
],
|
|
}),
|
|
{ status: 200, headers: { "Content-Type": "application/json" } }
|
|
);
|
|
return { response: toolResponse, url: BLACKBOX_CHAT_API, headers, transformedBody };
|
|
}
|
|
const finalResponse = stream
|
|
? new Response(buildStreamingResponse(content, model, id, created), {
|
|
status: 200,
|
|
headers: {
|
|
"Content-Type": "text/event-stream",
|
|
"Cache-Control": "no-cache",
|
|
"X-Accel-Buffering": "no",
|
|
},
|
|
})
|
|
: buildNonStreamingResponse(content, model, id, created);
|
|
return { response: finalResponse, url: BLACKBOX_CHAT_API, headers, transformedBody };
|
|
}
|
|
|
|
const finalResponse = stream
|
|
? new Response(buildStreamingResponse(responseText, model, id, created), {
|
|
status: 200,
|
|
headers: {
|
|
"Content-Type": "text/event-stream",
|
|
"Cache-Control": "no-cache",
|
|
"X-Accel-Buffering": "no",
|
|
},
|
|
})
|
|
: buildNonStreamingResponse(responseText, model, id, created);
|
|
|
|
return {
|
|
response: finalResponse,
|
|
url: BLACKBOX_CHAT_API,
|
|
headers,
|
|
transformedBody,
|
|
};
|
|
}
|
|
}
|