agents
Version:
A home for your AI agents
317 lines (316 loc) • 9.89 kB
JavaScript
//#region src/harness/ai-sdk/turns.ts
function ownerOf(toolName, { tools, owner }) {
const tool = tools && Object.hasOwn(tools, toolName) ? tools[toolName] : void 0;
return tool && !tool.execute ? owner : void 0;
}
const json = (value) => value ?? null;
const object = (value) => typeof value === "object" && value !== null ? value : void 0;
function withMetadata(value, metadata) {
const providerMetadata = object(metadata);
return providerMetadata ? {
...value,
providerMetadata
} : value;
}
let warned = false;
function warnSkipped(type) {
if (warned) return;
warned = true;
console.warn(`Channels skipped an AI SDK part it cannot carry: "${type}"`);
}
/**
* Convert an AI SDK UI message stream into response chunks.
*
* An `error` or `abort` chunk makes the iteration throw once the stream
* ends, so a failed or aborted generation never reads as a finished one, while the stream still
* runs to its end and its `onEnd` sees the partial message.
*/
async function* toResponseChunks(stream, options = {}) {
let error;
for await (const chunk of stream) {
if (chunk.type === "error") error ??= new Error(chunk.errorText);
if (chunk.type === "abort") error ??= /* @__PURE__ */ new Error("The generation was aborted");
const converted = toResponseChunk(chunk, options);
if (converted) yield converted;
}
if (error) throw error;
}
/** Convert one AI SDK UI message chunk into a response chunk, if it has one. */
function toResponseChunk(chunk, options) {
switch (chunk.type) {
case "text-start":
case "text-end":
case "reasoning-start":
case "reasoning-end": return withMetadata({
type: chunk.type,
id: chunk.id
}, chunk.providerMetadata);
case "text-delta":
case "reasoning-delta": return {
type: chunk.type,
id: chunk.id,
delta: chunk.delta
};
case "tool-input-start": return tool(chunk, options, { type: "tool-input-start" });
case "tool-input-delta": return {
type: "tool-input-delta",
toolCallId: chunk.toolCallId,
delta: chunk.inputTextDelta
};
case "tool-input-available": return withMetadata(tool(chunk, options, {
type: "tool-input-available",
input: json(chunk.input)
}), chunk.providerMetadata);
case "tool-input-error": {
const { owner: _owner, ...call } = tool(chunk, {}, {});
return {
...call,
type: "tool-input-error",
input: json(chunk.input),
errorText: chunk.errorText
};
}
case "tool-approval-request": return {
type: "tool-approval-request",
toolCallId: chunk.toolCallId,
approvalId: chunk.approvalId
};
case "tool-output-available": return {
type: "tool-output-available",
toolCallId: chunk.toolCallId,
output: json(chunk.output),
...chunk.preliminary && { preliminary: true },
...chunk.providerExecuted && { providerExecuted: true }
};
case "tool-output-error": return {
type: "tool-output-error",
toolCallId: chunk.toolCallId,
errorText: chunk.errorText,
...chunk.providerExecuted && { providerExecuted: true }
};
case "tool-output-denied": return {
type: "tool-output-denied",
toolCallId: chunk.toolCallId
};
case "source-url":
case "source-document":
case "file": {
const { providerMetadata, ...part } = chunk;
return withMetadata(part, providerMetadata);
}
case "start":
case "finish": return chunk.messageMetadata === void 0 ? void 0 : metadata(chunk.messageMetadata);
case "message-metadata": return metadata(chunk.messageMetadata);
case "start-step":
case "finish-step":
case "tool-approval-response":
case "error":
case "abort": return;
default:
if (chunk.type.startsWith("data-") && "data" in chunk) return {
type: "data",
name: chunk.type.slice(5),
...chunk.id !== void 0 && { id: chunk.id },
data: json(chunk.data),
...chunk.transient && { transient: true }
};
warnSkipped(chunk.type);
return;
}
}
function tool(chunk, options, rest) {
const owner = ownerOf(chunk.toolName, options);
return {
toolCallId: chunk.toolCallId,
toolName: chunk.toolName,
...owner !== void 0 && { owner },
...chunk.providerExecuted && { providerExecuted: true },
...chunk.dynamic && { dynamic: true },
...chunk.title !== void 0 && { title: chunk.title },
...rest
};
}
function metadata(value) {
const converted = object(value);
return converted ? {
type: "metadata",
metadata: converted
} : void 0;
}
/**
* Convert a response chunk into an AI SDK UI message chunk. Fields that only
* Channels has, such as a tool call's `owner`, are dropped.
*/
function toUIMessageChunk(chunk) {
switch (chunk.type) {
case "tool-input-start":
case "tool-input-available": {
const { owner: _owner, ...rest } = chunk;
return rest;
}
case "tool-input-delta": return {
type: "tool-input-delta",
toolCallId: chunk.toolCallId,
inputTextDelta: chunk.delta
};
case "data": {
const { name, ...rest } = chunk;
return {
...rest,
type: `data-${name}`
};
}
case "metadata": return {
type: "message-metadata",
messageMetadata: chunk.metadata
};
default: return chunk;
}
}
/** Convert a saved AI SDK UI message into a transcript message. */
function toTranscriptMessage(message, options = {}) {
const metadata = object(message.metadata);
return {
id: message.id,
role: message.role,
parts: message.parts.flatMap((part) => {
const converted = toMessagePart(part, options);
return converted ? [converted] : [];
}),
...metadata && { metadata }
};
}
function toMessagePart(part, options) {
switch (part.type) {
case "text":
case "reasoning": return withMetadata({
type: part.type,
text: part.text
}, part.providerMetadata);
case "file":
case "source-url":
case "source-document": {
const { providerMetadata, ...rest } = part;
if ("providerReference" in rest) delete rest.providerReference;
return withMetadata(rest, providerMetadata);
}
case "step-start": return;
case "dynamic-tool": return toToolPart(part, part.toolName, true, options);
default:
if (part.type.startsWith("tool-") && "toolCallId" in part) return toToolPart(part, part.type.slice(5), false, options);
if (part.type.startsWith("data-") && "data" in part) return {
type: "data",
name: part.type.slice(5),
...part.id !== void 0 && { id: part.id },
data: json(part.data)
};
warnSkipped(part.type);
return;
}
}
function toToolPart(part, toolName, dynamic, options) {
const owner = ownerOf(toolName, options);
return withMetadata({
type: "tool",
toolCallId: part.toolCallId,
toolName,
state: part.state,
...part.input !== void 0 && { input: json(part.input) },
...part.state === "output-available" && {
output: json(part.output),
...part.preliminary && { preliminary: true }
},
...part.state === "output-error" && { errorText: part.errorText },
...part.approval && { approval: {
id: part.approval.id,
...part.approval.approved !== void 0 && { approved: part.approval.approved },
...part.approval.reason !== void 0 && { reason: part.approval.reason }
} },
...owner !== void 0 && { owner },
...part.providerExecuted && { providerExecuted: true },
...dynamic && { dynamic: true },
...part.title !== void 0 && { title: part.title }
}, part.callProviderMetadata);
}
/** Convert a transcript message into an AI SDK UI message. */
function toUIMessage(message) {
return {
id: message.id,
role: message.role,
parts: message.parts.map(toUIPart),
...message.metadata && { metadata: message.metadata }
};
}
function toUIPart(part) {
switch (part.type) {
case "data": {
const { name, ...rest } = part;
return {
...rest,
type: `data-${name}`
};
}
case "tool": {
const { type: _type, toolName, dynamic, owner: _owner, providerMetadata, ...rest } = part;
const call = {
...rest,
...providerMetadata && { callProviderMetadata: providerMetadata }
};
return dynamic ? {
...call,
type: "dynamic-tool",
toolName
} : {
...call,
type: `tool-${toolName}`
};
}
default: return part;
}
}
/** Whether the message has a tool call waiting for a result or an approval. */
function awaitsInput(message) {
return message.parts.some((part) => "toolCallId" in part && (part.state === "input-available" || part.state === "approval-requested"));
}
/**
* Record a tool result or approval response on the tool call it answers.
* Returns the updated message, or undefined when no call is waiting for it,
* so the first answer wins.
*/
function answerToolCall(messages, event) {
for (const message of messages) {
const index = message.parts.findIndex((part) => answers(part, event));
if (index === -1) continue;
const parts = [...message.parts];
const part = parts[index];
parts[index] = event.type === "approval-response" ? {
...part,
state: event.approved ? "approval-responded" : "output-denied",
approval: {
id: event.approvalId,
approved: event.approved,
...event.reason !== void 0 && { reason: event.reason }
}
} : event.type === "tool-result" && event.result.ok ? {
...part,
state: "output-available",
output: event.result.output
} : {
...part,
state: "output-error",
errorText: event.type === "tool-result" && !event.result.ok ? event.result.errorText ?? "" : ""
};
return {
...message,
parts
};
}
}
function answers(part, event) {
if (!("toolCallId" in part)) return false;
if (event.type === "tool-result") return part.toolCallId === event.toolCallId && (part.state === "input-available" || part.state === "approval-responded");
return event.type === "approval-response" && part.state === "approval-requested" && part.approval.id === event.approvalId;
}
//#endregion
export { toTranscriptMessage as a, toResponseChunks as i, awaitsInput as n, toUIMessage as o, toResponseChunk as r, toUIMessageChunk as s, answerToolCall as t };
//# sourceMappingURL=turns-DCV3ycDl.js.map