agents
Version:
A home for your AI agents
271 lines (270 loc) • 9.84 kB
JavaScript
import { t as applyChunkToParts } from "./message-builder-BymO4N_D.js";
//#region src/chat/stream-accumulator.ts
function asMetadata(value) {
if (value != null && typeof value === "object" && !Array.isArray(value)) return value;
}
var StreamAccumulator = class {
constructor(options) {
this.messageId = options.messageId;
this._isContinuation = options.continuation ?? false;
this.parts = options.existingParts ? [...options.existingParts] : [];
this.metadata = options.existingMetadata ? { ...options.existingMetadata } : void 0;
}
applyChunk(chunk) {
const handled = applyChunkToParts(this.parts, chunk);
if (chunk.type === "tool-approval-request" && chunk.toolCallId) return {
handled,
action: {
type: "tool-approval-request",
toolCallId: chunk.toolCallId
}
};
if ((chunk.type === "tool-output-available" || chunk.type === "tool-output-error") && chunk.toolCallId) {
if (!this.parts.some((p) => "toolCallId" in p && p.toolCallId === chunk.toolCallId)) return {
handled,
action: {
type: "cross-message-tool-update",
updateType: chunk.type === "tool-output-available" ? "output-available" : "output-error",
toolCallId: chunk.toolCallId,
output: chunk.output,
errorText: chunk.errorText,
preliminary: chunk.preliminary
}
};
}
if (!handled) switch (chunk.type) {
case "start": {
if (chunk.messageId != null && !this._isContinuation) this.messageId = chunk.messageId;
const startMeta = asMetadata(chunk.messageMetadata);
if (startMeta) this.metadata = this.metadata ? {
...this.metadata,
...startMeta
} : { ...startMeta };
return {
handled: true,
action: {
type: "start",
messageId: chunk.messageId,
metadata: startMeta
}
};
}
case "finish": {
const finishMeta = asMetadata(chunk.messageMetadata);
if (finishMeta) this.metadata = this.metadata ? {
...this.metadata,
...finishMeta
} : { ...finishMeta };
return {
handled: true,
action: {
type: "finish",
finishReason: "finishReason" in chunk ? chunk.finishReason : void 0,
metadata: finishMeta
}
};
}
case "message-metadata": {
const msgMeta = asMetadata(chunk.messageMetadata);
if (msgMeta) this.metadata = this.metadata ? {
...this.metadata,
...msgMeta
} : { ...msgMeta };
return {
handled: true,
action: {
type: "message-metadata",
metadata: msgMeta ?? {}
}
};
}
case "finish-step": return { handled: true };
case "error": return {
handled: true,
action: {
type: "error",
error: chunk.errorText ?? JSON.stringify(chunk)
}
};
}
return { handled };
}
/** Snapshot the current state as a UIMessage. */
toMessage() {
return {
id: this.messageId,
role: "assistant",
parts: [...this.parts],
...this.metadata != null && { metadata: this.metadata }
};
}
/**
* Merge this accumulator's message into an existing message array.
* Handles continuation (walk backward for last assistant), replacement
* (update existing by messageId), or append (new message).
*/
mergeInto(messages) {
let existingIdx = messages.findIndex((m) => m.id === this.messageId);
if (existingIdx < 0 && this._isContinuation) {
for (let i = messages.length - 1; i >= 0; i--) if (messages[i].role === "assistant") {
existingIdx = i;
break;
}
}
const partialMessage = {
id: existingIdx >= 0 ? messages[existingIdx].id : this.messageId,
role: "assistant",
parts: [...this.parts],
...this.metadata != null && { metadata: this.metadata }
};
if (existingIdx >= 0) {
const updated = [...messages];
updated[existingIdx] = partialMessage;
return updated;
}
return [...messages, partialMessage];
}
};
//#endregion
//#region src/chat/broadcast-state.ts
function transition(state, event) {
switch (event.type) {
case "clear": return {
state: { status: "idle" },
isStreaming: false
};
case "resume-fallback": {
const accumulator = new StreamAccumulator({ messageId: event.messageId });
return {
state: {
status: "observing",
streamId: event.streamId,
accumulator
},
isStreaming: true
};
}
case "response": {
let accumulator;
const isReplayedStart = event.replay === true && event.chunkData?.type === "start";
if (state.status === "idle" || state.streamId !== event.streamId || isReplayedStart) {
let messageId = event.messageId;
let existingParts;
let existingMetadata;
if (event.continuation && event.currentMessages) {
for (let i = event.currentMessages.length - 1; i >= 0; i--) if (event.currentMessages[i].role === "assistant") {
messageId = event.currentMessages[i].id;
existingParts = [...event.currentMessages[i].parts];
if (event.currentMessages[i].metadata != null) existingMetadata = { ...event.currentMessages[i].metadata };
break;
}
}
accumulator = new StreamAccumulator({
messageId,
continuation: event.continuation,
existingParts,
existingMetadata
});
} else accumulator = state.accumulator;
if (event.chunkData) accumulator.applyChunk(event.chunkData);
let messagesUpdate;
if (event.done) {
messagesUpdate = (prev) => accumulator.mergeInto(prev);
return {
state: { status: "idle" },
messagesUpdate,
isStreaming: false
};
}
if (event.chunkData && !event.replay) messagesUpdate = (prev) => accumulator.mergeInto(prev);
else if (event.replayComplete) messagesUpdate = (prev) => accumulator.mergeInto(prev);
return {
state: {
status: "observing",
streamId: event.streamId,
accumulator
},
messagesUpdate,
isStreaming: true
};
}
}
}
//#endregion
//#region src/chat/protocol.ts
/**
* Wire protocol message type constants for the cf_agent_chat_* protocol.
*
* These are the string values used on the wire between agent servers and
* clients. Both @cloudflare/ai-chat (via its MessageType enum) and
* @cloudflare/think use these values.
*/
const STREAM_RESUME_NONE_REASONS = {
/** No active, pending, or terminal stream exists for this agent. */
IDLE: "idle",
/** An active tool continuation is owned by another live connection. */
CONTINUATION_OWNED: "continuation-owned"
};
const CHAT_MESSAGE_TYPES = {
CHAT_MESSAGES: "cf_agent_chat_messages",
USE_CHAT_REQUEST: "cf_agent_use_chat_request",
USE_CHAT_RESPONSE: "cf_agent_use_chat_response",
CHAT_CLEAR: "cf_agent_chat_clear",
CHAT_REQUEST_CANCEL: "cf_agent_chat_request_cancel",
STREAM_RESUMING: "cf_agent_stream_resuming",
STREAM_RESUME_ACK: "cf_agent_stream_resume_ack",
STREAM_RESUME_REQUEST: "cf_agent_stream_resume_request",
STREAM_RESUME_NONE: "cf_agent_stream_resume_none",
STREAM_PENDING: "cf_agent_stream_pending",
TOOL_RESULT: "cf_agent_tool_result",
TOOL_APPROVAL: "cf_agent_tool_approval",
MESSAGE_UPDATED: "cf_agent_message_updated",
CHAT_RECOVERING: "cf_agent_chat_recovering"
};
//#endregion
//#region src/chat/wire-types.ts
/**
* Enum for message types to improve type safety and maintainability
*/
let MessageType = /* @__PURE__ */ function(MessageType) {
MessageType["CF_AGENT_CHAT_MESSAGES"] = "cf_agent_chat_messages";
MessageType["CF_AGENT_USE_CHAT_REQUEST"] = "cf_agent_use_chat_request";
MessageType["CF_AGENT_USE_CHAT_RESPONSE"] = "cf_agent_use_chat_response";
MessageType["CF_AGENT_CHAT_CLEAR"] = "cf_agent_chat_clear";
MessageType["CF_AGENT_CHAT_REQUEST_CANCEL"] = "cf_agent_chat_request_cancel";
/** Sent by server when client connects and there's an active stream to resume */
MessageType["CF_AGENT_STREAM_RESUMING"] = "cf_agent_stream_resuming";
/** Sent by client to acknowledge stream resuming notification and request chunks */
MessageType["CF_AGENT_STREAM_RESUME_ACK"] = "cf_agent_stream_resume_ack";
/** Sent by client after message handler is ready, requesting stream resume check */
MessageType["CF_AGENT_STREAM_RESUME_REQUEST"] = "cf_agent_stream_resume_request";
/** Sent by server when client requests resume but no active stream exists */
MessageType["CF_AGENT_STREAM_RESUME_NONE"] = "cf_agent_stream_resume_none";
/**
* Sent by server when a turn is accepted but its resumable stream has not
* started yet (queued / debouncing / waiting on MCP / async setup). Tells a
* reconnecting client to keep waiting rather than resolve its resume probe to
* "no stream". Resolved by a later `CF_AGENT_STREAM_RESUMING` (stream started)
* or `CF_AGENT_STREAM_RESUME_NONE` (settled without streaming). See #1784.
*/
MessageType["CF_AGENT_STREAM_PENDING"] = "cf_agent_stream_pending";
/** Client sends tool result to server (for client-side tools) */
MessageType["CF_AGENT_TOOL_RESULT"] = "cf_agent_tool_result";
/** Server notifies client that a message was updated (e.g., tool result applied) */
MessageType["CF_AGENT_MESSAGE_UPDATED"] = "cf_agent_message_updated";
/** Client sends tool approval response to server (for tools with needsApproval) */
MessageType["CF_AGENT_TOOL_APPROVAL"] = "cf_agent_tool_approval";
/**
* Server→client progress hint: a durable chat turn is being recovered
* (interrupted by a deploy/eviction or a stream-stall watchdog abort and now
* resuming). Sent when a recovery continuation is scheduled and cleared on
* every terminal outcome. (`@cloudflare/think` also replays it on connect;
* `@cloudflare/ai-chat` broadcasts the live signal only — see #1645.)
* Backward-compatible — clients that don't understand it ignore it. See #1620.
*/
MessageType["CF_AGENT_CHAT_RECOVERING"] = "cf_agent_chat_recovering";
return MessageType;
}({});
//#endregion
export { StreamAccumulator as a, transition as i, CHAT_MESSAGE_TYPES as n, STREAM_RESUME_NONE_REASONS as r, MessageType as t };
//# sourceMappingURL=wire-types-CU9rLoeS.js.map