refactor(fixtures): move image and text mocks
This commit is contained in:
parent
ba65248dc2
commit
6a2cf98404
77
src/fixtures/images.js
Normal file
77
src/fixtures/images.js
Normal file
@ -0,0 +1,77 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { DEFAULT_IMAGE_MODEL, PLUGIN_ID } from "../constants.js";
|
||||
|
||||
const KITCHEN_SINK_OFFICE_IMAGE_FILE = "kitchen_sink_office.png";
|
||||
const KITCHEN_SINK_OFFICE_IMAGE = readFileSync(
|
||||
new URL(`../assets/${KITCHEN_SINK_OFFICE_IMAGE_FILE}`, import.meta.url),
|
||||
);
|
||||
const KITCHEN_SINK_OFFICE_SHA256 = sha256Hex(KITCHEN_SINK_OFFICE_IMAGE);
|
||||
|
||||
const KITCHEN_IMAGE_FIXTURES = [
|
||||
{
|
||||
id: "office-lobby-sink",
|
||||
label: "Kitchen Sink Office",
|
||||
assetName: KITCHEN_SINK_OFFICE_IMAGE_FILE,
|
||||
buffer: KITCHEN_SINK_OFFICE_IMAGE,
|
||||
sha256: KITCHEN_SINK_OFFICE_SHA256,
|
||||
mimeType: "image/png",
|
||||
width: 1024,
|
||||
height: 1024,
|
||||
description: "office lobby scene with a lobster-costumed figure holding a real sink",
|
||||
source: "bundled-real-image",
|
||||
},
|
||||
];
|
||||
|
||||
export function createKitchenSinkImageAsset({
|
||||
prompt,
|
||||
jobId,
|
||||
scenario = "image.generate",
|
||||
model = DEFAULT_IMAGE_MODEL,
|
||||
}) {
|
||||
const fixture = selectKitchenImageFixture(prompt);
|
||||
const buffer = Buffer.from(fixture.buffer);
|
||||
const seed = stableHash(`${jobId}:${prompt}:${fixture.id}`);
|
||||
return {
|
||||
buffer,
|
||||
mimeType: fixture.mimeType,
|
||||
fileName: `${jobId}.png`,
|
||||
dataUrl: `data:${fixture.mimeType};base64,${buffer.toString("base64")}`,
|
||||
revisedPrompt: `Kitchen Sink office image fixture: ${prompt}`,
|
||||
metadata: {
|
||||
kitchenSink: true,
|
||||
assetId: fixture.id,
|
||||
assetName: fixture.assetName,
|
||||
source: fixture.source,
|
||||
model,
|
||||
width: fixture.width,
|
||||
height: fixture.height,
|
||||
sizeBytes: buffer.byteLength,
|
||||
sha256: fixture.sha256,
|
||||
contentHash: fixture.sha256.slice(0, 16),
|
||||
seed,
|
||||
finishReason: "success",
|
||||
pluginId: PLUGIN_ID,
|
||||
scenarioId: scenario,
|
||||
jobId,
|
||||
prompt,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function selectKitchenImageFixture(_prompt) {
|
||||
return KITCHEN_IMAGE_FIXTURES[0];
|
||||
}
|
||||
|
||||
function sha256Hex(buffer) {
|
||||
return createHash("sha256").update(buffer).digest("hex");
|
||||
}
|
||||
|
||||
function stableHash(input) {
|
||||
let hash = 2166136261;
|
||||
for (let index = 0; index < input.length; index += 1) {
|
||||
hash ^= input.charCodeAt(index);
|
||||
hash = Math.imul(hash, 16777619);
|
||||
}
|
||||
return (hash >>> 0).toString(16).padStart(8, "0");
|
||||
}
|
||||
205
src/fixtures/text.js
Normal file
205
src/fixtures/text.js
Normal file
@ -0,0 +1,205 @@
|
||||
import {
|
||||
DEFAULT_IMAGE_MODEL,
|
||||
DEFAULT_MEDIA_MODEL,
|
||||
DEFAULT_TEXT_MODEL,
|
||||
IMAGE_PROVIDER_ID,
|
||||
TEXT_PROVIDER_ID,
|
||||
WEB_FETCH_PROVIDER_ID,
|
||||
WEB_SEARCH_PROVIDER_ID,
|
||||
} from "../constants.js";
|
||||
|
||||
export function kitchenTextProviderConfig() {
|
||||
return {
|
||||
baseUrl: "kitchen-sink://local",
|
||||
apiKey: "kitchen-sink-local-fixture",
|
||||
auth: "token",
|
||||
api: "kitchen-sink",
|
||||
models: [kitchenTextModelDefinition()],
|
||||
};
|
||||
}
|
||||
|
||||
export function kitchenTextModelDefinition() {
|
||||
return {
|
||||
id: DEFAULT_TEXT_MODEL,
|
||||
name: "Kitchen Sink Text Fixture",
|
||||
api: "kitchen-sink",
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 8192,
|
||||
maxTokens: 2048,
|
||||
description: "Deterministic OpenClaw plugin text-provider fixture.",
|
||||
};
|
||||
}
|
||||
|
||||
export function createKitchenTextStream(model, context) {
|
||||
const stream = createAssistantMessageEventStream();
|
||||
queueMicrotask(() => {
|
||||
const prompt = extractLastUserPrompt(context);
|
||||
const text = kitchenTextResponse(prompt);
|
||||
const message = {
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text }],
|
||||
api: model?.api || "kitchen-sink",
|
||||
provider: TEXT_PROVIDER_ID,
|
||||
model: model?.id || DEFAULT_TEXT_MODEL,
|
||||
usage: estimateUsage(prompt, text),
|
||||
stopReason: "stop",
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
stream.push({ type: "start", partial: { ...message, content: [] } });
|
||||
stream.push({ type: "text_start", contentIndex: 0, partial: { ...message, content: [] } });
|
||||
stream.push({ type: "text_delta", contentIndex: 0, delta: text, partial: message });
|
||||
stream.push({ type: "text_end", contentIndex: 0, content: text, partial: message });
|
||||
stream.push({ type: "done", reason: "stop", message });
|
||||
stream.end(message);
|
||||
});
|
||||
return stream;
|
||||
}
|
||||
|
||||
export function kitchenTextResponse(prompt) {
|
||||
const normalized = normalizePrompt(prompt, "kitchen sink text inference");
|
||||
if (/\b(image|picture|draw|generate)\b/i.test(normalized)) {
|
||||
return [
|
||||
"Kitchen Sink text fixture:",
|
||||
`prompt="${normalized}"`,
|
||||
`I would route this to ${IMAGE_PROVIDER_ID}/${DEFAULT_IMAGE_MODEL}, create a queued image job, wait for completion, then return the bundled kitchen_sink_office.png asset with PNG metadata.`,
|
||||
].join(" ");
|
||||
}
|
||||
if (/\b(search|find|lookup|web)\b/i.test(normalized)) {
|
||||
return [
|
||||
"Kitchen Sink text fixture:",
|
||||
`prompt="${normalized}"`,
|
||||
`I would call ${WEB_SEARCH_PROVIDER_ID} for ranked fixture results and ${WEB_FETCH_PROVIDER_ID} for deterministic document fetches.`,
|
||||
].join(" ");
|
||||
}
|
||||
if (/\b(rate limit|timeout|fail|error)\b/i.test(normalized)) {
|
||||
return [
|
||||
"Kitchen Sink text fixture:",
|
||||
`prompt="${normalized}"`,
|
||||
"Failure fixtures are available: rate limit returns 429 with retry metadata, timeout returns 504, and fail returns a deterministic provider error.",
|
||||
].join(" ");
|
||||
}
|
||||
return [
|
||||
"Kitchen Sink text fixture:",
|
||||
`prompt="${normalized}"`,
|
||||
"Available realistic surfaces: direct prefix, registered tools, image provider lifecycle, media understanding, web search, web fetch, channel health, hooks, detached tasks, and text provider catalog.",
|
||||
].join(" ");
|
||||
}
|
||||
|
||||
export function kitchenImageDescription(prompt, count) {
|
||||
return [
|
||||
`Kitchen Sink media fixture described ${count || 1} image${count === 1 ? "" : "s"}.`,
|
||||
`Prompt: ${normalizePrompt(prompt, "describe kitchen sink image")}.`,
|
||||
"Visible content: the bundled kitchen_sink_office PNG: an office lobby scene with a lobster-costumed figure holding a real sink.",
|
||||
].join(" ");
|
||||
}
|
||||
|
||||
export function estimateUsage(prompt = "", text = "") {
|
||||
const input = estimateTokens(prompt);
|
||||
const output = estimateTokens(text);
|
||||
return {
|
||||
input,
|
||||
output,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
totalTokens: input + output,
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
total: 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createAssistantMessageEventStream() {
|
||||
const queue = [];
|
||||
const waiters = [];
|
||||
let done = false;
|
||||
let finalResult;
|
||||
let resolveResult;
|
||||
const resultPromise = new Promise((resolve) => {
|
||||
resolveResult = resolve;
|
||||
});
|
||||
|
||||
return {
|
||||
push(event) {
|
||||
if (done) {
|
||||
return;
|
||||
}
|
||||
if (event.type === "done" || event.type === "error") {
|
||||
finalResult = event.type === "done" ? event.message : event.error;
|
||||
done = true;
|
||||
resolveResult(finalResult);
|
||||
}
|
||||
const waiter = waiters.shift();
|
||||
if (waiter) {
|
||||
waiter({ value: event, done: false });
|
||||
} else {
|
||||
queue.push(event);
|
||||
}
|
||||
},
|
||||
end(result) {
|
||||
if (result !== undefined && finalResult === undefined) {
|
||||
finalResult = result;
|
||||
resolveResult(result);
|
||||
}
|
||||
done = true;
|
||||
while (waiters.length > 0) {
|
||||
waiters.shift()({ value: undefined, done: true });
|
||||
}
|
||||
},
|
||||
async *[Symbol.asyncIterator]() {
|
||||
while (true) {
|
||||
if (queue.length > 0) {
|
||||
yield queue.shift();
|
||||
} else if (done) {
|
||||
return;
|
||||
} else {
|
||||
const next = await new Promise((resolve) => waiters.push(resolve));
|
||||
if (next.done) {
|
||||
return;
|
||||
}
|
||||
yield next.value;
|
||||
}
|
||||
}
|
||||
},
|
||||
result() {
|
||||
return resultPromise;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function extractLastUserPrompt(context) {
|
||||
const messages = Array.isArray(context?.messages) ? context.messages : [];
|
||||
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
||||
const message = messages[index];
|
||||
if (message?.role !== "user") {
|
||||
continue;
|
||||
}
|
||||
if (typeof message.content === "string") {
|
||||
return message.content;
|
||||
}
|
||||
if (Array.isArray(message.content)) {
|
||||
const text = message.content
|
||||
.filter((item) => item?.type === "text" && typeof item.text === "string")
|
||||
.map((item) => item.text)
|
||||
.join(" ")
|
||||
.trim();
|
||||
if (text) {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
}
|
||||
return "kitchen sink text inference";
|
||||
}
|
||||
|
||||
function estimateTokens(text) {
|
||||
return Math.max(1, Math.ceil(String(text).trim().split(/\s+/).filter(Boolean).length * 1.35));
|
||||
}
|
||||
|
||||
function normalizePrompt(value, fallback) {
|
||||
const text = String(value ?? "").replace(/\s+/g, " ").trim();
|
||||
return text || fallback;
|
||||
}
|
||||
266
src/scenarios.js
266
src/scenarios.js
@ -1,16 +1,13 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { readFileSync } from "node:fs";
|
||||
import {
|
||||
CHANNEL_ACCOUNT_ID,
|
||||
CHANNEL_ID,
|
||||
COMPACTION_PROVIDER_ID,
|
||||
DEFAULT_EMBEDDING_MODEL,
|
||||
DEFAULT_IMAGE_DELAY_MS,
|
||||
DEFAULT_IMAGE_MODEL,
|
||||
DEFAULT_MEDIA_MODEL,
|
||||
DEFAULT_MUSIC_MODEL,
|
||||
DEFAULT_SPEECH_MODEL,
|
||||
DEFAULT_TEXT_MODEL,
|
||||
DEFAULT_VIDEO_MODEL,
|
||||
IMAGE_PROVIDER_ID,
|
||||
MEDIA_PROVIDER_ID,
|
||||
@ -18,13 +15,21 @@ import {
|
||||
MUSIC_PROVIDER_ID,
|
||||
PLUGIN_ID,
|
||||
REALTIME_TRANSCRIPTION_PROVIDER_ID,
|
||||
REALTIME_VOICE_PROVIDER_ID,
|
||||
SPEECH_PROVIDER_ID,
|
||||
TEXT_PROVIDER_ID,
|
||||
VIDEO_PROVIDER_ID,
|
||||
WEB_FETCH_PROVIDER_ID,
|
||||
WEB_SEARCH_PROVIDER_ID,
|
||||
} from "./constants.js";
|
||||
import { createKitchenSinkImageAsset } from "./fixtures/images.js";
|
||||
import {
|
||||
createKitchenTextStream,
|
||||
estimateUsage,
|
||||
kitchenImageDescription,
|
||||
kitchenTextModelDefinition,
|
||||
kitchenTextProviderConfig,
|
||||
kitchenTextResponse,
|
||||
} from "./fixtures/text.js";
|
||||
|
||||
export {
|
||||
CHANNEL_ACCOUNT_ID,
|
||||
@ -51,25 +56,14 @@ export {
|
||||
WEB_FETCH_PROVIDER_ID,
|
||||
WEB_SEARCH_PROVIDER_ID,
|
||||
} from "./constants.js";
|
||||
const KITCHEN_SINK_OFFICE_IMAGE_FILE = "kitchen_sink_office.png";
|
||||
const KITCHEN_SINK_OFFICE_IMAGE = readFileSync(
|
||||
new URL(`./assets/${KITCHEN_SINK_OFFICE_IMAGE_FILE}`, import.meta.url),
|
||||
);
|
||||
const KITCHEN_SINK_OFFICE_SHA256 = sha256Hex(KITCHEN_SINK_OFFICE_IMAGE);
|
||||
const KITCHEN_IMAGE_FIXTURES = [
|
||||
{
|
||||
id: "office-lobby-sink",
|
||||
label: "Kitchen Sink Office",
|
||||
assetName: KITCHEN_SINK_OFFICE_IMAGE_FILE,
|
||||
buffer: KITCHEN_SINK_OFFICE_IMAGE,
|
||||
sha256: KITCHEN_SINK_OFFICE_SHA256,
|
||||
mimeType: "image/png",
|
||||
width: 1024,
|
||||
height: 1024,
|
||||
description: "office lobby scene with a lobster-costumed figure holding a real sink",
|
||||
source: "bundled-real-image",
|
||||
},
|
||||
];
|
||||
export { createKitchenSinkImageAsset } from "./fixtures/images.js";
|
||||
export {
|
||||
createKitchenTextStream,
|
||||
kitchenImageDescription,
|
||||
kitchenTextModelDefinition,
|
||||
kitchenTextProviderConfig,
|
||||
kitchenTextResponse,
|
||||
} from "./fixtures/text.js";
|
||||
|
||||
export const KITCHEN_HUMAN_SCENARIOS = Object.freeze([
|
||||
{
|
||||
@ -329,37 +323,6 @@ export async function runKitchenScenario(runtime, request = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
export function createKitchenSinkImageAsset({ prompt, jobId, scenario = "image.generate", model = DEFAULT_IMAGE_MODEL }) {
|
||||
const fixture = selectKitchenImageFixture(prompt);
|
||||
const buffer = Buffer.from(fixture.buffer);
|
||||
const seed = stableHash(`${jobId}:${prompt}:${fixture.id}`);
|
||||
return {
|
||||
buffer,
|
||||
mimeType: fixture.mimeType,
|
||||
fileName: `${jobId}.png`,
|
||||
dataUrl: `data:${fixture.mimeType};base64,${buffer.toString("base64")}`,
|
||||
revisedPrompt: `Kitchen Sink office image fixture: ${prompt}`,
|
||||
metadata: {
|
||||
kitchenSink: true,
|
||||
assetId: fixture.id,
|
||||
assetName: fixture.assetName,
|
||||
source: fixture.source,
|
||||
model,
|
||||
width: fixture.width,
|
||||
height: fixture.height,
|
||||
sizeBytes: buffer.byteLength,
|
||||
sha256: fixture.sha256,
|
||||
contentHash: fixture.sha256.slice(0, 16),
|
||||
seed,
|
||||
finishReason: "success",
|
||||
pluginId: PLUGIN_ID,
|
||||
scenarioId: scenario,
|
||||
jobId,
|
||||
prompt,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function shouldHandleKitchenText(text) {
|
||||
return /^kitchen(?:\s|$)/i.test(String(text ?? "").trim());
|
||||
}
|
||||
@ -761,92 +724,6 @@ export function kitchenImageReply(result) {
|
||||
};
|
||||
}
|
||||
|
||||
export function kitchenTextProviderConfig() {
|
||||
return {
|
||||
baseUrl: "kitchen-sink://local",
|
||||
apiKey: "kitchen-sink-local-fixture",
|
||||
auth: "token",
|
||||
api: "kitchen-sink",
|
||||
models: [kitchenTextModelDefinition()],
|
||||
};
|
||||
}
|
||||
|
||||
export function kitchenTextModelDefinition() {
|
||||
return {
|
||||
id: DEFAULT_TEXT_MODEL,
|
||||
name: "Kitchen Sink Text Fixture",
|
||||
api: "kitchen-sink",
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 8192,
|
||||
maxTokens: 2048,
|
||||
description: "Deterministic OpenClaw plugin text-provider fixture.",
|
||||
};
|
||||
}
|
||||
|
||||
export function createKitchenTextStream(model, context) {
|
||||
const stream = createAssistantMessageEventStream();
|
||||
queueMicrotask(() => {
|
||||
const prompt = extractLastUserPrompt(context);
|
||||
const text = kitchenTextResponse(prompt);
|
||||
const message = {
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text }],
|
||||
api: model?.api || "kitchen-sink",
|
||||
provider: TEXT_PROVIDER_ID,
|
||||
model: model?.id || DEFAULT_TEXT_MODEL,
|
||||
usage: estimateUsage(prompt, text),
|
||||
stopReason: "stop",
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
stream.push({ type: "start", partial: { ...message, content: [] } });
|
||||
stream.push({ type: "text_start", contentIndex: 0, partial: { ...message, content: [] } });
|
||||
stream.push({ type: "text_delta", contentIndex: 0, delta: text, partial: message });
|
||||
stream.push({ type: "text_end", contentIndex: 0, content: text, partial: message });
|
||||
stream.push({ type: "done", reason: "stop", message });
|
||||
stream.end(message);
|
||||
});
|
||||
return stream;
|
||||
}
|
||||
|
||||
export function kitchenTextResponse(prompt) {
|
||||
const normalized = normalizePrompt(prompt, "kitchen sink text inference");
|
||||
if (/\b(image|picture|draw|generate)\b/i.test(normalized)) {
|
||||
return [
|
||||
"Kitchen Sink text fixture:",
|
||||
`prompt="${normalized}"`,
|
||||
`I would route this to ${IMAGE_PROVIDER_ID}/${DEFAULT_IMAGE_MODEL}, create a queued image job, wait for completion, then return the bundled kitchen_sink_office.png asset with PNG metadata.`,
|
||||
].join(" ");
|
||||
}
|
||||
if (/\b(search|find|lookup|web)\b/i.test(normalized)) {
|
||||
return [
|
||||
"Kitchen Sink text fixture:",
|
||||
`prompt="${normalized}"`,
|
||||
`I would call ${WEB_SEARCH_PROVIDER_ID} for ranked fixture results and ${WEB_FETCH_PROVIDER_ID} for deterministic document fetches.`,
|
||||
].join(" ");
|
||||
}
|
||||
if (/\b(rate limit|timeout|fail|error)\b/i.test(normalized)) {
|
||||
return [
|
||||
"Kitchen Sink text fixture:",
|
||||
`prompt="${normalized}"`,
|
||||
"Failure fixtures are available: rate limit returns 429 with retry metadata, timeout returns 504, and fail returns a deterministic provider error.",
|
||||
].join(" ");
|
||||
}
|
||||
return [
|
||||
"Kitchen Sink text fixture:",
|
||||
`prompt="${normalized}"`,
|
||||
"Available realistic surfaces: direct prefix, registered tools, image provider lifecycle, media understanding, web search, web fetch, channel health, hooks, detached tasks, and text provider catalog.",
|
||||
].join(" ");
|
||||
}
|
||||
|
||||
export function kitchenImageDescription(prompt, count) {
|
||||
return [
|
||||
`Kitchen Sink media fixture described ${count || 1} image${count === 1 ? "" : "s"}.`,
|
||||
`Prompt: ${normalizePrompt(prompt, "describe kitchen sink image")}.`,
|
||||
"Visible content: the bundled kitchen_sink_office PNG: an office lobby scene with a lobster-costumed figure holding a real sink.",
|
||||
].join(" ");
|
||||
}
|
||||
|
||||
export function kitchenToolSchema(promptDescription) {
|
||||
return {
|
||||
type: "object",
|
||||
@ -985,111 +862,6 @@ function transitionKitchenJob(job, status, date, patch = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
function createAssistantMessageEventStream() {
|
||||
const queue = [];
|
||||
const waiters = [];
|
||||
let done = false;
|
||||
let finalResult;
|
||||
let resolveResult;
|
||||
const resultPromise = new Promise((resolve) => {
|
||||
resolveResult = resolve;
|
||||
});
|
||||
|
||||
return {
|
||||
push(event) {
|
||||
if (done) {
|
||||
return;
|
||||
}
|
||||
if (event.type === "done" || event.type === "error") {
|
||||
finalResult = event.type === "done" ? event.message : event.error;
|
||||
done = true;
|
||||
resolveResult(finalResult);
|
||||
}
|
||||
const waiter = waiters.shift();
|
||||
if (waiter) {
|
||||
waiter({ value: event, done: false });
|
||||
} else {
|
||||
queue.push(event);
|
||||
}
|
||||
},
|
||||
end(result) {
|
||||
if (result !== undefined && finalResult === undefined) {
|
||||
finalResult = result;
|
||||
resolveResult(result);
|
||||
}
|
||||
done = true;
|
||||
while (waiters.length > 0) {
|
||||
waiters.shift()({ value: undefined, done: true });
|
||||
}
|
||||
},
|
||||
async *[Symbol.asyncIterator]() {
|
||||
while (true) {
|
||||
if (queue.length > 0) {
|
||||
yield queue.shift();
|
||||
} else if (done) {
|
||||
return;
|
||||
} else {
|
||||
const next = await new Promise((resolve) => waiters.push(resolve));
|
||||
if (next.done) {
|
||||
return;
|
||||
}
|
||||
yield next.value;
|
||||
}
|
||||
}
|
||||
},
|
||||
result() {
|
||||
return resultPromise;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function extractLastUserPrompt(context) {
|
||||
const messages = Array.isArray(context?.messages) ? context.messages : [];
|
||||
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
||||
const message = messages[index];
|
||||
if (message?.role !== "user") {
|
||||
continue;
|
||||
}
|
||||
if (typeof message.content === "string") {
|
||||
return message.content;
|
||||
}
|
||||
if (Array.isArray(message.content)) {
|
||||
const text = message.content
|
||||
.filter((item) => item?.type === "text" && typeof item.text === "string")
|
||||
.map((item) => item.text)
|
||||
.join(" ")
|
||||
.trim();
|
||||
if (text) {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
}
|
||||
return "kitchen sink text inference";
|
||||
}
|
||||
|
||||
function estimateUsage(prompt = "", text = "") {
|
||||
const input = estimateTokens(prompt);
|
||||
const output = estimateTokens(text);
|
||||
return {
|
||||
input,
|
||||
output,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
totalTokens: input + output,
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
total: 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function estimateTokens(text) {
|
||||
return Math.max(1, Math.ceil(String(text).trim().split(/\s+/).filter(Boolean).length * 1.35));
|
||||
}
|
||||
|
||||
function classifyKitchenFailure(prompt) {
|
||||
const text = String(prompt ?? "").toLowerCase();
|
||||
if (/\brate[ -]?limit|429|too many requests\b/.test(text)) {
|
||||
@ -1121,10 +893,6 @@ function classifyKitchenFailure(prompt) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function selectKitchenImageFixture(_prompt) {
|
||||
return KITCHEN_IMAGE_FIXTURES[0];
|
||||
}
|
||||
|
||||
function mediaJob(kind, id, prompt, scenarioId) {
|
||||
const createdAt = "2026-04-28T00:00:00.000Z";
|
||||
return {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user