pim-agent 0.0.1 → 0.9.0

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 (261) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +293 -7
  3. package/bin/pim.ts +151 -0
  4. package/package.json +82 -4
  5. package/packages/core/src/attachments/AttachmentStore.ts +99 -0
  6. package/packages/core/src/attachments/Attachments.ts +69 -0
  7. package/packages/core/src/extensions/CoreExtensions.ts +51 -0
  8. package/packages/core/src/extensions/apply-patch/coordinator.ts +53 -0
  9. package/packages/core/src/extensions/apply-patch/executor.ts +550 -0
  10. package/packages/core/src/extensions/apply-patch/index.ts +69 -0
  11. package/packages/core/src/extensions/apply-patch/matcher.ts +47 -0
  12. package/packages/core/src/extensions/apply-patch/model.ts +39 -0
  13. package/packages/core/src/extensions/apply-patch/parser.ts +379 -0
  14. package/packages/core/src/extensions/apply-patch/render.ts +216 -0
  15. package/packages/core/src/extensions/apply-patch/schema.ts +37 -0
  16. package/packages/core/src/extensions/apply-patch/types.ts +30 -0
  17. package/packages/core/src/extensions/bash/capture.ts +86 -0
  18. package/packages/core/src/extensions/bash/format.ts +110 -0
  19. package/packages/core/src/extensions/bash/image.ts +31 -0
  20. package/packages/core/src/extensions/bash/index.ts +88 -0
  21. package/packages/core/src/extensions/bash/render.ts +31 -0
  22. package/packages/core/src/extensions/bash/run.ts +221 -0
  23. package/packages/core/src/extensions/bash/schema.ts +64 -0
  24. package/packages/core/src/extensions/edit/edit.ts +366 -0
  25. package/packages/core/src/extensions/edit/index.ts +36 -0
  26. package/packages/core/src/extensions/edit/render.ts +16 -0
  27. package/packages/core/src/extensions/edit/schema.ts +37 -0
  28. package/packages/core/src/extensions/glob/glob.ts +34 -0
  29. package/packages/core/src/extensions/glob/index.ts +83 -0
  30. package/packages/core/src/extensions/glob/render.ts +72 -0
  31. package/packages/core/src/extensions/glob/schema.ts +66 -0
  32. package/packages/core/src/extensions/grep/grep.ts +256 -0
  33. package/packages/core/src/extensions/grep/index.ts +114 -0
  34. package/packages/core/src/extensions/grep/render.ts +271 -0
  35. package/packages/core/src/extensions/grep/schema.ts +92 -0
  36. package/packages/core/src/extensions/read/ImageMemory.ts +42 -0
  37. package/packages/core/src/extensions/read/image.ts +57 -0
  38. package/packages/core/src/extensions/read/index.ts +88 -0
  39. package/packages/core/src/extensions/read/read.ts +191 -0
  40. package/packages/core/src/extensions/read/render.ts +58 -0
  41. package/packages/core/src/extensions/read/schema.ts +49 -0
  42. package/packages/core/src/extensions/subagent/index.ts +33 -0
  43. package/packages/core/src/extensions/subagent/render.ts +127 -0
  44. package/packages/core/src/extensions/subagent/schema.ts +9 -0
  45. package/packages/core/src/extensions/subagent/subagent.ts +419 -0
  46. package/packages/core/src/extensions/system-prompt/index.ts +28 -0
  47. package/packages/core/src/extensions/system-prompt/prompt.ts +228 -0
  48. package/packages/core/src/extensions/todo/index.ts +123 -0
  49. package/packages/core/src/extensions/todo/render.ts +156 -0
  50. package/packages/core/src/extensions/todo/schema.ts +24 -0
  51. package/packages/core/src/extensions/todo/todo.ts +185 -0
  52. package/packages/core/src/extensions/web-fetch/JinaReaderClient.ts +187 -0
  53. package/packages/core/src/extensions/web-fetch/WebViewFetchClient.ts +183 -0
  54. package/packages/core/src/extensions/web-fetch/WebViewMarkdownSnapshot.ts +512 -0
  55. package/packages/core/src/extensions/web-fetch/fetch.ts +265 -0
  56. package/packages/core/src/extensions/web-fetch/image.ts +165 -0
  57. package/packages/core/src/extensions/web-fetch/index.ts +83 -0
  58. package/packages/core/src/extensions/web-fetch/render.ts +100 -0
  59. package/packages/core/src/extensions/web-fetch/schema.ts +46 -0
  60. package/packages/core/src/extensions/web-search/ExaMcpClient.ts +251 -0
  61. package/packages/core/src/extensions/web-search/SearchBreaker.ts +147 -0
  62. package/packages/core/src/extensions/web-search/SearchChain.ts +77 -0
  63. package/packages/core/src/extensions/web-search/index.ts +90 -0
  64. package/packages/core/src/extensions/web-search/providers/DuckDuckGoProvider.ts +195 -0
  65. package/packages/core/src/extensions/web-search/providers/ExaProvider.ts +54 -0
  66. package/packages/core/src/extensions/web-search/providers/FirecrawlProvider.ts +144 -0
  67. package/packages/core/src/extensions/web-search/providers/SearchProvider.ts +118 -0
  68. package/packages/core/src/extensions/web-search/render.ts +49 -0
  69. package/packages/core/src/extensions/web-search/schema.ts +29 -0
  70. package/packages/core/src/extensions/web-search/search.ts +23 -0
  71. package/packages/core/src/extensions/write/index.ts +58 -0
  72. package/packages/core/src/extensions/write/render.ts +16 -0
  73. package/packages/core/src/extensions/write/schema.ts +14 -0
  74. package/packages/core/src/extensions/write/write.ts +87 -0
  75. package/packages/core/src/picker/FilePickerSuggestionEngine.ts +14 -0
  76. package/packages/core/src/picker/InProcessFilePickerSuggestionEngine.ts +52 -0
  77. package/packages/core/src/picker/PickerItem.ts +6 -0
  78. package/packages/core/src/picker/PickerService.ts +110 -0
  79. package/packages/core/src/picker/RemoteFilePickerSuggestionEngine.ts +56 -0
  80. package/packages/core/src/picker/WorkerFilePickerSuggestionEngine.ts +268 -0
  81. package/packages/core/src/picker/catalog.ts +220 -0
  82. package/packages/core/src/picker/commandRanker.ts +17 -0
  83. package/packages/core/src/picker/filePickerWorker.ts +66 -0
  84. package/packages/core/src/picker/filePickerWorkerMessages.ts +39 -0
  85. package/packages/core/src/picker/ranker.ts +245 -0
  86. package/packages/core/src/picker/token.ts +70 -0
  87. package/packages/core/src/session/AgentRuntime.ts +85 -0
  88. package/packages/core/src/session/EventLog.ts +213 -0
  89. package/packages/core/src/session/MessageText.ts +21 -0
  90. package/packages/core/src/session/ReadCursors.ts +94 -0
  91. package/packages/core/src/session/SessionCache.ts +68 -0
  92. package/packages/core/src/session/SessionHost.ts +876 -0
  93. package/packages/core/src/session/SessionLease.ts +279 -0
  94. package/packages/core/src/session/SessionRegistry.ts +169 -0
  95. package/packages/core/src/session/WriteMark.ts +37 -0
  96. package/packages/core/src/shared/Cli.ts +25 -0
  97. package/packages/core/src/shared/DaemonUnit.ts +13 -0
  98. package/packages/core/src/shared/DiffLines.ts +304 -0
  99. package/packages/core/src/shared/DiffRenderer.ts +283 -0
  100. package/packages/core/src/shared/DiffView.ts +29 -0
  101. package/packages/core/src/shared/Directories.ts +61 -0
  102. package/packages/core/src/shared/EditMatcher.ts +767 -0
  103. package/packages/core/src/shared/Errors.ts +9 -0
  104. package/packages/core/src/shared/ExtensionToggles.ts +130 -0
  105. package/packages/core/src/shared/FileEnumerator.ts +361 -0
  106. package/packages/core/src/shared/FileScanner.ts +57 -0
  107. package/packages/core/src/shared/FileWatch.ts +107 -0
  108. package/packages/core/src/shared/Format.ts +74 -0
  109. package/packages/core/src/shared/Fs.ts +91 -0
  110. package/packages/core/src/shared/FsErrors.ts +67 -0
  111. package/packages/core/src/shared/FuzzyMatcher.ts +71 -0
  112. package/packages/core/src/shared/Git.ts +89 -0
  113. package/packages/core/src/shared/GlobExclusions.ts +13 -0
  114. package/packages/core/src/shared/Http.ts +14 -0
  115. package/packages/core/src/shared/ImageMime.ts +36 -0
  116. package/packages/core/src/shared/Images.ts +357 -0
  117. package/packages/core/src/shared/Json.ts +19 -0
  118. package/packages/core/src/shared/Languages.ts +95 -0
  119. package/packages/core/src/shared/Levenshtein.ts +30 -0
  120. package/packages/core/src/shared/Lines.ts +79 -0
  121. package/packages/core/src/shared/McpClient.ts +444 -0
  122. package/packages/core/src/shared/OutputBudget.ts +84 -0
  123. package/packages/core/src/shared/PatchSummary.ts +40 -0
  124. package/packages/core/src/shared/Paths.ts +80 -0
  125. package/packages/core/src/shared/PimSettings.ts +128 -0
  126. package/packages/core/src/shared/PimVersion.ts +93 -0
  127. package/packages/core/src/shared/Pool.ts +26 -0
  128. package/packages/core/src/shared/Proc.ts +30 -0
  129. package/packages/core/src/shared/RateLimiter.ts +50 -0
  130. package/packages/core/src/shared/Renderer.ts +371 -0
  131. package/packages/core/src/shared/SafePath.ts +17 -0
  132. package/packages/core/src/shared/SearchRender.ts +82 -0
  133. package/packages/core/src/shared/SendFile.ts +23 -0
  134. package/packages/core/src/shared/SpillCache.ts +78 -0
  135. package/packages/core/src/shared/SubagentLogs.ts +102 -0
  136. package/packages/core/src/shared/Supervisor.ts +460 -0
  137. package/packages/core/src/shared/Sweeper.ts +32 -0
  138. package/packages/core/src/shared/Tools.ts +801 -0
  139. package/packages/core/src/shared/Updater.ts +193 -0
  140. package/packages/core/src/view/AnsiPainter.ts +227 -0
  141. package/packages/core/src/view/BodyRenderer.ts +102 -0
  142. package/packages/core/src/view/DiffBlocks.ts +52 -0
  143. package/packages/core/src/view/DiffLayout.ts +69 -0
  144. package/packages/core/src/view/MarkdownPainter.ts +314 -0
  145. package/packages/core/src/view/Painting.ts +182 -0
  146. package/packages/core/src/view/ViewBlock.ts +124 -0
  147. package/packages/daemon/src/Daemon.ts +63 -0
  148. package/packages/daemon/src/DaemonInstall.ts +115 -0
  149. package/packages/daemon/src/Surfaces.ts +66 -0
  150. package/packages/daemon/src/TelegramSurface.ts +30 -0
  151. package/packages/daemon/src/WebSurface.ts +57 -0
  152. package/packages/daemon/src/index.ts +37 -0
  153. package/packages/protocol/src/Command.ts +92 -0
  154. package/packages/protocol/src/ImageRoute.ts +23 -0
  155. package/packages/protocol/src/Protocol.ts +6 -0
  156. package/packages/protocol/src/ServerEvent.ts +251 -0
  157. package/packages/server/src/AttachmentEndpoint.ts +184 -0
  158. package/packages/server/src/ClientConnection.ts +265 -0
  159. package/packages/server/src/ImageEndpoint.ts +36 -0
  160. package/packages/server/src/ProbeClient.ts +312 -0
  161. package/packages/server/src/Reloader.ts +93 -0
  162. package/packages/server/src/SendFileTool.ts +88 -0
  163. package/packages/server/src/SessionCatalogue.ts +202 -0
  164. package/packages/server/src/SessionProjection.ts +194 -0
  165. package/packages/server/src/SessionStream.ts +601 -0
  166. package/packages/server/src/StaticClient.ts +95 -0
  167. package/packages/server/src/WebOptions.ts +72 -0
  168. package/packages/server/src/WsGateway.ts +563 -0
  169. package/packages/server/src/probe.ts +146 -0
  170. package/packages/telegram/src/Bot.ts +229 -0
  171. package/packages/telegram/src/Commands.ts +768 -0
  172. package/packages/telegram/src/Config.ts +148 -0
  173. package/packages/telegram/src/Markdown.ts +235 -0
  174. package/packages/telegram/src/Message.ts +152 -0
  175. package/packages/telegram/src/Renderer.ts +456 -0
  176. package/packages/telegram/src/SendFileSchema.ts +18 -0
  177. package/packages/telegram/src/SendFileTool.ts +65 -0
  178. package/packages/telegram/src/Session.ts +252 -0
  179. package/packages/telegram/src/SessionRegistry.ts +126 -0
  180. package/packages/telegram/src/TaskScheduler.ts +296 -0
  181. package/packages/telegram/src/TaskSchema.ts +88 -0
  182. package/packages/telegram/src/TaskStore.ts +67 -0
  183. package/packages/telegram/src/TaskTool.ts +208 -0
  184. package/packages/telegram/src/TelegramHtml.ts +288 -0
  185. package/packages/telegram/src/TypingIndicator.ts +43 -0
  186. package/packages/telegram/src/UpdateConfirm.ts +61 -0
  187. package/packages/tui/src/extensions/_init/index.ts +144 -0
  188. package/packages/tui/src/extensions/command-picker/index.ts +43 -0
  189. package/packages/tui/src/extensions/file-picker/index.ts +156 -0
  190. package/packages/tui/src/extensions/footer/index.ts +117 -0
  191. package/packages/tui/src/extensions/footer/powerline.ts +87 -0
  192. package/packages/tui/src/extensions/footer/segments.ts +202 -0
  193. package/packages/tui/src/extensions/pim/index.ts +89 -0
  194. package/packages/tui/src/extensions/session-lease/index.ts +248 -0
  195. package/packages/tui/src/extensions/tps/index.ts +118 -0
  196. package/packages/tui/src/extensions/working-indicator/index.ts +75 -0
  197. package/packages/tui/src/extensions/wrapProvider.ts +18 -0
  198. package/packages/tui/src/themes/pim-dark.json +84 -0
  199. package/packages/tui/src/themes/pim-light.json +84 -0
  200. package/packages/tui/src/themes/themeCliArgs.ts +20 -0
  201. package/packages/web/dist/client/apple-touch-icon.png +0 -0
  202. package/packages/web/dist/client/assets/bash-LmRjUKlR.js +1 -0
  203. package/packages/web/dist/client/assets/c-DBVbJFpC.js +1 -0
  204. package/packages/web/dist/client/assets/clojure-CR7dvLJ4.js +1 -0
  205. package/packages/web/dist/client/assets/cmake-CgP6gB4W.js +1 -0
  206. package/packages/web/dist/client/assets/commit-mono-latin-300-italic-B48RJMeK.woff +0 -0
  207. package/packages/web/dist/client/assets/commit-mono-latin-300-italic-DBq5bJCE.woff2 +0 -0
  208. package/packages/web/dist/client/assets/commit-mono-latin-400-italic-BXinMwCi.woff +0 -0
  209. package/packages/web/dist/client/assets/commit-mono-latin-400-italic-DjSHLl2N.woff2 +0 -0
  210. package/packages/web/dist/client/assets/commit-mono-latin-400-normal-s0S3qwFW.woff +0 -0
  211. package/packages/web/dist/client/assets/commit-mono-latin-400-normal-wzhe4RuD.woff2 +0 -0
  212. package/packages/web/dist/client/assets/commit-mono-latin-600-normal-BuQDXT0M.woff +0 -0
  213. package/packages/web/dist/client/assets/commit-mono-latin-600-normal-SwgSWHaV.woff2 +0 -0
  214. package/packages/web/dist/client/assets/commit-mono-latin-700-normal-1-GU0IUE.woff +0 -0
  215. package/packages/web/dist/client/assets/commit-mono-latin-700-normal-DU8mrtj2.woff2 +0 -0
  216. package/packages/web/dist/client/assets/core-CGdQx9la.js +3 -0
  217. package/packages/web/dist/client/assets/cpp-N2d8M7Nc.js +1 -0
  218. package/packages/web/dist/client/assets/csharp-yYt0Ikks.js +1 -0
  219. package/packages/web/dist/client/assets/css-D8M8GYaw.js +1 -0
  220. package/packages/web/dist/client/assets/diff-BhdZizse.js +1 -0
  221. package/packages/web/dist/client/assets/dockerfile-C0zufGsU.js +1 -0
  222. package/packages/web/dist/client/assets/elixir-DXIHlGsR.js +1 -0
  223. package/packages/web/dist/client/assets/erlang-BjYI8dhz.js +1 -0
  224. package/packages/web/dist/client/assets/go-CYstVVJV.js +1 -0
  225. package/packages/web/dist/client/assets/graphql-BHHhacaR.js +1 -0
  226. package/packages/web/dist/client/assets/haskell-DUkhXIda.js +1 -0
  227. package/packages/web/dist/client/assets/index-BMNwleWO.js +40 -0
  228. package/packages/web/dist/client/assets/index-CqjB2RPr.css +1 -0
  229. package/packages/web/dist/client/assets/ini-B1CroMsf.js +1 -0
  230. package/packages/web/dist/client/assets/java-Dde-Wnb4.js +1 -0
  231. package/packages/web/dist/client/assets/javascript-8AmHuH5O.js +1 -0
  232. package/packages/web/dist/client/assets/json-PmyKWHmm.js +1 -0
  233. package/packages/web/dist/client/assets/kotlin-DgSK8bu-.js +2 -0
  234. package/packages/web/dist/client/assets/less-Bp0mgmpG.js +1 -0
  235. package/packages/web/dist/client/assets/lua-1dXYaL-H.js +1 -0
  236. package/packages/web/dist/client/assets/makefile-BX-o3EIz.js +1 -0
  237. package/packages/web/dist/client/assets/markdown-D6FrnlqJ.js +1 -0
  238. package/packages/web/dist/client/assets/ocaml-B-P1Uyq8.js +1 -0
  239. package/packages/web/dist/client/assets/perl-CvqMZYuf.js +1 -0
  240. package/packages/web/dist/client/assets/php-DuZl5e-L.js +2 -0
  241. package/packages/web/dist/client/assets/powershell-BUe16zE4.js +1 -0
  242. package/packages/web/dist/client/assets/protobuf-DGOW1qT8.js +1 -0
  243. package/packages/web/dist/client/assets/python-gHmu6VPG.js +1 -0
  244. package/packages/web/dist/client/assets/r-DNWPCnjT.js +1 -0
  245. package/packages/web/dist/client/assets/ruby-BGpZ3p4_.js +1 -0
  246. package/packages/web/dist/client/assets/rust-BIKPVoi6.js +1 -0
  247. package/packages/web/dist/client/assets/scala-C9lP4FWY.js +1 -0
  248. package/packages/web/dist/client/assets/scss-gOyOKiXd.js +1 -0
  249. package/packages/web/dist/client/assets/sql-DWRKyvJ-.js +1 -0
  250. package/packages/web/dist/client/assets/swift-BTn9__hL.js +1 -0
  251. package/packages/web/dist/client/assets/typescript-CxkPLmSc.js +1 -0
  252. package/packages/web/dist/client/assets/vim-Dhhcn_CA.js +1 -0
  253. package/packages/web/dist/client/assets/xml-COl5yFSC.js +1 -0
  254. package/packages/web/dist/client/assets/yaml-DjOhhVLG.js +1 -0
  255. package/packages/web/dist/client/favicon.svg +1 -0
  256. package/packages/web/dist/client/icon-192.png +0 -0
  257. package/packages/web/dist/client/icon-512.png +0 -0
  258. package/packages/web/dist/client/icon-maskable.png +0 -0
  259. package/packages/web/dist/client/index.html +24 -0
  260. package/packages/web/dist/client/manifest.webmanifest +21 -0
  261. package/packages/web/dist/client/wordmark.svg +1 -0
@@ -0,0 +1,444 @@
1
+ import { Errors } from "./Errors";
2
+ import { createKy, type HttpFetch } from "./Http";
3
+ import { Json } from "./Json";
4
+ import { HTTPError, type KyInstance } from "ky";
5
+ import type { RateLimiter } from "./RateLimiter";
6
+
7
+ export type McpFetch = HttpFetch;
8
+
9
+ export type McpClientOptions = {
10
+ readonly endpoint: string;
11
+ readonly headers?: Readonly<Record<string, string>>;
12
+ readonly fetch?: McpFetch;
13
+ readonly clientName?: string;
14
+ readonly clientVersion?: string;
15
+ readonly rateLimiter?: RateLimiter;
16
+ };
17
+
18
+ export type CallToolInput = {
19
+ readonly name: string;
20
+ readonly arguments: Readonly<Record<string, unknown>>;
21
+ readonly signal?: AbortSignal;
22
+ };
23
+
24
+ const protocolVersion = "2025-06-18";
25
+
26
+ type JsonRpcResponse = {
27
+ readonly id?: unknown;
28
+ readonly result?: unknown;
29
+ readonly error?: unknown;
30
+ };
31
+
32
+ export class McpClientError extends Error {
33
+ public readonly status: number | undefined;
34
+
35
+ public constructor(message: string, status?: number) {
36
+ super(message);
37
+ this.name = "McpClientError";
38
+ this.status = status;
39
+ }
40
+ }
41
+
42
+ export class McpClient {
43
+ private readonly endpoint: string;
44
+ private readonly extraHeaders: Readonly<Record<string, string>>;
45
+ private readonly clientName: string;
46
+ private readonly clientVersion: string;
47
+ private readonly ky: KyInstance;
48
+ private readonly rateLimiter: RateLimiter | undefined;
49
+ private nextRequestId = 1;
50
+ private sessionPromise: Promise<string | undefined> | undefined;
51
+
52
+ public constructor(options: McpClientOptions) {
53
+ this.endpoint = options.endpoint;
54
+ this.extraHeaders = options.headers ?? {};
55
+ this.clientName = options.clientName ?? "pim-agent";
56
+ this.clientVersion = options.clientVersion ?? "0.0.0";
57
+ this.rateLimiter = options.rateLimiter;
58
+ this.ky = createKy(options.fetch);
59
+ }
60
+
61
+ public async callTool(input: CallToolInput): Promise<unknown> {
62
+ const pending = this.ensureSession();
63
+ const sessionId = await pending;
64
+
65
+ try {
66
+ return await this.invokeTool(input, sessionId);
67
+ } catch (error) {
68
+ if (
69
+ input.signal?.aborted ||
70
+ sessionId === undefined ||
71
+ !isStaleSessionError(error)
72
+ ) {
73
+ throw error;
74
+ }
75
+
76
+ this.invalidateSession(pending);
77
+
78
+ return this.invokeTool(input, await this.ensureSession());
79
+ }
80
+ }
81
+
82
+ private ensureSession(): Promise<string | undefined> {
83
+ const pending = (this.sessionPromise ??= this.handshake().catch(
84
+ (error: unknown) => {
85
+ this.invalidateSession(pending);
86
+ throw error;
87
+ }
88
+ ));
89
+
90
+ return pending;
91
+ }
92
+
93
+ private invalidateSession(pending: Promise<string | undefined>): void {
94
+ if (this.sessionPromise === pending) {
95
+ this.sessionPromise = undefined;
96
+ }
97
+ }
98
+
99
+ private async handshake(): Promise<string | undefined> {
100
+ const initialize = await this.sendRpcRequest({
101
+ id: this.nextRequestId++,
102
+ method: "initialize",
103
+ params: {
104
+ protocolVersion,
105
+ clientInfo: {
106
+ name: this.clientName,
107
+ version: this.clientVersion,
108
+ },
109
+ capabilities: {},
110
+ },
111
+ });
112
+
113
+ await this.sendNotification({
114
+ sessionId: initialize.sessionId,
115
+ method: "notifications/initialized",
116
+ params: {},
117
+ });
118
+
119
+ return initialize.sessionId;
120
+ }
121
+
122
+ private async invokeTool(
123
+ input: CallToolInput,
124
+ sessionId: string | undefined
125
+ ): Promise<unknown> {
126
+ const requestId = this.nextRequestId++;
127
+
128
+ try {
129
+ const toolCall = await this.sendRpcRequest({
130
+ id: requestId,
131
+ ...(sessionId === undefined ? {} : { sessionId }),
132
+ method: "tools/call",
133
+ params: {
134
+ name: input.name,
135
+ arguments: input.arguments,
136
+ },
137
+ ...(input.signal === undefined ? {} : { signal: input.signal }),
138
+ });
139
+
140
+ return toolCall.envelope.result;
141
+ } catch (error) {
142
+ if (input.signal?.aborted) {
143
+ await this.sendCancellation(sessionId, requestId);
144
+ throw new McpClientError("MCP request aborted.");
145
+ }
146
+
147
+ throw error;
148
+ }
149
+ }
150
+
151
+ private async sendRpcRequest(input: {
152
+ readonly id: number;
153
+ readonly sessionId?: string;
154
+ readonly method: string;
155
+ readonly params: Readonly<Record<string, unknown>>;
156
+ readonly signal?: AbortSignal;
157
+ }): Promise<{
158
+ readonly envelope: JsonRpcResponse;
159
+ readonly sessionId: string | undefined;
160
+ }> {
161
+ const response = await this.postJson(
162
+ {
163
+ jsonrpc: "2.0",
164
+ id: input.id,
165
+ method: input.method,
166
+ params: input.params,
167
+ },
168
+ input.sessionId,
169
+ input.signal
170
+ );
171
+ const envelope = await this.readRpcResponse(response, input.id);
172
+ const sessionId =
173
+ input.sessionId ?? response.headers.get("mcp-session-id") ?? undefined;
174
+
175
+ if (Json.asRecord(envelope.error) !== undefined) {
176
+ throw new McpClientError(
177
+ `MCP JSON-RPC error: ${describeRpcError(envelope.error)}`
178
+ );
179
+ }
180
+
181
+ return { envelope, sessionId };
182
+ }
183
+
184
+ private async sendNotification(input: {
185
+ readonly sessionId: string | undefined;
186
+ readonly method: string;
187
+ readonly params: Readonly<Record<string, unknown>>;
188
+ readonly rateLimited?: boolean;
189
+ }): Promise<void> {
190
+ await this.postJson(
191
+ {
192
+ jsonrpc: "2.0",
193
+ method: input.method,
194
+ params: input.params,
195
+ },
196
+ input.sessionId,
197
+ undefined,
198
+ input.rateLimited ?? true
199
+ );
200
+ }
201
+
202
+ private async sendCancellation(
203
+ sessionId: string | undefined,
204
+ requestId: number | undefined
205
+ ): Promise<void> {
206
+ if (sessionId === undefined || requestId === undefined) {
207
+ return;
208
+ }
209
+
210
+ try {
211
+ await this.sendNotification({
212
+ sessionId,
213
+ method: "notifications/cancelled",
214
+ params: {
215
+ requestId,
216
+ reason: "Tool call aborted.",
217
+ },
218
+ rateLimited: false,
219
+ });
220
+ } catch {}
221
+ }
222
+
223
+ private async postJson(
224
+ body: Readonly<Record<string, unknown>>,
225
+ sessionId?: string,
226
+ signal?: AbortSignal,
227
+ rateLimited = true
228
+ ): Promise<Response> {
229
+ if (rateLimited) {
230
+ await this.rateLimiter?.acquire();
231
+ }
232
+
233
+ try {
234
+ return await this.ky(this.endpoint, {
235
+ method: "POST",
236
+ headers: this.createHeaders(sessionId),
237
+ json: body,
238
+ ...(signal === undefined ? {} : { signal }),
239
+ });
240
+ } catch (error) {
241
+ if (signal?.aborted) {
242
+ throw error;
243
+ }
244
+
245
+ if (error instanceof HTTPError) {
246
+ throw new McpClientError(
247
+ `MCP request failed with HTTP ${error.response.status}: ${excerpt(stringifyErrorData(error.data))}`,
248
+ error.response.status
249
+ );
250
+ }
251
+
252
+ throw new McpClientError(`MCP request failed: ${Errors.describe(error)}`);
253
+ }
254
+ }
255
+
256
+ private createHeaders(sessionId: string | undefined): Headers {
257
+ const headers = new Headers({
258
+ accept: "application/json, text/event-stream",
259
+ "mcp-protocol-version": protocolVersion,
260
+ });
261
+
262
+ if (sessionId !== undefined) {
263
+ headers.set("mcp-session-id", sessionId);
264
+ }
265
+
266
+ for (const [name, value] of Object.entries(this.extraHeaders)) {
267
+ headers.set(name, value);
268
+ }
269
+
270
+ return headers;
271
+ }
272
+
273
+ private async readRpcResponse(
274
+ response: Response,
275
+ id: number
276
+ ): Promise<JsonRpcResponse> {
277
+ const contentType = response.headers.get("content-type") ?? "";
278
+
279
+ if (contentType.includes("text/event-stream")) {
280
+ return this.readSseRpcResponse(response, id);
281
+ }
282
+
283
+ const parsed = parseJson(await response.text());
284
+ const envelope = asRpcResponse(parsed);
285
+
286
+ if (envelope === undefined || envelope.id !== id) {
287
+ throw new McpClientError("MCP returned a malformed JSON-RPC envelope.");
288
+ }
289
+
290
+ return envelope;
291
+ }
292
+
293
+ private async readSseRpcResponse(
294
+ response: Response,
295
+ id: number
296
+ ): Promise<JsonRpcResponse> {
297
+ if (response.body === null) {
298
+ throw new McpClientError("MCP returned an empty SSE response.");
299
+ }
300
+
301
+ const reader = response.body.getReader();
302
+ const decoder = new TextDecoder();
303
+ let buffer = "";
304
+ let eventName = "message";
305
+ const dataLines: string[] = [];
306
+
307
+ const dispatch = (): JsonRpcResponse | undefined => {
308
+ const data = dataLines.join("\n");
309
+ const currentEventName = eventName;
310
+
311
+ dataLines.length = 0;
312
+ eventName = "message";
313
+
314
+ if (data.length === 0 || currentEventName !== "message") {
315
+ return undefined;
316
+ }
317
+
318
+ const envelope = asRpcResponse(parseJson(data));
319
+
320
+ if (envelope === undefined) {
321
+ throw new McpClientError("MCP returned a malformed JSON-RPC envelope.");
322
+ }
323
+
324
+ return envelope.id === id ? envelope : undefined;
325
+ };
326
+
327
+ try {
328
+ while (true) {
329
+ const read = await reader.read();
330
+
331
+ buffer += decoder.decode(read.value, { stream: !read.done });
332
+
333
+ while (true) {
334
+ const newlineIndex = buffer.indexOf("\n");
335
+
336
+ if (newlineIndex === -1) {
337
+ break;
338
+ }
339
+
340
+ const rawLine = buffer.slice(0, newlineIndex);
341
+ const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
342
+
343
+ buffer = buffer.slice(newlineIndex + 1);
344
+
345
+ if (line.length === 0) {
346
+ const envelope = dispatch();
347
+
348
+ if (envelope !== undefined) {
349
+ return envelope;
350
+ }
351
+
352
+ continue;
353
+ }
354
+
355
+ if (line.startsWith("event:")) {
356
+ eventName = line.slice("event:".length).trim();
357
+ continue;
358
+ }
359
+
360
+ if (line.startsWith("data:")) {
361
+ dataLines.push(line.slice("data:".length).trimStart());
362
+ }
363
+ }
364
+
365
+ if (read.done) {
366
+ const envelope = dispatch();
367
+
368
+ if (envelope !== undefined) {
369
+ return envelope;
370
+ }
371
+
372
+ break;
373
+ }
374
+ }
375
+ } finally {
376
+ await reader.cancel().catch(() => {});
377
+ }
378
+
379
+ throw new McpClientError(
380
+ "MCP SSE response did not include the expected message."
381
+ );
382
+ }
383
+ }
384
+
385
+ function isStaleSessionError(error: unknown): boolean {
386
+ return error instanceof McpClientError && error.status === 404;
387
+ }
388
+
389
+ function asRpcResponse(value: unknown): JsonRpcResponse | undefined {
390
+ const record = Json.asRecord(value);
391
+
392
+ if (record === undefined) {
393
+ return undefined;
394
+ }
395
+
396
+ return {
397
+ ...(record["id"] === undefined ? {} : { id: record["id"] }),
398
+ ...(record["result"] === undefined ? {} : { result: record["result"] }),
399
+ ...(record["error"] === undefined ? {} : { error: record["error"] }),
400
+ };
401
+ }
402
+
403
+ function parseJson(text: string): unknown {
404
+ try {
405
+ return JSON.parse(text);
406
+ } catch {
407
+ throw new McpClientError("MCP returned invalid JSON.");
408
+ }
409
+ }
410
+
411
+ function describeRpcError(error: unknown): string {
412
+ const record = Json.asRecord(error);
413
+ const message = record?.["message"];
414
+
415
+ if (typeof message === "string" && message.length > 0) {
416
+ return message;
417
+ }
418
+
419
+ return JSON.stringify(error);
420
+ }
421
+
422
+ function stringifyErrorData(data: unknown): string {
423
+ if (typeof data === "string") {
424
+ return data;
425
+ }
426
+
427
+ if (data === undefined) {
428
+ return "";
429
+ }
430
+
431
+ return JSON.stringify(data);
432
+ }
433
+
434
+ function excerpt(text: string): string {
435
+ const normalized = text.replace(/\s+/gu, " ").trim();
436
+
437
+ if (normalized.length === 0) {
438
+ return "<empty>";
439
+ }
440
+
441
+ return normalized.length > 200
442
+ ? `${normalized.slice(0, 200)}...`
443
+ : normalized;
444
+ }
@@ -0,0 +1,84 @@
1
+ const maxBytes = 32 * 1024;
2
+ const maxLineLength = 2000;
3
+
4
+ function truncateLine(line: string): string {
5
+ if (line.length <= maxLineLength) {
6
+ return line;
7
+ }
8
+
9
+ return `${line.slice(0, maxLineLength)}... (line truncated to ${maxLineLength} chars)`;
10
+ }
11
+
12
+ function truncateUtf8(
13
+ content: string,
14
+ limit: number = maxBytes
15
+ ): {
16
+ readonly body: string;
17
+ readonly returnedBytes: number;
18
+ readonly totalBytes: number;
19
+ readonly truncated: boolean;
20
+ } {
21
+ const totalBytes = Buffer.byteLength(content, "utf8");
22
+
23
+ if (totalBytes <= limit) {
24
+ return {
25
+ body: content,
26
+ returnedBytes: totalBytes,
27
+ totalBytes,
28
+ truncated: false,
29
+ };
30
+ }
31
+
32
+ const encoded = new TextEncoder().encode(content);
33
+ let cut = limit;
34
+ while (cut > 0 && ((encoded[cut] ?? 0) & 0xc0) === 0x80) {
35
+ cut -= 1;
36
+ }
37
+
38
+ const body = new TextDecoder("utf-8").decode(encoded.subarray(0, cut));
39
+
40
+ return { body, returnedBytes: cut, totalBytes, truncated: true };
41
+ }
42
+
43
+ function applyByteCap(
44
+ items: readonly string[],
45
+ options: {
46
+ readonly maxBytes?: number;
47
+ readonly separator?: string;
48
+ } = {}
49
+ ): {
50
+ readonly visible: readonly string[];
51
+ readonly droppedItems: number;
52
+ } {
53
+ const limit = options.maxBytes ?? maxBytes;
54
+ const separator = options.separator ?? "\n";
55
+ const separatorBytes = Buffer.byteLength(separator, "utf8");
56
+ const visible: string[] = [];
57
+ let bytes = 0;
58
+
59
+ for (const item of items) {
60
+ const itemBytes = Buffer.byteLength(item, "utf8");
61
+ const cost = visible.length === 0 ? itemBytes : separatorBytes + itemBytes;
62
+
63
+ if (visible.length > 0 && bytes + cost > limit) {
64
+ break;
65
+ }
66
+
67
+ visible.push(item);
68
+ bytes += cost;
69
+
70
+ if (bytes >= limit) {
71
+ break;
72
+ }
73
+ }
74
+
75
+ return { visible, droppedItems: items.length - visible.length };
76
+ }
77
+
78
+ export const OutputBudget = {
79
+ maxBytes,
80
+ maxLineLength,
81
+ truncateLine,
82
+ truncateUtf8,
83
+ applyByteCap,
84
+ };
@@ -0,0 +1,40 @@
1
+ export const ADD_FILE_MARKER = "*** Add File: ";
2
+ export const DELETE_FILE_MARKER = "*** Delete File: ";
3
+ export const UPDATE_FILE_MARKER = "*** Update File: ";
4
+ export const MOVE_TO_MARKER = "*** Move to:";
5
+
6
+ const FILE_MARKERS = [
7
+ ADD_FILE_MARKER,
8
+ DELETE_FILE_MARKER,
9
+ UPDATE_FILE_MARKER,
10
+ ] as const;
11
+
12
+ function firstPath(input: string): string | undefined {
13
+ for (const raw of input.split("\n")) {
14
+ const line = raw.trim();
15
+ for (const marker of FILE_MARKERS) {
16
+ if (line.startsWith(marker)) {
17
+ return cleanPath(line.slice(marker.length));
18
+ }
19
+ }
20
+ }
21
+ return undefined;
22
+ }
23
+
24
+ export function cleanPath(raw: string): string {
25
+ let path = raw.trim();
26
+ if (path.startsWith("@")) {
27
+ path = path.slice(1).trim();
28
+ }
29
+ if (path.length >= 2) {
30
+ const first = path[0]!;
31
+ const last = path.at(-1)!;
32
+ if ((first === '"' || first === "'" || first === "`") && first === last) {
33
+ path = path.slice(1, -1);
34
+ }
35
+ }
36
+ return path;
37
+ }
38
+
39
+ /** Fault-tolerant scan of V4A patch text for renderers; unrecognized lines are ignored. */
40
+ export const PatchSummary = { firstPath };
@@ -0,0 +1,80 @@
1
+ import { chmod, mkdir } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { isAbsolute, relative, resolve as pathResolve, sep } from "node:path";
4
+
5
+ function pimHomeDir(): string {
6
+ return expandHome(process.env.PIM_HOME_DIR ?? "~/.pim");
7
+ }
8
+
9
+ async function ensurePimHome(): Promise<string> {
10
+ const dir = pimHomeDir();
11
+ await mkdir(dir, { recursive: true, mode: 0o700 });
12
+ await chmod(dir, 0o700);
13
+ return dir;
14
+ }
15
+
16
+ function resolve(value: string, baseDir: string): string {
17
+ const expanded = expandHome(value);
18
+ return isAbsolute(expanded) ? expanded : pathResolve(baseDir, expanded);
19
+ }
20
+
21
+ function toForwardSlashes(path: string): string {
22
+ return sep === "/" ? path : path.split(sep).join("/");
23
+ }
24
+
25
+ function expandHome(value: string): string {
26
+ if (value === "~") {
27
+ return homedir();
28
+ }
29
+
30
+ if (value.startsWith("~/")) {
31
+ return pathResolve(homedir(), value.slice(2));
32
+ }
33
+
34
+ return value;
35
+ }
36
+
37
+ function abbreviateHome(path: string): string {
38
+ const home = homedir();
39
+ return home && path.startsWith(home) ? `~${path.slice(home.length)}` : path;
40
+ }
41
+
42
+ function displayRelative(path: string, cwd: string): string {
43
+ const rel = relative(cwd, path);
44
+
45
+ if (rel === "" || rel.startsWith("..") || isAbsolute(rel)) {
46
+ return path;
47
+ }
48
+
49
+ return rel;
50
+ }
51
+
52
+ function compare(left: string, right: string): number {
53
+ if (left < right) {
54
+ return -1;
55
+ }
56
+ if (left > right) {
57
+ return 1;
58
+ }
59
+ return 0;
60
+ }
61
+
62
+ function titleOr(
63
+ path: string | undefined,
64
+ cwd: string,
65
+ placeholder = "..."
66
+ ): string {
67
+ return path ? displayRelative(path, cwd) : placeholder;
68
+ }
69
+
70
+ export const Paths = {
71
+ pimHomeDir,
72
+ ensurePimHome,
73
+ resolve,
74
+ toForwardSlashes,
75
+ expandHome,
76
+ abbreviateHome,
77
+ displayRelative,
78
+ titleOr,
79
+ compare,
80
+ };