tarsk 0.2.5 → 0.3.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.
- package/README.md +1 -7
- package/dist/index.d.ts +3 -0
- package/dist/index.js +92 -32
- package/dist/lib/response-builder.d.ts +50 -0
- package/dist/lib/response-builder.js +56 -0
- package/dist/lib/stream-helper.d.ts +39 -0
- package/dist/lib/stream-helper.js +43 -0
- package/dist/managers/ConversationManager.d.ts +83 -0
- package/dist/managers/ConversationManager.js +129 -0
- package/dist/managers/GitManager.d.ts +133 -0
- package/dist/managers/GitManager.js +330 -0
- package/dist/managers/MetadataManager.d.ts +139 -0
- package/dist/managers/MetadataManager.js +309 -0
- package/dist/managers/ModelManager.d.ts +57 -0
- package/dist/managers/ModelManager.js +129 -0
- package/dist/managers/NeovateExecutor.d.ts +40 -0
- package/dist/managers/NeovateExecutor.js +138 -0
- package/dist/managers/ProjectManager.d.ts +162 -0
- package/dist/managers/ProjectManager.js +353 -0
- package/dist/managers/ThreadManager.d.ts +181 -0
- package/dist/managers/ThreadManager.js +325 -0
- package/dist/managers/conversation-manager.d.ts +83 -0
- package/dist/managers/conversation-manager.js +129 -0
- package/dist/managers/git-manager.d.ts +133 -0
- package/dist/managers/git-manager.js +330 -0
- package/dist/managers/metadata-manager.d.ts +139 -0
- package/dist/managers/metadata-manager.js +305 -0
- package/dist/managers/model-manager.d.ts +59 -0
- package/dist/managers/model-manager.js +144 -0
- package/dist/managers/neovate-executor.d.ts +43 -0
- package/dist/managers/neovate-executor.js +205 -0
- package/dist/managers/processing-state-manager.d.ts +40 -0
- package/dist/managers/processing-state-manager.js +27 -0
- package/dist/managers/project-manager.d.ts +199 -0
- package/dist/managers/project-manager.js +465 -0
- package/dist/managers/thread-manager.d.ts +193 -0
- package/dist/managers/thread-manager.js +368 -0
- package/dist/model-info-aihubmix.d.ts +25 -0
- package/dist/model-info-aihubmix.js +117 -0
- package/dist/model-info-openai.d.ts +17 -0
- package/dist/model-info-openai.js +59 -0
- package/dist/model-info-openrouter.d.ts +25 -0
- package/dist/model-info-openrouter.js +101 -0
- package/dist/model-info.d.ts +37 -0
- package/dist/model-info.js +39 -0
- package/dist/provider-data.d.ts +101 -0
- package/dist/provider-data.js +471 -0
- package/dist/provider.d.ts +10 -0
- package/dist/provider.js +192 -0
- package/dist/public/android-chrome-192x192.png +0 -0
- package/dist/public/android-chrome-512x512.png +0 -0
- package/dist/public/apple-touch-icon.png +0 -0
- package/dist/public/assets/index-B443aj9k.js +8506 -0
- package/dist/public/assets/index-CjXGVbI7.css +1 -0
- package/dist/public/assets/index-DJC-p914.js +8506 -0
- package/dist/public/favicon-16x16.png +0 -0
- package/dist/public/favicon-32x32.png +0 -0
- package/dist/public/favicon.ico +0 -0
- package/dist/public/index.html +28 -0
- package/dist/public/manifest.json +82 -0
- package/dist/public/placeholder-logo.svg +1 -0
- package/dist/public/placeholder.svg +1 -0
- package/dist/public/snpro.woff2 +0 -0
- package/dist/public/tarsk-color.svg +12 -0
- package/dist/public/tarsk.png +0 -0
- package/dist/public/tarsk.svg +12 -0
- package/dist/public/zalando-sans.woff2 +0 -0
- package/dist/routes/chat-old.d.ts +21 -0
- package/dist/routes/chat-old.js +251 -0
- package/dist/routes/chat.d.ts +21 -0
- package/dist/routes/chat.js +217 -0
- package/dist/routes/git.d.ts +4 -0
- package/dist/routes/git.js +668 -0
- package/dist/routes/models.d.ts +18 -0
- package/dist/routes/models.js +128 -0
- package/dist/routes/projects-old.d.ts +20 -0
- package/dist/routes/projects-old.js +297 -0
- package/dist/routes/projects.d.ts +20 -0
- package/dist/routes/projects.js +365 -0
- package/dist/routes/providers.d.ts +15 -0
- package/dist/routes/providers.js +130 -0
- package/dist/routes/threads-old.d.ts +14 -0
- package/dist/routes/threads-old.js +393 -0
- package/dist/routes/threads.d.ts +14 -0
- package/dist/routes/threads.js +352 -0
- package/dist/types/models.d.ts +315 -0
- package/dist/types/models.js +11 -0
- package/dist/utils/env-manager.d.ts +3 -0
- package/dist/utils/env-manager.js +60 -0
- package/dist/utils/open-router-models.d.ts +45 -0
- package/dist/utils/open-router-models.js +103 -0
- package/dist/utils/openai-models.d.ts +63 -0
- package/dist/utils/openai-models.js +152 -0
- package/dist/utils/openai-pricing-scraper.d.ts +17 -0
- package/dist/utils/openai-pricing-scraper.js +185 -0
- package/dist/utils/validation.d.ts +10 -0
- package/dist/utils/validation.js +20 -0
- package/dist/utils.d.ts +10 -0
- package/dist/utils.js +12 -0
- package/package.json +36 -22
- package/LICENSE.md +0 -7
- package/dist/agent/agent.js +0 -131
- package/dist/agent/interfaces.js +0 -1
- package/dist/api/encryption.js +0 -41
- package/dist/api/models.js +0 -169
- package/dist/api/prompt.js +0 -12
- package/dist/api/settings.js +0 -43
- package/dist/api/test.js +0 -29
- package/dist/api/tools.js +0 -287
- package/dist/api/utils.js +0 -18
- package/dist/interfaces/meta.js +0 -1
- package/dist/interfaces/model.js +0 -1
- package/dist/interfaces/settings.js +0 -1
- package/dist/log/log.js +0 -33
- package/dist/prompt.js +0 -49
- package/dist/tools.js +0 -84
- package/dist/utils/files.js +0 -14
- package/dist/utils/json-file.js +0 -28
- package/dist/utils/strip-markdown.js +0 -5
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thread routes for the REST API
|
|
3
|
+
*
|
|
4
|
+
* Handles all thread-related operations:
|
|
5
|
+
* - POST /api/threads - Create a new thread
|
|
6
|
+
* - GET /api/threads?projectId=xxx - List threads for a project
|
|
7
|
+
* - DELETE /api/threads/:id - Delete a thread
|
|
8
|
+
*/
|
|
9
|
+
import { Hono } from 'hono';
|
|
10
|
+
import { validateThreadName } from '../utils/validation.js';
|
|
11
|
+
import { streamAsyncGenerator } from '../lib/stream-helper.js';
|
|
12
|
+
import { ResponseBuilder } from '../lib/response-builder.js';
|
|
13
|
+
const extractTextBlocksFromContent = (content) => {
|
|
14
|
+
const trimmed = content.trim();
|
|
15
|
+
if (!trimmed.startsWith('[') && !trimmed.startsWith('{')) {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const parsed = JSON.parse(trimmed);
|
|
20
|
+
if (Array.isArray(parsed)) {
|
|
21
|
+
return parsed
|
|
22
|
+
.filter((block) => block && typeof block === 'object' && block.type === 'text')
|
|
23
|
+
.map((block) => (typeof block.text === 'string' ? block.text : ''))
|
|
24
|
+
.filter((text) => text.length > 0);
|
|
25
|
+
}
|
|
26
|
+
if (parsed && typeof parsed === 'object' && typeof parsed.text === 'string') {
|
|
27
|
+
return [parsed.text];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
return [];
|
|
34
|
+
};
|
|
35
|
+
const extractAssistantContent = (events, fallback) => {
|
|
36
|
+
if (!events || events.length === 0) {
|
|
37
|
+
return fallback;
|
|
38
|
+
}
|
|
39
|
+
const resultEvent = [...events].reverse().find((event) => event.type === 'result');
|
|
40
|
+
if (resultEvent && typeof resultEvent.content === 'string' && resultEvent.content.trim().length > 0) {
|
|
41
|
+
return resultEvent.content;
|
|
42
|
+
}
|
|
43
|
+
const assistantMessages = events.filter((event) => event.type === 'message' && event.role === 'assistant');
|
|
44
|
+
let lastTextBlock = null;
|
|
45
|
+
const assistantChunks = [];
|
|
46
|
+
for (const event of assistantMessages) {
|
|
47
|
+
if (typeof event.content !== 'string') {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
assistantChunks.push(event.content);
|
|
51
|
+
const textBlocks = extractTextBlocksFromContent(event.content);
|
|
52
|
+
if (textBlocks.length > 0) {
|
|
53
|
+
lastTextBlock = textBlocks[textBlocks.length - 1];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (lastTextBlock) {
|
|
57
|
+
return lastTextBlock;
|
|
58
|
+
}
|
|
59
|
+
if (assistantChunks.length === 0) {
|
|
60
|
+
return fallback;
|
|
61
|
+
}
|
|
62
|
+
const combined = assistantChunks.join('');
|
|
63
|
+
return combined.trim().length > 0 ? combined : fallback;
|
|
64
|
+
};
|
|
65
|
+
export function createThreadRoutes(threadManager, gitManager, conversationManager) {
|
|
66
|
+
const router = new Hono();
|
|
67
|
+
/**
|
|
68
|
+
* POST /api/threads
|
|
69
|
+
* Create a new thread for a project
|
|
70
|
+
*
|
|
71
|
+
* Request body:
|
|
72
|
+
* {
|
|
73
|
+
* "projectId": "uuid",
|
|
74
|
+
* "title": "Optional thread title"
|
|
75
|
+
* }
|
|
76
|
+
*
|
|
77
|
+
* Response: Newline-delimited JSON stream of ThreadEvent objects
|
|
78
|
+
*
|
|
79
|
+
* Requirements:
|
|
80
|
+
* - 6.3 - THE CLI SHALL expose REST API endpoints for Thread operations
|
|
81
|
+
* - 8.3 - WHEN creating a Thread, THE App SHALL send a POST request with the Project identifier to the CLI
|
|
82
|
+
* - 8.4 - THE CLI SHALL support streaming responses to the client
|
|
83
|
+
*/
|
|
84
|
+
router.post('/', async (c) => {
|
|
85
|
+
try {
|
|
86
|
+
const body = await c.req.json();
|
|
87
|
+
const { projectId, title } = body;
|
|
88
|
+
// Validate projectId is provided
|
|
89
|
+
if (!projectId || typeof projectId !== 'string') {
|
|
90
|
+
const { response, statusCode } = ResponseBuilder.error('INVALID_REQUEST', 'projectId is required and must be a string', 400);
|
|
91
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
92
|
+
return c.json(response, statusCode);
|
|
93
|
+
}
|
|
94
|
+
// Validate title if provided
|
|
95
|
+
if (title !== undefined && typeof title !== 'string') {
|
|
96
|
+
const { response, statusCode } = ResponseBuilder.error('INVALID_REQUEST', 'title must be a string if provided', 400);
|
|
97
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
98
|
+
return c.json(response, statusCode);
|
|
99
|
+
}
|
|
100
|
+
// Validate thread name (empty/whitespace is allowed, will auto-generate)
|
|
101
|
+
if (title && title.trim() !== '') {
|
|
102
|
+
const nameError = validateThreadName(title);
|
|
103
|
+
if (nameError) {
|
|
104
|
+
const { response, statusCode } = ResponseBuilder.error('INVALID_REQUEST', nameError, 400);
|
|
105
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
106
|
+
return c.json(response, statusCode);
|
|
107
|
+
}
|
|
108
|
+
// Check for duplicate branch names
|
|
109
|
+
const existingThreads = await threadManager.listThreads(projectId);
|
|
110
|
+
const sanitizedNewName = gitManager.sanitizeBranchName(title);
|
|
111
|
+
const existingSanitized = existingThreads.map(t => gitManager.sanitizeBranchName(t.title));
|
|
112
|
+
if (existingSanitized.includes(sanitizedNewName)) {
|
|
113
|
+
const { response, statusCode } = ResponseBuilder.error('DUPLICATE_BRANCH', `A thread with branch name "${sanitizedNewName}" already exists`, 409);
|
|
114
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
115
|
+
return c.json(response, statusCode);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Stream the thread creation events
|
|
119
|
+
return streamAsyncGenerator(c, threadManager.createThread(projectId, title));
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
const { response, statusCode } = ResponseBuilder.error('REQUEST_PARSE_ERROR', 'Failed to parse request body', 400, error instanceof Error ? error.message : String(error));
|
|
123
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
124
|
+
return c.json(response, statusCode);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
/**
|
|
128
|
+
* GET /api/threads?projectId=xxx
|
|
129
|
+
* List all threads for a specific project
|
|
130
|
+
*
|
|
131
|
+
* Query parameters:
|
|
132
|
+
* - projectId (required): The project ID to filter threads
|
|
133
|
+
*
|
|
134
|
+
* Response:
|
|
135
|
+
* [
|
|
136
|
+
* {
|
|
137
|
+
* "id": "uuid",
|
|
138
|
+
* "projectId": "uuid",
|
|
139
|
+
* "title": "Thread title",
|
|
140
|
+
* "path": "/path/to/thread",
|
|
141
|
+
* "currentBranch": "branch-name",
|
|
142
|
+
* "createdAt": "2024-01-01T00:00:00Z"
|
|
143
|
+
* }
|
|
144
|
+
* ]
|
|
145
|
+
*
|
|
146
|
+
* Requirements:
|
|
147
|
+
* - 6.3 - THE CLI SHALL expose REST API endpoints for Thread operations
|
|
148
|
+
*/
|
|
149
|
+
router.get('/', async (c) => {
|
|
150
|
+
try {
|
|
151
|
+
const projectId = c.req.query('projectId');
|
|
152
|
+
// Validate projectId is provided
|
|
153
|
+
if (!projectId) {
|
|
154
|
+
const { response, statusCode } = ResponseBuilder.error('INVALID_REQUEST', 'projectId query parameter is required', 400);
|
|
155
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
156
|
+
return c.json(response, statusCode);
|
|
157
|
+
}
|
|
158
|
+
const threads = await threadManager.listThreads(projectId);
|
|
159
|
+
return c.json(threads);
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
const { response, statusCode } = ResponseBuilder.error('LIST_THREADS_ERROR', 'Failed to list threads', 500, error instanceof Error ? error.message : String(error));
|
|
163
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
164
|
+
return c.json(response, statusCode);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
/**
|
|
168
|
+
* DELETE /api/threads/:id
|
|
169
|
+
* Delete a thread
|
|
170
|
+
*
|
|
171
|
+
* Response:
|
|
172
|
+
* {
|
|
173
|
+
* "success": true,
|
|
174
|
+
* "message": "Thread deleted successfully"
|
|
175
|
+
* }
|
|
176
|
+
*
|
|
177
|
+
* Requirements:
|
|
178
|
+
* - 6.3 - THE CLI SHALL expose REST API endpoints for Thread operations
|
|
179
|
+
* - 8.4 - WHEN deleting a Thread, THE App SHALL send a DELETE request with the Thread identifier to the CLI
|
|
180
|
+
*/
|
|
181
|
+
router.delete('/:id', async (c) => {
|
|
182
|
+
try {
|
|
183
|
+
const threadId = c.req.param('id');
|
|
184
|
+
if (!threadId) {
|
|
185
|
+
const { response, statusCode } = ResponseBuilder.error('INVALID_REQUEST', 'Thread ID is required', 400);
|
|
186
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
187
|
+
return c.json(response, statusCode);
|
|
188
|
+
}
|
|
189
|
+
await threadManager.deleteThread(threadId);
|
|
190
|
+
const { response, statusCode } = ResponseBuilder.success({ success: true, message: 'Thread deleted successfully' });
|
|
191
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
192
|
+
return c.json(response, statusCode);
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
196
|
+
// Check if it's a "not found" error
|
|
197
|
+
if (errorMessage.includes('not found')) {
|
|
198
|
+
const { response, statusCode } = ResponseBuilder.error('THREAD_NOT_FOUND', errorMessage, 404);
|
|
199
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
200
|
+
return c.json(response, statusCode);
|
|
201
|
+
}
|
|
202
|
+
const { response, statusCode } = ResponseBuilder.error('DELETE_THREAD_ERROR', 'Failed to delete thread', 500, errorMessage);
|
|
203
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
204
|
+
return c.json(response, statusCode);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
/**
|
|
208
|
+
* POST /api/threads/:id/select
|
|
209
|
+
* Select a thread as the active thread
|
|
210
|
+
*
|
|
211
|
+
* Response:
|
|
212
|
+
* {
|
|
213
|
+
* "success": true,
|
|
214
|
+
* "message": "Thread selected successfully",
|
|
215
|
+
* "threadId": "uuid"
|
|
216
|
+
* }
|
|
217
|
+
*
|
|
218
|
+
* Requirements:
|
|
219
|
+
* - 6.3 - THE CLI SHALL expose REST API endpoints for Thread operations
|
|
220
|
+
*/
|
|
221
|
+
router.post('/:id/select', async (c) => {
|
|
222
|
+
try {
|
|
223
|
+
const threadId = c.req.param('id');
|
|
224
|
+
if (!threadId) {
|
|
225
|
+
const { response, statusCode } = ResponseBuilder.error('INVALID_REQUEST', 'Thread ID is required', 400);
|
|
226
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
227
|
+
return c.json(response, statusCode);
|
|
228
|
+
}
|
|
229
|
+
// Verify thread exists
|
|
230
|
+
const thread = await threadManager.getThread(threadId);
|
|
231
|
+
if (!thread) {
|
|
232
|
+
const { response, statusCode } = ResponseBuilder.error('THREAD_NOT_FOUND', `Thread not found: ${threadId}`, 404);
|
|
233
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
234
|
+
return c.json(response, statusCode);
|
|
235
|
+
}
|
|
236
|
+
await threadManager.selectThread(threadId);
|
|
237
|
+
const { response, statusCode } = ResponseBuilder.success({ success: true, message: 'Thread selected successfully', threadId });
|
|
238
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
239
|
+
return c.json(response, statusCode);
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
const { response, statusCode } = ResponseBuilder.error('SELECT_THREAD_ERROR', 'Failed to select thread', 500, error instanceof Error ? error.message : String(error));
|
|
243
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
244
|
+
return c.json(response, statusCode);
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
/**
|
|
248
|
+
* GET /api/threads/:id/messages
|
|
249
|
+
* Get chat history for a thread
|
|
250
|
+
*/
|
|
251
|
+
router.get('/:id/messages', async (c) => {
|
|
252
|
+
try {
|
|
253
|
+
const threadId = c.req.param('id');
|
|
254
|
+
if (!threadId) {
|
|
255
|
+
const { response, statusCode } = ResponseBuilder.error('INVALID_REQUEST', 'Thread ID is required', 400);
|
|
256
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
257
|
+
return c.json(response, statusCode);
|
|
258
|
+
}
|
|
259
|
+
const thread = await threadManager.getThread(threadId);
|
|
260
|
+
if (!thread) {
|
|
261
|
+
const { response, statusCode } = ResponseBuilder.error('THREAD_NOT_FOUND', `Thread not found: ${threadId}`, 404);
|
|
262
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
263
|
+
return c.json(response, statusCode);
|
|
264
|
+
}
|
|
265
|
+
const history = await conversationManager.getConversationHistory(thread.path);
|
|
266
|
+
if (!history || history.messages.length === 0) {
|
|
267
|
+
return c.json([]);
|
|
268
|
+
}
|
|
269
|
+
const messages = history.messages.flatMap((message) => {
|
|
270
|
+
const userMessage = {
|
|
271
|
+
messageId: `user-${message.id}`,
|
|
272
|
+
sender: 'user',
|
|
273
|
+
content: message.request.message,
|
|
274
|
+
contentType: 'text',
|
|
275
|
+
timestamp: message.timestamp,
|
|
276
|
+
model: message.request.model,
|
|
277
|
+
attachments: message.request.attachments?.map((attachment) => ({
|
|
278
|
+
fileName: attachment.name,
|
|
279
|
+
content: attachment.content,
|
|
280
|
+
size: attachment.size,
|
|
281
|
+
}))
|
|
282
|
+
};
|
|
283
|
+
if (!message.response) {
|
|
284
|
+
return [userMessage];
|
|
285
|
+
}
|
|
286
|
+
const agentMessage = {
|
|
287
|
+
messageId: `agent-${message.id}`,
|
|
288
|
+
sender: 'agent',
|
|
289
|
+
content: extractAssistantContent(message.response.events, message.response.content),
|
|
290
|
+
contentType: 'markdown',
|
|
291
|
+
timestamp: message.response.completedAt,
|
|
292
|
+
model: message.request.model,
|
|
293
|
+
};
|
|
294
|
+
return [userMessage, agentMessage];
|
|
295
|
+
});
|
|
296
|
+
return c.json(messages);
|
|
297
|
+
}
|
|
298
|
+
catch (error) {
|
|
299
|
+
const { response, statusCode } = ResponseBuilder.error('GET_THREAD_MESSAGES_ERROR', 'Failed to load thread messages', 500, error instanceof Error ? error.message : String(error));
|
|
300
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
301
|
+
return c.json(response, statusCode);
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
/**
|
|
305
|
+
* GET /api/threads/:id/files
|
|
306
|
+
* List all files in a thread's directory
|
|
307
|
+
*/
|
|
308
|
+
router.get('/:id/files', async (c) => {
|
|
309
|
+
try {
|
|
310
|
+
const threadId = c.req.param('id');
|
|
311
|
+
if (!threadId) {
|
|
312
|
+
const { response, statusCode } = ResponseBuilder.error('INVALID_REQUEST', 'Thread ID is required', 400);
|
|
313
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
314
|
+
return c.json(response, statusCode);
|
|
315
|
+
}
|
|
316
|
+
const files = await threadManager.listFiles(threadId);
|
|
317
|
+
return c.json(files);
|
|
318
|
+
}
|
|
319
|
+
catch (error) {
|
|
320
|
+
const { response, statusCode } = ResponseBuilder.error('LIST_THREAD_FILES_ERROR', 'Failed to list thread files', 500, error instanceof Error ? error.message : String(error));
|
|
321
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
322
|
+
return c.json(response, statusCode);
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
/**
|
|
326
|
+
* PATCH /api/threads/:id
|
|
327
|
+
* Update thread metadata
|
|
328
|
+
*/
|
|
329
|
+
router.patch('/:id', async (c) => {
|
|
330
|
+
try {
|
|
331
|
+
const threadId = c.req.param('id');
|
|
332
|
+
const body = await c.req.json();
|
|
333
|
+
if (!threadId) {
|
|
334
|
+
const { response, statusCode } = ResponseBuilder.error('INVALID_REQUEST', 'Thread ID is required', 400);
|
|
335
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
336
|
+
return c.json(response, statusCode);
|
|
337
|
+
}
|
|
338
|
+
await threadManager.updateThread(threadId, body);
|
|
339
|
+
const { response, statusCode } = ResponseBuilder.success({ success: true, message: 'Thread updated successfully' });
|
|
340
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
341
|
+
return c.json(response, statusCode);
|
|
342
|
+
}
|
|
343
|
+
catch (error) {
|
|
344
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
345
|
+
const { response, statusCode } = ResponseBuilder.error('UPDATE_THREAD_ERROR', 'Failed to update thread', 500, errorMessage);
|
|
346
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
347
|
+
return c.json(response, statusCode);
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
return router;
|
|
351
|
+
}
|
|
352
|
+
//# sourceMappingURL=threads.js.map
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data models for the Project Threads Manager
|
|
3
|
+
*
|
|
4
|
+
* This file defines the core data structures used throughout the application
|
|
5
|
+
* for projects, threads, chat messages, and file attachments.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Available IDEs/editors for opening projects
|
|
9
|
+
*/
|
|
10
|
+
export declare const AVAILABLE_PROGRAMS: string[];
|
|
11
|
+
/**
|
|
12
|
+
* Command represents a custom shell command that can be run on a project
|
|
13
|
+
*/
|
|
14
|
+
export interface Command {
|
|
15
|
+
/** Unique identifier (UUID) */
|
|
16
|
+
id: string;
|
|
17
|
+
/** Human-readable name */
|
|
18
|
+
name: string;
|
|
19
|
+
/** Lucide icon name */
|
|
20
|
+
icon: string;
|
|
21
|
+
/** The shell command to execute */
|
|
22
|
+
commandLine: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Project represents a git repository with associated threads
|
|
26
|
+
*/
|
|
27
|
+
export interface Project {
|
|
28
|
+
/** Unique identifier (UUID) */
|
|
29
|
+
id: string;
|
|
30
|
+
/** Project name derived from git URL */
|
|
31
|
+
name: string;
|
|
32
|
+
/** Original git repository URL */
|
|
33
|
+
gitUrl: string;
|
|
34
|
+
/** Absolute path to project folder */
|
|
35
|
+
path: string;
|
|
36
|
+
/** Timestamp when project was created */
|
|
37
|
+
createdAt: Date;
|
|
38
|
+
/** Array of thread IDs belonging to this project */
|
|
39
|
+
threads: string[];
|
|
40
|
+
/** Preferred IDE to open the project in (VS Code, Cursor, Windsurf, Xcode, Android Studio) */
|
|
41
|
+
openWith?: string;
|
|
42
|
+
/** Custom commands defined for this project */
|
|
43
|
+
commands?: Command[];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Thread represents a specific clone instance of a project's git repository
|
|
47
|
+
*/
|
|
48
|
+
export interface Thread {
|
|
49
|
+
/** Unique identifier (UUID) */
|
|
50
|
+
id: string;
|
|
51
|
+
/** Parent project ID */
|
|
52
|
+
projectId: string;
|
|
53
|
+
/** User-defined or auto-generated title */
|
|
54
|
+
title: string;
|
|
55
|
+
/** Absolute path to thread folder */
|
|
56
|
+
path: string;
|
|
57
|
+
/** Current git branch for this thread */
|
|
58
|
+
currentBranch: string;
|
|
59
|
+
/** Timestamp when thread was created */
|
|
60
|
+
createdAt: Date;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* ChatMessage represents a message in the agent conversation
|
|
64
|
+
* (Frontend only - not persisted by CLI)
|
|
65
|
+
*/
|
|
66
|
+
export interface ChatMessage {
|
|
67
|
+
/** Unique identifier (UUID) */
|
|
68
|
+
id: string;
|
|
69
|
+
/** Associated thread ID */
|
|
70
|
+
threadId: string;
|
|
71
|
+
/** Message sender role */
|
|
72
|
+
role: 'user' | 'agent';
|
|
73
|
+
/** Message content */
|
|
74
|
+
content: string;
|
|
75
|
+
/** Timestamp when message was sent */
|
|
76
|
+
timestamp: Date;
|
|
77
|
+
/** Optional file attachments */
|
|
78
|
+
attachments?: FileAttachment[];
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* FileAttachment represents a file attached to a chat message
|
|
82
|
+
*/
|
|
83
|
+
export interface FileAttachment {
|
|
84
|
+
/** File name */
|
|
85
|
+
name: string;
|
|
86
|
+
/** File path relative to thread */
|
|
87
|
+
path: string;
|
|
88
|
+
/** File size in bytes */
|
|
89
|
+
size: number;
|
|
90
|
+
/** MIME type of the file */
|
|
91
|
+
mimeType: string;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Event types for streaming operations
|
|
95
|
+
*/
|
|
96
|
+
/**
|
|
97
|
+
* ProjectEvent is yielded during project creation operations
|
|
98
|
+
*/
|
|
99
|
+
export interface ProjectEvent {
|
|
100
|
+
/** Event type */
|
|
101
|
+
type: 'progress' | 'complete' | 'error';
|
|
102
|
+
/** Progress or error message */
|
|
103
|
+
message?: string;
|
|
104
|
+
/** Error details (only present on 'error' type) */
|
|
105
|
+
error?: unknown;
|
|
106
|
+
/** Completed project data (only present on 'complete' type) */
|
|
107
|
+
project?: Project;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* ThreadEvent is yielded during thread creation operations
|
|
111
|
+
*/
|
|
112
|
+
export interface ThreadEvent {
|
|
113
|
+
/** Event type */
|
|
114
|
+
type: 'progress' | 'complete' | 'error';
|
|
115
|
+
/** Progress or error message */
|
|
116
|
+
message?: string;
|
|
117
|
+
/** Error details (only present on 'error' type) */
|
|
118
|
+
error?: unknown;
|
|
119
|
+
/** Completed thread data (only present on 'complete' type) */
|
|
120
|
+
thread?: Thread;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* GitEvent is yielded during git clone operations
|
|
124
|
+
*/
|
|
125
|
+
export interface GitEvent {
|
|
126
|
+
/** Event type */
|
|
127
|
+
type: 'stdout' | 'stderr' | 'complete' | 'error';
|
|
128
|
+
/** Output data from git command */
|
|
129
|
+
data?: string;
|
|
130
|
+
/** Error message (only present on 'error' type) */
|
|
131
|
+
message?: string;
|
|
132
|
+
/** Error details (only present on 'error' type) */
|
|
133
|
+
error?: unknown;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* NeovateEvent is yielded during neovate SDK prompt execution
|
|
137
|
+
*/
|
|
138
|
+
export interface NeovateEvent {
|
|
139
|
+
/** Event type */
|
|
140
|
+
type: 'message' | 'result' | 'error';
|
|
141
|
+
/** Role of the message sender (for 'message' type) */
|
|
142
|
+
role?: string;
|
|
143
|
+
/** Message or result content */
|
|
144
|
+
content?: string;
|
|
145
|
+
/** Error details (only present on 'error' type) */
|
|
146
|
+
error?: {
|
|
147
|
+
/** Machine-readable error code */
|
|
148
|
+
code?: string;
|
|
149
|
+
/** Human-readable error message */
|
|
150
|
+
message: string;
|
|
151
|
+
/** Additional error context */
|
|
152
|
+
details?: unknown;
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* ExecutionContext provides context for neovate SDK prompt execution
|
|
157
|
+
*/
|
|
158
|
+
export interface ExecutionContext {
|
|
159
|
+
/** Thread ID for context */
|
|
160
|
+
threadId: string;
|
|
161
|
+
/** Absolute path to thread directory */
|
|
162
|
+
threadPath: string;
|
|
163
|
+
/** Optional model selection */
|
|
164
|
+
model?: string;
|
|
165
|
+
/** Optional provider selection (e.g., 'openrouter', 'anthropic') */
|
|
166
|
+
provider?: string;
|
|
167
|
+
/** Optional file attachments */
|
|
168
|
+
attachments?: FileData[];
|
|
169
|
+
/** Optional plan mode flag */
|
|
170
|
+
planMode?: boolean;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* FileData represents file data for transmission over API
|
|
174
|
+
*/
|
|
175
|
+
export interface FileData {
|
|
176
|
+
/** File name */
|
|
177
|
+
name: string;
|
|
178
|
+
/** File content (base64 encoded for binary files) */
|
|
179
|
+
content: string;
|
|
180
|
+
/** File size in bytes */
|
|
181
|
+
size: number;
|
|
182
|
+
/** MIME type */
|
|
183
|
+
mimeType: string;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* API Request/Response types
|
|
187
|
+
*/
|
|
188
|
+
/**
|
|
189
|
+
* ChatRequest is sent from frontend to CLI for chat messages
|
|
190
|
+
*/
|
|
191
|
+
export interface ChatRequest {
|
|
192
|
+
/** Thread ID for context */
|
|
193
|
+
threadId: string;
|
|
194
|
+
/** User message content */
|
|
195
|
+
content: string;
|
|
196
|
+
/** Selected model */
|
|
197
|
+
model: string;
|
|
198
|
+
/** Selected provider (lowercased name like 'openrouter', 'anthropic', etc.) */
|
|
199
|
+
provider?: string;
|
|
200
|
+
/** Optional file attachments */
|
|
201
|
+
attachments?: FileData[];
|
|
202
|
+
/** Optional plan mode flag */
|
|
203
|
+
planMode?: boolean;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* ChatResponse is streamed from CLI to frontend during chat
|
|
207
|
+
*/
|
|
208
|
+
export interface ChatResponse {
|
|
209
|
+
/** Message ID */
|
|
210
|
+
messageId: string;
|
|
211
|
+
/** Response content (may be partial during streaming) */
|
|
212
|
+
content: string;
|
|
213
|
+
/** Whether the response is complete */
|
|
214
|
+
isComplete: boolean;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* StreamEvent wraps streaming responses with type information
|
|
218
|
+
*/
|
|
219
|
+
export interface StreamEvent<T> {
|
|
220
|
+
/** Event type */
|
|
221
|
+
type: 'progress' | 'complete' | 'error';
|
|
222
|
+
/** Event data (type depends on T) */
|
|
223
|
+
data?: T;
|
|
224
|
+
/** Progress or error message */
|
|
225
|
+
message?: string;
|
|
226
|
+
/** Progress percentage (0-100) */
|
|
227
|
+
progress?: number;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* ErrorResponse is returned for failed API operations
|
|
231
|
+
*/
|
|
232
|
+
export interface ErrorResponse {
|
|
233
|
+
error: {
|
|
234
|
+
/** Machine-readable error code */
|
|
235
|
+
code: string;
|
|
236
|
+
/** Human-readable error message */
|
|
237
|
+
message: string;
|
|
238
|
+
/** Additional error context */
|
|
239
|
+
details?: unknown;
|
|
240
|
+
/** ISO 8601 timestamp */
|
|
241
|
+
timestamp: string;
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Conversation history types
|
|
246
|
+
*/
|
|
247
|
+
/**
|
|
248
|
+
* ConversationMessage represents a single request/response exchange
|
|
249
|
+
*/
|
|
250
|
+
export interface ConversationMessage {
|
|
251
|
+
/** Unique message identifier */
|
|
252
|
+
id: string;
|
|
253
|
+
/** Timestamp of the request */
|
|
254
|
+
timestamp: string;
|
|
255
|
+
/** User's request */
|
|
256
|
+
request: {
|
|
257
|
+
/** User message content */
|
|
258
|
+
message: string;
|
|
259
|
+
/** Model used for the request */
|
|
260
|
+
model: string;
|
|
261
|
+
/** Optional file attachments */
|
|
262
|
+
attachments?: FileData[];
|
|
263
|
+
/** Optional plan mode flag */
|
|
264
|
+
planMode?: boolean;
|
|
265
|
+
};
|
|
266
|
+
/** Agent's response (null if incomplete) */
|
|
267
|
+
response: {
|
|
268
|
+
/** Complete response content */
|
|
269
|
+
content: string;
|
|
270
|
+
/** All events captured during execution */
|
|
271
|
+
events: NeovateEvent[];
|
|
272
|
+
/** Response completion timestamp */
|
|
273
|
+
completedAt: string;
|
|
274
|
+
} | null;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* ConversationHistory stores all conversation messages for a thread
|
|
278
|
+
*/
|
|
279
|
+
export interface ConversationHistory {
|
|
280
|
+
/** Thread ID */
|
|
281
|
+
threadId: string;
|
|
282
|
+
/** Array of conversation messages */
|
|
283
|
+
messages: ConversationMessage[];
|
|
284
|
+
/** Last updated timestamp */
|
|
285
|
+
lastUpdated: string;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Model represents an AI model available from a provider
|
|
289
|
+
*/
|
|
290
|
+
export interface Model {
|
|
291
|
+
/** Unique model identifier (e.g., "gpt-4", "openrouter/gpt-4") */
|
|
292
|
+
id: string;
|
|
293
|
+
/** Human-readable model name */
|
|
294
|
+
name: string;
|
|
295
|
+
/** Detailed description of the model */
|
|
296
|
+
description: string;
|
|
297
|
+
/** Provider that offers this model */
|
|
298
|
+
provider: string;
|
|
299
|
+
/**
|
|
300
|
+
* Pricing information for the model (optional).
|
|
301
|
+
* If omitted, pricing is unknown for this provider/model.
|
|
302
|
+
*/
|
|
303
|
+
pricing?: {
|
|
304
|
+
/** Cost per token in USD */
|
|
305
|
+
prompt: number;
|
|
306
|
+
/** Cost per token in USD */
|
|
307
|
+
completion: number;
|
|
308
|
+
};
|
|
309
|
+
/** Supported parameters for this model (optional) */
|
|
310
|
+
supportedParameters?: {
|
|
311
|
+
/** Whether this model supports function/tool calling */
|
|
312
|
+
tools?: boolean;
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
//# sourceMappingURL=models.d.ts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data models for the Project Threads Manager
|
|
3
|
+
*
|
|
4
|
+
* This file defines the core data structures used throughout the application
|
|
5
|
+
* for projects, threads, chat messages, and file attachments.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Available IDEs/editors for opening projects
|
|
9
|
+
*/
|
|
10
|
+
export const AVAILABLE_PROGRAMS = ['VS Code', 'Cursor', 'Windsurf', 'Xcode', 'Android Studio', 'Kiro'];
|
|
11
|
+
//# sourceMappingURL=models.js.map
|