UNPKG

agents

Version:

A home for your AI agents

271 lines (270 loc) 9.84 kB
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