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,185 @@
|
|
|
1
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { STATUSES, type TodoItem, type TodoStatus } from "./schema";
|
|
3
|
+
|
|
4
|
+
export const TODO_STATE_CUSTOM_TYPE = "pim-todo-state";
|
|
5
|
+
|
|
6
|
+
export type TodoSummary = Record<TodoStatus, number>;
|
|
7
|
+
|
|
8
|
+
export type TodoDetails = {
|
|
9
|
+
readonly todos: readonly TodoItem[];
|
|
10
|
+
readonly summary: TodoSummary;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type FormatChecklistOptions = {
|
|
14
|
+
readonly activeOnly?: boolean;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/** Identity key for the per-session state slot. */
|
|
18
|
+
export type TodoSessionKey = ExtensionContext["sessionManager"];
|
|
19
|
+
|
|
20
|
+
const itemsBySession = new WeakMap<TodoSessionKey, TodoItem[]>();
|
|
21
|
+
|
|
22
|
+
const markers: Record<TodoStatus, string> = {
|
|
23
|
+
pending: "[ ]",
|
|
24
|
+
in_progress: "[>]",
|
|
25
|
+
completed: "[x]",
|
|
26
|
+
cancelled: "[~]",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const statusSet: ReadonlySet<TodoStatus> = new Set(STATUSES);
|
|
30
|
+
|
|
31
|
+
export function getCurrentItems(
|
|
32
|
+
sessionManager: TodoSessionKey
|
|
33
|
+
): readonly TodoItem[] {
|
|
34
|
+
return itemsBySession.get(sessionManager) ?? [];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function replaceItems(
|
|
38
|
+
sessionManager: TodoSessionKey,
|
|
39
|
+
items: readonly TodoItem[]
|
|
40
|
+
): readonly TodoItem[] {
|
|
41
|
+
const normalized = normalizeItems(items);
|
|
42
|
+
itemsBySession.set(sessionManager, normalized);
|
|
43
|
+
return normalized;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function resetItems(sessionManager: TodoSessionKey): void {
|
|
47
|
+
itemsBySession.set(sessionManager, []);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function reconstructFromBranch(
|
|
51
|
+
sessionManager: TodoSessionKey,
|
|
52
|
+
branch: readonly unknown[]
|
|
53
|
+
): readonly TodoItem[] {
|
|
54
|
+
const items = findLatestTodoItems(branch);
|
|
55
|
+
itemsBySession.set(sessionManager, items);
|
|
56
|
+
return items;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function normalizeItems(items: readonly TodoItem[]): TodoItem[] {
|
|
60
|
+
return items.flatMap((item) => {
|
|
61
|
+
const content = normalizeContent(item.content);
|
|
62
|
+
return content ? [{ content, status: item.status }] : [];
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function hasActiveItems(items: readonly TodoItem[]): boolean {
|
|
67
|
+
return items.some(isActive);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function summarizeItems(items: readonly TodoItem[]): TodoSummary {
|
|
71
|
+
const summary: TodoSummary = {
|
|
72
|
+
pending: 0,
|
|
73
|
+
in_progress: 0,
|
|
74
|
+
completed: 0,
|
|
75
|
+
cancelled: 0,
|
|
76
|
+
};
|
|
77
|
+
for (const item of items) {
|
|
78
|
+
summary[item.status] += 1;
|
|
79
|
+
}
|
|
80
|
+
return summary;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function makeDetails(items: readonly TodoItem[]): TodoDetails {
|
|
84
|
+
return {
|
|
85
|
+
todos: structuredClone(items),
|
|
86
|
+
summary: summarizeItems(items),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function formatUpdateSummary(items: readonly TodoItem[]): string {
|
|
91
|
+
const summary = summarizeItems(items);
|
|
92
|
+
const segments = [
|
|
93
|
+
summary.completed > 0 ? `${summary.completed} completed` : undefined,
|
|
94
|
+
summary.in_progress > 0 ? `${summary.in_progress} in progress` : undefined,
|
|
95
|
+
summary.pending > 0 ? `${summary.pending} pending` : undefined,
|
|
96
|
+
summary.cancelled > 0 ? `${summary.cancelled} cancelled` : undefined,
|
|
97
|
+
].filter((segment) => segment !== undefined);
|
|
98
|
+
return segments.length === 0
|
|
99
|
+
? "Todos cleared."
|
|
100
|
+
: `Todos updated: ${segments.join(", ")}.`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function formatChecklist(
|
|
104
|
+
items: readonly TodoItem[],
|
|
105
|
+
options: FormatChecklistOptions = {}
|
|
106
|
+
): string {
|
|
107
|
+
return items
|
|
108
|
+
.filter((item) => !options.activeOnly || isActive(item))
|
|
109
|
+
.map((item) => `${markers[item.status]} ${item.content}`)
|
|
110
|
+
.join("\n");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function isActive(item: TodoItem): boolean {
|
|
114
|
+
return item.status === "pending" || item.status === "in_progress";
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function normalizeContent(content: string): string {
|
|
118
|
+
return content.trim().replaceAll(/\s+/g, " ");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function findLatestTodoItems(branch: readonly unknown[]): TodoItem[] {
|
|
122
|
+
for (let i = branch.length - 1; i >= 0; i--) {
|
|
123
|
+
const items = extractTodoItems(branch[i]);
|
|
124
|
+
if (items) {
|
|
125
|
+
return items;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function extractTodoItems(entry: unknown): TodoItem[] | undefined {
|
|
132
|
+
if (!isRecord(entry)) {
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
if (entry.type === "message") {
|
|
136
|
+
const message = entry.message;
|
|
137
|
+
if (
|
|
138
|
+
!isRecord(message) ||
|
|
139
|
+
message.role !== "toolResult" ||
|
|
140
|
+
message.toolName !== "todo"
|
|
141
|
+
) {
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
const details = message.details;
|
|
145
|
+
if (!isRecord(details) || !Array.isArray(details.todos)) {
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
return normalizeUnknownItems(details.todos);
|
|
149
|
+
}
|
|
150
|
+
if (entry.type === "custom" && entry.customType === TODO_STATE_CUSTOM_TYPE) {
|
|
151
|
+
const data = entry.data;
|
|
152
|
+
if (!isRecord(data) || !Array.isArray(data.todos)) {
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
return normalizeUnknownItems(data.todos);
|
|
156
|
+
}
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function normalizeUnknownItems(items: readonly unknown[]): TodoItem[] {
|
|
161
|
+
const out: TodoItem[] = [];
|
|
162
|
+
for (const item of items) {
|
|
163
|
+
if (!isRecord(item)) {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
const { content, status } = item;
|
|
167
|
+
if (typeof content !== "string" || !isStatus(status)) {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const normalizedContent = normalizeContent(content);
|
|
171
|
+
if (!normalizedContent) {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
out.push({ content: normalizedContent, status });
|
|
175
|
+
}
|
|
176
|
+
return out;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function isStatus(value: unknown): value is TodoStatus {
|
|
180
|
+
return typeof value === "string" && statusSet.has(value as TodoStatus);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
184
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
185
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { Errors } from "../../shared/Errors";
|
|
2
|
+
import { createKy, type HttpFetch } from "../../shared/Http";
|
|
3
|
+
import { Json } from "../../shared/Json";
|
|
4
|
+
import { HTTPError, TimeoutError, type KyInstance } from "ky";
|
|
5
|
+
import type { WebFetchPage } from "./fetch";
|
|
6
|
+
|
|
7
|
+
type JinaReaderClientOptions = {
|
|
8
|
+
readonly endpoint?: string;
|
|
9
|
+
readonly apiKey?: string;
|
|
10
|
+
readonly fetch?: HttpFetch;
|
|
11
|
+
readonly timeoutMs?: number;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type JinaReaderFetchInput = {
|
|
15
|
+
readonly url: string;
|
|
16
|
+
readonly signal?: AbortSignal;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
class JinaReaderClientError extends Error {
|
|
20
|
+
public constructor(message: string) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = "JinaReaderClientError";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const defaultTimeoutMs = 20_000;
|
|
27
|
+
|
|
28
|
+
export class JinaReaderClient {
|
|
29
|
+
private readonly endpoint: string;
|
|
30
|
+
private readonly headers: Headers;
|
|
31
|
+
private readonly ky: KyInstance;
|
|
32
|
+
private readonly timeoutMs: number;
|
|
33
|
+
|
|
34
|
+
public constructor(options: JinaReaderClientOptions = {}) {
|
|
35
|
+
this.endpoint = normalizeEndpoint(options.endpoint ?? "https://r.jina.ai");
|
|
36
|
+
this.headers = buildHeaders(options.apiKey);
|
|
37
|
+
this.timeoutMs = options.timeoutMs ?? defaultTimeoutMs;
|
|
38
|
+
this.ky = createKy(options.fetch);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public async fetchUrl(input: JinaReaderFetchInput): Promise<WebFetchPage> {
|
|
42
|
+
if (input.signal?.aborted) {
|
|
43
|
+
throw new JinaReaderClientError("Request aborted.");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let response: Response;
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
response = await this.ky(`${this.endpoint}/${input.url}`, {
|
|
50
|
+
headers: this.headers,
|
|
51
|
+
timeout: this.timeoutMs,
|
|
52
|
+
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
53
|
+
});
|
|
54
|
+
} catch (error) {
|
|
55
|
+
const aborted = input.signal?.aborted ?? false;
|
|
56
|
+
|
|
57
|
+
if (Errors.isAbort(error) || aborted) {
|
|
58
|
+
throw new JinaReaderClientError("Request aborted.");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (error instanceof TimeoutError) {
|
|
62
|
+
throw new JinaReaderClientError(
|
|
63
|
+
`Request timed out after ${this.timeoutMs}ms.`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (error instanceof HTTPError) {
|
|
68
|
+
throw new JinaReaderClientError(
|
|
69
|
+
`Request failed with HTTP ${error.response.status}: ${excerpt(stringifyErrorData(error.data))}`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
throw new JinaReaderClientError(
|
|
74
|
+
`Request failed: ${Errors.describe(error)}`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return parseResponse(input.url, response, await response.text());
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function buildHeaders(apiKey: string | undefined): Headers {
|
|
83
|
+
const headers = new Headers({ Accept: "application/json" });
|
|
84
|
+
|
|
85
|
+
if (apiKey !== undefined && apiKey.length > 0) {
|
|
86
|
+
headers.set("Authorization", `Bearer ${apiKey}`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return headers;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function normalizeEndpoint(endpoint: string): string {
|
|
93
|
+
return endpoint.replace(/\/+$/u, "");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function parseResponse(
|
|
97
|
+
requestedUrl: string,
|
|
98
|
+
response: Response,
|
|
99
|
+
responseText: string
|
|
100
|
+
): WebFetchPage {
|
|
101
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
102
|
+
const declaresJson =
|
|
103
|
+
contentType.includes("application/json") || contentType.includes("+json");
|
|
104
|
+
const parsedJson = Json.tryParseJson(responseText);
|
|
105
|
+
|
|
106
|
+
if (parsedJson === undefined) {
|
|
107
|
+
if (declaresJson) {
|
|
108
|
+
throw new JinaReaderClientError("Response contained malformed JSON.");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return createPage({
|
|
112
|
+
title: "",
|
|
113
|
+
url: requestedUrl,
|
|
114
|
+
content: responseText,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return parseJsonPayload(requestedUrl, parsedJson);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function parseJsonPayload(
|
|
122
|
+
requestedUrl: string,
|
|
123
|
+
parsedJson: unknown
|
|
124
|
+
): WebFetchPage {
|
|
125
|
+
const responseRecord = Json.asRecord(parsedJson);
|
|
126
|
+
const payload = Json.asRecord(responseRecord?.["data"]) ?? responseRecord;
|
|
127
|
+
|
|
128
|
+
if (payload === undefined) {
|
|
129
|
+
throw new JinaReaderClientError("Response contained invalid payload.");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return createPage({
|
|
133
|
+
title: optionalString(payload["title"], "title") ?? "",
|
|
134
|
+
url: optionalString(payload["url"], "url") ?? requestedUrl,
|
|
135
|
+
content: requiredString(payload["content"], "content"),
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function createPage(page: WebFetchPage): WebFetchPage {
|
|
140
|
+
if (page.content.trim().length === 0) {
|
|
141
|
+
throw new JinaReaderClientError("Response contained empty content.");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return page;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function requiredString(value: unknown, name: string): string {
|
|
148
|
+
if (typeof value !== "string") {
|
|
149
|
+
throw new JinaReaderClientError(
|
|
150
|
+
`Response contained invalid payload: expected string ${name}.`
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return value;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function optionalString(value: unknown, name: string): string | undefined {
|
|
158
|
+
if (value === undefined || value === null) {
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (typeof value !== "string") {
|
|
163
|
+
throw new JinaReaderClientError(
|
|
164
|
+
`Response contained invalid payload: expected string ${name}.`
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return value;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function stringifyErrorData(data: unknown): string {
|
|
172
|
+
if (typeof data === "string") {
|
|
173
|
+
return data;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (data === undefined) {
|
|
177
|
+
return "";
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return JSON.stringify(data);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function excerpt(text: string): string {
|
|
184
|
+
const excerpt = text.replaceAll(/\s+/gu, " ").trim().slice(0, 200);
|
|
185
|
+
|
|
186
|
+
return excerpt.length === 0 ? "empty response body" : excerpt;
|
|
187
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { Errors } from "../../shared/Errors";
|
|
2
|
+
import { platform } from "node:os";
|
|
3
|
+
import type { WebFetchPage } from "./fetch";
|
|
4
|
+
import { createMarkdownSnapshotScript } from "./WebViewMarkdownSnapshot";
|
|
5
|
+
|
|
6
|
+
export type WebViewLike = {
|
|
7
|
+
readonly url: string;
|
|
8
|
+
readonly title: string;
|
|
9
|
+
readonly navigate: (url: string) => Promise<void>;
|
|
10
|
+
readonly evaluate: <T = unknown>(script: string) => Promise<T>;
|
|
11
|
+
readonly close: () => void;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type WebViewFactory = () => WebViewLike;
|
|
15
|
+
|
|
16
|
+
type WebViewFetchClientOptions = {
|
|
17
|
+
readonly factory?: WebViewFactory;
|
|
18
|
+
readonly timeoutMs?: number;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type WebViewFetchInput = {
|
|
22
|
+
readonly url: string;
|
|
23
|
+
readonly signal?: AbortSignal;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type WebViewSnapshot = {
|
|
27
|
+
readonly title: string;
|
|
28
|
+
readonly url: string;
|
|
29
|
+
readonly content: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
class WebViewFetchClientError extends Error {
|
|
33
|
+
public constructor(message: string) {
|
|
34
|
+
super(message);
|
|
35
|
+
this.name = "WebViewFetchClientError";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const defaultTimeoutMs = 20_000;
|
|
40
|
+
|
|
41
|
+
export class WebViewFetchClient {
|
|
42
|
+
private readonly factory: WebViewFactory;
|
|
43
|
+
private readonly timeoutMs: number;
|
|
44
|
+
|
|
45
|
+
public constructor(options: WebViewFetchClientOptions = {}) {
|
|
46
|
+
this.factory = options.factory ?? defaultFactory;
|
|
47
|
+
this.timeoutMs = options.timeoutMs ?? defaultTimeoutMs;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public async fetchHtml(input: WebViewFetchInput): Promise<WebFetchPage> {
|
|
51
|
+
return this.capturePage(input, htmlSnapshotScript());
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public async fetchMarkdown(input: WebViewFetchInput): Promise<WebFetchPage> {
|
|
55
|
+
return this.capturePage(input, createMarkdownSnapshotScript());
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private async capturePage(
|
|
59
|
+
input: WebViewFetchInput,
|
|
60
|
+
snapshotScript: string
|
|
61
|
+
): Promise<WebFetchPage> {
|
|
62
|
+
const signal = input.signal;
|
|
63
|
+
|
|
64
|
+
if (signal?.aborted) {
|
|
65
|
+
throw new WebViewFetchClientError("Request aborted.");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let view: WebViewLike;
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
view = this.factory();
|
|
72
|
+
} catch (error) {
|
|
73
|
+
throw new WebViewFetchClientError(
|
|
74
|
+
`Request failed: ${Errors.describe(error)}`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let aborted = false;
|
|
79
|
+
let timedOut = false;
|
|
80
|
+
const onAbort = () => {
|
|
81
|
+
aborted = true;
|
|
82
|
+
safeClose(view);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
86
|
+
|
|
87
|
+
const timeoutHandle = setTimeout(() => {
|
|
88
|
+
timedOut = true;
|
|
89
|
+
safeClose(view);
|
|
90
|
+
}, this.timeoutMs);
|
|
91
|
+
|
|
92
|
+
const checkInterrupted = (): void => {
|
|
93
|
+
if (timedOut) {
|
|
94
|
+
throw new WebViewFetchClientError(
|
|
95
|
+
`Request timed out after ${this.timeoutMs}ms.`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
if (aborted) {
|
|
99
|
+
throw new WebViewFetchClientError("Request aborted.");
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
await view.navigate(input.url);
|
|
105
|
+
checkInterrupted();
|
|
106
|
+
|
|
107
|
+
const snapshot = readSnapshot(
|
|
108
|
+
await view.evaluate<unknown>(snapshotScript)
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
if (snapshot.content.trim().length === 0) {
|
|
112
|
+
throw new WebViewFetchClientError("Response contained empty content.");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const finalUrl =
|
|
116
|
+
snapshot.url.length > 0
|
|
117
|
+
? snapshot.url
|
|
118
|
+
: view.url.length > 0
|
|
119
|
+
? view.url
|
|
120
|
+
: input.url;
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
title: snapshot.title,
|
|
124
|
+
url: finalUrl,
|
|
125
|
+
content: snapshot.content,
|
|
126
|
+
};
|
|
127
|
+
} catch (error) {
|
|
128
|
+
checkInterrupted();
|
|
129
|
+
|
|
130
|
+
if (error instanceof WebViewFetchClientError) {
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
throw new WebViewFetchClientError(
|
|
135
|
+
`Request failed: ${Errors.describe(error)}`
|
|
136
|
+
);
|
|
137
|
+
} finally {
|
|
138
|
+
clearTimeout(timeoutHandle);
|
|
139
|
+
signal?.removeEventListener("abort", onAbort);
|
|
140
|
+
safeClose(view);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function htmlSnapshotScript(): string {
|
|
146
|
+
return String.raw`(() => ({
|
|
147
|
+
title: document.title,
|
|
148
|
+
url: location.href,
|
|
149
|
+
content: document.documentElement.outerHTML,
|
|
150
|
+
}))()`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function readSnapshot(value: unknown): WebViewSnapshot {
|
|
154
|
+
if (typeof value !== "object" || value === null) {
|
|
155
|
+
throw new WebViewFetchClientError("Response contained invalid payload.");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const record = value as Record<string, unknown>;
|
|
159
|
+
|
|
160
|
+
if (typeof record["content"] !== "string") {
|
|
161
|
+
throw new WebViewFetchClientError("Response contained invalid payload.");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
content: record["content"],
|
|
166
|
+
title: typeof record["title"] === "string" ? record["title"] : "",
|
|
167
|
+
url: typeof record["url"] === "string" ? record["url"] : "",
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function safeClose(view: WebViewLike): void {
|
|
172
|
+
try {
|
|
173
|
+
view.close();
|
|
174
|
+
} catch {
|
|
175
|
+
// close() throws if already closed.
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function defaultFactory(): WebViewLike {
|
|
180
|
+
return new Bun.WebView(
|
|
181
|
+
platform() === "darwin" ? undefined : { backend: "chrome" }
|
|
182
|
+
);
|
|
183
|
+
}
|