@downcity/agent 1.1.22 → 1.1.23

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 (107) hide show
  1. package/README.md +6 -5
  2. package/bin/core/AgentContextTypes.d.ts +21 -2
  3. package/bin/core/AgentContextTypes.d.ts.map +1 -1
  4. package/bin/index.d.ts +5 -1
  5. package/bin/index.d.ts.map +1 -1
  6. package/bin/index.js.map +1 -1
  7. package/bin/runtime/server/http/Server.d.ts +1 -1
  8. package/bin/runtime/server/http/Server.d.ts.map +1 -1
  9. package/bin/runtime/server/http/Server.js +4 -0
  10. package/bin/runtime/server/http/Server.js.map +1 -1
  11. package/bin/runtime/server/http/control/ExecuteBySession.js +1 -1
  12. package/bin/runtime/server/http/control/ExecuteBySession.js.map +1 -1
  13. package/bin/runtime/server/http/control/StreamBySession.d.ts +3 -3
  14. package/bin/runtime/server/http/control/StreamBySession.js +6 -6
  15. package/bin/runtime/server/http/control/StreamBySession.js.map +1 -1
  16. package/bin/runtime/server/http/execute/execute.js +1 -1
  17. package/bin/runtime/server/http/execute/execute.js.map +1 -1
  18. package/bin/runtime/server/http/sdk/Router.d.ts +14 -0
  19. package/bin/runtime/server/http/sdk/Router.d.ts.map +1 -0
  20. package/bin/runtime/server/http/sdk/Router.js +18 -0
  21. package/bin/runtime/server/http/sdk/Router.js.map +1 -0
  22. package/bin/runtime/server/http/sdk/SessionRoutes.d.ts +15 -0
  23. package/bin/runtime/server/http/sdk/SessionRoutes.d.ts.map +1 -0
  24. package/bin/runtime/server/http/sdk/SessionRoutes.js +190 -0
  25. package/bin/runtime/server/http/sdk/SessionRoutes.js.map +1 -0
  26. package/bin/sdk/AgentSdkTypes.d.ts +1 -124
  27. package/bin/sdk/AgentSdkTypes.d.ts.map +1 -1
  28. package/bin/sdk/RemoteAgent.d.ts +27 -8
  29. package/bin/sdk/RemoteAgent.d.ts.map +1 -1
  30. package/bin/sdk/RemoteAgent.js +266 -38
  31. package/bin/sdk/RemoteAgent.js.map +1 -1
  32. package/bin/sdk/Session.d.ts +31 -10
  33. package/bin/sdk/Session.d.ts.map +1 -1
  34. package/bin/sdk/Session.js +181 -73
  35. package/bin/sdk/Session.js.map +1 -1
  36. package/bin/sdk/SessionEventMapper.d.ts +32 -0
  37. package/bin/sdk/SessionEventMapper.d.ts.map +1 -0
  38. package/bin/sdk/{StreamEvents.js → SessionEventMapper.js} +43 -28
  39. package/bin/sdk/SessionEventMapper.js.map +1 -0
  40. package/bin/sdk/session/ServicePort.d.ts +51 -4
  41. package/bin/sdk/session/ServicePort.d.ts.map +1 -1
  42. package/bin/sdk/session/ServicePort.js +17 -10
  43. package/bin/sdk/session/ServicePort.js.map +1 -1
  44. package/bin/sdk/session/runtime/SessionEventHub.d.ts +24 -0
  45. package/bin/sdk/session/runtime/SessionEventHub.d.ts.map +1 -0
  46. package/bin/sdk/session/runtime/SessionEventHub.js +39 -0
  47. package/bin/sdk/session/runtime/SessionEventHub.js.map +1 -0
  48. package/bin/sdk/session/runtime/SessionPromptRuntime.d.ts +68 -0
  49. package/bin/sdk/session/runtime/SessionPromptRuntime.d.ts.map +1 -0
  50. package/bin/sdk/session/runtime/SessionPromptRuntime.js +170 -0
  51. package/bin/sdk/session/runtime/SessionPromptRuntime.js.map +1 -0
  52. package/bin/service/builtins/chat/runtime/ChatQueueWorker.js +1 -1
  53. package/bin/service/builtins/chat/runtime/ChatQueueWorker.js.map +1 -1
  54. package/bin/service/builtins/chat/runtime/ChatSession.d.ts +1 -1
  55. package/bin/service/builtins/chat/runtime/ChatSession.js +1 -1
  56. package/bin/service/builtins/contact/runtime/ChatRuntime.js +1 -1
  57. package/bin/service/builtins/contact/runtime/ChatRuntime.js.map +1 -1
  58. package/bin/types/sdk/AgentSessionEvent.d.ts +160 -0
  59. package/bin/types/sdk/AgentSessionEvent.d.ts.map +1 -0
  60. package/bin/types/sdk/AgentSessionEvent.js +9 -0
  61. package/bin/types/sdk/AgentSessionEvent.js.map +1 -0
  62. package/bin/types/sdk/AgentSessionPrompt.d.ts +21 -0
  63. package/bin/types/sdk/AgentSessionPrompt.d.ts.map +1 -0
  64. package/bin/types/sdk/AgentSessionPrompt.js +9 -0
  65. package/bin/types/sdk/AgentSessionPrompt.js.map +1 -0
  66. package/bin/types/sdk/AgentSessionTurn.d.ts +59 -0
  67. package/bin/types/sdk/AgentSessionTurn.d.ts.map +1 -0
  68. package/bin/types/sdk/AgentSessionTurn.js +10 -0
  69. package/bin/types/sdk/AgentSessionTurn.js.map +1 -0
  70. package/bin/types/sdk/AgentUiChunkEvent.d.ts +100 -0
  71. package/bin/types/sdk/AgentUiChunkEvent.d.ts.map +1 -0
  72. package/bin/types/sdk/AgentUiChunkEvent.js +9 -0
  73. package/bin/types/sdk/AgentUiChunkEvent.js.map +1 -0
  74. package/package.json +2 -1
  75. package/scripts/session-prompt-runtime.test.mjs +167 -0
  76. package/src/core/AgentContextTypes.ts +24 -2
  77. package/src/index.ts +11 -3
  78. package/src/runtime/server/http/Server.ts +5 -1
  79. package/src/runtime/server/http/control/ExecuteBySession.ts +1 -1
  80. package/src/runtime/server/http/control/StreamBySession.ts +11 -11
  81. package/src/runtime/server/http/execute/execute.ts +1 -1
  82. package/src/runtime/server/http/sdk/Router.ts +22 -0
  83. package/src/runtime/server/http/sdk/SessionRoutes.ts +232 -0
  84. package/src/sdk/AgentSdkTypes.ts +1 -137
  85. package/src/sdk/RemoteAgent.ts +368 -49
  86. package/src/sdk/Session.ts +235 -86
  87. package/src/sdk/{StreamEvents.ts → SessionEventMapper.ts} +50 -40
  88. package/src/sdk/session/ServicePort.ts +74 -12
  89. package/src/sdk/session/runtime/SessionEventHub.ts +47 -0
  90. package/src/sdk/session/runtime/SessionPromptRuntime.ts +269 -0
  91. package/src/service/builtins/chat/runtime/ChatQueueWorker.ts +1 -1
  92. package/src/service/builtins/chat/runtime/ChatSession.ts +1 -1
  93. package/src/service/builtins/contact/runtime/ChatRuntime.ts +1 -1
  94. package/src/session/README.md +8 -7
  95. package/src/types/sdk/AgentSessionEvent.ts +195 -0
  96. package/src/types/sdk/AgentSessionPrompt.ts +21 -0
  97. package/src/types/sdk/AgentSessionTurn.ts +65 -0
  98. package/src/types/sdk/AgentUiChunkEvent.ts +108 -0
  99. package/tsconfig.tsbuildinfo +1 -1
  100. package/bin/sdk/AsyncQueue.d.ts +0 -26
  101. package/bin/sdk/AsyncQueue.d.ts.map +0 -1
  102. package/bin/sdk/AsyncQueue.js +0 -75
  103. package/bin/sdk/AsyncQueue.js.map +0 -1
  104. package/bin/sdk/StreamEvents.d.ts +0 -36
  105. package/bin/sdk/StreamEvents.d.ts.map +0 -1
  106. package/bin/sdk/StreamEvents.js.map +0 -1
  107. package/src/sdk/AsyncQueue.ts +0 -92
@@ -3,12 +3,12 @@
3
3
  *
4
4
  * 关键点(中文)
5
5
  * - 面向 `new Agent(...)` 的本地会话使用场景。
6
- * - 统一收口消息落盘、session 级模型配置、run/stream/fork 等高层 API。
6
+ * - 统一收口消息落盘、session 级模型配置、prompt/subscribe/fork 等高层 API。
7
7
  * - 内部继续复用 `Executor` / `JsonlSessionHistoryStore` / Composer 体系。
8
8
  */
9
9
 
10
10
  import { nanoid } from "nanoid";
11
- import type { LanguageModel, Tool } from "ai";
11
+ import type { Tool } from "ai";
12
12
  import { Executor } from "@session/Executor.js";
13
13
  import { JsonlSessionHistoryComposer } from "@session/composer/history/jsonl/JsonlSessionHistoryComposer.js";
14
14
  import { JsonlSessionHistoryStore } from "@/session/store/history/jsonl/JsonlSessionHistoryStore.js";
@@ -17,10 +17,7 @@ import type {
17
17
  AgentSessionConfigSnapshot,
18
18
  AgentSessionForkInput,
19
19
  AgentSessionMetadata,
20
- AgentSessionRunInput,
21
- AgentSessionRunResult,
22
20
  AgentSessionSetInput,
23
- AgentSessionStreamEvent,
24
21
  AgentSessionSystemBlock,
25
22
  AgentSessionSystemSnapshot,
26
23
  } from "@/sdk/AgentSdkTypes.js";
@@ -40,14 +37,29 @@ import {
40
37
  getSdkAgentSessionArchiveDirPath,
41
38
  getSdkAgentSessionDirPath,
42
39
  } from "@/sdk/session/index.js";
43
- import { AsyncQueue } from "@/sdk/AsyncQueue.js";
44
40
  import type { SessionPort } from "@/core/AgentContextTypes.js";
45
- import { pushUiMessageChunkAsSdkEvent } from "@/sdk/StreamEvents.js";
41
+ import {
42
+ mapAgentEventToSessionEvent,
43
+ mapUiMessageChunkToAgentEvent,
44
+ } from "@/sdk/SessionEventMapper.js";
46
45
  import {
47
46
  persistSdkAssistantResult,
48
47
  touchSessionMetadata,
49
48
  } from "@/sdk/session/index.js";
50
49
  import { createSessionServicePort } from "@/sdk/session/index.js";
50
+ import type {
51
+ AgentSessionSubscriber,
52
+ AgentSessionUnsubscribe,
53
+ } from "@/types/sdk/AgentSessionEvent.js";
54
+ import type { AgentSessionPromptInput } from "@/types/sdk/AgentSessionPrompt.js";
55
+ import type { AgentSessionTurnHandle } from "@/types/sdk/AgentSessionTurn.js";
56
+ import type { SessionUserMessageV1 } from "@/session/types/SessionMessages.js";
57
+ import { SessionEventHub } from "@/sdk/session/runtime/SessionEventHub.js";
58
+ import { SessionPromptRuntime } from "@/sdk/session/runtime/SessionPromptRuntime.js";
59
+ import type {
60
+ SessionAssistantStepCallback,
61
+ SessionUiMessageChunkCallback,
62
+ } from "@/session/types/SessionRun.js";
51
63
 
52
64
  type SessionOptions = {
53
65
  /**
@@ -120,12 +132,15 @@ export class Session {
120
132
  private readonly historyStore: JsonlSessionHistoryStore;
121
133
  private readonly historyComposer: JsonlSessionHistoryComposer;
122
134
  private readonly executor: Executor;
135
+ private readonly eventHub = new SessionEventHub();
136
+ private readonly promptRuntime: SessionPromptRuntime;
123
137
  private sessionConfig: AgentSessionConfigSnapshot = {};
124
138
  private createdAt = Date.now();
125
139
  private timezone = resolveSystemTimezone();
126
140
  private initializePromise: Promise<this> | null = null;
127
141
  private ensureConfiguredPromise: Promise<void> | null = null;
128
142
  private servicePort: SessionPort | null = null;
143
+ private directExecutionReserved = false;
129
144
 
130
145
  constructor(options: SessionOptions) {
131
146
  this.id = String(options.sessionId || "").trim();
@@ -189,6 +204,22 @@ export class Session {
189
204
  }),
190
205
  getTools: () => this.tools,
191
206
  });
207
+ this.promptRuntime = new SessionPromptRuntime({
208
+ sessionId: this.id,
209
+ publish: (event) => {
210
+ this.eventHub.publish(event);
211
+ },
212
+ createAndPersistUserMessage: async (query) => {
213
+ return await this.createAndPersistUserPromptMessage(query);
214
+ },
215
+ executeTurn: async ({ turnId, query, onStepMerge }) => {
216
+ return await this.executePromptTurn({
217
+ turnId,
218
+ query,
219
+ onStepMerge,
220
+ });
221
+ },
222
+ });
192
223
  }
193
224
 
194
225
  /**
@@ -259,6 +290,34 @@ export class Session {
259
290
  });
260
291
  }
261
292
 
293
+ /**
294
+ * 追加一条新的 Session prompt。
295
+ *
296
+ * 关键点(中文)
297
+ * - 这是 Session actor 模型下唯一的输入入口。
298
+ * - 首条输入、运行中补充输入、排到下一轮的输入,调用方式完全一致。
299
+ */
300
+ async prompt(input: AgentSessionPromptInput): Promise<AgentSessionTurnHandle> {
301
+ const query = String(input.query || "").trim();
302
+ if (!query) {
303
+ throw new Error("session.prompt requires a non-empty query");
304
+ }
305
+ await this.ensureRunnable();
306
+ this.assertCanPrompt();
307
+ return await this.promptRuntime.prompt(query);
308
+ }
309
+
310
+ /**
311
+ * 订阅当前 Session 的未来事件。
312
+ *
313
+ * 关键点(中文)
314
+ * - 只广播订阅之后产生的事件。
315
+ * - 不做历史回放;历史仍通过 `history()` 读取。
316
+ */
317
+ subscribe(subscriber: AgentSessionSubscriber): AgentSessionUnsubscribe {
318
+ return this.eventHub.subscribe(subscriber);
319
+ }
320
+
262
321
  /**
263
322
  * 追加一条 user 文本消息。
264
323
  */
@@ -332,7 +391,11 @@ export class Session {
332
391
  * 返回当前 session 是否正在执行。
333
392
  */
334
393
  isExecuting(): boolean {
335
- return this.executor.isExecuting();
394
+ return (
395
+ this.directExecutionReserved ||
396
+ this.promptRuntime.isActive() ||
397
+ this.executor.isExecuting()
398
+ );
336
399
  }
337
400
 
338
401
  /**
@@ -342,83 +405,6 @@ export class Session {
342
405
  this.executor.clearExecutor();
343
406
  }
344
407
 
345
- /**
346
- * 执行一轮非流式请求。
347
- */
348
- async run(input: AgentSessionRunInput): Promise<AgentSessionRunResult> {
349
- const query = String(input.query || "").trim();
350
- if (!query) {
351
- throw new Error("session.run requires a non-empty query");
352
- }
353
- await this.ensureReadyForExecution();
354
- if (!this.sessionConfig.model) {
355
- throw new Error(
356
- `Session "${this.id}" requires a configured model. Pass model to new Agent({ model }), call session.set({ model }) first, or let the host configure the session during creation.`,
357
- );
358
- }
359
- await this.appendUserMessage({ text: query });
360
- const result = await this.executor.run({
361
- query,
362
- });
363
- await this.persistAssistantResult(result.assistantMessage);
364
- return {
365
- success: result.success,
366
- ...(result.error ? { error: result.error } : {}),
367
- text: extractTextFromUiMessage(result.assistantMessage),
368
- assistantMessage: result.assistantMessage,
369
- };
370
- }
371
-
372
- /**
373
- * 执行一轮流式请求。
374
- */
375
- async *stream(
376
- input: AgentSessionRunInput,
377
- ): AsyncIterable<AgentSessionStreamEvent> {
378
- const query = String(input.query || "").trim();
379
- if (!query) {
380
- throw new Error("session.stream requires a non-empty query");
381
- }
382
- await this.ensureReadyForExecution();
383
- if (!this.sessionConfig.model) {
384
- throw new Error(
385
- `Session "${this.id}" requires a configured model. Pass model to new Agent({ model }), call session.set({ model }) first, or let the host configure the session during creation.`,
386
- );
387
- }
388
- const queue = new AsyncQueue<AgentSessionStreamEvent>();
389
- const toolNameByCallId = new Map<string, string>();
390
- await this.appendUserMessage({ text: query });
391
-
392
- const runPromise = (async () => {
393
- try {
394
- const result = await this.executor.run({
395
- query,
396
- onUiMessageChunkCallback: async (chunk) => {
397
- pushUiMessageChunkAsSdkEvent({
398
- queue,
399
- chunk,
400
- toolNameByCallId,
401
- });
402
- },
403
- });
404
- await this.persistAssistantResult(result.assistantMessage);
405
- } catch (error) {
406
- queue.push({
407
- type: "error",
408
- error: error instanceof Error ? error.message : String(error),
409
- });
410
- } finally {
411
- queue.close();
412
- }
413
- })();
414
-
415
- for await (const event of queue) {
416
- yield event;
417
- }
418
-
419
- await runPromise;
420
- }
421
-
422
408
  /**
423
409
  * 从当前 session 创建一个分叉会话。
424
410
  */
@@ -490,7 +476,29 @@ export class Session {
490
476
  if (this.servicePort) return this.servicePort;
491
477
  this.servicePort = createSessionServicePort({
492
478
  sessionId: this.id,
493
- executor: this.executor,
479
+ getExecutor: () => this.executor.getExecutor(),
480
+ executeDirect: async (runParams) => {
481
+ return await this.executeDirect(runParams);
482
+ },
483
+ prompt: async (input) => {
484
+ return await this.prompt(input);
485
+ },
486
+ subscribe: (subscriber) => {
487
+ return this.subscribe(subscriber);
488
+ },
489
+ clearExecutor: () => {
490
+ this.executor.clearExecutor();
491
+ },
492
+ afterSessionUpdatedAsync: async () => {
493
+ await this.executor.afterSessionUpdatedAsync();
494
+ },
495
+ appendUserMessage: async (messageParams) => {
496
+ await this.executor.appendUserMessage(messageParams);
497
+ },
498
+ appendAssistantMessage: async (messageParams) => {
499
+ await this.executor.appendAssistantMessage(messageParams);
500
+ },
501
+ isExecuting: () => this.isExecuting(),
494
502
  historyStore: this.historyStore,
495
503
  ensureReadyForExecution: async () => {
496
504
  await this.ensureReadyForExecution();
@@ -523,6 +531,43 @@ export class Session {
523
531
  }
524
532
  }
525
533
 
534
+ private async ensureRunnable(): Promise<void> {
535
+ await this.ensureReadyForExecution();
536
+ if (!this.sessionConfig.model) {
537
+ throw new Error(
538
+ `Session "${this.id}" requires a configured model. Pass model to new Agent({ model }), call session.set({ model }) first, or let the host configure the session during creation.`,
539
+ );
540
+ }
541
+ }
542
+
543
+ private assertCanPrompt(): void {
544
+ if (!this.directExecutionReserved && !this.executor.isExecuting()) {
545
+ return;
546
+ }
547
+ if (this.promptRuntime.isActive()) {
548
+ return;
549
+ }
550
+ throw new Error(
551
+ "session.prompt cannot attach to an active direct session execution. Use prompt()/subscribe() as the public interactive API, or wait for the current execution to finish.",
552
+ );
553
+ }
554
+
555
+ private beginDirectExecution(): void {
556
+ if (this.promptRuntime.isActive()) {
557
+ throw new Error(
558
+ "Direct session execution cannot start while session.prompt() is active. Keep SDK-facing interactive sessions on prompt()/subscribe(), or wait for the actor turn to finish.",
559
+ );
560
+ }
561
+ if (this.directExecutionReserved || this.executor.isExecuting()) {
562
+ throw new Error("Direct session execution does not support concurrent execution on the same session.");
563
+ }
564
+ this.directExecutionReserved = true;
565
+ }
566
+
567
+ private endDirectExecution(): void {
568
+ this.directExecutionReserved = false;
569
+ }
570
+
526
571
  private async touchMetadata(): Promise<void> {
527
572
  await touchSessionMetadata({
528
573
  projectRoot: this.projectRoot,
@@ -544,4 +589,108 @@ export class Session {
544
589
  assistantMessage,
545
590
  });
546
591
  }
592
+
593
+ private async createAndPersistUserPromptMessage(
594
+ query: string,
595
+ ): Promise<SessionUserMessageV1> {
596
+ const message = this.historyStore.userText({
597
+ text: String(query || "").trim(),
598
+ metadata: {
599
+ sessionId: this.id,
600
+ },
601
+ }) as SessionUserMessageV1;
602
+ await this.executor.appendUserMessage({
603
+ message,
604
+ });
605
+ await this.touchMetadata();
606
+ return message;
607
+ }
608
+
609
+ private async executePromptTurn(input: {
610
+ turnId: string;
611
+ query: string;
612
+ onStepMerge: () => Promise<SessionUserMessageV1[]>;
613
+ }): Promise<{
614
+ text: string;
615
+ success: boolean;
616
+ error?: string;
617
+ }> {
618
+ const toolNameByCallId = new Map<string, string>();
619
+ const result = await this.executor.run({
620
+ query: input.query,
621
+ onStepCallback: input.onStepMerge,
622
+ onUiMessageChunkCallback: async (chunk) => {
623
+ if (chunk.type === "tool-input-start") {
624
+ toolNameByCallId.set(chunk.toolCallId, chunk.toolName);
625
+ return;
626
+ }
627
+ const event = mapUiMessageChunkToAgentEvent(chunk);
628
+ if (!event) return;
629
+ const resolvedEvent =
630
+ (
631
+ event.type === "tool-result" ||
632
+ event.type === "tool-error"
633
+ ) &&
634
+ event.toolName === "unknown"
635
+ ? {
636
+ ...event,
637
+ toolName:
638
+ toolNameByCallId.get(event.toolCallId) || event.toolName,
639
+ }
640
+ : event;
641
+ if (
642
+ resolvedEvent.type === "tool-call" ||
643
+ resolvedEvent.type === "tool-error"
644
+ ) {
645
+ toolNameByCallId.set(
646
+ resolvedEvent.toolCallId,
647
+ resolvedEvent.toolName,
648
+ );
649
+ }
650
+ const sessionEvent = mapAgentEventToSessionEvent({
651
+ event: resolvedEvent,
652
+ turnId: input.turnId,
653
+ });
654
+ if (sessionEvent) {
655
+ this.eventHub.publish(sessionEvent);
656
+ }
657
+ },
658
+ });
659
+ await this.persistAssistantResult(result.assistantMessage);
660
+ return {
661
+ text: extractTextFromUiMessage(result.assistantMessage),
662
+ success: result.success,
663
+ ...(result.error ? { error: result.error } : {}),
664
+ };
665
+ }
666
+
667
+ private async executeDirect(input: {
668
+ query: string;
669
+ onStepCallback?: () => Promise<SessionUserMessageV1[]>;
670
+ onAssistantStepCallback?: SessionAssistantStepCallback;
671
+ onUiMessageChunkCallback?: SessionUiMessageChunkCallback;
672
+ }) {
673
+ const query = String(input.query || "").trim();
674
+ if (!query) {
675
+ throw new Error("Session direct execution requires a non-empty query");
676
+ }
677
+ await this.ensureRunnable();
678
+ this.beginDirectExecution();
679
+ try {
680
+ return await this.executor.run({
681
+ query,
682
+ ...(typeof input.onStepCallback === "function"
683
+ ? { onStepCallback: input.onStepCallback }
684
+ : {}),
685
+ ...(typeof input.onAssistantStepCallback === "function"
686
+ ? { onAssistantStepCallback: input.onAssistantStepCallback }
687
+ : {}),
688
+ ...(typeof input.onUiMessageChunkCallback === "function"
689
+ ? { onUiMessageChunkCallback: input.onUiMessageChunkCallback }
690
+ : {}),
691
+ });
692
+ } finally {
693
+ this.endDirectExecution();
694
+ }
695
+ }
547
696
  }
@@ -1,15 +1,15 @@
1
1
  /**
2
- * SDK Stream 事件映射辅助。
2
+ * Session 事件映射辅助。
3
3
  *
4
4
  * 关键点(中文)
5
- * - 把底层 AI SDK `UIMessageChunk` 归一到 `AgentSessionStreamEvent`。
6
- * - 仅保留 SDK 当前需要的稳定事件,不把所有底层细节直接暴露出去。
5
+ * - 把底层 AI SDK `UIMessageChunk` 归一到内部 `AgentUiChunkEvent`。
6
+ * - 再把内部 chunk 事件转换为 `session.subscribe()` 可见的 Session 事件。
7
7
  */
8
8
 
9
9
  import type { UIMessageChunk } from "ai";
10
10
  import type { JsonValue } from "@/types/common/Json.js";
11
- import type { AgentSessionStreamEvent } from "@/sdk/AgentSdkTypes.js";
12
- import type { AsyncQueue } from "@/sdk/AsyncQueue.js";
11
+ import type { AgentSessionEvent } from "@/types/sdk/AgentSessionEvent.js";
12
+ import type { AgentUiChunkEvent } from "@/types/sdk/AgentUiChunkEvent.js";
13
13
 
14
14
  function toJsonValue(value: unknown): JsonValue {
15
15
  if (value === undefined) return null;
@@ -29,11 +29,11 @@ function toJsonValue(value: unknown): JsonValue {
29
29
  }
30
30
 
31
31
  /**
32
- * 把单个 UI chunk 映射为 SDK stream 事件。
32
+ * 把单个 UI chunk 映射为内部 chunk 事件。
33
33
  */
34
- export function mapUiMessageChunkToSdkEvent(
34
+ export function mapUiMessageChunkToAgentEvent(
35
35
  chunk: UIMessageChunk,
36
- ): AgentSessionStreamEvent | null {
36
+ ): AgentUiChunkEvent | null {
37
37
  switch (chunk.type) {
38
38
  case "text-delta":
39
39
  return {
@@ -88,7 +88,7 @@ export function mapUiMessageChunkToSdkEvent(
88
88
  case "abort":
89
89
  return {
90
90
  type: "error",
91
- error: String(chunk.reason || "stream aborted"),
91
+ error: String(chunk.reason || "execution aborted"),
92
92
  };
93
93
  default:
94
94
  return null;
@@ -96,43 +96,53 @@ export function mapUiMessageChunkToSdkEvent(
96
96
  }
97
97
 
98
98
  /**
99
- * UI chunk 推入 SDK stream 队列。
99
+ * 把内部 chunk 事件映射为 Session actor 事件。
100
100
  *
101
101
  * 关键点(中文)
102
- * - `tool-output-*` chunk 本身不总是携带 toolName。
103
- * - 这里用调用 id 维护一次流式执行内的工具名称映射,避免上层重复关心底层 chunk 细节。
102
+ * - Session 级事件只保留 turn 维度所需的最小字段。
103
+ * - `finish` 与通用 `error` turn 生命周期统一表达,不在这里直接透出。
104
104
  */
105
- export function pushUiMessageChunkAsSdkEvent(params: {
105
+ export function mapAgentEventToSessionEvent(params: {
106
106
  /**
107
- * SDK stream 事件队列。
107
+ * 当前内部 chunk 事件。
108
108
  */
109
- queue: AsyncQueue<AgentSessionStreamEvent>;
109
+ event: AgentUiChunkEvent;
110
110
  /**
111
- * 底层 AI SDK UI chunk。
111
+ * 当前 turn 标识。
112
112
  */
113
- chunk: UIMessageChunk;
114
- /**
115
- * 当前 stream 生命周期内的 toolCallId toolName 映射。
116
- */
117
- toolNameByCallId: Map<string, string>;
118
- }): void {
119
- const { queue, chunk, toolNameByCallId } = params;
120
- if (chunk.type === "tool-input-start") {
121
- toolNameByCallId.set(chunk.toolCallId, chunk.toolName);
122
- return;
123
- }
124
- const event = mapUiMessageChunkToSdkEvent(chunk);
125
- if (!event) return;
126
- if (event.type === "tool-call" || event.type === "tool-error") {
127
- toolNameByCallId.set(event.toolCallId, event.toolName);
128
- }
129
- if (
130
- (event.type === "tool-result" || event.type === "tool-error") &&
131
- event.toolName === "unknown"
132
- ) {
133
- const toolName = toolNameByCallId.get(event.toolCallId);
134
- queue.push(toolName ? { ...event, toolName } : event);
135
- return;
113
+ turnId: string;
114
+ }): AgentSessionEvent | null {
115
+ const { event, turnId } = params;
116
+ switch (event.type) {
117
+ case "text-delta":
118
+ return {
119
+ type: "text-delta",
120
+ turnId,
121
+ text: event.text,
122
+ };
123
+ case "reasoning-delta":
124
+ return {
125
+ type: "reasoning-delta",
126
+ turnId,
127
+ text: event.text,
128
+ };
129
+ case "tool-call":
130
+ return {
131
+ type: "tool-call",
132
+ turnId,
133
+ toolCallId: event.toolCallId,
134
+ toolName: event.toolName,
135
+ args: event.args,
136
+ };
137
+ case "tool-result":
138
+ return {
139
+ type: "tool-result",
140
+ turnId,
141
+ toolCallId: event.toolCallId,
142
+ toolName: event.toolName,
143
+ result: event.result,
144
+ };
145
+ default:
146
+ return null;
136
147
  }
137
- queue.push(event);
138
148
  }
@@ -2,12 +2,24 @@
2
2
  * SDK Session service 端口构造器。
3
3
  *
4
4
  * 关键点(中文)
5
- * - 把 SDK 本地 session 适配成 chat service 依赖的 `SessionPort`。
6
- * - service 侧直接复用底层 `Executor` 协议,避免 SDK `run()` 包装层重复补写消息。
5
+ * - 把 SDK 本地 session 适配成 runtime / service 依赖的 `SessionPort`。
6
+ * - SDK 公开面只保留 `prompt()` / `subscribe()`;内部单轮执行原语仍通过这里暴露给 service/runtime。
7
7
  */
8
8
 
9
9
  import type { SessionPort } from "@/core/AgentContextTypes.js";
10
10
  import type { SessionHistoryStore } from "@/session/store/history/SessionHistoryStore.js";
11
+ import type { SessionRunResult } from "@/session/types/SessionRun.js";
12
+ import type { SessionUserMessageV1 } from "@/session/types/SessionMessages.js";
13
+ import type {
14
+ SessionAssistantStepCallback,
15
+ SessionUiMessageChunkCallback,
16
+ } from "@/session/types/SessionRun.js";
17
+ import type { AgentSessionPromptInput } from "@/types/sdk/AgentSessionPrompt.js";
18
+ import type {
19
+ AgentSessionSubscriber,
20
+ AgentSessionUnsubscribe,
21
+ } from "@/types/sdk/AgentSessionEvent.js";
22
+ import type { AgentSessionTurnHandle } from "@/types/sdk/AgentSessionTurn.js";
11
23
 
12
24
  /**
13
25
  * 构造 SDK SessionPort 的参数。
@@ -18,9 +30,52 @@ export interface CreateSessionServicePortParams {
18
30
  */
19
31
  sessionId: string;
20
32
  /**
21
- * 底层执行编排器。
33
+ * 读取当前 session 底层执行端口。
22
34
  */
23
- executor: Omit<SessionPort, "sessionId" | "getHistoryStore">;
35
+ getExecutor: SessionPort["getExecutor"];
36
+ /**
37
+ * 运行一次内部 direct execution。
38
+ *
39
+ * 关键点(中文)
40
+ * - 这是 runtime / service 侧保留的内部原语。
41
+ * - 它不属于 SDK 用户推荐直接调用的公开模式。
42
+ */
43
+ executeDirect: (params: {
44
+ query: string;
45
+ onStepCallback?: () => Promise<SessionUserMessageV1[]>;
46
+ onAssistantStepCallback?: SessionAssistantStepCallback;
47
+ onUiMessageChunkCallback?: SessionUiMessageChunkCallback;
48
+ }) => Promise<SessionRunResult>;
49
+ /**
50
+ * 追加一条新的 session prompt。
51
+ */
52
+ prompt: (input: AgentSessionPromptInput) => Promise<AgentSessionTurnHandle>;
53
+ /**
54
+ * 订阅当前 session 的 future 事件。
55
+ */
56
+ subscribe: (
57
+ subscriber: AgentSessionSubscriber,
58
+ ) => AgentSessionUnsubscribe;
59
+ /**
60
+ * 清理当前 session executor 状态。
61
+ */
62
+ clearExecutor: () => void;
63
+ /**
64
+ * session 更新后的异步通知回调。
65
+ */
66
+ afterSessionUpdatedAsync: () => Promise<void>;
67
+ /**
68
+ * 追加 user 消息到底层历史。
69
+ */
70
+ appendUserMessage: SessionPort["appendUserMessage"];
71
+ /**
72
+ * 追加 assistant 消息到底层历史。
73
+ */
74
+ appendAssistantMessage: SessionPort["appendAssistantMessage"];
75
+ /**
76
+ * 返回当前 session 是否正在执行。
77
+ */
78
+ isExecuting: () => boolean;
24
79
  /**
25
80
  * 当前 session 历史持久化端口。
26
81
  */
@@ -43,26 +98,33 @@ export function createSessionServicePort(
43
98
  ): SessionPort {
44
99
  return {
45
100
  sessionId: params.sessionId,
46
- getExecutor: () => params.executor.getExecutor(),
101
+ getExecutor: () => params.getExecutor(),
47
102
  getHistoryStore: () => params.historyStore,
48
- run: async (runParams) => {
103
+ execute: async (runParams) => {
104
+ await params.ensureReadyForExecution();
105
+ return await params.executeDirect(runParams);
106
+ },
107
+ prompt: async (input) => {
49
108
  await params.ensureReadyForExecution();
50
- return await params.executor.run(runParams);
109
+ return await params.prompt(input);
110
+ },
111
+ subscribe: (subscriber) => {
112
+ return params.subscribe(subscriber);
51
113
  },
52
114
  clearExecutor: () => {
53
- params.executor.clearExecutor();
115
+ params.clearExecutor();
54
116
  },
55
117
  afterSessionUpdatedAsync: async () => {
56
- await params.executor.afterSessionUpdatedAsync();
118
+ await params.afterSessionUpdatedAsync();
57
119
  },
58
120
  appendUserMessage: async (messageParams) => {
59
- await params.executor.appendUserMessage(messageParams);
121
+ await params.appendUserMessage(messageParams);
60
122
  await params.touchMetadata();
61
123
  },
62
124
  appendAssistantMessage: async (messageParams) => {
63
- await params.executor.appendAssistantMessage(messageParams);
125
+ await params.appendAssistantMessage(messageParams);
64
126
  await params.touchMetadata();
65
127
  },
66
- isExecuting: () => params.executor.isExecuting(),
128
+ isExecuting: () => params.isExecuting(),
67
129
  };
68
130
  }