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,357 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AgentToolResult,
|
|
3
|
+
resizeImage,
|
|
4
|
+
} from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import { extname } from "node:path";
|
|
6
|
+
import { Format } from "./Format";
|
|
7
|
+
import { ImageMime, type ImageMimeType } from "./ImageMime";
|
|
8
|
+
import { Lines } from "./Lines";
|
|
9
|
+
import { SpillCache } from "./SpillCache";
|
|
10
|
+
|
|
11
|
+
export type { ImageMimeType };
|
|
12
|
+
|
|
13
|
+
/** What a tool needs off `ctx.model` to know whether a picture can be sent. */
|
|
14
|
+
export type VisionModel = {
|
|
15
|
+
readonly id: string;
|
|
16
|
+
readonly input: readonly string[];
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/** An image the providers will take: within their formats, dimensions and byte budget. */
|
|
20
|
+
export type NormalisedImage = {
|
|
21
|
+
readonly base64: string;
|
|
22
|
+
readonly mimeType: ImageMimeType;
|
|
23
|
+
readonly width: number;
|
|
24
|
+
readonly height: number;
|
|
25
|
+
/** Encoded size after the resize, which is what the cache holds. */
|
|
26
|
+
readonly bytes: number;
|
|
27
|
+
readonly originalWidth: number;
|
|
28
|
+
readonly originalHeight: number;
|
|
29
|
+
/** The container the frames were counted in: a resize is free to re-encode as another. */
|
|
30
|
+
readonly originalMimeType: ImageMimeType;
|
|
31
|
+
readonly resized: boolean;
|
|
32
|
+
/** Frames in the source container, 1 for a still: the encoded copy holds only the first. */
|
|
33
|
+
readonly frames: number;
|
|
34
|
+
readonly sha256: string;
|
|
35
|
+
/** `~/.pim/cache/img-<sha256>.<ext>`, or null when the cache write failed. */
|
|
36
|
+
readonly cachePath: string | null;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** What every tool records about a picture it showed: the picture itself never rides here, `sha256` addresses it in the spill cache. */
|
|
40
|
+
export type ImageDetails = {
|
|
41
|
+
readonly sha256: string;
|
|
42
|
+
readonly mimeType: ImageMimeType;
|
|
43
|
+
readonly width: number;
|
|
44
|
+
readonly height: number;
|
|
45
|
+
readonly bytes: number;
|
|
46
|
+
readonly resized: boolean;
|
|
47
|
+
readonly frames: number;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
type ToolContent = AgentToolResult<unknown>["content"];
|
|
51
|
+
|
|
52
|
+
/** Never decode past this: a mislabelled video is rejected on its size alone. */
|
|
53
|
+
const MAX_SOURCE_BYTES = 25 * 1024 * 1024;
|
|
54
|
+
const MAX_EDGE = 2000;
|
|
55
|
+
const MAX_ENCODED_BYTES = 3_932_160;
|
|
56
|
+
|
|
57
|
+
/** Enough for every magic-byte signature, WebP's `RIFF….WEBP` included. */
|
|
58
|
+
const SNIFF_BYTES = 12;
|
|
59
|
+
|
|
60
|
+
const CACHE_PREFIX = "img-";
|
|
61
|
+
|
|
62
|
+
const PNG = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
|
|
63
|
+
const JPEG = [0xff, 0xd8, 0xff];
|
|
64
|
+
const GIF = [0x47, 0x49, 0x46, 0x38];
|
|
65
|
+
const RIFF = [0x52, 0x49, 0x46, 0x46];
|
|
66
|
+
const WEBP = [0x57, 0x45, 0x42, 0x50];
|
|
67
|
+
const ZIP = [0x50, 0x4b, 0x03, 0x04];
|
|
68
|
+
const PDF = [0x25, 0x50, 0x44, 0x46, 0x2d];
|
|
69
|
+
const ANMF = [0x41, 0x4e, 0x4d, 0x46];
|
|
70
|
+
const ACTL = [0x61, 0x63, 0x54, 0x4c];
|
|
71
|
+
const IDAT = [0x49, 0x44, 0x41, 0x54];
|
|
72
|
+
|
|
73
|
+
const GIF_EXTENSION = 0x21;
|
|
74
|
+
const GIF_IMAGE_DESCRIPTOR = 0x2c;
|
|
75
|
+
|
|
76
|
+
function startsWith(
|
|
77
|
+
bytes: Uint8Array,
|
|
78
|
+
signature: readonly number[],
|
|
79
|
+
offset = 0
|
|
80
|
+
): boolean {
|
|
81
|
+
return signature.every((byte, index) => bytes[offset + index] === byte);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Magic bytes only: an extension is a claim, not evidence. */
|
|
85
|
+
function sniff(bytes: Uint8Array): ImageMimeType | null {
|
|
86
|
+
if (startsWith(bytes, PNG)) {
|
|
87
|
+
return "image/png";
|
|
88
|
+
}
|
|
89
|
+
if (startsWith(bytes, JPEG)) {
|
|
90
|
+
return "image/jpeg";
|
|
91
|
+
}
|
|
92
|
+
if (startsWith(bytes, GIF)) {
|
|
93
|
+
return "image/gif";
|
|
94
|
+
}
|
|
95
|
+
if (startsWith(bytes, RIFF) && startsWith(bytes, WEBP, 8)) {
|
|
96
|
+
return "image/webp";
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function view(bytes: Uint8Array): DataView {
|
|
102
|
+
return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** RIFF chunks are padded to an even length, so an odd body carries one trailing byte. */
|
|
106
|
+
function webpFrames(bytes: Uint8Array): number {
|
|
107
|
+
const reader = view(bytes);
|
|
108
|
+
let count = 0;
|
|
109
|
+
let at = 12;
|
|
110
|
+
while (at + 8 <= bytes.byteLength) {
|
|
111
|
+
const size = reader.getUint32(at + 4, true);
|
|
112
|
+
if (startsWith(bytes, ANMF, at)) {
|
|
113
|
+
count += 1;
|
|
114
|
+
}
|
|
115
|
+
at += 8 + size + (size % 2);
|
|
116
|
+
}
|
|
117
|
+
return count;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** `acTL` carries the count outright, and must appear before the first `IDAT`. */
|
|
121
|
+
function apngFrames(bytes: Uint8Array): number {
|
|
122
|
+
const reader = view(bytes);
|
|
123
|
+
let at = 8;
|
|
124
|
+
while (at + 12 <= bytes.byteLength) {
|
|
125
|
+
if (startsWith(bytes, ACTL, at + 4)) {
|
|
126
|
+
return reader.getUint32(at + 8);
|
|
127
|
+
}
|
|
128
|
+
if (startsWith(bytes, IDAT, at + 4)) {
|
|
129
|
+
return 1;
|
|
130
|
+
}
|
|
131
|
+
at += 12 + reader.getUint32(at);
|
|
132
|
+
}
|
|
133
|
+
return 1;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function gifFrames(bytes: Uint8Array): number {
|
|
137
|
+
let at = 13 + colourTableBytes(bytes[10] ?? 0);
|
|
138
|
+
let count = 0;
|
|
139
|
+
while (at < bytes.byteLength) {
|
|
140
|
+
if (bytes[at] === GIF_IMAGE_DESCRIPTOR) {
|
|
141
|
+
count += 1;
|
|
142
|
+
at = subBlocksEnd(bytes, at + 11 + colourTableBytes(bytes[at + 9] ?? 0));
|
|
143
|
+
} else if (bytes[at] === GIF_EXTENSION) {
|
|
144
|
+
at = subBlocksEnd(bytes, at + 2);
|
|
145
|
+
} else {
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return count;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function colourTableBytes(packed: number): number {
|
|
153
|
+
return (packed & 0x80) === 0 ? 0 : 3 * 2 ** ((packed & 0x07) + 1);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function subBlocksEnd(bytes: Uint8Array, from: number): number {
|
|
157
|
+
let at = from;
|
|
158
|
+
while (at < bytes.byteLength) {
|
|
159
|
+
const size = bytes[at] ?? 0;
|
|
160
|
+
if (size === 0) {
|
|
161
|
+
return at + 1;
|
|
162
|
+
}
|
|
163
|
+
at += 1 + size;
|
|
164
|
+
}
|
|
165
|
+
return bytes.byteLength;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const FRAME_READERS: Partial<
|
|
169
|
+
Record<ImageMimeType, (bytes: Uint8Array) => number>
|
|
170
|
+
> = {
|
|
171
|
+
"image/webp": webpFrames,
|
|
172
|
+
"image/png": apngFrames,
|
|
173
|
+
"image/gif": gifFrames,
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
/** Headers only: the count has to be read before the resize, which keeps frame 1 alone. */
|
|
177
|
+
function frames(bytes: Uint8Array, mimeType: ImageMimeType): number {
|
|
178
|
+
return Math.max(1, FRAME_READERS[mimeType]?.(bytes) ?? 1);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** An unknown model is not a blind one: only a model that names its inputs can rule the picture out. */
|
|
182
|
+
function canSee(model: VisionModel | undefined): boolean {
|
|
183
|
+
return model === undefined || model.input.includes("image");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** What a model without eyes is told in place of the picture it cannot be sent. */
|
|
187
|
+
function noVisionNote(tool: string, subject: string): string {
|
|
188
|
+
return `[${tool} tool: ${subject}; the current model has no vision input.]`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** A name that claims a picture — a claim the bytes still have to back. */
|
|
192
|
+
function looksLikeImageName(path: string): boolean {
|
|
193
|
+
return ImageMime.namesExtension(extname(path).slice(1).toLowerCase());
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** What the spill cache holds the picture under; the resize picks the extension, never the source path. */
|
|
197
|
+
function cacheName(sha256: string, mimeType: ImageMimeType): string {
|
|
198
|
+
return `${CACHE_PREFIX}${sha256}.${ImageMime.extensionOf(mimeType)}`;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Refuse on the stat alone, so a mislabelled video is never pulled into memory. */
|
|
202
|
+
function assertWithinSourceCap(byteLength: number, path: string): void {
|
|
203
|
+
if (byteLength > MAX_SOURCE_BYTES) {
|
|
204
|
+
throw new Error(
|
|
205
|
+
`Image is ${Format.bytes(byteLength)}, over the ${Format.bytes(MAX_SOURCE_BYTES)} read cap. Shrink it first: magick ${path} -resize ${MAX_EDGE}x${MAX_EDGE} /tmp/small.png`
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function normalise(
|
|
211
|
+
bytes: Uint8Array,
|
|
212
|
+
path: string
|
|
213
|
+
): Promise<NormalisedImage> {
|
|
214
|
+
assertWithinSourceCap(bytes.byteLength, path);
|
|
215
|
+
|
|
216
|
+
const sniffed = sniff(bytes);
|
|
217
|
+
if (sniffed === null) {
|
|
218
|
+
throw new Error(
|
|
219
|
+
`File has an image extension but its content is not a valid PNG/JPEG/GIF/WebP. Detected: ${describe(bytes)}. This usually means a download saved an error page instead of the image. Use bash: file ${path}`
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const resized = await resizeImage(bytes, sniffed, {
|
|
224
|
+
maxWidth: MAX_EDGE,
|
|
225
|
+
maxHeight: MAX_EDGE,
|
|
226
|
+
maxBytes: MAX_ENCODED_BYTES,
|
|
227
|
+
});
|
|
228
|
+
if (resized === null) {
|
|
229
|
+
throw new Error(
|
|
230
|
+
`Could not shrink ${path} (${Format.bytes(bytes.byteLength)}) under the ${Format.bytes(MAX_ENCODED_BYTES)} provider limit. Re-save it smaller: magick ${path} -resize ${MAX_EDGE}x${MAX_EDGE} -quality 80 /tmp/small.jpg`
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// An unresized encode is the input re-spelled: decoding it back would copy the bytes twice.
|
|
235
|
+
const data = resized.wasResized ? Buffer.from(resized.data, "base64") : bytes;
|
|
236
|
+
const mimeType = ImageMime.isSupported(resized.mimeType)
|
|
237
|
+
? resized.mimeType
|
|
238
|
+
: sniffed;
|
|
239
|
+
const sha256 = Bun.SHA256.hash(data, "hex");
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
base64: resized.data,
|
|
243
|
+
mimeType,
|
|
244
|
+
width: resized.width,
|
|
245
|
+
height: resized.height,
|
|
246
|
+
bytes: data.byteLength,
|
|
247
|
+
originalWidth: resized.originalWidth,
|
|
248
|
+
originalHeight: resized.originalHeight,
|
|
249
|
+
originalMimeType: sniffed,
|
|
250
|
+
resized:
|
|
251
|
+
resized.width !== resized.originalWidth ||
|
|
252
|
+
resized.height !== resized.originalHeight,
|
|
253
|
+
frames: frames(bytes, sniffed),
|
|
254
|
+
sha256,
|
|
255
|
+
cachePath: await SpillCache.writeNamed(cacheName(sha256, mimeType), data),
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** The record of the picture a tool hands its view, off the picture itself. */
|
|
260
|
+
function detailsOf(image: NormalisedImage): ImageDetails {
|
|
261
|
+
return {
|
|
262
|
+
sha256: image.sha256,
|
|
263
|
+
mimeType: image.mimeType,
|
|
264
|
+
width: image.width,
|
|
265
|
+
height: image.height,
|
|
266
|
+
bytes: image.bytes,
|
|
267
|
+
resized: image.resized,
|
|
268
|
+
frames: image.frames,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** Text first, the way pi's own read orders it. */
|
|
273
|
+
function contentOf(image: NormalisedImage, note?: string): ToolContent {
|
|
274
|
+
const picture = {
|
|
275
|
+
type: "image" as const,
|
|
276
|
+
data: image.base64,
|
|
277
|
+
mimeType: image.mimeType,
|
|
278
|
+
};
|
|
279
|
+
return note === undefined
|
|
280
|
+
? [picture]
|
|
281
|
+
: [{ type: "text", text: note }, picture];
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** How the model maps coordinates it reads off the picture back onto the file. */
|
|
285
|
+
function resizeNote(image: NormalisedImage): string | undefined {
|
|
286
|
+
if (!image.resized) {
|
|
287
|
+
return undefined;
|
|
288
|
+
}
|
|
289
|
+
const scale = (image.originalWidth / image.width).toFixed(2);
|
|
290
|
+
return `image resized from ${image.originalWidth}x${image.originalHeight} to ${image.width}x${image.height}; multiply coordinates by ${scale} to map to the original.`;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* What the picture leaves out, which a still under every cap says nothing about:
|
|
295
|
+
* providers see one frame, so an unmentioned animation reads as an empty screen.
|
|
296
|
+
*/
|
|
297
|
+
function animationNote(image: NormalisedImage): string | undefined {
|
|
298
|
+
if (image.frames < 2) {
|
|
299
|
+
return undefined;
|
|
300
|
+
}
|
|
301
|
+
return `animated ${ImageMime.extensionOf(image.originalMimeType)}: ${image.frames} frames, ${image.originalWidth}x${image.originalHeight}; frame 1 shown.`;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** Everything true of the picture that is not in the picture. */
|
|
305
|
+
function noteOf(image: NormalisedImage): string | undefined {
|
|
306
|
+
const notes = [resizeNote(image), animationNote(image)].filter(
|
|
307
|
+
(note) => note !== undefined
|
|
308
|
+
);
|
|
309
|
+
return notes.length === 0 ? undefined : notes.join(" ");
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** What the bytes are instead, in the words a person would use to go find them. */
|
|
313
|
+
function describe(bytes: Uint8Array): string {
|
|
314
|
+
if (bytes.length === 0) {
|
|
315
|
+
return "an empty file";
|
|
316
|
+
}
|
|
317
|
+
if (startsWith(bytes, PDF)) {
|
|
318
|
+
return "PDF document";
|
|
319
|
+
}
|
|
320
|
+
if (startsWith(bytes, ZIP)) {
|
|
321
|
+
return "ZIP archive (.pptx/.docx/.xlsx are ZIPs)";
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const head = new TextDecoder().decode(bytes.subarray(0, 512)).trimStart();
|
|
325
|
+
if (/^<(?:!doctype html|html|head|body)\b/i.test(head)) {
|
|
326
|
+
return "HTML document";
|
|
327
|
+
}
|
|
328
|
+
if (!Lines.isBinaryBytes(bytes)) {
|
|
329
|
+
return "JSON or text";
|
|
330
|
+
}
|
|
331
|
+
return `unrecognized bytes (hex: ${hex(bytes)})`;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function hex(bytes: Uint8Array): string {
|
|
335
|
+
return Array.from(bytes.subarray(0, 8), (byte) =>
|
|
336
|
+
byte.toString(16).padStart(2, "0")
|
|
337
|
+
).join(" ");
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export const Images = {
|
|
341
|
+
MAX_SOURCE_BYTES,
|
|
342
|
+
SNIFF_BYTES,
|
|
343
|
+
CACHE_PREFIX,
|
|
344
|
+
sniff,
|
|
345
|
+
frames,
|
|
346
|
+
isSupported: ImageMime.isSupported,
|
|
347
|
+
canSee,
|
|
348
|
+
noVisionNote,
|
|
349
|
+
extensionOf: ImageMime.extensionOf,
|
|
350
|
+
looksLikeImageName,
|
|
351
|
+
assertWithinSourceCap,
|
|
352
|
+
normalise,
|
|
353
|
+
detailsOf,
|
|
354
|
+
contentOf,
|
|
355
|
+
noteOf,
|
|
356
|
+
cacheName,
|
|
357
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
function asRecord(
|
|
2
|
+
value: unknown
|
|
3
|
+
): Readonly<Record<string, unknown>> | undefined {
|
|
4
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
5
|
+
return undefined;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
return value as Readonly<Record<string, unknown>>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function tryParseJson(text: string): unknown | undefined {
|
|
12
|
+
try {
|
|
13
|
+
return JSON.parse(text);
|
|
14
|
+
} catch {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const Json = { asRecord, tryParseJson };
|
|
@@ -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,30 @@
|
|
|
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
|
+
export const Levenshtein = { distance };
|
|
@@ -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
|
+
};
|