@incremark/chat-core 0.4.0-alpha.1

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.
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 wangyishuai
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1,89 @@
1
+ import { nanoid } from 'nanoid';
2
+
3
+ var __defProp = Object.defineProperty;
4
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
5
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
6
+ var TOOL_CALL_STATES = {
7
+ INPUT_STREAMING: "input-streaming",
8
+ INPUT_AVAILABLE: "input-available",
9
+ APPROVAL_REQUESTED: "approval-requested",
10
+ APPROVAL_RESPONDED: "approval-responded",
11
+ EXECUTING: "executing",
12
+ OUTPUT_AVAILABLE: "output-available",
13
+ OUTPUT_ERROR: "output-error",
14
+ OUTPUT_DENIED: "output-denied"
15
+ };
16
+ function isPartType(part, type) {
17
+ return part.type === type;
18
+ }
19
+ function isTextPart(part) {
20
+ return part.type === "text";
21
+ }
22
+ function isToolCallPart(part) {
23
+ return part.type === "tool-call";
24
+ }
25
+ function isSourcePart(part) {
26
+ return part.type === "source";
27
+ }
28
+ function isFilePart(part) {
29
+ return part.type === "file";
30
+ }
31
+ function isUIPart(part) {
32
+ return part.type === "ui";
33
+ }
34
+ function isReasoningPart(part) {
35
+ return part.type === "reasoning";
36
+ }
37
+ function createTextMessage(role, content, format = "markdown") {
38
+ return {
39
+ id: generateId(),
40
+ role,
41
+ parts: [{ type: "text", content, format }],
42
+ status: "success",
43
+ createdAt: Date.now()
44
+ };
45
+ }
46
+ function createStreamingMessage(role) {
47
+ return {
48
+ id: generateId(),
49
+ role,
50
+ parts: [],
51
+ status: "streaming",
52
+ createdAt: Date.now()
53
+ };
54
+ }
55
+ function generateId() {
56
+ return `msg-${Date.now()}-${nanoid()}`;
57
+ }
58
+
59
+ // src/protocol/transport.ts
60
+ var MockTransport = class {
61
+ constructor() {
62
+ __publicField(this, "aborted", false);
63
+ }
64
+ async *send(messages) {
65
+ messages[messages.length - 1];
66
+ const response = "This is a mock response from the AI assistant.";
67
+ const words = response.split(" ");
68
+ for (const word of words) {
69
+ if (this.aborted) {
70
+ yield { type: "error", error: "Aborted by user" };
71
+ return;
72
+ }
73
+ yield {
74
+ type: "text",
75
+ content: word + " ",
76
+ format: "markdown"
77
+ };
78
+ await new Promise((resolve) => setTimeout(resolve, 50));
79
+ }
80
+ yield { type: "done" };
81
+ }
82
+ abort() {
83
+ this.aborted = true;
84
+ }
85
+ };
86
+
87
+ export { MockTransport, TOOL_CALL_STATES, __publicField, createStreamingMessage, createTextMessage, isFilePart, isPartType, isReasoningPart, isSourcePart, isTextPart, isToolCallPart, isUIPart };
88
+ //# sourceMappingURL=chunk-FOTOSDBV.js.map
89
+ //# sourceMappingURL=chunk-FOTOSDBV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/protocol/message.ts","../src/protocol/transport.ts"],"names":[],"mappings":";;;;;AA4DO,IAAM,gBAAA,GAAmB;AAAA,EAC9B,eAAA,EAAiB,iBAAA;AAAA,EACjB,eAAA,EAAiB,iBAAA;AAAA,EACjB,kBAAA,EAAoB,oBAAA;AAAA,EACpB,kBAAA,EAAoB,oBAAA;AAAA,EACpB,SAAA,EAAW,WAAA;AAAA,EACX,gBAAA,EAAkB,kBAAA;AAAA,EAClB,YAAA,EAAc,cAAA;AAAA,EACd,aAAA,EAAe;AACjB;AA4HO,SAAS,UAAA,CACd,MACA,IAAA,EAC0B;AAC1B,EAAA,OAAO,KAAK,IAAA,KAAS,IAAA;AACvB;AAKO,SAAS,WAAW,IAAA,EAAqC;AAC9D,EAAA,OAAO,KAAK,IAAA,KAAS,MAAA;AACvB;AAKO,SAAS,eAAe,IAAA,EAAyC;AACtE,EAAA,OAAO,KAAK,IAAA,KAAS,WAAA;AACvB;AAKO,SAAS,aAAa,IAAA,EAAuC;AAClE,EAAA,OAAO,KAAK,IAAA,KAAS,QAAA;AACvB;AAKO,SAAS,WAAW,IAAA,EAAqC;AAC9D,EAAA,OAAO,KAAK,IAAA,KAAS,MAAA;AACvB;AAKO,SAAS,SAAS,IAAA,EAAmC;AAC1D,EAAA,OAAO,KAAK,IAAA,KAAS,IAAA;AACvB;AAKO,SAAS,gBAAgB,IAAA,EAA0C;AACxE,EAAA,OAAO,KAAK,IAAA,KAAS,WAAA;AACvB;AAyBO,SAAS,iBAAA,CACd,IAAA,EACA,OAAA,EACA,MAAA,GAA+B,UAAA,EAClB;AACb,EAAA,OAAO;AAAA,IACL,IAAI,UAAA,EAAW;AAAA,IACf,IAAA;AAAA,IACA,OAAO,CAAC,EAAE,MAAM,MAAA,EAAQ,OAAA,EAAS,QAAQ,CAAA;AAAA,IACzC,MAAA,EAAQ,SAAA;AAAA,IACR,SAAA,EAAW,KAAK,GAAA;AAAI,GACtB;AACF;AAKO,SAAS,uBAAuB,IAAA,EAA0C;AAC/E,EAAA,OAAO;AAAA,IACL,IAAI,UAAA,EAAW;AAAA,IACf,IAAA;AAAA,IACA,OAAO,EAAC;AAAA,IACR,MAAA,EAAQ,WAAA;AAAA,IACR,SAAA,EAAW,KAAK,GAAA;AAAI,GACtB;AACF;AAKA,SAAS,UAAA,GAAqB;AAC5B,EAAA,OAAO,OAAO,IAAA,CAAK,GAAA,EAAK,CAAA,CAAA,EAAI,QAAQ,CAAA,CAAA;AACtC;;;AC1MO,IAAM,gBAAN,MAA6C;AAAA,EAA7C,WAAA,GAAA;AACL,IAAA,aAAA,CAAA,IAAA,EAAQ,SAAA,EAAU,KAAA,CAAA;AAAA,EAAA;AAAA,EAElB,OAAO,KAAK,QAAA,EAAqD;AAC/D,IAAoB,QAAA,CAAS,QAAA,CAAS,MAAA,GAAS,CAAC;AAGhD,IAAA,MAAM,QAAA,GAAW,gDAAA;AACjB,IAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,KAAA,CAAM,GAAG,CAAA;AAEhC,IAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,MAAA,IAAI,KAAK,OAAA,EAAS;AAChB,QAAA,MAAM,EAAE,IAAA,EAAM,OAAA,EAAS,KAAA,EAAO,iBAAA,EAAkB;AAChD,QAAA;AAAA,MACF;AAEA,MAAA,MAAM;AAAA,QACJ,IAAA,EAAM,MAAA;AAAA,QACN,SAAS,IAAA,GAAO,GAAA;AAAA,QAChB,MAAA,EAAQ;AAAA,OACV;AAGA,MAAA,MAAM,IAAI,OAAA,CAAQ,CAAA,OAAA,KAAW,UAAA,CAAW,OAAA,EAAS,EAAE,CAAC,CAAA;AAAA,IACtD;AAEA,IAAA,MAAM,EAAE,MAAM,MAAA,EAAO;AAAA,EACvB;AAAA,EAEA,KAAA,GAAc;AACZ,IAAA,IAAA,CAAK,OAAA,GAAU,IAAA;AAAA,EACjB;AACF","file":"chunk-FOTOSDBV.js","sourcesContent":["import { nanoid } from 'nanoid'\n\n/**\n * Message protocol types for Incremark Chat-UI\n * Based on Vercel AI SDK & TanStack AI's parts-based message structure\n *\n * Extensibility: Users can extend MessagePart via module augmentation:\n * ```ts\n * declare module '@incremark/chat-core' {\n * interface CustomParts {\n * 'my-custom': { type: 'my-custom'; data: string };\n * }\n * }\n * ```\n */\n\n// ============================================================================\n// Base Types\n// ============================================================================\n\n/**\n * Supported message roles\n */\nexport type MessageRole = 'user' | 'assistant' | 'system' | 'agent';\n\n/**\n * Message status during lifecycle\n */\nexport type MessageStatus = 'pending' | 'streaming' | 'success' | 'error';\n\n// ============================================================================\n// Part Types\n// ============================================================================\n\n/**\n * Base part interface - all parts must have a type\n */\nexport interface BasePart {\n type: string;\n}\n\n/**\n * Text content part\n */\nexport interface TextPart extends BasePart {\n type: 'text';\n content: string;\n format?: 'markdown' | 'plain';\n}\n\n/**\n * Tool call state - 用户可自定义任意状态\n * 提供预定义常量供参考,但不限制用户使用其他值\n */\nexport type ToolCallState = string;\n\n/**\n * 预定义的工具调用状态常量(参考 Vercel AI SDK)\n * 用户可以使用这些常量,也可以使用自定义状态\n */\nexport const TOOL_CALL_STATES = {\n INPUT_STREAMING: 'input-streaming',\n INPUT_AVAILABLE: 'input-available',\n APPROVAL_REQUESTED: 'approval-requested',\n APPROVAL_RESPONDED: 'approval-responded',\n EXECUTING: 'executing',\n OUTPUT_AVAILABLE: 'output-available',\n OUTPUT_ERROR: 'output-error',\n OUTPUT_DENIED: 'output-denied'\n} as const;\n\n/**\n * Tool call part (when AI wants to use a tool)\n */\nexport interface ToolCallPart extends BasePart {\n type: 'tool-call';\n toolCallId: string;\n toolName: string;\n args: Record<string, unknown>;\n state: ToolCallState;\n /** 工具执行结果 */\n output?: unknown;\n /** 错误信息(当 state 为 output-error 时) */\n error?: string;\n}\n\n/**\n * Source reference type\n */\nexport type SourceType = 'url' | 'document';\n\n/**\n * Source part (reference to external content)\n */\nexport interface SourcePart extends BasePart {\n type: 'source';\n sourceId: string;\n sourceType: SourceType;\n url?: string;\n title?: string;\n /** 文档类型(当 sourceType 为 document 时) */\n mediaType?: string;\n}\n\n/**\n * File part (generated or uploaded file)\n */\nexport interface FilePart extends BasePart {\n type: 'file';\n fileId: string;\n /** base64 编码的文件内容或 URL */\n data: string;\n mediaType: string;\n filename?: string;\n}\n\n/**\n * UI component part (for A2UI - Agent to UI)\n */\nexport interface UIPart extends BasePart {\n type: 'ui';\n component: string;\n props: Record<string, unknown>;\n}\n\n/**\n * Reasoning/thought part (for chain-of-thought display)\n */\nexport interface ReasoningPart extends BasePart {\n type: 'reasoning';\n content: string;\n /** 推理状态 */\n status?: 'thinking' | 'completed' | 'error';\n /** 思考开始时间(毫秒时间戳) */\n startTime?: number;\n /** 思考结束时间(毫秒时间戳) */\n endTime?: number;\n /** 思考标题 */\n title?: string;\n}\n\n// ============================================================================\n// Extensibility Support\n// ============================================================================\n\n/**\n * Built-in parts registry\n */\nexport interface BuiltinParts {\n text: TextPart;\n 'tool-call': ToolCallPart;\n source: SourcePart;\n file: FilePart;\n ui: UIPart;\n reasoning: ReasoningPart;\n}\n\n/**\n * Custom parts registry - extend via module augmentation\n * @example\n * ```ts\n * declare module '@incremark/chat-core' {\n * interface CustomParts {\n * 'weather-card': { type: 'weather-card'; city: string; temp: number };\n * }\n * }\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface CustomParts {}\n\n/**\n * All registered parts\n */\nexport type PartsRegistry = BuiltinParts & CustomParts;\n\n/**\n * Union type for all message parts (built-in + custom)\n */\nexport type MessagePart = PartsRegistry[keyof PartsRegistry];\n\n/**\n * Get part type by name\n */\nexport type PartByType<T extends keyof PartsRegistry> = PartsRegistry[T];\n\n// ============================================================================\n// Type Guards\n// ============================================================================\n\n/**\n * Generic type guard for any part type\n */\nexport function isPartType<T extends keyof PartsRegistry>(\n part: MessagePart,\n type: T\n): part is PartsRegistry[T] {\n return part.type === type;\n}\n\n/**\n * Type guard for TextPart\n */\nexport function isTextPart(part: MessagePart): part is TextPart {\n return part.type === 'text';\n}\n\n/**\n * Type guard for ToolCallPart\n */\nexport function isToolCallPart(part: MessagePart): part is ToolCallPart {\n return part.type === 'tool-call';\n}\n\n/**\n * Type guard for SourcePart\n */\nexport function isSourcePart(part: MessagePart): part is SourcePart {\n return part.type === 'source';\n}\n\n/**\n * Type guard for FilePart\n */\nexport function isFilePart(part: MessagePart): part is FilePart {\n return part.type === 'file';\n}\n\n/**\n * Type guard for UIPart\n */\nexport function isUIPart(part: MessagePart): part is UIPart {\n return part.type === 'ui';\n}\n\n/**\n * Type guard for ReasoningPart\n */\nexport function isReasoningPart(part: MessagePart): part is ReasoningPart {\n return part.type === 'reasoning';\n}\n\n// ============================================================================\n// Message Types\n// ============================================================================\n\n/**\n * Complete message structure\n */\nexport interface ChatMessage {\n id: string;\n role: MessageRole;\n parts: MessagePart[];\n status: MessageStatus;\n createdAt: number;\n metadata?: Record<string, unknown>;\n}\n\n// ============================================================================\n// Factory Functions\n// ============================================================================\n\n/**\n * Create a new text message\n */\nexport function createTextMessage(\n role: MessageRole,\n content: string,\n format: 'markdown' | 'plain' = 'markdown'\n): ChatMessage {\n return {\n id: generateId(),\n role,\n parts: [{ type: 'text', content, format }],\n status: 'success',\n createdAt: Date.now()\n };\n}\n\n/**\n * Create a new empty streaming message\n */\nexport function createStreamingMessage(role: 'assistant' | 'agent'): ChatMessage {\n return {\n id: generateId(),\n role,\n parts: [],\n status: 'streaming',\n createdAt: Date.now()\n };\n}\n\n/**\n * Simple ID generator (can be replaced with more sophisticated one)\n */\nfunction generateId(): string {\n return `msg-${Date.now()}-${nanoid()}`;\n}\n","/**\n * Transport layer protocol for chat communication\n * Inspired by Vercel AI SDK 5.0+ transport architecture\n */\n\nimport type { ChatMessage } from './message.js';\n\n/**\n * Stream part - chunks of data received during streaming\n */\nexport type StreamPart =\n | TextStreamPart\n | ToolCallStreamPart\n | UIStreamPart\n | ReasoningStreamPart\n | DoneStreamPart\n | ErrorStreamPart;\n\n/**\n * Text content stream part\n */\nexport interface TextStreamPart {\n type: 'text';\n content: string;\n format?: 'markdown' | 'plain';\n}\n\n/**\n * Tool call stream part\n */\nexport interface ToolCallStreamPart {\n type: 'tool-call';\n toolCallId?: string;\n toolName: string;\n args: Record<string, any>;\n}\n\n/**\n * UI component stream part\n */\nexport interface UIStreamPart {\n type: 'ui';\n component: string;\n props: Record<string, any>;\n}\n\n/**\n * Reasoning stream part\n */\nexport interface ReasoningStreamPart {\n type: 'reasoning';\n content: string;\n}\n\n/**\n * Stream completion marker\n */\nexport interface DoneStreamPart {\n type: 'done';\n}\n\n/**\n * Error stream part\n */\nexport interface ErrorStreamPart {\n type: 'error';\n error: string;\n}\n\n/**\n * Transport interface for sending messages and receiving stream\n * This is the key abstraction that allows different implementations:\n * - HTTP streaming\n * - WebSocket\n * - Server-Sent Events (SSE)\n * - Mock for testing\n */\nexport interface ChatTransport {\n /**\n * Send messages and return async generator of stream parts\n *\n * @param messages - Full conversation history (or context window)\n * @returns Async generator yielding stream parts\n */\n send(messages: ChatMessage[]): AsyncGenerator<StreamPart>;\n\n /**\n * Abort the current stream (optional)\n */\n abort?(): void;\n}\n\n/**\n * Simple mock transport for testing\n */\nexport class MockTransport implements ChatTransport {\n private aborted = false;\n\n async *send(messages: ChatMessage[]): AsyncGenerator<StreamPart> {\n const lastMessage = messages[messages.length - 1];\n\n // Simulate streaming response\n const response = \"This is a mock response from the AI assistant.\";\n const words = response.split(' ');\n\n for (const word of words) {\n if (this.aborted) {\n yield { type: 'error', error: 'Aborted by user' };\n return;\n }\n\n yield {\n type: 'text',\n content: word + ' ',\n format: 'markdown'\n };\n\n // Simulate network delay\n await new Promise(resolve => setTimeout(resolve, 50));\n }\n\n yield { type: 'done' };\n }\n\n abort(): void {\n this.aborted = true;\n }\n}\n"]}
@@ -0,0 +1,104 @@
1
+ import { ChatMessage, MessageStatus, ChatTransport } from './protocol/index.js';
2
+ export { BasePart, BuiltinParts, CustomParts, DoneStreamPart, ErrorStreamPart, FilePart, MessagePart, MessageRole, MockTransport, PartByType, PartsRegistry, ReasoningPart, ReasoningStreamPart, SourcePart, SourceType, StreamPart, TOOL_CALL_STATES, TextPart, TextStreamPart, ToolCallPart, ToolCallState, ToolCallStreamPart, UIPart, UIStreamPart, createStreamingMessage, createTextMessage, isFilePart, isPartType, isReasoningPart, isSourcePart, isTextPart, isToolCallPart, isUIPart } from './protocol/index.js';
3
+
4
+ /**
5
+ * ChatEngine - Core engine for managing chat state and streaming
6
+ * Framework-agnostic implementation
7
+ */
8
+
9
+ /**
10
+ * State update listener type
11
+ */
12
+ type StateListener = (state: ChatEngineState) => void;
13
+ /**
14
+ * Engine state snapshot
15
+ */
16
+ interface ChatEngineState {
17
+ messages: ChatMessage[];
18
+ status: MessageStatus;
19
+ error?: string;
20
+ }
21
+ /**
22
+ * ID generator function type
23
+ */
24
+ type IDGenerator = () => string;
25
+ /**
26
+ * Default ID generator using nanoid
27
+ */
28
+ declare const defaultIDGenerator: IDGenerator;
29
+ /**
30
+ * Configuration for ChatEngine
31
+ */
32
+ interface ChatEngineConfig {
33
+ transport: ChatTransport;
34
+ initialMessages?: ChatMessage[];
35
+ idGenerator?: IDGenerator;
36
+ }
37
+ /**
38
+ * Core engine for chat functionality
39
+ * - Manages message history
40
+ * - Handles streaming responses
41
+ * - Emits state updates
42
+ */
43
+ declare class ChatEngine {
44
+ private messages;
45
+ private currentMessage;
46
+ private status;
47
+ private error;
48
+ private listeners;
49
+ private transport;
50
+ private abortController;
51
+ private idGenerator;
52
+ constructor(config: ChatEngineConfig);
53
+ /**
54
+ * Get current state (immutable snapshot)
55
+ */
56
+ getState(): ChatEngineState;
57
+ /**
58
+ * Subscribe to state updates
59
+ * @returns Unsubscribe function
60
+ */
61
+ subscribe(listener: StateListener): () => void;
62
+ /**
63
+ * Send a text message and process the streaming response
64
+ */
65
+ sendMessage(text: string): Promise<void>;
66
+ /**
67
+ * Abort current streaming
68
+ */
69
+ abort(): void;
70
+ /**
71
+ * Process streaming response from transport
72
+ */
73
+ private processStream;
74
+ /**
75
+ * Handle individual stream part
76
+ */
77
+ private handleStreamPart;
78
+ /**
79
+ * Handle text stream part - incrementally update last text part or create new one
80
+ */
81
+ private handleTextPart;
82
+ /**
83
+ * Handle tool call part
84
+ */
85
+ private handleToolCallPart;
86
+ /**
87
+ * Handle UI part
88
+ */
89
+ private handleUIPart;
90
+ /**
91
+ * Handle reasoning part
92
+ */
93
+ private handleReasoningPart;
94
+ /**
95
+ * Emit state update to all listeners
96
+ */
97
+ private emitUpdate;
98
+ /**
99
+ * Generate unique ID using configured generator
100
+ */
101
+ private generateId;
102
+ }
103
+
104
+ export { ChatEngine, type ChatEngineConfig, type ChatEngineState, ChatMessage, ChatTransport, type IDGenerator, MessageStatus, type StateListener, defaultIDGenerator };
package/dist/index.js ADDED
@@ -0,0 +1,198 @@
1
+ import { __publicField } from './chunk-FOTOSDBV.js';
2
+ export { MockTransport, TOOL_CALL_STATES, createStreamingMessage, createTextMessage, isFilePart, isPartType, isReasoningPart, isSourcePart, isTextPart, isToolCallPart, isUIPart } from './chunk-FOTOSDBV.js';
3
+ import { nanoid } from 'nanoid';
4
+
5
+ var defaultIDGenerator = () => `msg_${nanoid()}`;
6
+ var ChatEngine = class {
7
+ constructor(config) {
8
+ __publicField(this, "messages", []);
9
+ __publicField(this, "currentMessage", null);
10
+ __publicField(this, "status", "success");
11
+ __publicField(this, "error");
12
+ __publicField(this, "listeners", /* @__PURE__ */ new Set());
13
+ __publicField(this, "transport");
14
+ __publicField(this, "abortController", null);
15
+ __publicField(this, "idGenerator");
16
+ this.transport = config.transport;
17
+ this.idGenerator = config.idGenerator || defaultIDGenerator;
18
+ if (config.initialMessages) {
19
+ this.messages = [...config.initialMessages];
20
+ }
21
+ }
22
+ /**
23
+ * Get current state (immutable snapshot)
24
+ */
25
+ getState() {
26
+ return {
27
+ messages: [...this.messages],
28
+ status: this.status,
29
+ error: this.error
30
+ };
31
+ }
32
+ /**
33
+ * Subscribe to state updates
34
+ * @returns Unsubscribe function
35
+ */
36
+ subscribe(listener) {
37
+ this.listeners.add(listener);
38
+ return () => this.listeners.delete(listener);
39
+ }
40
+ /**
41
+ * Send a text message and process the streaming response
42
+ */
43
+ async sendMessage(text) {
44
+ const userMessage = {
45
+ id: this.generateId(),
46
+ role: "user",
47
+ parts: [{ type: "text", content: text, format: "plain" }],
48
+ status: "success",
49
+ createdAt: Date.now()
50
+ };
51
+ this.messages.push(userMessage);
52
+ this.emitUpdate();
53
+ const assistantMessage = {
54
+ id: this.generateId(),
55
+ role: "assistant",
56
+ parts: [],
57
+ status: "streaming",
58
+ createdAt: Date.now()
59
+ };
60
+ this.messages.push(assistantMessage);
61
+ this.currentMessage = assistantMessage;
62
+ this.status = "streaming";
63
+ this.error = void 0;
64
+ this.emitUpdate();
65
+ try {
66
+ await this.processStream();
67
+ assistantMessage.status = "success";
68
+ this.status = "success";
69
+ } catch (err) {
70
+ assistantMessage.status = "error";
71
+ this.status = "error";
72
+ this.error = err instanceof Error ? err.message : String(err);
73
+ } finally {
74
+ this.currentMessage = null;
75
+ this.emitUpdate();
76
+ }
77
+ }
78
+ /**
79
+ * Abort current streaming
80
+ */
81
+ abort() {
82
+ if (this.abortController) {
83
+ this.abortController.abort();
84
+ this.abortController = null;
85
+ }
86
+ this.transport.abort?.();
87
+ }
88
+ /**
89
+ * Process streaming response from transport
90
+ */
91
+ async processStream() {
92
+ this.abortController = new AbortController();
93
+ for await (const part of this.transport.send(this.messages)) {
94
+ if (this.abortController.signal.aborted) {
95
+ throw new Error("Aborted by user");
96
+ }
97
+ this.handleStreamPart(part);
98
+ }
99
+ }
100
+ /**
101
+ * Handle individual stream part
102
+ */
103
+ handleStreamPart(part) {
104
+ if (!this.currentMessage) return;
105
+ switch (part.type) {
106
+ case "text":
107
+ this.handleTextPart(part);
108
+ break;
109
+ case "tool-call":
110
+ this.handleToolCallPart(part);
111
+ break;
112
+ case "ui":
113
+ this.handleUIPart(part);
114
+ break;
115
+ case "reasoning":
116
+ this.handleReasoningPart(part);
117
+ break;
118
+ case "done":
119
+ break;
120
+ case "error":
121
+ throw new Error(part.error);
122
+ }
123
+ this.emitUpdate();
124
+ }
125
+ /**
126
+ * Handle text stream part - incrementally update last text part or create new one
127
+ */
128
+ handleTextPart(part) {
129
+ if (!this.currentMessage) return;
130
+ const lastPart = this.currentMessage.parts[this.currentMessage.parts.length - 1];
131
+ if (lastPart && lastPart.type === "text") {
132
+ lastPart.content += part.content;
133
+ } else {
134
+ this.currentMessage.parts.push({
135
+ type: "text",
136
+ content: part.content,
137
+ format: part.format || "markdown"
138
+ });
139
+ }
140
+ }
141
+ /**
142
+ * Handle tool call part
143
+ */
144
+ handleToolCallPart(part) {
145
+ if (!this.currentMessage) return;
146
+ this.currentMessage.parts.push({
147
+ type: "tool-call",
148
+ toolCallId: part.toolCallId || this.generateId(),
149
+ toolName: part.toolName,
150
+ args: part.args,
151
+ state: "input-available"
152
+ });
153
+ }
154
+ /**
155
+ * Handle UI part
156
+ */
157
+ handleUIPart(part) {
158
+ if (!this.currentMessage) return;
159
+ this.currentMessage.parts.push({
160
+ type: "ui",
161
+ component: part.component,
162
+ props: part.props
163
+ });
164
+ }
165
+ /**
166
+ * Handle reasoning part
167
+ */
168
+ handleReasoningPart(part) {
169
+ if (!this.currentMessage) return;
170
+ this.currentMessage.parts.push({
171
+ type: "reasoning",
172
+ content: part.content
173
+ });
174
+ }
175
+ /**
176
+ * Emit state update to all listeners
177
+ */
178
+ emitUpdate() {
179
+ const state = this.getState();
180
+ this.listeners.forEach((listener) => {
181
+ try {
182
+ listener(state);
183
+ } catch (err) {
184
+ console.error("Error in state listener:", err);
185
+ }
186
+ });
187
+ }
188
+ /**
189
+ * Generate unique ID using configured generator
190
+ */
191
+ generateId() {
192
+ return this.idGenerator();
193
+ }
194
+ };
195
+
196
+ export { ChatEngine, defaultIDGenerator };
197
+ //# sourceMappingURL=index.js.map
198
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/engine/chat-engine.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * ChatEngine - Core engine for managing chat state and streaming\n * Framework-agnostic implementation\n */\n\nimport { nanoid } from 'nanoid';\nimport type {\n ChatMessage,\n MessagePart,\n MessageStatus,\n StreamPart,\n ChatTransport,\n TextPart,\n ToolCallPart,\n UIPart,\n ReasoningPart\n} from '../protocol/index.js';\n\n/**\n * State update listener type\n */\nexport type StateListener = (state: ChatEngineState) => void;\n\n/**\n * Engine state snapshot\n */\nexport interface ChatEngineState {\n messages: ChatMessage[];\n status: MessageStatus;\n error?: string;\n}\n\n/**\n * ID generator function type\n */\nexport type IDGenerator = () => string;\n\n/**\n * Default ID generator using nanoid\n */\nexport const defaultIDGenerator: IDGenerator = () => `msg_${nanoid()}`;\n\n/**\n * Configuration for ChatEngine\n */\nexport interface ChatEngineConfig {\n transport: ChatTransport;\n initialMessages?: ChatMessage[];\n idGenerator?: IDGenerator;\n}\n\n/**\n * Core engine for chat functionality\n * - Manages message history\n * - Handles streaming responses\n * - Emits state updates\n */\nexport class ChatEngine {\n private messages: ChatMessage[] = [];\n private currentMessage: ChatMessage | null = null;\n private status: MessageStatus = 'success';\n private error: string | undefined;\n private listeners: Set<StateListener> = new Set();\n private transport: ChatTransport;\n private abortController: AbortController | null = null;\n private idGenerator: IDGenerator;\n\n constructor(config: ChatEngineConfig) {\n this.transport = config.transport;\n this.idGenerator = config.idGenerator || defaultIDGenerator;\n if (config.initialMessages) {\n this.messages = [...config.initialMessages];\n }\n }\n\n /**\n * Get current state (immutable snapshot)\n */\n getState(): ChatEngineState {\n return {\n messages: [...this.messages],\n status: this.status,\n error: this.error\n };\n }\n\n /**\n * Subscribe to state updates\n * @returns Unsubscribe function\n */\n subscribe(listener: StateListener): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n /**\n * Send a text message and process the streaming response\n */\n async sendMessage(text: string): Promise<void> {\n // Create user message\n const userMessage: ChatMessage = {\n id: this.generateId(),\n role: 'user',\n parts: [{ type: 'text', content: text, format: 'plain' }],\n status: 'success',\n createdAt: Date.now()\n };\n\n this.messages.push(userMessage);\n this.emitUpdate();\n\n // Create assistant message placeholder\n const assistantMessage: ChatMessage = {\n id: this.generateId(),\n role: 'assistant',\n parts: [],\n status: 'streaming',\n createdAt: Date.now()\n };\n\n this.messages.push(assistantMessage);\n this.currentMessage = assistantMessage;\n this.status = 'streaming';\n this.error = undefined;\n this.emitUpdate();\n\n // Process stream\n try {\n await this.processStream();\n assistantMessage.status = 'success';\n this.status = 'success';\n } catch (err) {\n assistantMessage.status = 'error';\n this.status = 'error';\n this.error = err instanceof Error ? err.message : String(err);\n } finally {\n this.currentMessage = null;\n this.emitUpdate();\n }\n }\n\n /**\n * Abort current streaming\n */\n abort(): void {\n if (this.abortController) {\n this.abortController.abort();\n this.abortController = null;\n }\n this.transport.abort?.();\n }\n\n /**\n * Process streaming response from transport\n */\n private async processStream(): Promise<void> {\n this.abortController = new AbortController();\n\n for await (const part of this.transport.send(this.messages)) {\n if (this.abortController.signal.aborted) {\n throw new Error('Aborted by user');\n }\n\n this.handleStreamPart(part);\n }\n }\n\n /**\n * Handle individual stream part\n */\n private handleStreamPart(part: StreamPart): void {\n if (!this.currentMessage) return;\n\n switch (part.type) {\n case 'text':\n this.handleTextPart(part);\n break;\n\n case 'tool-call':\n this.handleToolCallPart(part);\n break;\n\n case 'ui':\n this.handleUIPart(part);\n break;\n\n case 'reasoning':\n this.handleReasoningPart(part);\n break;\n\n case 'done':\n // Stream completed\n break;\n\n case 'error':\n throw new Error(part.error);\n }\n\n this.emitUpdate();\n }\n\n /**\n * Handle text stream part - incrementally update last text part or create new one\n */\n private handleTextPart(part: StreamPart & { type: 'text' }): void {\n if (!this.currentMessage) return;\n\n const lastPart = this.currentMessage.parts[this.currentMessage.parts.length - 1];\n\n // If last part is text, append to it (incremental update)\n if (lastPart && lastPart.type === 'text') {\n lastPart.content += part.content;\n } else {\n // Otherwise create new text part\n this.currentMessage.parts.push({\n type: 'text',\n content: part.content,\n format: part.format || 'markdown'\n });\n }\n }\n\n /**\n * Handle tool call part\n */\n private handleToolCallPart(part: StreamPart & { type: 'tool-call' }): void {\n if (!this.currentMessage) return;\n\n this.currentMessage.parts.push({\n type: 'tool-call',\n toolCallId: part.toolCallId || this.generateId(),\n toolName: part.toolName,\n args: part.args,\n state: 'input-available'\n });\n }\n\n /**\n * Handle UI part\n */\n private handleUIPart(part: StreamPart & { type: 'ui' }): void {\n if (!this.currentMessage) return;\n\n this.currentMessage.parts.push({\n type: 'ui',\n component: part.component,\n props: part.props\n });\n }\n\n /**\n * Handle reasoning part\n */\n private handleReasoningPart(part: StreamPart & { type: 'reasoning' }): void {\n if (!this.currentMessage) return;\n\n this.currentMessage.parts.push({\n type: 'reasoning',\n content: part.content\n });\n }\n\n /**\n * Emit state update to all listeners\n */\n private emitUpdate(): void {\n const state = this.getState();\n this.listeners.forEach(listener => {\n try {\n listener(state);\n } catch (err) {\n console.error('Error in state listener:', err);\n }\n });\n }\n\n /**\n * Generate unique ID using configured generator\n */\n private generateId(): string {\n return this.idGenerator();\n }\n}\n"]}
@@ -0,0 +1,287 @@
1
+ /**
2
+ * Message protocol types for Incremark Chat-UI
3
+ * Based on Vercel AI SDK & TanStack AI's parts-based message structure
4
+ *
5
+ * Extensibility: Users can extend MessagePart via module augmentation:
6
+ * ```ts
7
+ * declare module '@incremark/chat-core' {
8
+ * interface CustomParts {
9
+ * 'my-custom': { type: 'my-custom'; data: string };
10
+ * }
11
+ * }
12
+ * ```
13
+ */
14
+ /**
15
+ * Supported message roles
16
+ */
17
+ type MessageRole = 'user' | 'assistant' | 'system' | 'agent';
18
+ /**
19
+ * Message status during lifecycle
20
+ */
21
+ type MessageStatus = 'pending' | 'streaming' | 'success' | 'error';
22
+ /**
23
+ * Base part interface - all parts must have a type
24
+ */
25
+ interface BasePart {
26
+ type: string;
27
+ }
28
+ /**
29
+ * Text content part
30
+ */
31
+ interface TextPart extends BasePart {
32
+ type: 'text';
33
+ content: string;
34
+ format?: 'markdown' | 'plain';
35
+ }
36
+ /**
37
+ * Tool call state - 用户可自定义任意状态
38
+ * 提供预定义常量供参考,但不限制用户使用其他值
39
+ */
40
+ type ToolCallState = string;
41
+ /**
42
+ * 预定义的工具调用状态常量(参考 Vercel AI SDK)
43
+ * 用户可以使用这些常量,也可以使用自定义状态
44
+ */
45
+ declare const TOOL_CALL_STATES: {
46
+ readonly INPUT_STREAMING: "input-streaming";
47
+ readonly INPUT_AVAILABLE: "input-available";
48
+ readonly APPROVAL_REQUESTED: "approval-requested";
49
+ readonly APPROVAL_RESPONDED: "approval-responded";
50
+ readonly EXECUTING: "executing";
51
+ readonly OUTPUT_AVAILABLE: "output-available";
52
+ readonly OUTPUT_ERROR: "output-error";
53
+ readonly OUTPUT_DENIED: "output-denied";
54
+ };
55
+ /**
56
+ * Tool call part (when AI wants to use a tool)
57
+ */
58
+ interface ToolCallPart extends BasePart {
59
+ type: 'tool-call';
60
+ toolCallId: string;
61
+ toolName: string;
62
+ args: Record<string, unknown>;
63
+ state: ToolCallState;
64
+ /** 工具执行结果 */
65
+ output?: unknown;
66
+ /** 错误信息(当 state 为 output-error 时) */
67
+ error?: string;
68
+ }
69
+ /**
70
+ * Source reference type
71
+ */
72
+ type SourceType = 'url' | 'document';
73
+ /**
74
+ * Source part (reference to external content)
75
+ */
76
+ interface SourcePart extends BasePart {
77
+ type: 'source';
78
+ sourceId: string;
79
+ sourceType: SourceType;
80
+ url?: string;
81
+ title?: string;
82
+ /** 文档类型(当 sourceType 为 document 时) */
83
+ mediaType?: string;
84
+ }
85
+ /**
86
+ * File part (generated or uploaded file)
87
+ */
88
+ interface FilePart extends BasePart {
89
+ type: 'file';
90
+ fileId: string;
91
+ /** base64 编码的文件内容或 URL */
92
+ data: string;
93
+ mediaType: string;
94
+ filename?: string;
95
+ }
96
+ /**
97
+ * UI component part (for A2UI - Agent to UI)
98
+ */
99
+ interface UIPart extends BasePart {
100
+ type: 'ui';
101
+ component: string;
102
+ props: Record<string, unknown>;
103
+ }
104
+ /**
105
+ * Reasoning/thought part (for chain-of-thought display)
106
+ */
107
+ interface ReasoningPart extends BasePart {
108
+ type: 'reasoning';
109
+ content: string;
110
+ /** 推理状态 */
111
+ status?: 'thinking' | 'completed' | 'error';
112
+ /** 思考开始时间(毫秒时间戳) */
113
+ startTime?: number;
114
+ /** 思考结束时间(毫秒时间戳) */
115
+ endTime?: number;
116
+ /** 思考标题 */
117
+ title?: string;
118
+ }
119
+ /**
120
+ * Built-in parts registry
121
+ */
122
+ interface BuiltinParts {
123
+ text: TextPart;
124
+ 'tool-call': ToolCallPart;
125
+ source: SourcePart;
126
+ file: FilePart;
127
+ ui: UIPart;
128
+ reasoning: ReasoningPart;
129
+ }
130
+ /**
131
+ * Custom parts registry - extend via module augmentation
132
+ * @example
133
+ * ```ts
134
+ * declare module '@incremark/chat-core' {
135
+ * interface CustomParts {
136
+ * 'weather-card': { type: 'weather-card'; city: string; temp: number };
137
+ * }
138
+ * }
139
+ * ```
140
+ */
141
+ interface CustomParts {
142
+ }
143
+ /**
144
+ * All registered parts
145
+ */
146
+ type PartsRegistry = BuiltinParts & CustomParts;
147
+ /**
148
+ * Union type for all message parts (built-in + custom)
149
+ */
150
+ type MessagePart = PartsRegistry[keyof PartsRegistry];
151
+ /**
152
+ * Get part type by name
153
+ */
154
+ type PartByType<T extends keyof PartsRegistry> = PartsRegistry[T];
155
+ /**
156
+ * Generic type guard for any part type
157
+ */
158
+ declare function isPartType<T extends keyof PartsRegistry>(part: MessagePart, type: T): part is PartsRegistry[T];
159
+ /**
160
+ * Type guard for TextPart
161
+ */
162
+ declare function isTextPart(part: MessagePart): part is TextPart;
163
+ /**
164
+ * Type guard for ToolCallPart
165
+ */
166
+ declare function isToolCallPart(part: MessagePart): part is ToolCallPart;
167
+ /**
168
+ * Type guard for SourcePart
169
+ */
170
+ declare function isSourcePart(part: MessagePart): part is SourcePart;
171
+ /**
172
+ * Type guard for FilePart
173
+ */
174
+ declare function isFilePart(part: MessagePart): part is FilePart;
175
+ /**
176
+ * Type guard for UIPart
177
+ */
178
+ declare function isUIPart(part: MessagePart): part is UIPart;
179
+ /**
180
+ * Type guard for ReasoningPart
181
+ */
182
+ declare function isReasoningPart(part: MessagePart): part is ReasoningPart;
183
+ /**
184
+ * Complete message structure
185
+ */
186
+ interface ChatMessage {
187
+ id: string;
188
+ role: MessageRole;
189
+ parts: MessagePart[];
190
+ status: MessageStatus;
191
+ createdAt: number;
192
+ metadata?: Record<string, unknown>;
193
+ }
194
+ /**
195
+ * Create a new text message
196
+ */
197
+ declare function createTextMessage(role: MessageRole, content: string, format?: 'markdown' | 'plain'): ChatMessage;
198
+ /**
199
+ * Create a new empty streaming message
200
+ */
201
+ declare function createStreamingMessage(role: 'assistant' | 'agent'): ChatMessage;
202
+
203
+ /**
204
+ * Transport layer protocol for chat communication
205
+ * Inspired by Vercel AI SDK 5.0+ transport architecture
206
+ */
207
+
208
+ /**
209
+ * Stream part - chunks of data received during streaming
210
+ */
211
+ type StreamPart = TextStreamPart | ToolCallStreamPart | UIStreamPart | ReasoningStreamPart | DoneStreamPart | ErrorStreamPart;
212
+ /**
213
+ * Text content stream part
214
+ */
215
+ interface TextStreamPart {
216
+ type: 'text';
217
+ content: string;
218
+ format?: 'markdown' | 'plain';
219
+ }
220
+ /**
221
+ * Tool call stream part
222
+ */
223
+ interface ToolCallStreamPart {
224
+ type: 'tool-call';
225
+ toolCallId?: string;
226
+ toolName: string;
227
+ args: Record<string, any>;
228
+ }
229
+ /**
230
+ * UI component stream part
231
+ */
232
+ interface UIStreamPart {
233
+ type: 'ui';
234
+ component: string;
235
+ props: Record<string, any>;
236
+ }
237
+ /**
238
+ * Reasoning stream part
239
+ */
240
+ interface ReasoningStreamPart {
241
+ type: 'reasoning';
242
+ content: string;
243
+ }
244
+ /**
245
+ * Stream completion marker
246
+ */
247
+ interface DoneStreamPart {
248
+ type: 'done';
249
+ }
250
+ /**
251
+ * Error stream part
252
+ */
253
+ interface ErrorStreamPart {
254
+ type: 'error';
255
+ error: string;
256
+ }
257
+ /**
258
+ * Transport interface for sending messages and receiving stream
259
+ * This is the key abstraction that allows different implementations:
260
+ * - HTTP streaming
261
+ * - WebSocket
262
+ * - Server-Sent Events (SSE)
263
+ * - Mock for testing
264
+ */
265
+ interface ChatTransport {
266
+ /**
267
+ * Send messages and return async generator of stream parts
268
+ *
269
+ * @param messages - Full conversation history (or context window)
270
+ * @returns Async generator yielding stream parts
271
+ */
272
+ send(messages: ChatMessage[]): AsyncGenerator<StreamPart>;
273
+ /**
274
+ * Abort the current stream (optional)
275
+ */
276
+ abort?(): void;
277
+ }
278
+ /**
279
+ * Simple mock transport for testing
280
+ */
281
+ declare class MockTransport implements ChatTransport {
282
+ private aborted;
283
+ send(messages: ChatMessage[]): AsyncGenerator<StreamPart>;
284
+ abort(): void;
285
+ }
286
+
287
+ export { type BasePart, type BuiltinParts, type ChatMessage, type ChatTransport, type CustomParts, type DoneStreamPart, type ErrorStreamPart, type FilePart, type MessagePart, type MessageRole, type MessageStatus, MockTransport, type PartByType, type PartsRegistry, type ReasoningPart, type ReasoningStreamPart, type SourcePart, type SourceType, type StreamPart, TOOL_CALL_STATES, type TextPart, type TextStreamPart, type ToolCallPart, type ToolCallState, type ToolCallStreamPart, type UIPart, type UIStreamPart, createStreamingMessage, createTextMessage, isFilePart, isPartType, isReasoningPart, isSourcePart, isTextPart, isToolCallPart, isUIPart };
@@ -0,0 +1,3 @@
1
+ export { MockTransport, TOOL_CALL_STATES, createStreamingMessage, createTextMessage, isFilePart, isPartType, isReasoningPart, isSourcePart, isTextPart, isToolCallPart, isUIPart } from '../chunk-FOTOSDBV.js';
2
+ //# sourceMappingURL=index.js.map
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@incremark/chat-core",
3
+ "version": "0.4.0-alpha.1",
4
+ "description": "Framework-agnostic core library for building AI chat interfaces with incremental rendering support.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ },
14
+ "./protocol": {
15
+ "types": "./dist/protocol/index.d.ts",
16
+ "import": "./dist/protocol/index.js"
17
+ }
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "devDependencies": {
23
+ "tsup": "^8.5.1",
24
+ "typescript": "^5.3.0",
25
+ "vitest": "^4.0.16"
26
+ },
27
+ "keywords": [
28
+ "ai",
29
+ "chat",
30
+ "llm",
31
+ "streaming",
32
+ "incremental",
33
+ "framework-agnostic",
34
+ "typescript"
35
+ ],
36
+ "license": "MIT",
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "https://github.com/kingshuaishuai/incremark.git",
40
+ "directory": "packages/chat-core"
41
+ },
42
+ "dependencies": {
43
+ "nanoid": "^5.1.6"
44
+ },
45
+ "scripts": {
46
+ "build": "tsup",
47
+ "dev": "tsup --watch",
48
+ "test": "vitest",
49
+ "test:run": "vitest run",
50
+ "test:coverage": "vitest run -- --coverage"
51
+ }
52
+ }