openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
195 lines (194 loc) • 7.06 kB
JavaScript
import { l as normalizeStringEntries } from "./string-normalization-WNUDCpXX.js";
import "./string-coerce-runtime-CEGJWkQ_.js";
import { C as withRemoteHttpResponse, T as formatUnavailableBatchError, _ as buildBatchHeaders, b as applyEmbeddingBatchOutputLine, d as uploadBatchJsonlFile, f as resolveBatchCompletionFromStatus, g as runEmbeddingBatchGroups, h as buildEmbeddingBatchGroupOptions, m as throwIfBatchTerminalFailure, p as resolveCompletedBatchResult, v as normalizeBatchBaseUrl, w as extractBatchErrorMessage, x as postJsonWithRetry, y as EMBEDDING_BATCH_ENDPOINT } from "./memory-core-host-engine-embeddings-nTgmnTkX.js";
import { Readable } from "node:stream";
import { createInterface } from "node:readline";
//#region extensions/voyage/embedding-batch.ts
const VOYAGE_BATCH_ENDPOINT = EMBEDDING_BATCH_ENDPOINT;
const VOYAGE_BATCH_COMPLETION_WINDOW = "12h";
const VOYAGE_BATCH_MAX_REQUESTS = 5e4;
function resolveVoyageBatchDeps(overrides) {
return {
now: overrides?.now ?? Date.now,
sleep: overrides?.sleep ?? (async (ms) => await new Promise((resolve) => {
setTimeout(resolve, ms);
})),
postJsonWithRetry: overrides?.postJsonWithRetry ?? postJsonWithRetry,
uploadBatchJsonlFile: overrides?.uploadBatchJsonlFile ?? uploadBatchJsonlFile,
withRemoteHttpResponse: overrides?.withRemoteHttpResponse ?? withRemoteHttpResponse
};
}
async function assertVoyageResponseOk(res, context) {
if (!res.ok) {
const text = await res.text();
throw new Error(`${context}: ${res.status} ${text}`);
}
}
function buildVoyageBatchRequest(params) {
return {
url: `${normalizeBatchBaseUrl(params.client)}/${params.path}`,
ssrfPolicy: params.client.ssrfPolicy,
init: { headers: buildBatchHeaders(params.client, { json: true }) },
onResponse: params.onResponse
};
}
async function submitVoyageBatch(params) {
const baseUrl = normalizeBatchBaseUrl(params.client);
const inputFileId = await params.deps.uploadBatchJsonlFile({
client: params.client,
requests: params.requests,
errorPrefix: "voyage batch file upload failed"
});
return await params.deps.postJsonWithRetry({
url: `${baseUrl}/batches`,
headers: buildBatchHeaders(params.client, { json: true }),
ssrfPolicy: params.client.ssrfPolicy,
body: {
input_file_id: inputFileId,
endpoint: VOYAGE_BATCH_ENDPOINT,
completion_window: VOYAGE_BATCH_COMPLETION_WINDOW,
request_params: {
model: params.client.model,
input_type: "document"
},
metadata: {
source: "clawdbot-memory",
agent: params.agentId
}
},
errorPrefix: "voyage batch create failed"
});
}
async function fetchVoyageBatchStatus(params) {
return await params.deps.withRemoteHttpResponse(buildVoyageBatchRequest({
client: params.client,
path: `batches/${params.batchId}`,
onResponse: async (res) => {
await assertVoyageResponseOk(res, "voyage batch status failed");
return await res.json();
}
}));
}
async function readVoyageBatchError(params) {
try {
return await params.deps.withRemoteHttpResponse(buildVoyageBatchRequest({
client: params.client,
path: `files/${params.errorFileId}/content`,
onResponse: async (res) => {
await assertVoyageResponseOk(res, "voyage batch error file content failed");
const text = await res.text();
if (!text.trim()) return;
return extractBatchErrorMessage(normalizeStringEntries(text.split("\n")).map((line) => JSON.parse(line)));
}
}));
} catch (err) {
return formatUnavailableBatchError(err);
}
}
async function waitForVoyageBatch(params) {
const start = params.deps.now();
let current = params.initial;
while (true) {
const status = current ?? await fetchVoyageBatchStatus({
client: params.client,
batchId: params.batchId,
deps: params.deps
});
const state = status.status ?? "unknown";
if (state === "completed") return resolveBatchCompletionFromStatus({
provider: "voyage",
batchId: params.batchId,
status
});
await throwIfBatchTerminalFailure({
provider: "voyage",
status: {
...status,
id: params.batchId
},
readError: async (errorFileId) => await readVoyageBatchError({
client: params.client,
errorFileId,
deps: params.deps
})
});
if (!params.wait) throw new Error(`voyage batch ${params.batchId} still ${state}; wait disabled`);
if (params.deps.now() - start > params.timeoutMs) throw new Error(`voyage batch ${params.batchId} timed out after ${params.timeoutMs}ms`);
params.debug?.(`voyage batch ${params.batchId} ${state}; waiting ${params.pollIntervalMs}ms`);
await params.deps.sleep(params.pollIntervalMs);
current = void 0;
}
}
async function runVoyageEmbeddingBatches(params) {
const deps = resolveVoyageBatchDeps(params.deps);
return await runEmbeddingBatchGroups({
...buildEmbeddingBatchGroupOptions(params, {
maxRequests: VOYAGE_BATCH_MAX_REQUESTS,
debugLabel: "memory embeddings: voyage batch submit"
}),
runGroup: async ({ group, groupIndex, groups, byCustomId, pollIntervalMs, timeoutMs }) => {
const batchInfo = await submitVoyageBatch({
client: params.client,
requests: group,
agentId: params.agentId,
deps
});
if (!batchInfo.id) throw new Error("voyage batch create failed: missing batch id");
const batchId = batchInfo.id;
params.debug?.("memory embeddings: voyage batch created", {
batchId: batchInfo.id,
status: batchInfo.status,
group: groupIndex + 1,
groups,
requests: group.length
});
const completed = await resolveCompletedBatchResult({
provider: "voyage",
status: batchInfo,
wait: params.wait,
waitForBatch: async () => await waitForVoyageBatch({
client: params.client,
batchId,
wait: params.wait,
pollIntervalMs,
timeoutMs,
debug: params.debug,
initial: batchInfo,
deps
})
});
const baseUrl = normalizeBatchBaseUrl(params.client);
const errors = [];
const remaining = new Set(group.map((request) => request.custom_id));
await deps.withRemoteHttpResponse({
url: `${baseUrl}/files/${completed.outputFileId}/content`,
ssrfPolicy: params.client.ssrfPolicy,
init: { headers: buildBatchHeaders(params.client, { json: true }) },
onResponse: async (contentRes) => {
if (!contentRes.ok) {
const text = await contentRes.text();
throw new Error(`voyage batch file content failed: ${contentRes.status} ${text}`);
}
if (!contentRes.body) return;
const reader = createInterface({
input: Readable.fromWeb(contentRes.body),
terminal: false
});
for await (const rawLine of reader) {
if (!rawLine.trim()) continue;
applyEmbeddingBatchOutputLine({
line: JSON.parse(rawLine),
remaining,
errors,
byCustomId
});
}
}
});
if (errors.length > 0) throw new Error(`voyage batch ${batchInfo.id} failed: ${errors.join("; ")}`);
if (remaining.size > 0) throw new Error(`voyage batch ${batchInfo.id} missing ${remaining.size} embedding responses`);
}
});
}
//#endregion
export { runVoyageEmbeddingBatches as t };