@downcity/agent 1.1.22 → 1.1.25
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 +6 -5
- package/bin/core/AgentContextTypes.d.ts +21 -2
- package/bin/core/AgentContextTypes.d.ts.map +1 -1
- package/bin/index.d.ts +6 -3
- package/bin/index.d.ts.map +1 -1
- package/bin/index.js.map +1 -1
- package/bin/runtime/server/http/Server.d.ts +1 -1
- package/bin/runtime/server/http/Server.d.ts.map +1 -1
- package/bin/runtime/server/http/Server.js +4 -0
- package/bin/runtime/server/http/Server.js.map +1 -1
- package/bin/runtime/server/http/control/ExecuteBySession.js +1 -1
- package/bin/runtime/server/http/control/ExecuteBySession.js.map +1 -1
- package/bin/runtime/server/http/control/SessionRoutes.d.ts.map +1 -1
- package/bin/runtime/server/http/control/SessionRoutes.js +0 -27
- package/bin/runtime/server/http/control/SessionRoutes.js.map +1 -1
- package/bin/runtime/server/http/execute/execute.js +1 -1
- package/bin/runtime/server/http/execute/execute.js.map +1 -1
- package/bin/runtime/server/http/sdk/Router.d.ts +14 -0
- package/bin/runtime/server/http/sdk/Router.d.ts.map +1 -0
- package/bin/runtime/server/http/sdk/Router.js +18 -0
- package/bin/runtime/server/http/sdk/Router.js.map +1 -0
- package/bin/runtime/server/http/sdk/SessionRoutes.d.ts +15 -0
- package/bin/runtime/server/http/sdk/SessionRoutes.d.ts.map +1 -0
- package/bin/runtime/server/http/sdk/SessionRoutes.js +190 -0
- package/bin/runtime/server/http/sdk/SessionRoutes.js.map +1 -0
- package/bin/sdk/AgentSdkTypes.d.ts +1 -124
- package/bin/sdk/AgentSdkTypes.d.ts.map +1 -1
- package/bin/sdk/RemoteAgent.d.ts +27 -8
- package/bin/sdk/RemoteAgent.d.ts.map +1 -1
- package/bin/sdk/RemoteAgent.js +266 -38
- package/bin/sdk/RemoteAgent.js.map +1 -1
- package/bin/sdk/Session.d.ts +31 -10
- package/bin/sdk/Session.d.ts.map +1 -1
- package/bin/sdk/Session.js +181 -73
- package/bin/sdk/Session.js.map +1 -1
- package/bin/sdk/SessionEventMapper.d.ts +32 -0
- package/bin/sdk/SessionEventMapper.d.ts.map +1 -0
- package/bin/sdk/{StreamEvents.js → SessionEventMapper.js} +43 -28
- package/bin/sdk/SessionEventMapper.js.map +1 -0
- package/bin/sdk/session/ServicePort.d.ts +51 -4
- package/bin/sdk/session/ServicePort.d.ts.map +1 -1
- package/bin/sdk/session/ServicePort.js +17 -10
- package/bin/sdk/session/ServicePort.js.map +1 -1
- package/bin/sdk/session/runtime/SessionEventHub.d.ts +24 -0
- package/bin/sdk/session/runtime/SessionEventHub.d.ts.map +1 -0
- package/bin/sdk/session/runtime/SessionEventHub.js +39 -0
- package/bin/sdk/session/runtime/SessionEventHub.js.map +1 -0
- package/bin/sdk/session/runtime/SessionPromptRuntime.d.ts +68 -0
- package/bin/sdk/session/runtime/SessionPromptRuntime.d.ts.map +1 -0
- package/bin/sdk/session/runtime/SessionPromptRuntime.js +170 -0
- package/bin/sdk/session/runtime/SessionPromptRuntime.js.map +1 -0
- package/bin/service/builtins/chat/runtime/ChatQueueWorker.js +1 -1
- package/bin/service/builtins/chat/runtime/ChatQueueWorker.js.map +1 -1
- package/bin/service/builtins/chat/runtime/ChatSession.d.ts +1 -1
- package/bin/service/builtins/chat/runtime/ChatSession.js +1 -1
- package/bin/service/builtins/contact/runtime/ChatRuntime.js +1 -1
- package/bin/service/builtins/contact/runtime/ChatRuntime.js.map +1 -1
- package/bin/types/sdk/AgentSessionEvent.d.ts +160 -0
- package/bin/types/sdk/AgentSessionEvent.d.ts.map +1 -0
- package/bin/types/sdk/AgentSessionEvent.js +9 -0
- package/bin/types/sdk/AgentSessionEvent.js.map +1 -0
- package/bin/types/sdk/AgentSessionPrompt.d.ts +21 -0
- package/bin/types/sdk/AgentSessionPrompt.d.ts.map +1 -0
- package/bin/types/sdk/AgentSessionPrompt.js +9 -0
- package/bin/types/sdk/AgentSessionPrompt.js.map +1 -0
- package/bin/types/sdk/AgentSessionTurn.d.ts +59 -0
- package/bin/types/sdk/AgentSessionTurn.d.ts.map +1 -0
- package/bin/types/sdk/AgentSessionTurn.js +10 -0
- package/bin/types/sdk/AgentSessionTurn.js.map +1 -0
- package/bin/types/sdk/InternalUiChunkEvent.d.ts +100 -0
- package/bin/types/sdk/InternalUiChunkEvent.d.ts.map +1 -0
- package/bin/types/sdk/InternalUiChunkEvent.js +9 -0
- package/bin/types/sdk/InternalUiChunkEvent.js.map +1 -0
- package/package.json +2 -1
- package/scripts/session-prompt-runtime.test.mjs +167 -0
- package/src/core/AgentContextTypes.ts +24 -2
- package/src/index.ts +11 -8
- package/src/runtime/server/http/Server.ts +5 -1
- package/src/runtime/server/http/control/ExecuteBySession.ts +1 -1
- package/src/runtime/server/http/control/SessionRoutes.ts +0 -27
- package/src/runtime/server/http/execute/execute.ts +1 -1
- package/src/runtime/server/http/sdk/Router.ts +22 -0
- package/src/runtime/server/http/sdk/SessionRoutes.ts +232 -0
- package/src/sdk/AgentSdkTypes.ts +1 -137
- package/src/sdk/RemoteAgent.ts +368 -49
- package/src/sdk/Session.ts +235 -86
- package/src/sdk/{StreamEvents.ts → SessionEventMapper.ts} +50 -40
- package/src/sdk/session/ServicePort.ts +74 -12
- package/src/sdk/session/runtime/SessionEventHub.ts +47 -0
- package/src/sdk/session/runtime/SessionPromptRuntime.ts +269 -0
- package/src/service/builtins/chat/runtime/ChatQueueWorker.ts +1 -1
- package/src/service/builtins/chat/runtime/ChatSession.ts +1 -1
- package/src/service/builtins/contact/runtime/ChatRuntime.ts +1 -1
- package/src/session/README.md +8 -7
- package/src/types/sdk/AgentSessionEvent.ts +195 -0
- package/src/types/sdk/AgentSessionPrompt.ts +21 -0
- package/src/types/sdk/AgentSessionTurn.ts +65 -0
- package/src/types/sdk/InternalUiChunkEvent.ts +108 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/bin/runtime/server/http/control/StreamBySession.d.ts +0 -22
- package/bin/runtime/server/http/control/StreamBySession.d.ts.map +0 -1
- package/bin/runtime/server/http/control/StreamBySession.js +0 -135
- package/bin/runtime/server/http/control/StreamBySession.js.map +0 -1
- package/bin/sdk/AsyncQueue.d.ts +0 -26
- package/bin/sdk/AsyncQueue.d.ts.map +0 -1
- package/bin/sdk/AsyncQueue.js +0 -75
- package/bin/sdk/AsyncQueue.js.map +0 -1
- package/bin/sdk/StreamEvents.d.ts +0 -36
- package/bin/sdk/StreamEvents.d.ts.map +0 -1
- package/bin/sdk/StreamEvents.js.map +0 -1
- package/src/runtime/server/http/control/StreamBySession.ts +0 -198
- package/src/sdk/AsyncQueue.ts +0 -92
package/src/sdk/RemoteAgent.ts
CHANGED
|
@@ -3,19 +3,76 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 关键点(中文)
|
|
5
5
|
* - 面向已通过 `agent.start({ http: { ... } })` 暴露出来的远程/本地 HTTP 服务。
|
|
6
|
-
* - 与本地 `
|
|
6
|
+
* - 与本地 `Session` 保持同一套 Session actor 使用面:`prompt()` + `subscribe()`。
|
|
7
|
+
* - `RemoteAgent` 只负责 transport 与本地 turn handle 合成,不重复实现第二套会话编排器。
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
10
|
import type {
|
|
10
11
|
AgentSessionMetadata,
|
|
11
|
-
AgentSessionRunInput,
|
|
12
|
-
AgentSessionRunResult,
|
|
13
12
|
AgentSessionSetInput,
|
|
14
|
-
AgentSessionStreamEvent,
|
|
15
13
|
AgentSessionSystemSnapshot,
|
|
16
14
|
RemoteAgentOptions,
|
|
17
15
|
} from "@/sdk/AgentSdkTypes.js";
|
|
18
16
|
import type { SessionMessageV1 } from "@/session/types/SessionMessages.js";
|
|
17
|
+
import type {
|
|
18
|
+
AgentSessionEvent,
|
|
19
|
+
AgentSessionSubscriber,
|
|
20
|
+
AgentSessionUnsubscribe,
|
|
21
|
+
} from "@/types/sdk/AgentSessionEvent.js";
|
|
22
|
+
import type { AgentSessionPromptInput } from "@/types/sdk/AgentSessionPrompt.js";
|
|
23
|
+
import type {
|
|
24
|
+
AgentSessionTurnHandle,
|
|
25
|
+
AgentSessionTurnResult,
|
|
26
|
+
} from "@/types/sdk/AgentSessionTurn.js";
|
|
27
|
+
import { SessionEventHub } from "@/sdk/session/runtime/SessionEventHub.js";
|
|
28
|
+
|
|
29
|
+
type Deferred<T> = {
|
|
30
|
+
/**
|
|
31
|
+
* 当前延迟 Promise。
|
|
32
|
+
*/
|
|
33
|
+
promise: Promise<T>;
|
|
34
|
+
/**
|
|
35
|
+
* 兑现 Promise。
|
|
36
|
+
*/
|
|
37
|
+
resolve: (value: T) => void;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
type RemoteTurnLifecycle = {
|
|
41
|
+
/**
|
|
42
|
+
* 当前 turnId。
|
|
43
|
+
*/
|
|
44
|
+
turnId: string;
|
|
45
|
+
/**
|
|
46
|
+
* 当前 turn 的最终结果快照。
|
|
47
|
+
*/
|
|
48
|
+
result: AgentSessionTurnResult | null;
|
|
49
|
+
/**
|
|
50
|
+
* 当前 turn 完成 Promise 控制器。
|
|
51
|
+
*/
|
|
52
|
+
deferredFinished: Deferred<AgentSessionTurnResult>;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
type EventConnectionReady = {
|
|
56
|
+
/**
|
|
57
|
+
* 当前 ready promise 是否已经完成。
|
|
58
|
+
*/
|
|
59
|
+
settled: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* 标记事件连接已经在服务端完成订阅。
|
|
62
|
+
*/
|
|
63
|
+
resolve: () => void;
|
|
64
|
+
/**
|
|
65
|
+
* 标记事件连接在 ready 前失败。
|
|
66
|
+
*/
|
|
67
|
+
reject: (error: unknown) => void;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
type SdkEventsReadyFrame = {
|
|
71
|
+
/**
|
|
72
|
+
* SDK HTTP events 连接内部 ready 标记。
|
|
73
|
+
*/
|
|
74
|
+
type: "sdk-events-ready";
|
|
75
|
+
};
|
|
19
76
|
|
|
20
77
|
/**
|
|
21
78
|
* 远程 Session 客户端。
|
|
@@ -23,6 +80,13 @@ import type { SessionMessageV1 } from "@/session/types/SessionMessages.js";
|
|
|
23
80
|
class RemoteSession {
|
|
24
81
|
readonly id: string;
|
|
25
82
|
private readonly baseUrl: string;
|
|
83
|
+
private readonly eventHub = new SessionEventHub();
|
|
84
|
+
private readonly turnsById = new Map<string, RemoteTurnLifecycle>();
|
|
85
|
+
private readonly completedTurnIds: string[] = [];
|
|
86
|
+
private eventPumpConnectPromise: Promise<void> | null = null;
|
|
87
|
+
private eventPumpAbortController: AbortController | null = null;
|
|
88
|
+
private eventPumpRunning = false;
|
|
89
|
+
private eventSubscriberCount = 0;
|
|
26
90
|
|
|
27
91
|
constructor(baseUrl: string, sessionId: string) {
|
|
28
92
|
this.baseUrl = baseUrl;
|
|
@@ -39,25 +103,60 @@ class RemoteSession {
|
|
|
39
103
|
}
|
|
40
104
|
|
|
41
105
|
/**
|
|
42
|
-
*
|
|
106
|
+
* 向当前远程 session 追加一条新的 prompt。
|
|
43
107
|
*/
|
|
44
|
-
async
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
108
|
+
async prompt(input: AgentSessionPromptInput): Promise<AgentSessionTurnHandle> {
|
|
109
|
+
const query = String(input.query || "").trim();
|
|
110
|
+
if (!query) {
|
|
111
|
+
throw new Error("remote session.prompt requires a non-empty query");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
await this.ensureEventPump();
|
|
115
|
+
|
|
116
|
+
const response = await fetch(
|
|
117
|
+
`${this.baseUrl}/api/sdk/sessions/${encodeURIComponent(this.id)}/prompt`,
|
|
118
|
+
{
|
|
119
|
+
method: "POST",
|
|
120
|
+
headers: {
|
|
121
|
+
"Content-Type": "application/json",
|
|
122
|
+
},
|
|
123
|
+
body: JSON.stringify({
|
|
124
|
+
query,
|
|
125
|
+
}),
|
|
49
126
|
},
|
|
50
|
-
|
|
51
|
-
});
|
|
127
|
+
);
|
|
52
128
|
const payload = (await response.json()) as {
|
|
53
129
|
success?: boolean;
|
|
54
130
|
error?: string;
|
|
55
|
-
|
|
131
|
+
turn?: {
|
|
132
|
+
id?: string;
|
|
133
|
+
};
|
|
56
134
|
};
|
|
57
|
-
if (!response.ok || !payload.success || !payload.
|
|
58
|
-
throw new Error(String(payload.error || "Remote session
|
|
135
|
+
if (!response.ok || !payload.success || !payload.turn?.id) {
|
|
136
|
+
throw new Error(String(payload.error || "Remote session prompt failed"));
|
|
59
137
|
}
|
|
60
|
-
|
|
138
|
+
|
|
139
|
+
const lifecycle = this.ensureTurnLifecycle(payload.turn.id);
|
|
140
|
+
return createTurnHandle(lifecycle);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* 订阅当前远程 session 的 future 事件。
|
|
145
|
+
*/
|
|
146
|
+
subscribe(subscriber: AgentSessionSubscriber): AgentSessionUnsubscribe {
|
|
147
|
+
this.eventSubscriberCount += 1;
|
|
148
|
+
void this.ensureEventPump().catch((error) => {
|
|
149
|
+
this.eventHub.publish({
|
|
150
|
+
type: "error",
|
|
151
|
+
message: error instanceof Error ? error.message : String(error),
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
const unsubscribe = this.eventHub.subscribe(subscriber);
|
|
155
|
+
return () => {
|
|
156
|
+
unsubscribe();
|
|
157
|
+
this.eventSubscriberCount = Math.max(0, this.eventSubscriberCount - 1);
|
|
158
|
+
this.maybeStopEventPump();
|
|
159
|
+
};
|
|
61
160
|
}
|
|
62
161
|
|
|
63
162
|
/**
|
|
@@ -128,48 +227,187 @@ class RemoteSession {
|
|
|
128
227
|
return new RemoteSession(this.baseUrl, payload.session.sessionId);
|
|
129
228
|
}
|
|
130
229
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
230
|
+
private async ensureEventPump(): Promise<void> {
|
|
231
|
+
if (this.eventPumpConnectPromise) {
|
|
232
|
+
await this.eventPumpConnectPromise;
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (this.eventPumpRunning) return;
|
|
236
|
+
|
|
237
|
+
this.eventPumpConnectPromise = (async () => {
|
|
238
|
+
const abortController = new AbortController();
|
|
239
|
+
this.eventPumpAbortController = abortController;
|
|
240
|
+
const response = await fetch(
|
|
241
|
+
`${this.baseUrl}/api/sdk/sessions/${encodeURIComponent(this.id)}/events`,
|
|
242
|
+
{
|
|
243
|
+
signal: abortController.signal,
|
|
143
244
|
},
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
245
|
+
);
|
|
246
|
+
if (!response.ok || !response.body) {
|
|
247
|
+
const text = await response.text().catch(() => "");
|
|
248
|
+
throw new Error(
|
|
249
|
+
text || `Remote session events failed (${response.status})`,
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
this.eventPumpRunning = true;
|
|
253
|
+
const ready = createEventConnectionReady();
|
|
254
|
+
void this.consumeEventConnection(response.body, abortController, ready)
|
|
255
|
+
.finally(() => {
|
|
256
|
+
const wasAborted = abortController.signal.aborted;
|
|
257
|
+
this.eventPumpRunning = false;
|
|
258
|
+
if (this.eventPumpAbortController === abortController) {
|
|
259
|
+
this.eventPumpAbortController = null;
|
|
260
|
+
}
|
|
261
|
+
if (!wasAborted) {
|
|
262
|
+
this.failPendingTurns("Remote session events connection closed");
|
|
263
|
+
this.eventHub.publish({
|
|
264
|
+
type: "error",
|
|
265
|
+
message: "Remote session events connection closed",
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
await ready.promise;
|
|
270
|
+
})();
|
|
271
|
+
|
|
272
|
+
try {
|
|
273
|
+
await this.eventPumpConnectPromise;
|
|
274
|
+
} catch (error) {
|
|
275
|
+
this.eventPumpAbortController = null;
|
|
276
|
+
throw error;
|
|
277
|
+
} finally {
|
|
278
|
+
this.eventPumpConnectPromise = null;
|
|
150
279
|
}
|
|
280
|
+
}
|
|
151
281
|
|
|
282
|
+
private async consumeEventConnection(
|
|
283
|
+
body: ReadableStream<Uint8Array>,
|
|
284
|
+
abortController: AbortController,
|
|
285
|
+
ready: EventConnectionReady,
|
|
286
|
+
): Promise<void> {
|
|
152
287
|
const decoder = new TextDecoder();
|
|
153
|
-
const reader =
|
|
288
|
+
const reader = body.getReader();
|
|
154
289
|
let buffered = "";
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
while (true) {
|
|
293
|
+
const { done, value } = await reader.read();
|
|
294
|
+
if (done) break;
|
|
295
|
+
buffered += decoder.decode(value, { stream: true });
|
|
296
|
+
let newlineIndex = buffered.indexOf("\n");
|
|
297
|
+
while (newlineIndex >= 0) {
|
|
298
|
+
const line = buffered.slice(0, newlineIndex).trim();
|
|
299
|
+
buffered = buffered.slice(newlineIndex + 1);
|
|
300
|
+
if (line) {
|
|
301
|
+
this.handleEventLine(JSON.parse(line) as unknown, ready);
|
|
302
|
+
}
|
|
303
|
+
newlineIndex = buffered.indexOf("\n");
|
|
165
304
|
}
|
|
166
|
-
|
|
305
|
+
}
|
|
306
|
+
const tail = buffered.trim();
|
|
307
|
+
if (tail) {
|
|
308
|
+
this.handleEventLine(JSON.parse(tail) as unknown, ready);
|
|
309
|
+
}
|
|
310
|
+
rejectEventConnectionReady(
|
|
311
|
+
ready,
|
|
312
|
+
"Remote session events connection closed before ready",
|
|
313
|
+
);
|
|
314
|
+
} catch (error) {
|
|
315
|
+
if (!abortController.signal.aborted) {
|
|
316
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
317
|
+
rejectEventConnectionReady(ready, message);
|
|
318
|
+
this.failPendingTurns(message);
|
|
319
|
+
this.eventHub.publish({
|
|
320
|
+
type: "error",
|
|
321
|
+
message,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
} finally {
|
|
325
|
+
try {
|
|
326
|
+
reader.releaseLock();
|
|
327
|
+
} catch {
|
|
328
|
+
// ignore
|
|
167
329
|
}
|
|
168
330
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
private handleEventLine(
|
|
334
|
+
value: unknown,
|
|
335
|
+
ready: EventConnectionReady,
|
|
336
|
+
): void {
|
|
337
|
+
if (isSdkEventsReadyFrame(value)) {
|
|
338
|
+
resolveEventConnectionReady(ready);
|
|
339
|
+
return;
|
|
172
340
|
}
|
|
341
|
+
this.handleEvent(value as AgentSessionEvent);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
private handleEvent(event: AgentSessionEvent): void {
|
|
345
|
+
const turnId = extractTurnId(event);
|
|
346
|
+
if (turnId) {
|
|
347
|
+
this.ensureTurnLifecycle(turnId);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (event.type === "turn-finish") {
|
|
351
|
+
const lifecycle = this.ensureTurnLifecycle(event.turnId);
|
|
352
|
+
const result: AgentSessionTurnResult = {
|
|
353
|
+
turnId: event.turnId,
|
|
354
|
+
text: event.text,
|
|
355
|
+
success: event.success,
|
|
356
|
+
...(event.error ? { error: event.error } : {}),
|
|
357
|
+
};
|
|
358
|
+
lifecycle.result = result;
|
|
359
|
+
lifecycle.deferredFinished.resolve(result);
|
|
360
|
+
this.rememberCompletedTurn(event.turnId);
|
|
361
|
+
this.maybeStopEventPump();
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
this.eventHub.publish(event);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
private ensureTurnLifecycle(turnId: string): RemoteTurnLifecycle {
|
|
368
|
+
const cached = this.turnsById.get(turnId);
|
|
369
|
+
if (cached) return cached;
|
|
370
|
+
const created: RemoteTurnLifecycle = {
|
|
371
|
+
turnId,
|
|
372
|
+
result: null,
|
|
373
|
+
deferredFinished: createDeferred<AgentSessionTurnResult>(),
|
|
374
|
+
};
|
|
375
|
+
this.turnsById.set(turnId, created);
|
|
376
|
+
return created;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
private failPendingTurns(message: string): void {
|
|
380
|
+
for (const lifecycle of this.turnsById.values()) {
|
|
381
|
+
if (lifecycle.result) continue;
|
|
382
|
+
const result: AgentSessionTurnResult = {
|
|
383
|
+
turnId: lifecycle.turnId,
|
|
384
|
+
text: "",
|
|
385
|
+
success: false,
|
|
386
|
+
error: message,
|
|
387
|
+
};
|
|
388
|
+
lifecycle.result = result;
|
|
389
|
+
lifecycle.deferredFinished.resolve(result);
|
|
390
|
+
this.rememberCompletedTurn(lifecycle.turnId);
|
|
391
|
+
}
|
|
392
|
+
this.maybeStopEventPump();
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
private rememberCompletedTurn(turnId: string): void {
|
|
396
|
+
this.completedTurnIds.push(turnId);
|
|
397
|
+
while (this.completedTurnIds.length > 200) {
|
|
398
|
+
const oldestTurnId = this.completedTurnIds.shift();
|
|
399
|
+
if (oldestTurnId) {
|
|
400
|
+
this.turnsById.delete(oldestTurnId);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
private maybeStopEventPump(): void {
|
|
406
|
+
if (this.eventSubscriberCount > 0) return;
|
|
407
|
+
if ([...this.turnsById.values()].some((item) => item.result === null)) return;
|
|
408
|
+
if (!this.eventPumpAbortController) return;
|
|
409
|
+
this.eventPumpAbortController.abort();
|
|
410
|
+
this.eventPumpAbortController = null;
|
|
173
411
|
}
|
|
174
412
|
}
|
|
175
413
|
|
|
@@ -227,3 +465,84 @@ export class RemoteAgent {
|
|
|
227
465
|
return payload.sessions;
|
|
228
466
|
}
|
|
229
467
|
}
|
|
468
|
+
|
|
469
|
+
function createDeferred<T>(): Deferred<T> {
|
|
470
|
+
let resolve!: (value: T) => void;
|
|
471
|
+
const promise = new Promise<T>((innerResolve) => {
|
|
472
|
+
resolve = innerResolve;
|
|
473
|
+
});
|
|
474
|
+
return {
|
|
475
|
+
promise,
|
|
476
|
+
resolve,
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function createEventConnectionReady(): EventConnectionReady & {
|
|
481
|
+
/**
|
|
482
|
+
* 等待事件连接 ready 的 Promise。
|
|
483
|
+
*/
|
|
484
|
+
promise: Promise<void>;
|
|
485
|
+
} {
|
|
486
|
+
let resolve!: () => void;
|
|
487
|
+
let reject!: (error: unknown) => void;
|
|
488
|
+
const promise = new Promise<void>((innerResolve, innerReject) => {
|
|
489
|
+
resolve = innerResolve;
|
|
490
|
+
reject = innerReject;
|
|
491
|
+
});
|
|
492
|
+
return {
|
|
493
|
+
promise,
|
|
494
|
+
settled: false,
|
|
495
|
+
resolve,
|
|
496
|
+
reject,
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function resolveEventConnectionReady(ready: EventConnectionReady): void {
|
|
501
|
+
if (ready.settled) return;
|
|
502
|
+
ready.settled = true;
|
|
503
|
+
ready.resolve();
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function rejectEventConnectionReady(
|
|
507
|
+
ready: EventConnectionReady,
|
|
508
|
+
error: unknown,
|
|
509
|
+
): void {
|
|
510
|
+
if (ready.settled) return;
|
|
511
|
+
ready.settled = true;
|
|
512
|
+
ready.reject(error);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function isSdkEventsReadyFrame(value: unknown): value is SdkEventsReadyFrame {
|
|
516
|
+
return (
|
|
517
|
+
typeof value === "object" &&
|
|
518
|
+
value !== null &&
|
|
519
|
+
"type" in value &&
|
|
520
|
+
(value as { type?: unknown }).type === "sdk-events-ready"
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function createTurnHandle(
|
|
525
|
+
lifecycle: RemoteTurnLifecycle,
|
|
526
|
+
): AgentSessionTurnHandle {
|
|
527
|
+
return {
|
|
528
|
+
id: lifecycle.turnId,
|
|
529
|
+
get result() {
|
|
530
|
+
return lifecycle.result;
|
|
531
|
+
},
|
|
532
|
+
finished: lifecycle.deferredFinished.promise,
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function extractTurnId(event: AgentSessionEvent): string | null {
|
|
537
|
+
switch (event.type) {
|
|
538
|
+
case "turn-start":
|
|
539
|
+
case "text-delta":
|
|
540
|
+
case "reasoning-delta":
|
|
541
|
+
case "tool-call":
|
|
542
|
+
case "tool-result":
|
|
543
|
+
case "turn-finish":
|
|
544
|
+
return event.turnId;
|
|
545
|
+
default:
|
|
546
|
+
return null;
|
|
547
|
+
}
|
|
548
|
+
}
|