pim-agent 0.0.1 → 0.10.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.
- package/LICENSE +21 -0
- package/README.md +293 -7
- package/bin/pim.ts +151 -0
- package/package.json +82 -4
- package/packages/core/src/attachments/AttachmentStore.ts +99 -0
- package/packages/core/src/attachments/Attachments.ts +69 -0
- package/packages/core/src/extensions/CoreExtensions.ts +54 -0
- package/packages/core/src/extensions/apply-patch/coordinator.ts +53 -0
- package/packages/core/src/extensions/apply-patch/executor.ts +550 -0
- package/packages/core/src/extensions/apply-patch/index.ts +69 -0
- package/packages/core/src/extensions/apply-patch/matcher.ts +47 -0
- package/packages/core/src/extensions/apply-patch/model.ts +39 -0
- package/packages/core/src/extensions/apply-patch/parser.ts +379 -0
- package/packages/core/src/extensions/apply-patch/render.ts +177 -0
- package/packages/core/src/extensions/apply-patch/schema.ts +37 -0
- package/packages/core/src/extensions/apply-patch/types.ts +30 -0
- package/packages/core/src/extensions/bash/capture.ts +86 -0
- package/packages/core/src/extensions/bash/format.ts +110 -0
- package/packages/core/src/extensions/bash/image.ts +31 -0
- package/packages/core/src/extensions/bash/index.ts +88 -0
- package/packages/core/src/extensions/bash/render.ts +31 -0
- package/packages/core/src/extensions/bash/run.ts +221 -0
- package/packages/core/src/extensions/bash/schema.ts +64 -0
- package/packages/core/src/extensions/edit/edit.ts +366 -0
- package/packages/core/src/extensions/edit/index.ts +36 -0
- package/packages/core/src/extensions/edit/render.ts +16 -0
- package/packages/core/src/extensions/edit/schema.ts +37 -0
- package/packages/core/src/extensions/glob/glob.ts +34 -0
- package/packages/core/src/extensions/glob/index.ts +83 -0
- package/packages/core/src/extensions/glob/render.ts +72 -0
- package/packages/core/src/extensions/glob/schema.ts +66 -0
- package/packages/core/src/extensions/grep/grep.ts +256 -0
- package/packages/core/src/extensions/grep/index.ts +114 -0
- package/packages/core/src/extensions/grep/render.ts +271 -0
- package/packages/core/src/extensions/grep/schema.ts +92 -0
- package/packages/core/src/extensions/read/ImageMemory.ts +42 -0
- package/packages/core/src/extensions/read/image.ts +57 -0
- package/packages/core/src/extensions/read/index.ts +88 -0
- package/packages/core/src/extensions/read/read.ts +191 -0
- package/packages/core/src/extensions/read/render.ts +58 -0
- package/packages/core/src/extensions/read/schema.ts +49 -0
- package/packages/core/src/extensions/subagent/index.ts +33 -0
- package/packages/core/src/extensions/subagent/render.ts +127 -0
- package/packages/core/src/extensions/subagent/schema.ts +9 -0
- package/packages/core/src/extensions/subagent/subagent.ts +419 -0
- package/packages/core/src/extensions/system-prompt/index.ts +36 -0
- package/packages/core/src/extensions/system-prompt/prompt.ts +232 -0
- package/packages/core/src/extensions/todo/index.ts +123 -0
- package/packages/core/src/extensions/todo/render.ts +156 -0
- package/packages/core/src/extensions/todo/schema.ts +24 -0
- package/packages/core/src/extensions/todo/todo.ts +185 -0
- package/packages/core/src/extensions/web-fetch/JinaReaderClient.ts +187 -0
- package/packages/core/src/extensions/web-fetch/WebViewFetchClient.ts +183 -0
- package/packages/core/src/extensions/web-fetch/WebViewMarkdownSnapshot.ts +512 -0
- package/packages/core/src/extensions/web-fetch/fetch.ts +265 -0
- package/packages/core/src/extensions/web-fetch/image.ts +165 -0
- package/packages/core/src/extensions/web-fetch/index.ts +83 -0
- package/packages/core/src/extensions/web-fetch/render.ts +100 -0
- package/packages/core/src/extensions/web-fetch/schema.ts +46 -0
- package/packages/core/src/extensions/web-search/ExaMcpClient.ts +251 -0
- package/packages/core/src/extensions/web-search/SearchBreaker.ts +147 -0
- package/packages/core/src/extensions/web-search/SearchChain.ts +77 -0
- package/packages/core/src/extensions/web-search/index.ts +90 -0
- package/packages/core/src/extensions/web-search/providers/DuckDuckGoProvider.ts +195 -0
- package/packages/core/src/extensions/web-search/providers/ExaProvider.ts +54 -0
- package/packages/core/src/extensions/web-search/providers/FirecrawlProvider.ts +144 -0
- package/packages/core/src/extensions/web-search/providers/SearchProvider.ts +118 -0
- package/packages/core/src/extensions/web-search/render.ts +49 -0
- package/packages/core/src/extensions/web-search/schema.ts +29 -0
- package/packages/core/src/extensions/web-search/search.ts +23 -0
- package/packages/core/src/extensions/write/index.ts +58 -0
- package/packages/core/src/extensions/write/render.ts +16 -0
- package/packages/core/src/extensions/write/schema.ts +14 -0
- package/packages/core/src/extensions/write/write.ts +87 -0
- package/packages/core/src/picker/FilePickerSuggestionEngine.ts +14 -0
- package/packages/core/src/picker/InProcessFilePickerSuggestionEngine.ts +52 -0
- package/packages/core/src/picker/PickerItem.ts +6 -0
- package/packages/core/src/picker/PickerService.ts +132 -0
- package/packages/core/src/picker/RemoteFilePickerSuggestionEngine.ts +56 -0
- package/packages/core/src/picker/WorkerFilePickerSuggestionEngine.ts +268 -0
- package/packages/core/src/picker/catalog.ts +220 -0
- package/packages/core/src/picker/commandRanker.ts +17 -0
- package/packages/core/src/picker/filePickerWorker.ts +66 -0
- package/packages/core/src/picker/filePickerWorkerMessages.ts +39 -0
- package/packages/core/src/picker/ranker.ts +245 -0
- package/packages/core/src/picker/token.ts +77 -0
- package/packages/core/src/session/AgentRuntime.ts +85 -0
- package/packages/core/src/session/EventLog.ts +156 -0
- package/packages/core/src/session/MessageText.ts +21 -0
- package/packages/core/src/session/ReadCursors.ts +94 -0
- package/packages/core/src/session/SearchIndex.ts +752 -0
- package/packages/core/src/session/SearchTokens.ts +97 -0
- package/packages/core/src/session/SessionCache.ts +68 -0
- package/packages/core/src/session/SessionDigest.ts +161 -0
- package/packages/core/src/session/SessionHost.ts +892 -0
- package/packages/core/src/session/SessionLease.ts +279 -0
- package/packages/core/src/session/SessionMeta.ts +267 -0
- package/packages/core/src/session/SessionName.ts +18 -0
- package/packages/core/src/session/SessionRegistry.ts +244 -0
- package/packages/core/src/session/WriteMark.ts +51 -0
- package/packages/core/src/shared/Cli.ts +25 -0
- package/packages/core/src/shared/DaemonUnit.ts +13 -0
- package/packages/core/src/shared/DiffLines.ts +304 -0
- package/packages/core/src/shared/DiffPatch.ts +70 -0
- package/packages/core/src/shared/DiffRenderer.ts +283 -0
- package/packages/core/src/shared/DiffView.ts +29 -0
- package/packages/core/src/shared/Directories.ts +61 -0
- package/packages/core/src/shared/EditMatcher.ts +767 -0
- package/packages/core/src/shared/Errors.ts +9 -0
- package/packages/core/src/shared/ExtensionToggles.ts +130 -0
- package/packages/core/src/shared/FileEnumerator.ts +361 -0
- package/packages/core/src/shared/FileScanner.ts +57 -0
- package/packages/core/src/shared/FileWatch.ts +121 -0
- package/packages/core/src/shared/Format.ts +74 -0
- package/packages/core/src/shared/Fs.ts +91 -0
- package/packages/core/src/shared/FsErrors.ts +67 -0
- package/packages/core/src/shared/FuzzyMatcher.ts +71 -0
- package/packages/core/src/shared/Git.ts +530 -0
- package/packages/core/src/shared/GitMonitor.ts +221 -0
- package/packages/core/src/shared/GlobExclusions.ts +13 -0
- package/packages/core/src/shared/Http.ts +14 -0
- package/packages/core/src/shared/ImageMime.ts +36 -0
- package/packages/core/src/shared/Images.ts +357 -0
- package/packages/core/src/shared/Json.ts +19 -0
- package/packages/core/src/shared/Languages.ts +95 -0
- package/packages/core/src/shared/Levenshtein.ts +83 -0
- package/packages/core/src/shared/Lines.ts +79 -0
- package/packages/core/src/shared/McpClient.ts +444 -0
- package/packages/core/src/shared/OutputBudget.ts +84 -0
- package/packages/core/src/shared/PatchSummary.ts +40 -0
- package/packages/core/src/shared/Paths.ts +80 -0
- package/packages/core/src/shared/PimSettings.ts +128 -0
- package/packages/core/src/shared/PimVersion.ts +93 -0
- package/packages/core/src/shared/Pool.ts +26 -0
- package/packages/core/src/shared/Proc.ts +50 -0
- package/packages/core/src/shared/RateLimiter.ts +50 -0
- package/packages/core/src/shared/Renderer.ts +371 -0
- package/packages/core/src/shared/RepoDiff.ts +741 -0
- package/packages/core/src/shared/SafePath.ts +17 -0
- package/packages/core/src/shared/SearchRender.ts +82 -0
- package/packages/core/src/shared/SendFile.ts +23 -0
- package/packages/core/src/shared/SpillCache.ts +78 -0
- package/packages/core/src/shared/SubagentLogs.ts +102 -0
- package/packages/core/src/shared/Supervisor.ts +460 -0
- package/packages/core/src/shared/Surface.ts +2 -0
- package/packages/core/src/shared/Sweeper.ts +32 -0
- package/packages/core/src/shared/Tools.ts +801 -0
- package/packages/core/src/shared/Updater.ts +209 -0
- package/packages/core/src/view/AnsiPainter.ts +227 -0
- package/packages/core/src/view/BodyRenderer.ts +102 -0
- package/packages/core/src/view/DiffBlocks.ts +52 -0
- package/packages/core/src/view/DiffExpand.ts +214 -0
- package/packages/core/src/view/DiffLayout.ts +69 -0
- package/packages/core/src/view/DiffPairs.ts +47 -0
- package/packages/core/src/view/MarkdownPainter.ts +314 -0
- package/packages/core/src/view/MovePath.ts +70 -0
- package/packages/core/src/view/Painting.ts +182 -0
- package/packages/core/src/view/ViewBlock.ts +124 -0
- package/packages/daemon/src/Daemon.ts +63 -0
- package/packages/daemon/src/DaemonInstall.ts +115 -0
- package/packages/daemon/src/Surfaces.ts +66 -0
- package/packages/daemon/src/TelegramSurface.ts +30 -0
- package/packages/daemon/src/WebSurface.ts +56 -0
- package/packages/daemon/src/index.ts +37 -0
- package/packages/protocol/src/Command.ts +219 -0
- package/packages/protocol/src/Diff.ts +15 -0
- package/packages/protocol/src/ImageRoute.ts +23 -0
- package/packages/protocol/src/ServerEvent.ts +406 -0
- package/packages/server/src/AttachmentEndpoint.ts +184 -0
- package/packages/server/src/ClientConnection.ts +277 -0
- package/packages/server/src/ImageEndpoint.ts +36 -0
- package/packages/server/src/ProbeClient.ts +374 -0
- package/packages/server/src/Reloader.ts +93 -0
- package/packages/server/src/SendFileTool.ts +88 -0
- package/packages/server/src/SessionCatalogue.ts +456 -0
- package/packages/server/src/SessionProjection.ts +194 -0
- package/packages/server/src/SessionStream.ts +627 -0
- package/packages/server/src/StaticClient.ts +178 -0
- package/packages/server/src/WebOptions.ts +72 -0
- package/packages/server/src/WsGateway.ts +798 -0
- package/packages/server/src/probe.ts +200 -0
- package/packages/telegram/src/Bot.ts +229 -0
- package/packages/telegram/src/Commands.ts +768 -0
- package/packages/telegram/src/Config.ts +148 -0
- package/packages/telegram/src/Markdown.ts +235 -0
- package/packages/telegram/src/Message.ts +152 -0
- package/packages/telegram/src/Renderer.ts +456 -0
- package/packages/telegram/src/SendFileSchema.ts +18 -0
- package/packages/telegram/src/SendFileTool.ts +65 -0
- package/packages/telegram/src/Session.ts +253 -0
- package/packages/telegram/src/SessionRegistry.ts +126 -0
- package/packages/telegram/src/TaskScheduler.ts +296 -0
- package/packages/telegram/src/TaskSchema.ts +88 -0
- package/packages/telegram/src/TaskStore.ts +67 -0
- package/packages/telegram/src/TaskTool.ts +208 -0
- package/packages/telegram/src/TelegramHtml.ts +288 -0
- package/packages/telegram/src/TypingIndicator.ts +43 -0
- package/packages/telegram/src/UpdateConfirm.ts +61 -0
- package/packages/tui/src/extensions/_init/index.ts +144 -0
- package/packages/tui/src/extensions/command-picker/index.ts +75 -0
- package/packages/tui/src/extensions/file-picker/index.ts +156 -0
- package/packages/tui/src/extensions/footer/index.ts +93 -0
- package/packages/tui/src/extensions/footer/powerline.ts +87 -0
- package/packages/tui/src/extensions/footer/segments.ts +202 -0
- package/packages/tui/src/extensions/pim/index.ts +89 -0
- package/packages/tui/src/extensions/session-lease/index.ts +253 -0
- package/packages/tui/src/extensions/tps/index.ts +118 -0
- package/packages/tui/src/extensions/working-indicator/index.ts +75 -0
- package/packages/tui/src/extensions/wrapProvider.ts +18 -0
- package/packages/tui/src/themes/pim-dark.json +84 -0
- package/packages/tui/src/themes/pim-light.json +84 -0
- package/packages/tui/src/themes/themeCliArgs.ts +20 -0
- package/packages/web/dist/client/apple-touch-icon.png +0 -0
- package/packages/web/dist/client/assets/bash-LmRjUKlR.js +1 -0
- package/packages/web/dist/client/assets/c-DBVbJFpC.js +1 -0
- package/packages/web/dist/client/assets/clojure-CR7dvLJ4.js +1 -0
- package/packages/web/dist/client/assets/cmake-CgP6gB4W.js +1 -0
- package/packages/web/dist/client/assets/commit-mono-latin-300-italic-B48RJMeK.woff +0 -0
- package/packages/web/dist/client/assets/commit-mono-latin-300-italic-DBq5bJCE.woff2 +0 -0
- package/packages/web/dist/client/assets/commit-mono-latin-300-normal-B-iV2FbL.woff2 +0 -0
- package/packages/web/dist/client/assets/commit-mono-latin-300-normal-kM0OTZCv.woff +0 -0
- package/packages/web/dist/client/assets/commit-mono-latin-400-italic-BXinMwCi.woff +0 -0
- package/packages/web/dist/client/assets/commit-mono-latin-400-italic-DjSHLl2N.woff2 +0 -0
- package/packages/web/dist/client/assets/commit-mono-latin-400-normal-s0S3qwFW.woff +0 -0
- package/packages/web/dist/client/assets/commit-mono-latin-400-normal-wzhe4RuD.woff2 +0 -0
- package/packages/web/dist/client/assets/commit-mono-latin-600-normal-BuQDXT0M.woff +0 -0
- package/packages/web/dist/client/assets/commit-mono-latin-600-normal-SwgSWHaV.woff2 +0 -0
- package/packages/web/dist/client/assets/commit-mono-latin-700-normal-1-GU0IUE.woff +0 -0
- package/packages/web/dist/client/assets/commit-mono-latin-700-normal-DU8mrtj2.woff2 +0 -0
- package/packages/web/dist/client/assets/core-CA0aSzPu.js +3 -0
- package/packages/web/dist/client/assets/cpp-N2d8M7Nc.js +1 -0
- package/packages/web/dist/client/assets/csharp-yYt0Ikks.js +1 -0
- package/packages/web/dist/client/assets/css-D8M8GYaw.js +1 -0
- package/packages/web/dist/client/assets/diff-BhdZizse.js +1 -0
- package/packages/web/dist/client/assets/dockerfile-C0zufGsU.js +1 -0
- package/packages/web/dist/client/assets/elixir-DXIHlGsR.js +1 -0
- package/packages/web/dist/client/assets/erlang-BjYI8dhz.js +1 -0
- package/packages/web/dist/client/assets/go-CYstVVJV.js +1 -0
- package/packages/web/dist/client/assets/graphql-BHHhacaR.js +1 -0
- package/packages/web/dist/client/assets/haskell-DUkhXIda.js +1 -0
- package/packages/web/dist/client/assets/index-B9be-i9d.css +1 -0
- package/packages/web/dist/client/assets/index-CpinM5NK.js +45 -0
- package/packages/web/dist/client/assets/ini-B1CroMsf.js +1 -0
- package/packages/web/dist/client/assets/java-Dde-Wnb4.js +1 -0
- package/packages/web/dist/client/assets/javascript-8AmHuH5O.js +1 -0
- package/packages/web/dist/client/assets/json-PmyKWHmm.js +1 -0
- package/packages/web/dist/client/assets/kotlin-DgSK8bu-.js +2 -0
- package/packages/web/dist/client/assets/less-Bp0mgmpG.js +1 -0
- package/packages/web/dist/client/assets/lua-1dXYaL-H.js +1 -0
- package/packages/web/dist/client/assets/makefile-BX-o3EIz.js +1 -0
- package/packages/web/dist/client/assets/markdown-D6FrnlqJ.js +1 -0
- package/packages/web/dist/client/assets/ocaml-B-P1Uyq8.js +1 -0
- package/packages/web/dist/client/assets/perl-CvqMZYuf.js +1 -0
- package/packages/web/dist/client/assets/php-DuZl5e-L.js +2 -0
- package/packages/web/dist/client/assets/powershell-BUe16zE4.js +1 -0
- package/packages/web/dist/client/assets/protobuf-DGOW1qT8.js +1 -0
- package/packages/web/dist/client/assets/python-gHmu6VPG.js +1 -0
- package/packages/web/dist/client/assets/r-DNWPCnjT.js +1 -0
- package/packages/web/dist/client/assets/ruby-BGpZ3p4_.js +1 -0
- package/packages/web/dist/client/assets/rust-BIKPVoi6.js +1 -0
- package/packages/web/dist/client/assets/scala-C9lP4FWY.js +1 -0
- package/packages/web/dist/client/assets/scss-gOyOKiXd.js +1 -0
- package/packages/web/dist/client/assets/sql-DWRKyvJ-.js +1 -0
- package/packages/web/dist/client/assets/swift-BTn9__hL.js +1 -0
- package/packages/web/dist/client/assets/typescript-CxkPLmSc.js +1 -0
- package/packages/web/dist/client/assets/vim-Dhhcn_CA.js +1 -0
- package/packages/web/dist/client/assets/xml-COl5yFSC.js +1 -0
- package/packages/web/dist/client/assets/yaml-DjOhhVLG.js +1 -0
- package/packages/web/dist/client/favicon.svg +1 -0
- package/packages/web/dist/client/icon-192.png +0 -0
- package/packages/web/dist/client/icon-512.png +0 -0
- package/packages/web/dist/client/icon-maskable.png +0 -0
- package/packages/web/dist/client/index.html +24 -0
- package/packages/web/dist/client/manifest.webmanifest +21 -0
- package/packages/web/dist/client/wordmark.svg +1 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// Values are highlight.js language ids; keep this module import-free, the web bundle needs it.
|
|
2
|
+
|
|
3
|
+
const ALIASES: Readonly<Record<string, string>> = {
|
|
4
|
+
ts: "typescript",
|
|
5
|
+
mts: "typescript",
|
|
6
|
+
cts: "typescript",
|
|
7
|
+
tsx: "typescript",
|
|
8
|
+
js: "javascript",
|
|
9
|
+
jsx: "javascript",
|
|
10
|
+
mjs: "javascript",
|
|
11
|
+
cjs: "javascript",
|
|
12
|
+
py: "python",
|
|
13
|
+
rb: "ruby",
|
|
14
|
+
rs: "rust",
|
|
15
|
+
go: "go",
|
|
16
|
+
java: "java",
|
|
17
|
+
kt: "kotlin",
|
|
18
|
+
kts: "kotlin",
|
|
19
|
+
swift: "swift",
|
|
20
|
+
c: "c",
|
|
21
|
+
h: "c",
|
|
22
|
+
cpp: "cpp",
|
|
23
|
+
cc: "cpp",
|
|
24
|
+
cxx: "cpp",
|
|
25
|
+
hpp: "cpp",
|
|
26
|
+
cs: "csharp",
|
|
27
|
+
php: "php",
|
|
28
|
+
sh: "bash",
|
|
29
|
+
bash: "bash",
|
|
30
|
+
zsh: "bash",
|
|
31
|
+
shell: "bash",
|
|
32
|
+
fish: "fish",
|
|
33
|
+
ps1: "powershell",
|
|
34
|
+
sql: "sql",
|
|
35
|
+
html: "html",
|
|
36
|
+
htm: "html",
|
|
37
|
+
vue: "html",
|
|
38
|
+
svelte: "html",
|
|
39
|
+
css: "css",
|
|
40
|
+
scss: "scss",
|
|
41
|
+
sass: "scss",
|
|
42
|
+
less: "less",
|
|
43
|
+
json: "json",
|
|
44
|
+
jsonc: "json",
|
|
45
|
+
yaml: "yaml",
|
|
46
|
+
yml: "yaml",
|
|
47
|
+
toml: "toml",
|
|
48
|
+
ini: "ini",
|
|
49
|
+
xml: "xml",
|
|
50
|
+
svg: "xml",
|
|
51
|
+
md: "markdown",
|
|
52
|
+
markdown: "markdown",
|
|
53
|
+
dockerfile: "dockerfile",
|
|
54
|
+
containerfile: "dockerfile",
|
|
55
|
+
makefile: "makefile",
|
|
56
|
+
cmake: "cmake",
|
|
57
|
+
lua: "lua",
|
|
58
|
+
perl: "perl",
|
|
59
|
+
pl: "perl",
|
|
60
|
+
r: "r",
|
|
61
|
+
scala: "scala",
|
|
62
|
+
clj: "clojure",
|
|
63
|
+
ex: "elixir",
|
|
64
|
+
exs: "elixir",
|
|
65
|
+
erl: "erlang",
|
|
66
|
+
hs: "haskell",
|
|
67
|
+
ml: "ocaml",
|
|
68
|
+
vim: "vim",
|
|
69
|
+
graphql: "graphql",
|
|
70
|
+
gql: "graphql",
|
|
71
|
+
proto: "protobuf",
|
|
72
|
+
tf: "hcl",
|
|
73
|
+
hcl: "hcl",
|
|
74
|
+
diff: "diff",
|
|
75
|
+
patch: "diff",
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const CANONICAL: ReadonlySet<string> = new Set(Object.values(ALIASES));
|
|
79
|
+
|
|
80
|
+
// Never guess a language: auto-detection paints ordinary prose as keywords.
|
|
81
|
+
function resolve(name: string | undefined): string | undefined {
|
|
82
|
+
if (name === undefined) {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const key = name.trim().toLowerCase();
|
|
87
|
+
return CANONICAL.has(key) ? key : ALIASES[key];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function fromPath(path: string): string | undefined {
|
|
91
|
+
const base = path.split(/[/\\]/).pop() ?? "";
|
|
92
|
+
return resolve(base.includes(".") ? base.split(".").pop() : base);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export const Languages = { resolve, fromPath };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
function distance(left: string, right: string): number {
|
|
2
|
+
if (left === right) {
|
|
3
|
+
return 0;
|
|
4
|
+
}
|
|
5
|
+
if (left.length === 0 || right.length === 0) {
|
|
6
|
+
return Math.max(left.length, right.length);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
10
|
+
let current = Array.from({ length: right.length + 1 }, () => 0);
|
|
11
|
+
|
|
12
|
+
for (let leftIndex = 1; leftIndex <= left.length; leftIndex += 1) {
|
|
13
|
+
current[0] = leftIndex;
|
|
14
|
+
|
|
15
|
+
for (let rightIndex = 1; rightIndex <= right.length; rightIndex += 1) {
|
|
16
|
+
const cost = left[leftIndex - 1] === right[rightIndex - 1] ? 0 : 1;
|
|
17
|
+
current[rightIndex] = Math.min(
|
|
18
|
+
previous[rightIndex]! + 1,
|
|
19
|
+
current[rightIndex - 1]! + 1,
|
|
20
|
+
previous[rightIndex - 1]! + cost
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
[previous, current] = [current, previous];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return previous[right.length] ?? 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Damerau–Levenshtein, where a transposition is one edit rather than two, and
|
|
32
|
+
* a pair that cannot reach `max` answers `max + 1` without finishing the
|
|
33
|
+
* matrix — the shape a vocabulary scan wants, since almost every term it asks
|
|
34
|
+
* about is nowhere near.
|
|
35
|
+
*/
|
|
36
|
+
function damerau(left: string, right: string, max: number): number {
|
|
37
|
+
const over = max + 1;
|
|
38
|
+
if (left === right) {
|
|
39
|
+
return 0;
|
|
40
|
+
}
|
|
41
|
+
if (Math.abs(left.length - right.length) > max) {
|
|
42
|
+
return over;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const width = right.length + 1;
|
|
46
|
+
let before = Array.from({ length: width }, () => 0);
|
|
47
|
+
let previous = Array.from({ length: width }, (_, index) => index);
|
|
48
|
+
let current = Array.from({ length: width }, () => 0);
|
|
49
|
+
|
|
50
|
+
for (let leftIndex = 1; leftIndex <= left.length; leftIndex += 1) {
|
|
51
|
+
current[0] = leftIndex;
|
|
52
|
+
let best = leftIndex;
|
|
53
|
+
|
|
54
|
+
for (let rightIndex = 1; rightIndex <= right.length; rightIndex += 1) {
|
|
55
|
+
const cost = left[leftIndex - 1] === right[rightIndex - 1] ? 0 : 1;
|
|
56
|
+
let value = Math.min(
|
|
57
|
+
previous[rightIndex]! + 1,
|
|
58
|
+
current[rightIndex - 1]! + 1,
|
|
59
|
+
previous[rightIndex - 1]! + cost
|
|
60
|
+
);
|
|
61
|
+
if (
|
|
62
|
+
leftIndex > 1 &&
|
|
63
|
+
rightIndex > 1 &&
|
|
64
|
+
left[leftIndex - 1] === right[rightIndex - 2] &&
|
|
65
|
+
left[leftIndex - 2] === right[rightIndex - 1]
|
|
66
|
+
) {
|
|
67
|
+
value = Math.min(value, before[rightIndex - 2]! + 1);
|
|
68
|
+
}
|
|
69
|
+
current[rightIndex] = value;
|
|
70
|
+
best = Math.min(best, value);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (best > max) {
|
|
74
|
+
return over;
|
|
75
|
+
}
|
|
76
|
+
[before, previous, current] = [previous, current, before];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const found = previous[right.length]!;
|
|
80
|
+
return found > max ? over : found;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const Levenshtein = { distance, damerau };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const utf8Bom = "\uFEFF";
|
|
2
|
+
const utf8BomBytes = new Uint8Array([0xef, 0xbb, 0xbf]);
|
|
3
|
+
|
|
4
|
+
/** How far in a NUL is still evidence of a binary file rather than text. */
|
|
5
|
+
const binarySniffBytes = 8192;
|
|
6
|
+
|
|
7
|
+
function normalize(content: string): string {
|
|
8
|
+
return stripUtf8Bom(content).replaceAll("\r\n", "\n").replaceAll("\r", "\n");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function splitNormalized(normalized: string): readonly string[] {
|
|
12
|
+
if (normalized.length === 0) {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const parts = normalized.split("\n");
|
|
17
|
+
|
|
18
|
+
if (parts.at(-1) === "") {
|
|
19
|
+
parts.pop();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return parts;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function split(content: string): readonly string[] {
|
|
26
|
+
return splitNormalized(normalize(content));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function continuationLine(head: string): number {
|
|
30
|
+
const { lines, hasTrailingNewline } = splitWithTrailingNewline(head);
|
|
31
|
+
return Math.max(1, lines.length + (hasTrailingNewline ? 1 : 0));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function splitWithTrailingNewline(content: string): {
|
|
35
|
+
readonly lines: readonly string[];
|
|
36
|
+
readonly hasTrailingNewline: boolean;
|
|
37
|
+
} {
|
|
38
|
+
const normalized = normalize(content);
|
|
39
|
+
return {
|
|
40
|
+
lines: splitNormalized(normalized),
|
|
41
|
+
hasTrailingNewline: normalized.endsWith("\n"),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function stripUtf8Bom(content: string): string {
|
|
46
|
+
return content.startsWith(utf8Bom) ? content.slice(1) : content;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function hasUtf8Bom(bytes: Uint8Array): boolean {
|
|
50
|
+
return (
|
|
51
|
+
bytes[0] === utf8BomBytes[0] &&
|
|
52
|
+
bytes[1] === utf8BomBytes[1] &&
|
|
53
|
+
bytes[2] === utf8BomBytes[2]
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function isBinaryBytes(bytes: Uint8Array): boolean {
|
|
58
|
+
return bytes.subarray(0, binarySniffBytes).includes(0);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function isBinary(file: Bun.BunFile): Promise<boolean> {
|
|
62
|
+
return isBinaryBytes(
|
|
63
|
+
new Uint8Array(await file.slice(0, binarySniffBytes).arrayBuffer())
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const Lines = {
|
|
68
|
+
utf8Bom,
|
|
69
|
+
binarySniffBytes,
|
|
70
|
+
normalize,
|
|
71
|
+
split,
|
|
72
|
+
splitNormalized,
|
|
73
|
+
continuationLine,
|
|
74
|
+
splitWithTrailingNewline,
|
|
75
|
+
stripUtf8Bom,
|
|
76
|
+
hasUtf8Bom,
|
|
77
|
+
isBinaryBytes,
|
|
78
|
+
isBinary,
|
|
79
|
+
};
|
|
@@ -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
|
+
}
|