@kafca/agentdock 0.1.46 → 0.1.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +6 -0
  2. package/dist/renderer/assets/{Badge-BIesaDYG.js → Badge-BYREM3t3.js} +1 -1
  3. package/dist/renderer/assets/{Chat-CpataoDh.js → Chat-Dtb5X7sB.js} +1 -1
  4. package/dist/renderer/assets/{ChatMarkdown-_Mdmltgv.js → ChatMarkdown-_slLloCh.js} +1 -1
  5. package/dist/renderer/assets/{Config-Pe5-zKZk.js → Config-pHichs7H.js} +1 -1
  6. package/dist/renderer/assets/{CronList-BnaOSPT8.js → CronList-CJQMeCjz.js} +1 -1
  7. package/dist/renderer/assets/{Dashboard-D05l8GKE.js → Dashboard-CY77Imde.js} +2 -2
  8. package/dist/renderer/assets/{EmptyState-BDBavDll.js → EmptyState-DCqmct2y.js} +1 -1
  9. package/dist/renderer/assets/{KnowledgeDetail-DvR798l_.js → KnowledgeDetail-DFrxa-pv.js} +1 -1
  10. package/dist/renderer/assets/{KnowledgeHome-r2ZuvU8-.js → KnowledgeHome-DL-vAfrH.js} +1 -1
  11. package/dist/renderer/assets/{Logs-BaljtmAV.js → Logs-jw9Uv590.js} +1 -1
  12. package/dist/renderer/assets/{Modal-CXhD_OWR.js → Modal-ChSm7tk6.js} +1 -1
  13. package/dist/renderer/assets/{MonitorList-D7tyipbx.js → MonitorList-DrEwyS71.js} +1 -1
  14. package/dist/renderer/assets/{Page-KUuFYgjg.js → Page-DhD-zgMU.js} +1 -1
  15. package/dist/renderer/assets/ProjectDetail-BVSjpfpL.js +11 -0
  16. package/dist/renderer/assets/{ProjectList-CbdHd5Ev.js → ProjectList-CHGbTJjk.js} +1 -1
  17. package/dist/renderer/assets/{Select-Djc8BXMC.js → Select-rHs9CSam.js} +1 -1
  18. package/dist/renderer/assets/{SessionChat-DOy0iKQR.js → SessionChat-CDc2FNMA.js} +1 -1
  19. package/dist/renderer/assets/{SessionList-DHgl1s1D.js → SessionList-Mr1uAnRW.js} +1 -1
  20. package/dist/renderer/assets/ThreadChat-Detu5UfN.js +23 -0
  21. package/dist/renderer/assets/{Workspace-DTocqY-F.js → Workspace-MRRg8ndJ.js} +1 -1
  22. package/dist/renderer/assets/{arrow-left-CXyya3hP.js → arrow-left-BU2NB2LA.js} +1 -1
  23. package/dist/renderer/assets/{book-open-9n4Kxc7E.js → book-open-D-TGvWwa.js} +1 -1
  24. package/dist/renderer/assets/{bot-C9HBk5XH.js → bot-IYNh8tH5.js} +1 -1
  25. package/dist/renderer/assets/{check-CutSg9a5.js → check-CCZ0JO_S.js} +1 -1
  26. package/dist/renderer/assets/{chevron-down-DrydR4i3.js → chevron-down-00bRZi15.js} +1 -1
  27. package/dist/renderer/assets/{circle-BEjtahw6.js → circle-CpQ8x4k_.js} +1 -1
  28. package/dist/renderer/assets/{desktop-DATgk_ka.js → desktop-D0k2aGSj.js} +1 -1
  29. package/dist/renderer/assets/{funnel-BhqZpS4f.js → funnel-UbcrCBCp.js} +1 -1
  30. package/dist/renderer/assets/{heart-Dp8Vs-7_.js → heart-DvLsV7tL.js} +1 -1
  31. package/dist/renderer/assets/{index-BKpv0P0s.js → index-B2L4G-b_.js} +1 -1
  32. package/dist/renderer/assets/{index-cI3pWBnN.js → index-dy-g2Bh4.js} +2 -2
  33. package/dist/renderer/assets/{pencil-ywcJzeOo.js → pencil-qBYOgW5M.js} +1 -1
  34. package/dist/renderer/assets/{play-AtN492Cx.js → play-Dn6b4AGj.js} +1 -1
  35. package/dist/renderer/assets/{plus-BEj14BBV.js → plus-CaApUwEm.js} +1 -1
  36. package/dist/renderer/assets/projects-BwF5RlNV.js +1 -0
  37. package/dist/renderer/assets/{save-D6ov4CNv.js → save-CN6yvTfD.js} +1 -1
  38. package/dist/renderer/assets/{search-C3bvw7AR.js → search-Dn2MxYiO.js} +1 -1
  39. package/dist/renderer/assets/{server-C1Uyedyx.js → server-DpcVLHeh.js} +1 -1
  40. package/dist/renderer/assets/{session-utils-DpRwJ6Nh.js → session-utils-DnD2Q_y1.js} +1 -1
  41. package/dist/renderer/assets/{sessions-BWmAppwK.js → sessions-eK5D4FLt.js} +1 -1
  42. package/dist/renderer/assets/{shield-check-CVx1mEs2.js → shield-check-DSSJkacw.js} +1 -1
  43. package/dist/renderer/assets/{trash-2-o-heXOgs.js → trash-2-DmYJlEvO.js} +1 -1
  44. package/dist/renderer/assets/{user-dPOzUqIc.js → user-Cvf_uiyQ.js} +1 -1
  45. package/dist/renderer/assets/{wifi-off-Fz5c3_x_.js → wifi-off-CCTloj-V.js} +1 -1
  46. package/dist/renderer/index.html +1 -1
  47. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-session-coordinator.js +2 -1
  48. package/dist-electron/services/local-ai-core/src/agents/pi/launch.js +1 -1
  49. package/dist-electron/services/local-ai-core/src/agents/shared/launch-utils.js +22 -0
  50. package/dist-electron/services/local-ai-core/src/channel/lark/local-core-lark-gateway.js +132 -348
  51. package/dist-electron/services/local-ai-core/src/channel/shared/base-channel-gateway.js +303 -0
  52. package/dist-electron/services/local-ai-core/src/channel/weixin/local-core-weixin-gateway.js +131 -382
  53. package/dist-electron/services/local-ai-core/src/runtime/agent-runtime-detector.js +2 -1
  54. package/dist-electron/services/local-ai-core/src/runtime/channel-service.js +150 -0
  55. package/dist-electron/services/local-ai-core/src/runtime/env-utils.js +12 -0
  56. package/dist-electron/services/local-ai-core/src/runtime/external-service.js +234 -0
  57. package/dist-electron/services/local-ai-core/src/runtime/handlers/automation-handler.js +34 -0
  58. package/dist-electron/services/local-ai-core/src/runtime/handlers/capabilities-handler.js +12 -0
  59. package/dist-electron/services/local-ai-core/src/runtime/handlers/channel-handler.js +65 -0
  60. package/dist-electron/services/local-ai-core/src/runtime/handlers/external-handler.js +17 -0
  61. package/dist-electron/services/local-ai-core/src/runtime/handlers/knowledge-handler.js +65 -0
  62. package/dist-electron/services/local-ai-core/src/runtime/handlers/openai-handler.js +496 -0
  63. package/dist-electron/services/local-ai-core/src/runtime/handlers/provider-handler.js +24 -0
  64. package/dist-electron/services/local-ai-core/src/runtime/handlers/runtime-handler.js +68 -0
  65. package/dist-electron/services/local-ai-core/src/runtime/handlers/runtimes-handler.js +37 -0
  66. package/dist-electron/services/local-ai-core/src/runtime/handlers/scheduler-handler.js +34 -0
  67. package/dist-electron/services/local-ai-core/src/runtime/handlers/security-handler.js +49 -0
  68. package/dist-electron/services/local-ai-core/src/runtime/handlers/task-handler.js +27 -0
  69. package/dist-electron/services/local-ai-core/src/runtime/handlers/thread-handler.js +42 -0
  70. package/dist-electron/services/local-ai-core/src/runtime/handlers/workspace-handler.js +29 -0
  71. package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +40 -592
  72. package/dist-electron/services/local-ai-core/src/runtime/server-helpers.js +96 -0
  73. package/dist-electron/services/local-ai-core/src/runtime/server.js +107 -1084
  74. package/dist-electron/services/local-ai-core/src/runtime/standalone.js +13 -1
  75. package/dist-electron/src/pages/Projects/project-provider-config.js +75 -0
  76. package/dist-electron/tests/contracts/architecture-docs.test.js +1 -0
  77. package/dist-electron/tests/electron/agent-runtime-detector.test.js +18 -0
  78. package/dist-electron/tests/electron/knowledge-skill-script.test.js +15 -5
  79. package/dist-electron/tests/electron/lac-cli.test.js +591 -688
  80. package/dist-electron/tests/electron/plugin-kernel.test.js +47 -31
  81. package/dist-electron/tests/electron/project-provider-config.test.js +71 -0
  82. package/dist-electron/tests/electron/workspace-task-store.test.js +19 -9
  83. package/dist-electron/tests/integration/local-core-cors.test.js +22 -0
  84. package/package.json +1 -1
  85. package/dist/renderer/assets/ProjectDetail-Ddq94-yu.js +0 -11
  86. package/dist/renderer/assets/ThreadChat-I7dYibDc.js +0 -23
  87. package/dist/renderer/assets/projects-Clar8lgb.js +0 -1
@@ -3,456 +3,21 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.LocalAiCoreServer = void 0;
4
4
  const node_http_1 = require("node:http");
5
5
  const server_routes_js_1 = require("./server-routes.js");
6
- const local_core_errors_js_1 = require("../kernel/local-core-errors.js");
7
- class OpenAiChatCompletionStreamAdapter {
8
- options;
9
- created = Math.floor(Date.now() / 1000);
10
- completionId;
11
- assistantPreviewContentByHandle = new Map();
12
- thoughtContentByHandle = new Map();
13
- emittedMessageIds = new Set();
14
- closed = false;
15
- roleSent = false;
16
- constructor(options) {
17
- this.options = options;
18
- this.completionId = `chatcmpl_${sanitizeOpenAiId(options.runId)}`;
19
- }
20
- start() {
21
- this.options.response.writeHead(200, {
22
- 'Content-Type': 'text/event-stream',
23
- 'Cache-Control': 'no-cache, no-transform',
24
- Connection: 'keep-alive',
25
- });
26
- this.sendChunk({ role: 'assistant' }, {
27
- event: 'run_started',
28
- kind: 'assistant',
29
- run_id: this.options.runId,
30
- });
31
- this.roleSent = true;
32
- }
33
- handleBridgeEvent(event) {
34
- if (this.closed || String(event.replyCtx || '') !== this.options.runId) {
35
- return;
36
- }
37
- if (!this.roleSent) {
38
- this.start();
39
- }
40
- if (event.type === 'typing_stop') {
41
- this.finish('stop', { event: 'run_finished', kind: 'status' });
42
- return;
43
- }
44
- if ((event.type === 'status' || event.type === 'card') && event.error) {
45
- this.error(event.error, 'runtime_error');
46
- return;
47
- }
48
- if (event.type === 'buttons' && event.bridgeKind === 'permission') {
49
- this.error('Unexpected permission request for yolo OpenAI chat run.', 'unexpected_permission_request');
50
- return;
51
- }
52
- if (event.type === 'preview_start' || event.type === 'update_message') {
53
- this.handlePreviewEvent(event);
54
- return;
55
- }
56
- if (event.type === 'reply') {
57
- this.handleReplyEvent(event);
58
- return;
59
- }
60
- if (event.type === 'status' || event.type === 'card') {
61
- this.sendChunk({}, {
62
- event: event.type,
63
- kind: event.bridgeKind || 'status',
64
- run_id: this.options.runId,
65
- content: event.content,
66
- ok: event.ok,
67
- card: event.card,
68
- });
69
- }
70
- }
71
- replayMessage(input) {
72
- if (this.closed || this.emittedMessageIds.has(input.id)) {
73
- return;
74
- }
75
- this.emittedMessageIds.add(input.id);
76
- const event = {
77
- type: 'reply',
78
- replyCtx: this.options.runId,
79
- messageId: input.id,
80
- content: input.content,
81
- bridgeKind: input.bridgeKind,
82
- bridgeStatus: input.bridgeStatus,
83
- toolCall: input.toolCall,
84
- };
85
- this.handleReplyEvent(event);
86
- }
87
- finish(finishReason = 'stop', agentdock = {}) {
88
- if (this.closed) {
89
- return;
90
- }
91
- const chunk = this.baseChunk({}, {
92
- run_id: this.options.runId,
93
- ...agentdock,
94
- }, finishReason);
95
- this.options.response.write(createOpenAiSseData(chunk));
96
- this.options.response.write(createOpenAiDone());
97
- this.closed = true;
98
- this.options.onClose();
99
- this.options.response.end();
100
- }
101
- error(message, code = 'runtime_error') {
102
- if (this.closed) {
103
- return;
104
- }
105
- const chunk = this.baseChunk({}, {
106
- run_id: this.options.runId,
107
- event: 'error',
108
- kind: 'status',
109
- });
110
- chunk.error = {
111
- message,
112
- type: code,
113
- code,
114
- };
115
- this.options.response.write(createOpenAiSseData(chunk));
116
- this.options.response.write(createOpenAiDone());
117
- this.closed = true;
118
- this.options.onClose();
119
- this.options.response.end();
120
- }
121
- handlePreviewEvent(event) {
122
- const content = String(event.content || '');
123
- const handle = String(event.previewHandle || event.messageId || event.replyCtx || '');
124
- if (!content || !handle) {
125
- return;
126
- }
127
- if (event.bridgeKind === 'thought') {
128
- const prior = this.thoughtContentByHandle.get(handle) || '';
129
- const delta = diffAccumulatedText(prior, content);
130
- this.thoughtContentByHandle.set(handle, content);
131
- if (!delta) {
132
- return;
133
- }
134
- this.sendProgressChunk(this.progressText('thinking', delta), {
135
- event: 'thought_delta',
136
- kind: 'thought',
137
- run_id: this.options.runId,
138
- thought: {
139
- content,
140
- delta,
141
- preview_handle: handle,
142
- },
143
- });
144
- return;
145
- }
146
- if (event.bridgeKind && event.bridgeKind !== 'assistant') {
147
- return;
148
- }
149
- const prior = this.assistantPreviewContentByHandle.get(handle) || '';
150
- const delta = diffAccumulatedText(prior, content);
151
- this.assistantPreviewContentByHandle.set(handle, content);
152
- if (!delta) {
153
- return;
154
- }
155
- this.sendChunk({ content: delta }, {
156
- event: 'assistant_delta',
157
- kind: 'assistant',
158
- run_id: this.options.runId,
159
- preview_handle: handle,
160
- });
161
- }
162
- handleReplyEvent(event) {
163
- const content = String(event.content || '');
164
- if (event.messageId) {
165
- if (this.emittedMessageIds.has(event.messageId)) {
166
- return;
167
- }
168
- this.emittedMessageIds.add(event.messageId);
169
- }
170
- if (event.bridgeKind === 'tool') {
171
- this.sendProgressChunk(this.formatToolProgressContent(event), {
172
- event: 'tool_update',
173
- kind: 'tool',
174
- run_id: this.options.runId,
175
- message_id: event.messageId,
176
- tool: event.toolCall || {
177
- output: content,
178
- },
179
- });
180
- return;
181
- }
182
- if (event.bridgeKind === 'plan') {
183
- this.sendProgressChunk(this.progressText('plan', content), {
184
- event: 'plan_update',
185
- kind: 'plan',
186
- run_id: this.options.runId,
187
- message_id: event.messageId,
188
- plan: {
189
- content,
190
- },
191
- });
192
- return;
193
- }
194
- if (event.bridgeKind === 'thought') {
195
- this.sendProgressChunk(this.progressText('thinking', content), {
196
- event: 'thought_message',
197
- kind: 'thought',
198
- run_id: this.options.runId,
199
- message_id: event.messageId,
200
- thought: {
201
- content,
202
- delta: content,
203
- },
204
- });
205
- return;
206
- }
207
- if (event.bridgeKind === 'status' || event.bridgeKind === 'permission') {
208
- this.sendProgressChunk(this.progressText(event.bridgeKind, content), {
209
- event: event.bridgeKind === 'permission' ? 'permission_required' : 'status',
210
- kind: event.bridgeKind,
211
- run_id: this.options.runId,
212
- message_id: event.messageId,
213
- content,
214
- bridge_status: event.bridgeStatus,
215
- });
216
- return;
217
- }
218
- if (content) {
219
- this.sendChunk({ content }, {
220
- event: 'assistant_message',
221
- kind: 'assistant',
222
- run_id: this.options.runId,
223
- message_id: event.messageId,
224
- });
225
- }
226
- }
227
- sendProgressChunk(content, agentdock) {
228
- this.sendChunk(this.options.progressMode === 'content' && content ? { content } : {}, agentdock);
229
- }
230
- sendChunk(delta, agentdock, finishReason = null) {
231
- if (this.closed) {
232
- return;
233
- }
234
- this.options.response.write(createOpenAiSseData(this.baseChunk(delta, agentdock, finishReason)));
235
- }
236
- baseChunk(delta, agentdock, finishReason = null) {
237
- return {
238
- id: this.completionId,
239
- object: 'chat.completion.chunk',
240
- created: this.created,
241
- model: this.options.model,
242
- choices: [
243
- {
244
- index: 0,
245
- delta,
246
- finish_reason: finishReason,
247
- },
248
- ],
249
- agentdock,
250
- };
251
- }
252
- progressText(kind, content) {
253
- return content ? `\n\n[${kind}]\n${content}` : '';
254
- }
255
- formatToolProgressContent(event) {
256
- const tool = event.toolCall;
257
- const name = tool?.name || tool?.label || 'tool';
258
- const status = tool?.status || '';
259
- const output = tool?.output || tool?.detail || event.content || '';
260
- return output ? `\n\n[tool:${name}${status ? ` ${status}` : ''}]\n${output}` : '';
261
- }
262
- }
263
- function json(res, statusCode, data, ok = true, error) {
264
- res.statusCode = statusCode;
265
- res.setHeader('Content-Type', 'application/json; charset=utf-8');
266
- res.end(JSON.stringify(ok ? { ok: true, data } : { ok: false, error }));
267
- }
268
- function rawJson(res, statusCode, data) {
269
- res.statusCode = statusCode;
270
- res.setHeader('Content-Type', 'application/json; charset=utf-8');
271
- res.end(JSON.stringify(data));
272
- }
273
- function openAiJsonError(res, statusCode, message, code = 'invalid_request_error') {
274
- rawJson(res, statusCode, {
275
- error: {
276
- message,
277
- type: code,
278
- code,
279
- },
280
- });
281
- }
282
- function jsonError(res, statusCode, error) {
283
- const info = (0, local_core_errors_js_1.toLocalCoreErrorInfo)(error);
284
- res.statusCode = statusCode;
285
- res.setHeader('Content-Type', 'application/json; charset=utf-8');
286
- res.end(JSON.stringify((0, local_core_errors_js_1.errorInfoToHttpBody)(info)));
287
- }
288
- async function readJsonBody(req) {
289
- const body = await readRawBody(req);
290
- if (!body.length) {
291
- return {};
292
- }
293
- return JSON.parse(Buffer.from(body).toString('utf8'));
294
- }
295
- async function readRawBody(req) {
296
- const chunks = [];
297
- for await (const chunk of req) {
298
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
299
- }
300
- return Buffer.concat(chunks);
301
- }
302
- function setCorsHeaders(req, res) {
303
- const origin = String(req.headers.origin || '');
304
- if (origin === 'null' || origin.startsWith('http://127.0.0.1:') || origin.startsWith('http://localhost:')) {
305
- res.setHeader('Access-Control-Allow-Origin', origin);
306
- res.setHeader('Vary', 'Origin');
307
- }
308
- res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PATCH,DELETE,OPTIONS');
309
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
310
- }
311
- function createSseEvent(name, payload) {
312
- return `event: ${name}\ndata: ${JSON.stringify(payload)}\n\n`;
313
- }
314
- function createOpenAiSseData(payload) {
315
- return `data: ${JSON.stringify(payload)}\n\n`;
316
- }
317
- function createOpenAiDone() {
318
- return 'data: [DONE]\n\n';
319
- }
320
- function sanitizeOpenAiId(value) {
321
- return String(value || '')
322
- .replace(/[^A-Za-z0-9_-]/g, '_')
323
- .slice(0, 120) || 'run';
324
- }
325
- function diffAccumulatedText(previous, next) {
326
- if (!next) {
327
- return '';
328
- }
329
- if (!previous) {
330
- return next;
331
- }
332
- if (next.startsWith(previous)) {
333
- return next.slice(previous.length);
334
- }
335
- if (previous === next) {
336
- return '';
337
- }
338
- return next;
339
- }
340
- function isTerminalAgentTaskStatus(status) {
341
- return status === 'completed' || status === 'failed' || status === 'cancelled';
342
- }
343
- function extractMetadataValue(metadata, key) {
344
- const value = metadata[key];
345
- return String(value === undefined || value === null ? '' : value).trim();
346
- }
347
- function parseOpenAiChatCompletionRequest(input) {
348
- const metadata = input.metadata && typeof input.metadata === 'object' && !Array.isArray(input.metadata)
349
- ? input.metadata
350
- : {};
351
- const userId = extractMetadataValue(metadata, 'user_id') || String(input.user || '').trim();
352
- const projectId = extractMetadataValue(metadata, 'project_id');
353
- if (!userId) {
354
- throw new Error('metadata.user_id is required.');
355
- }
356
- if (!projectId) {
357
- throw new Error('metadata.project_id is required.');
358
- }
359
- const unsupported = [
360
- input.tools !== undefined ? 'tools' : '',
361
- input.tool_choice !== undefined ? 'tool_choice' : '',
362
- input.response_format !== undefined ? 'response_format' : '',
363
- input.audio !== undefined ? 'audio' : '',
364
- input.logprobs !== undefined ? 'logprobs' : '',
365
- ].filter(Boolean);
366
- if (unsupported.length > 0) {
367
- throw new Error(`Unsupported OpenAI chat field(s): ${unsupported.join(', ')}.`);
368
- }
369
- if (input.n !== undefined && Number(input.n) !== 1) {
370
- throw new Error('Only n=1 is supported.');
371
- }
372
- const messages = Array.isArray(input.messages) ? input.messages : [];
373
- if (messages.length === 0) {
374
- throw new Error('messages must contain at least one text message.');
375
- }
376
- const prompt = messages
377
- .map((message) => formatOpenAiMessage(message))
378
- .filter(Boolean)
379
- .join('\n\n');
380
- if (!prompt.trim()) {
381
- throw new Error('messages must contain text content.');
382
- }
383
- const progressMode = extractMetadataValue(metadata, 'agentdock_progress_mode') === 'content'
384
- ? 'content'
385
- : 'extension';
386
- const model = String(input.model || extractMetadataValue(metadata, 'model') || '').trim();
387
- const externalRun = {
388
- user_id: userId,
389
- external_project_id: projectId,
390
- external_thread_id: extractMetadataValue(metadata, 'thread_id') || undefined,
391
- display_name: extractMetadataValue(metadata, 'display_name') || undefined,
392
- agent_type: extractMetadataValue(metadata, 'agent_type') || 'pi',
393
- provider_id: extractMetadataValue(metadata, 'provider_id') || undefined,
394
- model: model || undefined,
395
- title: extractMetadataValue(metadata, 'title') || undefined,
396
- metadata: {
397
- ...metadata,
398
- openai_compatible: true,
399
- },
400
- prompt,
401
- permission_mode: 'bypassPermissions',
402
- runtime_env: {
403
- AGENTDOCK_OPENAI_COMPAT: '1',
404
- },
405
- };
406
- return {
407
- externalRun,
408
- model: model || externalRun.agent_type || 'agentdock',
409
- stream: Boolean(input.stream),
410
- progressMode,
411
- };
412
- }
413
- function formatOpenAiMessage(message) {
414
- const role = String(message?.role || 'user').trim() || 'user';
415
- const content = extractOpenAiMessageText(message?.content);
416
- if (!content.trim()) {
417
- return '';
418
- }
419
- if (role === 'user') {
420
- return content;
421
- }
422
- return `[${role}]\n${content}`;
423
- }
424
- function extractOpenAiMessageText(content) {
425
- if (typeof content === 'string') {
426
- return content;
427
- }
428
- if (content === null || content === undefined) {
429
- return '';
430
- }
431
- if (!Array.isArray(content)) {
432
- throw new Error('Only text message content is supported.');
433
- }
434
- return content.map((part) => {
435
- const type = String(part?.type || 'text');
436
- if (type !== 'text') {
437
- throw new Error(`Only text message parts are supported; received ${type}.`);
438
- }
439
- return String(part?.text || '');
440
- }).join('');
441
- }
442
- function collectAssistantMessagesForRun(snapshot) {
443
- const thread = snapshot.thread;
444
- const startedAt = Date.parse(snapshot.task?.startedAt || snapshot.task?.createdAt || '');
445
- if (!thread || !Number.isFinite(startedAt)) {
446
- return [];
447
- }
448
- return (thread.messages || []).filter((message) => {
449
- if (message.role !== 'assistant') {
450
- return false;
451
- }
452
- const messageAt = Date.parse(message.timestamp || '');
453
- return Number.isFinite(messageAt) && messageAt >= startedAt;
454
- });
455
- }
6
+ const server_helpers_js_1 = require("./server-helpers.js");
7
+ const runtime_handler_js_1 = require("./handlers/runtime-handler.js");
8
+ const runtimes_handler_js_1 = require("./handlers/runtimes-handler.js");
9
+ const thread_handler_js_1 = require("./handlers/thread-handler.js");
10
+ const workspace_handler_js_1 = require("./handlers/workspace-handler.js");
11
+ const security_handler_js_1 = require("./handlers/security-handler.js");
12
+ const task_handler_js_1 = require("./handlers/task-handler.js");
13
+ const scheduler_handler_js_1 = require("./handlers/scheduler-handler.js");
14
+ const automation_handler_js_1 = require("./handlers/automation-handler.js");
15
+ const knowledge_handler_js_1 = require("./handlers/knowledge-handler.js");
16
+ const capabilities_handler_js_1 = require("./handlers/capabilities-handler.js");
17
+ const provider_handler_js_1 = require("./handlers/provider-handler.js");
18
+ const channel_handler_js_1 = require("./handlers/channel-handler.js");
19
+ const external_handler_js_1 = require("./handlers/external-handler.js");
20
+ const openai_handler_js_1 = require("./handlers/openai-handler.js");
456
21
  class LocalAiCoreServer {
457
22
  bindings;
458
23
  host;
@@ -462,6 +27,7 @@ class LocalAiCoreServer {
462
27
  externalReplayTimers = new Map();
463
28
  externalRunSseClients = new Map();
464
29
  openAiRunStreams = new Map();
30
+ handlers = new Map();
465
31
  server = (0, node_http_1.createServer)((req, res) => {
466
32
  void this.handleRequest(req, res);
467
33
  });
@@ -469,44 +35,8 @@ class LocalAiCoreServer {
469
35
  this.bindings = bindings;
470
36
  this.host = options.host || '127.0.0.1';
471
37
  this.port = options.port ?? 9831;
472
- this.bindings.on('runtime', (runtime) => {
473
- this.broadcast({ type: 'runtime.updated', runtime });
474
- });
475
- this.bindings.on('bridge', (bridge) => {
476
- this.broadcast({ type: 'stream.updated', stream: bridge });
477
- if (bridge.replyCtx) {
478
- const runId = String(bridge.replyCtx);
479
- this.broadcastExternalRunStream(runId, bridge);
480
- this.broadcastOpenAiRunStream(runId, bridge);
481
- }
482
- if (bridge.sessionKey) {
483
- const threadId = this.findThreadIdFromSessionKey(bridge.sessionKey);
484
- this.broadcast({
485
- type: 'presence.updated',
486
- threadId,
487
- live: bridge.type !== 'typing_stop',
488
- stream: bridge,
489
- });
490
- }
491
- });
492
- this.bindings.on('thread-session-activated', (event) => {
493
- this.broadcast({ type: 'thread.session.activated', ...event });
494
- });
495
- this.bindings.on('scheduler-job', (job) => {
496
- this.broadcast({ type: 'scheduler.job.updated', job });
497
- });
498
- this.bindings.on('scheduler-run', (run) => {
499
- this.broadcast({ type: 'scheduler.run.updated', run });
500
- });
501
- this.bindings.on('automation-monitor', (monitor) => {
502
- this.broadcast({ type: 'automation.monitor.updated', monitor });
503
- });
504
- this.bindings.on('automation-monitor-run', (run) => {
505
- this.broadcast({ type: 'automation.monitor.run.updated', run });
506
- });
507
- this.bindings.on('runtime-detection', (event) => {
508
- this.broadcast(event);
509
- });
38
+ this.registerHandlers();
39
+ this.wireEvents();
510
40
  }
511
41
  async start() {
512
42
  await new Promise((resolve, reject) => {
@@ -552,8 +82,82 @@ class LocalAiCoreServer {
552
82
  });
553
83
  });
554
84
  }
85
+ registerHandlers() {
86
+ const b = this.bindings;
87
+ (0, runtime_handler_js_1.registerRuntimeHandlers)(this.handlers, b.controller, b.errorReporter, (res) => this.attachSseClient(res));
88
+ (0, runtimes_handler_js_1.registerRuntimesHandlers)(this.handlers, b.runtimeDetection);
89
+ (0, thread_handler_js_1.registerThreadHandlers)(this.handlers, b.workspaceRouter);
90
+ (0, workspace_handler_js_1.registerWorkspaceHandlers)(this.handlers, b.workspaceRouter);
91
+ (0, security_handler_js_1.registerSecurityHandlers)(this.handlers, b.workspaceRouter);
92
+ (0, task_handler_js_1.registerTaskHandlers)(this.handlers, b.workspaceRouter);
93
+ (0, scheduler_handler_js_1.registerSchedulerHandlers)(this.handlers, b.scheduledJobs);
94
+ (0, automation_handler_js_1.registerAutomationHandlers)(this.handlers, b.automationMonitors);
95
+ (0, knowledge_handler_js_1.registerKnowledgeHandlers)(this.handlers, b.knowledgeProvider);
96
+ (0, capabilities_handler_js_1.registerCapabilitiesHandlers)(this.handlers, b.kernel);
97
+ (0, provider_handler_js_1.registerProviderHandlers)(this.handlers, b.store);
98
+ (0, channel_handler_js_1.registerChannelHandlers)(this.handlers, b.channelService);
99
+ (0, external_handler_js_1.registerExternalHandlers)(this.handlers, b.externalService, (runId, res) => this.attachExternalRunSseClient(runId, res));
100
+ const openAiReg = {
101
+ addAdapter: (runId, adapter) => {
102
+ const adapters = this.openAiRunStreams.get(runId) || new Set();
103
+ adapters.add(adapter);
104
+ this.openAiRunStreams.set(runId, adapters);
105
+ },
106
+ removeAdapter: (runId, adapter) => {
107
+ const adapters = this.openAiRunStreams.get(runId);
108
+ if (adapters) {
109
+ adapters.delete(adapter);
110
+ if (adapters.size === 0) {
111
+ this.openAiRunStreams.delete(runId);
112
+ }
113
+ }
114
+ },
115
+ };
116
+ (0, openai_handler_js_1.registerOpenAiHandler)(this.handlers, b.externalService, openAiReg);
117
+ }
118
+ wireEvents() {
119
+ const b = this.bindings;
120
+ b.controller.on('runtime', (runtime) => {
121
+ this.broadcast({ type: 'runtime.updated', runtime });
122
+ });
123
+ b.controller.on('bridge', (bridge) => {
124
+ this.broadcast({ type: 'stream.updated', stream: bridge });
125
+ if (bridge.replyCtx) {
126
+ const runId = String(bridge.replyCtx);
127
+ this.broadcastExternalRunStream(runId, bridge);
128
+ this.broadcastOpenAiRunStream(runId, bridge);
129
+ }
130
+ if (bridge.sessionKey) {
131
+ const threadId = this.findThreadIdFromSessionKey(bridge.sessionKey);
132
+ this.broadcast({
133
+ type: 'presence.updated',
134
+ threadId,
135
+ live: bridge.type !== 'typing_stop',
136
+ stream: bridge,
137
+ });
138
+ }
139
+ });
140
+ b.controller.on('thread-session-activated', (event) => {
141
+ this.broadcast({ type: 'thread.session.activated', ...event });
142
+ });
143
+ b.controller.on('scheduler-job', (job) => {
144
+ this.broadcast({ type: 'scheduler.job.updated', job });
145
+ });
146
+ b.controller.on('scheduler-run', (run) => {
147
+ this.broadcast({ type: 'scheduler.run.updated', run });
148
+ });
149
+ b.controller.on('automation-monitor', (monitor) => {
150
+ this.broadcast({ type: 'automation.monitor.updated', monitor });
151
+ });
152
+ b.controller.on('automation-monitor-run', (run) => {
153
+ this.broadcast({ type: 'automation.monitor.run.updated', run });
154
+ });
155
+ b.controller.on('runtime-detection', (event) => {
156
+ this.broadcast(event);
157
+ });
158
+ }
555
159
  async handleRequest(req, res) {
556
- setCorsHeaders(req, res);
160
+ (0, server_helpers_js_1.setCorsHeaders)(req, res);
557
161
  if (req.method === 'OPTIONS') {
558
162
  res.statusCode = 204;
559
163
  res.end();
@@ -564,467 +168,19 @@ class LocalAiCoreServer {
564
168
  try {
565
169
  const route = (0, server_routes_js_1.parseLocalAiCoreRoute)(req.method, path);
566
170
  if (route) {
567
- await this.handleParsedRoute(route, req, res, url);
568
- return;
569
- }
570
- jsonError(res, 404, new Error(`Unknown route: ${path}`));
571
- }
572
- catch (error) {
573
- jsonError(res, 500, error);
574
- }
575
- }
576
- async handleParsedRoute(route, req, res, url) {
577
- switch (route.name) {
578
- case 'health':
579
- json(res, 200, { name: 'local-ai-core', version: '0.1.0' });
580
- return;
581
- case 'runtime.status':
582
- json(res, 200, await this.bindings.getRuntimeStatus());
583
- return;
584
- case 'runtime.service.start':
585
- json(res, 200, await this.bindings.startService());
586
- return;
587
- case 'runtime.service.stop':
588
- json(res, 200, await this.bindings.stopService());
589
- return;
590
- case 'runtime.service.restart':
591
- json(res, 200, await this.bindings.restartService());
592
- return;
593
- case 'runtime.logs': {
594
- const limit = Number(url.searchParams.get('limit') || '200');
595
- json(res, 200, this.bindings.getLogs(limit));
596
- return;
597
- }
598
- case 'logs.list': {
599
- const level = url.searchParams.get('level') || 'sys';
600
- const limit = Number(url.searchParams.get('limit') || '200');
601
- json(res, 200, {
602
- entries: this.bindings.getLogEntries(level, limit).map((entry) => ({
603
- time: entry.ts,
604
- level: entry.level,
605
- scope: entry.scope,
606
- message: entry.message,
607
- meta: entry.meta,
608
- })),
609
- });
610
- return;
611
- }
612
- case 'runtime.agent-runtimes':
613
- case 'runtimes.list':
614
- json(res, 200, await this.runtimeDetectionResponse());
615
- return;
616
- case 'runtimes.detail': {
617
- const runtimes = await this.bindings.listInstalledAgentRuntimes();
618
- const runtime = runtimes.find((entry) => entry.runtimeId === route.runtimeId || entry.agentType === route.runtimeId);
619
- if (!runtime) {
620
- json(res, 404, null, false, 'Runtime not found');
171
+ const handler = this.handlers.get(route.name);
172
+ if (handler) {
173
+ await handler(route, req, res, url);
621
174
  return;
622
175
  }
623
- json(res, 200, runtime);
624
- return;
625
- }
626
- case 'runtimes.refresh': {
627
- const runtimes = await this.bindings.refreshInstalledAgentRuntimes();
628
- json(res, 200, { runtimes, checking: this.bindings.isRuntimeDetectionRunning() });
629
- return;
630
- }
631
- case 'runtimes.refresh-one': {
632
- const runtimes = await this.bindings.refreshInstalledAgentRuntimes(route.runtimeId);
633
- json(res, 200, { runtimes, checking: this.bindings.isRuntimeDetectionRunning(route.runtimeId) });
634
- return;
635
- }
636
- case 'runtime.config.read':
637
- json(res, 200, await this.bindings.readConfigFile());
638
- return;
639
- case 'runtime.config.save-raw': {
640
- const body = await readJsonBody(req);
641
- json(res, 200, await this.bindings.saveRawConfigFile(String(body.raw || '')));
642
- return;
643
- }
644
- case 'runtime.config.save-structured': {
645
- const body = await readJsonBody(req);
646
- json(res, 200, await this.bindings.saveStructuredConfigFile((body.config || {})));
647
- return;
648
- }
649
- case 'runtime.settings.save': {
650
- const body = await readJsonBody(req);
651
- json(res, 200, await this.bindings.saveSettings(body));
652
- return;
653
- }
654
- case 'scheduler.jobs.list': {
655
- const workspaceId = String(url.searchParams.get('workspace_id') || '');
656
- json(res, 200, { jobs: await this.bindings.listScheduledJobs(workspaceId || undefined) });
657
- return;
658
- }
659
- case 'scheduler.jobs.create': {
660
- const body = await readJsonBody(req);
661
- json(res, 200, await this.bindings.createScheduledJob(body));
662
- return;
663
- }
664
- case 'scheduler.job.get':
665
- json(res, 200, await this.bindings.getScheduledJob(route.jobId));
666
- return;
667
- case 'scheduler.job.runs':
668
- json(res, 200, { runs: await this.bindings.listScheduledJobRuns(route.jobId) });
669
- return;
670
- case 'scheduler.job.run':
671
- json(res, 200, await this.bindings.runScheduledJob(route.jobId));
672
- return;
673
- case 'scheduler.job.update': {
674
- const body = await readJsonBody(req);
675
- json(res, 200, await this.bindings.updateScheduledJob(route.jobId, body));
676
- return;
677
- }
678
- case 'scheduler.job.delete':
679
- json(res, 200, await this.bindings.deleteScheduledJob(route.jobId));
680
- return;
681
- case 'automation.monitors.list': {
682
- const workspaceId = String(url.searchParams.get('workspace_id') || '');
683
- json(res, 200, { monitors: await this.bindings.listAutomationMonitors(workspaceId || undefined) });
684
- return;
685
- }
686
- case 'automation.monitors.create': {
687
- const body = await readJsonBody(req);
688
- json(res, 200, await this.bindings.createAutomationMonitor(body));
689
- return;
690
- }
691
- case 'automation.monitor.get':
692
- json(res, 200, await this.bindings.getAutomationMonitor(route.monitorId));
693
- return;
694
- case 'automation.monitor.runs':
695
- json(res, 200, { runs: await this.bindings.listAutomationMonitorRuns(route.monitorId) });
696
- return;
697
- case 'automation.monitor.run':
698
- json(res, 200, await this.bindings.runAutomationMonitor(route.monitorId));
699
- return;
700
- case 'automation.monitor.update': {
701
- const body = await readJsonBody(req);
702
- json(res, 200, await this.bindings.updateAutomationMonitor(route.monitorId, body));
703
- return;
704
- }
705
- case 'automation.monitor.delete':
706
- json(res, 200, await this.bindings.deleteAutomationMonitor(route.monitorId));
707
- return;
708
- case 'threads.list': {
709
- const workspaceId = String(url.searchParams.get('workspace_id') || '');
710
- json(res, 200, { threads: workspaceId ? await this.bindings.listThreads(workspaceId) : [] });
711
- return;
712
- }
713
- case 'threads.create': {
714
- const body = await readJsonBody(req);
715
- json(res, 200, await this.bindings.createThread(String(body.workspaceId || ''), String(body.title || '') || undefined));
716
- return;
717
- }
718
- case 'thread.get':
719
- json(res, 200, await this.bindings.getThread(route.threadId));
720
- return;
721
- case 'thread.update-knowledge-bases': {
722
- const body = await readJsonBody(req);
723
- const knowledgeBaseIds = Array.isArray(body.knowledgeBaseIds)
724
- ? body.knowledgeBaseIds.map((value) => String(value || ''))
725
- : [];
726
- json(res, 200, await this.bindings.updateThreadKnowledgeBases(route.threadId, knowledgeBaseIds));
727
- return;
728
- }
729
- case 'thread.rename': {
730
- const body = await readJsonBody(req);
731
- json(res, 200, await this.bindings.renameThread(route.threadId, String(body.title || '')));
176
+ (0, server_helpers_js_1.jsonError)(res, 404, new Error(`No handler for route: ${route.name}`));
732
177
  return;
733
178
  }
734
- case 'thread.delete':
735
- json(res, 200, await this.bindings.deleteThread(route.threadId));
736
- return;
737
- case 'thread.messages.send': {
738
- const body = await readJsonBody(req);
739
- json(res, 200, await this.bindings.sendThreadMessage(route.threadId, String(body.content || '')));
740
- return;
741
- }
742
- case 'thread.actions.send': {
743
- const body = await readJsonBody(req);
744
- json(res, 200, await this.bindings.sendThreadAction(route.threadId, String(body.content || '')));
745
- return;
746
- }
747
- case 'run.interrupt':
748
- json(res, 200, await this.bindings.interruptRun(route.runId));
749
- return;
750
- case 'workspaces.list':
751
- json(res, 200, { workspaces: await this.bindings.listWorkspaces() });
752
- return;
753
- case 'workspace.streaming-probe':
754
- json(res, 200, await this.bindings.probeWorkspaceStreaming(route.workspaceId));
755
- return;
756
- case 'workspace-registry.list':
757
- json(res, 200, { workspaces: await this.bindings.listWorkspaceRegistry() });
758
- return;
759
- case 'workspace-registry.create': {
760
- const body = await readJsonBody(req);
761
- json(res, 200, await this.bindings.createWorkspaceRegistryEntry(body));
762
- return;
763
- }
764
- case 'workspace-registry.get':
765
- json(res, 200, await this.bindings.getWorkspaceRegistryEntry(route.workspaceId));
766
- return;
767
- case 'workspace-registry.update': {
768
- const body = await readJsonBody(req);
769
- json(res, 200, await this.bindings.updateWorkspaceRegistryEntry(route.workspaceId, body));
770
- return;
771
- }
772
- case 'workspace-registry.delete':
773
- json(res, 200, await this.bindings.deleteWorkspaceRegistryEntry(route.workspaceId));
774
- return;
775
- case 'providers.list':
776
- json(res, 200, await this.bindings.listModelProviders());
777
- return;
778
- case 'providers.create': {
779
- const body = await readJsonBody(req);
780
- json(res, 200, await this.bindings.createModelProvider(body));
781
- return;
782
- }
783
- case 'provider.update': {
784
- const body = await readJsonBody(req);
785
- json(res, 200, await this.bindings.updateModelProvider(route.providerId, body));
786
- return;
787
- }
788
- case 'provider.delete':
789
- json(res, 200, await this.bindings.deleteModelProvider(route.providerId));
790
- return;
791
- case 'external.project.ensure': {
792
- const body = await readJsonBody(req);
793
- json(res, 200, await this.bindings.ensureExternalProject(body));
794
- return;
795
- }
796
- case 'external.run.create': {
797
- const body = await readJsonBody(req);
798
- json(res, 200, await this.bindings.createExternalRun(body));
799
- return;
800
- }
801
- case 'external.run.events':
802
- await this.attachExternalRunSseClient(route.runId, res);
803
- return;
804
- case 'openai.chat.completions':
805
- await this.handleOpenAiChatCompletions(req, res);
806
- return;
807
- case 'workspace-security.get':
808
- json(res, 200, await this.bindings.getWorkspaceSecuritySettings(route.workspaceId));
809
- return;
810
- case 'workspace-security.update': {
811
- const body = await readJsonBody(req);
812
- json(res, 200, await this.bindings.updateWorkspaceSecuritySettings(route.workspaceId, body));
813
- return;
814
- }
815
- case 'security.command-risk.classify': {
816
- const body = await readJsonBody(req);
817
- json(res, 200, await this.bindings.classifyCommand(String(body.command || ''), String(body.workspaceId || '') || undefined));
818
- return;
819
- }
820
- case 'approvals.list': {
821
- const statusParam = url.searchParams.get('status') || '';
822
- const status = statusParam ? statusParam.split(',').map((item) => item.trim()).filter(Boolean) : undefined;
823
- json(res, 200, await this.bindings.listApprovalRequests({
824
- workspaceId: url.searchParams.get('workspace_id') || undefined,
825
- taskId: url.searchParams.get('task_id') || undefined,
826
- status,
827
- limit: Number(url.searchParams.get('limit') || '50'),
828
- }));
829
- return;
830
- }
831
- case 'approvals.create': {
832
- const body = await readJsonBody(req);
833
- json(res, 200, await this.bindings.createApprovalRequest(body));
834
- return;
835
- }
836
- case 'approval.get':
837
- json(res, 200, await this.bindings.getApprovalRequest(route.approvalId));
838
- return;
839
- case 'approval.resolve': {
840
- const body = await readJsonBody(req);
841
- json(res, 200, await this.bindings.resolveApprovalRequest(route.approvalId, body));
842
- return;
843
- }
844
- case 'audit-events.list': {
845
- const typeParam = url.searchParams.get('type') || '';
846
- const type = typeParam ? typeParam.split(',').map((item) => item.trim()).filter(Boolean) : undefined;
847
- json(res, 200, await this.bindings.listAuditEvents({
848
- workspaceId: url.searchParams.get('workspace_id') || undefined,
849
- taskId: url.searchParams.get('task_id') || undefined,
850
- approvalId: url.searchParams.get('approval_id') || undefined,
851
- type,
852
- limit: Number(url.searchParams.get('limit') || '50'),
853
- }));
854
- return;
855
- }
856
- case 'tasks.list': {
857
- const statusParam = url.searchParams.get('status') || '';
858
- const status = statusParam ? statusParam.split(',').map((item) => item.trim()).filter(Boolean) : undefined;
859
- json(res, 200, await this.bindings.listAgentTasks({
860
- workspaceId: url.searchParams.get('workspace_id') || undefined,
861
- runtimeId: url.searchParams.get('runtime_id') || undefined,
862
- status,
863
- limit: Number(url.searchParams.get('limit') || '50'),
864
- }));
865
- return;
866
- }
867
- case 'tasks.create': {
868
- const body = await readJsonBody(req);
869
- json(res, 200, await this.bindings.createAgentTask(body));
870
- return;
871
- }
872
- case 'task.get':
873
- json(res, 200, await this.bindings.getAgentTask(route.taskId));
874
- return;
875
- case 'task.update': {
876
- const body = await readJsonBody(req);
877
- json(res, 200, await this.bindings.updateAgentTask(route.taskId, body));
878
- return;
879
- }
880
- case 'knowledge.sources.list':
881
- json(res, 200, { sources: await this.bindings.listKnowledgeSources() });
882
- return;
883
- case 'knowledge.config.read':
884
- json(res, 200, await this.bindings.getKnowledgeConfig());
885
- return;
886
- case 'knowledge.config.update': {
887
- const body = await readJsonBody(req);
888
- json(res, 200, await this.bindings.updateKnowledgeConfig(body));
889
- return;
890
- }
891
- case 'knowledge.folders.list':
892
- json(res, 200, { folders: await this.bindings.listKnowledgeFolders() });
893
- return;
894
- case 'knowledge.folders.create': {
895
- const body = await readJsonBody(req);
896
- json(res, 200, await this.bindings.createKnowledgeFolder(body));
897
- return;
898
- }
899
- case 'knowledge.folder.update': {
900
- const body = await readJsonBody(req);
901
- json(res, 200, await this.bindings.updateKnowledgeFolder(route.folderId, body));
902
- return;
903
- }
904
- case 'knowledge.folder.delete':
905
- json(res, 200, await this.bindings.deleteKnowledgeFolder(route.folderId));
906
- return;
907
- case 'knowledge.bases.list':
908
- json(res, 200, { bases: await this.bindings.listKnowledgeBases() });
909
- return;
910
- case 'knowledge.bases.create': {
911
- const body = await readJsonBody(req);
912
- json(res, 200, await this.bindings.createKnowledgeBase(body));
913
- return;
914
- }
915
- case 'knowledge.base.get':
916
- json(res, 200, await this.bindings.getKnowledgeBase(route.knowledgeBaseId));
917
- return;
918
- case 'knowledge.base.update': {
919
- const body = await readJsonBody(req);
920
- json(res, 200, await this.bindings.updateKnowledgeBase(route.knowledgeBaseId, body));
921
- return;
922
- }
923
- case 'knowledge.base.delete':
924
- json(res, 200, await this.bindings.deleteKnowledgeBase(route.knowledgeBaseId));
925
- return;
926
- case 'knowledge.base.files.list':
927
- json(res, 200, { files: await this.bindings.listKnowledgeBaseFiles(route.knowledgeBaseId) });
928
- return;
929
- case 'knowledge.base.files.upload': {
930
- const contentType = String(req.headers['content-type'] || '').trim();
931
- if (!contentType) {
932
- throw new Error('Upload content type is required.');
933
- }
934
- const body = await readRawBody(req);
935
- json(res, 200, { results: await this.bindings.uploadKnowledgeBaseFiles(route.knowledgeBaseId, { contentType, body }) });
936
- return;
937
- }
938
- case 'knowledge.base.file.delete':
939
- json(res, 200, await this.bindings.deleteKnowledgeBaseFile(route.knowledgeBaseId, route.fileId));
940
- return;
941
- case 'knowledge.base.search': {
942
- const body = await readJsonBody(req);
943
- json(res, 200, { results: await this.bindings.searchKnowledgeBase(route.knowledgeBaseId, body) });
944
- return;
945
- }
946
- case 'capabilities.read':
947
- json(res, 200, await this.bindings.getCapabilities());
948
- return;
949
- case 'capabilities.snapshot':
950
- json(res, 200, await this.bindings.getCapabilitySnapshot());
951
- return;
952
- case 'diagnostics.errors':
953
- json(res, 200, { errors: await this.bindings.listDiagnosticErrors() });
954
- return;
955
- case 'diagnostics.doctor':
956
- json(res, 200, await this.bindings.runDiagnosticsDoctor());
957
- return;
958
- case 'diagnostics.deployment':
959
- json(res, 200, await this.bindings.runDeploymentDiagnostics());
960
- return;
961
- case 'plugins.diagnostics':
962
- json(res, 200, await this.bindings.getPluginDiagnostics());
963
- return;
964
- case 'events.stream':
965
- this.attachSseClient(res);
966
- return;
967
- case 'platform.gateways.list':
968
- json(res, 200, { gateways: await this.bindings.listChannelGatewayStatuses(route.platform) });
969
- return;
970
- case 'platform.pairings.list': {
971
- const workspaceId = String(url.searchParams.get('workspace_id') || '');
972
- json(res, 200, { pairings: await this.bindings.listChannelPendingPairings(route.platform, workspaceId || undefined) });
973
- return;
974
- }
975
- case 'platform.users.list': {
976
- const workspaceId = String(url.searchParams.get('workspace_id') || '');
977
- json(res, 200, { users: await this.bindings.listChannelAuthorizedUsers(route.platform, workspaceId || undefined) });
978
- return;
979
- }
980
- case 'platform.gateway.get':
981
- json(res, 200, await this.bindings.getChannelGatewayStatus(route.platform, route.workspaceId, this.channelInstanceId(url)));
982
- return;
983
- case 'platform.qrcode.status': {
984
- const ticket = String(url.searchParams.get('ticket') || '');
985
- if (!ticket) {
986
- jsonError(res, 400, new Error('Missing ticket parameter'));
987
- return;
988
- }
989
- json(res, 200, await this.bindings.checkChannelQrCodeStatus(route.platform, route.workspaceId, ticket, this.channelInstanceId(url)));
990
- return;
991
- }
992
- case 'platform.pairing.approve': {
993
- const body = await readJsonBody(req);
994
- json(res, 200, await this.bindings.approveChannelPairing(route.platform, String(body.code || '')));
995
- return;
996
- }
997
- case 'platform.pairing.reject': {
998
- const body = await readJsonBody(req);
999
- json(res, 200, await this.bindings.rejectChannelPairing(route.platform, String(body.code || '')));
1000
- return;
1001
- }
1002
- case 'platform.gateway.test':
1003
- json(res, 200, await this.bindings.testChannelConnection(route.platform, route.workspaceId, this.channelInstanceId(url)));
1004
- return;
1005
- case 'platform.gateway.enable':
1006
- json(res, 200, await this.bindings.enableChannelGateway(route.platform, route.workspaceId, this.channelInstanceId(url)));
1007
- return;
1008
- case 'platform.gateway.disable':
1009
- json(res, 200, await this.bindings.disableChannelGateway(route.platform, route.workspaceId, this.channelInstanceId(url)));
1010
- return;
1011
- case 'platform.file.send': {
1012
- const body = await readJsonBody(req);
1013
- json(res, 200, await this.bindings.sendChannelFile(route.platform, route.workspaceId, body));
1014
- return;
1015
- }
1016
- case 'platform.message.send': {
1017
- const body = await readJsonBody(req);
1018
- json(res, 200, await this.bindings.sendChannelMessage(route.platform, route.workspaceId, body));
1019
- return;
1020
- }
1021
- case 'platform.qrcode.create':
1022
- json(res, 200, await this.bindings.getChannelQrCode(route.platform, route.workspaceId, this.channelInstanceId(url)));
1023
- return;
179
+ (0, server_helpers_js_1.jsonError)(res, 404, new Error(`Unknown route: ${path}`));
180
+ }
181
+ catch (error) {
182
+ (0, server_helpers_js_1.jsonError)(res, 500, error);
1024
183
  }
1025
- }
1026
- channelInstanceId(url) {
1027
- return String(url.searchParams.get('instance_id') || url.searchParams.get('instanceId') || '').trim() || undefined;
1028
184
  }
1029
185
  attachSseClient(res) {
1030
186
  res.writeHead(200, {
@@ -1060,7 +216,7 @@ class LocalAiCoreServer {
1060
216
  }, 15000);
1061
217
  this.heartbeatTimers.set(res, heartbeat);
1062
218
  const replayTimer = setInterval(() => {
1063
- void this.bindings.getExternalRunSnapshot(runId)
219
+ void this.bindings.externalService.getRunSnapshot(runId)
1064
220
  .then((snapshot) => this.replayExternalRunMessages(res, runId, snapshot, replayedMessageIds))
1065
221
  .catch(() => undefined);
1066
222
  }, 1000);
@@ -1075,131 +231,15 @@ class LocalAiCoreServer {
1075
231
  this.externalRunSseClients.delete(runId);
1076
232
  }
1077
233
  });
1078
- const snapshot = await this.bindings.getExternalRunSnapshot(runId);
1079
- res.write(createSseEvent('external.run.snapshot', {
234
+ const snapshot = await this.bindings.externalService.getRunSnapshot(runId);
235
+ res.write((0, server_helpers_js_1.createSseEvent)('external.run.snapshot', {
1080
236
  type: 'external.run.snapshot',
1081
237
  snapshot,
1082
238
  }));
1083
239
  this.replayExternalRunMessages(res, runId, snapshot, replayedMessageIds);
1084
240
  }
1085
- async handleOpenAiChatCompletions(req, res) {
1086
- let parsed;
1087
- try {
1088
- parsed = parseOpenAiChatCompletionRequest(await readJsonBody(req));
1089
- }
1090
- catch (error) {
1091
- openAiJsonError(res, 400, error instanceof Error ? error.message : String(error));
1092
- return;
1093
- }
1094
- if (parsed.stream) {
1095
- await this.handleOpenAiStreamingChatCompletion(parsed, res);
1096
- return;
1097
- }
1098
- await this.handleOpenAiNonStreamingChatCompletion(parsed, res);
1099
- }
1100
- async handleOpenAiStreamingChatCompletion(parsed, res) {
1101
- try {
1102
- const created = await this.bindings.createExternalRun(parsed.externalRun);
1103
- const adapters = this.openAiRunStreams.get(created.run_id) || new Set();
1104
- const adapter = new OpenAiChatCompletionStreamAdapter({
1105
- runId: created.run_id,
1106
- model: parsed.model,
1107
- response: res,
1108
- progressMode: parsed.progressMode,
1109
- onClose: () => {
1110
- adapters.delete(adapter);
1111
- if (adapters.size === 0) {
1112
- this.openAiRunStreams.delete(created.run_id);
1113
- }
1114
- },
1115
- });
1116
- adapters.add(adapter);
1117
- this.openAiRunStreams.set(created.run_id, adapters);
1118
- adapter.start();
1119
- res.on('close', () => {
1120
- adapters.delete(adapter);
1121
- if (adapters.size === 0) {
1122
- this.openAiRunStreams.delete(created.run_id);
1123
- }
1124
- });
1125
- const snapshot = await this.bindings.getExternalRunSnapshot(created.run_id);
1126
- this.replayOpenAiRunMessages(adapter, snapshot);
1127
- if (isTerminalAgentTaskStatus(snapshot.task?.status)) {
1128
- adapter.finish('stop', { event: 'run_finished', kind: 'status' });
1129
- }
1130
- }
1131
- catch (error) {
1132
- openAiJsonError(res, 500, error instanceof Error ? error.message : String(error), 'agentdock_run_error');
1133
- }
1134
- }
1135
- async handleOpenAiNonStreamingChatCompletion(parsed, res) {
1136
- let created;
1137
- try {
1138
- created = await this.bindings.createExternalRun(parsed.externalRun);
1139
- }
1140
- catch (error) {
1141
- openAiJsonError(res, 500, error instanceof Error ? error.message : String(error), 'agentdock_run_error');
1142
- return;
1143
- }
1144
- const started = Date.now();
1145
- const timeoutMs = 10 * 60 * 1000;
1146
- let snapshot = await this.bindings.getExternalRunSnapshot(created.run_id);
1147
- while (!isTerminalAgentTaskStatus(snapshot.task?.status) && Date.now() - started < timeoutMs) {
1148
- await new Promise((resolve) => setTimeout(resolve, 500));
1149
- snapshot = await this.bindings.getExternalRunSnapshot(created.run_id);
1150
- }
1151
- if (!isTerminalAgentTaskStatus(snapshot.task?.status)) {
1152
- openAiJsonError(res, 504, 'OpenAI-compatible chat completion timed out waiting for the agent run.', 'run_timeout');
1153
- return;
1154
- }
1155
- if (snapshot.task?.status === 'failed') {
1156
- openAiJsonError(res, 500, snapshot.task.error || 'Agent run failed.', 'agentdock_run_failed');
1157
- return;
1158
- }
1159
- if (snapshot.task?.status === 'cancelled') {
1160
- openAiJsonError(res, 500, snapshot.task.error || 'Agent run was cancelled.', 'agentdock_run_cancelled');
1161
- return;
1162
- }
1163
- const messages = collectAssistantMessagesForRun(snapshot);
1164
- const finalContent = messages
1165
- .filter((message) => message.kind === 'final' || !message.bridgeKind || message.bridgeKind === 'assistant')
1166
- .map((message) => message.content)
1167
- .filter(Boolean)
1168
- .join('\n\n');
1169
- const response = {
1170
- id: `chatcmpl_${sanitizeOpenAiId(created.run_id)}`,
1171
- object: 'chat.completion',
1172
- created: Math.floor(Date.now() / 1000),
1173
- model: parsed.model,
1174
- choices: [
1175
- {
1176
- index: 0,
1177
- message: {
1178
- role: 'assistant',
1179
- content: finalContent,
1180
- },
1181
- finish_reason: 'stop',
1182
- },
1183
- ],
1184
- agentdock: {
1185
- run_id: created.run_id,
1186
- workspace_id: created.workspace_id,
1187
- thread_id: created.thread_id,
1188
- task_id: created.task_id,
1189
- events: messages
1190
- .filter((message) => message.bridgeKind && message.bridgeKind !== 'assistant')
1191
- .map((message) => ({
1192
- event: `${message.bridgeKind}_update`,
1193
- kind: message.bridgeKind,
1194
- content: message.content,
1195
- tool: message.toolCall,
1196
- })),
1197
- },
1198
- };
1199
- rawJson(res, 200, response);
1200
- }
1201
241
  broadcast(event) {
1202
- const payload = createSseEvent(event.type, event);
242
+ const payload = (0, server_helpers_js_1.createSseEvent)(event.type, event);
1203
243
  for (const client of this.sseClients) {
1204
244
  client.write(payload);
1205
245
  }
@@ -1209,7 +249,7 @@ class LocalAiCoreServer {
1209
249
  if (!clients?.size) {
1210
250
  return;
1211
251
  }
1212
- const payload = createSseEvent('external.run.stream', {
252
+ const payload = (0, server_helpers_js_1.createSseEvent)('external.run.stream', {
1213
253
  type: 'external.run.stream',
1214
254
  runId,
1215
255
  stream,
@@ -1227,17 +267,6 @@ class LocalAiCoreServer {
1227
267
  adapter.handleBridgeEvent(stream);
1228
268
  }
1229
269
  }
1230
- replayOpenAiRunMessages(adapter, snapshot) {
1231
- for (const message of collectAssistantMessagesForRun(snapshot)) {
1232
- adapter.replayMessage({
1233
- id: message.id,
1234
- content: message.content,
1235
- bridgeKind: message.bridgeKind,
1236
- bridgeStatus: message.bridgeStatus,
1237
- toolCall: message.toolCall,
1238
- });
1239
- }
1240
- }
1241
270
  replayExternalRunMessages(res, runId, snapshot, replayedMessageIds) {
1242
271
  const thread = snapshot.thread;
1243
272
  const startedAt = Date.parse(snapshot.task?.startedAt || snapshot.task?.createdAt || '');
@@ -1256,7 +285,7 @@ class LocalAiCoreServer {
1256
285
  continue;
1257
286
  }
1258
287
  replayedMessageIds.add(message.id);
1259
- res.write(createSseEvent('external.run.stream', {
288
+ res.write((0, server_helpers_js_1.createSseEvent)('external.run.stream', {
1260
289
  type: 'external.run.stream',
1261
290
  runId,
1262
291
  stream: {
@@ -1270,12 +299,6 @@ class LocalAiCoreServer {
1270
299
  }));
1271
300
  }
1272
301
  }
1273
- async runtimeDetectionResponse() {
1274
- return {
1275
- runtimes: await this.bindings.listInstalledAgentRuntimes(),
1276
- checking: this.bindings.isRuntimeDetectionRunning(),
1277
- };
1278
- }
1279
302
  findThreadIdFromSessionKey(sessionKey) {
1280
303
  const parts = sessionKey.split(':');
1281
304
  if (parts.length < 3) {