@contractspec/module.ai-chat 0.0.0-canary-20260113170453
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 +21 -0
- package/README.md +169 -0
- package/dist/ai-chat.feature.d.ts +12 -0
- package/dist/ai-chat.feature.d.ts.map +1 -0
- package/dist/ai-chat.feature.js +102 -0
- package/dist/ai-chat.feature.js.map +1 -0
- package/dist/ai-chat.operations.d.ts +243 -0
- package/dist/ai-chat.operations.d.ts.map +1 -0
- package/dist/ai-chat.operations.js +172 -0
- package/dist/ai-chat.operations.js.map +1 -0
- package/dist/context/context-builder.d.ts +57 -0
- package/dist/context/context-builder.d.ts.map +1 -0
- package/dist/context/context-builder.js +148 -0
- package/dist/context/context-builder.js.map +1 -0
- package/dist/context/file-operations.d.ts +100 -0
- package/dist/context/file-operations.d.ts.map +1 -0
- package/dist/context/file-operations.js +175 -0
- package/dist/context/file-operations.js.map +1 -0
- package/dist/context/index.d.ts +4 -0
- package/dist/context/index.js +5 -0
- package/dist/context/workspace-context.d.ts +117 -0
- package/dist/context/workspace-context.d.ts.map +1 -0
- package/dist/context/workspace-context.js +124 -0
- package/dist/context/workspace-context.js.map +1 -0
- package/dist/core/chat-service.d.ts +73 -0
- package/dist/core/chat-service.d.ts.map +1 -0
- package/dist/core/chat-service.js +227 -0
- package/dist/core/chat-service.js.map +1 -0
- package/dist/core/conversation-store.d.ts +74 -0
- package/dist/core/conversation-store.d.ts.map +1 -0
- package/dist/core/conversation-store.js +109 -0
- package/dist/core/conversation-store.js.map +1 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.js +4 -0
- package/dist/core/message-types.d.ts +150 -0
- package/dist/core/message-types.d.ts.map +1 -0
- package/dist/events.d.ts +115 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +98 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +23 -0
- package/dist/presentation/components/ChatContainer.d.ts +21 -0
- package/dist/presentation/components/ChatContainer.d.ts.map +1 -0
- package/dist/presentation/components/ChatContainer.js +63 -0
- package/dist/presentation/components/ChatContainer.js.map +1 -0
- package/dist/presentation/components/ChatInput.d.ts +35 -0
- package/dist/presentation/components/ChatInput.d.ts.map +1 -0
- package/dist/presentation/components/ChatInput.js +149 -0
- package/dist/presentation/components/ChatInput.js.map +1 -0
- package/dist/presentation/components/ChatMessage.d.ts +24 -0
- package/dist/presentation/components/ChatMessage.d.ts.map +1 -0
- package/dist/presentation/components/ChatMessage.js +136 -0
- package/dist/presentation/components/ChatMessage.js.map +1 -0
- package/dist/presentation/components/CodePreview.d.ts +40 -0
- package/dist/presentation/components/CodePreview.d.ts.map +1 -0
- package/dist/presentation/components/CodePreview.js +127 -0
- package/dist/presentation/components/CodePreview.js.map +1 -0
- package/dist/presentation/components/ContextIndicator.d.ts +26 -0
- package/dist/presentation/components/ContextIndicator.d.ts.map +1 -0
- package/dist/presentation/components/ContextIndicator.js +97 -0
- package/dist/presentation/components/ContextIndicator.js.map +1 -0
- package/dist/presentation/components/ModelPicker.d.ts +39 -0
- package/dist/presentation/components/ModelPicker.d.ts.map +1 -0
- package/dist/presentation/components/ModelPicker.js +202 -0
- package/dist/presentation/components/ModelPicker.js.map +1 -0
- package/dist/presentation/components/index.d.ts +7 -0
- package/dist/presentation/components/index.js +8 -0
- package/dist/presentation/hooks/index.d.ts +3 -0
- package/dist/presentation/hooks/index.js +4 -0
- package/dist/presentation/hooks/useChat.d.ts +67 -0
- package/dist/presentation/hooks/useChat.d.ts.map +1 -0
- package/dist/presentation/hooks/useChat.js +172 -0
- package/dist/presentation/hooks/useChat.js.map +1 -0
- package/dist/presentation/hooks/useProviders.d.ts +38 -0
- package/dist/presentation/hooks/useProviders.d.ts.map +1 -0
- package/dist/presentation/hooks/useProviders.js +41 -0
- package/dist/presentation/hooks/useProviders.js.map +1 -0
- package/dist/presentation/index.d.ts +11 -0
- package/dist/presentation/index.js +12 -0
- package/dist/providers/chat-utilities.d.ts +15 -0
- package/dist/providers/chat-utilities.d.ts.map +1 -0
- package/dist/providers/chat-utilities.js +17 -0
- package/dist/providers/chat-utilities.js.map +1 -0
- package/dist/providers/index.d.ts +3 -0
- package/dist/providers/index.js +4 -0
- package/dist/schema.d.ts +222 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +100 -0
- package/dist/schema.js.map +1 -0
- package/package.json +87 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { InMemoryConversationStore } from "./conversation-store.js";
|
|
2
|
+
import { generateText, streamText } from "ai";
|
|
3
|
+
|
|
4
|
+
//#region src/core/chat-service.ts
|
|
5
|
+
/**
|
|
6
|
+
* Main chat orchestration service
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Default system prompt for ContractSpec vibe coding
|
|
10
|
+
*/
|
|
11
|
+
const DEFAULT_SYSTEM_PROMPT = `You are ContractSpec AI, an expert coding assistant specialized in ContractSpec development.
|
|
12
|
+
|
|
13
|
+
Your capabilities:
|
|
14
|
+
- Help users create, modify, and understand ContractSpec specifications
|
|
15
|
+
- Generate code that follows ContractSpec patterns and best practices
|
|
16
|
+
- Explain concepts from the ContractSpec documentation
|
|
17
|
+
- Suggest improvements and identify issues in specs and implementations
|
|
18
|
+
|
|
19
|
+
Guidelines:
|
|
20
|
+
- Be concise but thorough
|
|
21
|
+
- Provide code examples when helpful
|
|
22
|
+
- Reference relevant ContractSpec concepts and patterns
|
|
23
|
+
- Ask clarifying questions when the user's intent is unclear
|
|
24
|
+
- When suggesting code changes, explain the rationale`;
|
|
25
|
+
/**
|
|
26
|
+
* Main chat service for AI-powered conversations
|
|
27
|
+
*/
|
|
28
|
+
var ChatService = class {
|
|
29
|
+
provider;
|
|
30
|
+
context;
|
|
31
|
+
store;
|
|
32
|
+
systemPrompt;
|
|
33
|
+
maxHistoryMessages;
|
|
34
|
+
onUsage;
|
|
35
|
+
constructor(config) {
|
|
36
|
+
this.provider = config.provider;
|
|
37
|
+
this.context = config.context;
|
|
38
|
+
this.store = config.store ?? new InMemoryConversationStore();
|
|
39
|
+
this.systemPrompt = config.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
40
|
+
this.maxHistoryMessages = config.maxHistoryMessages ?? 20;
|
|
41
|
+
this.onUsage = config.onUsage;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Send a message and get a complete response
|
|
45
|
+
*/
|
|
46
|
+
async send(options) {
|
|
47
|
+
let conversation;
|
|
48
|
+
if (options.conversationId) {
|
|
49
|
+
const existing = await this.store.get(options.conversationId);
|
|
50
|
+
if (!existing) throw new Error(`Conversation ${options.conversationId} not found`);
|
|
51
|
+
conversation = existing;
|
|
52
|
+
} else conversation = await this.store.create({
|
|
53
|
+
status: "active",
|
|
54
|
+
provider: this.provider.name,
|
|
55
|
+
model: this.provider.model,
|
|
56
|
+
messages: [],
|
|
57
|
+
workspacePath: this.context?.workspacePath
|
|
58
|
+
});
|
|
59
|
+
await this.store.appendMessage(conversation.id, {
|
|
60
|
+
role: "user",
|
|
61
|
+
content: options.content,
|
|
62
|
+
status: "completed",
|
|
63
|
+
attachments: options.attachments
|
|
64
|
+
});
|
|
65
|
+
const prompt = this.buildPrompt(conversation, options);
|
|
66
|
+
const model = this.provider.getModel();
|
|
67
|
+
try {
|
|
68
|
+
const result = await generateText({
|
|
69
|
+
model,
|
|
70
|
+
prompt,
|
|
71
|
+
system: this.systemPrompt
|
|
72
|
+
});
|
|
73
|
+
const assistantMessage = await this.store.appendMessage(conversation.id, {
|
|
74
|
+
role: "assistant",
|
|
75
|
+
content: result.text,
|
|
76
|
+
status: "completed"
|
|
77
|
+
});
|
|
78
|
+
const updatedConversation = await this.store.get(conversation.id);
|
|
79
|
+
if (!updatedConversation) throw new Error("Conversation lost after update");
|
|
80
|
+
return {
|
|
81
|
+
message: assistantMessage,
|
|
82
|
+
conversation: updatedConversation
|
|
83
|
+
};
|
|
84
|
+
} catch (error) {
|
|
85
|
+
await this.store.appendMessage(conversation.id, {
|
|
86
|
+
role: "assistant",
|
|
87
|
+
content: "",
|
|
88
|
+
status: "error",
|
|
89
|
+
error: {
|
|
90
|
+
code: "generation_failed",
|
|
91
|
+
message: error instanceof Error ? error.message : String(error)
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Send a message and get a streaming response
|
|
99
|
+
*/
|
|
100
|
+
async stream(options) {
|
|
101
|
+
let conversation;
|
|
102
|
+
if (options.conversationId) {
|
|
103
|
+
const existing = await this.store.get(options.conversationId);
|
|
104
|
+
if (!existing) throw new Error(`Conversation ${options.conversationId} not found`);
|
|
105
|
+
conversation = existing;
|
|
106
|
+
} else conversation = await this.store.create({
|
|
107
|
+
status: "active",
|
|
108
|
+
provider: this.provider.name,
|
|
109
|
+
model: this.provider.model,
|
|
110
|
+
messages: [],
|
|
111
|
+
workspacePath: this.context?.workspacePath
|
|
112
|
+
});
|
|
113
|
+
await this.store.appendMessage(conversation.id, {
|
|
114
|
+
role: "user",
|
|
115
|
+
content: options.content,
|
|
116
|
+
status: "completed",
|
|
117
|
+
attachments: options.attachments
|
|
118
|
+
});
|
|
119
|
+
const assistantMessage = await this.store.appendMessage(conversation.id, {
|
|
120
|
+
role: "assistant",
|
|
121
|
+
content: "",
|
|
122
|
+
status: "streaming"
|
|
123
|
+
});
|
|
124
|
+
const prompt = this.buildPrompt(conversation, options);
|
|
125
|
+
const model = this.provider.getModel();
|
|
126
|
+
const self = {
|
|
127
|
+
systemPrompt: this.systemPrompt,
|
|
128
|
+
store: this.store
|
|
129
|
+
};
|
|
130
|
+
async function* streamGenerator() {
|
|
131
|
+
let fullContent = "";
|
|
132
|
+
try {
|
|
133
|
+
const result = streamText({
|
|
134
|
+
model,
|
|
135
|
+
prompt,
|
|
136
|
+
system: self.systemPrompt
|
|
137
|
+
});
|
|
138
|
+
for await (const chunk of result.textStream) {
|
|
139
|
+
fullContent += chunk;
|
|
140
|
+
yield {
|
|
141
|
+
type: "text",
|
|
142
|
+
content: chunk
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
await self.store.updateMessage(conversation.id, assistantMessage.id, {
|
|
146
|
+
content: fullContent,
|
|
147
|
+
status: "completed"
|
|
148
|
+
});
|
|
149
|
+
yield { type: "done" };
|
|
150
|
+
} catch (error) {
|
|
151
|
+
await self.store.updateMessage(conversation.id, assistantMessage.id, {
|
|
152
|
+
content: fullContent,
|
|
153
|
+
status: "error",
|
|
154
|
+
error: {
|
|
155
|
+
code: "stream_failed",
|
|
156
|
+
message: error instanceof Error ? error.message : String(error)
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
yield {
|
|
160
|
+
type: "error",
|
|
161
|
+
error: {
|
|
162
|
+
code: "stream_failed",
|
|
163
|
+
message: error instanceof Error ? error.message : String(error)
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
conversationId: conversation.id,
|
|
170
|
+
messageId: assistantMessage.id,
|
|
171
|
+
stream: streamGenerator()
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Get a conversation by ID
|
|
176
|
+
*/
|
|
177
|
+
async getConversation(conversationId) {
|
|
178
|
+
return this.store.get(conversationId);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* List conversations
|
|
182
|
+
*/
|
|
183
|
+
async listConversations(options) {
|
|
184
|
+
return this.store.list({
|
|
185
|
+
status: "active",
|
|
186
|
+
...options
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Delete a conversation
|
|
191
|
+
*/
|
|
192
|
+
async deleteConversation(conversationId) {
|
|
193
|
+
return this.store.delete(conversationId);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Build prompt string for LLM
|
|
197
|
+
*/
|
|
198
|
+
buildPrompt(conversation, options) {
|
|
199
|
+
let prompt = "";
|
|
200
|
+
const historyStart = Math.max(0, conversation.messages.length - this.maxHistoryMessages);
|
|
201
|
+
for (let i = historyStart; i < conversation.messages.length; i++) {
|
|
202
|
+
const msg = conversation.messages[i];
|
|
203
|
+
if (!msg) continue;
|
|
204
|
+
if (msg.role === "user" || msg.role === "assistant") prompt += `${msg.role === "user" ? "User" : "Assistant"}: ${msg.content}\n\n`;
|
|
205
|
+
}
|
|
206
|
+
let content = options.content;
|
|
207
|
+
if (options.attachments?.length) {
|
|
208
|
+
const attachmentInfo = options.attachments.map((a) => {
|
|
209
|
+
if (a.type === "file" || a.type === "code") return `\n\n### ${a.name}\n\`\`\`\n${a.content}\n\`\`\``;
|
|
210
|
+
return `\n\n[Attachment: ${a.name}]`;
|
|
211
|
+
}).join("");
|
|
212
|
+
content += attachmentInfo;
|
|
213
|
+
}
|
|
214
|
+
prompt += `User: ${content}\n\nAssistant:`;
|
|
215
|
+
return prompt;
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
/**
|
|
219
|
+
* Create a chat service with the given configuration
|
|
220
|
+
*/
|
|
221
|
+
function createChatService(config) {
|
|
222
|
+
return new ChatService(config);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
//#endregion
|
|
226
|
+
export { ChatService, createChatService };
|
|
227
|
+
//# sourceMappingURL=chat-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-service.js","names":[],"sources":["../../src/core/chat-service.ts"],"sourcesContent":["/**\n * Main chat orchestration service\n */\nimport { generateText, streamText } from 'ai';\nimport type { Provider as ChatProvider } from '@contractspec/lib.ai-providers';\nimport type { WorkspaceContext } from '../context/workspace-context';\nimport type { ConversationStore } from './conversation-store';\nimport { InMemoryConversationStore } from './conversation-store';\nimport type {\n ChatConversation,\n ChatStreamChunk,\n SendMessageOptions,\n SendMessageResult,\n StreamMessageResult,\n} from './message-types';\n\n/**\n * Configuration for ChatService\n */\nexport interface ChatServiceConfig {\n /** LLM provider to use */\n provider: ChatProvider;\n /** Optional workspace context for code-aware chat */\n context?: WorkspaceContext;\n /** Optional conversation store (defaults to in-memory) */\n store?: ConversationStore;\n /** Default system prompt */\n systemPrompt?: string;\n /** Maximum conversation history to include */\n maxHistoryMessages?: number;\n /** Callback for usage tracking */\n onUsage?: (usage: { inputTokens: number; outputTokens: number }) => void;\n}\n\n/**\n * Default system prompt for ContractSpec vibe coding\n */\nconst DEFAULT_SYSTEM_PROMPT = `You are ContractSpec AI, an expert coding assistant specialized in ContractSpec development.\n\nYour capabilities:\n- Help users create, modify, and understand ContractSpec specifications\n- Generate code that follows ContractSpec patterns and best practices\n- Explain concepts from the ContractSpec documentation\n- Suggest improvements and identify issues in specs and implementations\n\nGuidelines:\n- Be concise but thorough\n- Provide code examples when helpful\n- Reference relevant ContractSpec concepts and patterns\n- Ask clarifying questions when the user's intent is unclear\n- When suggesting code changes, explain the rationale`;\n\n/**\n * Main chat service for AI-powered conversations\n */\nexport class ChatService {\n private readonly provider: ChatProvider;\n private readonly context?: WorkspaceContext;\n private readonly store: ConversationStore;\n private readonly systemPrompt: string;\n private readonly maxHistoryMessages: number;\n private readonly onUsage?: (usage: {\n inputTokens: number;\n outputTokens: number;\n }) => void;\n\n constructor(config: ChatServiceConfig) {\n this.provider = config.provider;\n this.context = config.context;\n this.store = config.store ?? new InMemoryConversationStore();\n this.systemPrompt = config.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;\n this.maxHistoryMessages = config.maxHistoryMessages ?? 20;\n this.onUsage = config.onUsage;\n }\n\n /**\n * Send a message and get a complete response\n */\n async send(options: SendMessageOptions): Promise<SendMessageResult> {\n // Get or create conversation\n let conversation: ChatConversation;\n if (options.conversationId) {\n const existing = await this.store.get(options.conversationId);\n if (!existing) {\n throw new Error(`Conversation ${options.conversationId} not found`);\n }\n conversation = existing;\n } else {\n conversation = await this.store.create({\n status: 'active',\n provider: this.provider.name,\n model: this.provider.model,\n messages: [],\n workspacePath: this.context?.workspacePath,\n });\n }\n\n // Add user message\n await this.store.appendMessage(conversation.id, {\n role: 'user',\n content: options.content,\n status: 'completed',\n attachments: options.attachments,\n });\n\n // Build prompt from messages\n const prompt = this.buildPrompt(conversation, options);\n\n // Get the language model\n const model = this.provider.getModel();\n\n try {\n // Generate response\n const result = await generateText({\n model,\n prompt,\n system: this.systemPrompt,\n });\n\n // Save assistant message\n const assistantMessage = await this.store.appendMessage(conversation.id, {\n role: 'assistant',\n content: result.text,\n status: 'completed',\n });\n\n // Refresh conversation\n const updatedConversation = await this.store.get(conversation.id);\n if (!updatedConversation) {\n throw new Error('Conversation lost after update');\n }\n\n return {\n message: assistantMessage,\n conversation: updatedConversation,\n };\n } catch (error) {\n // Save error message\n await this.store.appendMessage(conversation.id, {\n role: 'assistant',\n content: '',\n status: 'error',\n error: {\n code: 'generation_failed',\n message: error instanceof Error ? error.message : String(error),\n },\n });\n\n throw error;\n }\n }\n\n /**\n * Send a message and get a streaming response\n */\n async stream(options: SendMessageOptions): Promise<StreamMessageResult> {\n // Get or create conversation\n let conversation: ChatConversation;\n if (options.conversationId) {\n const existing = await this.store.get(options.conversationId);\n if (!existing) {\n throw new Error(`Conversation ${options.conversationId} not found`);\n }\n conversation = existing;\n } else {\n conversation = await this.store.create({\n status: 'active',\n provider: this.provider.name,\n model: this.provider.model,\n messages: [],\n workspacePath: this.context?.workspacePath,\n });\n }\n\n // Add user message\n await this.store.appendMessage(conversation.id, {\n role: 'user',\n content: options.content,\n status: 'completed',\n attachments: options.attachments,\n });\n\n // Create placeholder for assistant message\n const assistantMessage = await this.store.appendMessage(conversation.id, {\n role: 'assistant',\n content: '',\n status: 'streaming',\n });\n\n // Build prompt\n const prompt = this.buildPrompt(conversation, options);\n\n // Get the language model\n const model = this.provider.getModel();\n\n // Create async generator for streaming\n const self = {\n systemPrompt: this.systemPrompt,\n store: this.store,\n };\n async function* streamGenerator(): AsyncIterable<ChatStreamChunk> {\n let fullContent = '';\n\n try {\n const result = streamText({\n model,\n prompt,\n system: self.systemPrompt,\n });\n\n for await (const chunk of result.textStream) {\n fullContent += chunk;\n yield { type: 'text', content: chunk };\n }\n\n // Update message with final content\n await self.store.updateMessage(conversation.id, assistantMessage.id, {\n content: fullContent,\n status: 'completed',\n });\n\n yield {\n type: 'done',\n };\n } catch (error) {\n await self.store.updateMessage(conversation.id, assistantMessage.id, {\n content: fullContent,\n status: 'error',\n error: {\n code: 'stream_failed',\n message: error instanceof Error ? error.message : String(error),\n },\n });\n\n yield {\n type: 'error',\n error: {\n code: 'stream_failed',\n message: error instanceof Error ? error.message : String(error),\n },\n };\n }\n }\n\n return {\n conversationId: conversation.id,\n messageId: assistantMessage.id,\n stream: streamGenerator(),\n };\n }\n\n /**\n * Get a conversation by ID\n */\n async getConversation(\n conversationId: string\n ): Promise<ChatConversation | null> {\n return this.store.get(conversationId);\n }\n\n /**\n * List conversations\n */\n async listConversations(options?: {\n limit?: number;\n offset?: number;\n }): Promise<ChatConversation[]> {\n return this.store.list({\n status: 'active',\n ...options,\n });\n }\n\n /**\n * Delete a conversation\n */\n async deleteConversation(conversationId: string): Promise<boolean> {\n return this.store.delete(conversationId);\n }\n\n /**\n * Build prompt string for LLM\n */\n private buildPrompt(\n conversation: ChatConversation,\n options: SendMessageOptions\n ): string {\n let prompt = '';\n\n // Add conversation history (limited)\n const historyStart = Math.max(\n 0,\n conversation.messages.length - this.maxHistoryMessages\n );\n for (let i = historyStart; i < conversation.messages.length; i++) {\n const msg = conversation.messages[i];\n if (!msg) continue;\n if (msg.role === 'user' || msg.role === 'assistant') {\n prompt += `${msg.role === 'user' ? 'User' : 'Assistant'}: ${msg.content}\\n\\n`;\n }\n }\n\n // Add current message with attachments\n let content = options.content;\n if (options.attachments?.length) {\n const attachmentInfo = options.attachments\n .map((a) => {\n if (a.type === 'file' || a.type === 'code') {\n return `\\n\\n### ${a.name}\\n\\`\\`\\`\\n${a.content}\\n\\`\\`\\``;\n }\n return `\\n\\n[Attachment: ${a.name}]`;\n })\n .join('');\n content += attachmentInfo;\n }\n\n prompt += `User: ${content}\\n\\nAssistant:`;\n\n return prompt;\n }\n}\n\n/**\n * Create a chat service with the given configuration\n */\nexport function createChatService(config: ChatServiceConfig): ChatService {\n return new ChatService(config);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { ChatConversation, ChatMessage, ConversationStatus } from "./message-types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/core/conversation-store.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Interface for conversation persistence
|
|
7
|
+
*/
|
|
8
|
+
interface ConversationStore {
|
|
9
|
+
/**
|
|
10
|
+
* Get a conversation by ID
|
|
11
|
+
*/
|
|
12
|
+
get(conversationId: string): Promise<ChatConversation | null>;
|
|
13
|
+
/**
|
|
14
|
+
* Create a new conversation
|
|
15
|
+
*/
|
|
16
|
+
create(conversation: Omit<ChatConversation, 'id' | 'createdAt' | 'updatedAt'>): Promise<ChatConversation>;
|
|
17
|
+
/**
|
|
18
|
+
* Update conversation properties
|
|
19
|
+
*/
|
|
20
|
+
update(conversationId: string, updates: Partial<Pick<ChatConversation, 'title' | 'status' | 'summary' | 'metadata'>>): Promise<ChatConversation | null>;
|
|
21
|
+
/**
|
|
22
|
+
* Append a message to a conversation
|
|
23
|
+
*/
|
|
24
|
+
appendMessage(conversationId: string, message: Omit<ChatMessage, 'id' | 'conversationId' | 'createdAt' | 'updatedAt'>): Promise<ChatMessage>;
|
|
25
|
+
/**
|
|
26
|
+
* Update a message in a conversation
|
|
27
|
+
*/
|
|
28
|
+
updateMessage(conversationId: string, messageId: string, updates: Partial<ChatMessage>): Promise<ChatMessage | null>;
|
|
29
|
+
/**
|
|
30
|
+
* Delete a conversation
|
|
31
|
+
*/
|
|
32
|
+
delete(conversationId: string): Promise<boolean>;
|
|
33
|
+
/**
|
|
34
|
+
* List conversations with optional filters
|
|
35
|
+
*/
|
|
36
|
+
list(options?: {
|
|
37
|
+
status?: ConversationStatus;
|
|
38
|
+
limit?: number;
|
|
39
|
+
offset?: number;
|
|
40
|
+
}): Promise<ChatConversation[]>;
|
|
41
|
+
/**
|
|
42
|
+
* Search conversations by content
|
|
43
|
+
*/
|
|
44
|
+
search(query: string, limit?: number): Promise<ChatConversation[]>;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* In-memory conversation store for development and testing
|
|
48
|
+
*/
|
|
49
|
+
declare class InMemoryConversationStore implements ConversationStore {
|
|
50
|
+
private readonly conversations;
|
|
51
|
+
get(conversationId: string): Promise<ChatConversation | null>;
|
|
52
|
+
create(conversation: Omit<ChatConversation, 'id' | 'createdAt' | 'updatedAt'>): Promise<ChatConversation>;
|
|
53
|
+
update(conversationId: string, updates: Partial<Pick<ChatConversation, 'title' | 'status' | 'summary' | 'metadata'>>): Promise<ChatConversation | null>;
|
|
54
|
+
appendMessage(conversationId: string, message: Omit<ChatMessage, 'id' | 'conversationId' | 'createdAt' | 'updatedAt'>): Promise<ChatMessage>;
|
|
55
|
+
updateMessage(conversationId: string, messageId: string, updates: Partial<ChatMessage>): Promise<ChatMessage | null>;
|
|
56
|
+
delete(conversationId: string): Promise<boolean>;
|
|
57
|
+
list(options?: {
|
|
58
|
+
status?: ConversationStatus;
|
|
59
|
+
limit?: number;
|
|
60
|
+
offset?: number;
|
|
61
|
+
}): Promise<ChatConversation[]>;
|
|
62
|
+
search(query: string, limit?: number): Promise<ChatConversation[]>;
|
|
63
|
+
/**
|
|
64
|
+
* Clear all conversations (for testing)
|
|
65
|
+
*/
|
|
66
|
+
clear(): void;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Create an in-memory conversation store
|
|
70
|
+
*/
|
|
71
|
+
declare function createInMemoryConversationStore(): ConversationStore;
|
|
72
|
+
//#endregion
|
|
73
|
+
export { ConversationStore, InMemoryConversationStore, createInMemoryConversationStore };
|
|
74
|
+
//# sourceMappingURL=conversation-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversation-store.d.ts","names":[],"sources":["../../src/core/conversation-store.ts"],"sourcesContent":[],"mappings":";;;;;;;AAsBkB,UAVD,iBAAA,CAUC;EACL;;;EAQP,GAAA,CAAA,cAAA,EAAA,MAAA,CAAA,EAfyB,OAezB,CAfiC,gBAejC,GAAA,IAAA,CAAA;EADO;;;EAWP,MAAA,CAAA,YAAA,EAnBY,IAmBZ,CAnBiB,gBAmBjB,EAAA,IAAA,GAAA,WAAA,GAAA,WAAA,CAAA,CAAA,EAlBD,OAkBC,CAlBO,gBAkBP,CAAA;EADO;;;EAYQ,MAAA,CAAA,cAAA,EAAA,MAAA,EAAA,OAAA,EAtBR,OAsBQ,CArBf,IAqBe,CArBV,gBAqBU,EAAA,OAAA,GAAA,QAAA,GAAA,SAAA,GAAA,UAAA,CAAA,CAAA,CAAA,EAnBhB,OAmBgB,CAnBR,gBAmBQ,GAAA,IAAA,CAAA;EAAR;;;EAMqB,aAAA,CAAA,cAAA,EAAA,MAAA,EAAA,OAAA,EAlBrB,IAkBqB,CAjB5B,WAiB4B,EAAA,IAAA,GAAA,gBAAA,GAAA,WAAA,GAAA,WAAA,CAAA,CAAA,EAd7B,OAc6B,CAdrB,WAcqB,CAAA;EAMrB;;;EAQoC,aAAA,CAAA,cAAA,EAAA,MAAA,EAAA,SAAA,EAAA,MAAA,EAAA,OAAA,EApBpC,OAoBoC,CApB5B,WAoB4B,CAAA,CAAA,EAnB5C,OAmB4C,CAnBpC,WAmBoC,GAAA,IAAA,CAAA;EAAR;;AAazC;EAG6C,MAAA,CAAA,cAAA,EAAA,MAAA,CAAA,EA9BX,OA8BW,CAAA,OAAA,CAAA;EAAR;;;EAMxB,IAAA,CAAA,OAcA,CAdA,EAAA;IAAR,MAAA,CAAA,EA9BQ,kBA8BR;IAeM,KAAA,CAAA,EAAA,MAAA;IAAL,MAAA,CAAA,EAAA,MAAA;EADO,CAAA,CAAA,EAzCP,OAyCO,CAzCC,gBAyCD,EAAA,CAAA;EAGA;;;EAeA,MAAA,CAAA,KAAA,EAAA,MAAA,EAAA,KAAA,CAAA,EAAA,MAAA,CAAA,EAtD4B,OAsD5B,CAtDoC,gBAsDpC,EAAA,CAAA;;;;;AA4BA,cArEA,yBAAA,YAAqC,iBAqErC,CAAA;EAAR,iBAAA,aAAA;EAsBmC,GAAA,CAAA,cAAA,EAAA,MAAA,CAAA,EAxFH,OAwFG,CAxFK,gBAwFL,GAAA,IAAA,CAAA;EAK3B,MAAA,CAAA,YAAA,EAxFK,IAwFL,CAxFU,gBAwFV,EAAA,IAAA,GAAA,WAAA,GAAA,WAAA,CAAA,CAAA,EAvFR,OAuFQ,CAvFA,gBAuFA,CAAA;EAGC,MAAA,CAAA,cAAA,EAAA,MAAA,EAAA,OAAA,EA5ED,OA4EC,CA3ER,IA2EQ,CA3EH,gBA2EG,EAAA,OAAA,GAAA,QAAA,GAAA,SAAA,GAAA,UAAA,CAAA,CAAA,CAAA,EAzET,OAyES,CAzED,gBAyEC,GAAA,IAAA,CAAA;EAAR,aAAA,CAAA,cAAA,EAAA,MAAA,EAAA,OAAA,EA1DO,IA0DP,CAzDA,WAyDA,EAAA,IAAA,GAAA,gBAAA,GAAA,WAAA,GAAA,WAAA,CAAA,CAAA,EAtDD,OAsDC,CAtDO,WAsDP,CAAA;EAe6C,aAAA,CAAA,cAAA,EAAA,MAAA,EAAA,SAAA,EAAA,MAAA,EAAA,OAAA,EA9CtC,OA8CsC,CA9C9B,WA8C8B,CAAA,CAAA,EA7C9C,OA6C8C,CA7CtC,WA6CsC,GAAA,IAAA,CAAA;EAAR,MAAA,CAAA,cAAA,EAAA,MAAA,CAAA,EAvBH,OAuBG,CAAA,OAAA,CAAA;EAlHO,IAAA,CAAA,QAAA,EAAA;IAAiB,MAAA,CAAA,EAgGtD,kBAhGsD;IAsJnD,KAAA,CAAA,EAAA,MAAA;;MAnDV,QAAQ;yCAe6B,QAAQ;;;;;;;;;iBAoCnC,+BAAA,CAAA,GAAmC"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
//#region src/core/conversation-store.ts
|
|
2
|
+
/**
|
|
3
|
+
* Generate a unique ID
|
|
4
|
+
*/
|
|
5
|
+
function generateId(prefix) {
|
|
6
|
+
return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* In-memory conversation store for development and testing
|
|
10
|
+
*/
|
|
11
|
+
var InMemoryConversationStore = class {
|
|
12
|
+
conversations = /* @__PURE__ */ new Map();
|
|
13
|
+
async get(conversationId) {
|
|
14
|
+
return this.conversations.get(conversationId) ?? null;
|
|
15
|
+
}
|
|
16
|
+
async create(conversation) {
|
|
17
|
+
const now = /* @__PURE__ */ new Date();
|
|
18
|
+
const fullConversation = {
|
|
19
|
+
...conversation,
|
|
20
|
+
id: generateId("conv"),
|
|
21
|
+
createdAt: now,
|
|
22
|
+
updatedAt: now
|
|
23
|
+
};
|
|
24
|
+
this.conversations.set(fullConversation.id, fullConversation);
|
|
25
|
+
return fullConversation;
|
|
26
|
+
}
|
|
27
|
+
async update(conversationId, updates) {
|
|
28
|
+
const conversation = this.conversations.get(conversationId);
|
|
29
|
+
if (!conversation) return null;
|
|
30
|
+
const updated = {
|
|
31
|
+
...conversation,
|
|
32
|
+
...updates,
|
|
33
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
34
|
+
};
|
|
35
|
+
this.conversations.set(conversationId, updated);
|
|
36
|
+
return updated;
|
|
37
|
+
}
|
|
38
|
+
async appendMessage(conversationId, message) {
|
|
39
|
+
const conversation = this.conversations.get(conversationId);
|
|
40
|
+
if (!conversation) throw new Error(`Conversation ${conversationId} not found`);
|
|
41
|
+
const now = /* @__PURE__ */ new Date();
|
|
42
|
+
const fullMessage = {
|
|
43
|
+
...message,
|
|
44
|
+
id: generateId("msg"),
|
|
45
|
+
conversationId,
|
|
46
|
+
createdAt: now,
|
|
47
|
+
updatedAt: now
|
|
48
|
+
};
|
|
49
|
+
conversation.messages.push(fullMessage);
|
|
50
|
+
conversation.updatedAt = now;
|
|
51
|
+
return fullMessage;
|
|
52
|
+
}
|
|
53
|
+
async updateMessage(conversationId, messageId, updates) {
|
|
54
|
+
const conversation = this.conversations.get(conversationId);
|
|
55
|
+
if (!conversation) return null;
|
|
56
|
+
const messageIndex = conversation.messages.findIndex((m) => m.id === messageId);
|
|
57
|
+
if (messageIndex === -1) return null;
|
|
58
|
+
const message = conversation.messages[messageIndex];
|
|
59
|
+
if (!message) return null;
|
|
60
|
+
const updated = {
|
|
61
|
+
...message,
|
|
62
|
+
...updates,
|
|
63
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
64
|
+
};
|
|
65
|
+
conversation.messages[messageIndex] = updated;
|
|
66
|
+
conversation.updatedAt = /* @__PURE__ */ new Date();
|
|
67
|
+
return updated;
|
|
68
|
+
}
|
|
69
|
+
async delete(conversationId) {
|
|
70
|
+
return this.conversations.delete(conversationId);
|
|
71
|
+
}
|
|
72
|
+
async list(options) {
|
|
73
|
+
let results = Array.from(this.conversations.values());
|
|
74
|
+
if (options?.status) results = results.filter((c) => c.status === options.status);
|
|
75
|
+
results.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
|
|
76
|
+
const offset = options?.offset ?? 0;
|
|
77
|
+
const limit = options?.limit ?? 100;
|
|
78
|
+
return results.slice(offset, offset + limit);
|
|
79
|
+
}
|
|
80
|
+
async search(query, limit = 20) {
|
|
81
|
+
const lowerQuery = query.toLowerCase();
|
|
82
|
+
const results = [];
|
|
83
|
+
for (const conversation of this.conversations.values()) {
|
|
84
|
+
if (conversation.title?.toLowerCase().includes(lowerQuery)) {
|
|
85
|
+
results.push(conversation);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (conversation.messages.some((m) => m.content.toLowerCase().includes(lowerQuery))) results.push(conversation);
|
|
89
|
+
if (results.length >= limit) break;
|
|
90
|
+
}
|
|
91
|
+
return results;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Clear all conversations (for testing)
|
|
95
|
+
*/
|
|
96
|
+
clear() {
|
|
97
|
+
this.conversations.clear();
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Create an in-memory conversation store
|
|
102
|
+
*/
|
|
103
|
+
function createInMemoryConversationStore() {
|
|
104
|
+
return new InMemoryConversationStore();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
//#endregion
|
|
108
|
+
export { InMemoryConversationStore, createInMemoryConversationStore };
|
|
109
|
+
//# sourceMappingURL=conversation-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversation-store.js","names":[],"sources":["../../src/core/conversation-store.ts"],"sourcesContent":["/**\n * Conversation storage interface and implementations\n */\nimport type {\n ChatConversation,\n ChatMessage,\n ConversationStatus,\n} from './message-types';\n\n/**\n * Interface for conversation persistence\n */\nexport interface ConversationStore {\n /**\n * Get a conversation by ID\n */\n get(conversationId: string): Promise<ChatConversation | null>;\n\n /**\n * Create a new conversation\n */\n create(\n conversation: Omit<ChatConversation, 'id' | 'createdAt' | 'updatedAt'>\n ): Promise<ChatConversation>;\n\n /**\n * Update conversation properties\n */\n update(\n conversationId: string,\n updates: Partial<\n Pick<ChatConversation, 'title' | 'status' | 'summary' | 'metadata'>\n >\n ): Promise<ChatConversation | null>;\n\n /**\n * Append a message to a conversation\n */\n appendMessage(\n conversationId: string,\n message: Omit<\n ChatMessage,\n 'id' | 'conversationId' | 'createdAt' | 'updatedAt'\n >\n ): Promise<ChatMessage>;\n\n /**\n * Update a message in a conversation\n */\n updateMessage(\n conversationId: string,\n messageId: string,\n updates: Partial<ChatMessage>\n ): Promise<ChatMessage | null>;\n\n /**\n * Delete a conversation\n */\n delete(conversationId: string): Promise<boolean>;\n\n /**\n * List conversations with optional filters\n */\n list(options?: {\n status?: ConversationStatus;\n limit?: number;\n offset?: number;\n }): Promise<ChatConversation[]>;\n\n /**\n * Search conversations by content\n */\n search(query: string, limit?: number): Promise<ChatConversation[]>;\n}\n\n/**\n * Generate a unique ID\n */\nfunction generateId(prefix: string): string {\n return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;\n}\n\n/**\n * In-memory conversation store for development and testing\n */\nexport class InMemoryConversationStore implements ConversationStore {\n private readonly conversations = new Map<string, ChatConversation>();\n\n async get(conversationId: string): Promise<ChatConversation | null> {\n return this.conversations.get(conversationId) ?? null;\n }\n\n async create(\n conversation: Omit<ChatConversation, 'id' | 'createdAt' | 'updatedAt'>\n ): Promise<ChatConversation> {\n const now = new Date();\n const fullConversation: ChatConversation = {\n ...conversation,\n id: generateId('conv'),\n createdAt: now,\n updatedAt: now,\n };\n this.conversations.set(fullConversation.id, fullConversation);\n return fullConversation;\n }\n\n async update(\n conversationId: string,\n updates: Partial<\n Pick<ChatConversation, 'title' | 'status' | 'summary' | 'metadata'>\n >\n ): Promise<ChatConversation | null> {\n const conversation = this.conversations.get(conversationId);\n if (!conversation) return null;\n\n const updated = {\n ...conversation,\n ...updates,\n updatedAt: new Date(),\n };\n this.conversations.set(conversationId, updated);\n return updated;\n }\n\n async appendMessage(\n conversationId: string,\n message: Omit<\n ChatMessage,\n 'id' | 'conversationId' | 'createdAt' | 'updatedAt'\n >\n ): Promise<ChatMessage> {\n const conversation = this.conversations.get(conversationId);\n if (!conversation) {\n throw new Error(`Conversation ${conversationId} not found`);\n }\n\n const now = new Date();\n const fullMessage: ChatMessage = {\n ...message,\n id: generateId('msg'),\n conversationId,\n createdAt: now,\n updatedAt: now,\n };\n\n conversation.messages.push(fullMessage);\n conversation.updatedAt = now;\n return fullMessage;\n }\n\n async updateMessage(\n conversationId: string,\n messageId: string,\n updates: Partial<ChatMessage>\n ): Promise<ChatMessage | null> {\n const conversation = this.conversations.get(conversationId);\n if (!conversation) return null;\n\n const messageIndex = conversation.messages.findIndex(\n (m) => m.id === messageId\n );\n if (messageIndex === -1) return null;\n\n const message = conversation.messages[messageIndex];\n if (!message) return null;\n\n const updated = {\n ...message,\n ...updates,\n updatedAt: new Date(),\n };\n conversation.messages[messageIndex] = updated;\n conversation.updatedAt = new Date();\n return updated;\n }\n\n async delete(conversationId: string): Promise<boolean> {\n return this.conversations.delete(conversationId);\n }\n\n async list(options?: {\n status?: ConversationStatus;\n limit?: number;\n offset?: number;\n }): Promise<ChatConversation[]> {\n let results = Array.from(this.conversations.values());\n\n if (options?.status) {\n results = results.filter((c) => c.status === options.status);\n }\n\n // Sort by updatedAt descending\n results.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());\n\n const offset = options?.offset ?? 0;\n const limit = options?.limit ?? 100;\n return results.slice(offset, offset + limit);\n }\n\n async search(query: string, limit = 20): Promise<ChatConversation[]> {\n const lowerQuery = query.toLowerCase();\n const results: ChatConversation[] = [];\n\n for (const conversation of this.conversations.values()) {\n // Search in title\n if (conversation.title?.toLowerCase().includes(lowerQuery)) {\n results.push(conversation);\n continue;\n }\n\n // Search in messages\n const hasMatch = conversation.messages.some((m) =>\n m.content.toLowerCase().includes(lowerQuery)\n );\n if (hasMatch) {\n results.push(conversation);\n }\n\n if (results.length >= limit) break;\n }\n\n return results;\n }\n\n /**\n * Clear all conversations (for testing)\n */\n clear(): void {\n this.conversations.clear();\n }\n}\n\n/**\n * Create an in-memory conversation store\n */\nexport function createInMemoryConversationStore(): ConversationStore {\n return new InMemoryConversationStore();\n}\n"],"mappings":";;;;AA8EA,SAAS,WAAW,QAAwB;AAC1C,QAAO,GAAG,OAAO,GAAG,KAAK,KAAK,CAAC,GAAG,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,MAAM,GAAG,GAAG;;;;;AAM3E,IAAa,4BAAb,MAAoE;CAClE,AAAiB,gCAAgB,IAAI,KAA+B;CAEpE,MAAM,IAAI,gBAA0D;AAClE,SAAO,KAAK,cAAc,IAAI,eAAe,IAAI;;CAGnD,MAAM,OACJ,cAC2B;EAC3B,MAAM,sBAAM,IAAI,MAAM;EACtB,MAAM,mBAAqC;GACzC,GAAG;GACH,IAAI,WAAW,OAAO;GACtB,WAAW;GACX,WAAW;GACZ;AACD,OAAK,cAAc,IAAI,iBAAiB,IAAI,iBAAiB;AAC7D,SAAO;;CAGT,MAAM,OACJ,gBACA,SAGkC;EAClC,MAAM,eAAe,KAAK,cAAc,IAAI,eAAe;AAC3D,MAAI,CAAC,aAAc,QAAO;EAE1B,MAAM,UAAU;GACd,GAAG;GACH,GAAG;GACH,2BAAW,IAAI,MAAM;GACtB;AACD,OAAK,cAAc,IAAI,gBAAgB,QAAQ;AAC/C,SAAO;;CAGT,MAAM,cACJ,gBACA,SAIsB;EACtB,MAAM,eAAe,KAAK,cAAc,IAAI,eAAe;AAC3D,MAAI,CAAC,aACH,OAAM,IAAI,MAAM,gBAAgB,eAAe,YAAY;EAG7D,MAAM,sBAAM,IAAI,MAAM;EACtB,MAAM,cAA2B;GAC/B,GAAG;GACH,IAAI,WAAW,MAAM;GACrB;GACA,WAAW;GACX,WAAW;GACZ;AAED,eAAa,SAAS,KAAK,YAAY;AACvC,eAAa,YAAY;AACzB,SAAO;;CAGT,MAAM,cACJ,gBACA,WACA,SAC6B;EAC7B,MAAM,eAAe,KAAK,cAAc,IAAI,eAAe;AAC3D,MAAI,CAAC,aAAc,QAAO;EAE1B,MAAM,eAAe,aAAa,SAAS,WACxC,MAAM,EAAE,OAAO,UACjB;AACD,MAAI,iBAAiB,GAAI,QAAO;EAEhC,MAAM,UAAU,aAAa,SAAS;AACtC,MAAI,CAAC,QAAS,QAAO;EAErB,MAAM,UAAU;GACd,GAAG;GACH,GAAG;GACH,2BAAW,IAAI,MAAM;GACtB;AACD,eAAa,SAAS,gBAAgB;AACtC,eAAa,4BAAY,IAAI,MAAM;AACnC,SAAO;;CAGT,MAAM,OAAO,gBAA0C;AACrD,SAAO,KAAK,cAAc,OAAO,eAAe;;CAGlD,MAAM,KAAK,SAIqB;EAC9B,IAAI,UAAU,MAAM,KAAK,KAAK,cAAc,QAAQ,CAAC;AAErD,MAAI,SAAS,OACX,WAAU,QAAQ,QAAQ,MAAM,EAAE,WAAW,QAAQ,OAAO;AAI9D,UAAQ,MAAM,GAAG,MAAM,EAAE,UAAU,SAAS,GAAG,EAAE,UAAU,SAAS,CAAC;EAErE,MAAM,SAAS,SAAS,UAAU;EAClC,MAAM,QAAQ,SAAS,SAAS;AAChC,SAAO,QAAQ,MAAM,QAAQ,SAAS,MAAM;;CAG9C,MAAM,OAAO,OAAe,QAAQ,IAAiC;EACnE,MAAM,aAAa,MAAM,aAAa;EACtC,MAAM,UAA8B,EAAE;AAEtC,OAAK,MAAM,gBAAgB,KAAK,cAAc,QAAQ,EAAE;AAEtD,OAAI,aAAa,OAAO,aAAa,CAAC,SAAS,WAAW,EAAE;AAC1D,YAAQ,KAAK,aAAa;AAC1B;;AAOF,OAHiB,aAAa,SAAS,MAAM,MAC3C,EAAE,QAAQ,aAAa,CAAC,SAAS,WAAW,CAC7C,CAEC,SAAQ,KAAK,aAAa;AAG5B,OAAI,QAAQ,UAAU,MAAO;;AAG/B,SAAO;;;;;CAMT,QAAc;AACZ,OAAK,cAAc,OAAO;;;;;;AAO9B,SAAgB,kCAAqD;AACnE,QAAO,IAAI,2BAA2B"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { ChatAttachment, ChatCodeBlock, ChatConversation, ChatMessage, ChatRole, ChatSource, ChatStreamChunk, ChatToolCall, ConversationStatus, MessageStatus, SendMessageOptions, SendMessageResult, StreamMessageResult } from "./message-types.js";
|
|
2
|
+
import { ConversationStore, InMemoryConversationStore, createInMemoryConversationStore } from "./conversation-store.js";
|
|
3
|
+
import { ChatService, ChatServiceConfig, createChatService } from "./chat-service.js";
|
|
4
|
+
export { ChatAttachment, ChatCodeBlock, ChatConversation, ChatMessage, ChatRole, ChatService, ChatServiceConfig, ChatSource, ChatStreamChunk, ChatToolCall, ConversationStatus, ConversationStore, InMemoryConversationStore, MessageStatus, SendMessageOptions, SendMessageResult, StreamMessageResult, createChatService, createInMemoryConversationStore };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { InMemoryConversationStore, createInMemoryConversationStore } from "./conversation-store.js";
|
|
2
|
+
import { ChatService, createChatService } from "./chat-service.js";
|
|
3
|
+
|
|
4
|
+
export { ChatService, InMemoryConversationStore, createChatService, createInMemoryConversationStore };
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
//#region src/core/message-types.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Message and conversation types for AI Chat
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Role of a message participant
|
|
7
|
+
*/
|
|
8
|
+
type ChatRole = 'user' | 'assistant' | 'system';
|
|
9
|
+
/**
|
|
10
|
+
* Status of a message
|
|
11
|
+
*/
|
|
12
|
+
type MessageStatus = 'pending' | 'streaming' | 'completed' | 'error';
|
|
13
|
+
/**
|
|
14
|
+
* Attachment type for messages
|
|
15
|
+
*/
|
|
16
|
+
interface ChatAttachment {
|
|
17
|
+
id: string;
|
|
18
|
+
type: 'file' | 'image' | 'code' | 'spec';
|
|
19
|
+
name: string;
|
|
20
|
+
content?: string;
|
|
21
|
+
mimeType?: string;
|
|
22
|
+
size?: number;
|
|
23
|
+
path?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Code block within a message
|
|
27
|
+
*/
|
|
28
|
+
interface ChatCodeBlock {
|
|
29
|
+
id: string;
|
|
30
|
+
language: string;
|
|
31
|
+
code: string;
|
|
32
|
+
filename?: string;
|
|
33
|
+
startLine?: number;
|
|
34
|
+
endLine?: number;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Tool call information
|
|
38
|
+
*/
|
|
39
|
+
interface ChatToolCall {
|
|
40
|
+
id: string;
|
|
41
|
+
name: string;
|
|
42
|
+
args: Record<string, unknown>;
|
|
43
|
+
result?: unknown;
|
|
44
|
+
status: 'pending' | 'running' | 'completed' | 'error';
|
|
45
|
+
error?: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Source/citation in a message
|
|
49
|
+
*/
|
|
50
|
+
interface ChatSource {
|
|
51
|
+
id: string;
|
|
52
|
+
title: string;
|
|
53
|
+
url?: string;
|
|
54
|
+
snippet?: string;
|
|
55
|
+
type: 'file' | 'spec' | 'doc' | 'web';
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* A single chat message
|
|
59
|
+
*/
|
|
60
|
+
interface ChatMessage {
|
|
61
|
+
id: string;
|
|
62
|
+
conversationId: string;
|
|
63
|
+
role: ChatRole;
|
|
64
|
+
content: string;
|
|
65
|
+
status: MessageStatus;
|
|
66
|
+
createdAt: Date;
|
|
67
|
+
updatedAt: Date;
|
|
68
|
+
attachments?: ChatAttachment[];
|
|
69
|
+
codeBlocks?: ChatCodeBlock[];
|
|
70
|
+
toolCalls?: ChatToolCall[];
|
|
71
|
+
sources?: ChatSource[];
|
|
72
|
+
reasoning?: string;
|
|
73
|
+
usage?: {
|
|
74
|
+
inputTokens: number;
|
|
75
|
+
outputTokens: number;
|
|
76
|
+
};
|
|
77
|
+
error?: {
|
|
78
|
+
code: string;
|
|
79
|
+
message: string;
|
|
80
|
+
};
|
|
81
|
+
metadata?: Record<string, unknown>;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Conversation status
|
|
85
|
+
*/
|
|
86
|
+
type ConversationStatus = 'active' | 'archived' | 'deleted';
|
|
87
|
+
/**
|
|
88
|
+
* A conversation containing multiple messages
|
|
89
|
+
*/
|
|
90
|
+
interface ChatConversation {
|
|
91
|
+
id: string;
|
|
92
|
+
title?: string;
|
|
93
|
+
status: ConversationStatus;
|
|
94
|
+
createdAt: Date;
|
|
95
|
+
updatedAt: Date;
|
|
96
|
+
provider: string;
|
|
97
|
+
model: string;
|
|
98
|
+
workspacePath?: string;
|
|
99
|
+
contextFiles?: string[];
|
|
100
|
+
messages: ChatMessage[];
|
|
101
|
+
summary?: string;
|
|
102
|
+
metadata?: Record<string, unknown>;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Options for sending a message
|
|
106
|
+
*/
|
|
107
|
+
interface SendMessageOptions {
|
|
108
|
+
conversationId?: string;
|
|
109
|
+
content: string;
|
|
110
|
+
attachments?: ChatAttachment[];
|
|
111
|
+
systemPrompt?: string;
|
|
112
|
+
maxTokens?: number;
|
|
113
|
+
temperature?: number;
|
|
114
|
+
stream?: boolean;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Streaming chunk from AI response
|
|
118
|
+
*/
|
|
119
|
+
interface ChatStreamChunk {
|
|
120
|
+
type: 'text' | 'reasoning' | 'tool_call' | 'source' | 'error' | 'done';
|
|
121
|
+
content?: string;
|
|
122
|
+
toolCall?: ChatToolCall;
|
|
123
|
+
source?: ChatSource;
|
|
124
|
+
error?: {
|
|
125
|
+
code: string;
|
|
126
|
+
message: string;
|
|
127
|
+
};
|
|
128
|
+
usage?: {
|
|
129
|
+
inputTokens: number;
|
|
130
|
+
outputTokens: number;
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Result of sending a message
|
|
135
|
+
*/
|
|
136
|
+
interface SendMessageResult {
|
|
137
|
+
message: ChatMessage;
|
|
138
|
+
conversation: ChatConversation;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Streaming result
|
|
142
|
+
*/
|
|
143
|
+
interface StreamMessageResult {
|
|
144
|
+
conversationId: string;
|
|
145
|
+
messageId: string;
|
|
146
|
+
stream: AsyncIterable<ChatStreamChunk>;
|
|
147
|
+
}
|
|
148
|
+
//#endregion
|
|
149
|
+
export { ChatAttachment, ChatCodeBlock, ChatConversation, ChatMessage, ChatRole, ChatSource, ChatStreamChunk, ChatToolCall, ConversationStatus, MessageStatus, SendMessageOptions, SendMessageResult, StreamMessageResult };
|
|
150
|
+
//# sourceMappingURL=message-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-types.d.ts","names":[],"sources":["../../src/core/message-types.ts"],"sourcesContent":[],"mappings":";;AAOA;AAKA;AAKA;AAaA;AAYA;AAYiB,KA/CL,QAAA,GA+Ce,MAAA,GAAA,WAAA,GAAA,QAAA;AAW3B;;;AAMa,KA3DD,aAAA,GA2DC,SAAA,GAAA,WAAA,GAAA,WAAA,GAAA,OAAA;;;;AAMC,UA5DG,cAAA,CA4DH;EACF,EAAA,EAAA,MAAA;EAkBC,IAAA,EAAA,MAAA,GAAA,OAAA,GAAA,MAAA,GAAA,MAAA;EAAM,IAAA,EAAA,MAAA;EAMP,OAAA,CAAA,EAAA,MAAA;EAKK,QAAA,CAAA,EAAA,MAAA;EAGP,IAAA,CAAA,EAAA,MAAA;EACG,IAAA,CAAA,EAAA,MAAA;;;;;AAwBI,UAzGA,aAAA,CAyGkB;EAalB,EAAA,EAAA,MAAA;EAYA,QAAA,EAAA,MAAA;EAQA,IAAA,EAAA,MAAA;;;;;;;;UA9HA,YAAA;;;QAGT;;;;;;;;UASS,UAAA;;;;;;;;;;UAWA,WAAA;;;QAGT;;UAEE;aACG;aACA;gBAGG;eACD;cACD;YACF;;;;;;;;;;aAkBC;;;;;KAMD,kBAAA;;;;UAKK,gBAAA;;;UAGP;aACG;aACA;;;;;YAWD;;aAMC;;;;;UAMI,kBAAA;;;gBAGD;;;;;;;;;UAUC,eAAA;;;aAGJ;WACF;;;;;;;;;;;;;UAQM,iBAAA;WACN;gBACK;;;;;UAMC,mBAAA;;;UAGP,cAAc"}
|