@witqq/agent-sdk 0.6.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/README.md +539 -6
  2. package/dist/{types-BvwNzZCj.d.cts → agent-CW9XbmG_.d.ts} +148 -95
  3. package/dist/{types-BvwNzZCj.d.ts → agent-DxY68NZL.d.cts} +148 -95
  4. package/dist/auth/index.cjs +260 -2
  5. package/dist/auth/index.cjs.map +1 -1
  6. package/dist/auth/index.d.cts +21 -138
  7. package/dist/auth/index.d.ts +21 -138
  8. package/dist/auth/index.js +260 -3
  9. package/dist/auth/index.js.map +1 -1
  10. package/dist/backends/claude.cjs +653 -140
  11. package/dist/backends/claude.cjs.map +1 -1
  12. package/dist/backends/claude.d.cts +4 -1
  13. package/dist/backends/claude.d.ts +4 -1
  14. package/dist/backends/claude.js +653 -140
  15. package/dist/backends/claude.js.map +1 -1
  16. package/dist/backends/copilot.cjs +428 -88
  17. package/dist/backends/copilot.cjs.map +1 -1
  18. package/dist/backends/copilot.d.cts +13 -4
  19. package/dist/backends/copilot.d.ts +13 -4
  20. package/dist/backends/copilot.js +428 -88
  21. package/dist/backends/copilot.js.map +1 -1
  22. package/dist/backends/vercel-ai.cjs +349 -77
  23. package/dist/backends/vercel-ai.cjs.map +1 -1
  24. package/dist/backends/vercel-ai.d.cts +3 -1
  25. package/dist/backends/vercel-ai.d.ts +3 -1
  26. package/dist/backends/vercel-ai.js +349 -77
  27. package/dist/backends/vercel-ai.js.map +1 -1
  28. package/dist/backends-BSrsBYFn.d.cts +39 -0
  29. package/dist/backends-BSrsBYFn.d.ts +39 -0
  30. package/dist/chat/accumulator.cjs +147 -0
  31. package/dist/chat/accumulator.cjs.map +1 -0
  32. package/dist/chat/accumulator.d.cts +64 -0
  33. package/dist/chat/accumulator.d.ts +64 -0
  34. package/dist/chat/accumulator.js +145 -0
  35. package/dist/chat/accumulator.js.map +1 -0
  36. package/dist/chat/backends.cjs +3524 -0
  37. package/dist/chat/backends.cjs.map +1 -0
  38. package/dist/chat/backends.d.cts +66 -0
  39. package/dist/chat/backends.d.ts +66 -0
  40. package/dist/chat/backends.js +3512 -0
  41. package/dist/chat/backends.js.map +1 -0
  42. package/dist/chat/context.cjs +280 -0
  43. package/dist/chat/context.cjs.map +1 -0
  44. package/dist/chat/context.d.cts +191 -0
  45. package/dist/chat/context.d.ts +191 -0
  46. package/dist/chat/context.js +277 -0
  47. package/dist/chat/context.js.map +1 -0
  48. package/dist/chat/core.cjs +305 -0
  49. package/dist/chat/core.cjs.map +1 -0
  50. package/dist/chat/core.d.cts +84 -0
  51. package/dist/chat/core.d.ts +84 -0
  52. package/dist/chat/core.js +282 -0
  53. package/dist/chat/core.js.map +1 -0
  54. package/dist/chat/errors.cjs +273 -0
  55. package/dist/chat/errors.cjs.map +1 -0
  56. package/dist/chat/errors.d.cts +97 -0
  57. package/dist/chat/errors.d.ts +97 -0
  58. package/dist/chat/errors.js +266 -0
  59. package/dist/chat/errors.js.map +1 -0
  60. package/dist/chat/events.cjs +203 -0
  61. package/dist/chat/events.cjs.map +1 -0
  62. package/dist/chat/events.d.cts +245 -0
  63. package/dist/chat/events.d.ts +245 -0
  64. package/dist/chat/events.js +196 -0
  65. package/dist/chat/events.js.map +1 -0
  66. package/dist/chat/index.cjs +5550 -0
  67. package/dist/chat/index.cjs.map +1 -0
  68. package/dist/chat/index.d.cts +77 -0
  69. package/dist/chat/index.d.ts +77 -0
  70. package/dist/chat/index.js +5505 -0
  71. package/dist/chat/index.js.map +1 -0
  72. package/dist/chat/react/theme.css +2517 -0
  73. package/dist/chat/react.cjs +3589 -0
  74. package/dist/chat/react.cjs.map +1 -0
  75. package/dist/chat/react.d.cts +1088 -0
  76. package/dist/chat/react.d.ts +1088 -0
  77. package/dist/chat/react.js +3547 -0
  78. package/dist/chat/react.js.map +1 -0
  79. package/dist/chat/runtime.cjs +1245 -0
  80. package/dist/chat/runtime.cjs.map +1 -0
  81. package/dist/chat/runtime.d.cts +182 -0
  82. package/dist/chat/runtime.d.ts +182 -0
  83. package/dist/chat/runtime.js +1243 -0
  84. package/dist/chat/runtime.js.map +1 -0
  85. package/dist/chat/server.cjs +2668 -0
  86. package/dist/chat/server.cjs.map +1 -0
  87. package/dist/chat/server.d.cts +648 -0
  88. package/dist/chat/server.d.ts +648 -0
  89. package/dist/chat/server.js +2628 -0
  90. package/dist/chat/server.js.map +1 -0
  91. package/dist/chat/sessions.cjs +380 -0
  92. package/dist/chat/sessions.cjs.map +1 -0
  93. package/dist/chat/sessions.d.cts +158 -0
  94. package/dist/chat/sessions.d.ts +158 -0
  95. package/dist/chat/sessions.js +376 -0
  96. package/dist/chat/sessions.js.map +1 -0
  97. package/dist/chat/sqlite.cjs +441 -0
  98. package/dist/chat/sqlite.cjs.map +1 -0
  99. package/dist/chat/sqlite.d.cts +128 -0
  100. package/dist/chat/sqlite.d.ts +128 -0
  101. package/dist/chat/sqlite.js +435 -0
  102. package/dist/chat/sqlite.js.map +1 -0
  103. package/dist/chat/state.cjs +190 -0
  104. package/dist/chat/state.cjs.map +1 -0
  105. package/dist/chat/state.d.cts +95 -0
  106. package/dist/chat/state.d.ts +95 -0
  107. package/dist/chat/state.js +180 -0
  108. package/dist/chat/state.js.map +1 -0
  109. package/dist/chat/storage.cjs +249 -0
  110. package/dist/chat/storage.cjs.map +1 -0
  111. package/dist/chat/storage.d.cts +197 -0
  112. package/dist/chat/storage.d.ts +197 -0
  113. package/dist/chat/storage.js +245 -0
  114. package/dist/chat/storage.js.map +1 -0
  115. package/dist/errors-C-so0M4t.d.cts +33 -0
  116. package/dist/errors-C-so0M4t.d.ts +33 -0
  117. package/dist/errors-CmVvczxZ.d.cts +28 -0
  118. package/dist/errors-CmVvczxZ.d.ts +28 -0
  119. package/dist/in-process-transport-C1JnJGVR.d.ts +228 -0
  120. package/dist/in-process-transport-C7DSqPyX.d.cts +228 -0
  121. package/dist/index.cjs +365 -59
  122. package/dist/index.cjs.map +1 -1
  123. package/dist/index.d.cts +322 -125
  124. package/dist/index.d.ts +322 -125
  125. package/dist/index.js +359 -60
  126. package/dist/index.js.map +1 -1
  127. package/dist/provider-types-PTSlRPNB.d.cts +39 -0
  128. package/dist/provider-types-PTSlRPNB.d.ts +39 -0
  129. package/dist/refresh-manager-B81PpYBr.d.cts +153 -0
  130. package/dist/refresh-manager-Dlv_iNZi.d.ts +153 -0
  131. package/dist/testing.cjs +383 -0
  132. package/dist/testing.cjs.map +1 -0
  133. package/dist/testing.d.cts +132 -0
  134. package/dist/testing.d.ts +132 -0
  135. package/dist/testing.js +377 -0
  136. package/dist/testing.js.map +1 -0
  137. package/dist/token-store-CSUBgYwn.d.ts +48 -0
  138. package/dist/token-store-CuC4hB9Z.d.cts +48 -0
  139. package/dist/transport-Cdh3M0tS.d.cts +68 -0
  140. package/dist/transport-Ciap4PWK.d.ts +68 -0
  141. package/dist/types-4vbcmPTp.d.cts +143 -0
  142. package/dist/types-BxggH0Yh.d.ts +143 -0
  143. package/dist/types-DRgd_9R7.d.cts +363 -0
  144. package/dist/types-ajANVzf7.d.ts +363 -0
  145. package/package.json +178 -6
@@ -0,0 +1,147 @@
1
+ 'use strict';
2
+
3
+ // src/chat/types.ts
4
+ function createChatId() {
5
+ return crypto.randomUUID();
6
+ }
7
+
8
+ // src/chat/accumulator.ts
9
+ var MessageAccumulator = class {
10
+ messageId;
11
+ parts = [];
12
+ status = "pending";
13
+ currentTextPart = null;
14
+ currentReasoningPart = null;
15
+ toolCallParts = /* @__PURE__ */ new Map();
16
+ _finalized = false;
17
+ constructor(messageId) {
18
+ this.messageId = messageId ?? createChatId();
19
+ }
20
+ /** Get current message ID */
21
+ get id() {
22
+ return this.messageId;
23
+ }
24
+ /**
25
+ * Apply an AgentEvent to accumulate into the message
26
+ * @param event - AgentEvent to process
27
+ * @throws Error if accumulator is already finalized
28
+ */
29
+ apply(event) {
30
+ if (this._finalized) throw new Error("Cannot apply events to finalized accumulator");
31
+ if (this.status === "pending") {
32
+ this.status = "streaming";
33
+ }
34
+ switch (event.type) {
35
+ case "text_delta":
36
+ this.handleTextDelta(event.text);
37
+ break;
38
+ case "thinking_start":
39
+ this.finalizeCurrentText();
40
+ this.currentReasoningPart = { type: "reasoning", text: "", status: "streaming" };
41
+ this.parts.push(this.currentReasoningPart);
42
+ break;
43
+ case "thinking_delta":
44
+ if (this.currentReasoningPart) {
45
+ this.currentReasoningPart.text += event.text;
46
+ }
47
+ break;
48
+ case "thinking_end":
49
+ if (this.currentReasoningPart) {
50
+ this.currentReasoningPart.status = "complete";
51
+ this.currentReasoningPart = null;
52
+ }
53
+ break;
54
+ case "tool_call_start": {
55
+ this.finalizeCurrentText();
56
+ const toolPart = {
57
+ type: "tool_call",
58
+ toolCallId: event.toolCallId,
59
+ name: event.toolName,
60
+ args: event.args,
61
+ status: "running"
62
+ };
63
+ this.toolCallParts.set(event.toolCallId, toolPart);
64
+ this.parts.push(toolPart);
65
+ break;
66
+ }
67
+ case "tool_call_end": {
68
+ const existing = this.toolCallParts.get(event.toolCallId);
69
+ if (existing) {
70
+ existing.result = event.result;
71
+ existing.status = "complete";
72
+ }
73
+ break;
74
+ }
75
+ case "error":
76
+ this.status = "error";
77
+ break;
78
+ }
79
+ }
80
+ handleTextDelta(text) {
81
+ if (!this.currentTextPart) {
82
+ this.currentTextPart = { type: "text", text: "", status: "streaming" };
83
+ this.parts.push(this.currentTextPart);
84
+ }
85
+ this.currentTextPart.text += text;
86
+ }
87
+ finalizeCurrentText() {
88
+ if (this.currentTextPart) {
89
+ this.currentTextPart.status = "complete";
90
+ this.currentTextPart = null;
91
+ }
92
+ }
93
+ /**
94
+ * Get a snapshot of the current accumulated message (for streaming UI)
95
+ * @returns ChatMessage with current parts and "streaming" status
96
+ */
97
+ snapshot() {
98
+ const now = (/* @__PURE__ */ new Date()).toISOString();
99
+ return {
100
+ id: this.messageId,
101
+ role: "assistant",
102
+ parts: this.parts.map((p) => ({ ...p })),
103
+ status: this.status === "pending" ? "pending" : "streaming",
104
+ createdAt: now,
105
+ updatedAt: now
106
+ };
107
+ }
108
+ /**
109
+ * Finalize the accumulator and return the complete ChatMessage
110
+ * @returns Completed ChatMessage with all parts finalized
111
+ * @throws Error if accumulator is already finalized
112
+ */
113
+ finalize() {
114
+ if (this._finalized) throw new Error("Accumulator already finalized");
115
+ this._finalized = true;
116
+ this.finalizeCurrentText();
117
+ if (this.currentReasoningPart) {
118
+ this.currentReasoningPart.status = "complete";
119
+ this.currentReasoningPart = null;
120
+ }
121
+ for (const [, toolPart] of this.toolCallParts) {
122
+ if (toolPart.status === "running" || toolPart.status === "pending") {
123
+ toolPart.status = "error";
124
+ }
125
+ }
126
+ if (this.status !== "error" && this.status !== "cancelled") {
127
+ this.status = "complete";
128
+ }
129
+ const now = (/* @__PURE__ */ new Date()).toISOString();
130
+ return {
131
+ id: this.messageId,
132
+ role: "assistant",
133
+ parts: this.parts,
134
+ status: this.status,
135
+ createdAt: now,
136
+ updatedAt: now
137
+ };
138
+ }
139
+ /** Check if the accumulator has been finalized */
140
+ get finalized() {
141
+ return this._finalized;
142
+ }
143
+ };
144
+
145
+ exports.MessageAccumulator = MessageAccumulator;
146
+ //# sourceMappingURL=accumulator.cjs.map
147
+ //# sourceMappingURL=accumulator.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/chat/types.ts","../../src/chat/accumulator.ts"],"names":[],"mappings":"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","file":"accumulator.cjs","sourcesContent":["/**\n * @witqq/agent-sdk — Chat domain types\n *\n * All type definitions and interfaces for the chat layer.\n * Pure types + ChatId generation (tightly coupled to branded type).\n */\n\nimport type { UsageData, ToolDefinition, ErrorCode } from \"../types.js\";\nimport type { AuthToken } from \"../auth/types.js\";\n\n// ─── Unique ID ─────────────────────────────────────────────────\n\n/** Branded type for unique identifiers */\nexport type ChatId = string & { readonly __brand: \"ChatId\" };\n\n/**\n * Generate a new unique ChatId (crypto.randomUUID-based)\n * @returns Branded ChatId string\n */\nexport function createChatId(): ChatId {\n return crypto.randomUUID() as ChatId;\n}\n\n/** UUID v4 pattern for ChatId validation */\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n\n/**\n * Cast a string to ChatId with UUID format validation.\n * Use this instead of manual `as ChatId` type assertions.\n *\n * @param value - String to validate and cast\n * @returns Branded ChatId\n * @throws {TypeError} If value is not a valid UUID v4 format\n */\nexport function toChatId(value: string): ChatId {\n if (!UUID_RE.test(value)) {\n throw new TypeError(`Invalid ChatId: \"${value}\" is not a valid UUID`);\n }\n return value as ChatId;\n}\n\n/**\n * Accepts either a plain string or branded ChatId for API convenience.\n * Use this in public API signatures so consumers don't need `as ChatId` casts.\n */\nexport type ChatIdLike = string | ChatId;\n\n// ─── Status Types ──────────────────────────────────────────────\n\n/** Lifecycle status of a message part (text, reasoning, etc.) */\nexport type PartStatus = \"pending\" | \"streaming\" | \"complete\" | \"error\";\n/** Lifecycle status of a tool call within a message */\nexport type ToolCallStatus = \"pending\" | \"running\" | \"requires_approval\" | \"complete\" | \"error\" | \"denied\";\n/** Lifecycle status of an entire message */\nexport type MessageStatus = \"pending\" | \"streaming\" | \"complete\" | \"error\" | \"cancelled\";\n/** Lifecycle status of a chat session */\nexport type SessionStatus = \"active\";\n/** Lifecycle status of the chat runtime */\nexport type RuntimeStatus = \"idle\" | \"streaming\" | \"error\" | \"disposed\";\n\n// ─── Message Parts (union) ─────────────────────────────────────\n\n/** Plain text content part */\nexport interface TextPart { type: \"text\"; text: string; status: PartStatus; }\n/** Model reasoning/thinking content part */\nexport interface ReasoningPart { type: \"reasoning\"; text: string; status: PartStatus; }\n/** Tool invocation part with call ID, arguments, optional result */\nexport interface ToolCallPart { type: \"tool_call\"; toolCallId: string; name: string; args: unknown; result?: unknown; status: ToolCallStatus; error?: string; }\n/** Source reference part (URL citation) */\nexport interface SourcePart { type: \"source\"; url: string; title?: string; status: PartStatus; }\n/** File attachment part (base64-encoded data) */\nexport interface FilePart { type: \"file\"; name: string; mimeType: string; data: string; status: PartStatus; }\n/** Union of all message part types */\nexport type MessagePart = TextPart | ReasoningPart | ToolCallPart | SourcePart | FilePart;\n\n// ─── Chat Message ──────────────────────────────────────────────\n\n/** Role of message author */\nexport type ChatRole = \"user\" | \"assistant\" | \"system\";\n\n/** Metadata attached to messages — useful preset for the TMetadata generic */\nexport interface ChatMessageMetadata {\n model?: string;\n backend?: string;\n usage?: UsageData;\n isSummary?: boolean;\n estimatedTokens?: number;\n custom?: Record<string, unknown>;\n}\n\n/** Message status */\nexport type ChatMessageStatus = MessageStatus;\n\n/** A single chat message — the fundamental unit of conversation */\nexport interface ChatMessage<TMetadata = unknown> {\n id: ChatId;\n role: ChatRole;\n parts: MessagePart[];\n metadata?: TMetadata;\n createdAt: string;\n updatedAt?: string;\n status: MessageStatus;\n}\n\n// ─── Supporting Types ──────────────────────────────────────────\n\n// ─── Chat Session ──────────────────────────────────────────────\n\n/** Session configuration snapshot */\nexport interface ChatSessionConfig {\n model: string;\n backend: string;\n systemPrompt?: string;\n temperature?: number;\n maxTokens?: number;\n}\n\n/**\n * Session metadata tracking usage statistics and custom extensions.\n *\n * Updated automatically by session stores on each `addMessage()` call.\n * The generic `TCustom` parameter allows type-safe application-specific\n * metadata via the `custom` field.\n *\n * @typeParam TCustom - Shape of the `custom` field (defaults to `Record<string, unknown>`)\n */\nexport interface ChatSessionMetadata<TCustom extends Record<string, unknown> = Record<string, unknown>> {\n /** Number of messages in the session (updated by session store) */\n messageCount: number;\n /** Total token count across all messages in the session */\n totalTokens: number;\n /** Optional tags for session categorization and filtering */\n tags?: string[];\n /** Application-specific metadata — typed via the TCustom generic parameter */\n custom?: TCustom;\n}\n\n/** Chat session — a conversation with ordered messages (pure serializable data) */\nexport interface ChatSession<TCustom extends Record<string, unknown> = Record<string, unknown>> {\n id: ChatId;\n title?: string;\n messages: ChatMessage[];\n config: ChatSessionConfig;\n metadata: ChatSessionMetadata<TCustom>;\n status: SessionStatus;\n createdAt: string;\n updatedAt: string;\n backendSessionId?: string;\n}\n\n/**\n * Reactive wrapper around ChatSession — provides subscribe/getSnapshot for\n * React useSyncExternalStore integration and lastMessage convenience getter.\n * Session stores may optionally return ObservableSession instances.\n */\nexport interface ObservableSession<TCustom extends Record<string, unknown> = Record<string, unknown>>\n extends ChatSession<TCustom> {\n /** Subscribe to session changes (for React useSyncExternalStore) */\n subscribe(callback: () => void): () => void;\n /** Get immutable snapshot of session state (for React useSyncExternalStore) */\n getSnapshot(): ChatSession<TCustom>;\n /** Last message in the session */\n readonly lastMessage: ChatMessage | undefined;\n}\n\n/** Lightweight session info for listing (without full message array) */\nexport interface SessionInfo {\n id: ChatId;\n title?: string;\n status: SessionStatus;\n messageCount: number;\n lastMessage?: ChatMessage;\n createdAt: string;\n updatedAt: string;\n}\n\n// ─── Chat Events ───────────────────────────────────────────────\n\n/** Events emitted during chat operation */\nexport type ChatEvent =\n | { type: \"message:start\"; messageId: ChatId; role: ChatRole }\n | { type: \"message:delta\"; messageId: ChatId; text: string }\n | { type: \"message:complete\"; messageId: ChatId; message: ChatMessage }\n | {\n type: \"tool:start\";\n messageId: ChatId;\n toolCallId: string;\n toolName: string;\n args: Record<string, unknown>;\n }\n | {\n type: \"tool:complete\";\n messageId: ChatId;\n toolCallId: string;\n toolName: string;\n result: unknown;\n isError?: boolean;\n }\n | { type: \"thinking:start\"; messageId: ChatId }\n | { type: \"thinking:delta\"; messageId: ChatId; text: string }\n | { type: \"thinking:end\"; messageId: ChatId }\n | {\n type: \"permission:request\";\n messageId: ChatId;\n toolName: string;\n toolArgs: Record<string, unknown>;\n }\n | {\n type: \"permission:response\";\n messageId: ChatId;\n toolName: string;\n allowed: boolean;\n }\n | {\n type: \"usage\";\n promptTokens: number;\n completionTokens: number;\n model?: string;\n }\n | { type: \"session:created\"; sessionId: ChatId }\n | { type: \"session:updated\"; sessionId: ChatId }\n | {\n type: \"error\";\n error: string;\n recoverable: boolean;\n code?: ErrorCode;\n messageId?: ChatId;\n }\n | { type: \"typing:start\" }\n | { type: \"typing:end\" }\n | { type: \"heartbeat\" }\n | { type: \"done\"; finalOutput?: string };\n\n/** All possible ChatEvent type strings */\nexport type ChatEventType = ChatEvent[\"type\"];\n\n// ─── Chat Middleware ───────────────────────────────────────────\n\n/** Context passed to ChatMiddleware hooks */\nexport interface ChatMiddlewareContext {\n sessionId: ChatId;\n signal: AbortSignal;\n}\n\n/** Runtime-level middleware for the send/receive lifecycle.\n * Different from EventMiddleware which operates at the event bus level. */\nexport interface ChatMiddleware {\n /** Transform message before sending to backend. Return null to reject the send. */\n onBeforeSend?(message: ChatMessage, context: ChatMiddlewareContext): ChatMessage | null | Promise<ChatMessage | null>;\n /** Transform/intercept stream events */\n onEvent?(event: ChatEvent, context: ChatMiddlewareContext): ChatEvent | null | Promise<ChatEvent | null>;\n /** Transform completed message after receiving from backend */\n onAfterReceive?(message: ChatMessage, context: ChatMiddlewareContext): ChatMessage | Promise<ChatMessage>;\n /** Intercept errors — return null to suppress, return error to propagate */\n onError?(error: Error, context: ChatMiddlewareContext): Error | null | Promise<Error | null>;\n}\n\n// ─── Chat Provider Abstraction ─────────────────────────────────\n\n/** Options for sending a message to a provider */\nexport interface SendMessageOptions {\n signal?: AbortSignal;\n /** Model to use for this request. Required for server-side runtime.send(). */\n model?: string;\n /** Per-call system prompt override (forwarded to the backend agent) */\n systemPrompt?: string;\n context?: Record<string, unknown>;\n /** Additional tools to include in this request */\n tools?: ToolDefinition[];\n}\n\n/** Options for runtime.send() — requires backend routing info */\nexport interface RuntimeSendOptions {\n /** Backend to route this request to (key in backends map) */\n backend: string;\n /** Authentication credentials for the backend factory */\n credentials: AuthToken;\n /** Model to use for this request */\n model: string;\n /** Per-call system prompt override (forwarded to the backend agent) */\n systemPrompt?: string;\n /** Abort signal */\n signal?: AbortSignal;\n /** Request-scoped context */\n context?: Record<string, unknown>;\n /** Additional tools */\n tools?: ToolDefinition[];\n}\n\n/**\n * @deprecated IChatProvider has been inlined into IChatBackend.\n * Import IChatBackend from \"@witqq/agent-sdk/chat/backends\" instead.\n * Kept as type alias for backward compatibility.\n */\nexport type IChatProvider = import(\"./backends/types.js\").IChatBackend;\n\n// ─── Factory Functions ─────────────────────────────────────────\n\n/**\n * Create a simple text ChatMessage.\n *\n * @param text - Message text content\n * @param role - Message role (default: \"user\")\n * @returns A complete ChatMessage with a single TextPart\n */\nexport function createTextMessage(text: string, role: ChatRole = \"user\"): ChatMessage {\n return {\n id: createChatId(),\n role,\n parts: [{ type: \"text\", text, status: \"complete\" }],\n createdAt: new Date().toISOString(),\n status: \"complete\",\n };\n}\n\n/** Type guard: checks if a session has reactive API (subscribe/getSnapshot) */\nexport function isObservableSession<TCustom extends Record<string, unknown> = Record<string, unknown>>(\n session: ChatSession<TCustom>,\n): session is ObservableSession<TCustom> {\n return \"subscribe\" in session && typeof (session as ObservableSession<TCustom>).subscribe === \"function\"\n && \"getSnapshot\" in session && typeof (session as ObservableSession<TCustom>).getSnapshot === \"function\";\n}\n","/**\n * @witqq/agent-sdk/chat/accumulator\n *\n * MessageAccumulator converts a stream of AgentEvent objects into a ChatMessage\n * with correct MessagePart array. Handles text, reasoning, and tool call\n * accumulation with proper status transitions.\n */\n\nimport type { AgentEvent } from \"../types.js\";\nimport type { ChatMessage, ChatId, MessagePart, TextPart, ReasoningPart, ToolCallPart } from \"./core.js\";\nimport { createChatId } from \"./core.js\";\n\n/**\n * Converts a stream of AgentEvent objects into a complete ChatMessage.\n * Tracks text, reasoning, and tool call parts with proper status transitions.\n *\n * @example\n * ```typescript\n * const acc = new MessageAccumulator();\n * for await (const event of agentEvents) {\n * acc.apply(event);\n * renderMessage(acc.snapshot()); // in-progress UI update\n * }\n * const message = acc.finalize();\n * ```\n */\nexport class MessageAccumulator {\n private readonly messageId: ChatId;\n private readonly parts: MessagePart[] = [];\n private status: \"pending\" | \"streaming\" | \"complete\" | \"error\" | \"cancelled\" = \"pending\";\n private currentTextPart: TextPart | null = null;\n private currentReasoningPart: ReasoningPart | null = null;\n private toolCallParts = new Map<string, ToolCallPart>();\n private _finalized = false;\n\n constructor(messageId?: ChatId) {\n this.messageId = messageId ?? createChatId();\n }\n\n /** Get current message ID */\n get id(): ChatId { return this.messageId; }\n\n /**\n * Apply an AgentEvent to accumulate into the message\n * @param event - AgentEvent to process\n * @throws Error if accumulator is already finalized\n */\n apply(event: AgentEvent): void {\n if (this._finalized) throw new Error(\"Cannot apply events to finalized accumulator\");\n\n if (this.status === \"pending\") {\n this.status = \"streaming\";\n }\n\n switch (event.type) {\n case \"text_delta\":\n this.handleTextDelta(event.text);\n break;\n case \"thinking_start\":\n this.finalizeCurrentText();\n this.currentReasoningPart = { type: \"reasoning\", text: \"\", status: \"streaming\" };\n this.parts.push(this.currentReasoningPart);\n break;\n case \"thinking_delta\":\n if (this.currentReasoningPart) {\n this.currentReasoningPart.text += event.text;\n }\n break;\n case \"thinking_end\":\n if (this.currentReasoningPart) {\n this.currentReasoningPart.status = \"complete\";\n this.currentReasoningPart = null;\n }\n break;\n case \"tool_call_start\": {\n this.finalizeCurrentText();\n const toolPart: ToolCallPart = {\n type: \"tool_call\",\n toolCallId: event.toolCallId,\n name: event.toolName,\n args: event.args,\n status: \"running\",\n };\n this.toolCallParts.set(event.toolCallId, toolPart);\n this.parts.push(toolPart);\n break;\n }\n case \"tool_call_end\": {\n const existing = this.toolCallParts.get(event.toolCallId);\n if (existing) {\n existing.result = event.result;\n existing.status = \"complete\";\n }\n break;\n }\n case \"error\":\n this.status = \"error\";\n break;\n case \"done\":\n break;\n // Other events (heartbeat, ask_user, etc.) — ignore\n }\n }\n\n private handleTextDelta(text: string): void {\n if (!this.currentTextPart) {\n this.currentTextPart = { type: \"text\", text: \"\", status: \"streaming\" };\n this.parts.push(this.currentTextPart);\n }\n this.currentTextPart.text += text;\n }\n\n private finalizeCurrentText(): void {\n if (this.currentTextPart) {\n this.currentTextPart.status = \"complete\";\n this.currentTextPart = null;\n }\n }\n\n /**\n * Get a snapshot of the current accumulated message (for streaming UI)\n * @returns ChatMessage with current parts and \"streaming\" status\n */\n snapshot(): ChatMessage {\n const now = new Date().toISOString();\n return {\n id: this.messageId,\n role: \"assistant\",\n parts: this.parts.map(p => ({ ...p })),\n status: this.status === \"pending\" ? \"pending\" : \"streaming\",\n createdAt: now,\n updatedAt: now,\n };\n }\n\n /**\n * Finalize the accumulator and return the complete ChatMessage\n * @returns Completed ChatMessage with all parts finalized\n * @throws Error if accumulator is already finalized\n */\n finalize(): ChatMessage {\n if (this._finalized) throw new Error(\"Accumulator already finalized\");\n this._finalized = true;\n\n // Finalize any open parts\n this.finalizeCurrentText();\n if (this.currentReasoningPart) {\n this.currentReasoningPart.status = \"complete\";\n this.currentReasoningPart = null;\n }\n\n // Mark incomplete tool calls as error\n for (const [, toolPart] of this.toolCallParts) {\n if (toolPart.status === \"running\" || toolPart.status === \"pending\") {\n toolPart.status = \"error\";\n }\n }\n\n // Set final message status\n if (this.status !== \"error\" && this.status !== \"cancelled\") {\n this.status = \"complete\";\n }\n\n const now = new Date().toISOString();\n return {\n id: this.messageId,\n role: \"assistant\",\n parts: this.parts,\n status: this.status,\n createdAt: now,\n updatedAt: now,\n };\n }\n\n /** Check if the accumulator has been finalized */\n get finalized(): boolean { return this._finalized; }\n}\n"]}
@@ -0,0 +1,64 @@
1
+ import { A as AgentEvent } from '../agent-DxY68NZL.cjs';
2
+ import { b as ChatId, c as ChatMessage } from '../types-DRgd_9R7.cjs';
3
+ import 'zod';
4
+ import '../errors-C-so0M4t.cjs';
5
+ import '../types-4vbcmPTp.cjs';
6
+ import '../errors-CmVvczxZ.cjs';
7
+
8
+ /**
9
+ * @witqq/agent-sdk/chat/accumulator
10
+ *
11
+ * MessageAccumulator converts a stream of AgentEvent objects into a ChatMessage
12
+ * with correct MessagePart array. Handles text, reasoning, and tool call
13
+ * accumulation with proper status transitions.
14
+ */
15
+
16
+ /**
17
+ * Converts a stream of AgentEvent objects into a complete ChatMessage.
18
+ * Tracks text, reasoning, and tool call parts with proper status transitions.
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * const acc = new MessageAccumulator();
23
+ * for await (const event of agentEvents) {
24
+ * acc.apply(event);
25
+ * renderMessage(acc.snapshot()); // in-progress UI update
26
+ * }
27
+ * const message = acc.finalize();
28
+ * ```
29
+ */
30
+ declare class MessageAccumulator {
31
+ private readonly messageId;
32
+ private readonly parts;
33
+ private status;
34
+ private currentTextPart;
35
+ private currentReasoningPart;
36
+ private toolCallParts;
37
+ private _finalized;
38
+ constructor(messageId?: ChatId);
39
+ /** Get current message ID */
40
+ get id(): ChatId;
41
+ /**
42
+ * Apply an AgentEvent to accumulate into the message
43
+ * @param event - AgentEvent to process
44
+ * @throws Error if accumulator is already finalized
45
+ */
46
+ apply(event: AgentEvent): void;
47
+ private handleTextDelta;
48
+ private finalizeCurrentText;
49
+ /**
50
+ * Get a snapshot of the current accumulated message (for streaming UI)
51
+ * @returns ChatMessage with current parts and "streaming" status
52
+ */
53
+ snapshot(): ChatMessage;
54
+ /**
55
+ * Finalize the accumulator and return the complete ChatMessage
56
+ * @returns Completed ChatMessage with all parts finalized
57
+ * @throws Error if accumulator is already finalized
58
+ */
59
+ finalize(): ChatMessage;
60
+ /** Check if the accumulator has been finalized */
61
+ get finalized(): boolean;
62
+ }
63
+
64
+ export { MessageAccumulator };
@@ -0,0 +1,64 @@
1
+ import { A as AgentEvent } from '../agent-CW9XbmG_.js';
2
+ import { b as ChatId, c as ChatMessage } from '../types-ajANVzf7.js';
3
+ import 'zod';
4
+ import '../errors-C-so0M4t.js';
5
+ import '../types-BxggH0Yh.js';
6
+ import '../errors-CmVvczxZ.js';
7
+
8
+ /**
9
+ * @witqq/agent-sdk/chat/accumulator
10
+ *
11
+ * MessageAccumulator converts a stream of AgentEvent objects into a ChatMessage
12
+ * with correct MessagePart array. Handles text, reasoning, and tool call
13
+ * accumulation with proper status transitions.
14
+ */
15
+
16
+ /**
17
+ * Converts a stream of AgentEvent objects into a complete ChatMessage.
18
+ * Tracks text, reasoning, and tool call parts with proper status transitions.
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * const acc = new MessageAccumulator();
23
+ * for await (const event of agentEvents) {
24
+ * acc.apply(event);
25
+ * renderMessage(acc.snapshot()); // in-progress UI update
26
+ * }
27
+ * const message = acc.finalize();
28
+ * ```
29
+ */
30
+ declare class MessageAccumulator {
31
+ private readonly messageId;
32
+ private readonly parts;
33
+ private status;
34
+ private currentTextPart;
35
+ private currentReasoningPart;
36
+ private toolCallParts;
37
+ private _finalized;
38
+ constructor(messageId?: ChatId);
39
+ /** Get current message ID */
40
+ get id(): ChatId;
41
+ /**
42
+ * Apply an AgentEvent to accumulate into the message
43
+ * @param event - AgentEvent to process
44
+ * @throws Error if accumulator is already finalized
45
+ */
46
+ apply(event: AgentEvent): void;
47
+ private handleTextDelta;
48
+ private finalizeCurrentText;
49
+ /**
50
+ * Get a snapshot of the current accumulated message (for streaming UI)
51
+ * @returns ChatMessage with current parts and "streaming" status
52
+ */
53
+ snapshot(): ChatMessage;
54
+ /**
55
+ * Finalize the accumulator and return the complete ChatMessage
56
+ * @returns Completed ChatMessage with all parts finalized
57
+ * @throws Error if accumulator is already finalized
58
+ */
59
+ finalize(): ChatMessage;
60
+ /** Check if the accumulator has been finalized */
61
+ get finalized(): boolean;
62
+ }
63
+
64
+ export { MessageAccumulator };
@@ -0,0 +1,145 @@
1
+ // src/chat/types.ts
2
+ function createChatId() {
3
+ return crypto.randomUUID();
4
+ }
5
+
6
+ // src/chat/accumulator.ts
7
+ var MessageAccumulator = class {
8
+ messageId;
9
+ parts = [];
10
+ status = "pending";
11
+ currentTextPart = null;
12
+ currentReasoningPart = null;
13
+ toolCallParts = /* @__PURE__ */ new Map();
14
+ _finalized = false;
15
+ constructor(messageId) {
16
+ this.messageId = messageId ?? createChatId();
17
+ }
18
+ /** Get current message ID */
19
+ get id() {
20
+ return this.messageId;
21
+ }
22
+ /**
23
+ * Apply an AgentEvent to accumulate into the message
24
+ * @param event - AgentEvent to process
25
+ * @throws Error if accumulator is already finalized
26
+ */
27
+ apply(event) {
28
+ if (this._finalized) throw new Error("Cannot apply events to finalized accumulator");
29
+ if (this.status === "pending") {
30
+ this.status = "streaming";
31
+ }
32
+ switch (event.type) {
33
+ case "text_delta":
34
+ this.handleTextDelta(event.text);
35
+ break;
36
+ case "thinking_start":
37
+ this.finalizeCurrentText();
38
+ this.currentReasoningPart = { type: "reasoning", text: "", status: "streaming" };
39
+ this.parts.push(this.currentReasoningPart);
40
+ break;
41
+ case "thinking_delta":
42
+ if (this.currentReasoningPart) {
43
+ this.currentReasoningPart.text += event.text;
44
+ }
45
+ break;
46
+ case "thinking_end":
47
+ if (this.currentReasoningPart) {
48
+ this.currentReasoningPart.status = "complete";
49
+ this.currentReasoningPart = null;
50
+ }
51
+ break;
52
+ case "tool_call_start": {
53
+ this.finalizeCurrentText();
54
+ const toolPart = {
55
+ type: "tool_call",
56
+ toolCallId: event.toolCallId,
57
+ name: event.toolName,
58
+ args: event.args,
59
+ status: "running"
60
+ };
61
+ this.toolCallParts.set(event.toolCallId, toolPart);
62
+ this.parts.push(toolPart);
63
+ break;
64
+ }
65
+ case "tool_call_end": {
66
+ const existing = this.toolCallParts.get(event.toolCallId);
67
+ if (existing) {
68
+ existing.result = event.result;
69
+ existing.status = "complete";
70
+ }
71
+ break;
72
+ }
73
+ case "error":
74
+ this.status = "error";
75
+ break;
76
+ }
77
+ }
78
+ handleTextDelta(text) {
79
+ if (!this.currentTextPart) {
80
+ this.currentTextPart = { type: "text", text: "", status: "streaming" };
81
+ this.parts.push(this.currentTextPart);
82
+ }
83
+ this.currentTextPart.text += text;
84
+ }
85
+ finalizeCurrentText() {
86
+ if (this.currentTextPart) {
87
+ this.currentTextPart.status = "complete";
88
+ this.currentTextPart = null;
89
+ }
90
+ }
91
+ /**
92
+ * Get a snapshot of the current accumulated message (for streaming UI)
93
+ * @returns ChatMessage with current parts and "streaming" status
94
+ */
95
+ snapshot() {
96
+ const now = (/* @__PURE__ */ new Date()).toISOString();
97
+ return {
98
+ id: this.messageId,
99
+ role: "assistant",
100
+ parts: this.parts.map((p) => ({ ...p })),
101
+ status: this.status === "pending" ? "pending" : "streaming",
102
+ createdAt: now,
103
+ updatedAt: now
104
+ };
105
+ }
106
+ /**
107
+ * Finalize the accumulator and return the complete ChatMessage
108
+ * @returns Completed ChatMessage with all parts finalized
109
+ * @throws Error if accumulator is already finalized
110
+ */
111
+ finalize() {
112
+ if (this._finalized) throw new Error("Accumulator already finalized");
113
+ this._finalized = true;
114
+ this.finalizeCurrentText();
115
+ if (this.currentReasoningPart) {
116
+ this.currentReasoningPart.status = "complete";
117
+ this.currentReasoningPart = null;
118
+ }
119
+ for (const [, toolPart] of this.toolCallParts) {
120
+ if (toolPart.status === "running" || toolPart.status === "pending") {
121
+ toolPart.status = "error";
122
+ }
123
+ }
124
+ if (this.status !== "error" && this.status !== "cancelled") {
125
+ this.status = "complete";
126
+ }
127
+ const now = (/* @__PURE__ */ new Date()).toISOString();
128
+ return {
129
+ id: this.messageId,
130
+ role: "assistant",
131
+ parts: this.parts,
132
+ status: this.status,
133
+ createdAt: now,
134
+ updatedAt: now
135
+ };
136
+ }
137
+ /** Check if the accumulator has been finalized */
138
+ get finalized() {
139
+ return this._finalized;
140
+ }
141
+ };
142
+
143
+ export { MessageAccumulator };
144
+ //# sourceMappingURL=accumulator.js.map
145
+ //# sourceMappingURL=accumulator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/chat/types.ts","../../src/chat/accumulator.ts"],"names":[],"mappings":"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","file":"accumulator.js","sourcesContent":["/**\n * @witqq/agent-sdk — Chat domain types\n *\n * All type definitions and interfaces for the chat layer.\n * Pure types + ChatId generation (tightly coupled to branded type).\n */\n\nimport type { UsageData, ToolDefinition, ErrorCode } from \"../types.js\";\nimport type { AuthToken } from \"../auth/types.js\";\n\n// ─── Unique ID ─────────────────────────────────────────────────\n\n/** Branded type for unique identifiers */\nexport type ChatId = string & { readonly __brand: \"ChatId\" };\n\n/**\n * Generate a new unique ChatId (crypto.randomUUID-based)\n * @returns Branded ChatId string\n */\nexport function createChatId(): ChatId {\n return crypto.randomUUID() as ChatId;\n}\n\n/** UUID v4 pattern for ChatId validation */\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n\n/**\n * Cast a string to ChatId with UUID format validation.\n * Use this instead of manual `as ChatId` type assertions.\n *\n * @param value - String to validate and cast\n * @returns Branded ChatId\n * @throws {TypeError} If value is not a valid UUID v4 format\n */\nexport function toChatId(value: string): ChatId {\n if (!UUID_RE.test(value)) {\n throw new TypeError(`Invalid ChatId: \"${value}\" is not a valid UUID`);\n }\n return value as ChatId;\n}\n\n/**\n * Accepts either a plain string or branded ChatId for API convenience.\n * Use this in public API signatures so consumers don't need `as ChatId` casts.\n */\nexport type ChatIdLike = string | ChatId;\n\n// ─── Status Types ──────────────────────────────────────────────\n\n/** Lifecycle status of a message part (text, reasoning, etc.) */\nexport type PartStatus = \"pending\" | \"streaming\" | \"complete\" | \"error\";\n/** Lifecycle status of a tool call within a message */\nexport type ToolCallStatus = \"pending\" | \"running\" | \"requires_approval\" | \"complete\" | \"error\" | \"denied\";\n/** Lifecycle status of an entire message */\nexport type MessageStatus = \"pending\" | \"streaming\" | \"complete\" | \"error\" | \"cancelled\";\n/** Lifecycle status of a chat session */\nexport type SessionStatus = \"active\";\n/** Lifecycle status of the chat runtime */\nexport type RuntimeStatus = \"idle\" | \"streaming\" | \"error\" | \"disposed\";\n\n// ─── Message Parts (union) ─────────────────────────────────────\n\n/** Plain text content part */\nexport interface TextPart { type: \"text\"; text: string; status: PartStatus; }\n/** Model reasoning/thinking content part */\nexport interface ReasoningPart { type: \"reasoning\"; text: string; status: PartStatus; }\n/** Tool invocation part with call ID, arguments, optional result */\nexport interface ToolCallPart { type: \"tool_call\"; toolCallId: string; name: string; args: unknown; result?: unknown; status: ToolCallStatus; error?: string; }\n/** Source reference part (URL citation) */\nexport interface SourcePart { type: \"source\"; url: string; title?: string; status: PartStatus; }\n/** File attachment part (base64-encoded data) */\nexport interface FilePart { type: \"file\"; name: string; mimeType: string; data: string; status: PartStatus; }\n/** Union of all message part types */\nexport type MessagePart = TextPart | ReasoningPart | ToolCallPart | SourcePart | FilePart;\n\n// ─── Chat Message ──────────────────────────────────────────────\n\n/** Role of message author */\nexport type ChatRole = \"user\" | \"assistant\" | \"system\";\n\n/** Metadata attached to messages — useful preset for the TMetadata generic */\nexport interface ChatMessageMetadata {\n model?: string;\n backend?: string;\n usage?: UsageData;\n isSummary?: boolean;\n estimatedTokens?: number;\n custom?: Record<string, unknown>;\n}\n\n/** Message status */\nexport type ChatMessageStatus = MessageStatus;\n\n/** A single chat message — the fundamental unit of conversation */\nexport interface ChatMessage<TMetadata = unknown> {\n id: ChatId;\n role: ChatRole;\n parts: MessagePart[];\n metadata?: TMetadata;\n createdAt: string;\n updatedAt?: string;\n status: MessageStatus;\n}\n\n// ─── Supporting Types ──────────────────────────────────────────\n\n// ─── Chat Session ──────────────────────────────────────────────\n\n/** Session configuration snapshot */\nexport interface ChatSessionConfig {\n model: string;\n backend: string;\n systemPrompt?: string;\n temperature?: number;\n maxTokens?: number;\n}\n\n/**\n * Session metadata tracking usage statistics and custom extensions.\n *\n * Updated automatically by session stores on each `addMessage()` call.\n * The generic `TCustom` parameter allows type-safe application-specific\n * metadata via the `custom` field.\n *\n * @typeParam TCustom - Shape of the `custom` field (defaults to `Record<string, unknown>`)\n */\nexport interface ChatSessionMetadata<TCustom extends Record<string, unknown> = Record<string, unknown>> {\n /** Number of messages in the session (updated by session store) */\n messageCount: number;\n /** Total token count across all messages in the session */\n totalTokens: number;\n /** Optional tags for session categorization and filtering */\n tags?: string[];\n /** Application-specific metadata — typed via the TCustom generic parameter */\n custom?: TCustom;\n}\n\n/** Chat session — a conversation with ordered messages (pure serializable data) */\nexport interface ChatSession<TCustom extends Record<string, unknown> = Record<string, unknown>> {\n id: ChatId;\n title?: string;\n messages: ChatMessage[];\n config: ChatSessionConfig;\n metadata: ChatSessionMetadata<TCustom>;\n status: SessionStatus;\n createdAt: string;\n updatedAt: string;\n backendSessionId?: string;\n}\n\n/**\n * Reactive wrapper around ChatSession — provides subscribe/getSnapshot for\n * React useSyncExternalStore integration and lastMessage convenience getter.\n * Session stores may optionally return ObservableSession instances.\n */\nexport interface ObservableSession<TCustom extends Record<string, unknown> = Record<string, unknown>>\n extends ChatSession<TCustom> {\n /** Subscribe to session changes (for React useSyncExternalStore) */\n subscribe(callback: () => void): () => void;\n /** Get immutable snapshot of session state (for React useSyncExternalStore) */\n getSnapshot(): ChatSession<TCustom>;\n /** Last message in the session */\n readonly lastMessage: ChatMessage | undefined;\n}\n\n/** Lightweight session info for listing (without full message array) */\nexport interface SessionInfo {\n id: ChatId;\n title?: string;\n status: SessionStatus;\n messageCount: number;\n lastMessage?: ChatMessage;\n createdAt: string;\n updatedAt: string;\n}\n\n// ─── Chat Events ───────────────────────────────────────────────\n\n/** Events emitted during chat operation */\nexport type ChatEvent =\n | { type: \"message:start\"; messageId: ChatId; role: ChatRole }\n | { type: \"message:delta\"; messageId: ChatId; text: string }\n | { type: \"message:complete\"; messageId: ChatId; message: ChatMessage }\n | {\n type: \"tool:start\";\n messageId: ChatId;\n toolCallId: string;\n toolName: string;\n args: Record<string, unknown>;\n }\n | {\n type: \"tool:complete\";\n messageId: ChatId;\n toolCallId: string;\n toolName: string;\n result: unknown;\n isError?: boolean;\n }\n | { type: \"thinking:start\"; messageId: ChatId }\n | { type: \"thinking:delta\"; messageId: ChatId; text: string }\n | { type: \"thinking:end\"; messageId: ChatId }\n | {\n type: \"permission:request\";\n messageId: ChatId;\n toolName: string;\n toolArgs: Record<string, unknown>;\n }\n | {\n type: \"permission:response\";\n messageId: ChatId;\n toolName: string;\n allowed: boolean;\n }\n | {\n type: \"usage\";\n promptTokens: number;\n completionTokens: number;\n model?: string;\n }\n | { type: \"session:created\"; sessionId: ChatId }\n | { type: \"session:updated\"; sessionId: ChatId }\n | {\n type: \"error\";\n error: string;\n recoverable: boolean;\n code?: ErrorCode;\n messageId?: ChatId;\n }\n | { type: \"typing:start\" }\n | { type: \"typing:end\" }\n | { type: \"heartbeat\" }\n | { type: \"done\"; finalOutput?: string };\n\n/** All possible ChatEvent type strings */\nexport type ChatEventType = ChatEvent[\"type\"];\n\n// ─── Chat Middleware ───────────────────────────────────────────\n\n/** Context passed to ChatMiddleware hooks */\nexport interface ChatMiddlewareContext {\n sessionId: ChatId;\n signal: AbortSignal;\n}\n\n/** Runtime-level middleware for the send/receive lifecycle.\n * Different from EventMiddleware which operates at the event bus level. */\nexport interface ChatMiddleware {\n /** Transform message before sending to backend. Return null to reject the send. */\n onBeforeSend?(message: ChatMessage, context: ChatMiddlewareContext): ChatMessage | null | Promise<ChatMessage | null>;\n /** Transform/intercept stream events */\n onEvent?(event: ChatEvent, context: ChatMiddlewareContext): ChatEvent | null | Promise<ChatEvent | null>;\n /** Transform completed message after receiving from backend */\n onAfterReceive?(message: ChatMessage, context: ChatMiddlewareContext): ChatMessage | Promise<ChatMessage>;\n /** Intercept errors — return null to suppress, return error to propagate */\n onError?(error: Error, context: ChatMiddlewareContext): Error | null | Promise<Error | null>;\n}\n\n// ─── Chat Provider Abstraction ─────────────────────────────────\n\n/** Options for sending a message to a provider */\nexport interface SendMessageOptions {\n signal?: AbortSignal;\n /** Model to use for this request. Required for server-side runtime.send(). */\n model?: string;\n /** Per-call system prompt override (forwarded to the backend agent) */\n systemPrompt?: string;\n context?: Record<string, unknown>;\n /** Additional tools to include in this request */\n tools?: ToolDefinition[];\n}\n\n/** Options for runtime.send() — requires backend routing info */\nexport interface RuntimeSendOptions {\n /** Backend to route this request to (key in backends map) */\n backend: string;\n /** Authentication credentials for the backend factory */\n credentials: AuthToken;\n /** Model to use for this request */\n model: string;\n /** Per-call system prompt override (forwarded to the backend agent) */\n systemPrompt?: string;\n /** Abort signal */\n signal?: AbortSignal;\n /** Request-scoped context */\n context?: Record<string, unknown>;\n /** Additional tools */\n tools?: ToolDefinition[];\n}\n\n/**\n * @deprecated IChatProvider has been inlined into IChatBackend.\n * Import IChatBackend from \"@witqq/agent-sdk/chat/backends\" instead.\n * Kept as type alias for backward compatibility.\n */\nexport type IChatProvider = import(\"./backends/types.js\").IChatBackend;\n\n// ─── Factory Functions ─────────────────────────────────────────\n\n/**\n * Create a simple text ChatMessage.\n *\n * @param text - Message text content\n * @param role - Message role (default: \"user\")\n * @returns A complete ChatMessage with a single TextPart\n */\nexport function createTextMessage(text: string, role: ChatRole = \"user\"): ChatMessage {\n return {\n id: createChatId(),\n role,\n parts: [{ type: \"text\", text, status: \"complete\" }],\n createdAt: new Date().toISOString(),\n status: \"complete\",\n };\n}\n\n/** Type guard: checks if a session has reactive API (subscribe/getSnapshot) */\nexport function isObservableSession<TCustom extends Record<string, unknown> = Record<string, unknown>>(\n session: ChatSession<TCustom>,\n): session is ObservableSession<TCustom> {\n return \"subscribe\" in session && typeof (session as ObservableSession<TCustom>).subscribe === \"function\"\n && \"getSnapshot\" in session && typeof (session as ObservableSession<TCustom>).getSnapshot === \"function\";\n}\n","/**\n * @witqq/agent-sdk/chat/accumulator\n *\n * MessageAccumulator converts a stream of AgentEvent objects into a ChatMessage\n * with correct MessagePart array. Handles text, reasoning, and tool call\n * accumulation with proper status transitions.\n */\n\nimport type { AgentEvent } from \"../types.js\";\nimport type { ChatMessage, ChatId, MessagePart, TextPart, ReasoningPart, ToolCallPart } from \"./core.js\";\nimport { createChatId } from \"./core.js\";\n\n/**\n * Converts a stream of AgentEvent objects into a complete ChatMessage.\n * Tracks text, reasoning, and tool call parts with proper status transitions.\n *\n * @example\n * ```typescript\n * const acc = new MessageAccumulator();\n * for await (const event of agentEvents) {\n * acc.apply(event);\n * renderMessage(acc.snapshot()); // in-progress UI update\n * }\n * const message = acc.finalize();\n * ```\n */\nexport class MessageAccumulator {\n private readonly messageId: ChatId;\n private readonly parts: MessagePart[] = [];\n private status: \"pending\" | \"streaming\" | \"complete\" | \"error\" | \"cancelled\" = \"pending\";\n private currentTextPart: TextPart | null = null;\n private currentReasoningPart: ReasoningPart | null = null;\n private toolCallParts = new Map<string, ToolCallPart>();\n private _finalized = false;\n\n constructor(messageId?: ChatId) {\n this.messageId = messageId ?? createChatId();\n }\n\n /** Get current message ID */\n get id(): ChatId { return this.messageId; }\n\n /**\n * Apply an AgentEvent to accumulate into the message\n * @param event - AgentEvent to process\n * @throws Error if accumulator is already finalized\n */\n apply(event: AgentEvent): void {\n if (this._finalized) throw new Error(\"Cannot apply events to finalized accumulator\");\n\n if (this.status === \"pending\") {\n this.status = \"streaming\";\n }\n\n switch (event.type) {\n case \"text_delta\":\n this.handleTextDelta(event.text);\n break;\n case \"thinking_start\":\n this.finalizeCurrentText();\n this.currentReasoningPart = { type: \"reasoning\", text: \"\", status: \"streaming\" };\n this.parts.push(this.currentReasoningPart);\n break;\n case \"thinking_delta\":\n if (this.currentReasoningPart) {\n this.currentReasoningPart.text += event.text;\n }\n break;\n case \"thinking_end\":\n if (this.currentReasoningPart) {\n this.currentReasoningPart.status = \"complete\";\n this.currentReasoningPart = null;\n }\n break;\n case \"tool_call_start\": {\n this.finalizeCurrentText();\n const toolPart: ToolCallPart = {\n type: \"tool_call\",\n toolCallId: event.toolCallId,\n name: event.toolName,\n args: event.args,\n status: \"running\",\n };\n this.toolCallParts.set(event.toolCallId, toolPart);\n this.parts.push(toolPart);\n break;\n }\n case \"tool_call_end\": {\n const existing = this.toolCallParts.get(event.toolCallId);\n if (existing) {\n existing.result = event.result;\n existing.status = \"complete\";\n }\n break;\n }\n case \"error\":\n this.status = \"error\";\n break;\n case \"done\":\n break;\n // Other events (heartbeat, ask_user, etc.) — ignore\n }\n }\n\n private handleTextDelta(text: string): void {\n if (!this.currentTextPart) {\n this.currentTextPart = { type: \"text\", text: \"\", status: \"streaming\" };\n this.parts.push(this.currentTextPart);\n }\n this.currentTextPart.text += text;\n }\n\n private finalizeCurrentText(): void {\n if (this.currentTextPart) {\n this.currentTextPart.status = \"complete\";\n this.currentTextPart = null;\n }\n }\n\n /**\n * Get a snapshot of the current accumulated message (for streaming UI)\n * @returns ChatMessage with current parts and \"streaming\" status\n */\n snapshot(): ChatMessage {\n const now = new Date().toISOString();\n return {\n id: this.messageId,\n role: \"assistant\",\n parts: this.parts.map(p => ({ ...p })),\n status: this.status === \"pending\" ? \"pending\" : \"streaming\",\n createdAt: now,\n updatedAt: now,\n };\n }\n\n /**\n * Finalize the accumulator and return the complete ChatMessage\n * @returns Completed ChatMessage with all parts finalized\n * @throws Error if accumulator is already finalized\n */\n finalize(): ChatMessage {\n if (this._finalized) throw new Error(\"Accumulator already finalized\");\n this._finalized = true;\n\n // Finalize any open parts\n this.finalizeCurrentText();\n if (this.currentReasoningPart) {\n this.currentReasoningPart.status = \"complete\";\n this.currentReasoningPart = null;\n }\n\n // Mark incomplete tool calls as error\n for (const [, toolPart] of this.toolCallParts) {\n if (toolPart.status === \"running\" || toolPart.status === \"pending\") {\n toolPart.status = \"error\";\n }\n }\n\n // Set final message status\n if (this.status !== \"error\" && this.status !== \"cancelled\") {\n this.status = \"complete\";\n }\n\n const now = new Date().toISOString();\n return {\n id: this.messageId,\n role: \"assistant\",\n parts: this.parts,\n status: this.status,\n createdAt: now,\n updatedAt: now,\n };\n }\n\n /** Check if the accumulator has been finalized */\n get finalized(): boolean { return this._finalized; }\n}\n"]}