@sema-agent/core
Version:
Stateless, task-oriented AI agent core
87 lines (86 loc) • 4.45 kB
JavaScript
import { ensureToolTurnInvariants } from "../internal/llm.js";
import { resolveTaskModel } from "./roles.js";
import { readDegradation } from "../brain/degrading.js";
import { cacheFamilyOf, uncachedInputTokensOf } from "./runner/usage-accounting.js";
import { providerReportedModel } from "./message-utils.js";
import { providerToolCallId } from "../brain/tool-call-id.js";
function normalizeSideQueryUsage(model, u) {
const input = uncachedInputTokensOf(cacheFamilyOf(model), u);
return input === u.input ? u : { ...u, input };
}
export async function runSideQuery(spec, deps) {
if (!Array.isArray(spec.messages) || spec.messages.length === 0) {
throw new Error("sideQuery requires a non-empty messages array");
}
const resolved = resolveTaskModel({
...(spec.model !== undefined ? { model: spec.model } : {}),
...(spec.modelRole !== undefined ? { modelRole: spec.modelRole } : {}),
...(spec.thinking !== undefined ? { thinking: spec.thinking } : {}),
}, deps);
const now = Date.now();
const normalize = (m) => {
const timestamp = typeof m.timestamp === "number" ? m.timestamp : now;
if (m.role === "assistant") {
const content = typeof m.content === "string" ? [{ type: "text", text: m.content }] : m.content;
return {
role: "assistant",
content,
timestamp,
api: m.api ?? "openai-completions",
provider: m.provider ?? "side-query-input",
model: m.model ?? "side-query-input",
usage: m.usage ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } },
stopReason: m.stopReason ?? "stop",
};
}
if (m.role === "toolResult") {
const details = m.details;
return { role: "toolResult", toolCallId: m.toolCallId, toolName: m.toolName, content: m.content, isError: m.isError ?? false, timestamp, ...(details !== undefined ? { details } : {}) };
}
return { role: "user", content: m.content, timestamp };
};
const effectiveSystemPrompt = spec.systemPrompt ?? resolved.systemPrompt;
const context = {
...(effectiveSystemPrompt !== undefined ? { systemPrompt: effectiveSystemPrompt } : {}),
messages: ensureToolTurnInvariants(spec.messages.map(normalize)).messages,
...(spec.tools !== undefined && spec.tools.length > 0
? { tools: spec.tools.map((t) => ({ name: t.name, description: t.description, parameters: t.parameters })) }
: {}),
};
const thinking = resolved.thinking ?? resolved.model.defaultThinking;
const auth = await spec.getApiKeyAndHeaders?.(resolved.model);
const options = {
...(spec.signal !== undefined ? { signal: spec.signal } : {}),
...(spec.maxOutputTokens !== undefined ? { maxTokens: spec.maxOutputTokens } : {}),
...(thinking !== undefined ? { reasoning: thinking } : {}),
...(auth?.apiKey !== undefined ? { apiKey: auth.apiKey } : {}),
...(auth?.headers !== undefined ? { headers: auth.headers } : {}),
};
const stream = await Promise.resolve(deps.brain.stream(resolved.model, context, options));
const msg = (await stream.result());
const text = msg.content
.filter((c) => c.type === "text")
.map((c) => c.text)
.join("");
const toolCalls = msg.content
.filter((c) => c.type === "toolCall")
.map((c) => {
const wireId = providerToolCallId(c);
return { id: c.id, ...(wireId !== c.id ? { wireId } : {}), name: c.name, args: c.arguments };
});
const degraded = readDegradation(msg);
const servedModel = typeof msg.model === "string" && msg.model.length > 0 ? msg.model : resolved.model.id;
const reportedModel = providerReportedModel(msg);
return {
text,
toolCalls,
model: resolved.model.id,
servedModel,
...(degraded !== undefined ? { degraded } : {}),
...(reportedModel !== undefined ? { responseModel: reportedModel } : {}),
usage: normalizeSideQueryUsage(resolved.model, msg.usage),
...(msg.usageMissing === true ? { usageMissing: true } : {}),
stopReason: msg.stopReason,
...(msg.errorMessage !== undefined ? { errorMessage: msg.errorMessage } : {}),
};
}