@ny4n/durable 0.0.0-stage → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (303) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +711 -2
  3. package/dist/documents.d.ts +66 -0
  4. package/dist/documents.d.ts.map +1 -0
  5. package/dist/documents.js +98 -0
  6. package/dist/documents.js.map +1 -0
  7. package/dist/entries.d.ts +31 -0
  8. package/dist/entries.d.ts.map +1 -0
  9. package/dist/entries.js +31 -0
  10. package/dist/entries.js.map +1 -0
  11. package/dist/env/decode.d.ts +16 -0
  12. package/dist/env/decode.d.ts.map +1 -0
  13. package/dist/env/decode.js +28 -0
  14. package/dist/env/decode.js.map +1 -0
  15. package/dist/env/index.d.ts +263 -0
  16. package/dist/env/index.d.ts.map +1 -0
  17. package/dist/env/index.js +49 -0
  18. package/dist/env/index.js.map +1 -0
  19. package/dist/env/line-scan.d.ts +13 -0
  20. package/dist/env/line-scan.d.ts.map +1 -0
  21. package/dist/env/line-scan.js +147 -0
  22. package/dist/env/line-scan.js.map +1 -0
  23. package/dist/env/node-watch.d.ts +29 -0
  24. package/dist/env/node-watch.d.ts.map +1 -0
  25. package/dist/env/node-watch.js +485 -0
  26. package/dist/env/node-watch.js.map +1 -0
  27. package/dist/env/node.d.ts +56 -0
  28. package/dist/env/node.d.ts.map +1 -0
  29. package/dist/env/node.js +1166 -0
  30. package/dist/env/node.js.map +1 -0
  31. package/dist/errors.d.ts +32 -0
  32. package/dist/errors.d.ts.map +1 -0
  33. package/dist/errors.js +53 -0
  34. package/dist/errors.js.map +1 -0
  35. package/dist/harness/agent.d.ts +33 -0
  36. package/dist/harness/agent.d.ts.map +1 -0
  37. package/dist/harness/agent.js +235 -0
  38. package/dist/harness/agent.js.map +1 -0
  39. package/dist/harness/assistant.d.ts +15 -0
  40. package/dist/harness/assistant.d.ts.map +1 -0
  41. package/dist/harness/assistant.js +23 -0
  42. package/dist/harness/assistant.js.map +1 -0
  43. package/dist/harness/compaction.d.ts +56 -0
  44. package/dist/harness/compaction.d.ts.map +1 -0
  45. package/dist/harness/compaction.js +384 -0
  46. package/dist/harness/compaction.js.map +1 -0
  47. package/dist/harness/context.d.ts +63 -0
  48. package/dist/harness/context.d.ts.map +1 -0
  49. package/dist/harness/context.js +221 -0
  50. package/dist/harness/context.js.map +1 -0
  51. package/dist/harness/define.d.ts +18 -0
  52. package/dist/harness/define.d.ts.map +1 -0
  53. package/dist/harness/define.js +25 -0
  54. package/dist/harness/define.js.map +1 -0
  55. package/dist/harness/events.d.ts +180 -0
  56. package/dist/harness/events.d.ts.map +1 -0
  57. package/dist/harness/events.js +370 -0
  58. package/dist/harness/events.js.map +1 -0
  59. package/dist/harness/generation.d.ts +54 -0
  60. package/dist/harness/generation.d.ts.map +1 -0
  61. package/dist/harness/generation.js +514 -0
  62. package/dist/harness/generation.js.map +1 -0
  63. package/dist/harness/harness.d.ts +17 -0
  64. package/dist/harness/harness.d.ts.map +1 -0
  65. package/dist/harness/harness.js +332 -0
  66. package/dist/harness/harness.js.map +1 -0
  67. package/dist/harness/inbox.d.ts +59 -0
  68. package/dist/harness/inbox.d.ts.map +1 -0
  69. package/dist/harness/inbox.js +97 -0
  70. package/dist/harness/inbox.js.map +1 -0
  71. package/dist/harness/json.d.ts +10 -0
  72. package/dist/harness/json.d.ts.map +1 -0
  73. package/dist/harness/json.js +33 -0
  74. package/dist/harness/json.js.map +1 -0
  75. package/dist/harness/live.d.ts +142 -0
  76. package/dist/harness/live.d.ts.map +1 -0
  77. package/dist/harness/live.js +149 -0
  78. package/dist/harness/live.js.map +1 -0
  79. package/dist/harness/output.d.ts +69 -0
  80. package/dist/harness/output.d.ts.map +1 -0
  81. package/dist/harness/output.js +318 -0
  82. package/dist/harness/output.js.map +1 -0
  83. package/dist/harness/prompt.d.ts +27 -0
  84. package/dist/harness/prompt.d.ts.map +1 -0
  85. package/dist/harness/prompt.js +131 -0
  86. package/dist/harness/prompt.js.map +1 -0
  87. package/dist/harness/provider.d.ts +14 -0
  88. package/dist/harness/provider.d.ts.map +1 -0
  89. package/dist/harness/provider.js +30 -0
  90. package/dist/harness/provider.js.map +1 -0
  91. package/dist/harness/registry.d.ts +6 -0
  92. package/dist/harness/registry.d.ts.map +1 -0
  93. package/dist/harness/registry.js +101 -0
  94. package/dist/harness/registry.js.map +1 -0
  95. package/dist/harness/run.d.ts +17 -0
  96. package/dist/harness/run.d.ts.map +1 -0
  97. package/dist/harness/run.js +26 -0
  98. package/dist/harness/run.js.map +1 -0
  99. package/dist/harness/scheduler.d.ts +106 -0
  100. package/dist/harness/scheduler.d.ts.map +1 -0
  101. package/dist/harness/scheduler.js +1743 -0
  102. package/dist/harness/scheduler.js.map +1 -0
  103. package/dist/harness/submissions.d.ts +29 -0
  104. package/dist/harness/submissions.d.ts.map +1 -0
  105. package/dist/harness/submissions.js +175 -0
  106. package/dist/harness/submissions.js.map +1 -0
  107. package/dist/harness/task-graph.d.ts +49 -0
  108. package/dist/harness/task-graph.d.ts.map +1 -0
  109. package/dist/harness/task-graph.js +168 -0
  110. package/dist/harness/task-graph.js.map +1 -0
  111. package/dist/harness/tool.d.ts +76 -0
  112. package/dist/harness/tool.d.ts.map +1 -0
  113. package/dist/harness/tool.js +736 -0
  114. package/dist/harness/tool.js.map +1 -0
  115. package/dist/harness/types.d.ts +713 -0
  116. package/dist/harness/types.d.ts.map +1 -0
  117. package/dist/harness/types.js +2 -0
  118. package/dist/harness/types.js.map +1 -0
  119. package/dist/harness/usage.d.ts +18 -0
  120. package/dist/harness/usage.d.ts.map +1 -0
  121. package/dist/harness/usage.js +57 -0
  122. package/dist/harness/usage.js.map +1 -0
  123. package/dist/harness/util.d.ts +19 -0
  124. package/dist/harness/util.d.ts.map +1 -0
  125. package/dist/harness/util.js +58 -0
  126. package/dist/harness/util.js.map +1 -0
  127. package/dist/harness/view.d.ts +47 -0
  128. package/dist/harness/view.d.ts.map +1 -0
  129. package/dist/harness/view.js +206 -0
  130. package/dist/harness/view.js.map +1 -0
  131. package/dist/ids.d.ts +6 -0
  132. package/dist/ids.d.ts.map +1 -0
  133. package/dist/ids.js +9 -0
  134. package/dist/ids.js.map +1 -0
  135. package/dist/images/cloudflare.d.ts +4 -0
  136. package/dist/images/cloudflare.d.ts.map +1 -0
  137. package/dist/images/cloudflare.js +9 -0
  138. package/dist/images/cloudflare.js.map +1 -0
  139. package/dist/images/exif.d.ts +3 -0
  140. package/dist/images/exif.d.ts.map +1 -0
  141. package/dist/images/exif.js +71 -0
  142. package/dist/images/exif.js.map +1 -0
  143. package/dist/images/index.d.ts +18 -0
  144. package/dist/images/index.d.ts.map +1 -0
  145. package/dist/images/index.js +125 -0
  146. package/dist/images/index.js.map +1 -0
  147. package/dist/images/node-worker.d.ts +17 -0
  148. package/dist/images/node-worker.d.ts.map +1 -0
  149. package/dist/images/node-worker.js +16 -0
  150. package/dist/images/node-worker.js.map +1 -0
  151. package/dist/images/node.d.ts +21 -0
  152. package/dist/images/node.d.ts.map +1 -0
  153. package/dist/images/node.js +121 -0
  154. package/dist/images/node.js.map +1 -0
  155. package/dist/index.d.ts +24 -0
  156. package/dist/index.d.ts.map +1 -0
  157. package/dist/index.js +20 -0
  158. package/dist/index.js.map +1 -0
  159. package/dist/session/forks.d.ts +10 -0
  160. package/dist/session/forks.d.ts.map +1 -0
  161. package/dist/session/forks.js +42 -0
  162. package/dist/session/forks.js.map +1 -0
  163. package/dist/session/observation.d.ts +42 -0
  164. package/dist/session/observation.d.ts.map +1 -0
  165. package/dist/session/observation.js +281 -0
  166. package/dist/session/observation.js.map +1 -0
  167. package/dist/session/session.d.ts +133 -0
  168. package/dist/session/session.d.ts.map +1 -0
  169. package/dist/session/session.js +811 -0
  170. package/dist/session/session.js.map +1 -0
  171. package/dist/session/transaction.d.ts +119 -0
  172. package/dist/session/transaction.d.ts.map +1 -0
  173. package/dist/session/transaction.js +795 -0
  174. package/dist/session/transaction.js.map +1 -0
  175. package/dist/storage/jsonl/index.d.ts +2 -0
  176. package/dist/storage/jsonl/index.d.ts.map +1 -0
  177. package/dist/storage/jsonl/index.js +2 -0
  178. package/dist/storage/jsonl/index.js.map +1 -0
  179. package/dist/storage/jsonl/node.d.ts +6 -0
  180. package/dist/storage/jsonl/node.d.ts.map +1 -0
  181. package/dist/storage/jsonl/node.js +8 -0
  182. package/dist/storage/jsonl/node.js.map +1 -0
  183. package/dist/storage/jsonl/storage.d.ts +143 -0
  184. package/dist/storage/jsonl/storage.d.ts.map +1 -0
  185. package/dist/storage/jsonl/storage.js +682 -0
  186. package/dist/storage/jsonl/storage.js.map +1 -0
  187. package/dist/storage/memory.d.ts +62 -0
  188. package/dist/storage/memory.d.ts.map +1 -0
  189. package/dist/storage/memory.js +733 -0
  190. package/dist/storage/memory.js.map +1 -0
  191. package/dist/storage/scan.d.ts +18 -0
  192. package/dist/storage/scan.d.ts.map +1 -0
  193. package/dist/storage/scan.js +29 -0
  194. package/dist/storage/scan.js.map +1 -0
  195. package/dist/storage/sqlite/cloudflare.d.ts +46 -0
  196. package/dist/storage/sqlite/cloudflare.d.ts.map +1 -0
  197. package/dist/storage/sqlite/cloudflare.js +106 -0
  198. package/dist/storage/sqlite/cloudflare.js.map +1 -0
  199. package/dist/storage/sqlite/database.d.ts +37 -0
  200. package/dist/storage/sqlite/database.d.ts.map +1 -0
  201. package/dist/storage/sqlite/database.js +2 -0
  202. package/dist/storage/sqlite/database.js.map +1 -0
  203. package/dist/storage/sqlite/index.d.ts +4 -0
  204. package/dist/storage/sqlite/index.d.ts.map +1 -0
  205. package/dist/storage/sqlite/index.js +3 -0
  206. package/dist/storage/sqlite/index.js.map +1 -0
  207. package/dist/storage/sqlite/migrations.d.ts +11 -0
  208. package/dist/storage/sqlite/migrations.d.ts.map +1 -0
  209. package/dist/storage/sqlite/migrations.js +111 -0
  210. package/dist/storage/sqlite/migrations.js.map +1 -0
  211. package/dist/storage/sqlite/node.d.ts +41 -0
  212. package/dist/storage/sqlite/node.d.ts.map +1 -0
  213. package/dist/storage/sqlite/node.js +179 -0
  214. package/dist/storage/sqlite/node.js.map +1 -0
  215. package/dist/storage/sqlite/storage.d.ts +65 -0
  216. package/dist/storage/sqlite/storage.d.ts.map +1 -0
  217. package/dist/storage/sqlite/storage.js +670 -0
  218. package/dist/storage/sqlite/storage.js.map +1 -0
  219. package/dist/tasks.d.ts +6 -0
  220. package/dist/tasks.d.ts.map +1 -0
  221. package/dist/tasks.js +5 -0
  222. package/dist/tasks.js.map +1 -0
  223. package/dist/testing/assertions.d.ts +5 -0
  224. package/dist/testing/assertions.d.ts.map +1 -0
  225. package/dist/testing/assertions.js +25 -0
  226. package/dist/testing/assertions.js.map +1 -0
  227. package/dist/testing/env-conformance.d.ts +7 -0
  228. package/dist/testing/env-conformance.d.ts.map +1 -0
  229. package/dist/testing/env-conformance.js +431 -0
  230. package/dist/testing/env-conformance.js.map +1 -0
  231. package/dist/testing/index.d.ts +7 -0
  232. package/dist/testing/index.d.ts.map +1 -0
  233. package/dist/testing/index.js +6 -0
  234. package/dist/testing/index.js.map +1 -0
  235. package/dist/testing/runner.d.ts +12 -0
  236. package/dist/testing/runner.d.ts.map +1 -0
  237. package/dist/testing/runner.js +23 -0
  238. package/dist/testing/runner.js.map +1 -0
  239. package/dist/testing/storage-benchmark.d.ts +41 -0
  240. package/dist/testing/storage-benchmark.d.ts.map +1 -0
  241. package/dist/testing/storage-benchmark.js +350 -0
  242. package/dist/testing/storage-benchmark.js.map +1 -0
  243. package/dist/testing/storage-conformance.d.ts +4 -0
  244. package/dist/testing/storage-conformance.d.ts.map +1 -0
  245. package/dist/testing/storage-conformance.js +1213 -0
  246. package/dist/testing/storage-conformance.js.map +1 -0
  247. package/dist/testing/types.d.ts +37 -0
  248. package/dist/testing/types.d.ts.map +1 -0
  249. package/dist/testing/types.js +2 -0
  250. package/dist/testing/types.js.map +1 -0
  251. package/dist/tools/bash.d.ts +55 -0
  252. package/dist/tools/bash.d.ts.map +1 -0
  253. package/dist/tools/bash.js +138 -0
  254. package/dist/tools/bash.js.map +1 -0
  255. package/dist/tools/edit-diff.d.ts +89 -0
  256. package/dist/tools/edit-diff.d.ts.map +1 -0
  257. package/dist/tools/edit-diff.js +386 -0
  258. package/dist/tools/edit-diff.js.map +1 -0
  259. package/dist/tools/edit.d.ts +18 -0
  260. package/dist/tools/edit.d.ts.map +1 -0
  261. package/dist/tools/edit.js +117 -0
  262. package/dist/tools/edit.js.map +1 -0
  263. package/dist/tools/env.d.ts +5 -0
  264. package/dist/tools/env.d.ts.map +1 -0
  265. package/dist/tools/env.js +7 -0
  266. package/dist/tools/env.js.map +1 -0
  267. package/dist/tools/file-mutation-queue.d.ts +9 -0
  268. package/dist/tools/file-mutation-queue.d.ts.map +1 -0
  269. package/dist/tools/file-mutation-queue.js +52 -0
  270. package/dist/tools/file-mutation-queue.js.map +1 -0
  271. package/dist/tools/image-processor.d.ts +47 -0
  272. package/dist/tools/image-processor.d.ts.map +1 -0
  273. package/dist/tools/image-processor.js +30 -0
  274. package/dist/tools/image-processor.js.map +1 -0
  275. package/dist/tools/image.d.ts +20 -0
  276. package/dist/tools/image.d.ts.map +1 -0
  277. package/dist/tools/image.js +178 -0
  278. package/dist/tools/image.js.map +1 -0
  279. package/dist/tools/index.d.ts +46 -0
  280. package/dist/tools/index.d.ts.map +1 -0
  281. package/dist/tools/index.js +23 -0
  282. package/dist/tools/index.js.map +1 -0
  283. package/dist/tools/path-utils.d.ts +5 -0
  284. package/dist/tools/path-utils.d.ts.map +1 -0
  285. package/dist/tools/path-utils.js +26 -0
  286. package/dist/tools/path-utils.js.map +1 -0
  287. package/dist/tools/read.d.ts +25 -0
  288. package/dist/tools/read.d.ts.map +1 -0
  289. package/dist/tools/read.js +235 -0
  290. package/dist/tools/read.js.map +1 -0
  291. package/dist/tools/write.d.ts +10 -0
  292. package/dist/tools/write.d.ts.map +1 -0
  293. package/dist/tools/write.js +31 -0
  294. package/dist/tools/write.js.map +1 -0
  295. package/dist/truncate.d.ts +64 -0
  296. package/dist/truncate.d.ts.map +1 -0
  297. package/dist/truncate.js +154 -0
  298. package/dist/truncate.js.map +1 -0
  299. package/dist/types.d.ts +932 -0
  300. package/dist/types.d.ts.map +1 -0
  301. package/dist/types.js +3 -0
  302. package/dist/types.js.map +1 -0
  303. package/package.json +140 -5
@@ -0,0 +1,117 @@
1
+ import { Type } from "typebox";
2
+ import { defineTool } from "../harness/define.js";
3
+ import { applyEditsToNormalizedContent, detectLineEnding, generateDiffString, generateUnifiedPatch, normalizeToLF, restoreLineEndings, stripBom, } from "./edit-diff.js";
4
+ import { requireEnv } from "./env.js";
5
+ import { withFileMutationQueue } from "./file-mutation-queue.js";
6
+ import { resolveToolPath } from "./path-utils.js";
7
+ const replaceEditSchema = Type.Object({
8
+ oldText: Type.String({
9
+ description: "Exact text for one targeted replacement. It must be unique in the original file and must not overlap with any other edits[].oldText in the same call.",
10
+ }),
11
+ newText: Type.String({ description: "Replacement text for this targeted edit." }),
12
+ });
13
+ const editSchema = Type.Object({
14
+ path: Type.String({ description: "Path to the file to edit (relative or absolute)" }),
15
+ edits: Type.Array(replaceEditSchema, {
16
+ description: "One or more targeted replacements. Each edit is matched against the original file, not incrementally. Do not include overlapping or nested edits. If two changes touch the same block or nearby lines, merge them into one edit instead.",
17
+ }),
18
+ });
19
+ function isSingleEditInput(value) {
20
+ if (!value || typeof value !== "object" || Array.isArray(value))
21
+ return false;
22
+ const edit = value;
23
+ return typeof edit.oldText === "string" && typeof edit.newText === "string";
24
+ }
25
+ /**
26
+ * Repair shapes models commonly send: `edits` as a JSON string or as a single edit object, and a top-level
27
+ * `oldText`/`newText` pair. Works on a copy; the call's arguments stay unchanged.
28
+ */
29
+ function prepareEditArguments(input) {
30
+ if (!input || typeof input !== "object" || Array.isArray(input))
31
+ return input;
32
+ const args = { ...input };
33
+ if (typeof args.edits === "string") {
34
+ try {
35
+ const parsed = JSON.parse(args.edits);
36
+ if (Array.isArray(parsed)) {
37
+ args.edits = parsed;
38
+ }
39
+ else if (isSingleEditInput(parsed)) {
40
+ args.edits = [parsed];
41
+ }
42
+ }
43
+ catch { }
44
+ }
45
+ else if (isSingleEditInput(args.edits)) {
46
+ args.edits = [args.edits];
47
+ }
48
+ const legacy = args;
49
+ if (typeof legacy.oldText !== "string" || typeof legacy.newText !== "string")
50
+ return legacy;
51
+ const edits = Array.isArray(legacy.edits) ? [...legacy.edits] : [];
52
+ edits.push({ oldText: legacy.oldText, newText: legacy.newText });
53
+ const { oldText: _oldText, newText: _newText, ...rest } = legacy;
54
+ return { ...rest, edits };
55
+ }
56
+ function validateEditInput(input) {
57
+ if (!Array.isArray(input.edits) || input.edits.length === 0) {
58
+ throw new Error("Edit tool input is invalid. edits must contain at least one replacement.");
59
+ }
60
+ return { path: input.path, edits: input.edits };
61
+ }
62
+ function editAccessError(path, error) {
63
+ return new Error(`Could not edit file: ${path}. Error code: ${error.code}.`, { cause: error });
64
+ }
65
+ export function createEditTool() {
66
+ return defineTool({
67
+ name: "edit",
68
+ description: "Edit a single file using exact text replacement. Every edits[].oldText must match a unique, non-overlapping region of the original file. If two changes affect the same block or nearby lines, merge them into one edit instead of emitting overlapping edits. Do not include large unchanged regions just to connect distant changes.",
69
+ parameters: editSchema,
70
+ prepareArguments: prepareEditArguments,
71
+ async execute(args, api, context) {
72
+ const { path, edits } = validateEditInput(args);
73
+ const env = requireEnv(api);
74
+ const absolutePath = await resolveToolPath(env, path, context);
75
+ return withFileMutationQueue(env, absolutePath, async () => {
76
+ if (context.abortSignal?.aborted)
77
+ throw new Error("Operation aborted");
78
+ const info = await env.fileInfo(absolutePath, context);
79
+ if (!info.ok)
80
+ throw editAccessError(path, info.error);
81
+ if (info.value.kind !== "file" && info.value.kind !== "symlink") {
82
+ throw new Error(`Could not edit file: ${path}. Path is not a file.`);
83
+ }
84
+ const readResult = await env.readTextFile(absolutePath, context);
85
+ if (!readResult.ok)
86
+ throw editAccessError(path, readResult.error);
87
+ if (context.abortSignal?.aborted)
88
+ throw new Error("Operation aborted");
89
+ const { bom, text: content } = stripBom(readResult.value);
90
+ const originalEnding = detectLineEnding(content);
91
+ const normalizedContent = normalizeToLF(content);
92
+ const { baseContent, newContent } = applyEditsToNormalizedContent(normalizedContent, edits, path);
93
+ if (context.abortSignal?.aborted)
94
+ throw new Error("Operation aborted");
95
+ const finalContent = bom + restoreLineEndings(newContent, originalEnding);
96
+ const writeResult = await env.writeFile(absolutePath, finalContent, context);
97
+ if (!writeResult.ok)
98
+ throw editAccessError(path, writeResult.error);
99
+ if (context.abortSignal?.aborted)
100
+ throw new Error("Operation aborted");
101
+ const diffResult = generateDiffString(baseContent, newContent);
102
+ const details = {
103
+ diff: diffResult.diff,
104
+ patch: generateUnifiedPatch(path, baseContent, newContent),
105
+ ...(diffResult.firstChangedLine === undefined
106
+ ? {}
107
+ : { firstChangedLine: diffResult.firstChangedLine }),
108
+ };
109
+ return {
110
+ output: [{ type: "text", text: `Successfully replaced ${edits.length} block(s) in ${path}.` }],
111
+ details,
112
+ };
113
+ }, context);
114
+ },
115
+ });
116
+ }
117
+ //# sourceMappingURL=edit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edit.js","sourceRoot":"","sources":["../../src/tools/edit.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Static, Type } from \"typebox\";\nimport type { FileError } from \"../env/index.ts\";\nimport { defineTool } from \"../harness/define.ts\";\nimport type { ToolRegistration } from \"../harness/types.ts\";\nimport {\n\tapplyEditsToNormalizedContent,\n\tdetectLineEnding,\n\ttype Edit,\n\tgenerateDiffString,\n\tgenerateUnifiedPatch,\n\tnormalizeToLF,\n\trestoreLineEndings,\n\tstripBom,\n} from \"./edit-diff.ts\";\nimport { requireEnv } from \"./env.ts\";\nimport { withFileMutationQueue } from \"./file-mutation-queue.ts\";\nimport { resolveToolPath } from \"./path-utils.ts\";\n\nconst replaceEditSchema = Type.Object({\n\toldText: Type.String({\n\t\tdescription:\n\t\t\t\"Exact text for one targeted replacement. It must be unique in the original file and must not overlap with any other edits[].oldText in the same call.\",\n\t}),\n\tnewText: Type.String({ description: \"Replacement text for this targeted edit.\" }),\n});\n\nconst editSchema = Type.Object({\n\tpath: Type.String({ description: \"Path to the file to edit (relative or absolute)\" }),\n\tedits: Type.Array(replaceEditSchema, {\n\t\tdescription:\n\t\t\t\"One or more targeted replacements. Each edit is matched against the original file, not incrementally. Do not include overlapping or nested edits. If two changes touch the same block or nearby lines, merge them into one edit instead.\",\n\t}),\n});\n\nexport type EditToolInput = Static<typeof editSchema>;\ntype LegacyEditToolInput = EditToolInput & { oldText?: unknown; newText?: unknown };\ntype SingleEditInput = { oldText: string; newText: string };\n\nfunction isSingleEditInput(value: unknown): value is SingleEditInput {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst edit = value as Record<string, unknown>;\n\treturn typeof edit.oldText === \"string\" && typeof edit.newText === \"string\";\n}\n\n/**\n * Repair shapes models commonly send: `edits` as a JSON string or as a single edit object, and a top-level\n * `oldText`/`newText` pair. Works on a copy; the call's arguments stay unchanged.\n */\nfunction prepareEditArguments(input: unknown): EditToolInput {\n\tif (!input || typeof input !== \"object\" || Array.isArray(input)) return input as EditToolInput;\n\tconst args: Record<string, unknown> = { ...input };\n\tif (typeof args.edits === \"string\") {\n\t\ttry {\n\t\t\tconst parsed: unknown = JSON.parse(args.edits);\n\t\t\tif (Array.isArray(parsed)) {\n\t\t\t\targs.edits = parsed;\n\t\t\t} else if (isSingleEditInput(parsed)) {\n\t\t\t\targs.edits = [parsed];\n\t\t\t}\n\t\t} catch {}\n\t} else if (isSingleEditInput(args.edits)) {\n\t\targs.edits = [args.edits];\n\t}\n\n\tconst legacy = args as LegacyEditToolInput;\n\tif (typeof legacy.oldText !== \"string\" || typeof legacy.newText !== \"string\") return legacy;\n\tconst edits = Array.isArray(legacy.edits) ? [...legacy.edits] : [];\n\tedits.push({ oldText: legacy.oldText, newText: legacy.newText });\n\tconst { oldText: _oldText, newText: _newText, ...rest } = legacy;\n\treturn { ...rest, edits };\n}\n\nexport type EditToolDetails = {\n\tdiff: string;\n\tpatch: string;\n\tfirstChangedLine?: number;\n};\n\nfunction validateEditInput(input: EditToolInput): { path: string; edits: Edit[] } {\n\tif (!Array.isArray(input.edits) || input.edits.length === 0) {\n\t\tthrow new Error(\"Edit tool input is invalid. edits must contain at least one replacement.\");\n\t}\n\treturn { path: input.path, edits: input.edits };\n}\n\nfunction editAccessError(path: string, error: FileError): Error {\n\treturn new Error(`Could not edit file: ${path}. Error code: ${error.code}.`, { cause: error });\n}\n\nexport function createEditTool(): ToolRegistration<typeof editSchema, EditToolDetails> {\n\treturn defineTool({\n\t\tname: \"edit\",\n\t\tdescription:\n\t\t\t\"Edit a single file using exact text replacement. Every edits[].oldText must match a unique, non-overlapping region of the original file. If two changes affect the same block or nearby lines, merge them into one edit instead of emitting overlapping edits. Do not include large unchanged regions just to connect distant changes.\",\n\t\tparameters: editSchema,\n\t\tprepareArguments: prepareEditArguments,\n\t\tasync execute(args, api, context) {\n\t\t\tconst { path, edits } = validateEditInput(args);\n\t\t\tconst env = requireEnv(api);\n\t\t\tconst absolutePath = await resolveToolPath(env, path, context);\n\t\t\treturn withFileMutationQueue(\n\t\t\t\tenv,\n\t\t\t\tabsolutePath,\n\t\t\t\tasync () => {\n\t\t\t\t\tif (context.abortSignal?.aborted) throw new Error(\"Operation aborted\");\n\t\t\t\t\tconst info = await env.fileInfo(absolutePath, context);\n\t\t\t\t\tif (!info.ok) throw editAccessError(path, info.error);\n\t\t\t\t\tif (info.value.kind !== \"file\" && info.value.kind !== \"symlink\") {\n\t\t\t\t\t\tthrow new Error(`Could not edit file: ${path}. Path is not a file.`);\n\t\t\t\t\t}\n\n\t\t\t\t\tconst readResult = await env.readTextFile(absolutePath, context);\n\t\t\t\t\tif (!readResult.ok) throw editAccessError(path, readResult.error);\n\t\t\t\t\tif (context.abortSignal?.aborted) throw new Error(\"Operation aborted\");\n\n\t\t\t\t\tconst { bom, text: content } = stripBom(readResult.value);\n\t\t\t\t\tconst originalEnding = detectLineEnding(content);\n\t\t\t\t\tconst normalizedContent = normalizeToLF(content);\n\t\t\t\t\tconst { baseContent, newContent } = applyEditsToNormalizedContent(normalizedContent, edits, path);\n\t\t\t\t\tif (context.abortSignal?.aborted) throw new Error(\"Operation aborted\");\n\n\t\t\t\t\tconst finalContent = bom + restoreLineEndings(newContent, originalEnding);\n\t\t\t\t\tconst writeResult = await env.writeFile(absolutePath, finalContent, context);\n\t\t\t\t\tif (!writeResult.ok) throw editAccessError(path, writeResult.error);\n\t\t\t\t\tif (context.abortSignal?.aborted) throw new Error(\"Operation aborted\");\n\n\t\t\t\t\tconst diffResult = generateDiffString(baseContent, newContent);\n\t\t\t\t\tconst details: EditToolDetails = {\n\t\t\t\t\t\tdiff: diffResult.diff,\n\t\t\t\t\t\tpatch: generateUnifiedPatch(path, baseContent, newContent),\n\t\t\t\t\t\t...(diffResult.firstChangedLine === undefined\n\t\t\t\t\t\t\t? {}\n\t\t\t\t\t\t\t: { firstChangedLine: diffResult.firstChangedLine }),\n\t\t\t\t\t};\n\t\t\t\t\treturn {\n\t\t\t\t\t\toutput: [{ type: \"text\", text: `Successfully replaced ${edits.length} block(s) in ${path}.` }],\n\t\t\t\t\t\tdetails,\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t\tcontext,\n\t\t\t);\n\t\t},\n\t});\n}\n"]}
@@ -0,0 +1,5 @@
1
+ import type { ExecutionEnv } from "../env/index.ts";
2
+ import type { ToolExecutionApi } from "../harness/types.ts";
3
+ /** The call's execution environment; a tool without one fails with an ordinary error result. */
4
+ export declare function requireEnv(api: ToolExecutionApi): ExecutionEnv;
5
+ //# sourceMappingURL=env.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/tools/env.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5D,gGAAgG;AAChG,wBAAgB,UAAU,CAAC,GAAG,EAAE,gBAAgB,GAAG,YAAY,CAG9D"}
@@ -0,0 +1,7 @@
1
+ /** The call's execution environment; a tool without one fails with an ordinary error result. */
2
+ export function requireEnv(api) {
3
+ if (api.env === undefined)
4
+ throw new Error("No execution environment is configured");
5
+ return api.env;
6
+ }
7
+ //# sourceMappingURL=env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/tools/env.ts"],"names":[],"mappings":"AAGA,gGAAgG;AAChG,MAAM,UAAU,UAAU,CAAC,GAAqB;IAC/C,IAAI,GAAG,CAAC,GAAG,KAAK,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;IACrF,OAAO,GAAG,CAAC,GAAG,CAAC;AAChB,CAAC","sourcesContent":["import type { ExecutionEnv } from \"../env/index.ts\";\nimport type { ToolExecutionApi } from \"../harness/types.ts\";\n\n/** The call's execution environment; a tool without one fails with an ordinary error result. */\nexport function requireEnv(api: ToolExecutionApi): ExecutionEnv {\n\tif (api.env === undefined) throw new Error(\"No execution environment is configured\");\n\treturn api.env;\n}\n"]}
@@ -0,0 +1,9 @@
1
+ import type { Context } from "@ny4n/chord";
2
+ import { type ExecutionEnv } from "../env/index.ts";
3
+ /**
4
+ * Serialize `edit` and `write` mutations of one file within this process: same file system id and canonical path,
5
+ * whichever environment object the call got. Other files, and other file systems, never wait. Concurrent calls on one
6
+ * file run in the order their keys resolve. Not a lock against `bash` or other processes.
7
+ */
8
+ export declare function withFileMutationQueue<T>(env: ExecutionEnv, path: string, fn: () => Promise<T>, context: Context): Promise<T>;
9
+ //# sourceMappingURL=file-mutation-queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-mutation-queue.d.ts","sourceRoot":"","sources":["../../src/tools/file-mutation-queue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,KAAK,YAAY,EAAc,MAAM,iBAAiB,CAAC;AA0BhE;;;;GAIG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,EAC5C,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,OAAO,EAAE,OAAO,GACd,OAAO,CAAC,CAAC,CAAC,CAiBZ"}
@@ -0,0 +1,52 @@
1
+ import { getOrThrow } from "../env/index.js";
2
+ /** Tail of the mutation chain of each file, keyed by file system id and canonical path. */
3
+ const queues = new Map();
4
+ async function mutationKey(env, path, context) {
5
+ const absolutePath = getOrThrow(await env.absolutePath(path, context));
6
+ return `${env.id}\0${await canonical(env, absolutePath, context)}`;
7
+ }
8
+ /**
9
+ * The canonical path; for a file that does not exist yet, its canonical parent joined with its name, so a `write` that
10
+ * creates a file and a later mutation of it share one key even under a symlinked directory.
11
+ */
12
+ async function canonical(env, absolutePath, context) {
13
+ const result = await env.canonicalPath(absolutePath, context);
14
+ if (result.ok)
15
+ return result.value;
16
+ if (result.error.code === "not_supported")
17
+ return absolutePath;
18
+ if (result.error.code !== "not_found")
19
+ throw result.error;
20
+ // The file system splits the path, so a name may contain characters that are separators elsewhere.
21
+ const parent = getOrThrow(await env.joinPath([absolutePath, ".."], context));
22
+ if (parent === absolutePath || !absolutePath.startsWith(parent))
23
+ return absolutePath;
24
+ const name = absolutePath.slice(parent.length + (/[/\\]$/.test(parent) ? 0 : 1));
25
+ return getOrThrow(await env.joinPath([await canonical(env, parent, context), name], context));
26
+ }
27
+ /**
28
+ * Serialize `edit` and `write` mutations of one file within this process: same file system id and canonical path,
29
+ * whichever environment object the call got. Other files, and other file systems, never wait. Concurrent calls on one
30
+ * file run in the order their keys resolve. Not a lock against `bash` or other processes.
31
+ */
32
+ export async function withFileMutationQueue(env, path, fn, context) {
33
+ const key = await mutationKey(env, path, context);
34
+ // Take the slot without awaiting, so no other call can take it in between.
35
+ const previous = queues.get(key) ?? Promise.resolve();
36
+ let release = () => { };
37
+ const done = new Promise((resolve) => {
38
+ release = resolve;
39
+ });
40
+ const tail = previous.then(() => done);
41
+ queues.set(key, tail);
42
+ await previous;
43
+ try {
44
+ return await fn();
45
+ }
46
+ finally {
47
+ release();
48
+ if (queues.get(key) === tail)
49
+ queues.delete(key);
50
+ }
51
+ }
52
+ //# sourceMappingURL=file-mutation-queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-mutation-queue.js","sourceRoot":"","sources":["../../src/tools/file-mutation-queue.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAEhE,2FAA2F;AAC3F,MAAM,MAAM,GAAG,IAAI,GAAG,EAAyB,CAAC;AAEhD,KAAK,UAAU,WAAW,CAAC,GAAiB,EAAE,IAAY,EAAE,OAAgB;IAC3E,MAAM,YAAY,GAAG,UAAU,CAAC,MAAM,GAAG,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;IACvE,OAAO,GAAG,GAAG,CAAC,EAAE,KAAK,MAAM,SAAS,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE,CAAC;AACpE,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,SAAS,CAAC,GAAiB,EAAE,YAAoB,EAAE,OAAgB;IACjF,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,aAAa,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;IAC9D,IAAI,MAAM,CAAC,EAAE;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC;IACnC,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,KAAK,eAAe;QAAE,OAAO,YAAY,CAAC;IAC/D,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,KAAK,WAAW;QAAE,MAAM,MAAM,CAAC,KAAK,CAAC;IAC1D,mGAAmG;IACnG,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,GAAG,CAAC,QAAQ,CAAC,CAAC,YAAY,EAAE,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;IAC7E,IAAI,MAAM,KAAK,YAAY,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,MAAM,CAAC;QAAE,OAAO,YAAY,CAAC;IACrF,MAAM,IAAI,GAAG,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACjF,OAAO,UAAU,CAAC,MAAM,GAAG,CAAC,QAAQ,CAAC,CAAC,MAAM,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;AAC/F,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAC1C,GAAiB,EACjB,IAAY,EACZ,EAAoB,EACpB,OAAgB;IAEhB,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IAClD,2EAA2E;IAC3E,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IACtD,IAAI,OAAO,GAAG,GAAS,EAAE,GAAE,CAAC,CAAC;IAC7B,MAAM,IAAI,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;QAC1C,OAAO,GAAG,OAAO,CAAC;IACnB,CAAC,CAAC,CAAC;IACH,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IACvC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACtB,MAAM,QAAQ,CAAC;IACf,IAAI,CAAC;QACJ,OAAO,MAAM,EAAE,EAAE,CAAC;IACnB,CAAC;YAAS,CAAC;QACV,OAAO,EAAE,CAAC;QACV,IAAI,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,IAAI;YAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAClD,CAAC;AACF,CAAC","sourcesContent":["import type { Context } from \"@ny4n/chord\";\nimport { type ExecutionEnv, getOrThrow } from \"../env/index.ts\";\n\n/** Tail of the mutation chain of each file, keyed by file system id and canonical path. */\nconst queues = new Map<string, Promise<void>>();\n\nasync function mutationKey(env: ExecutionEnv, path: string, context: Context): Promise<string> {\n\tconst absolutePath = getOrThrow(await env.absolutePath(path, context));\n\treturn `${env.id}\\0${await canonical(env, absolutePath, context)}`;\n}\n\n/**\n * The canonical path; for a file that does not exist yet, its canonical parent joined with its name, so a `write` that\n * creates a file and a later mutation of it share one key even under a symlinked directory.\n */\nasync function canonical(env: ExecutionEnv, absolutePath: string, context: Context): Promise<string> {\n\tconst result = await env.canonicalPath(absolutePath, context);\n\tif (result.ok) return result.value;\n\tif (result.error.code === \"not_supported\") return absolutePath;\n\tif (result.error.code !== \"not_found\") throw result.error;\n\t// The file system splits the path, so a name may contain characters that are separators elsewhere.\n\tconst parent = getOrThrow(await env.joinPath([absolutePath, \"..\"], context));\n\tif (parent === absolutePath || !absolutePath.startsWith(parent)) return absolutePath;\n\tconst name = absolutePath.slice(parent.length + (/[/\\\\]$/.test(parent) ? 0 : 1));\n\treturn getOrThrow(await env.joinPath([await canonical(env, parent, context), name], context));\n}\n\n/**\n * Serialize `edit` and `write` mutations of one file within this process: same file system id and canonical path,\n * whichever environment object the call got. Other files, and other file systems, never wait. Concurrent calls on one\n * file run in the order their keys resolve. Not a lock against `bash` or other processes.\n */\nexport async function withFileMutationQueue<T>(\n\tenv: ExecutionEnv,\n\tpath: string,\n\tfn: () => Promise<T>,\n\tcontext: Context,\n): Promise<T> {\n\tconst key = await mutationKey(env, path, context);\n\t// Take the slot without awaiting, so no other call can take it in between.\n\tconst previous = queues.get(key) ?? Promise.resolve();\n\tlet release = (): void => {};\n\tconst done = new Promise<void>((resolve) => {\n\t\trelease = resolve;\n\t});\n\tconst tail = previous.then(() => done);\n\tqueues.set(key, tail);\n\tawait previous;\n\ttry {\n\t\treturn await fn();\n\t} finally {\n\t\trelease();\n\t\tif (queues.get(key) === tail) queues.delete(key);\n\t}\n}\n"]}
@@ -0,0 +1,47 @@
1
+ /** Limits an image the model sees must keep; the defaults below are the coding agent's. */
2
+ export type ImageLimits = {
3
+ /** Pixels. Default 2000. */
4
+ readonly maxWidth: number;
5
+ /** Pixels. Default 2000. */
6
+ readonly maxHeight: number;
7
+ /** Bytes of base64. Default 4.5 MiB, below Anthropic's 5 MB. */
8
+ readonly maxBytes: number;
9
+ };
10
+ export declare const DEFAULT_IMAGE_LIMITS: ImageLimits;
11
+ /** An image the model can take: a supported format, within the limits, as base64. */
12
+ export type PreparedImage = {
13
+ readonly data: string;
14
+ readonly mimeType: string;
15
+ /** Set when the image was resized: its size before and after, for the model to map coordinates back. */
16
+ readonly resized?: {
17
+ readonly from: {
18
+ readonly width: number;
19
+ readonly height: number;
20
+ };
21
+ readonly to: {
22
+ readonly width: number;
23
+ readonly height: number;
24
+ };
25
+ };
26
+ /** The original format, set when it differs from `mimeType`: BMP becomes PNG, a large PNG may become JPEG. */
27
+ readonly convertedFrom?: string;
28
+ };
29
+ /**
30
+ * Decodes, orients, resizes, and re-encodes images so the model can take them. `read` uses one when given; without
31
+ * one, it passes supported images within the byte limit through as they are. `@ny4n/durable/images`
32
+ * provides a Photon-based one.
33
+ */
34
+ export type ImageProcessor = {
35
+ /**
36
+ * The image, prepared to fit `limits`, or `undefined` when it cannot be decoded or made to fit. A promise, so a
37
+ * processor can do the work off the event loop.
38
+ */
39
+ prepare(bytes: Uint8Array, mimeType: string, limits: ImageLimits): Promise<PreparedImage | undefined>;
40
+ };
41
+ /** Formats every provider takes inline; others, such as BMP, need converting. */
42
+ export declare const INLINE_IMAGE_TYPES: ReadonlySet<string>;
43
+ /** Standard base64 of `bytes`, without platform APIs such as `Buffer` or `btoa`. */
44
+ export declare function toBase64(bytes: Uint8Array): string;
45
+ /** The length of the base64 of `byteLength` bytes. */
46
+ export declare function base64Length(byteLength: number): number;
47
+ //# sourceMappingURL=image-processor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-processor.d.ts","sourceRoot":"","sources":["../../src/tools/image-processor.ts"],"names":[],"mappings":"AAAA,2FAA2F;AAC3F,MAAM,MAAM,WAAW,GAAG;IACzB,4BAA4B;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,4BAA4B;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,WAA8E,CAAC;AAElH,qFAAqF;AACrF,MAAM,MAAM,aAAa,GAAG;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,wGAAwG;IACxG,QAAQ,CAAC,OAAO,CAAC,EAAE;QAClB,QAAQ,CAAC,IAAI,EAAE;YAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC;QACnE,QAAQ,CAAC,EAAE,EAAE;YAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC;KACjE,CAAC;IACF,8GAA8G;IAC9G,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CAChC,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG;IAC5B;;;OAGG;IACH,OAAO,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAAC;CACtG,CAAC;AAEF,iFAAiF;AACjF,eAAO,MAAM,kBAAkB,EAAE,WAAW,CAAC,MAAM,CAAmE,CAAC;AAIvH,oFAAoF;AACpF,wBAAgB,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAmBlD;AAED,sDAAsD;AACtD,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEvD"}
@@ -0,0 +1,30 @@
1
+ export const DEFAULT_IMAGE_LIMITS = { maxWidth: 2000, maxHeight: 2000, maxBytes: 4.5 * 1024 * 1024 };
2
+ /** Formats every provider takes inline; others, such as BMP, need converting. */
3
+ export const INLINE_IMAGE_TYPES = new Set(["image/png", "image/jpeg", "image/gif", "image/webp"]);
4
+ const BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
5
+ /** Standard base64 of `bytes`, without platform APIs such as `Buffer` or `btoa`. */
6
+ export function toBase64(bytes) {
7
+ const parts = [];
8
+ let chunk = "";
9
+ for (let i = 0; i < bytes.length; i += 3) {
10
+ const a = bytes[i];
11
+ const b = bytes[i + 1];
12
+ const c = bytes[i + 2];
13
+ chunk += BASE64[a >> 2];
14
+ chunk += BASE64[((a & 3) << 4) | ((b ?? 0) >> 4)];
15
+ chunk += b === undefined ? "=" : BASE64[((b & 15) << 2) | ((c ?? 0) >> 6)];
16
+ chunk += c === undefined ? "=" : BASE64[c & 63];
17
+ // Join in pieces: one string grown a character at a time is slow for megabytes.
18
+ if (chunk.length >= 8192) {
19
+ parts.push(chunk);
20
+ chunk = "";
21
+ }
22
+ }
23
+ parts.push(chunk);
24
+ return parts.join("");
25
+ }
26
+ /** The length of the base64 of `byteLength` bytes. */
27
+ export function base64Length(byteLength) {
28
+ return Math.ceil(byteLength / 3) * 4;
29
+ }
30
+ //# sourceMappingURL=image-processor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-processor.js","sourceRoot":"","sources":["../../src/tools/image-processor.ts"],"names":[],"mappings":"AAUA,MAAM,CAAC,MAAM,oBAAoB,GAAgB,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC;AA4BlH,iFAAiF;AACjF,MAAM,CAAC,MAAM,kBAAkB,GAAwB,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,CAAC,CAAC,CAAC;AAEvH,MAAM,MAAM,GAAG,kEAAkE,CAAC;AAElF,oFAAoF;AACpF,MAAM,UAAU,QAAQ,CAAC,KAAiB;IACzC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,KAAK,GAAG,EAAE,CAAC;IACf,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1C,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC;QACpB,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACvB,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACvB,KAAK,IAAI,MAAM,CAAC,CAAC,IAAI,CAAC,CAAE,CAAC;QACzB,KAAK,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAE,CAAC;QACnD,KAAK,IAAI,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAE,CAAC;QAC5E,KAAK,IAAI,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAE,CAAC;QACjD,gFAAgF;QAChF,IAAI,KAAK,CAAC,MAAM,IAAI,IAAI,EAAE,CAAC;YAC1B,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAClB,KAAK,GAAG,EAAE,CAAC;QACZ,CAAC;IACF,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClB,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACvB,CAAC;AAED,sDAAsD;AACtD,MAAM,UAAU,YAAY,CAAC,UAAkB;IAC9C,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;AACtC,CAAC","sourcesContent":["/** Limits an image the model sees must keep; the defaults below are the coding agent's. */\nexport type ImageLimits = {\n\t/** Pixels. Default 2000. */\n\treadonly maxWidth: number;\n\t/** Pixels. Default 2000. */\n\treadonly maxHeight: number;\n\t/** Bytes of base64. Default 4.5 MiB, below Anthropic's 5 MB. */\n\treadonly maxBytes: number;\n};\n\nexport const DEFAULT_IMAGE_LIMITS: ImageLimits = { maxWidth: 2000, maxHeight: 2000, maxBytes: 4.5 * 1024 * 1024 };\n\n/** An image the model can take: a supported format, within the limits, as base64. */\nexport type PreparedImage = {\n\treadonly data: string;\n\treadonly mimeType: string;\n\t/** Set when the image was resized: its size before and after, for the model to map coordinates back. */\n\treadonly resized?: {\n\t\treadonly from: { readonly width: number; readonly height: number };\n\t\treadonly to: { readonly width: number; readonly height: number };\n\t};\n\t/** The original format, set when it differs from `mimeType`: BMP becomes PNG, a large PNG may become JPEG. */\n\treadonly convertedFrom?: string;\n};\n\n/**\n * Decodes, orients, resizes, and re-encodes images so the model can take them. `read` uses one when given; without\n * one, it passes supported images within the byte limit through as they are. `@ny4n/durable/images`\n * provides a Photon-based one.\n */\nexport type ImageProcessor = {\n\t/**\n\t * The image, prepared to fit `limits`, or `undefined` when it cannot be decoded or made to fit. A promise, so a\n\t * processor can do the work off the event loop.\n\t */\n\tprepare(bytes: Uint8Array, mimeType: string, limits: ImageLimits): Promise<PreparedImage | undefined>;\n};\n\n/** Formats every provider takes inline; others, such as BMP, need converting. */\nexport const INLINE_IMAGE_TYPES: ReadonlySet<string> = new Set([\"image/png\", \"image/jpeg\", \"image/gif\", \"image/webp\"]);\n\nconst BASE64 = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n\n/** Standard base64 of `bytes`, without platform APIs such as `Buffer` or `btoa`. */\nexport function toBase64(bytes: Uint8Array): string {\n\tconst parts: string[] = [];\n\tlet chunk = \"\";\n\tfor (let i = 0; i < bytes.length; i += 3) {\n\t\tconst a = bytes[i]!;\n\t\tconst b = bytes[i + 1];\n\t\tconst c = bytes[i + 2];\n\t\tchunk += BASE64[a >> 2]!;\n\t\tchunk += BASE64[((a & 3) << 4) | ((b ?? 0) >> 4)]!;\n\t\tchunk += b === undefined ? \"=\" : BASE64[((b & 15) << 2) | ((c ?? 0) >> 6)]!;\n\t\tchunk += c === undefined ? \"=\" : BASE64[c & 63]!;\n\t\t// Join in pieces: one string grown a character at a time is slow for megabytes.\n\t\tif (chunk.length >= 8192) {\n\t\t\tparts.push(chunk);\n\t\t\tchunk = \"\";\n\t\t}\n\t}\n\tparts.push(chunk);\n\treturn parts.join(\"\");\n}\n\n/** The length of the base64 of `byteLength` bytes. */\nexport function base64Length(byteLength: number): number {\n\treturn Math.ceil(byteLength / 3) * 4;\n}\n"]}
@@ -0,0 +1,20 @@
1
+ /** Positional reads of a file of `size` bytes. */
2
+ export type ByteSource = {
3
+ readonly size: number;
4
+ read(offset: number, length: number): Promise<Uint8Array>;
5
+ };
6
+ /**
7
+ * `detectSupportedImageMimeType` of a whole file, reading only its header and, for PNG, the chunk headers up to the
8
+ * first `acTL` or `IDAT`.
9
+ */
10
+ export declare function detectSupportedImageMimeTypeOf(source: ByteSource): Promise<string | undefined>;
11
+ export declare function detectSupportedImageMimeType(buffer: Uint8Array): string | undefined;
12
+ /**
13
+ * The width and height an image of `mimeType` declares in its header, without decoding it: PNG's IHDR, GIF's screen,
14
+ * WebP's VP8, VP8L, or VP8X chunk, JPEG's first SOF segment. `undefined` when the header cannot be read.
15
+ */
16
+ export declare function imageDimensions(bytes: Uint8Array, mimeType: string): {
17
+ readonly width: number;
18
+ readonly height: number;
19
+ } | undefined;
20
+ //# sourceMappingURL=image.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../../src/tools/image.ts"],"names":[],"mappings":"AAKA,kDAAkD;AAClD,MAAM,MAAM,UAAU,GAAG;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CAC1D,CAAC;AAEF;;;GAGG;AACH,wBAAsB,8BAA8B,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAIpG;AA0BD,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,GAAG,SAAS,CAOnF;AAsBD;;;GAGG;AACH,wBAAgB,eAAe,CAC9B,KAAK,EAAE,UAAU,EACjB,QAAQ,EAAE,MAAM,GACd;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAqCjE"}
@@ -0,0 +1,178 @@
1
+ const PNG_SIGNATURE = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
2
+ /** Bytes every check except the APNG chunk walk needs: BMP reads up to offset 29. */
3
+ const HEADER_BYTES = 32;
4
+ const BLOCK_BYTES = 64 * 1024;
5
+ /**
6
+ * `detectSupportedImageMimeType` of a whole file, reading only its header and, for PNG, the chunk headers up to the
7
+ * first `acTL` or `IDAT`.
8
+ */
9
+ export async function detectSupportedImageMimeTypeOf(source) {
10
+ const header = await source.read(0, HEADER_BYTES);
11
+ if (!startsWith(header, PNG_SIGNATURE))
12
+ return detectSupportedImageMimeType(header);
13
+ return isPng(header) && !(await isAnimatedPngOf(source)) ? "image/png" : undefined;
14
+ }
15
+ /** `isAnimatedPng` over a file read in blocks. */
16
+ async function isAnimatedPngOf(source) {
17
+ let block = new Uint8Array(0);
18
+ let blockStart = 0;
19
+ const bytesAt = async (offset, length) => {
20
+ if (offset < blockStart || offset + length > blockStart + block.length) {
21
+ blockStart = offset;
22
+ block = await source.read(offset, BLOCK_BYTES);
23
+ }
24
+ return block.subarray(offset - blockStart, offset - blockStart + length);
25
+ };
26
+ let offset = PNG_SIGNATURE.length;
27
+ while (offset + 8 <= source.size) {
28
+ const chunkHeader = await bytesAt(offset, 8);
29
+ const chunkLength = readUint32BE(chunkHeader, 0);
30
+ if (startsWithAscii(chunkHeader, 4, "acTL"))
31
+ return true;
32
+ if (startsWithAscii(chunkHeader, 4, "IDAT"))
33
+ return false;
34
+ const nextOffset = offset + 8 + chunkLength + 4;
35
+ if (nextOffset <= offset || nextOffset > source.size)
36
+ return false;
37
+ offset = nextOffset;
38
+ }
39
+ return false;
40
+ }
41
+ export function detectSupportedImageMimeType(buffer) {
42
+ if (startsWith(buffer, [0xff, 0xd8, 0xff]))
43
+ return buffer[3] === 0xf7 ? undefined : "image/jpeg";
44
+ if (startsWith(buffer, PNG_SIGNATURE))
45
+ return isPng(buffer) && !isAnimatedPng(buffer) ? "image/png" : undefined;
46
+ if (startsWithAscii(buffer, 0, "GIF87a") || startsWithAscii(buffer, 0, "GIF89a"))
47
+ return "image/gif";
48
+ if (startsWithAscii(buffer, 0, "RIFF") && startsWithAscii(buffer, 8, "WEBP"))
49
+ return "image/webp";
50
+ if (startsWithAscii(buffer, 0, "BM") && isBmp(buffer))
51
+ return "image/bmp";
52
+ return undefined;
53
+ }
54
+ function isPng(buffer) {
55
+ return (buffer.length >= 16 && readUint32BE(buffer, PNG_SIGNATURE.length) === 13 && startsWithAscii(buffer, 12, "IHDR"));
56
+ }
57
+ function isAnimatedPng(buffer) {
58
+ let offset = PNG_SIGNATURE.length;
59
+ while (offset + 8 <= buffer.length) {
60
+ const chunkLength = readUint32BE(buffer, offset);
61
+ const chunkTypeOffset = offset + 4;
62
+ if (startsWithAscii(buffer, chunkTypeOffset, "acTL"))
63
+ return true;
64
+ if (startsWithAscii(buffer, chunkTypeOffset, "IDAT"))
65
+ return false;
66
+ const nextOffset = offset + 8 + chunkLength + 4;
67
+ if (nextOffset <= offset || nextOffset > buffer.length)
68
+ return false;
69
+ offset = nextOffset;
70
+ }
71
+ return false;
72
+ }
73
+ /**
74
+ * The width and height an image of `mimeType` declares in its header, without decoding it: PNG's IHDR, GIF's screen,
75
+ * WebP's VP8, VP8L, or VP8X chunk, JPEG's first SOF segment. `undefined` when the header cannot be read.
76
+ */
77
+ export function imageDimensions(bytes, mimeType) {
78
+ switch (mimeType) {
79
+ case "image/png":
80
+ return bytes.length < 24 ? undefined : { width: readUint32BE(bytes, 16), height: readUint32BE(bytes, 20) };
81
+ case "image/gif":
82
+ return bytes.length < 10 ? undefined : { width: readUint16LE(bytes, 6), height: readUint16LE(bytes, 8) };
83
+ case "image/webp":
84
+ if (bytes.length < 30)
85
+ return undefined;
86
+ if (startsWithAscii(bytes, 12, "VP8 "))
87
+ return { width: readUint16LE(bytes, 26) & 0x3fff, height: readUint16LE(bytes, 28) & 0x3fff };
88
+ if (startsWithAscii(bytes, 12, "VP8L")) {
89
+ const bits = readUint32LE(bytes, 21);
90
+ return { width: (bits & 0x3fff) + 1, height: ((bits >>> 14) & 0x3fff) + 1 };
91
+ }
92
+ if (startsWithAscii(bytes, 12, "VP8X")) {
93
+ const uint24 = (offset) => readUint16LE(bytes, offset) + ((bytes[offset + 2] ?? 0) << 16);
94
+ return { width: uint24(24) + 1, height: uint24(27) + 1 };
95
+ }
96
+ return undefined;
97
+ case "image/jpeg":
98
+ for (let offset = 2; offset + 9 <= bytes.length;) {
99
+ if (bytes[offset] !== 0xff)
100
+ return undefined;
101
+ const marker = bytes[offset + 1];
102
+ if (marker === 0xff) {
103
+ offset++;
104
+ continue;
105
+ }
106
+ // SOF0 to SOF15, except DHT (C4), JPG (C8), and DAC (CC), which share the range.
107
+ if (marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc) {
108
+ return { width: readUint16BE(bytes, offset + 7), height: readUint16BE(bytes, offset + 5) };
109
+ }
110
+ offset += 2 + readUint16BE(bytes, offset + 2);
111
+ }
112
+ return undefined;
113
+ default:
114
+ return undefined;
115
+ }
116
+ }
117
+ function isBmp(buffer) {
118
+ if (buffer.length < 26)
119
+ return false;
120
+ const declaredFileSize = readUint32LE(buffer, 2);
121
+ const pixelDataOffset = readUint32LE(buffer, 10);
122
+ const dibHeaderSize = readUint32LE(buffer, 14);
123
+ if (declaredFileSize !== 0 && declaredFileSize < 26)
124
+ return false;
125
+ if (pixelDataOffset < 14 + dibHeaderSize)
126
+ return false;
127
+ if (declaredFileSize !== 0 && pixelDataOffset >= declaredFileSize)
128
+ return false;
129
+ let colorPlanes;
130
+ let bitsPerPixel;
131
+ if (dibHeaderSize === 12) {
132
+ colorPlanes = readUint16LE(buffer, 22);
133
+ bitsPerPixel = readUint16LE(buffer, 24);
134
+ }
135
+ else if (dibHeaderSize >= 40 && dibHeaderSize <= 124) {
136
+ if (buffer.length < 30)
137
+ return false;
138
+ colorPlanes = readUint16LE(buffer, 26);
139
+ bitsPerPixel = readUint16LE(buffer, 28);
140
+ }
141
+ else {
142
+ return false;
143
+ }
144
+ return colorPlanes === 1 && [1, 4, 8, 16, 24, 32].includes(bitsPerPixel);
145
+ }
146
+ function readUint16LE(buffer, offset) {
147
+ return (buffer[offset] ?? 0) + ((buffer[offset + 1] ?? 0) << 8);
148
+ }
149
+ function readUint16BE(buffer, offset) {
150
+ return ((buffer[offset] ?? 0) << 8) + (buffer[offset + 1] ?? 0);
151
+ }
152
+ function readUint32BE(buffer, offset) {
153
+ return ((buffer[offset] ?? 0) * 0x1000000 +
154
+ ((buffer[offset + 1] ?? 0) << 16) +
155
+ ((buffer[offset + 2] ?? 0) << 8) +
156
+ (buffer[offset + 3] ?? 0));
157
+ }
158
+ function readUint32LE(buffer, offset) {
159
+ return ((buffer[offset] ?? 0) +
160
+ ((buffer[offset + 1] ?? 0) << 8) +
161
+ ((buffer[offset + 2] ?? 0) << 16) +
162
+ (buffer[offset + 3] ?? 0) * 0x1000000);
163
+ }
164
+ function startsWith(buffer, bytes) {
165
+ if (buffer.length < bytes.length)
166
+ return false;
167
+ return bytes.every((byte, index) => buffer[index] === byte);
168
+ }
169
+ function startsWithAscii(buffer, offset, text) {
170
+ if (buffer.length < offset + text.length)
171
+ return false;
172
+ for (let index = 0; index < text.length; index++) {
173
+ if (buffer[offset + index] !== text.charCodeAt(index))
174
+ return false;
175
+ }
176
+ return true;
177
+ }
178
+ //# sourceMappingURL=image.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image.js","sourceRoot":"","sources":["../../src/tools/image.ts"],"names":[],"mappings":"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","sourcesContent":["const PNG_SIGNATURE = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];\n/** Bytes every check except the APNG chunk walk needs: BMP reads up to offset 29. */\nconst HEADER_BYTES = 32;\nconst BLOCK_BYTES = 64 * 1024;\n\n/** Positional reads of a file of `size` bytes. */\nexport type ByteSource = {\n\treadonly size: number;\n\tread(offset: number, length: number): Promise<Uint8Array>;\n};\n\n/**\n * `detectSupportedImageMimeType` of a whole file, reading only its header and, for PNG, the chunk headers up to the\n * first `acTL` or `IDAT`.\n */\nexport async function detectSupportedImageMimeTypeOf(source: ByteSource): Promise<string | undefined> {\n\tconst header = await source.read(0, HEADER_BYTES);\n\tif (!startsWith(header, PNG_SIGNATURE)) return detectSupportedImageMimeType(header);\n\treturn isPng(header) && !(await isAnimatedPngOf(source)) ? \"image/png\" : undefined;\n}\n\n/** `isAnimatedPng` over a file read in blocks. */\nasync function isAnimatedPngOf(source: ByteSource): Promise<boolean> {\n\tlet block: Uint8Array = new Uint8Array(0);\n\tlet blockStart = 0;\n\tconst bytesAt = async (offset: number, length: number): Promise<Uint8Array> => {\n\t\tif (offset < blockStart || offset + length > blockStart + block.length) {\n\t\t\tblockStart = offset;\n\t\t\tblock = await source.read(offset, BLOCK_BYTES);\n\t\t}\n\t\treturn block.subarray(offset - blockStart, offset - blockStart + length);\n\t};\n\tlet offset = PNG_SIGNATURE.length;\n\twhile (offset + 8 <= source.size) {\n\t\tconst chunkHeader = await bytesAt(offset, 8);\n\t\tconst chunkLength = readUint32BE(chunkHeader, 0);\n\t\tif (startsWithAscii(chunkHeader, 4, \"acTL\")) return true;\n\t\tif (startsWithAscii(chunkHeader, 4, \"IDAT\")) return false;\n\t\tconst nextOffset = offset + 8 + chunkLength + 4;\n\t\tif (nextOffset <= offset || nextOffset > source.size) return false;\n\t\toffset = nextOffset;\n\t}\n\treturn false;\n}\n\nexport function detectSupportedImageMimeType(buffer: Uint8Array): string | undefined {\n\tif (startsWith(buffer, [0xff, 0xd8, 0xff])) return buffer[3] === 0xf7 ? undefined : \"image/jpeg\";\n\tif (startsWith(buffer, PNG_SIGNATURE)) return isPng(buffer) && !isAnimatedPng(buffer) ? \"image/png\" : undefined;\n\tif (startsWithAscii(buffer, 0, \"GIF87a\") || startsWithAscii(buffer, 0, \"GIF89a\")) return \"image/gif\";\n\tif (startsWithAscii(buffer, 0, \"RIFF\") && startsWithAscii(buffer, 8, \"WEBP\")) return \"image/webp\";\n\tif (startsWithAscii(buffer, 0, \"BM\") && isBmp(buffer)) return \"image/bmp\";\n\treturn undefined;\n}\n\nfunction isPng(buffer: Uint8Array): boolean {\n\treturn (\n\t\tbuffer.length >= 16 && readUint32BE(buffer, PNG_SIGNATURE.length) === 13 && startsWithAscii(buffer, 12, \"IHDR\")\n\t);\n}\n\nfunction isAnimatedPng(buffer: Uint8Array): boolean {\n\tlet offset = PNG_SIGNATURE.length;\n\twhile (offset + 8 <= buffer.length) {\n\t\tconst chunkLength = readUint32BE(buffer, offset);\n\t\tconst chunkTypeOffset = offset + 4;\n\t\tif (startsWithAscii(buffer, chunkTypeOffset, \"acTL\")) return true;\n\t\tif (startsWithAscii(buffer, chunkTypeOffset, \"IDAT\")) return false;\n\t\tconst nextOffset = offset + 8 + chunkLength + 4;\n\t\tif (nextOffset <= offset || nextOffset > buffer.length) return false;\n\t\toffset = nextOffset;\n\t}\n\treturn false;\n}\n\n/**\n * The width and height an image of `mimeType` declares in its header, without decoding it: PNG's IHDR, GIF's screen,\n * WebP's VP8, VP8L, or VP8X chunk, JPEG's first SOF segment. `undefined` when the header cannot be read.\n */\nexport function imageDimensions(\n\tbytes: Uint8Array,\n\tmimeType: string,\n): { readonly width: number; readonly height: number } | undefined {\n\tswitch (mimeType) {\n\t\tcase \"image/png\":\n\t\t\treturn bytes.length < 24 ? undefined : { width: readUint32BE(bytes, 16), height: readUint32BE(bytes, 20) };\n\t\tcase \"image/gif\":\n\t\t\treturn bytes.length < 10 ? undefined : { width: readUint16LE(bytes, 6), height: readUint16LE(bytes, 8) };\n\t\tcase \"image/webp\":\n\t\t\tif (bytes.length < 30) return undefined;\n\t\t\tif (startsWithAscii(bytes, 12, \"VP8 \"))\n\t\t\t\treturn { width: readUint16LE(bytes, 26) & 0x3fff, height: readUint16LE(bytes, 28) & 0x3fff };\n\t\t\tif (startsWithAscii(bytes, 12, \"VP8L\")) {\n\t\t\t\tconst bits = readUint32LE(bytes, 21);\n\t\t\t\treturn { width: (bits & 0x3fff) + 1, height: ((bits >>> 14) & 0x3fff) + 1 };\n\t\t\t}\n\t\t\tif (startsWithAscii(bytes, 12, \"VP8X\")) {\n\t\t\t\tconst uint24 = (offset: number): number => readUint16LE(bytes, offset) + ((bytes[offset + 2] ?? 0) << 16);\n\t\t\t\treturn { width: uint24(24) + 1, height: uint24(27) + 1 };\n\t\t\t}\n\t\t\treturn undefined;\n\t\tcase \"image/jpeg\":\n\t\t\tfor (let offset = 2; offset + 9 <= bytes.length; ) {\n\t\t\t\tif (bytes[offset] !== 0xff) return undefined;\n\t\t\t\tconst marker = bytes[offset + 1]!;\n\t\t\t\tif (marker === 0xff) {\n\t\t\t\t\toffset++;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\t// SOF0 to SOF15, except DHT (C4), JPG (C8), and DAC (CC), which share the range.\n\t\t\t\tif (marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc) {\n\t\t\t\t\treturn { width: readUint16BE(bytes, offset + 7), height: readUint16BE(bytes, offset + 5) };\n\t\t\t\t}\n\t\t\t\toffset += 2 + readUint16BE(bytes, offset + 2);\n\t\t\t}\n\t\t\treturn undefined;\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n\nfunction isBmp(buffer: Uint8Array): boolean {\n\tif (buffer.length < 26) return false;\n\tconst declaredFileSize = readUint32LE(buffer, 2);\n\tconst pixelDataOffset = readUint32LE(buffer, 10);\n\tconst dibHeaderSize = readUint32LE(buffer, 14);\n\tif (declaredFileSize !== 0 && declaredFileSize < 26) return false;\n\tif (pixelDataOffset < 14 + dibHeaderSize) return false;\n\tif (declaredFileSize !== 0 && pixelDataOffset >= declaredFileSize) return false;\n\n\tlet colorPlanes: number;\n\tlet bitsPerPixel: number;\n\tif (dibHeaderSize === 12) {\n\t\tcolorPlanes = readUint16LE(buffer, 22);\n\t\tbitsPerPixel = readUint16LE(buffer, 24);\n\t} else if (dibHeaderSize >= 40 && dibHeaderSize <= 124) {\n\t\tif (buffer.length < 30) return false;\n\t\tcolorPlanes = readUint16LE(buffer, 26);\n\t\tbitsPerPixel = readUint16LE(buffer, 28);\n\t} else {\n\t\treturn false;\n\t}\n\treturn colorPlanes === 1 && [1, 4, 8, 16, 24, 32].includes(bitsPerPixel);\n}\n\nfunction readUint16LE(buffer: Uint8Array, offset: number): number {\n\treturn (buffer[offset] ?? 0) + ((buffer[offset + 1] ?? 0) << 8);\n}\n\nfunction readUint16BE(buffer: Uint8Array, offset: number): number {\n\treturn ((buffer[offset] ?? 0) << 8) + (buffer[offset + 1] ?? 0);\n}\n\nfunction readUint32BE(buffer: Uint8Array, offset: number): number {\n\treturn (\n\t\t(buffer[offset] ?? 0) * 0x1000000 +\n\t\t((buffer[offset + 1] ?? 0) << 16) +\n\t\t((buffer[offset + 2] ?? 0) << 8) +\n\t\t(buffer[offset + 3] ?? 0)\n\t);\n}\n\nfunction readUint32LE(buffer: Uint8Array, offset: number): number {\n\treturn (\n\t\t(buffer[offset] ?? 0) +\n\t\t((buffer[offset + 1] ?? 0) << 8) +\n\t\t((buffer[offset + 2] ?? 0) << 16) +\n\t\t(buffer[offset + 3] ?? 0) * 0x1000000\n\t);\n}\n\nfunction startsWith(buffer: Uint8Array, bytes: number[]): boolean {\n\tif (buffer.length < bytes.length) return false;\n\treturn bytes.every((byte, index) => buffer[index] === byte);\n}\n\nfunction startsWithAscii(buffer: Uint8Array, offset: number, text: string): boolean {\n\tif (buffer.length < offset + text.length) return false;\n\tfor (let index = 0; index < text.length; index++) {\n\t\tif (buffer[offset + index] !== text.charCodeAt(index)) return false;\n\t}\n\treturn true;\n}\n"]}
@@ -0,0 +1,46 @@
1
+ import { type ReadToolOptions } from "./read.ts";
2
+ export { type BashExecution, type BashPrepare, type BashToolInput, type BashToolOptions, createBashTool, createPowerShellTool, type PowerShellToolInput, type PowerShellToolOptions, } from "./bash.ts";
3
+ export { createEditTool, type EditToolDetails, type EditToolInput } from "./edit.ts";
4
+ export { DEFAULT_IMAGE_LIMITS, type ImageLimits, type ImageProcessor, type PreparedImage, } from "./image-processor.ts";
5
+ export { createReadTool, type ReadToolDetails, type ReadToolInput, type ReadToolOptions } from "./read.ts";
6
+ export { createWriteTool, type WriteToolInput } from "./write.ts";
7
+ /**
8
+ * The `coding-tools` extension: `read`, `write`, `edit`, and `bash`; nothing installs it automatically.
9
+ * `createPowerShellTool()` adds `powershell`. `images` prepares the images `read` returns (see `ReadToolOptions`).
10
+ */
11
+ export declare function createCodingTools(options?: ReadToolOptions): import("../index.ts").Extension<import("../index.ts").ToolRegistration<import("typebox").TObject<{
12
+ command: import("typebox").TString;
13
+ timeout: import("typebox").TOptional<import("typebox").TNumber>;
14
+ }>> | import("../index.ts").ToolRegistration<import("typebox").TObject<{
15
+ path: import("typebox").TString;
16
+ content: import("typebox").TString;
17
+ }>> | import("../index.ts").ToolRegistration<import("typebox").TObject<{
18
+ path: import("typebox").TString;
19
+ edits: import("typebox").TArray<import("typebox").TObject<{
20
+ oldText: import("typebox").TString;
21
+ newText: import("typebox").TString;
22
+ }>>;
23
+ }>, import("./edit.ts").EditToolDetails> | import("../index.ts").ToolRegistration<import("typebox").TObject<{
24
+ path: import("typebox").TString;
25
+ offset: import("typebox").TOptional<import("typebox").TNumber>;
26
+ limit: import("typebox").TOptional<import("typebox").TNumber>;
27
+ }>, import("./read.ts").ReadToolDetails>>;
28
+ /** `createCodingTools()` without an image processor. */
29
+ export declare const CodingTools: import("../index.ts").Extension<import("../index.ts").ToolRegistration<import("typebox").TObject<{
30
+ command: import("typebox").TString;
31
+ timeout: import("typebox").TOptional<import("typebox").TNumber>;
32
+ }>> | import("../index.ts").ToolRegistration<import("typebox").TObject<{
33
+ path: import("typebox").TString;
34
+ content: import("typebox").TString;
35
+ }>> | import("../index.ts").ToolRegistration<import("typebox").TObject<{
36
+ path: import("typebox").TString;
37
+ edits: import("typebox").TArray<import("typebox").TObject<{
38
+ oldText: import("typebox").TString;
39
+ newText: import("typebox").TString;
40
+ }>>;
41
+ }>, import("./edit.ts").EditToolDetails> | import("../index.ts").ToolRegistration<import("typebox").TObject<{
42
+ path: import("typebox").TString;
43
+ offset: import("typebox").TOptional<import("typebox").TNumber>;
44
+ limit: import("typebox").TOptional<import("typebox").TNumber>;
45
+ }>, import("./read.ts").ReadToolDetails>>;
46
+ //# sourceMappingURL=index.d.ts.map