UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

87 lines (86 loc) • 4.45 kB
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 } : {}), }; }