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.
- 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 +51 -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 +216 -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 +28 -0
- package/packages/core/src/extensions/system-prompt/prompt.ts +228 -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 +110 -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 +70 -0
- package/packages/core/src/session/AgentRuntime.ts +85 -0
- package/packages/core/src/session/EventLog.ts +213 -0
- package/packages/core/src/session/MessageText.ts +21 -0
- package/packages/core/src/session/ReadCursors.ts +94 -0
- package/packages/core/src/session/SessionCache.ts +68 -0
- package/packages/core/src/session/SessionHost.ts +876 -0
- package/packages/core/src/session/SessionLease.ts +279 -0
- package/packages/core/src/session/SessionRegistry.ts +169 -0
- package/packages/core/src/session/WriteMark.ts +37 -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/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 +107 -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 +89 -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 +30 -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 +30 -0
- package/packages/core/src/shared/RateLimiter.ts +50 -0
- package/packages/core/src/shared/Renderer.ts +371 -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/Sweeper.ts +32 -0
- package/packages/core/src/shared/Tools.ts +801 -0
- package/packages/core/src/shared/Updater.ts +193 -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/DiffLayout.ts +69 -0
- package/packages/core/src/view/MarkdownPainter.ts +314 -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 +57 -0
- package/packages/daemon/src/index.ts +37 -0
- package/packages/protocol/src/Command.ts +92 -0
- package/packages/protocol/src/ImageRoute.ts +23 -0
- package/packages/protocol/src/Protocol.ts +6 -0
- package/packages/protocol/src/ServerEvent.ts +251 -0
- package/packages/server/src/AttachmentEndpoint.ts +184 -0
- package/packages/server/src/ClientConnection.ts +265 -0
- package/packages/server/src/ImageEndpoint.ts +36 -0
- package/packages/server/src/ProbeClient.ts +312 -0
- package/packages/server/src/Reloader.ts +93 -0
- package/packages/server/src/SendFileTool.ts +88 -0
- package/packages/server/src/SessionCatalogue.ts +202 -0
- package/packages/server/src/SessionProjection.ts +194 -0
- package/packages/server/src/SessionStream.ts +601 -0
- package/packages/server/src/StaticClient.ts +95 -0
- package/packages/server/src/WebOptions.ts +72 -0
- package/packages/server/src/WsGateway.ts +563 -0
- package/packages/server/src/probe.ts +146 -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 +252 -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 +43 -0
- package/packages/tui/src/extensions/file-picker/index.ts +156 -0
- package/packages/tui/src/extensions/footer/index.ts +117 -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 +248 -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-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-CGdQx9la.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-BMNwleWO.js +40 -0
- package/packages/web/dist/client/assets/index-CqjB2RPr.css +1 -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,128 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { type Static, Type } from "typebox";
|
|
3
|
+
import { Value } from "typebox/value";
|
|
4
|
+
|
|
5
|
+
import { Fs } from "./Fs";
|
|
6
|
+
import { Paths } from "./Paths";
|
|
7
|
+
|
|
8
|
+
const Schema = Type.Object({
|
|
9
|
+
extensions: Type.Object(
|
|
10
|
+
{
|
|
11
|
+
toggles: Type.Record(Type.String(), Type.Boolean(), { default: {} }),
|
|
12
|
+
},
|
|
13
|
+
{ default: { toggles: {} } }
|
|
14
|
+
),
|
|
15
|
+
exa: Type.Object(
|
|
16
|
+
{
|
|
17
|
+
apiKey: Type.Optional(Type.String()),
|
|
18
|
+
},
|
|
19
|
+
{ default: {} }
|
|
20
|
+
),
|
|
21
|
+
jina: Type.Object(
|
|
22
|
+
{
|
|
23
|
+
apiKey: Type.Optional(Type.String()),
|
|
24
|
+
},
|
|
25
|
+
{ default: {} }
|
|
26
|
+
),
|
|
27
|
+
firecrawl: Type.Object(
|
|
28
|
+
{
|
|
29
|
+
apiKey: Type.Optional(Type.String()),
|
|
30
|
+
},
|
|
31
|
+
{ default: {} }
|
|
32
|
+
),
|
|
33
|
+
read: Type.Object(
|
|
34
|
+
{
|
|
35
|
+
dedupImages: Type.Boolean({ default: true }),
|
|
36
|
+
},
|
|
37
|
+
{ default: { dedupImages: true } }
|
|
38
|
+
),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
type Settings = Static<typeof Schema>;
|
|
42
|
+
|
|
43
|
+
let loaded: Promise<Settings> | undefined;
|
|
44
|
+
let loadedPath: string | undefined;
|
|
45
|
+
const writes = Fs.serialised();
|
|
46
|
+
|
|
47
|
+
function path(): string {
|
|
48
|
+
return join(Paths.pimHomeDir(), "settings.json");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function load(): Promise<Settings> {
|
|
52
|
+
const settingsPath = path();
|
|
53
|
+
if (loadedPath !== settingsPath) {
|
|
54
|
+
loaded = undefined;
|
|
55
|
+
loadedPath = settingsPath;
|
|
56
|
+
}
|
|
57
|
+
loaded ??= (async () => {
|
|
58
|
+
const raw = await Fs.readJsonOr<unknown>(settingsPath, {});
|
|
59
|
+
const filled = Value.Default(Schema, raw);
|
|
60
|
+
return Value.Check(Schema, filled) ? filled : Value.Create(Schema);
|
|
61
|
+
})();
|
|
62
|
+
return loaded;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function getExaApiKey(): Promise<string | undefined> {
|
|
66
|
+
return (
|
|
67
|
+
normalize(process.env["EXA_API_KEY"]) ??
|
|
68
|
+
normalize((await get("exa")).apiKey)
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function getJinaApiKey(): Promise<string | undefined> {
|
|
73
|
+
return (
|
|
74
|
+
normalize(process.env["JINA_API_KEY"]) ??
|
|
75
|
+
normalize((await get("jina")).apiKey)
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function getFirecrawlApiKey(): Promise<string | undefined> {
|
|
80
|
+
return (
|
|
81
|
+
normalize(process.env["FIRECRAWL_API_KEY"]) ??
|
|
82
|
+
normalize((await get("firecrawl")).apiKey)
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function normalize(value: string | undefined): string | undefined {
|
|
87
|
+
const trimmed = value?.trim();
|
|
88
|
+
return trimmed === undefined || trimmed.length === 0 ? undefined : trimmed;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function get<K extends keyof Settings>(key: K): Promise<Settings[K]> {
|
|
92
|
+
return (await load())[key];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function set<K extends keyof Settings>(
|
|
96
|
+
key: K,
|
|
97
|
+
value: Settings[K]
|
|
98
|
+
): Promise<void> {
|
|
99
|
+
return update(key, () => value);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function update<K extends keyof Settings>(
|
|
103
|
+
key: K,
|
|
104
|
+
mutate: (current: Settings[K]) => Settings[K]
|
|
105
|
+
): Promise<void> {
|
|
106
|
+
return writes.run(async () => {
|
|
107
|
+
const current = await load();
|
|
108
|
+
const next: Settings = { ...current, [key]: mutate(current[key]) };
|
|
109
|
+
if (!Value.Check(Schema, next)) {
|
|
110
|
+
throw new Error(`Invalid value for pim setting "${String(key)}"`);
|
|
111
|
+
}
|
|
112
|
+
const settingsPath = path();
|
|
113
|
+
loaded = Promise.resolve(next);
|
|
114
|
+
loadedPath = settingsPath;
|
|
115
|
+
await Paths.ensurePimHome();
|
|
116
|
+
await Fs.writeJson(settingsPath, next);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export const PimSettings = {
|
|
121
|
+
path,
|
|
122
|
+
getExaApiKey,
|
|
123
|
+
getJinaApiKey,
|
|
124
|
+
getFirecrawlApiKey,
|
|
125
|
+
get,
|
|
126
|
+
set,
|
|
127
|
+
update,
|
|
128
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { Fs } from "./Fs";
|
|
2
|
+
import { createKy } from "./Http";
|
|
3
|
+
|
|
4
|
+
const REGISTRY = "https://registry.npmjs.org";
|
|
5
|
+
const DEFAULT_TIMEOUT_MS = 5_000;
|
|
6
|
+
|
|
7
|
+
type Manifest = {
|
|
8
|
+
readonly name: string;
|
|
9
|
+
readonly version: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type LatestOptions = {
|
|
13
|
+
readonly fetch?: typeof fetch;
|
|
14
|
+
readonly timeoutMs?: number;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
async function read(url: URL): Promise<Manifest> {
|
|
18
|
+
const pkg = await Fs.readJsonOr<Partial<Manifest> | null>(url, null);
|
|
19
|
+
return {
|
|
20
|
+
name: typeof pkg?.name === "string" ? pkg.name : "?",
|
|
21
|
+
version: typeof pkg?.version === "string" ? pkg.version : "?",
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function self(): Promise<Manifest> {
|
|
26
|
+
return read(new URL("../../../../package.json", import.meta.url));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function current(): Promise<string> {
|
|
30
|
+
return (await self()).version;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The npm package this pim was published as. */
|
|
34
|
+
async function name(): Promise<string> {
|
|
35
|
+
return (await self()).name;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** The pi that ships inside pim's install tree. */
|
|
39
|
+
async function pi(): Promise<string> {
|
|
40
|
+
return (
|
|
41
|
+
await read(
|
|
42
|
+
new URL(
|
|
43
|
+
import.meta.resolve("@earendil-works/pi-coding-agent/package.json")
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
).version;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function latest(
|
|
50
|
+
options: LatestOptions = {}
|
|
51
|
+
): Promise<string | undefined> {
|
|
52
|
+
const pkg = await self();
|
|
53
|
+
const client = createKy(options.fetch);
|
|
54
|
+
try {
|
|
55
|
+
const release = await client(
|
|
56
|
+
`${REGISTRY}/${pkg.name.replace("/", "%2f")}/latest`,
|
|
57
|
+
{
|
|
58
|
+
timeout: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
59
|
+
headers: { accept: "application/json" },
|
|
60
|
+
}
|
|
61
|
+
).json<{ readonly version?: unknown }>();
|
|
62
|
+
return typeof release.version === "string" && release.version.trim()
|
|
63
|
+
? release.version.trim()
|
|
64
|
+
: undefined;
|
|
65
|
+
} catch {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function core(version: string): readonly number[] | undefined {
|
|
71
|
+
const parts = version.trim().split(/[-+]/)[0]!.split(".");
|
|
72
|
+
const numbers = parts.map(Number);
|
|
73
|
+
return parts.length === 3 &&
|
|
74
|
+
numbers.every((part) => Number.isInteger(part) && part >= 0)
|
|
75
|
+
? numbers
|
|
76
|
+
: undefined;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function isNewer(candidate: string, installed: string): boolean {
|
|
80
|
+
const left = core(candidate);
|
|
81
|
+
const right = core(installed);
|
|
82
|
+
if (!left || !right) {
|
|
83
|
+
return candidate.trim() !== installed.trim();
|
|
84
|
+
}
|
|
85
|
+
for (const [index, part] of left.entries()) {
|
|
86
|
+
if (part !== right[index]) {
|
|
87
|
+
return part > right[index]!;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return installed.includes("-") && !candidate.includes("-");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export const PimVersion = { current, name, pi, latest, isNewer };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
async function mapPooled<T, R>(
|
|
2
|
+
items: readonly T[],
|
|
3
|
+
limit: number,
|
|
4
|
+
fn: (item: T, index: number) => Promise<R>
|
|
5
|
+
): Promise<R[]> {
|
|
6
|
+
const results: R[] = Array.from({ length: items.length });
|
|
7
|
+
let next = 0;
|
|
8
|
+
|
|
9
|
+
const worker = async (): Promise<void> => {
|
|
10
|
+
while (next < items.length) {
|
|
11
|
+
const index = next;
|
|
12
|
+
next += 1;
|
|
13
|
+
results[index] = await fn(items[index] as T, index);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
await Promise.all(
|
|
18
|
+
Array.from({ length: Math.min(Math.max(limit, 1), items.length) }, worker)
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
return results;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const Pool = {
|
|
25
|
+
mapPooled,
|
|
26
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type ProcOptions = {
|
|
2
|
+
readonly cwd?: string | undefined;
|
|
3
|
+
readonly stdout?: "pipe" | "inherit";
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export type ProcResult = {
|
|
7
|
+
readonly code: number;
|
|
8
|
+
readonly stdout: string;
|
|
9
|
+
readonly stderr: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
async function run(
|
|
13
|
+
cmd: ReadonlyArray<string>,
|
|
14
|
+
options: ProcOptions = {}
|
|
15
|
+
): Promise<ProcResult> {
|
|
16
|
+
const proc = Bun.spawn([...cmd], {
|
|
17
|
+
cwd: options.cwd,
|
|
18
|
+
stdout: options.stdout ?? "pipe",
|
|
19
|
+
stderr: "pipe",
|
|
20
|
+
});
|
|
21
|
+
const [stdout, stderr] = await Promise.all([
|
|
22
|
+
proc.stdout instanceof ReadableStream
|
|
23
|
+
? new Response(proc.stdout).text()
|
|
24
|
+
: "",
|
|
25
|
+
new Response(proc.stderr).text(),
|
|
26
|
+
]);
|
|
27
|
+
return { code: await proc.exited, stdout, stderr };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const Proc = { run };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export type RateLimiterOptions = {
|
|
2
|
+
readonly maxRequests: number;
|
|
3
|
+
readonly windowMs: number;
|
|
4
|
+
readonly now?: () => number;
|
|
5
|
+
readonly sleep?: (ms: number) => Promise<void>;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export class RateLimiter {
|
|
9
|
+
private readonly maxRequests: number;
|
|
10
|
+
private readonly windowMs: number;
|
|
11
|
+
private readonly now: () => number;
|
|
12
|
+
private readonly sleep: (ms: number) => Promise<void>;
|
|
13
|
+
private readonly timestamps: number[] = [];
|
|
14
|
+
private tail: Promise<void> = Promise.resolve();
|
|
15
|
+
|
|
16
|
+
public constructor(options: RateLimiterOptions) {
|
|
17
|
+
this.maxRequests = Math.max(1, options.maxRequests);
|
|
18
|
+
this.windowMs = options.windowMs;
|
|
19
|
+
this.now = options.now ?? Date.now;
|
|
20
|
+
this.sleep = options.sleep ?? ((ms) => Bun.sleep(ms));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public acquire(): Promise<void> {
|
|
24
|
+
const reserved = this.tail.then(() => this.reserve());
|
|
25
|
+
this.tail = reserved.catch(() => {});
|
|
26
|
+
|
|
27
|
+
return reserved;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private async reserve(): Promise<void> {
|
|
31
|
+
while (true) {
|
|
32
|
+
const now = this.now();
|
|
33
|
+
const threshold = now - this.windowMs;
|
|
34
|
+
|
|
35
|
+
while (this.timestamps.length > 0 && this.timestamps[0]! <= threshold) {
|
|
36
|
+
this.timestamps.shift();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (this.timestamps.length < this.maxRequests) {
|
|
40
|
+
this.timestamps.push(now);
|
|
41
|
+
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const waitMs = this.timestamps[0]! + this.windowMs - now;
|
|
46
|
+
|
|
47
|
+
await this.sleep(Math.max(0, waitMs));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AgentToolResult,
|
|
3
|
+
Theme,
|
|
4
|
+
ThemeColor,
|
|
5
|
+
ToolRenderResultOptions,
|
|
6
|
+
} from "@earendil-works/pi-coding-agent";
|
|
7
|
+
import type { MarkdownTheme } from "@earendil-works/pi-tui";
|
|
8
|
+
import {
|
|
9
|
+
type Component,
|
|
10
|
+
Container,
|
|
11
|
+
Markdown,
|
|
12
|
+
visibleWidth,
|
|
13
|
+
wrapTextWithAnsi,
|
|
14
|
+
} from "@earendil-works/pi-tui";
|
|
15
|
+
|
|
16
|
+
export type RenderContext = {
|
|
17
|
+
readonly lastComponent: Component | undefined;
|
|
18
|
+
readonly isPartial: boolean;
|
|
19
|
+
readonly isError: boolean;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type MarkerStatus = "warning" | "error" | "success";
|
|
23
|
+
|
|
24
|
+
export type PrefixSpec = {
|
|
25
|
+
readonly prefix: string;
|
|
26
|
+
readonly width: number;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const GAPPED_PREFIX: PrefixSpec = {
|
|
30
|
+
prefix: " │ ",
|
|
31
|
+
width: 3,
|
|
32
|
+
};
|
|
33
|
+
const TIGHT_PREFIX: PrefixSpec = {
|
|
34
|
+
prefix: " │",
|
|
35
|
+
width: 2,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
class ToolTitle implements Component {
|
|
39
|
+
private text = "";
|
|
40
|
+
private theme: Theme | undefined;
|
|
41
|
+
|
|
42
|
+
public setText(text: string, theme: Theme): void {
|
|
43
|
+
this.text = text;
|
|
44
|
+
this.theme = theme;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public render(width: number): string[] {
|
|
48
|
+
if (!this.text || this.text.trim() === "") {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const theme = this.theme;
|
|
53
|
+
const normalized = this.text.replace(/\t/g, " ");
|
|
54
|
+
const lines = wrapTextWithAnsi(normalized, Math.max(1, width));
|
|
55
|
+
|
|
56
|
+
if (lines.length <= 1 || theme === undefined) {
|
|
57
|
+
return lines.map((line) => padLine(line, width));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const inner = Math.max(1, width - GAPPED_PREFIX.width);
|
|
61
|
+
const out = [padLine(lines[0] ?? "", width)];
|
|
62
|
+
|
|
63
|
+
for (const logical of lines.slice(1)) {
|
|
64
|
+
for (const wrapped of wrapTextWithAnsi(logical, inner)) {
|
|
65
|
+
out.push(
|
|
66
|
+
padLine(theme.fg("toolOutput", GAPPED_PREFIX.prefix) + wrapped, width)
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public invalidate(): void {}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
class MarkdownTitle implements Component {
|
|
78
|
+
private prefix = "";
|
|
79
|
+
private title = "";
|
|
80
|
+
private theme: Theme | undefined;
|
|
81
|
+
|
|
82
|
+
public set(args: {
|
|
83
|
+
readonly prefix: string;
|
|
84
|
+
readonly title: string;
|
|
85
|
+
readonly theme: Theme;
|
|
86
|
+
}): void {
|
|
87
|
+
this.prefix = args.prefix;
|
|
88
|
+
this.title = args.title;
|
|
89
|
+
this.theme = args.theme;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public render(width: number): string[] {
|
|
93
|
+
const theme = this.theme;
|
|
94
|
+
if (!theme) {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const inner = Math.max(1, width - visibleWidth(this.prefix));
|
|
99
|
+
const titleLines = markdownLines({
|
|
100
|
+
text: this.title,
|
|
101
|
+
theme,
|
|
102
|
+
width: inner,
|
|
103
|
+
});
|
|
104
|
+
const lines = titleLines.length > 0 ? titleLines : [""];
|
|
105
|
+
const out = [padLine(this.prefix + (lines[0] ?? ""), width)];
|
|
106
|
+
|
|
107
|
+
for (const line of lines.slice(1)) {
|
|
108
|
+
out.push(
|
|
109
|
+
padLine(theme.fg("toolOutput", GAPPED_PREFIX.prefix) + line, width)
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
public invalidate(): void {}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function padLine(line: string, width: number): string {
|
|
120
|
+
return line + " ".repeat(Math.max(0, width - visibleWidth(line)));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function makeMarkdown(
|
|
124
|
+
text: string,
|
|
125
|
+
theme: Theme,
|
|
126
|
+
lineColor: ThemeColor | undefined
|
|
127
|
+
): Markdown {
|
|
128
|
+
return new Markdown(
|
|
129
|
+
text,
|
|
130
|
+
0,
|
|
131
|
+
0,
|
|
132
|
+
markdownTheme(theme),
|
|
133
|
+
lineColor ? { color: (t: string) => theme.fg(lineColor, t) } : undefined
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function markdownTheme(theme: Theme): MarkdownTheme {
|
|
138
|
+
return {
|
|
139
|
+
heading: (text: string) => theme.fg("mdHeading", text),
|
|
140
|
+
link: (text: string) => theme.fg("mdLink", text),
|
|
141
|
+
linkUrl: (text: string) => theme.fg("mdLinkUrl", text),
|
|
142
|
+
code: (text: string) => theme.fg("mdCode", text),
|
|
143
|
+
codeBlock: (text: string) => theme.fg("mdCodeBlock", text),
|
|
144
|
+
codeBlockBorder: (text: string) => theme.fg("mdCodeBlockBorder", text),
|
|
145
|
+
quote: (text: string) => theme.fg("mdQuote", text),
|
|
146
|
+
quoteBorder: (text: string) => theme.fg("mdQuoteBorder", text),
|
|
147
|
+
hr: (text: string) => theme.fg("mdHr", text),
|
|
148
|
+
listBullet: (text: string) => theme.fg("mdListBullet", text),
|
|
149
|
+
bold: (text: string) => theme.bold(text),
|
|
150
|
+
italic: (text: string) => theme.italic(text),
|
|
151
|
+
underline: (text: string) => theme.underline(text),
|
|
152
|
+
strikethrough: (text: string) => theme.strikethrough(text),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function markerColorFor(isPartial: boolean, isError: boolean): MarkerStatus {
|
|
157
|
+
if (isPartial) {
|
|
158
|
+
return "warning";
|
|
159
|
+
}
|
|
160
|
+
if (isError) {
|
|
161
|
+
return "error";
|
|
162
|
+
}
|
|
163
|
+
return "success";
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function firstText(
|
|
167
|
+
result:
|
|
168
|
+
| {
|
|
169
|
+
readonly content?: ReadonlyArray<{
|
|
170
|
+
readonly type: string;
|
|
171
|
+
readonly text?: string;
|
|
172
|
+
}>;
|
|
173
|
+
}
|
|
174
|
+
| undefined
|
|
175
|
+
): string {
|
|
176
|
+
const first = result?.content?.[0];
|
|
177
|
+
return first && "text" in first ? (first.text ?? "") : "";
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function extractErrorText(
|
|
181
|
+
result: {
|
|
182
|
+
readonly content?: ReadonlyArray<{
|
|
183
|
+
readonly type: string;
|
|
184
|
+
readonly text?: string;
|
|
185
|
+
}>;
|
|
186
|
+
},
|
|
187
|
+
fallback: string
|
|
188
|
+
): string {
|
|
189
|
+
const text = (result.content ?? [])
|
|
190
|
+
.filter((item) => item.type === "text")
|
|
191
|
+
.map((item) => item.text ?? "")
|
|
192
|
+
.join("\n")
|
|
193
|
+
.trim();
|
|
194
|
+
|
|
195
|
+
return text || fallback;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function toolTitleText(args: {
|
|
199
|
+
readonly label: string;
|
|
200
|
+
readonly title: string;
|
|
201
|
+
readonly theme: Theme;
|
|
202
|
+
readonly markerColor: MarkerStatus;
|
|
203
|
+
readonly labelColor?: ThemeColor;
|
|
204
|
+
}): string {
|
|
205
|
+
return titleHead(args) + args.theme.fg("toolTitle", ": " + args.title);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function titleHead(args: {
|
|
209
|
+
readonly label: string;
|
|
210
|
+
readonly theme: Theme;
|
|
211
|
+
readonly markerColor: MarkerStatus;
|
|
212
|
+
readonly labelColor?: ThemeColor;
|
|
213
|
+
}): string {
|
|
214
|
+
const { label, theme, markerColor, labelColor } = args;
|
|
215
|
+
return (
|
|
216
|
+
theme.fg(markerColor, " ▪") +
|
|
217
|
+
" " +
|
|
218
|
+
theme.fg(labelColor ?? "toolTitle", theme.bold(label))
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function makeTitleBlock(args: {
|
|
223
|
+
readonly text: string;
|
|
224
|
+
readonly theme: Theme;
|
|
225
|
+
readonly lastComponent?: Component;
|
|
226
|
+
}): Component {
|
|
227
|
+
const component =
|
|
228
|
+
args.lastComponent instanceof ToolTitle
|
|
229
|
+
? args.lastComponent
|
|
230
|
+
: new ToolTitle();
|
|
231
|
+
component.setText(args.text, args.theme);
|
|
232
|
+
return component;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function renderToolCallTitle(args: {
|
|
236
|
+
readonly label: string;
|
|
237
|
+
readonly title: string;
|
|
238
|
+
readonly theme: Theme;
|
|
239
|
+
readonly context: RenderContext;
|
|
240
|
+
readonly labelColor?: ThemeColor;
|
|
241
|
+
readonly markdown?: boolean;
|
|
242
|
+
}): Component {
|
|
243
|
+
const { theme, context } = args;
|
|
244
|
+
const markerColor = markerColorFor(
|
|
245
|
+
Boolean(context.isPartial),
|
|
246
|
+
Boolean(context.isError)
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
if (args.markdown === true) {
|
|
250
|
+
const component =
|
|
251
|
+
context.lastComponent instanceof MarkdownTitle
|
|
252
|
+
? context.lastComponent
|
|
253
|
+
: new MarkdownTitle();
|
|
254
|
+
component.set({
|
|
255
|
+
prefix: titleHead({ ...args, markerColor }) + theme.fg("toolTitle", ": "),
|
|
256
|
+
title: args.title,
|
|
257
|
+
theme,
|
|
258
|
+
});
|
|
259
|
+
return component;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return makeTitleBlock({
|
|
263
|
+
text: toolTitleText({ ...args, markerColor }),
|
|
264
|
+
theme,
|
|
265
|
+
lastComponent: context.lastComponent,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function makePrefixedBlock(args: {
|
|
270
|
+
readonly lines: readonly string[];
|
|
271
|
+
readonly theme: Theme;
|
|
272
|
+
readonly prefix: PrefixSpec;
|
|
273
|
+
readonly lineColor?: ThemeColor;
|
|
274
|
+
}): Component {
|
|
275
|
+
const { lines, theme, prefix, lineColor } = args;
|
|
276
|
+
return {
|
|
277
|
+
render(width: number): string[] {
|
|
278
|
+
const inner = Math.max(1, width - prefix.width);
|
|
279
|
+
const out: string[] = [];
|
|
280
|
+
for (const logical of lines) {
|
|
281
|
+
for (const w of wrapTextWithAnsi(logical, inner)) {
|
|
282
|
+
const body = lineColor ? theme.fg(lineColor, w) : w;
|
|
283
|
+
out.push(theme.fg("toolOutput", prefix.prefix) + body);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return out;
|
|
287
|
+
},
|
|
288
|
+
invalidate() {},
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function markdownLines(args: {
|
|
293
|
+
readonly text: string;
|
|
294
|
+
readonly theme: Theme;
|
|
295
|
+
readonly width: number;
|
|
296
|
+
readonly lineColor?: ThemeColor;
|
|
297
|
+
}): string[] {
|
|
298
|
+
return makeMarkdown(args.text, args.theme, args.lineColor)
|
|
299
|
+
.render(args.width)
|
|
300
|
+
.map((line) => line.trimEnd());
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function makeMarkdownBlock(args: {
|
|
304
|
+
readonly text: string;
|
|
305
|
+
readonly theme: Theme;
|
|
306
|
+
readonly prefix: PrefixSpec;
|
|
307
|
+
readonly lineColor?: ThemeColor;
|
|
308
|
+
}): Component {
|
|
309
|
+
const { text, theme, prefix, lineColor } = args;
|
|
310
|
+
const markdown = makeMarkdown(text, theme, lineColor);
|
|
311
|
+
|
|
312
|
+
return {
|
|
313
|
+
render(width: number): string[] {
|
|
314
|
+
const inner = Math.max(1, width - prefix.width);
|
|
315
|
+
return markdown
|
|
316
|
+
.render(inner)
|
|
317
|
+
.map((line) => theme.fg("toolOutput", prefix.prefix) + line.trimEnd());
|
|
318
|
+
},
|
|
319
|
+
invalidate(): void {
|
|
320
|
+
markdown.invalidate();
|
|
321
|
+
},
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function renderErrorResult(args: {
|
|
326
|
+
readonly result: AgentToolResult<unknown>;
|
|
327
|
+
readonly options: ToolRenderResultOptions;
|
|
328
|
+
readonly theme: Theme;
|
|
329
|
+
readonly context: RenderContext;
|
|
330
|
+
}): Container {
|
|
331
|
+
const { result, options, theme, context } = args;
|
|
332
|
+
const container =
|
|
333
|
+
(context.lastComponent as Container | undefined) ?? new Container();
|
|
334
|
+
container.clear();
|
|
335
|
+
|
|
336
|
+
if (options.isPartial || !options.expanded) {
|
|
337
|
+
return container;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const body = firstText(result);
|
|
341
|
+
if (!body) {
|
|
342
|
+
return container;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
container.addChild(
|
|
346
|
+
makePrefixedBlock({
|
|
347
|
+
lines: body.split("\n"),
|
|
348
|
+
theme,
|
|
349
|
+
prefix: GAPPED_PREFIX,
|
|
350
|
+
lineColor: "error",
|
|
351
|
+
})
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
container.invalidate();
|
|
355
|
+
return container;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export const Renderer = {
|
|
359
|
+
GAPPED_PREFIX,
|
|
360
|
+
TIGHT_PREFIX,
|
|
361
|
+
markerColorFor,
|
|
362
|
+
firstText,
|
|
363
|
+
extractErrorText,
|
|
364
|
+
toolTitleText,
|
|
365
|
+
makeTitleBlock,
|
|
366
|
+
renderToolCallTitle,
|
|
367
|
+
makePrefixedBlock,
|
|
368
|
+
markdownLines,
|
|
369
|
+
makeMarkdownBlock,
|
|
370
|
+
renderErrorResult,
|
|
371
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { basename, resolve, sep } from "node:path";
|
|
2
|
+
|
|
3
|
+
/** Reduces a client-supplied name to one harmless path segment. */
|
|
4
|
+
function safeName(name: string): string {
|
|
5
|
+
return basename(name).replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** Resolves `child` under `parent`, refusing anything that escapes it. */
|
|
9
|
+
function contain(parent: string, child: string): string {
|
|
10
|
+
const path = resolve(parent, child);
|
|
11
|
+
if (!path.startsWith(`${resolve(parent)}${sep}`)) {
|
|
12
|
+
throw new Error(`refusing path outside ${parent}: ${child}`);
|
|
13
|
+
}
|
|
14
|
+
return path;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const SafePath = { safeName, contain };
|