@visual-z/xeno 0.8.4 → 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.
Files changed (350) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +31 -746
  3. package/bin/xeno.exe +26 -0
  4. package/install.cjs +88 -0
  5. package/package.json +18 -48
  6. package/docs/app-server.md +0 -115
  7. package/docs/foundations.md +0 -88
  8. package/docs/install.md +0 -244
  9. package/docs/providers.md +0 -172
  10. package/docs/research/foundation-clients-2026-10-09.md +0 -27
  11. package/docs/research/foundation-editing-2026-10-09.md +0 -56
  12. package/docs/research/foundation-network-2026-10-09.md +0 -37
  13. package/docs/research/foundation-workflow-2026-10-09.md +0 -37
  14. package/src/apps/acp-server.ts +0 -41
  15. package/src/apps/agent-profiles.ts +0 -19
  16. package/src/apps/app-server.ts +0 -30
  17. package/src/apps/approve.ts +0 -49
  18. package/src/apps/cli.ts +0 -99
  19. package/src/apps/commands.ts +0 -319
  20. package/src/apps/completions.ts +0 -87
  21. package/src/apps/control-transport.ts +0 -90
  22. package/src/apps/export-cli.ts +0 -24
  23. package/src/apps/goal-flow.ts +0 -70
  24. package/src/apps/image-draft.ts +0 -46
  25. package/src/apps/input.ts +0 -312
  26. package/src/apps/model-catalog.ts +0 -69
  27. package/src/apps/preferences.ts +0 -20
  28. package/src/apps/project-trust.ts +0 -9
  29. package/src/apps/provider-connect.ts +0 -346
  30. package/src/apps/question-ui.ts +0 -27
  31. package/src/apps/render.ts +0 -181
  32. package/src/apps/resources.ts +0 -66
  33. package/src/apps/review.ts +0 -109
  34. package/src/apps/session-hub.ts +0 -96
  35. package/src/apps/session-lifecycle.ts +0 -64
  36. package/src/apps/shell.ts +0 -2071
  37. package/src/apps/startup-input.ts +0 -135
  38. package/src/apps/startup.ts +0 -1677
  39. package/src/apps/ui/app.ts +0 -1818
  40. package/src/apps/ui/control-model.ts +0 -43
  41. package/src/apps/ui/extension-host.ts +0 -194
  42. package/src/apps/ui/image-cache.ts +0 -121
  43. package/src/apps/ui/palette.ts +0 -88
  44. package/src/apps/ui/project.ts +0 -127
  45. package/src/apps/ui/standalone.ts +0 -148
  46. package/src/apps/ui/status-model.ts +0 -68
  47. package/src/apps/ui/surface.ts +0 -91
  48. package/src/apps/ui/theme.ts +0 -86
  49. package/src/apps/update-check.ts +0 -112
  50. package/src/apps/workflow.ts +0 -58
  51. package/src/core/acceptance.ts +0 -139
  52. package/src/core/action-defaults.ts +0 -58
  53. package/src/core/activity-status.ts +0 -52
  54. package/src/core/agent-profile.ts +0 -130
  55. package/src/core/attachment-input.ts +0 -17
  56. package/src/core/blind-checks.ts +0 -72
  57. package/src/core/boundary.ts +0 -174
  58. package/src/core/branch-receipt.ts +0 -26
  59. package/src/core/catalog-merge.ts +0 -19
  60. package/src/core/checkpoints.ts +0 -85
  61. package/src/core/churn.ts +0 -61
  62. package/src/core/command-artifacts.ts +0 -94
  63. package/src/core/command-contract.ts +0 -151
  64. package/src/core/completion-action.ts +0 -20
  65. package/src/core/completion-review.ts +0 -98
  66. package/src/core/context-detail.ts +0 -37
  67. package/src/core/context-policy.ts +0 -133
  68. package/src/core/context-window.ts +0 -54
  69. package/src/core/continuation.ts +0 -130
  70. package/src/core/control-protocol.ts +0 -69
  71. package/src/core/control-view.ts +0 -39
  72. package/src/core/cycle.ts +0 -17
  73. package/src/core/data-query.ts +0 -201
  74. package/src/core/deadline.ts +0 -40
  75. package/src/core/derive.ts +0 -516
  76. package/src/core/draft-images.ts +0 -56
  77. package/src/core/edit-plan.ts +0 -200
  78. package/src/core/environment.ts +0 -181
  79. package/src/core/event-validation.ts +0 -220
  80. package/src/core/events.ts +0 -670
  81. package/src/core/evidence-ladder.ts +0 -62
  82. package/src/core/evidence-links.ts +0 -68
  83. package/src/core/evidence.ts +0 -240
  84. package/src/core/execution.ts +0 -18
  85. package/src/core/exhaustive.ts +0 -18
  86. package/src/core/export.ts +0 -179
  87. package/src/core/extension-flags.ts +0 -14
  88. package/src/core/external-effects.ts +0 -488
  89. package/src/core/failure-recovery.ts +0 -173
  90. package/src/core/file-precondition.ts +0 -78
  91. package/src/core/file-references.ts +0 -35
  92. package/src/core/frontmatter.ts +0 -213
  93. package/src/core/git-status.ts +0 -29
  94. package/src/core/goal-view.ts +0 -64
  95. package/src/core/goal.ts +0 -200
  96. package/src/core/handoff.ts +0 -479
  97. package/src/core/history-query.ts +0 -139
  98. package/src/core/history.ts +0 -237
  99. package/src/core/image-limits.ts +0 -72
  100. package/src/core/images.ts +0 -46
  101. package/src/core/integration-health.ts +0 -34
  102. package/src/core/json-event.ts +0 -131
  103. package/src/core/json-pointer.ts +0 -46
  104. package/src/core/keymap.ts +0 -153
  105. package/src/core/lsp-framing.ts +0 -34
  106. package/src/core/mcp-config.ts +0 -68
  107. package/src/core/model-picker.ts +0 -64
  108. package/src/core/model-preferences.ts +0 -28
  109. package/src/core/model-replay.ts +0 -22
  110. package/src/core/model-scope.ts +0 -53
  111. package/src/core/note-guidance.ts +0 -34
  112. package/src/core/oauth-fields.ts +0 -44
  113. package/src/core/observation-view.ts +0 -152
  114. package/src/core/outcome.ts +0 -44
  115. package/src/core/overflow.ts +0 -59
  116. package/src/core/packages.ts +0 -47
  117. package/src/core/patch.ts +0 -130
  118. package/src/core/path-suggestions.ts +0 -59
  119. package/src/core/permissions.ts +0 -410
  120. package/src/core/process-receipt.ts +0 -105
  121. package/src/core/process-state.ts +0 -37
  122. package/src/core/program-status.ts +0 -40
  123. package/src/core/progress.ts +0 -150
  124. package/src/core/project.ts +0 -431
  125. package/src/core/prompt-policy.ts +0 -46
  126. package/src/core/prompts.ts +0 -111
  127. package/src/core/question.ts +0 -94
  128. package/src/core/queue.ts +0 -78
  129. package/src/core/read-bounds.ts +0 -24
  130. package/src/core/read-observation.ts +0 -106
  131. package/src/core/recovery-sources.ts +0 -74
  132. package/src/core/regex-dialect.ts +0 -25
  133. package/src/core/request-budget.ts +0 -23
  134. package/src/core/request-progress.ts +0 -87
  135. package/src/core/request-state.ts +0 -273
  136. package/src/core/resource-selection.ts +0 -36
  137. package/src/core/response-recovery.ts +0 -59
  138. package/src/core/resume.ts +0 -59
  139. package/src/core/review.ts +0 -68
  140. package/src/core/ripgrep-release.ts +0 -64
  141. package/src/core/runtime-target.ts +0 -94
  142. package/src/core/scheduling.ts +0 -8
  143. package/src/core/search-observation.ts +0 -97
  144. package/src/core/secrets.ts +0 -188
  145. package/src/core/session-archive.ts +0 -4
  146. package/src/core/session-browser.ts +0 -82
  147. package/src/core/session-entry.ts +0 -61
  148. package/src/core/session-exit.ts +0 -42
  149. package/src/core/session-format.ts +0 -169
  150. package/src/core/session-position.ts +0 -26
  151. package/src/core/session-tree.ts +0 -147
  152. package/src/core/settings.ts +0 -192
  153. package/src/core/skills.ts +0 -165
  154. package/src/core/sql-query.ts +0 -211
  155. package/src/core/stall.ts +0 -128
  156. package/src/core/stop-report.ts +0 -68
  157. package/src/core/text-normalize.ts +0 -81
  158. package/src/core/theme.ts +0 -166
  159. package/src/core/thinking.ts +0 -26
  160. package/src/core/tokens.ts +0 -151
  161. package/src/core/tool-contract.ts +0 -153
  162. package/src/core/tool-discovery.ts +0 -87
  163. package/src/core/tool-failure.ts +0 -81
  164. package/src/core/tool-input.ts +0 -44
  165. package/src/core/transcript.ts +0 -108
  166. package/src/core/tui-mode.ts +0 -29
  167. package/src/core/update-check.ts +0 -157
  168. package/src/core/version.ts +0 -55
  169. package/src/core/web-content.ts +0 -80
  170. package/src/core/work-state.ts +0 -154
  171. package/src/core/workflow-mode.ts +0 -34
  172. package/src/core/working-set.ts +0 -119
  173. package/src/core/yaml.ts +0 -282
  174. package/src/runtime/acp-agent.ts +0 -289
  175. package/src/runtime/agent-profiles.ts +0 -21
  176. package/src/runtime/auth.ts +0 -367
  177. package/src/runtime/blind-check.ts +0 -36
  178. package/src/runtime/branch-jobs.ts +0 -36
  179. package/src/runtime/catalog.ts +0 -285
  180. package/src/runtime/clipboard.ts +0 -125
  181. package/src/runtime/command-artifacts.ts +0 -64
  182. package/src/runtime/connection-reuse.ts +0 -40
  183. package/src/runtime/control-client.ts +0 -149
  184. package/src/runtime/control-connection.ts +0 -139
  185. package/src/runtime/counter-check.ts +0 -74
  186. package/src/runtime/environment-tools.ts +0 -60
  187. package/src/runtime/evidence-notes.ts +0 -35
  188. package/src/runtime/exec-sessions.ts +0 -456
  189. package/src/runtime/execution-control.ts +0 -40
  190. package/src/runtime/extension-loader.ts +0 -37
  191. package/src/runtime/extension-ui.ts +0 -47
  192. package/src/runtime/extensions.ts +0 -370
  193. package/src/runtime/formatter-tool.ts +0 -38
  194. package/src/runtime/formatter.ts +0 -50
  195. package/src/runtime/foundation.ts +0 -116
  196. package/src/runtime/history-cache.ts +0 -57
  197. package/src/runtime/history-tools.ts +0 -168
  198. package/src/runtime/images.ts +0 -80
  199. package/src/runtime/integrations.ts +0 -216
  200. package/src/runtime/loop.ts +0 -3108
  201. package/src/runtime/lsp-client.ts +0 -201
  202. package/src/runtime/mcp-client.ts +0 -264
  203. package/src/runtime/mcp-oauth.ts +0 -168
  204. package/src/runtime/mcp-schema.ts +0 -51
  205. package/src/runtime/model-config.ts +0 -232
  206. package/src/runtime/model-data.LICENSE +0 -27
  207. package/src/runtime/model-data.ts +0 -21
  208. package/src/runtime/model-discovery.ts +0 -208
  209. package/src/runtime/model-replay.ts +0 -10
  210. package/src/runtime/model-scope.ts +0 -20
  211. package/src/runtime/model-sources.ts +0 -151
  212. package/src/runtime/model.ts +0 -686
  213. package/src/runtime/models-fetch.ts +0 -57
  214. package/src/runtime/oauth-device.ts +0 -86
  215. package/src/runtime/oauth.ts +0 -853
  216. package/src/runtime/open-image.ts +0 -42
  217. package/src/runtime/openai-responses.ts +0 -304
  218. package/src/runtime/openai.ts +0 -697
  219. package/src/runtime/output-schema.ts +0 -26
  220. package/src/runtime/package-manager.ts +0 -125
  221. package/src/runtime/patch-tool.ts +0 -38
  222. package/src/runtime/permission-resources.ts +0 -18
  223. package/src/runtime/pi-provider.ts +0 -423
  224. package/src/runtime/program-status.ts +0 -38
  225. package/src/runtime/progress.ts +0 -26
  226. package/src/runtime/provider-access.ts +0 -21
  227. package/src/runtime/provider-connect.ts +0 -109
  228. package/src/runtime/provider-presets.ts +0 -98
  229. package/src/runtime/provider-protocols.ts +0 -29
  230. package/src/runtime/provider-request.ts +0 -87
  231. package/src/runtime/provider-tool-policy.ts +0 -33
  232. package/src/runtime/provider.ts +0 -580
  233. package/src/runtime/question-tool.ts +0 -62
  234. package/src/runtime/retry.ts +0 -447
  235. package/src/runtime/review-tool.ts +0 -48
  236. package/src/runtime/rich-text.ts +0 -36
  237. package/src/runtime/runtime-target.ts +0 -149
  238. package/src/runtime/secrets.ts +0 -79
  239. package/src/runtime/self-update.ts +0 -74
  240. package/src/runtime/session-control.ts +0 -265
  241. package/src/runtime/session-hub.ts +0 -85
  242. package/src/runtime/stream-idle.ts +0 -80
  243. package/src/runtime/tool-broker.ts +0 -137
  244. package/src/runtime/tool-discovery.ts +0 -58
  245. package/src/runtime/tool-queue.ts +0 -77
  246. package/src/runtime/tools.ts +0 -1820
  247. package/src/runtime/turn-effects.ts +0 -31
  248. package/src/runtime/update-check.ts +0 -48
  249. package/src/runtime/usage.ts +0 -253
  250. package/src/runtime/version.ts +0 -12
  251. package/src/runtime/web-http.ts +0 -56
  252. package/src/runtime/web-tools.ts +0 -88
  253. package/src/runtime/working-notes.ts +0 -60
  254. package/src/runtime/workspace-lifecycle.ts +0 -141
  255. package/src/store/acceptance.ts +0 -15
  256. package/src/store/agent-profile-files.ts +0 -13
  257. package/src/store/atomic.ts +0 -31
  258. package/src/store/auth-store.ts +0 -50
  259. package/src/store/blind-checks.ts +0 -32
  260. package/src/store/blobs.ts +0 -143
  261. package/src/store/checkpoint-history.ts +0 -32
  262. package/src/store/command-artifacts.ts +0 -96
  263. package/src/store/command.ts +0 -111
  264. package/src/store/config.ts +0 -69
  265. package/src/store/context-files.ts +0 -436
  266. package/src/store/file-checkpoints.ts +0 -108
  267. package/src/store/file-index.ts +0 -174
  268. package/src/store/foundation-config.ts +0 -24
  269. package/src/store/git-checkpoints.ts +0 -67
  270. package/src/store/git-status.ts +0 -15
  271. package/src/store/history-search.ts +0 -19
  272. package/src/store/log.ts +0 -725
  273. package/src/store/lsp-document.ts +0 -30
  274. package/src/store/mcp-credentials.ts +0 -85
  275. package/src/store/models.ts +0 -60
  276. package/src/store/observations.ts +0 -224
  277. package/src/store/packages.ts +0 -98
  278. package/src/store/patch.ts +0 -92
  279. package/src/store/permission-paths.ts +0 -34
  280. package/src/store/process-receipts.ts +0 -34
  281. package/src/store/query-source.ts +0 -49
  282. package/src/store/recovery.ts +0 -44
  283. package/src/store/replay.ts +0 -76
  284. package/src/store/resource-index.ts +0 -170
  285. package/src/store/resources.ts +0 -285
  286. package/src/store/restore.ts +0 -110
  287. package/src/store/review.ts +0 -109
  288. package/src/store/runtime-target.ts +0 -64
  289. package/src/store/search.ts +0 -728
  290. package/src/store/secrets.ts +0 -44
  291. package/src/store/serial.ts +0 -13
  292. package/src/store/session-archive.ts +0 -165
  293. package/src/store/session-browser.ts +0 -68
  294. package/src/store/session-position.ts +0 -27
  295. package/src/store/sessions.ts +0 -525
  296. package/src/store/source-cancellation.ts +0 -6
  297. package/src/store/tool-binaries.ts +0 -90
  298. package/src/store/trust.ts +0 -82
  299. package/src/store/update-cache.ts +0 -36
  300. package/src/store/user-settings.ts +0 -30
  301. package/src/store/workspace-clone.ts +0 -42
  302. package/src/store/workspace-observation.ts +0 -113
  303. package/src/store/workspace.ts +0 -115
  304. package/src/store/worktree.ts +0 -278
  305. package/src/tui/ansi-spans.ts +0 -126
  306. package/src/tui/diff.ts +0 -160
  307. package/src/tui/exhaustive.ts +0 -14
  308. package/src/tui/fuzzy.ts +0 -75
  309. package/src/tui/style.ts +0 -208
  310. package/src/tui/ui/atomic-draft.ts +0 -121
  311. package/src/tui/ui/branch-panel.ts +0 -47
  312. package/src/tui/ui/canvas.ts +0 -96
  313. package/src/tui/ui/colors.ts +0 -70
  314. package/src/tui/ui/composer.ts +0 -199
  315. package/src/tui/ui/control-panel.ts +0 -25
  316. package/src/tui/ui/control-sidebar.ts +0 -49
  317. package/src/tui/ui/dialog.ts +0 -398
  318. package/src/tui/ui/draft.ts +0 -23
  319. package/src/tui/ui/duration.ts +0 -16
  320. package/src/tui/ui/edge.ts +0 -186
  321. package/src/tui/ui/image-size.ts +0 -44
  322. package/src/tui/ui/ink.ts +0 -210
  323. package/src/tui/ui/items.ts +0 -42
  324. package/src/tui/ui/keys.ts +0 -40
  325. package/src/tui/ui/layout.ts +0 -74
  326. package/src/tui/ui/list-model.ts +0 -86
  327. package/src/tui/ui/list-view.ts +0 -172
  328. package/src/tui/ui/mascot.ts +0 -67
  329. package/src/tui/ui/messages.ts +0 -518
  330. package/src/tui/ui/motion.ts +0 -28
  331. package/src/tui/ui/paths.ts +0 -10
  332. package/src/tui/ui/pictures.ts +0 -108
  333. package/src/tui/ui/prompt-history.ts +0 -60
  334. package/src/tui/ui/screen.ts +0 -579
  335. package/src/tui/ui/status.ts +0 -403
  336. package/src/tui/ui/styled.ts +0 -85
  337. package/src/tui/ui/tool-card.ts +0 -150
  338. package/src/tui/width.ts +0 -567
  339. package/src/vendor/pi-rich/LICENSE.upstream +0 -21
  340. package/src/vendor/pi-rich/UPSTREAM.json +0 -17
  341. package/src/vendor/pi-rich/clipboard-command.ts +0 -44
  342. package/src/vendor/pi-rich/exif-orientation.ts +0 -182
  343. package/src/vendor/pi-rich/html.ts +0 -51
  344. package/src/vendor/pi-rich/image-convert.ts +0 -49
  345. package/src/vendor/pi-rich/image-resize-core.ts +0 -164
  346. package/src/vendor/pi-rich/image-resize-worker.ts +0 -42
  347. package/src/vendor/pi-rich/image-resize.ts +0 -123
  348. package/src/vendor/pi-rich/mime.ts +0 -116
  349. package/src/vendor/pi-rich/photon.ts +0 -139
  350. package/src/vendor/pi-rich/syntax-highlight.ts +0 -214
@@ -1,182 +0,0 @@
1
- import type { PhotonImageType } from "./photon.ts";
2
-
3
- type Photon = typeof import("@silvia-odwyer/photon-node");
4
-
5
- function readOrientationFromTiff(bytes: Uint8Array, tiffStart: number): number {
6
- if (tiffStart + 8 > bytes.length) return 1;
7
-
8
- const byteOrder = (bytes[tiffStart] << 8) | bytes[tiffStart + 1];
9
- const le = byteOrder === 0x4949;
10
-
11
- const read16 = (pos: number): number => {
12
- if (le) return bytes[pos] | (bytes[pos + 1] << 8);
13
- return (bytes[pos] << 8) | bytes[pos + 1];
14
- };
15
-
16
- const read32 = (pos: number): number => {
17
- if (le) return bytes[pos] | (bytes[pos + 1] << 8) | (bytes[pos + 2] << 16) | (bytes[pos + 3] << 24);
18
- return ((bytes[pos] << 24) | (bytes[pos + 1] << 16) | (bytes[pos + 2] << 8) | bytes[pos + 3]) >>> 0;
19
- };
20
-
21
- const ifdOffset = read32(tiffStart + 4);
22
- const ifdStart = tiffStart + ifdOffset;
23
- if (ifdStart + 2 > bytes.length) return 1;
24
-
25
- const entryCount = read16(ifdStart);
26
- for (let i = 0; i < entryCount; i++) {
27
- const entryPos = ifdStart + 2 + i * 12;
28
- if (entryPos + 12 > bytes.length) return 1;
29
-
30
- if (read16(entryPos) === 0x0112) {
31
- const value = read16(entryPos + 8);
32
- return value >= 1 && value <= 8 ? value : 1;
33
- }
34
- }
35
-
36
- return 1;
37
- }
38
-
39
- function findJpegTiffOffset(bytes: Uint8Array): number {
40
- let offset = 2;
41
- while (offset < bytes.length - 1) {
42
- if (bytes[offset] !== 0xff) return -1;
43
- const marker = bytes[offset + 1];
44
- if (marker === 0xff) {
45
- offset++;
46
- continue;
47
- }
48
-
49
- if (marker === 0xe1) {
50
- if (offset + 4 >= bytes.length) return -1;
51
- const segmentStart = offset + 4;
52
- if (segmentStart + 6 > bytes.length) return -1;
53
- if (hasExifHeader(bytes, segmentStart)) return segmentStart + 6;
54
- }
55
-
56
- if (offset + 4 > bytes.length) return -1;
57
- const length = (bytes[offset + 2] << 8) | bytes[offset + 3];
58
- offset += 2 + length;
59
- }
60
-
61
- return -1;
62
- }
63
-
64
- function findWebpTiffOffset(bytes: Uint8Array): number {
65
- let offset = 12;
66
- while (offset + 8 <= bytes.length) {
67
- const chunkId = String.fromCharCode(bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3]);
68
- const chunkSize =
69
- bytes[offset + 4] | (bytes[offset + 5] << 8) | (bytes[offset + 6] << 16) | (bytes[offset + 7] << 24);
70
- const dataStart = offset + 8;
71
-
72
- if (chunkId === "EXIF") {
73
- if (dataStart + chunkSize > bytes.length) return -1;
74
- // Some WebP files have "Exif\0\0" prefix before the TIFF header
75
- const tiffStart = chunkSize >= 6 && hasExifHeader(bytes, dataStart) ? dataStart + 6 : dataStart;
76
- return tiffStart;
77
- }
78
-
79
- // RIFF chunks are padded to even size
80
- offset = dataStart + chunkSize + (chunkSize % 2);
81
- }
82
-
83
- return -1;
84
- }
85
-
86
- function hasExifHeader(bytes: Uint8Array, offset: number): boolean {
87
- return (
88
- bytes[offset] === 0x45 &&
89
- bytes[offset + 1] === 0x78 &&
90
- bytes[offset + 2] === 0x69 &&
91
- bytes[offset + 3] === 0x66 &&
92
- bytes[offset + 4] === 0x00 &&
93
- bytes[offset + 5] === 0x00
94
- );
95
- }
96
-
97
- function getExifOrientation(bytes: Uint8Array): number {
98
- let tiffOffset = -1;
99
-
100
- // JPEG: starts with FF D8
101
- if (bytes.length >= 2 && bytes[0] === 0xff && bytes[1] === 0xd8) {
102
- tiffOffset = findJpegTiffOffset(bytes);
103
- }
104
- // WebP: starts with RIFF....WEBP
105
- else if (
106
- bytes.length >= 12 &&
107
- bytes[0] === 0x52 &&
108
- bytes[1] === 0x49 &&
109
- bytes[2] === 0x46 &&
110
- bytes[3] === 0x46 &&
111
- bytes[8] === 0x57 &&
112
- bytes[9] === 0x45 &&
113
- bytes[10] === 0x42 &&
114
- bytes[11] === 0x50
115
- ) {
116
- tiffOffset = findWebpTiffOffset(bytes);
117
- }
118
-
119
- if (tiffOffset === -1) return 1;
120
- return readOrientationFromTiff(bytes, tiffOffset);
121
- }
122
-
123
- type DstIndexFn = (x: number, y: number, w: number, h: number) => number;
124
-
125
- function rotate90(photon: Photon, image: PhotonImageType, dstIndex: DstIndexFn): PhotonImageType {
126
- const w = image.get_width();
127
- const h = image.get_height();
128
- const src = image.get_raw_pixels();
129
- const dst = new Uint8Array(src.length);
130
-
131
- for (let y = 0; y < h; y++) {
132
- for (let x = 0; x < w; x++) {
133
- const srcIdx = (y * w + x) * 4;
134
- const dstIdx = dstIndex(x, y, w, h) * 4;
135
- dst[dstIdx] = src[srcIdx];
136
- dst[dstIdx + 1] = src[srcIdx + 1];
137
- dst[dstIdx + 2] = src[srcIdx + 2];
138
- dst[dstIdx + 3] = src[srcIdx + 3];
139
- }
140
- }
141
-
142
- return new photon.PhotonImage(dst, h, w);
143
- }
144
-
145
- // Flip orientations mutate in-place. Rotations return a new image (caller must free the old one if different).
146
- export function applyExifOrientation(
147
- photon: Photon,
148
- image: PhotonImageType,
149
- originalBytes: Uint8Array,
150
- ): PhotonImageType {
151
- const orientation = getExifOrientation(originalBytes);
152
- if (orientation === 1) return image;
153
-
154
- switch (orientation) {
155
- case 2:
156
- photon.fliph(image);
157
- return image;
158
- case 3:
159
- photon.fliph(image);
160
- photon.flipv(image);
161
- return image;
162
- case 4:
163
- photon.flipv(image);
164
- return image;
165
- case 5: {
166
- const rotated = rotate90(photon, image, (x, y, _w, h) => x * h + (h - 1 - y));
167
- photon.fliph(rotated);
168
- return rotated;
169
- }
170
- case 6:
171
- return rotate90(photon, image, (x, y, _w, h) => x * h + (h - 1 - y));
172
- case 7: {
173
- const rotated = rotate90(photon, image, (x, y, w, h) => (w - 1 - x) * h + y);
174
- photon.fliph(rotated);
175
- return rotated;
176
- }
177
- case 8:
178
- return rotate90(photon, image, (x, y, w, h) => (w - 1 - x) * h + y);
179
- default:
180
- return image;
181
- }
182
- }
@@ -1,51 +0,0 @@
1
- export interface DecodedHtmlEntity {
2
- text: string;
3
- length: number;
4
- }
5
-
6
- function decodeCodePoint(codePoint: number): string | undefined {
7
- if (!Number.isInteger(codePoint) || codePoint < 0 || codePoint > 0x10ffff) {
8
- return undefined;
9
- }
10
- return String.fromCodePoint(codePoint);
11
- }
12
-
13
- export function decodeHtmlEntity(entity: string): string | undefined {
14
- switch (entity) {
15
- case "amp":
16
- return "&";
17
- case "lt":
18
- return "<";
19
- case "gt":
20
- return ">";
21
- case "quot":
22
- return '"';
23
- case "apos":
24
- return "'";
25
- }
26
-
27
- if (entity.startsWith("#x") || entity.startsWith("#X")) {
28
- return decodeCodePoint(Number.parseInt(entity.slice(2), 16));
29
- }
30
-
31
- if (entity.startsWith("#")) {
32
- return decodeCodePoint(Number.parseInt(entity.slice(1), 10));
33
- }
34
-
35
- return undefined;
36
- }
37
-
38
- export function decodeHtmlEntityAt(html: string, index: number): DecodedHtmlEntity | undefined {
39
- const semicolonIndex = html.indexOf(";", index + 1);
40
- if (semicolonIndex === -1 || semicolonIndex - index > 16) {
41
- return undefined;
42
- }
43
-
44
- const entity = html.slice(index + 1, semicolonIndex);
45
- const decoded = decodeHtmlEntity(entity);
46
- if (decoded === undefined) {
47
- return undefined;
48
- }
49
-
50
- return { text: decoded, length: semicolonIndex - index + 1 };
51
- }
@@ -1,49 +0,0 @@
1
- import { applyExifOrientation } from "./exif-orientation.ts";
2
- import { loadPhoton } from "./photon.ts";
3
-
4
- export async function convertImageBytesToPng(bytes: Uint8Array): Promise<Uint8Array | null> {
5
- const photon = await loadPhoton();
6
- if (!photon) {
7
- // Photon not available, can't convert
8
- return null;
9
- }
10
-
11
- try {
12
- const rawImage = photon.PhotonImage.new_from_byteslice(bytes);
13
- const image = applyExifOrientation(photon, rawImage, bytes);
14
- if (image !== rawImage) rawImage.free();
15
- try {
16
- return new Uint8Array(image.get_bytes());
17
- } finally {
18
- image.free();
19
- }
20
- } catch {
21
- // Conversion failed
22
- return null;
23
- }
24
- }
25
-
26
- /**
27
- * Convert image to PNG format for terminal display.
28
- * Kitty graphics protocol requires PNG format (f=100).
29
- */
30
- export async function convertToPng(
31
- base64Data: string,
32
- mimeType: string,
33
- ): Promise<{ data: string; mimeType: string } | null> {
34
- // Already PNG, no conversion needed
35
- if (mimeType === "image/png") {
36
- return { data: base64Data, mimeType };
37
- }
38
-
39
- const bytes = new Uint8Array(Buffer.from(base64Data, "base64"));
40
- const pngBytes = await convertImageBytesToPng(bytes);
41
- if (!pngBytes) {
42
- return null;
43
- }
44
-
45
- return {
46
- data: Buffer.from(pngBytes).toString("base64"),
47
- mimeType: "image/png",
48
- };
49
- }
@@ -1,164 +0,0 @@
1
- import { applyExifOrientation } from "./exif-orientation.ts";
2
- import { loadPhoton } from "./photon.ts";
3
-
4
- export interface ImageResizeOptions {
5
- maxWidth?: number; // Default: 2000
6
- maxHeight?: number; // Default: 2000
7
- maxBytes?: number; // Default: 4.5MB of base64 payload (below Anthropic's 5MB limit)
8
- jpegQuality?: number; // Default: 80
9
- }
10
-
11
- export interface ResizedImage {
12
- data: string; // base64
13
- mimeType: string;
14
- originalWidth: number;
15
- originalHeight: number;
16
- width: number;
17
- height: number;
18
- wasResized: boolean;
19
- }
20
-
21
- // 4.5MB of base64 payload. Provides headroom below Anthropic's 5MB limit.
22
- const DEFAULT_MAX_BYTES = 4.5 * 1024 * 1024;
23
-
24
- const DEFAULT_OPTIONS: Required<ImageResizeOptions> = {
25
- maxWidth: 2000,
26
- maxHeight: 2000,
27
- maxBytes: DEFAULT_MAX_BYTES,
28
- jpegQuality: 80,
29
- };
30
-
31
- interface EncodedCandidate {
32
- data: string;
33
- encodedSize: number;
34
- mimeType: string;
35
- }
36
-
37
- function encodeCandidate(buffer: Uint8Array, mimeType: string): EncodedCandidate {
38
- const data = Buffer.from(buffer).toString("base64");
39
- return {
40
- data,
41
- encodedSize: Buffer.byteLength(data, "utf-8"),
42
- mimeType,
43
- };
44
- }
45
-
46
- /**
47
- * Resize an image to fit within the specified max dimensions and encoded file size.
48
- * Returns null if the image cannot be resized below maxBytes.
49
- *
50
- * Uses Photon (Rust/WASM) for image processing. If Photon is not available,
51
- * returns null.
52
- *
53
- * Strategy for staying under maxBytes:
54
- * 1. First resize to maxWidth/maxHeight
55
- * 2. Try both PNG and JPEG formats, pick the smaller one
56
- * 3. If still too large, try JPEG with decreasing quality
57
- * 4. If still too large, progressively reduce dimensions until 1x1
58
- */
59
- export async function resizeImageInProcess(
60
- inputBytes: Uint8Array,
61
- mimeType: string,
62
- options?: ImageResizeOptions,
63
- ): Promise<ResizedImage | null> {
64
- const opts = { ...DEFAULT_OPTIONS, ...options };
65
- const inputBase64Size = Math.ceil(inputBytes.byteLength / 3) * 4;
66
-
67
- const photon = await loadPhoton();
68
- if (!photon) {
69
- return null;
70
- }
71
-
72
- let image: ReturnType<typeof photon.PhotonImage.new_from_byteslice> | undefined;
73
- try {
74
- const rawImage = photon.PhotonImage.new_from_byteslice(inputBytes);
75
- image = applyExifOrientation(photon, rawImage, inputBytes);
76
- if (image !== rawImage) rawImage.free();
77
-
78
- const originalWidth = image.get_width();
79
- const originalHeight = image.get_height();
80
- const format = mimeType.split("/")[1] ?? "png";
81
-
82
- // Check if already within all limits (dimensions AND encoded size)
83
- if (originalWidth <= opts.maxWidth && originalHeight <= opts.maxHeight && inputBase64Size < opts.maxBytes) {
84
- return {
85
- data: Buffer.from(inputBytes).toString("base64"),
86
- mimeType: mimeType || `image/${format}`,
87
- originalWidth,
88
- originalHeight,
89
- width: originalWidth,
90
- height: originalHeight,
91
- wasResized: false,
92
- };
93
- }
94
-
95
- // Calculate initial dimensions respecting max limits
96
- let targetWidth = originalWidth;
97
- let targetHeight = originalHeight;
98
-
99
- if (targetWidth > opts.maxWidth) {
100
- targetHeight = Math.round((targetHeight * opts.maxWidth) / targetWidth);
101
- targetWidth = opts.maxWidth;
102
- }
103
- if (targetHeight > opts.maxHeight) {
104
- targetWidth = Math.round((targetWidth * opts.maxHeight) / targetHeight);
105
- targetHeight = opts.maxHeight;
106
- }
107
-
108
- function tryEncodings(width: number, height: number, jpegQualities: number[]): EncodedCandidate[] {
109
- const resized = photon!.resize(image!, width, height, photon!.SamplingFilter.Lanczos3);
110
-
111
- try {
112
- const candidates: EncodedCandidate[] = [encodeCandidate(resized.get_bytes(), "image/png")];
113
- for (const quality of jpegQualities) {
114
- candidates.push(encodeCandidate(resized.get_bytes_jpeg(quality), "image/jpeg"));
115
- }
116
- return candidates;
117
- } finally {
118
- resized.free();
119
- }
120
- }
121
-
122
- const qualitySteps = Array.from(new Set([opts.jpegQuality, 85, 70, 55, 40]));
123
- let currentWidth = targetWidth;
124
- let currentHeight = targetHeight;
125
-
126
- while (true) {
127
- const candidates = tryEncodings(currentWidth, currentHeight, qualitySteps);
128
- for (const candidate of candidates) {
129
- if (candidate.encodedSize < opts.maxBytes) {
130
- return {
131
- data: candidate.data,
132
- mimeType: candidate.mimeType,
133
- originalWidth,
134
- originalHeight,
135
- width: currentWidth,
136
- height: currentHeight,
137
- wasResized: true,
138
- };
139
- }
140
- }
141
-
142
- if (currentWidth === 1 && currentHeight === 1) {
143
- break;
144
- }
145
-
146
- const nextWidth = currentWidth === 1 ? 1 : Math.max(1, Math.floor(currentWidth * 0.75));
147
- const nextHeight = currentHeight === 1 ? 1 : Math.max(1, Math.floor(currentHeight * 0.75));
148
- if (nextWidth === currentWidth && nextHeight === currentHeight) {
149
- break;
150
- }
151
-
152
- currentWidth = nextWidth;
153
- currentHeight = nextHeight;
154
- }
155
-
156
- return null;
157
- } catch {
158
- return null;
159
- } finally {
160
- if (image) {
161
- image.free();
162
- }
163
- }
164
- }
@@ -1,42 +0,0 @@
1
- import { parentPort } from "node:worker_threads";
2
- import { type ImageResizeOptions, type ResizedImage, resizeImageInProcess } from "./image-resize-core.ts";
3
-
4
- interface ResizeImageWorkerRequest {
5
- inputBytes: Uint8Array;
6
- mimeType: string;
7
- options?: ImageResizeOptions;
8
- }
9
-
10
- interface ResizeImageWorkerResponse {
11
- result?: ResizedImage | null;
12
- error?: string;
13
- }
14
-
15
- function isResizeImageWorkerRequest(value: unknown): value is ResizeImageWorkerRequest {
16
- if (!value || typeof value !== "object") return false;
17
- const record = value as Record<string, unknown>;
18
- return record.inputBytes instanceof Uint8Array && typeof record.mimeType === "string";
19
- }
20
-
21
- const port = parentPort;
22
- if (!port) {
23
- throw new Error("image resize worker requires parentPort");
24
- }
25
-
26
- port.once("message", (message: unknown) => {
27
- void (async () => {
28
- try {
29
- if (!isResizeImageWorkerRequest(message)) {
30
- throw new Error("Invalid image resize worker request");
31
- }
32
- const result = await resizeImageInProcess(message.inputBytes, message.mimeType, message.options);
33
- const response: ResizeImageWorkerResponse = { result };
34
- port.postMessage(response);
35
- } catch (error) {
36
- const response: ResizeImageWorkerResponse = {
37
- error: error instanceof Error ? error.message : String(error),
38
- };
39
- port.postMessage(response);
40
- }
41
- })();
42
- });
@@ -1,123 +0,0 @@
1
- import { Worker } from "node:worker_threads";
2
- import { type ImageResizeOptions, type ResizedImage, resizeImageInProcess } from "./image-resize-core.ts";
3
-
4
- export type { ImageResizeOptions, ResizedImage } from "./image-resize-core.ts";
5
-
6
- interface ResizeImageWorkerResponse {
7
- result?: ResizedImage | null;
8
- error?: string;
9
- }
10
-
11
- function toTransferableBytes(input: Uint8Array): Uint8Array<ArrayBuffer> {
12
- // Transfer detaches the buffer, so transfer a worker-owned copy and leave the
13
- // caller's bytes intact.
14
- return new Uint8Array(input);
15
- }
16
-
17
- function isResizeImageWorkerResponse(value: unknown): value is ResizeImageWorkerResponse {
18
- return value !== null && typeof value === "object";
19
- }
20
-
21
- function createResizeWorker(workerSpecifier: string | URL): Worker {
22
- return new Worker(workerSpecifier);
23
- }
24
-
25
- async function resizeImageInWorker(
26
- workerSpecifier: string | URL,
27
- inputBytes: Uint8Array,
28
- mimeType: string,
29
- options?: ImageResizeOptions,
30
- ): Promise<ResizedImage | null> {
31
- const worker = createResizeWorker(workerSpecifier);
32
- try {
33
- const inputBytesForWorker = toTransferableBytes(inputBytes);
34
- return await new Promise<ResizedImage | null>((resolve, reject) => {
35
- let settled = false;
36
- const settle = (result: ResizedImage | null): void => {
37
- if (settled) return;
38
- settled = true;
39
- resolve(result);
40
- };
41
- const fail = (error: Error): void => {
42
- if (settled) return;
43
- settled = true;
44
- reject(error);
45
- };
46
-
47
- worker.once("message", (message: unknown) => {
48
- if (!isResizeImageWorkerResponse(message)) {
49
- fail(new Error("Invalid image resize worker response"));
50
- return;
51
- }
52
- if (message.error) {
53
- fail(new Error(message.error));
54
- return;
55
- }
56
- settle(message.result ?? null);
57
- });
58
- worker.once("error", fail);
59
- worker.once("exit", (code) => {
60
- if (!settled) {
61
- fail(new Error(`Image resize worker exited with code ${code}`));
62
- }
63
- });
64
- worker.postMessage(
65
- {
66
- inputBytes: inputBytesForWorker,
67
- mimeType,
68
- options,
69
- },
70
- [inputBytesForWorker.buffer],
71
- );
72
- });
73
- } finally {
74
- void worker.terminate().catch(() => undefined);
75
- }
76
- }
77
-
78
- /**
79
- * Resize an image to fit within the specified max dimensions and encoded file size.
80
- * Runs Photon in a worker thread so WASM decoding, resizing, and encoding do not
81
- * block the TUI event loop. If the worker cannot be loaded (for example in some
82
- * Bun compiled executable layouts), fall back to in-process resizing so image
83
- * reads still work.
84
- */
85
- export async function resizeImage(
86
- inputBytes: Uint8Array,
87
- mimeType: string,
88
- options?: ImageResizeOptions,
89
- ): Promise<ResizedImage | null> {
90
- const isTypeScriptRuntime = import.meta.url.endsWith(".ts");
91
- const workerUrl = new URL(
92
- isTypeScriptRuntime ? "./image-resize-worker.ts" : "./image-resize-worker.js",
93
- import.meta.url,
94
- );
95
-
96
- // Bun compiled executables resolve worker entrypoints by string path, not via
97
- // new URL(..., import.meta.url). Try the string path first under Bun so the
98
- // release binary uses the embedded worker instead of falling back in-process.
99
- if (typeof process.versions.bun === "string") {
100
- try {
101
- return await resizeImageInWorker("./src/utils/image-resize-worker.ts", inputBytes, mimeType, options);
102
- } catch {}
103
- }
104
-
105
- try {
106
- return await resizeImageInWorker(workerUrl, inputBytes, mimeType, options);
107
- } catch {
108
- return resizeImageInProcess(inputBytes, mimeType, options);
109
- }
110
- }
111
-
112
- /**
113
- * Format a dimension note for resized images.
114
- * This helps the model understand the coordinate mapping.
115
- */
116
- export function formatDimensionNote(result: ResizedImage): string | undefined {
117
- if (!result.wasResized) {
118
- return undefined;
119
- }
120
-
121
- const scale = result.originalWidth / result.width;
122
- return `[Image: original ${result.originalWidth}x${result.originalHeight}, displayed at ${result.width}x${result.height}. Multiply coordinates by ${scale.toFixed(2)} to map to original image.]`;
123
- }