@kafca/agentdock 0.1.44 → 0.1.45
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 +71 -11
- package/bin/agentdock.mjs +5 -5
- package/dist/renderer/assets/{Badge-Qk9qHovH.js → Badge-BIojwD6s.js} +1 -1
- package/dist/renderer/assets/{Chat-BK-fm0Xr.js → Chat-wjBR3XEX.js} +1 -1
- package/dist/renderer/assets/{ChatMarkdown-BKdW8OON.js → ChatMarkdown-DqszsPB_.js} +1 -1
- package/dist/renderer/assets/Config-BDCHs5A2.js +21 -0
- package/dist/renderer/assets/{CronList-Z3TvSRKP.js → CronList-CEWQYTov.js} +1 -1
- package/dist/renderer/assets/Dashboard-DrWftfk3.js +26 -0
- package/dist/renderer/assets/{EmptyState-kD9yAwtw.js → EmptyState-BqCx1oWr.js} +1 -1
- package/dist/renderer/assets/{KnowledgeDetail-BAMCLrjz.js → KnowledgeDetail-BwK8Gpfa.js} +2 -2
- package/dist/renderer/assets/{KnowledgeHome-ChvjOfrc.js → KnowledgeHome-hhDTZMDO.js} +3 -3
- package/dist/renderer/assets/{Logs-Cv0nth3j.js → Logs-C9xPEhFT.js} +1 -1
- package/dist/renderer/assets/{Modal-DwY4lecS.js → Modal-D2U4r2V0.js} +1 -1
- package/dist/renderer/assets/{MonitorList-JEvMoWUm.js → MonitorList-CayKbfwJ.js} +1 -1
- package/dist/renderer/assets/{Page-CnB9ePsb.js → Page-urf5GcT_.js} +1 -1
- package/dist/renderer/assets/ProjectDetail-B1GyAr7a.js +11 -0
- package/dist/renderer/assets/{ProjectList-D6CXumR_.js → ProjectList-BoipQypE.js} +1 -1
- package/dist/renderer/assets/{Select-zfoksLGB.js → Select-CAscWpy5.js} +1 -1
- package/dist/renderer/assets/{SessionChat-DUTh4WoM.js → SessionChat-BAVy5221.js} +1 -1
- package/dist/renderer/assets/{SessionList-D9o-lUst.js → SessionList-BZpuMvh0.js} +1 -1
- package/dist/renderer/assets/{ThreadChat-BYXlT-Kc.js → ThreadChat-JX7m9biG.js} +1 -1
- package/dist/renderer/assets/Workspace-CFW8otjU.js +14 -0
- package/dist/renderer/assets/{arrow-left-CNEAuElg.js → arrow-left-7HrNuS-V.js} +1 -1
- package/dist/renderer/assets/{book-open-BNJMhNI-.js → book-open-BDoAu82s.js} +1 -1
- package/dist/renderer/assets/{check-BnclClRU.js → check-DGlyq4xX.js} +1 -1
- package/dist/renderer/assets/{chevron-down-Bh53e0O-.js → chevron-down-Bb_3EHq9.js} +1 -1
- package/dist/renderer/assets/{circle-ClZTWCWO.js → circle-DIpqZnYw.js} +1 -1
- package/dist/renderer/assets/desktop-Ci1fnRDR.js +1 -0
- package/dist/renderer/assets/{desktop-C6q6wBae.js → desktop-Do_UrC-o.js} +5 -5
- package/dist/renderer/assets/en-CmAr_0e6.js +1 -0
- package/dist/renderer/assets/es-Cs9x52ca.js +1 -0
- package/dist/renderer/assets/{funnel-_5hsz0xI.js → funnel-Bbf0ERLP.js} +1 -1
- package/dist/renderer/assets/{heart-u4Q1aOqj.js → heart-CGqU-qfw.js} +1 -1
- package/dist/renderer/assets/{index-BNFx4uYv.js → index-Df-Trovt.js} +2 -2
- package/dist/renderer/assets/{index-B36eowj-.js → index-DwhTjLA1.js} +36 -36
- package/dist/renderer/assets/index-RaTga2BC.css +11 -0
- package/dist/renderer/assets/ja-DtvtBRTW.js +1 -0
- package/dist/renderer/assets/{pencil-DLh9QNmT.js → pencil-CHiy1MvK.js} +1 -1
- package/dist/renderer/assets/{play-BDTk9Scm.js → play-CGYnmC4Q.js} +1 -1
- package/dist/renderer/assets/{plug-BG0JHW6B.js → plug-Mexu4gyV.js} +1 -1
- package/dist/renderer/assets/{plus-BSc8POsz.js → plus-B_Q5-Oj7.js} +1 -1
- package/dist/renderer/assets/projects-C6bbkC-O.js +1 -0
- package/dist/renderer/assets/{save-D5AO_G6a.js → save-CJ_QFPw0.js} +1 -1
- package/dist/renderer/assets/{search-CniPk7Ey.js → search-D8Dkhsyq.js} +1 -1
- package/dist/renderer/assets/{server-GsXyQAZm.js → server-Cd96R7WL.js} +1 -1
- package/dist/renderer/assets/{session-utils-BDxzgTBC.js → session-utils-BNUbQBfF.js} +1 -1
- package/dist/renderer/assets/{sessions-CLcKA1Tt.js → sessions-BACfV8s1.js} +1 -1
- package/dist/renderer/assets/{shield-check-4i0E3hmf.js → shield-check-Cnn-yQUA.js} +1 -1
- package/dist/renderer/assets/{trash-2-DhtOCsO5.js → trash-2-oWCxwQ9D.js} +1 -1
- package/dist/renderer/assets/{user-BtC_pVXV.js → user-BzE9BwsY.js} +1 -1
- package/dist/renderer/assets/{wifi-off-BHPzPY_B.js → wifi-off-DHxnNkdY.js} +1 -1
- package/dist/renderer/assets/zh-CcJ9vBZj.js +1 -0
- package/dist/renderer/assets/zh-TW-elI72AC1.js +1 -0
- package/dist/renderer/index.html +2 -2
- package/dist-electron/electron/main.js +26 -1
- package/dist-electron/packages/core-sdk/src/index.js +28 -0
- package/dist-electron/services/local-ai-core/src/acp/local-core-acp-backend.js +111 -62
- package/dist-electron/services/local-ai-core/src/acp/local-core-acp-session-coordinator.js +80 -7
- package/dist-electron/services/local-ai-core/src/acp/local-core-acp-transport.js +78 -5
- package/dist-electron/services/local-ai-core/src/acp/store/external-store.js +98 -0
- package/dist-electron/services/local-ai-core/src/acp/store/local-core-acp-store.js +33 -0
- package/dist-electron/services/local-ai-core/src/acp/store/model-provider-store.js +88 -0
- package/dist-electron/services/local-ai-core/src/acp/store/schema.js +43 -0
- package/dist-electron/services/local-ai-core/src/agents/pi/launch.js +2 -119
- package/dist-electron/services/local-ai-core/src/agents/pi/provider-materializer.js +148 -0
- package/dist-electron/services/local-ai-core/src/channel/lark/local-core-lark-gateway.js +65 -10
- package/dist-electron/services/local-ai-core/src/channel/shared/session-command-runtime.js +4 -2
- package/dist-electron/services/local-ai-core/src/channel/weixin/local-core-weixin-gateway.js +139 -29
- package/dist-electron/services/local-ai-core/src/execution/agent-execution-backend.js +56 -0
- package/dist-electron/services/local-ai-core/src/kernel/bootstrap.js +18 -57
- package/dist-electron/services/local-ai-core/src/kernel/local-core-errors.js +255 -0
- package/dist-electron/services/local-ai-core/src/plugins/builtin/catalog.js +78 -0
- package/dist-electron/services/local-ai-core/src/router/workspace-route-config.js +3 -1
- package/dist-electron/services/local-ai-core/src/router/workspace-router.js +64 -9
- package/dist-electron/services/local-ai-core/src/runtime/agent-runtime-detector.js +5 -2
- package/dist-electron/services/local-ai-core/src/runtime/config-migration.js +152 -0
- package/dist-electron/services/local-ai-core/src/runtime/deployment-diagnostics.js +175 -0
- package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +330 -3
- package/dist-electron/services/local-ai-core/src/runtime/local-core-runtime-state.js +11 -2
- package/dist-electron/services/local-ai-core/src/runtime/provider-config-migration.js +32 -0
- package/dist-electron/services/local-ai-core/src/runtime/runtime-detection-service.js +76 -8
- package/dist-electron/services/local-ai-core/src/runtime/server-routes.js +62 -0
- package/dist-electron/services/local-ai-core/src/runtime/server.js +708 -4
- package/dist-electron/services/local-ai-core/src/runtime/standalone.js +13 -3
- package/dist-electron/services/local-ai-core/src/sandbox/opensandbox-client.js +113 -0
- package/dist-electron/services/local-ai-core/src/sandbox/sandbox-config.js +273 -0
- package/dist-electron/services/local-ai-core/src/sandbox/sandbox-manager.js +215 -0
- package/dist-electron/services/local-ai-core/src/sandbox/sandbox-stdio-proxy.js +188 -0
- package/dist-electron/services/local-ai-core/src/thread/slash-command-registry.js +3 -0
- package/dist-electron/services/local-ai-core/src/thread/thread-command-service.js +32 -0
- package/dist-electron/services/local-ai-core/src/thread/thread-slash-command-dispatcher.js +71 -0
- package/dist-electron/shared/desktop.js +85 -1
- package/dist-electron/tests/contracts/architecture-docs.test.js +83 -0
- package/dist-electron/tests/electron/plugin-kernel.test.js +225 -18
- package/dist-electron/tests/electron/runtime-detection-service.test.js +57 -5
- package/dist-electron/tests/electron/sandbox-config.test.js +541 -0
- package/dist-electron/tests/electron/workspace-task-store.test.js +257 -0
- package/dist-electron/tests/integration/lark-gateway.test.js +59 -0
- package/dist-electron/tests/integration/local-core-refactor.test.js +183 -20
- package/dist-electron/tests/integration/local-core-routes.test.js +34 -0
- package/package.json +2 -3
- package/dist/renderer/assets/Config-B93CQGvY.js +0 -16
- package/dist/renderer/assets/Dashboard-Ch3QkWwf.js +0 -26
- package/dist/renderer/assets/ProjectDetail-DJa3sMmU.js +0 -11
- package/dist/renderer/assets/Workspace-BrhSwRQ-.js +0 -14
- package/dist/renderer/assets/desktop-C5vBT0yM.js +0 -1
- package/dist/renderer/assets/en-D2SkCrhG.js +0 -1
- package/dist/renderer/assets/es-C8UBPhxM.js +0 -1
- package/dist/renderer/assets/index-CaQZSRbg.css +0 -11
- package/dist/renderer/assets/ja-ChF4vbBL.js +0 -1
- package/dist/renderer/assets/projects-DwfZwxDW.js +0 -1
- package/dist/renderer/assets/zh-DIcdHycM.js +0 -1
- package/dist/renderer/assets/zh-TW-BtRwreY_.js +0 -1
|
@@ -3,11 +3,288 @@ 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
|
+
}
|
|
6
263
|
function json(res, statusCode, data, ok = true, error) {
|
|
7
264
|
res.statusCode = statusCode;
|
|
8
265
|
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
9
266
|
res.end(JSON.stringify(ok ? { ok: true, data } : { ok: false, error }));
|
|
10
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
|
+
}
|
|
11
288
|
async function readJsonBody(req) {
|
|
12
289
|
const body = await readRawBody(req);
|
|
13
290
|
if (!body.length) {
|
|
@@ -34,24 +311,174 @@ function setCorsHeaders(req, res) {
|
|
|
34
311
|
function createSseEvent(name, payload) {
|
|
35
312
|
return `event: ${name}\ndata: ${JSON.stringify(payload)}\n\n`;
|
|
36
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
|
+
}
|
|
37
456
|
class LocalAiCoreServer {
|
|
38
457
|
bindings;
|
|
39
458
|
host;
|
|
40
459
|
port;
|
|
41
460
|
sseClients = new Set();
|
|
42
461
|
heartbeatTimers = new Map();
|
|
462
|
+
externalReplayTimers = new Map();
|
|
463
|
+
externalRunSseClients = new Map();
|
|
464
|
+
openAiRunStreams = new Map();
|
|
43
465
|
server = (0, node_http_1.createServer)((req, res) => {
|
|
44
466
|
void this.handleRequest(req, res);
|
|
45
467
|
});
|
|
46
468
|
constructor(bindings, options = {}) {
|
|
47
469
|
this.bindings = bindings;
|
|
48
470
|
this.host = options.host || '127.0.0.1';
|
|
49
|
-
this.port = options.port
|
|
471
|
+
this.port = options.port ?? 9831;
|
|
50
472
|
this.bindings.on('runtime', (runtime) => {
|
|
51
473
|
this.broadcast({ type: 'runtime.updated', runtime });
|
|
52
474
|
});
|
|
53
475
|
this.bindings.on('bridge', (bridge) => {
|
|
54
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
|
+
}
|
|
55
482
|
if (bridge.sessionKey) {
|
|
56
483
|
const threadId = this.findThreadIdFromSessionKey(bridge.sessionKey);
|
|
57
484
|
this.broadcast({
|
|
@@ -99,6 +526,22 @@ class LocalAiCoreServer {
|
|
|
99
526
|
clearInterval(timer);
|
|
100
527
|
}
|
|
101
528
|
this.heartbeatTimers.clear();
|
|
529
|
+
for (const timer of this.externalReplayTimers.values()) {
|
|
530
|
+
clearInterval(timer);
|
|
531
|
+
}
|
|
532
|
+
this.externalReplayTimers.clear();
|
|
533
|
+
for (const clients of this.externalRunSseClients.values()) {
|
|
534
|
+
for (const client of clients) {
|
|
535
|
+
client.end();
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
this.externalRunSseClients.clear();
|
|
539
|
+
for (const adapters of this.openAiRunStreams.values()) {
|
|
540
|
+
for (const adapter of adapters) {
|
|
541
|
+
adapter.finish('stop', { event: 'server_stopped', kind: 'status' });
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
this.openAiRunStreams.clear();
|
|
102
545
|
await new Promise((resolve, reject) => {
|
|
103
546
|
this.server.close((error) => {
|
|
104
547
|
if (error) {
|
|
@@ -124,10 +567,10 @@ class LocalAiCoreServer {
|
|
|
124
567
|
await this.handleParsedRoute(route, req, res, url);
|
|
125
568
|
return;
|
|
126
569
|
}
|
|
127
|
-
|
|
570
|
+
jsonError(res, 404, new Error(`Unknown route: ${path}`));
|
|
128
571
|
}
|
|
129
572
|
catch (error) {
|
|
130
|
-
|
|
573
|
+
jsonError(res, 500, error);
|
|
131
574
|
}
|
|
132
575
|
}
|
|
133
576
|
async handleParsedRoute(route, req, res, url) {
|
|
@@ -329,6 +772,38 @@ class LocalAiCoreServer {
|
|
|
329
772
|
case 'workspace-registry.delete':
|
|
330
773
|
json(res, 200, await this.bindings.deleteWorkspaceRegistryEntry(route.workspaceId));
|
|
331
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;
|
|
332
807
|
case 'workspace-security.get':
|
|
333
808
|
json(res, 200, await this.bindings.getWorkspaceSecuritySettings(route.workspaceId));
|
|
334
809
|
return;
|
|
@@ -474,6 +949,15 @@ class LocalAiCoreServer {
|
|
|
474
949
|
case 'capabilities.snapshot':
|
|
475
950
|
json(res, 200, await this.bindings.getCapabilitySnapshot());
|
|
476
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;
|
|
477
961
|
case 'plugins.diagnostics':
|
|
478
962
|
json(res, 200, await this.bindings.getPluginDiagnostics());
|
|
479
963
|
return;
|
|
@@ -499,7 +983,7 @@ class LocalAiCoreServer {
|
|
|
499
983
|
case 'platform.qrcode.status': {
|
|
500
984
|
const ticket = String(url.searchParams.get('ticket') || '');
|
|
501
985
|
if (!ticket) {
|
|
502
|
-
|
|
986
|
+
jsonError(res, 400, new Error('Missing ticket parameter'));
|
|
503
987
|
return;
|
|
504
988
|
}
|
|
505
989
|
json(res, 200, await this.bindings.checkChannelQrCodeStatus(route.platform, route.workspaceId, ticket, this.channelInstanceId(url)));
|
|
@@ -560,12 +1044,232 @@ class LocalAiCoreServer {
|
|
|
560
1044
|
this.sseClients.delete(res);
|
|
561
1045
|
});
|
|
562
1046
|
}
|
|
1047
|
+
async attachExternalRunSseClient(runId, res) {
|
|
1048
|
+
res.writeHead(200, {
|
|
1049
|
+
'Content-Type': 'text/event-stream',
|
|
1050
|
+
'Cache-Control': 'no-cache, no-transform',
|
|
1051
|
+
Connection: 'keep-alive',
|
|
1052
|
+
});
|
|
1053
|
+
res.write(': connected\n\n');
|
|
1054
|
+
const clients = this.externalRunSseClients.get(runId) || new Set();
|
|
1055
|
+
clients.add(res);
|
|
1056
|
+
this.externalRunSseClients.set(runId, clients);
|
|
1057
|
+
const replayedMessageIds = new Set();
|
|
1058
|
+
const heartbeat = setInterval(() => {
|
|
1059
|
+
res.write(': heartbeat\n\n');
|
|
1060
|
+
}, 15000);
|
|
1061
|
+
this.heartbeatTimers.set(res, heartbeat);
|
|
1062
|
+
const replayTimer = setInterval(() => {
|
|
1063
|
+
void this.bindings.getExternalRunSnapshot(runId)
|
|
1064
|
+
.then((snapshot) => this.replayExternalRunMessages(res, runId, snapshot, replayedMessageIds))
|
|
1065
|
+
.catch(() => undefined);
|
|
1066
|
+
}, 1000);
|
|
1067
|
+
this.externalReplayTimers.set(res, replayTimer);
|
|
1068
|
+
res.on('close', () => {
|
|
1069
|
+
clearInterval(heartbeat);
|
|
1070
|
+
clearInterval(replayTimer);
|
|
1071
|
+
this.heartbeatTimers.delete(res);
|
|
1072
|
+
this.externalReplayTimers.delete(res);
|
|
1073
|
+
clients.delete(res);
|
|
1074
|
+
if (clients.size === 0) {
|
|
1075
|
+
this.externalRunSseClients.delete(runId);
|
|
1076
|
+
}
|
|
1077
|
+
});
|
|
1078
|
+
const snapshot = await this.bindings.getExternalRunSnapshot(runId);
|
|
1079
|
+
res.write(createSseEvent('external.run.snapshot', {
|
|
1080
|
+
type: 'external.run.snapshot',
|
|
1081
|
+
snapshot,
|
|
1082
|
+
}));
|
|
1083
|
+
this.replayExternalRunMessages(res, runId, snapshot, replayedMessageIds);
|
|
1084
|
+
}
|
|
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
|
+
}
|
|
563
1201
|
broadcast(event) {
|
|
564
1202
|
const payload = createSseEvent(event.type, event);
|
|
565
1203
|
for (const client of this.sseClients) {
|
|
566
1204
|
client.write(payload);
|
|
567
1205
|
}
|
|
568
1206
|
}
|
|
1207
|
+
broadcastExternalRunStream(runId, stream) {
|
|
1208
|
+
const clients = this.externalRunSseClients.get(runId);
|
|
1209
|
+
if (!clients?.size) {
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
const payload = createSseEvent('external.run.stream', {
|
|
1213
|
+
type: 'external.run.stream',
|
|
1214
|
+
runId,
|
|
1215
|
+
stream,
|
|
1216
|
+
});
|
|
1217
|
+
for (const client of clients) {
|
|
1218
|
+
client.write(payload);
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
broadcastOpenAiRunStream(runId, stream) {
|
|
1222
|
+
const adapters = this.openAiRunStreams.get(runId);
|
|
1223
|
+
if (!adapters?.size) {
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1226
|
+
for (const adapter of adapters) {
|
|
1227
|
+
adapter.handleBridgeEvent(stream);
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
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
|
+
replayExternalRunMessages(res, runId, snapshot, replayedMessageIds) {
|
|
1242
|
+
const thread = snapshot.thread;
|
|
1243
|
+
const startedAt = Date.parse(snapshot.task?.startedAt || snapshot.task?.createdAt || '');
|
|
1244
|
+
if (!thread || !Number.isFinite(startedAt)) {
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
for (const message of thread.messages || []) {
|
|
1248
|
+
if (message.role !== 'assistant') {
|
|
1249
|
+
continue;
|
|
1250
|
+
}
|
|
1251
|
+
const messageAt = Date.parse(message.timestamp || '');
|
|
1252
|
+
if (!Number.isFinite(messageAt) || messageAt < startedAt) {
|
|
1253
|
+
continue;
|
|
1254
|
+
}
|
|
1255
|
+
if (replayedMessageIds.has(message.id)) {
|
|
1256
|
+
continue;
|
|
1257
|
+
}
|
|
1258
|
+
replayedMessageIds.add(message.id);
|
|
1259
|
+
res.write(createSseEvent('external.run.stream', {
|
|
1260
|
+
type: 'external.run.stream',
|
|
1261
|
+
runId,
|
|
1262
|
+
stream: {
|
|
1263
|
+
type: message.kind === 'final' ? 'reply' : 'update_message',
|
|
1264
|
+
sessionKey: thread.bridgeSessionKey,
|
|
1265
|
+
replyCtx: runId,
|
|
1266
|
+
content: message.content,
|
|
1267
|
+
bridgeKind: message.bridgeKind,
|
|
1268
|
+
bridgeStatus: message.bridgeStatus,
|
|
1269
|
+
},
|
|
1270
|
+
}));
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
569
1273
|
async runtimeDetectionResponse() {
|
|
570
1274
|
return {
|
|
571
1275
|
runtimes: await this.bindings.listInstalledAgentRuntimes(),
|