@downcity/agent 1.1.43 → 1.1.51

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 (171) hide show
  1. package/README.md +6 -8
  2. package/bin/agent/Agent.d.ts +31 -16
  3. package/bin/agent/Agent.d.ts.map +1 -1
  4. package/bin/agent/Agent.js +47 -63
  5. package/bin/agent/Agent.js.map +1 -1
  6. package/bin/agent/RemoteAgent.d.ts +6 -6
  7. package/bin/agent/RemoteAgent.d.ts.map +1 -1
  8. package/bin/agent/RemoteAgent.js +377 -267
  9. package/bin/agent/RemoteAgent.js.map +1 -1
  10. package/bin/config/AgentInitializer.d.ts +4 -4
  11. package/bin/config/AgentInitializer.d.ts.map +1 -1
  12. package/bin/config/AgentInitializer.js +14 -12
  13. package/bin/config/AgentInitializer.js.map +1 -1
  14. package/bin/config/Config.d.ts +9 -30
  15. package/bin/config/Config.d.ts.map +1 -1
  16. package/bin/config/Config.js +23 -70
  17. package/bin/config/Config.js.map +1 -1
  18. package/bin/config/Defaults.d.ts.map +1 -1
  19. package/bin/config/Defaults.js +1 -9
  20. package/bin/config/Defaults.js.map +1 -1
  21. package/bin/config/DowncitySchema.d.ts.map +1 -1
  22. package/bin/config/DowncitySchema.js +3 -111
  23. package/bin/config/DowncitySchema.js.map +1 -1
  24. package/bin/config/ExecutionBinding.d.ts +0 -15
  25. package/bin/config/ExecutionBinding.d.ts.map +1 -1
  26. package/bin/config/ExecutionBinding.js +1 -21
  27. package/bin/config/ExecutionBinding.js.map +1 -1
  28. package/bin/config/Paths.d.ts +4 -43
  29. package/bin/config/Paths.d.ts.map +1 -1
  30. package/bin/config/Paths.js +10 -30
  31. package/bin/config/Paths.js.map +1 -1
  32. package/bin/config/PlatformPaths.d.ts +0 -4
  33. package/bin/config/PlatformPaths.d.ts.map +1 -1
  34. package/bin/config/PlatformPaths.js +5 -1
  35. package/bin/config/PlatformPaths.js.map +1 -1
  36. package/bin/executor/composer/system/default/assets/init/PROFILE.md.d.ts +1 -1
  37. package/bin/executor/composer/system/default/assets/init/PROFILE.md.d.ts.map +1 -1
  38. package/bin/executor/composer/system/default/assets/init/PROFILE.md.js +1 -1
  39. package/bin/executor/composer/system/default/assets/init/PROFILE.md.js.map +1 -1
  40. package/bin/executor/tools/shell/ShellToolFormatting.js +2 -2
  41. package/bin/executor/tools/shell/ShellToolFormatting.js.map +1 -1
  42. package/bin/executor/types/SessionRun.d.ts +1 -1
  43. package/bin/index.d.ts +4 -6
  44. package/bin/index.d.ts.map +1 -1
  45. package/bin/index.js +5 -5
  46. package/bin/index.js.map +1 -1
  47. package/bin/plugin/core/PluginCommandRequest.d.ts +1 -1
  48. package/bin/plugin/core/PluginCommandRequest.js +1 -1
  49. package/bin/plugin/core/PluginLocalExecution.d.ts.map +1 -1
  50. package/bin/plugin/core/PluginLocalExecution.js +4 -8
  51. package/bin/plugin/core/PluginLocalExecution.js.map +1 -1
  52. package/bin/plugin/types/PluginApi.d.ts +1 -1
  53. package/bin/rpc/Client.d.ts +104 -0
  54. package/bin/rpc/Client.d.ts.map +1 -0
  55. package/bin/rpc/Client.js +300 -0
  56. package/bin/rpc/Client.js.map +1 -0
  57. package/bin/rpc/Server.d.ts +41 -0
  58. package/bin/rpc/Server.d.ts.map +1 -0
  59. package/bin/rpc/Server.js +164 -0
  60. package/bin/rpc/Server.js.map +1 -0
  61. package/bin/runtime/host/daemon/Api.d.ts +1 -1
  62. package/bin/runtime/host/daemon/Client.d.ts +1 -1
  63. package/bin/runtime/host/daemon/Client.d.ts.map +1 -1
  64. package/bin/runtime/host/daemon/Client.js +30 -20
  65. package/bin/runtime/host/daemon/Client.js.map +1 -1
  66. package/bin/runtime/server/http/control/OverviewRoutes.js +1 -1
  67. package/bin/runtime/server/http/control/OverviewRoutes.js.map +1 -1
  68. package/bin/session/index.d.ts +1 -1
  69. package/bin/session/index.d.ts.map +1 -1
  70. package/bin/session/index.js +1 -1
  71. package/bin/session/index.js.map +1 -1
  72. package/bin/session/storage/Paths.d.ts +0 -4
  73. package/bin/session/storage/Paths.d.ts.map +1 -1
  74. package/bin/session/storage/Paths.js +0 -6
  75. package/bin/session/storage/Paths.js.map +1 -1
  76. package/bin/types/agent/AgentTypes.d.ts +23 -52
  77. package/bin/types/agent/AgentTypes.d.ts.map +1 -1
  78. package/bin/types/config/AgentProject.d.ts +6 -5
  79. package/bin/types/config/AgentProject.d.ts.map +1 -1
  80. package/bin/types/config/DowncityConfig.d.ts +8 -66
  81. package/bin/types/config/DowncityConfig.d.ts.map +1 -1
  82. package/bin/types/config/LlmConfig.d.ts +1 -1
  83. package/bin/types/config/Start.d.ts +3 -0
  84. package/bin/types/config/Start.d.ts.map +1 -1
  85. package/bin/types/config/Start.js +2 -0
  86. package/bin/types/config/Start.js.map +1 -1
  87. package/bin/types/runtime/host/Store.d.ts +7 -50
  88. package/bin/types/runtime/host/Store.d.ts.map +1 -1
  89. package/bin/types/runtime/platform/Platform.d.ts +6 -6
  90. package/bin/types/runtime/platform/Platform.d.ts.map +1 -1
  91. package/bin/types/runtime/platform/PlatformGateway.d.ts +2 -2
  92. package/bin/types/runtime/platform/PlatformGateway.d.ts.map +1 -1
  93. package/bin/utils/Time.d.ts +0 -1
  94. package/bin/utils/Time.d.ts.map +1 -1
  95. package/bin/utils/Time.js +0 -7
  96. package/bin/utils/Time.js.map +1 -1
  97. package/bin/utils/storage/index.d.ts +0 -1
  98. package/bin/utils/storage/index.d.ts.map +1 -1
  99. package/bin/utils/storage/index.js +0 -6
  100. package/bin/utils/storage/index.js.map +1 -1
  101. package/package.json +4 -6
  102. package/src/agent/Agent.ts +54 -71
  103. package/src/agent/RemoteAgent.ts +515 -345
  104. package/src/config/AgentInitializer.ts +14 -12
  105. package/src/config/Config.ts +28 -85
  106. package/src/config/Defaults.ts +1 -9
  107. package/src/config/DowncitySchema.ts +3 -113
  108. package/src/config/ExecutionBinding.ts +1 -23
  109. package/src/config/Paths.ts +10 -43
  110. package/src/config/PlatformPaths.ts +5 -1
  111. package/src/executor/composer/system/default/assets/init/PROFILE.md.ts +1 -1
  112. package/src/executor/composer/system/default/assets/init/PROFILE.md.ts.txt +1 -2
  113. package/src/executor/tools/shell/ShellToolFormatting.ts +2 -2
  114. package/src/executor/types/SessionRun.ts +1 -1
  115. package/src/index.ts +7 -14
  116. package/src/plugin/core/PluginCommandRequest.ts +1 -1
  117. package/src/plugin/core/PluginLocalExecution.ts +4 -8
  118. package/src/plugin/types/PluginApi.ts +1 -1
  119. package/src/rpc/Client.ts +467 -0
  120. package/src/rpc/Server.ts +302 -0
  121. package/src/runtime/host/daemon/Api.ts +1 -1
  122. package/src/runtime/host/daemon/Client.ts +44 -22
  123. package/src/runtime/server/http/control/OverviewRoutes.ts +1 -1
  124. package/src/session/index.ts +0 -1
  125. package/src/session/storage/Paths.ts +0 -10
  126. package/src/types/agent/AgentTypes.ts +23 -57
  127. package/src/types/config/AgentProject.ts +6 -5
  128. package/src/types/config/DowncityConfig.ts +8 -67
  129. package/src/types/config/LlmConfig.ts +1 -1
  130. package/src/types/config/Start.ts +3 -0
  131. package/src/types/runtime/host/Store.ts +7 -53
  132. package/src/types/runtime/platform/Platform.ts +6 -6
  133. package/src/types/runtime/platform/PlatformGateway.ts +2 -2
  134. package/src/utils/Time.ts +0 -6
  135. package/src/utils/storage/index.ts +0 -7
  136. package/tsconfig.tsbuildinfo +1 -1
  137. package/bin/config/ConfigEnvResolver.d.ts +0 -22
  138. package/bin/config/ConfigEnvResolver.d.ts.map +0 -1
  139. package/bin/config/ConfigEnvResolver.js +0 -41
  140. package/bin/config/ConfigEnvResolver.js.map +0 -1
  141. package/bin/runtime/server/rpc/Server.d.ts +0 -18
  142. package/bin/runtime/server/rpc/Server.d.ts.map +0 -1
  143. package/bin/runtime/server/rpc/Server.js +0 -315
  144. package/bin/runtime/server/rpc/Server.js.map +0 -1
  145. package/bin/runtime/transport/rpc/Client.d.ts +0 -13
  146. package/bin/runtime/transport/rpc/Client.d.ts.map +0 -1
  147. package/bin/runtime/transport/rpc/Client.js +0 -98
  148. package/bin/runtime/transport/rpc/Client.js.map +0 -1
  149. package/bin/runtime/transport/rpc/Paths.d.ts +0 -14
  150. package/bin/runtime/transport/rpc/Paths.d.ts.map +0 -1
  151. package/bin/runtime/transport/rpc/Paths.js +0 -42
  152. package/bin/runtime/transport/rpc/Paths.js.map +0 -1
  153. package/bin/runtime/transport/rpc/Transport.d.ts +0 -21
  154. package/bin/runtime/transport/rpc/Transport.d.ts.map +0 -1
  155. package/bin/runtime/transport/rpc/Transport.js +0 -30
  156. package/bin/runtime/transport/rpc/Transport.js.map +0 -1
  157. package/bin/types/common/ResolvedConfigValue.d.ts +0 -12
  158. package/bin/types/common/ResolvedConfigValue.d.ts.map +0 -1
  159. package/bin/types/common/ResolvedConfigValue.js +0 -2
  160. package/bin/types/common/ResolvedConfigValue.js.map +0 -1
  161. package/bin/types/runtime/rpc/LocalRpc.d.ts +0 -69
  162. package/bin/types/runtime/rpc/LocalRpc.d.ts.map +0 -1
  163. package/bin/types/runtime/rpc/LocalRpc.js +0 -9
  164. package/bin/types/runtime/rpc/LocalRpc.js.map +0 -1
  165. package/src/config/ConfigEnvResolver.ts +0 -52
  166. package/src/runtime/server/rpc/Server.ts +0 -408
  167. package/src/runtime/transport/rpc/Client.ts +0 -113
  168. package/src/runtime/transport/rpc/Paths.ts +0 -50
  169. package/src/runtime/transport/rpc/Transport.ts +0 -43
  170. package/src/types/common/ResolvedConfigValue.ts +0 -16
  171. package/src/types/runtime/rpc/LocalRpc.ts +0 -72
package/src/index.ts CHANGED
@@ -17,9 +17,8 @@ export type {
17
17
  AgentCreateSessionInput,
18
18
  AgentListSessionsInput,
19
19
  AgentOptions,
20
- AgentHttpBinding,
21
- AgentHttpStartOptions,
22
20
  AgentRpcBinding,
21
+ AgentRpcStartOptions,
23
22
  AgentStartOptions,
24
23
  AgentStartResult,
25
24
  AgentStopResult,
@@ -103,10 +102,11 @@ export {
103
102
  registerPluginActionCommandsForCli,
104
103
  } from "./plugin/core/PluginCommand.js";
105
104
 
106
- // Agent server 与 transport 集成
105
+ // Agent RPC 集成
106
+ export { startRpcServer } from "./rpc/Server.js";
107
+
108
+ // 宿主 HTTP gateway 集成
107
109
  export { startServer } from "./runtime/server/http/Server.js";
108
- export { startLocalRpcServer } from "./runtime/server/rpc/Server.js";
109
- export { callAgentTransport } from "./runtime/transport/rpc/Transport.js";
110
110
 
111
111
  // Runtime plugin 运行集成
112
112
  export {
@@ -127,7 +127,7 @@ export { persistProjectPluginConfig } from "./plugin/core/ProjectConfigStore.js"
127
127
  export {
128
128
  initializeAgentProject,
129
129
  isAgentProjectInitialized,
130
- normalizeDefaultAgentName,
130
+ normalizeDefaultAgentId,
131
131
  } from "./config/AgentInitializer.js";
132
132
  export { loadDowncityConfig } from "./config/Config.js";
133
133
  export {
@@ -193,7 +193,7 @@ export type {
193
193
  PlatformAgentShipStartConfig,
194
194
  } from "./types/runtime/platform/PlatformGateway.js";
195
195
 
196
- // Daemon / RPC 协议类型
196
+ // Daemon 协议类型
197
197
  export {
198
198
  DAEMON_LOG_FILENAME,
199
199
  DAEMON_META_FILENAME,
@@ -203,10 +203,6 @@ export type {
203
203
  DaemonMeta,
204
204
  DaemonStaleReason,
205
205
  } from "./types/runtime/daemon/Daemon.js";
206
- export type {
207
- LocalRpcRequest,
208
- LocalRpcResponse,
209
- } from "./types/runtime/rpc/LocalRpc.js";
210
206
 
211
207
  // Inline instant 协议类型
212
208
  export type {
@@ -281,16 +277,13 @@ export type {
281
277
 
282
278
  // Platform store 类型
283
279
  export type {
284
- StoredAgentEnvEntry,
285
280
  StoredChannelAccount,
286
281
  StoredChannelAccountChannel,
287
282
  StoredEnvEntry,
288
- StoredEnvScope,
289
283
  StoredGlobalEnvEntry,
290
284
  StoredModel,
291
285
  StoredModelProvider,
292
286
  StoredProviderMeta,
293
- UpsertAgentEnvEntryInput,
294
287
  UpsertChannelAccountInput,
295
288
  UpsertEnvEntryInput,
296
289
  UpsertGlobalEnvEntryInput,
@@ -2,7 +2,7 @@
2
2
  * PluginCommandRequest:统一 plugin runtime command 请求解析模块。
3
3
  *
4
4
  * 关键点(中文)
5
- * - 统一收口 HTTP / RPC 两种入口的请求体解析。
5
+ * - 统一收口当前 HTTP 入口的请求体解析。
6
6
  * - plugin runtime 远程调用统一走 runtime command 协议,不再让 action 自带 HTTP route。
7
7
  * - ActionSchedule 参数(`schedule` / `delay` / `time`)也在这里一次性归一化。
8
8
  */
@@ -9,7 +9,7 @@
9
9
 
10
10
  import path from "node:path";
11
11
  import { logger as defaultLogger } from "@/utils/logger/Logger.js";
12
- import { loadAgentEnvSnapshot, loadDowncityConfig } from "@/config/Config.js";
12
+ import { loadDowncityConfig, resolveAgentEnv } from "@/config/Config.js";
13
13
  import { isPluginEnabled } from "@/plugin/core/Activation.js";
14
14
  import { findPluginByName } from "@/plugin/core/PluginCatalog.js";
15
15
  import {
@@ -30,12 +30,8 @@ import type { AgentContext } from "@/types/runtime/agent/AgentContext.js";
30
30
  */
31
31
  export function createLocalPluginCommandContext(projectRoot: string): PluginCommandContext {
32
32
  const rootPath = path.resolve(String(projectRoot || "").trim() || ".");
33
- const env = loadAgentEnvSnapshot(rootPath);
34
- const config = loadDowncityConfig(rootPath, {
35
- projectEnv: env,
36
- agentEnv: env,
37
- globalEnv: {},
38
- });
33
+ const env = resolveAgentEnv(rootPath);
34
+ const config = loadDowncityConfig(rootPath);
39
35
 
40
36
  defaultLogger.bindProjectRoot(rootPath);
41
37
 
@@ -47,7 +43,7 @@ export function createLocalPluginCommandContext(projectRoot: string): PluginComm
47
43
  env,
48
44
  paths: createAgentPathRuntime(
49
45
  rootPath,
50
- String(config.name || "").trim() || path.basename(rootPath) || "agent",
46
+ String(config.id || "").trim() || path.basename(rootPath) || "agent",
51
47
  ),
52
48
  pluginConfig: createAgentPluginConfigRuntime(rootPath),
53
49
  };
@@ -155,7 +155,7 @@ export interface PluginCliBaseOptions {
155
155
  */
156
156
  json?: boolean;
157
157
  /**
158
- * agent 名称(从 managed agent registry 解析到项目路径)。
158
+ * agent id(从 managed agent registry 解析到项目路径)。
159
159
  */
160
160
  agent?: string;
161
161
  }
@@ -0,0 +1,467 @@
1
+ /**
2
+ * Agent 本机 RPC Client。
3
+ *
4
+ * 职责说明(中文)
5
+ * - 为 `RemoteAgent(rpc://...)` 提供最小客户端实现。
6
+ * - 复用逐行 JSON(NDJSON)协议,与本机 RPC Server 对接。
7
+ * - 当前只承载 Session actor 所需方法。
8
+ */
9
+
10
+ import net from "node:net";
11
+ import type {
12
+ AgentCreateSessionInput,
13
+ AgentListSessionsInput,
14
+ AgentSessionHistoryInput,
15
+ AgentSessionHistoryPage,
16
+ AgentSessionInfo,
17
+ AgentSessionSummaryPage,
18
+ AgentSessionSystemSnapshot,
19
+ } from "@/types/agent/AgentTypes.js";
20
+ import type { AgentSessionEvent } from "@/types/sdk/AgentSessionEvent.js";
21
+ import type { AgentSessionPromptInput } from "@/types/sdk/AgentSessionPrompt.js";
22
+
23
+ type RpcClientRequest =
24
+ | {
25
+ id: string;
26
+ method: "sdk.sessions.list";
27
+ params?: AgentListSessionsInput;
28
+ }
29
+ | {
30
+ id: string;
31
+ method: "sdk.sessions.create";
32
+ params?: AgentCreateSessionInput;
33
+ }
34
+ | {
35
+ id: string;
36
+ method: "sdk.sessions.get";
37
+ params: {
38
+ sessionId: string;
39
+ };
40
+ }
41
+ | {
42
+ id: string;
43
+ method: "sdk.sessions.prompt";
44
+ params: {
45
+ sessionId: string;
46
+ input: AgentSessionPromptInput;
47
+ };
48
+ }
49
+ | {
50
+ id: string;
51
+ method: "sdk.sessions.history";
52
+ params: {
53
+ sessionId: string;
54
+ input?: AgentSessionHistoryInput;
55
+ };
56
+ }
57
+ | {
58
+ id: string;
59
+ method: "sdk.sessions.system";
60
+ params: {
61
+ sessionId: string;
62
+ };
63
+ }
64
+ | {
65
+ id: string;
66
+ method: "sdk.sessions.fork";
67
+ params: {
68
+ sessionId: string;
69
+ messageId?: string;
70
+ };
71
+ }
72
+ | {
73
+ id: string;
74
+ method: "sdk.sessions.subscribe";
75
+ params: {
76
+ sessionId: string;
77
+ };
78
+ }
79
+ | {
80
+ id: string;
81
+ method: "sdk.sessions.unsubscribe";
82
+ params: {
83
+ subscriptionId: string;
84
+ };
85
+ };
86
+
87
+ type RpcResponseFrame = {
88
+ id: string;
89
+ success: boolean;
90
+ data?: unknown;
91
+ error?: string;
92
+ };
93
+
94
+ type RpcReadyFrame = {
95
+ type: "ready";
96
+ subscriptionId: string;
97
+ };
98
+
99
+ type RpcEventFrame = {
100
+ type: "event";
101
+ subscriptionId: string;
102
+ event: AgentSessionEvent;
103
+ };
104
+
105
+ type PendingRequest = {
106
+ resolve: (value: any) => void;
107
+ reject: (error: unknown) => void;
108
+ };
109
+
110
+ type RpcSubscription = {
111
+ on_ready: () => void;
112
+ on_event: (event: AgentSessionEvent) => void;
113
+ };
114
+
115
+ /**
116
+ * RPC endpoint。
117
+ */
118
+ export interface RpcClientEndpoint {
119
+ /** RPC host。 */
120
+ host: string;
121
+ /** RPC port。 */
122
+ port: number;
123
+ }
124
+
125
+ /**
126
+ * RPC Session 订阅句柄。
127
+ */
128
+ export interface RpcSessionSubscription {
129
+ /** 当前订阅 id。 */
130
+ subscription_id: string;
131
+ /** 取消订阅。 */
132
+ unsubscribe(): Promise<void>;
133
+ }
134
+
135
+ /**
136
+ * RPC Client。
137
+ */
138
+ export class RpcClient {
139
+ private readonly endpoint: RpcClientEndpoint;
140
+ private socket: net.Socket | null = null;
141
+ private connect_promise: Promise<void> | null = null;
142
+ private buffered = "";
143
+ private readonly pending_requests = new Map<string, PendingRequest>();
144
+ private readonly subscriptions = new Map<string, RpcSubscription>();
145
+ private request_sequence = 0;
146
+
147
+ constructor(endpoint: RpcClientEndpoint) {
148
+ this.endpoint = endpoint;
149
+ }
150
+
151
+ /**
152
+ * 列出远程 session。
153
+ */
154
+ async list_sessions(
155
+ input?: AgentListSessionsInput,
156
+ ): Promise<AgentSessionSummaryPage> {
157
+ const data = await this.request<{ page: AgentSessionSummaryPage }>({
158
+ method: "sdk.sessions.list",
159
+ params: input,
160
+ });
161
+ return data.page;
162
+ }
163
+
164
+ /**
165
+ * 创建远程 session。
166
+ */
167
+ async create_session(
168
+ input?: AgentCreateSessionInput,
169
+ ): Promise<AgentSessionInfo> {
170
+ const data = await this.request<{ session: AgentSessionInfo }>({
171
+ method: "sdk.sessions.create",
172
+ params: input,
173
+ });
174
+ return data.session;
175
+ }
176
+
177
+ /**
178
+ * 获取远程 session 信息。
179
+ */
180
+ async get_session(session_id: string): Promise<AgentSessionInfo> {
181
+ const data = await this.request<{ session: AgentSessionInfo }>({
182
+ method: "sdk.sessions.get",
183
+ params: {
184
+ sessionId: session_id,
185
+ },
186
+ });
187
+ return data.session;
188
+ }
189
+
190
+ /**
191
+ * 发送 prompt。
192
+ */
193
+ async prompt_session(params: {
194
+ session_id: string;
195
+ input: AgentSessionPromptInput;
196
+ }): Promise<{ id: string }> {
197
+ const data = await this.request<{ turn: { id: string } }>({
198
+ method: "sdk.sessions.prompt",
199
+ params: {
200
+ sessionId: params.session_id,
201
+ input: params.input,
202
+ },
203
+ });
204
+ return data.turn;
205
+ }
206
+
207
+ /**
208
+ * 读取 history。
209
+ */
210
+ async get_session_history(params: {
211
+ session_id: string;
212
+ input?: AgentSessionHistoryInput;
213
+ }): Promise<AgentSessionHistoryPage> {
214
+ const data = await this.request<{ history: AgentSessionHistoryPage }>({
215
+ method: "sdk.sessions.history",
216
+ params: {
217
+ sessionId: params.session_id,
218
+ input: params.input,
219
+ },
220
+ });
221
+ return data.history;
222
+ }
223
+
224
+ /**
225
+ * 读取 system snapshot。
226
+ */
227
+ async get_session_system(session_id: string): Promise<AgentSessionSystemSnapshot> {
228
+ const data = await this.request<{ system: AgentSessionSystemSnapshot }>({
229
+ method: "sdk.sessions.system",
230
+ params: {
231
+ sessionId: session_id,
232
+ },
233
+ });
234
+ return data.system;
235
+ }
236
+
237
+ /**
238
+ * 分叉 session。
239
+ */
240
+ async fork_session(params: {
241
+ session_id: string;
242
+ message_id?: string;
243
+ }): Promise<AgentSessionInfo> {
244
+ const data = await this.request<{ session: AgentSessionInfo }>({
245
+ method: "sdk.sessions.fork",
246
+ params: {
247
+ sessionId: params.session_id,
248
+ ...(params.message_id ? { messageId: params.message_id } : {}),
249
+ },
250
+ });
251
+ return data.session;
252
+ }
253
+
254
+ /**
255
+ * 订阅 session 事件。
256
+ */
257
+ async subscribe_session(params: {
258
+ session_id: string;
259
+ on_ready: () => void;
260
+ on_event: (event: AgentSessionEvent) => void;
261
+ }): Promise<RpcSessionSubscription> {
262
+ const data = await this.request<{ subscriptionId: string }>({
263
+ method: "sdk.sessions.subscribe",
264
+ params: {
265
+ sessionId: params.session_id,
266
+ },
267
+ });
268
+ const subscription_id = String(data.subscriptionId || "").trim();
269
+ if (!subscription_id) {
270
+ throw new Error("RPC subscription did not return subscriptionId");
271
+ }
272
+ this.subscriptions.set(subscription_id, {
273
+ on_ready: params.on_ready,
274
+ on_event: params.on_event,
275
+ });
276
+ params.on_ready();
277
+ return {
278
+ subscription_id,
279
+ unsubscribe: async () => {
280
+ this.subscriptions.delete(subscription_id);
281
+ await this.request({
282
+ method: "sdk.sessions.unsubscribe",
283
+ params: {
284
+ subscriptionId: subscription_id,
285
+ },
286
+ });
287
+ },
288
+ };
289
+ }
290
+
291
+ /**
292
+ * 关闭底层连接。
293
+ */
294
+ async close(): Promise<void> {
295
+ const socket = this.socket;
296
+ this.socket = null;
297
+ this.connect_promise = null;
298
+ this.buffered = "";
299
+ for (const pending of this.pending_requests.values()) {
300
+ pending.reject(new Error("RPC client closed"));
301
+ }
302
+ this.pending_requests.clear();
303
+ this.fail_all_subscriptions("RPC client closed");
304
+ if (!socket) return;
305
+ await new Promise<void>((resolve) => {
306
+ socket.end(() => resolve());
307
+ });
308
+ }
309
+
310
+ private async request<TData = unknown>(input: {
311
+ method: RpcClientRequest["method"];
312
+ params?: unknown;
313
+ }): Promise<TData> {
314
+ await this.ensure_connected();
315
+ const socket = this.socket;
316
+ if (!socket) {
317
+ throw new Error("RPC socket is not connected");
318
+ }
319
+ const id = `rpc_${Date.now()}_${this.request_sequence += 1}`;
320
+ const request = (
321
+ input.params === undefined
322
+ ? {
323
+ id,
324
+ method: input.method,
325
+ }
326
+ : {
327
+ id,
328
+ method: input.method,
329
+ params: input.params as never,
330
+ }
331
+ ) as RpcClientRequest;
332
+ const result = await new Promise<TData>((resolve, reject) => {
333
+ this.pending_requests.set(id, { resolve, reject });
334
+ socket.write(`${JSON.stringify(request)}\n`, (error) => {
335
+ if (!error) return;
336
+ this.pending_requests.delete(id);
337
+ reject(error);
338
+ });
339
+ });
340
+ return result;
341
+ }
342
+
343
+ private async ensure_connected(): Promise<void> {
344
+ if (this.socket && !this.socket.destroyed) return;
345
+ if (this.connect_promise) {
346
+ await this.connect_promise;
347
+ return;
348
+ }
349
+
350
+ this.connect_promise = new Promise<void>((resolve, reject) => {
351
+ const socket = net.createConnection({
352
+ host: this.endpoint.host,
353
+ port: this.endpoint.port,
354
+ });
355
+
356
+ socket.setEncoding("utf8");
357
+
358
+ socket.on("connect", () => {
359
+ this.socket = socket;
360
+ resolve();
361
+ });
362
+
363
+ socket.on("data", (chunk: string) => {
364
+ this.consume_data(chunk);
365
+ });
366
+
367
+ socket.on("error", (error) => {
368
+ if (!this.socket) {
369
+ reject(error);
370
+ return;
371
+ }
372
+ this.fail_all_pending(error);
373
+ this.fail_all_subscriptions(
374
+ error instanceof Error ? error.message : String(error),
375
+ );
376
+ });
377
+
378
+ socket.on("close", () => {
379
+ this.socket = null;
380
+ this.connect_promise = null;
381
+ this.fail_all_pending(new Error("RPC socket closed"));
382
+ this.fail_all_subscriptions("RPC socket closed");
383
+ });
384
+ });
385
+
386
+ try {
387
+ await this.connect_promise;
388
+ } catch (error) {
389
+ this.connect_promise = null;
390
+ throw error;
391
+ }
392
+ }
393
+
394
+ private consume_data(chunk: string): void {
395
+ this.buffered += chunk;
396
+ let newline_index = this.buffered.indexOf("\n");
397
+ while (newline_index >= 0) {
398
+ const line = this.buffered.slice(0, newline_index).trim();
399
+ this.buffered = this.buffered.slice(newline_index + 1);
400
+ if (line) {
401
+ this.consume_line(line);
402
+ }
403
+ newline_index = this.buffered.indexOf("\n");
404
+ }
405
+ }
406
+
407
+ private consume_line(line: string): void {
408
+ const payload = JSON.parse(line) as RpcResponseFrame | RpcReadyFrame | RpcEventFrame;
409
+ if ("type" in payload && payload.type === "ready") {
410
+ const subscription = this.subscriptions.get(payload.subscriptionId);
411
+ subscription?.on_ready();
412
+ return;
413
+ }
414
+ if ("type" in payload && payload.type === "event") {
415
+ const subscription = this.subscriptions.get(payload.subscriptionId);
416
+ subscription?.on_event(payload.event);
417
+ return;
418
+ }
419
+ const pending = this.pending_requests.get(payload.id);
420
+ if (!pending) return;
421
+ this.pending_requests.delete(payload.id);
422
+ if (payload.success) {
423
+ pending.resolve(payload.data);
424
+ return;
425
+ }
426
+ pending.reject(new Error(String(payload.error || "RPC request failed")));
427
+ }
428
+
429
+ private fail_all_pending(error: unknown): void {
430
+ for (const pending of this.pending_requests.values()) {
431
+ pending.reject(error);
432
+ }
433
+ this.pending_requests.clear();
434
+ }
435
+
436
+ private fail_all_subscriptions(message: string): void {
437
+ for (const subscription of this.subscriptions.values()) {
438
+ subscription.on_event({
439
+ type: "error",
440
+ message,
441
+ });
442
+ }
443
+ this.subscriptions.clear();
444
+ }
445
+ }
446
+
447
+ /**
448
+ * 解析 rpc url。
449
+ */
450
+ export function parse_rpc_url(url_input: string): RpcClientEndpoint {
451
+ const url = new URL(url_input);
452
+ if (url.protocol !== "rpc:") {
453
+ throw new Error(`Unsupported RPC protocol: ${url.protocol}`);
454
+ }
455
+ const host = String(url.hostname || "").trim();
456
+ const port = Number.parseInt(String(url.port || ""), 10);
457
+ if (!host) {
458
+ throw new Error("RPC url requires a host");
459
+ }
460
+ if (!Number.isInteger(port) || port <= 0 || port > 65535) {
461
+ throw new Error("RPC url requires a valid port");
462
+ }
463
+ return {
464
+ host,
465
+ port,
466
+ };
467
+ }