@astralyn/pi 0.86.0 → 0.87.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 (127) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/dist/bundle/chunks/chunk-INDBRWNK.js +2 -0
  3. package/dist/bundle/chunks/{chunk-MSHHFVMD.js → chunk-RKLIHRPH.js} +58 -51
  4. package/dist/bundle/chunks/github-copilot.js +1 -1
  5. package/dist/bundle/chunks/{openai-completions-WJIA7NEB.js → openai-completions-JZCEEJ43.js} +1 -1
  6. package/dist/bundle/chunks/{virtual-modules-B4BILEXT.js → virtual-modules-CG7NJ5WO.js} +1 -1
  7. package/dist/bundle/cli.js +1 -1
  8. package/dist/bundle/index.js +1 -1
  9. package/dist/bundle/rpc-entry.js +1 -1
  10. package/dist/cli/args.d.ts.map +1 -1
  11. package/dist/cli/args.js +1 -0
  12. package/dist/cli/args.js.map +1 -1
  13. package/dist/cli/file-processor.d.ts +1 -1
  14. package/dist/cli/file-processor.d.ts.map +1 -1
  15. package/dist/cli/file-processor.js.map +1 -1
  16. package/dist/core/agent-session-runtime.js +1 -1
  17. package/dist/core/agent-session-runtime.js.map +1 -1
  18. package/dist/core/agent-session.d.ts +31 -4
  19. package/dist/core/agent-session.d.ts.map +1 -1
  20. package/dist/core/agent-session.js +395 -126
  21. package/dist/core/agent-session.js.map +1 -1
  22. package/dist/core/cache-warmer.d.ts +1 -0
  23. package/dist/core/cache-warmer.d.ts.map +1 -1
  24. package/dist/core/cache-warmer.js +15 -1
  25. package/dist/core/cache-warmer.js.map +1 -1
  26. package/dist/core/compaction/compaction.d.ts +3 -1
  27. package/dist/core/compaction/compaction.d.ts.map +1 -1
  28. package/dist/core/compaction/compaction.js +146 -48
  29. package/dist/core/compaction/compaction.js.map +1 -1
  30. package/dist/core/crash-log.d.ts +5 -0
  31. package/dist/core/crash-log.d.ts.map +1 -1
  32. package/dist/core/crash-log.js +68 -0
  33. package/dist/core/crash-log.js.map +1 -1
  34. package/dist/core/export-html/template.js +6 -1
  35. package/dist/core/extensions/index.d.ts +1 -1
  36. package/dist/core/extensions/index.d.ts.map +1 -1
  37. package/dist/core/extensions/index.js.map +1 -1
  38. package/dist/core/extensions/runner.d.ts +16 -3
  39. package/dist/core/extensions/runner.d.ts.map +1 -1
  40. package/dist/core/extensions/runner.js +110 -5
  41. package/dist/core/extensions/runner.js.map +1 -1
  42. package/dist/core/extensions/types.d.ts +77 -6
  43. package/dist/core/extensions/types.d.ts.map +1 -1
  44. package/dist/core/extensions/types.js.map +1 -1
  45. package/dist/core/index.d.ts +1 -1
  46. package/dist/core/index.d.ts.map +1 -1
  47. package/dist/core/index.js.map +1 -1
  48. package/dist/core/model-config.d.ts +52 -0
  49. package/dist/core/model-config.d.ts.map +1 -1
  50. package/dist/core/model-config.js +16 -0
  51. package/dist/core/model-config.js.map +1 -1
  52. package/dist/core/model-resolver.d.ts.map +1 -1
  53. package/dist/core/model-resolver.js +1 -0
  54. package/dist/core/model-resolver.js.map +1 -1
  55. package/dist/core/prompt-templates.d.ts +6 -1
  56. package/dist/core/prompt-templates.d.ts.map +1 -1
  57. package/dist/core/prompt-templates.js +61 -35
  58. package/dist/core/prompt-templates.js.map +1 -1
  59. package/dist/core/provider-composer.d.ts +1 -0
  60. package/dist/core/provider-composer.d.ts.map +1 -1
  61. package/dist/core/provider-composer.js +19 -0
  62. package/dist/core/provider-composer.js.map +1 -1
  63. package/dist/core/resource-loader.d.ts.map +1 -1
  64. package/dist/core/resource-loader.js +6 -2
  65. package/dist/core/resource-loader.js.map +1 -1
  66. package/dist/core/sdk.d.ts.map +1 -1
  67. package/dist/core/sdk.js +3 -4
  68. package/dist/core/sdk.js.map +1 -1
  69. package/dist/core/session-manager.d.ts +36 -9
  70. package/dist/core/session-manager.d.ts.map +1 -1
  71. package/dist/core/session-manager.js +97 -7
  72. package/dist/core/session-manager.js.map +1 -1
  73. package/dist/core/tools/read.d.ts +4 -1
  74. package/dist/core/tools/read.d.ts.map +1 -1
  75. package/dist/core/tools/read.js +5 -1
  76. package/dist/core/tools/read.js.map +1 -1
  77. package/dist/extensions/btw/agent.d.ts.map +1 -1
  78. package/dist/extensions/btw/agent.js +8 -5
  79. package/dist/extensions/btw/agent.js.map +1 -1
  80. package/dist/index.d.ts +2 -2
  81. package/dist/index.d.ts.map +1 -1
  82. package/dist/index.js +1 -1
  83. package/dist/index.js.map +1 -1
  84. package/dist/main.d.ts.map +1 -1
  85. package/dist/main.js +4 -3
  86. package/dist/main.js.map +1 -1
  87. package/dist/modes/interactive/bug-report.d.ts +2 -0
  88. package/dist/modes/interactive/bug-report.d.ts.map +1 -1
  89. package/dist/modes/interactive/bug-report.js +6 -5
  90. package/dist/modes/interactive/bug-report.js.map +1 -1
  91. package/dist/modes/interactive/components/extension-editor.d.ts +4 -1
  92. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  93. package/dist/modes/interactive/components/extension-editor.js +7 -2
  94. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  95. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  96. package/dist/modes/interactive/components/tree-selector.js +7 -0
  97. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  98. package/dist/modes/interactive/interactive-mode.d.ts +5 -1
  99. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  100. package/dist/modes/interactive/interactive-mode.js +74 -5
  101. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  102. package/dist/utils/clipboard-image.d.ts.map +1 -1
  103. package/dist/utils/clipboard-image.js +1 -12
  104. package/dist/utils/clipboard-image.js.map +1 -1
  105. package/dist/utils/clipboard.d.ts.map +1 -1
  106. package/dist/utils/clipboard.js +74 -20
  107. package/dist/utils/clipboard.js.map +1 -1
  108. package/dist/utils/mime.js +1 -1
  109. package/dist/utils/mime.js.map +1 -1
  110. package/dist/utils/tool-result-images.d.ts +3 -1
  111. package/dist/utils/tool-result-images.d.ts.map +1 -1
  112. package/dist/utils/tool-result-images.js +4 -1
  113. package/dist/utils/tool-result-images.js.map +1 -1
  114. package/dist/utils/wsl.d.ts +3 -0
  115. package/dist/utils/wsl.d.ts.map +1 -0
  116. package/dist/utils/wsl.js +15 -0
  117. package/dist/utils/wsl.js.map +1 -0
  118. package/docs/compaction.md +23 -4
  119. package/docs/extensions.md +72 -8
  120. package/docs/models.md +30 -1
  121. package/docs/providers.md +8 -0
  122. package/docs/sdk.md +14 -3
  123. package/docs/session-format.md +25 -4
  124. package/examples/plugins/pi-example-plugin/src/session.ts +3 -2
  125. package/npm-shrinkwrap.json +43 -43
  126. package/package.json +8 -8
  127. package/dist/bundle/chunks/chunk-M2YWW37K.js +0 -2
@@ -1 +1 @@
1
- {"version":3,"file":"clipboard-image.d.ts","sourceRoot":"","sources":["../../src/utils/clipboard-image.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,cAAc,GAAG;IAC5B,KAAK,EAAE,UAAU,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CACjB,CAAC;AAOF,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAE9E;AAMD,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAazE;AAiLD,wBAAsB,kBAAkB,CAAC,OAAO,CAAC,EAAE;IAClD,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;CAC3B,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAqCjC"}
1
+ {"version":3,"file":"clipboard-image.d.ts","sourceRoot":"","sources":["../../src/utils/clipboard-image.ts"],"names":[],"mappings":"AAWA,MAAM,MAAM,cAAc,GAAG;IAC5B,KAAK,EAAE,UAAU,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CACjB,CAAC;AAOF,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAE9E;AAMD,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAazE;AAoKD,wBAAsB,kBAAkB,CAAC,OAAO,CAAC,EAAE;IAClD,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;CAC3B,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAqCjC"}
@@ -6,6 +6,7 @@ import { join } from "path";
6
6
  import { runClipboardCommand } from "./clipboard-command.js";
7
7
  import { detectSupportedImageMimeType } from "./mime.js";
8
8
  import { loadPhoton } from "./photon.js";
9
+ import { isWSL } from "./wsl.js";
9
10
  const SUPPORTED_IMAGE_MIME_TYPES = ["image/png", "image/jpeg", "image/webp", "image/gif"];
10
11
  const DEFAULT_LIST_TIMEOUT_MS = 1000;
11
12
  const DEFAULT_POWERSHELL_TIMEOUT_MS = 5000;
@@ -91,18 +92,6 @@ async function readClipboardImageViaWlPaste() {
91
92
  return null;
92
93
  return { bytes: data, mimeType: baseMimeType(selectedType) };
93
94
  }
94
- function isWSL(env = process.env) {
95
- if (env.WSL_DISTRO_NAME || env.WSLENV) {
96
- return true;
97
- }
98
- try {
99
- const release = readFileSync("/proc/version", "utf-8");
100
- return /microsoft|wsl/i.test(release);
101
- }
102
- catch {
103
- return false;
104
- }
105
- }
106
95
  /**
107
96
  * On WSL, the Linux clipboard (Wayland/X11) does not receive image data from
108
97
  * Windows screenshots (Win+Shift+S). PowerShell can access the Windows clipboard
@@ -1 +1 @@
1
- {"version":3,"file":"clipboard-image.js","sourceRoot":"","sources":["../../src/utils/clipboard-image.ts"],"names":[],"mappings":"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","sourcesContent":["import { getNativeClipboard } from \"@earendil-works/pi-tui\";\nimport { randomUUID } from \"crypto\";\nimport { readFileSync, unlinkSync } from \"fs\";\nimport { tmpdir } from \"os\";\nimport { join } from \"path\";\n\nimport { runClipboardCommand } from \"./clipboard-command.ts\";\nimport { detectSupportedImageMimeType } from \"./mime.ts\";\nimport { loadPhoton } from \"./photon.ts\";\n\nexport type ClipboardImage = {\n\tbytes: Uint8Array;\n\tmimeType: string;\n};\n\nconst SUPPORTED_IMAGE_MIME_TYPES = [\"image/png\", \"image/jpeg\", \"image/webp\", \"image/gif\"] as const;\n\nconst DEFAULT_LIST_TIMEOUT_MS = 1000;\nconst DEFAULT_POWERSHELL_TIMEOUT_MS = 5000;\n\nexport function isWaylandSession(env: NodeJS.ProcessEnv = process.env): boolean {\n\treturn Boolean(env.WAYLAND_DISPLAY) || env.XDG_SESSION_TYPE === \"wayland\";\n}\n\nfunction baseMimeType(mimeType: string): string {\n\treturn mimeType.split(\";\")[0]?.trim().toLowerCase() ?? mimeType.toLowerCase();\n}\n\nexport function extensionForImageMimeType(mimeType: string): string | null {\n\tswitch (baseMimeType(mimeType)) {\n\t\tcase \"image/png\":\n\t\t\treturn \"png\";\n\t\tcase \"image/jpeg\":\n\t\t\treturn \"jpg\";\n\t\tcase \"image/webp\":\n\t\t\treturn \"webp\";\n\t\tcase \"image/gif\":\n\t\t\treturn \"gif\";\n\t\tdefault:\n\t\t\treturn null;\n\t}\n}\n\nfunction selectPreferredImageMimeType(mimeTypes: string[]): string | null {\n\tconst normalized = mimeTypes\n\t\t.map((t) => t.trim())\n\t\t.filter(Boolean)\n\t\t.map((t) => ({ raw: t, base: baseMimeType(t) }));\n\n\tfor (const preferred of SUPPORTED_IMAGE_MIME_TYPES) {\n\t\tconst match = normalized.find((t) => t.base === preferred);\n\t\tif (match) {\n\t\t\treturn match.raw;\n\t\t}\n\t}\n\n\tconst anyImage = normalized.find((t) => t.base.startsWith(\"image/\"));\n\treturn anyImage?.raw ?? null;\n}\n\nfunction isSupportedImageMimeType(mimeType: string): boolean {\n\tconst base = baseMimeType(mimeType);\n\treturn SUPPORTED_IMAGE_MIME_TYPES.some((t) => t === base);\n}\n\n/**\n * Convert unsupported image formats to PNG using Photon.\n * Returns null if conversion is unavailable or fails.\n */\nasync function convertToPng(bytes: Uint8Array): Promise<Uint8Array | null> {\n\tconst photon = await loadPhoton();\n\tif (!photon) {\n\t\treturn null;\n\t}\n\n\ttry {\n\t\tconst image = photon.PhotonImage.new_from_byteslice(bytes);\n\t\ttry {\n\t\t\treturn image.get_bytes();\n\t\t} finally {\n\t\t\timage.free();\n\t\t}\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n// Undefined means the backend failed; null means it has no image. An empty\n// Wayland clipboard must not fall through to stale X11 clipboard contents.\nasync function readClipboardImageViaWlPaste(): Promise<ClipboardImage | null | undefined> {\n\tconst list = await runClipboardCommand(\"wl-paste\", [\"--list-types\"], { timeoutMs: DEFAULT_LIST_TIMEOUT_MS });\n\tif (list === undefined) return undefined;\n\n\tconst types = list\n\t\t.toString(\"utf-8\")\n\t\t.split(/\\r?\\n/)\n\t\t.map((t) => t.trim())\n\t\t.filter(Boolean);\n\n\tconst selectedType = selectPreferredImageMimeType(types);\n\tif (!selectedType) {\n\t\treturn null;\n\t}\n\n\tconst data = await runClipboardCommand(\"wl-paste\", [\"--type\", selectedType, \"--no-newline\"]);\n\tif (data === undefined) return undefined;\n\tif (data.length === 0) return null;\n\n\treturn { bytes: data, mimeType: baseMimeType(selectedType) };\n}\n\nfunction isWSL(env: NodeJS.ProcessEnv = process.env): boolean {\n\tif (env.WSL_DISTRO_NAME || env.WSLENV) {\n\t\treturn true;\n\t}\n\n\ttry {\n\t\tconst release = readFileSync(\"/proc/version\", \"utf-8\");\n\t\treturn /microsoft|wsl/i.test(release);\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * On WSL, the Linux clipboard (Wayland/X11) does not receive image data from\n * Windows screenshots (Win+Shift+S). PowerShell can access the Windows clipboard\n * directly, so we use it as a fallback.\n */\nasync function readClipboardImageViaPowerShell(): Promise<ClipboardImage | null> {\n\tconst tmpFile = join(tmpdir(), `pi-wsl-clip-${randomUUID()}.png`);\n\n\ttry {\n\t\tconst winPathResult = await runClipboardCommand(\"wslpath\", [\"-w\", tmpFile], {\n\t\t\ttimeoutMs: DEFAULT_LIST_TIMEOUT_MS,\n\t\t});\n\t\tif (winPathResult === undefined) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst winPath = winPathResult.toString(\"utf-8\").trim();\n\t\tif (!winPath) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst psQuotedWinPath = winPath.replaceAll(\"'\", \"''\");\n\t\tconst psScript = [\n\t\t\t\"Add-Type -AssemblyName System.Windows.Forms\",\n\t\t\t\"Add-Type -AssemblyName System.Drawing\",\n\t\t\t`$path = '${psQuotedWinPath}'`,\n\t\t\t\"$img = [System.Windows.Forms.Clipboard]::GetImage()\",\n\t\t\t\"if ($img) { $img.Save($path, [System.Drawing.Imaging.ImageFormat]::Png); Write-Output 'ok' } else { Write-Output 'empty' }\",\n\t\t].join(\"; \");\n\n\t\tconst result = await runClipboardCommand(\"powershell.exe\", [\"-NoProfile\", \"-Command\", psScript], {\n\t\t\ttimeoutMs: DEFAULT_POWERSHELL_TIMEOUT_MS,\n\t\t});\n\t\tif (result === undefined) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst output = result.toString(\"utf-8\").trim();\n\t\tif (output !== \"ok\") {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst bytes = readFileSync(tmpFile);\n\t\tif (bytes.length === 0) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn { bytes: new Uint8Array(bytes), mimeType: \"image/png\" };\n\t} catch {\n\t\treturn null;\n\t} finally {\n\t\ttry {\n\t\t\tunlinkSync(tmpFile);\n\t\t} catch {\n\t\t\t// Ignore cleanup errors.\n\t\t}\n\t}\n}\n\nasync function readClipboardImageViaXclip(): Promise<ClipboardImage | null | undefined> {\n\tconst targets = await runClipboardCommand(\"xclip\", [\"-selection\", \"clipboard\", \"-t\", \"TARGETS\", \"-o\"], {\n\t\ttimeoutMs: DEFAULT_LIST_TIMEOUT_MS,\n\t});\n\n\tlet candidateTypes: string[] = [];\n\tif (targets !== undefined) {\n\t\tcandidateTypes = targets\n\t\t\t.toString(\"utf-8\")\n\t\t\t.split(/\\r?\\n/)\n\t\t\t.map((t) => t.trim())\n\t\t\t.filter(Boolean);\n\t}\n\n\tconst preferred = selectPreferredImageMimeType(candidateTypes);\n\tif (targets !== undefined && !preferred) return null;\n\tconst tryTypes = new Set(preferred ? [preferred, ...SUPPORTED_IMAGE_MIME_TYPES] : SUPPORTED_IMAGE_MIME_TYPES);\n\n\tfor (const mimeType of tryTypes) {\n\t\tconst data = await runClipboardCommand(\"xclip\", [\"-selection\", \"clipboard\", \"-t\", mimeType, \"-o\"]);\n\t\tif (data !== undefined && data.length > 0) {\n\t\t\treturn { bytes: data, mimeType: baseMimeType(mimeType) };\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\nasync function readClipboardImageViaNativeClipboard(): Promise<ClipboardImage | null | undefined> {\n\tconst bytes = await getNativeClipboard()?.getImage();\n\tif (bytes === undefined) return undefined;\n\tif (!bytes?.length) return null;\n\treturn { bytes, mimeType: detectSupportedImageMimeType(bytes) ?? \"application/octet-stream\" };\n}\n\nexport async function readClipboardImage(options?: {\n\tenv?: NodeJS.ProcessEnv;\n\tplatform?: NodeJS.Platform;\n}): Promise<ClipboardImage | null> {\n\tconst env = options?.env ?? process.env;\n\tconst platform = options?.platform ?? process.platform;\n\n\tif (env.TERMUX_VERSION) {\n\t\treturn null;\n\t}\n\n\tlet image: ClipboardImage | null | undefined;\n\n\tif (platform === \"linux\") {\n\t\tconst wsl = isWSL(env);\n\t\tif (isWaylandSession(env) || wsl) {\n\t\t\timage = await readClipboardImageViaWlPaste();\n\t\t}\n\t\tif (image === undefined) image = await readClipboardImageViaXclip();\n\t\t// Preserve Linux's empty/unavailable distinction if Windows has no image.\n\t\tif (!image && wsl) image = (await readClipboardImageViaPowerShell()) ?? image;\n\t\tif (image === undefined) image = await readClipboardImageViaNativeClipboard();\n\t} else {\n\t\timage = await readClipboardImageViaNativeClipboard();\n\t}\n\n\tif (!image) {\n\t\treturn null;\n\t}\n\n\t// Convert unsupported formats (e.g., Windows DIB data wrapped as BMP) to PNG\n\tif (!isSupportedImageMimeType(image.mimeType)) {\n\t\tconst pngBytes = await convertToPng(image.bytes);\n\t\tif (!pngBytes) {\n\t\t\treturn null;\n\t\t}\n\t\treturn { bytes: pngBytes, mimeType: \"image/png\" };\n\t}\n\n\treturn image;\n}\n"]}
1
+ {"version":3,"file":"clipboard-image.js","sourceRoot":"","sources":["../../src/utils/clipboard-image.ts"],"names":[],"mappings":"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","sourcesContent":["import { getNativeClipboard } from \"@earendil-works/pi-tui\";\nimport { randomUUID } from \"crypto\";\nimport { readFileSync, unlinkSync } from \"fs\";\nimport { tmpdir } from \"os\";\nimport { join } from \"path\";\n\nimport { runClipboardCommand } from \"./clipboard-command.ts\";\nimport { detectSupportedImageMimeType } from \"./mime.ts\";\nimport { loadPhoton } from \"./photon.ts\";\nimport { isWSL } from \"./wsl.ts\";\n\nexport type ClipboardImage = {\n\tbytes: Uint8Array;\n\tmimeType: string;\n};\n\nconst SUPPORTED_IMAGE_MIME_TYPES = [\"image/png\", \"image/jpeg\", \"image/webp\", \"image/gif\"] as const;\n\nconst DEFAULT_LIST_TIMEOUT_MS = 1000;\nconst DEFAULT_POWERSHELL_TIMEOUT_MS = 5000;\n\nexport function isWaylandSession(env: NodeJS.ProcessEnv = process.env): boolean {\n\treturn Boolean(env.WAYLAND_DISPLAY) || env.XDG_SESSION_TYPE === \"wayland\";\n}\n\nfunction baseMimeType(mimeType: string): string {\n\treturn mimeType.split(\";\")[0]?.trim().toLowerCase() ?? mimeType.toLowerCase();\n}\n\nexport function extensionForImageMimeType(mimeType: string): string | null {\n\tswitch (baseMimeType(mimeType)) {\n\t\tcase \"image/png\":\n\t\t\treturn \"png\";\n\t\tcase \"image/jpeg\":\n\t\t\treturn \"jpg\";\n\t\tcase \"image/webp\":\n\t\t\treturn \"webp\";\n\t\tcase \"image/gif\":\n\t\t\treturn \"gif\";\n\t\tdefault:\n\t\t\treturn null;\n\t}\n}\n\nfunction selectPreferredImageMimeType(mimeTypes: string[]): string | null {\n\tconst normalized = mimeTypes\n\t\t.map((t) => t.trim())\n\t\t.filter(Boolean)\n\t\t.map((t) => ({ raw: t, base: baseMimeType(t) }));\n\n\tfor (const preferred of SUPPORTED_IMAGE_MIME_TYPES) {\n\t\tconst match = normalized.find((t) => t.base === preferred);\n\t\tif (match) {\n\t\t\treturn match.raw;\n\t\t}\n\t}\n\n\tconst anyImage = normalized.find((t) => t.base.startsWith(\"image/\"));\n\treturn anyImage?.raw ?? null;\n}\n\nfunction isSupportedImageMimeType(mimeType: string): boolean {\n\tconst base = baseMimeType(mimeType);\n\treturn SUPPORTED_IMAGE_MIME_TYPES.some((t) => t === base);\n}\n\n/**\n * Convert unsupported image formats to PNG using Photon.\n * Returns null if conversion is unavailable or fails.\n */\nasync function convertToPng(bytes: Uint8Array): Promise<Uint8Array | null> {\n\tconst photon = await loadPhoton();\n\tif (!photon) {\n\t\treturn null;\n\t}\n\n\ttry {\n\t\tconst image = photon.PhotonImage.new_from_byteslice(bytes);\n\t\ttry {\n\t\t\treturn image.get_bytes();\n\t\t} finally {\n\t\t\timage.free();\n\t\t}\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n// Undefined means the backend failed; null means it has no image. An empty\n// Wayland clipboard must not fall through to stale X11 clipboard contents.\nasync function readClipboardImageViaWlPaste(): Promise<ClipboardImage | null | undefined> {\n\tconst list = await runClipboardCommand(\"wl-paste\", [\"--list-types\"], { timeoutMs: DEFAULT_LIST_TIMEOUT_MS });\n\tif (list === undefined) return undefined;\n\n\tconst types = list\n\t\t.toString(\"utf-8\")\n\t\t.split(/\\r?\\n/)\n\t\t.map((t) => t.trim())\n\t\t.filter(Boolean);\n\n\tconst selectedType = selectPreferredImageMimeType(types);\n\tif (!selectedType) {\n\t\treturn null;\n\t}\n\n\tconst data = await runClipboardCommand(\"wl-paste\", [\"--type\", selectedType, \"--no-newline\"]);\n\tif (data === undefined) return undefined;\n\tif (data.length === 0) return null;\n\n\treturn { bytes: data, mimeType: baseMimeType(selectedType) };\n}\n\n/**\n * On WSL, the Linux clipboard (Wayland/X11) does not receive image data from\n * Windows screenshots (Win+Shift+S). PowerShell can access the Windows clipboard\n * directly, so we use it as a fallback.\n */\nasync function readClipboardImageViaPowerShell(): Promise<ClipboardImage | null> {\n\tconst tmpFile = join(tmpdir(), `pi-wsl-clip-${randomUUID()}.png`);\n\n\ttry {\n\t\tconst winPathResult = await runClipboardCommand(\"wslpath\", [\"-w\", tmpFile], {\n\t\t\ttimeoutMs: DEFAULT_LIST_TIMEOUT_MS,\n\t\t});\n\t\tif (winPathResult === undefined) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst winPath = winPathResult.toString(\"utf-8\").trim();\n\t\tif (!winPath) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst psQuotedWinPath = winPath.replaceAll(\"'\", \"''\");\n\t\tconst psScript = [\n\t\t\t\"Add-Type -AssemblyName System.Windows.Forms\",\n\t\t\t\"Add-Type -AssemblyName System.Drawing\",\n\t\t\t`$path = '${psQuotedWinPath}'`,\n\t\t\t\"$img = [System.Windows.Forms.Clipboard]::GetImage()\",\n\t\t\t\"if ($img) { $img.Save($path, [System.Drawing.Imaging.ImageFormat]::Png); Write-Output 'ok' } else { Write-Output 'empty' }\",\n\t\t].join(\"; \");\n\n\t\tconst result = await runClipboardCommand(\"powershell.exe\", [\"-NoProfile\", \"-Command\", psScript], {\n\t\t\ttimeoutMs: DEFAULT_POWERSHELL_TIMEOUT_MS,\n\t\t});\n\t\tif (result === undefined) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst output = result.toString(\"utf-8\").trim();\n\t\tif (output !== \"ok\") {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst bytes = readFileSync(tmpFile);\n\t\tif (bytes.length === 0) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn { bytes: new Uint8Array(bytes), mimeType: \"image/png\" };\n\t} catch {\n\t\treturn null;\n\t} finally {\n\t\ttry {\n\t\t\tunlinkSync(tmpFile);\n\t\t} catch {\n\t\t\t// Ignore cleanup errors.\n\t\t}\n\t}\n}\n\nasync function readClipboardImageViaXclip(): Promise<ClipboardImage | null | undefined> {\n\tconst targets = await runClipboardCommand(\"xclip\", [\"-selection\", \"clipboard\", \"-t\", \"TARGETS\", \"-o\"], {\n\t\ttimeoutMs: DEFAULT_LIST_TIMEOUT_MS,\n\t});\n\n\tlet candidateTypes: string[] = [];\n\tif (targets !== undefined) {\n\t\tcandidateTypes = targets\n\t\t\t.toString(\"utf-8\")\n\t\t\t.split(/\\r?\\n/)\n\t\t\t.map((t) => t.trim())\n\t\t\t.filter(Boolean);\n\t}\n\n\tconst preferred = selectPreferredImageMimeType(candidateTypes);\n\tif (targets !== undefined && !preferred) return null;\n\tconst tryTypes = new Set(preferred ? [preferred, ...SUPPORTED_IMAGE_MIME_TYPES] : SUPPORTED_IMAGE_MIME_TYPES);\n\n\tfor (const mimeType of tryTypes) {\n\t\tconst data = await runClipboardCommand(\"xclip\", [\"-selection\", \"clipboard\", \"-t\", mimeType, \"-o\"]);\n\t\tif (data !== undefined && data.length > 0) {\n\t\t\treturn { bytes: data, mimeType: baseMimeType(mimeType) };\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\nasync function readClipboardImageViaNativeClipboard(): Promise<ClipboardImage | null | undefined> {\n\tconst bytes = await getNativeClipboard()?.getImage();\n\tif (bytes === undefined) return undefined;\n\tif (!bytes?.length) return null;\n\treturn { bytes, mimeType: detectSupportedImageMimeType(bytes) ?? \"application/octet-stream\" };\n}\n\nexport async function readClipboardImage(options?: {\n\tenv?: NodeJS.ProcessEnv;\n\tplatform?: NodeJS.Platform;\n}): Promise<ClipboardImage | null> {\n\tconst env = options?.env ?? process.env;\n\tconst platform = options?.platform ?? process.platform;\n\n\tif (env.TERMUX_VERSION) {\n\t\treturn null;\n\t}\n\n\tlet image: ClipboardImage | null | undefined;\n\n\tif (platform === \"linux\") {\n\t\tconst wsl = isWSL(env);\n\t\tif (isWaylandSession(env) || wsl) {\n\t\t\timage = await readClipboardImageViaWlPaste();\n\t\t}\n\t\tif (image === undefined) image = await readClipboardImageViaXclip();\n\t\t// Preserve Linux's empty/unavailable distinction if Windows has no image.\n\t\tif (!image && wsl) image = (await readClipboardImageViaPowerShell()) ?? image;\n\t\tif (image === undefined) image = await readClipboardImageViaNativeClipboard();\n\t} else {\n\t\timage = await readClipboardImageViaNativeClipboard();\n\t}\n\n\tif (!image) {\n\t\treturn null;\n\t}\n\n\t// Convert unsupported formats (e.g., Windows DIB data wrapped as BMP) to PNG\n\tif (!isSupportedImageMimeType(image.mimeType)) {\n\t\tconst pngBytes = await convertToPng(image.bytes);\n\t\tif (!pngBytes) {\n\t\t\treturn null;\n\t\t}\n\t\treturn { bytes: pngBytes, mimeType: \"image/png\" };\n\t}\n\n\treturn image;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"clipboard.d.ts","sourceRoot":"","sources":["../../src/utils/clipboard.ts"],"names":[],"mappings":"AAmBA,iDAAiD;AACjD,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAkBhE;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAkDjE"}
1
+ {"version":3,"file":"clipboard.d.ts","sourceRoot":"","sources":["../../src/utils/clipboard.ts"],"names":[],"mappings":"AAoDA,iDAAiD;AACjD,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAkBhE;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgEjE"}
@@ -1,8 +1,12 @@
1
- import { platform } from "node:os";
1
+ import { randomUUID } from "node:crypto";
2
+ import { unlinkSync, writeFileSync } from "node:fs";
3
+ import { platform, tmpdir } from "node:os";
4
+ import { join } from "node:path";
2
5
  import { getNativeClipboard } from "@earendil-works/pi-tui";
3
6
  import { runClipboardCommand } from "./clipboard-command.js";
7
+ import { isWSL } from "./wsl.js";
4
8
  const MAX_OSC52_ENCODED_LENGTH = 100_000;
5
- function isRemoteSession(env = process.env) {
9
+ function isRemoteSession(env) {
6
10
  return Boolean(env.SSH_CONNECTION || env.SSH_CLIENT || env.MOSH_CONNECTION);
7
11
  }
8
12
  function emitOsc52(text) {
@@ -13,6 +17,38 @@ function emitOsc52(text) {
13
17
  process.stdout.write(`\x1b]52;c;${encoded}\x07`);
14
18
  return true;
15
19
  }
20
+ /**
21
+ * WSL without WSLg has no Linux display, so the Windows clipboard is written through
22
+ * interop. PowerShell reads the text from a file because `clip.exe` and PowerShell stdin
23
+ * decode piped bytes with the console code page, which mangles non-ASCII UTF-8.
24
+ */
25
+ async function copyViaWindowsClipboard(text) {
26
+ const tmpFile = join(tmpdir(), `pi-wsl-clip-${randomUUID()}.txt`);
27
+ try {
28
+ writeFileSync(tmpFile, text, { encoding: "utf8", mode: 0o600 });
29
+ const winPath = (await runClipboardCommand("wslpath", ["-w", tmpFile], { timeoutMs: 1000 }))
30
+ ?.toString("utf8")
31
+ .trim();
32
+ if (!winPath)
33
+ return false;
34
+ const script = `Set-Clipboard -Value ([System.IO.File]::ReadAllText('${winPath.replaceAll("'", "''")}', [System.Text.Encoding]::UTF8))`;
35
+ const result = await runClipboardCommand("powershell.exe", ["-NoProfile", "-Command", script], {
36
+ timeoutMs: 5000,
37
+ });
38
+ return result !== undefined;
39
+ }
40
+ catch {
41
+ return false;
42
+ }
43
+ finally {
44
+ try {
45
+ unlinkSync(tmpFile);
46
+ }
47
+ catch {
48
+ // The file may not have been created.
49
+ }
50
+ }
51
+ }
16
52
  /** Read plain text from the system clipboard. */
17
53
  export async function readClipboardText() {
18
54
  if (platform() === "linux") {
@@ -39,6 +75,7 @@ export async function readClipboardText() {
39
75
  }
40
76
  export async function copyToClipboard(text) {
41
77
  const p = platform();
78
+ const env = process.env;
42
79
  let copied = false;
43
80
  // Direct writes precede OSC 52 so the terminal cannot race the native writer.
44
81
  // Linux tools retain clipboard selection ownership after this call returns.
@@ -61,11 +98,11 @@ export async function copyToClipboard(text) {
61
98
  else if (p === "win32")
62
99
  commands.push(["clip", []]);
63
100
  else {
64
- if (process.env.TERMUX_VERSION)
101
+ if (env.TERMUX_VERSION)
65
102
  commands.push(["termux-clipboard-set", []]);
66
- if (process.env.WAYLAND_DISPLAY)
103
+ if (env.WAYLAND_DISPLAY)
67
104
  commands.push(["wl-copy", []]);
68
- if (process.env.DISPLAY) {
105
+ if (env.DISPLAY) {
69
106
  commands.push(["xclip", ["-selection", "clipboard"]], ["xsel", ["--clipboard", "--input"]]);
70
107
  }
71
108
  }
@@ -76,22 +113,39 @@ export async function copyToClipboard(text) {
76
113
  }
77
114
  }
78
115
  }
79
- if (isRemoteSession())
80
- copied = emitOsc52(text) || copied;
81
- if (!copied) {
82
- if (p === "linux") {
83
- if (process.env.TERMUX_VERSION) {
84
- throw new Error("Clipboard unavailable: install the Termux:API app and `termux-api` package");
85
- }
86
- if (process.env.WAYLAND_DISPLAY) {
87
- throw new Error("Clipboard unavailable: install `wl-clipboard` (`wl-copy`) or check Wayland access");
88
- }
89
- if (process.env.DISPLAY) {
90
- throw new Error("Clipboard unavailable: install `xclip` or `xsel`, or check X11 access");
91
- }
92
- throw new Error("Clipboard unavailable: no Wayland or X11 display detected");
116
+ let osc52Emitted = false;
117
+ if (!copied && p === "linux" && isWSL(env)) {
118
+ // Windows Terminal supports OSC 52; prefer it over the slower PowerShell round trip.
119
+ if (env.WT_SESSION)
120
+ osc52Emitted = emitOsc52(text);
121
+ copied = osc52Emitted || (await copyViaWindowsClipboard(text));
122
+ }
123
+ // OSC 52 cannot be verified, so a desktop session with a display reports the failure
124
+ // instead (#9618). Without a display the terminal is the only clipboard route (containers,
125
+ // WSL without WSLg), and remote sessions always emit it to reach the client clipboard.
126
+ const headless = p === "linux" && !env.DISPLAY && !env.WAYLAND_DISPLAY && !env.TERMUX_VERSION;
127
+ let oversized = false;
128
+ if (!osc52Emitted && (isRemoteSession(env) || (!copied && headless))) {
129
+ if (emitOsc52(text))
130
+ copied = true;
131
+ else
132
+ oversized = true;
133
+ }
134
+ if (copied)
135
+ return;
136
+ if (oversized)
137
+ throw new Error("Clipboard unavailable: text exceeds the OSC 52 size limit");
138
+ if (p === "linux") {
139
+ if (env.TERMUX_VERSION) {
140
+ throw new Error("Clipboard unavailable: install the Termux:API app and `termux-api` package");
141
+ }
142
+ if (env.WAYLAND_DISPLAY) {
143
+ throw new Error("Clipboard unavailable: install `wl-clipboard` (`wl-copy`) or check Wayland access");
144
+ }
145
+ if (env.DISPLAY) {
146
+ throw new Error("Clipboard unavailable: install `xclip` or `xsel`, or check X11 access");
93
147
  }
94
- throw new Error("Clipboard unavailable");
95
148
  }
149
+ throw new Error("Clipboard unavailable");
96
150
  }
97
151
  //# sourceMappingURL=clipboard.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"clipboard.js","sourceRoot":"","sources":["../../src/utils/clipboard.ts"],"names":[],"mappings":"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","sourcesContent":["import { platform } from \"node:os\";\nimport { getNativeClipboard } from \"@earendil-works/pi-tui\";\nimport { runClipboardCommand } from \"./clipboard-command.ts\";\n\nconst MAX_OSC52_ENCODED_LENGTH = 100_000;\n\nfunction isRemoteSession(env: NodeJS.ProcessEnv = process.env): boolean {\n\treturn Boolean(env.SSH_CONNECTION || env.SSH_CLIENT || env.MOSH_CONNECTION);\n}\n\nfunction emitOsc52(text: string): boolean {\n\tconst encoded = Buffer.from(text).toString(\"base64\");\n\tif (encoded.length > MAX_OSC52_ENCODED_LENGTH) {\n\t\treturn false;\n\t}\n\tprocess.stdout.write(`\\x1b]52;c;${encoded}\\x07`);\n\treturn true;\n}\n\n/** Read plain text from the system clipboard. */\nexport async function readClipboardText(): Promise<string | null> {\n\tif (platform() === \"linux\") {\n\t\tconst commands: [string, string[]][] = [];\n\t\tif (process.env.TERMUX_VERSION) commands.push([\"termux-clipboard-get\", []]);\n\t\tif (process.env.WAYLAND_DISPLAY) commands.push([\"wl-paste\", [\"--no-newline\", \"--type\", \"text\"]]);\n\t\tif (process.env.DISPLAY) {\n\t\t\tcommands.push([\"xclip\", [\"-selection\", \"clipboard\", \"-out\"]], [\"xsel\", [\"--clipboard\", \"--output\"]]);\n\t\t}\n\t\tfor (const [command, args] of commands) {\n\t\t\tconst bytes = await runClipboardCommand(command, args, { timeoutMs: 5000 });\n\t\t\tif (bytes !== undefined) return bytes.toString(\"utf8\") || null;\n\t\t}\n\t}\n\ttry {\n\t\treturn (await getNativeClipboard()?.getText()) || null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nexport async function copyToClipboard(text: string): Promise<void> {\n\tconst p = platform();\n\tlet copied = false;\n\t// Direct writes precede OSC 52 so the terminal cannot race the native writer.\n\t// Linux tools retain clipboard selection ownership after this call returns.\n\tif (p !== \"linux\") {\n\t\ttry {\n\t\t\tconst clipboard = getNativeClipboard();\n\t\t\tif (clipboard?.setText) {\n\t\t\t\tawait clipboard.setText(text);\n\t\t\t\tcopied = true;\n\t\t\t}\n\t\t} catch {\n\t\t\t// Try platform commands next.\n\t\t}\n\t}\n\tif (!copied) {\n\t\tconst commands: [string, string[]][] = [];\n\t\tif (p === \"darwin\") commands.push([\"pbcopy\", []]);\n\t\telse if (p === \"win32\") commands.push([\"clip\", []]);\n\t\telse {\n\t\t\tif (process.env.TERMUX_VERSION) commands.push([\"termux-clipboard-set\", []]);\n\t\t\tif (process.env.WAYLAND_DISPLAY) commands.push([\"wl-copy\", []]);\n\t\t\tif (process.env.DISPLAY) {\n\t\t\t\tcommands.push([\"xclip\", [\"-selection\", \"clipboard\"]], [\"xsel\", [\"--clipboard\", \"--input\"]]);\n\t\t\t}\n\t\t}\n\t\tfor (const [command, args] of commands) {\n\t\t\tif ((await runClipboardCommand(command, args, { input: text, timeoutMs: 5000 })) !== undefined) {\n\t\t\t\tcopied = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\tif (isRemoteSession()) copied = emitOsc52(text) || copied;\n\tif (!copied) {\n\t\tif (p === \"linux\") {\n\t\t\tif (process.env.TERMUX_VERSION) {\n\t\t\t\tthrow new Error(\"Clipboard unavailable: install the Termux:API app and `termux-api` package\");\n\t\t\t}\n\t\t\tif (process.env.WAYLAND_DISPLAY) {\n\t\t\t\tthrow new Error(\"Clipboard unavailable: install `wl-clipboard` (`wl-copy`) or check Wayland access\");\n\t\t\t}\n\t\t\tif (process.env.DISPLAY) {\n\t\t\t\tthrow new Error(\"Clipboard unavailable: install `xclip` or `xsel`, or check X11 access\");\n\t\t\t}\n\t\t\tthrow new Error(\"Clipboard unavailable: no Wayland or X11 display detected\");\n\t\t}\n\t\tthrow new Error(\"Clipboard unavailable\");\n\t}\n}\n"]}
1
+ {"version":3,"file":"clipboard.js","sourceRoot":"","sources":["../../src/utils/clipboard.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { unlinkSync, writeFileSync } from \"node:fs\";\nimport { platform, tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { getNativeClipboard } from \"@earendil-works/pi-tui\";\nimport { runClipboardCommand } from \"./clipboard-command.ts\";\nimport { isWSL } from \"./wsl.ts\";\n\nconst MAX_OSC52_ENCODED_LENGTH = 100_000;\n\nfunction isRemoteSession(env: NodeJS.ProcessEnv): boolean {\n\treturn Boolean(env.SSH_CONNECTION || env.SSH_CLIENT || env.MOSH_CONNECTION);\n}\n\nfunction emitOsc52(text: string): boolean {\n\tconst encoded = Buffer.from(text).toString(\"base64\");\n\tif (encoded.length > MAX_OSC52_ENCODED_LENGTH) {\n\t\treturn false;\n\t}\n\tprocess.stdout.write(`\\x1b]52;c;${encoded}\\x07`);\n\treturn true;\n}\n\n/**\n * WSL without WSLg has no Linux display, so the Windows clipboard is written through\n * interop. PowerShell reads the text from a file because `clip.exe` and PowerShell stdin\n * decode piped bytes with the console code page, which mangles non-ASCII UTF-8.\n */\nasync function copyViaWindowsClipboard(text: string): Promise<boolean> {\n\tconst tmpFile = join(tmpdir(), `pi-wsl-clip-${randomUUID()}.txt`);\n\ttry {\n\t\twriteFileSync(tmpFile, text, { encoding: \"utf8\", mode: 0o600 });\n\t\tconst winPath = (await runClipboardCommand(\"wslpath\", [\"-w\", tmpFile], { timeoutMs: 1000 }))\n\t\t\t?.toString(\"utf8\")\n\t\t\t.trim();\n\t\tif (!winPath) return false;\n\t\tconst script = `Set-Clipboard -Value ([System.IO.File]::ReadAllText('${winPath.replaceAll(\"'\", \"''\")}', [System.Text.Encoding]::UTF8))`;\n\t\tconst result = await runClipboardCommand(\"powershell.exe\", [\"-NoProfile\", \"-Command\", script], {\n\t\t\ttimeoutMs: 5000,\n\t\t});\n\t\treturn result !== undefined;\n\t} catch {\n\t\treturn false;\n\t} finally {\n\t\ttry {\n\t\t\tunlinkSync(tmpFile);\n\t\t} catch {\n\t\t\t// The file may not have been created.\n\t\t}\n\t}\n}\n\n/** Read plain text from the system clipboard. */\nexport async function readClipboardText(): Promise<string | null> {\n\tif (platform() === \"linux\") {\n\t\tconst commands: [string, string[]][] = [];\n\t\tif (process.env.TERMUX_VERSION) commands.push([\"termux-clipboard-get\", []]);\n\t\tif (process.env.WAYLAND_DISPLAY) commands.push([\"wl-paste\", [\"--no-newline\", \"--type\", \"text\"]]);\n\t\tif (process.env.DISPLAY) {\n\t\t\tcommands.push([\"xclip\", [\"-selection\", \"clipboard\", \"-out\"]], [\"xsel\", [\"--clipboard\", \"--output\"]]);\n\t\t}\n\t\tfor (const [command, args] of commands) {\n\t\t\tconst bytes = await runClipboardCommand(command, args, { timeoutMs: 5000 });\n\t\t\tif (bytes !== undefined) return bytes.toString(\"utf8\") || null;\n\t\t}\n\t}\n\ttry {\n\t\treturn (await getNativeClipboard()?.getText()) || null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nexport async function copyToClipboard(text: string): Promise<void> {\n\tconst p = platform();\n\tconst env = process.env;\n\tlet copied = false;\n\t// Direct writes precede OSC 52 so the terminal cannot race the native writer.\n\t// Linux tools retain clipboard selection ownership after this call returns.\n\tif (p !== \"linux\") {\n\t\ttry {\n\t\t\tconst clipboard = getNativeClipboard();\n\t\t\tif (clipboard?.setText) {\n\t\t\t\tawait clipboard.setText(text);\n\t\t\t\tcopied = true;\n\t\t\t}\n\t\t} catch {\n\t\t\t// Try platform commands next.\n\t\t}\n\t}\n\tif (!copied) {\n\t\tconst commands: [string, string[]][] = [];\n\t\tif (p === \"darwin\") commands.push([\"pbcopy\", []]);\n\t\telse if (p === \"win32\") commands.push([\"clip\", []]);\n\t\telse {\n\t\t\tif (env.TERMUX_VERSION) commands.push([\"termux-clipboard-set\", []]);\n\t\t\tif (env.WAYLAND_DISPLAY) commands.push([\"wl-copy\", []]);\n\t\t\tif (env.DISPLAY) {\n\t\t\t\tcommands.push([\"xclip\", [\"-selection\", \"clipboard\"]], [\"xsel\", [\"--clipboard\", \"--input\"]]);\n\t\t\t}\n\t\t}\n\t\tfor (const [command, args] of commands) {\n\t\t\tif ((await runClipboardCommand(command, args, { input: text, timeoutMs: 5000 })) !== undefined) {\n\t\t\t\tcopied = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\tlet osc52Emitted = false;\n\tif (!copied && p === \"linux\" && isWSL(env)) {\n\t\t// Windows Terminal supports OSC 52; prefer it over the slower PowerShell round trip.\n\t\tif (env.WT_SESSION) osc52Emitted = emitOsc52(text);\n\t\tcopied = osc52Emitted || (await copyViaWindowsClipboard(text));\n\t}\n\t// OSC 52 cannot be verified, so a desktop session with a display reports the failure\n\t// instead (#9618). Without a display the terminal is the only clipboard route (containers,\n\t// WSL without WSLg), and remote sessions always emit it to reach the client clipboard.\n\tconst headless = p === \"linux\" && !env.DISPLAY && !env.WAYLAND_DISPLAY && !env.TERMUX_VERSION;\n\tlet oversized = false;\n\tif (!osc52Emitted && (isRemoteSession(env) || (!copied && headless))) {\n\t\tif (emitOsc52(text)) copied = true;\n\t\telse oversized = true;\n\t}\n\tif (copied) return;\n\tif (oversized) throw new Error(\"Clipboard unavailable: text exceeds the OSC 52 size limit\");\n\tif (p === \"linux\") {\n\t\tif (env.TERMUX_VERSION) {\n\t\t\tthrow new Error(\"Clipboard unavailable: install the Termux:API app and `termux-api` package\");\n\t\t}\n\t\tif (env.WAYLAND_DISPLAY) {\n\t\t\tthrow new Error(\"Clipboard unavailable: install `wl-clipboard` (`wl-copy`) or check Wayland access\");\n\t\t}\n\t\tif (env.DISPLAY) {\n\t\t\tthrow new Error(\"Clipboard unavailable: install `xclip` or `xsel`, or check X11 access\");\n\t\t}\n\t}\n\tthrow new Error(\"Clipboard unavailable\");\n}\n"]}
@@ -8,7 +8,7 @@ export function detectSupportedImageMimeType(buffer) {
8
8
  if (startsWith(buffer, PNG_SIGNATURE)) {
9
9
  return isPng(buffer) && !isAnimatedPng(buffer) ? "image/png" : null;
10
10
  }
11
- if (startsWithAscii(buffer, 0, "GIF")) {
11
+ if (startsWithAscii(buffer, 0, "GIF87a") || startsWithAscii(buffer, 0, "GIF89a")) {
12
12
  return "image/gif";
13
13
  }
14
14
  if (startsWithAscii(buffer, 0, "RIFF") && startsWithAscii(buffer, 8, "WEBP")) {
@@ -1 +1 @@
1
- {"version":3,"file":"mime.js","sourceRoot":"","sources":["../../src/utils/mime.ts"],"names":[],"mappings":"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","sourcesContent":["import { open } from \"node:fs/promises\";\n\nconst IMAGE_TYPE_SNIFF_BYTES = 4100;\nconst PNG_SIGNATURE = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];\n\nexport function detectSupportedImageMimeType(buffer: Uint8Array): string | null {\n\tif (startsWith(buffer, [0xff, 0xd8, 0xff])) {\n\t\treturn buffer[3] === 0xf7 ? null : \"image/jpeg\";\n\t}\n\tif (startsWith(buffer, PNG_SIGNATURE)) {\n\t\treturn isPng(buffer) && !isAnimatedPng(buffer) ? \"image/png\" : null;\n\t}\n\tif (startsWithAscii(buffer, 0, \"GIF\")) {\n\t\treturn \"image/gif\";\n\t}\n\tif (startsWithAscii(buffer, 0, \"RIFF\") && startsWithAscii(buffer, 8, \"WEBP\")) {\n\t\treturn \"image/webp\";\n\t}\n\tif (startsWithAscii(buffer, 0, \"BM\") && isBmp(buffer)) {\n\t\treturn \"image/bmp\";\n\t}\n\treturn null;\n}\n\nexport async function detectSupportedImageMimeTypeFromFile(filePath: string): Promise<string | null> {\n\tconst fileHandle = await open(filePath, \"r\");\n\ttry {\n\t\tconst buffer = Buffer.alloc(IMAGE_TYPE_SNIFF_BYTES);\n\t\tconst { bytesRead } = await fileHandle.read(buffer, 0, IMAGE_TYPE_SNIFF_BYTES, 0);\n\t\treturn detectSupportedImageMimeType(buffer.subarray(0, bytesRead));\n\t} finally {\n\t\tawait fileHandle.close();\n\t}\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\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\nfunction isBmp(buffer: Uint8Array): boolean {\n\tif (buffer.length < 26) return false;\n\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\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 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"]}
1
+ {"version":3,"file":"mime.js","sourceRoot":"","sources":["../../src/utils/mime.ts"],"names":[],"mappings":"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","sourcesContent":["import { open } from \"node:fs/promises\";\n\nconst IMAGE_TYPE_SNIFF_BYTES = 4100;\nconst PNG_SIGNATURE = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];\n\nexport function detectSupportedImageMimeType(buffer: Uint8Array): string | null {\n\tif (startsWith(buffer, [0xff, 0xd8, 0xff])) {\n\t\treturn buffer[3] === 0xf7 ? null : \"image/jpeg\";\n\t}\n\tif (startsWith(buffer, PNG_SIGNATURE)) {\n\t\treturn isPng(buffer) && !isAnimatedPng(buffer) ? \"image/png\" : null;\n\t}\n\tif (startsWithAscii(buffer, 0, \"GIF87a\") || startsWithAscii(buffer, 0, \"GIF89a\")) {\n\t\treturn \"image/gif\";\n\t}\n\tif (startsWithAscii(buffer, 0, \"RIFF\") && startsWithAscii(buffer, 8, \"WEBP\")) {\n\t\treturn \"image/webp\";\n\t}\n\tif (startsWithAscii(buffer, 0, \"BM\") && isBmp(buffer)) {\n\t\treturn \"image/bmp\";\n\t}\n\treturn null;\n}\n\nexport async function detectSupportedImageMimeTypeFromFile(filePath: string): Promise<string | null> {\n\tconst fileHandle = await open(filePath, \"r\");\n\ttry {\n\t\tconst buffer = Buffer.alloc(IMAGE_TYPE_SNIFF_BYTES);\n\t\tconst { bytesRead } = await fileHandle.read(buffer, 0, IMAGE_TYPE_SNIFF_BYTES, 0);\n\t\treturn detectSupportedImageMimeType(buffer.subarray(0, bytesRead));\n\t} finally {\n\t\tawait fileHandle.close();\n\t}\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\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\nfunction isBmp(buffer: Uint8Array): boolean {\n\tif (buffer.length < 26) return false;\n\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\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 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"]}
@@ -1,8 +1,10 @@
1
- import type { ImageContent, TextContent } from "@earendil-works/pi-ai";
1
+ import type { ImageContent, ModelImageResizeOptions, TextContent } from "@earendil-works/pi-ai";
2
2
  export type ToolResultContent = TextContent | ImageContent;
3
3
  export interface NormalizeToolResultImagesOptions {
4
4
  /** Whether oversized images are resized to inline provider limits. Default: true */
5
5
  autoResizeImages?: boolean;
6
+ /** Model-specific resize profile. Uses the conservative built-in defaults when omitted. */
7
+ resizeOptions?: ModelImageResizeOptions;
6
8
  }
7
9
  /**
8
10
  * Normalize image blocks returned by tool results.
@@ -1 +1 @@
1
- {"version":3,"file":"tool-result-images.d.ts","sourceRoot":"","sources":["../../src/utils/tool-result-images.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAGvE,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG,YAAY,CAAC;AAE3D,MAAM,WAAW,gCAAgC;IAChD,oFAAoF;IACpF,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,yBAAyB,CAC9C,OAAO,EAAE,iBAAiB,EAAE,EAC5B,OAAO,CAAC,EAAE,gCAAgC,GACxC,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAqC9B"}
1
+ {"version":3,"file":"tool-result-images.d.ts","sourceRoot":"","sources":["../../src/utils/tool-result-images.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,uBAAuB,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAGhG,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG,YAAY,CAAC;AAE3D,MAAM,WAAW,gCAAgC;IAChD,oFAAoF;IACpF,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,2FAA2F;IAC3F,aAAa,CAAC,EAAE,uBAAuB,CAAC;CACxC;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,yBAAyB,CAC9C,OAAO,EAAE,iBAAiB,EAAE,EAC5B,OAAO,CAAC,EAAE,gCAAgC,GACxC,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAwC9B"}
@@ -22,7 +22,10 @@ export async function normalizeToolResultImages(content, options) {
22
22
  normalized.push(block);
23
23
  continue;
24
24
  }
25
- const processed = await processImage(Buffer.from(block.data, "base64"), block.mimeType, { autoResizeImages });
25
+ const processed = await processImage(Buffer.from(block.data, "base64"), block.mimeType, {
26
+ autoResizeImages,
27
+ resizeOptions: options?.resizeOptions,
28
+ });
26
29
  if (!processed.ok) {
27
30
  // Unlike `read`, keep the original block. The tool already produced this image and the
28
31
  // failure may just be an unavailable image backend, so passing it through preserves the
@@ -1 +1 @@
1
- {"version":3,"file":"tool-result-images.js","sourceRoot":"","sources":["../../src/utils/tool-result-images.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AASlD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC9C,OAA4B,EAC5B,OAA0C;IAE1C,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC,EAAE,CAAC;QACtD,OAAO,OAAO,CAAC;IAChB,CAAC;IAED,MAAM,gBAAgB,GAAG,OAAO,EAAE,gBAAgB,IAAI,IAAI,CAAC;IAC3D,MAAM,UAAU,GAAwB,EAAE,CAAC;IAC3C,IAAI,OAAO,GAAG,KAAK,CAAC;IAEpB,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC5B,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACvB,SAAS;QACV,CAAC;QAED,MAAM,SAAS,GAAG,MAAM,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,KAAK,CAAC,QAAQ,EAAE,EAAE,gBAAgB,EAAE,CAAC,CAAC;QAC9G,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,CAAC;YACnB,uFAAuF;YACvF,wFAAwF;YACxF,uEAAuE;YACvE,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACvB,SAAS;QACV,CAAC;QAED,IAAI,SAAS,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI,IAAI,SAAS,CAAC,QAAQ,KAAK,KAAK,CAAC,QAAQ,IAAI,SAAS,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5G,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACvB,SAAS;QACV,CAAC;QAED,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,SAAS,CAAC,QAAQ,EAAE,CAAC,CAAC;QACvF,IAAI,SAAS,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAChC,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACrE,CAAC;QACD,OAAO,GAAG,IAAI,CAAC;IAChB,CAAC;IAED,OAAO,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC;AACvC,CAAC","sourcesContent":["import type { ImageContent, TextContent } from \"@earendil-works/pi-ai\";\nimport { processImage } from \"./image-process.ts\";\n\nexport type ToolResultContent = TextContent | ImageContent;\n\nexport interface NormalizeToolResultImagesOptions {\n\t/** Whether oversized images are resized to inline provider limits. Default: true */\n\tautoResizeImages?: boolean;\n}\n\n/**\n * Normalize image blocks returned by tool results.\n *\n * The `read` tool and `@file` CLI attachments run their images through `processImage`, but tools\n * that produce images themselves (extensions, MCP bridges, screenshot tools) hand back arbitrary\n * base64 payloads that go straight into session history and every subsequent provider request.\n * Oversized images make the provider reject the whole conversation, not just the offending turn,\n * so normalize them once as they enter history.\n *\n * Returns the original array when nothing changed so callers can skip rewriting the result.\n */\nexport async function normalizeToolResultImages(\n\tcontent: ToolResultContent[],\n\toptions?: NormalizeToolResultImagesOptions,\n): Promise<ToolResultContent[]> {\n\tif (!content.some((block) => block.type === \"image\")) {\n\t\treturn content;\n\t}\n\n\tconst autoResizeImages = options?.autoResizeImages ?? true;\n\tconst normalized: ToolResultContent[] = [];\n\tlet changed = false;\n\n\tfor (const block of content) {\n\t\tif (block.type !== \"image\") {\n\t\t\tnormalized.push(block);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst processed = await processImage(Buffer.from(block.data, \"base64\"), block.mimeType, { autoResizeImages });\n\t\tif (!processed.ok) {\n\t\t\t// Unlike `read`, keep the original block. The tool already produced this image and the\n\t\t\t// failure may just be an unavailable image backend, so passing it through preserves the\n\t\t\t// behavior tools have today instead of silently deleting their output.\n\t\t\tnormalized.push(block);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (processed.data === block.data && processed.mimeType === block.mimeType && processed.hints.length === 0) {\n\t\t\tnormalized.push(block);\n\t\t\tcontinue;\n\t\t}\n\n\t\tnormalized.push({ type: \"image\", data: processed.data, mimeType: processed.mimeType });\n\t\tif (processed.hints.length > 0) {\n\t\t\tnormalized.push({ type: \"text\", text: processed.hints.join(\"\\n\") });\n\t\t}\n\t\tchanged = true;\n\t}\n\n\treturn changed ? normalized : content;\n}\n"]}
1
+ {"version":3,"file":"tool-result-images.js","sourceRoot":"","sources":["../../src/utils/tool-result-images.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAWlD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC9C,OAA4B,EAC5B,OAA0C;IAE1C,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC,EAAE,CAAC;QACtD,OAAO,OAAO,CAAC;IAChB,CAAC;IAED,MAAM,gBAAgB,GAAG,OAAO,EAAE,gBAAgB,IAAI,IAAI,CAAC;IAC3D,MAAM,UAAU,GAAwB,EAAE,CAAC;IAC3C,IAAI,OAAO,GAAG,KAAK,CAAC;IAEpB,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC5B,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACvB,SAAS;QACV,CAAC;QAED,MAAM,SAAS,GAAG,MAAM,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,KAAK,CAAC,QAAQ,EAAE;YACvF,gBAAgB;YAChB,aAAa,EAAE,OAAO,EAAE,aAAa;SACrC,CAAC,CAAC;QACH,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,CAAC;YACnB,uFAAuF;YACvF,wFAAwF;YACxF,uEAAuE;YACvE,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACvB,SAAS;QACV,CAAC;QAED,IAAI,SAAS,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI,IAAI,SAAS,CAAC,QAAQ,KAAK,KAAK,CAAC,QAAQ,IAAI,SAAS,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5G,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACvB,SAAS;QACV,CAAC;QAED,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,SAAS,CAAC,QAAQ,EAAE,CAAC,CAAC;QACvF,IAAI,SAAS,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAChC,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACrE,CAAC;QACD,OAAO,GAAG,IAAI,CAAC;IAChB,CAAC;IAED,OAAO,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC;AACvC,CAAC","sourcesContent":["import type { ImageContent, ModelImageResizeOptions, TextContent } from \"@earendil-works/pi-ai\";\nimport { processImage } from \"./image-process.ts\";\n\nexport type ToolResultContent = TextContent | ImageContent;\n\nexport interface NormalizeToolResultImagesOptions {\n\t/** Whether oversized images are resized to inline provider limits. Default: true */\n\tautoResizeImages?: boolean;\n\t/** Model-specific resize profile. Uses the conservative built-in defaults when omitted. */\n\tresizeOptions?: ModelImageResizeOptions;\n}\n\n/**\n * Normalize image blocks returned by tool results.\n *\n * The `read` tool and `@file` CLI attachments run their images through `processImage`, but tools\n * that produce images themselves (extensions, MCP bridges, screenshot tools) hand back arbitrary\n * base64 payloads that go straight into session history and every subsequent provider request.\n * Oversized images make the provider reject the whole conversation, not just the offending turn,\n * so normalize them once as they enter history.\n *\n * Returns the original array when nothing changed so callers can skip rewriting the result.\n */\nexport async function normalizeToolResultImages(\n\tcontent: ToolResultContent[],\n\toptions?: NormalizeToolResultImagesOptions,\n): Promise<ToolResultContent[]> {\n\tif (!content.some((block) => block.type === \"image\")) {\n\t\treturn content;\n\t}\n\n\tconst autoResizeImages = options?.autoResizeImages ?? true;\n\tconst normalized: ToolResultContent[] = [];\n\tlet changed = false;\n\n\tfor (const block of content) {\n\t\tif (block.type !== \"image\") {\n\t\t\tnormalized.push(block);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst processed = await processImage(Buffer.from(block.data, \"base64\"), block.mimeType, {\n\t\t\tautoResizeImages,\n\t\t\tresizeOptions: options?.resizeOptions,\n\t\t});\n\t\tif (!processed.ok) {\n\t\t\t// Unlike `read`, keep the original block. The tool already produced this image and the\n\t\t\t// failure may just be an unavailable image backend, so passing it through preserves the\n\t\t\t// behavior tools have today instead of silently deleting their output.\n\t\t\tnormalized.push(block);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (processed.data === block.data && processed.mimeType === block.mimeType && processed.hints.length === 0) {\n\t\t\tnormalized.push(block);\n\t\t\tcontinue;\n\t\t}\n\n\t\tnormalized.push({ type: \"image\", data: processed.data, mimeType: processed.mimeType });\n\t\tif (processed.hints.length > 0) {\n\t\t\tnormalized.push({ type: \"text\", text: processed.hints.join(\"\\n\") });\n\t\t}\n\t\tchanged = true;\n\t}\n\n\treturn changed ? normalized : content;\n}\n"]}
@@ -0,0 +1,3 @@
1
+ /** Windows Subsystem for Linux, where Windows executables are reachable through interop. */
2
+ export declare function isWSL(env?: NodeJS.ProcessEnv): boolean;
3
+ //# sourceMappingURL=wsl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wsl.d.ts","sourceRoot":"","sources":["../../src/utils/wsl.ts"],"names":[],"mappings":"AAEA,4FAA4F;AAC5F,wBAAgB,KAAK,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAWnE"}
@@ -0,0 +1,15 @@
1
+ import { readFileSync } from "node:fs";
2
+ /** Windows Subsystem for Linux, where Windows executables are reachable through interop. */
3
+ export function isWSL(env = process.env) {
4
+ if (env.WSL_DISTRO_NAME || env.WSLENV) {
5
+ return true;
6
+ }
7
+ try {
8
+ const release = readFileSync("/proc/version", "utf-8");
9
+ return /microsoft|wsl/i.test(release);
10
+ }
11
+ catch {
12
+ return false;
13
+ }
14
+ }
15
+ //# sourceMappingURL=wsl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wsl.js","sourceRoot":"","sources":["../../src/utils/wsl.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,4FAA4F;AAC5F,MAAM,UAAU,KAAK,CAAC,MAAyB,OAAO,CAAC,GAAG;IACzD,IAAI,GAAG,CAAC,eAAe,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;QACvC,OAAO,IAAI,CAAC;IACb,CAAC;IAED,IAAI,CAAC;QACJ,MAAM,OAAO,GAAG,YAAY,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;QACvD,OAAO,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,KAAK,CAAC;IACd,CAAC;AACF,CAAC","sourcesContent":["import { readFileSync } from \"node:fs\";\n\n/** Windows Subsystem for Linux, where Windows executables are reachable through interop. */\nexport function isWSL(env: NodeJS.ProcessEnv = process.env): boolean {\n\tif (env.WSL_DISTRO_NAME || env.WSLENV) {\n\t\treturn true;\n\t}\n\n\ttry {\n\t\tconst release = readFileSync(\"/proc/version\", \"utf-8\");\n\t\treturn /microsoft|wsl/i.test(release);\n\t} catch {\n\t\treturn false;\n\t}\n}\n"]}
@@ -33,7 +33,9 @@ contextTokens > contextWindow − reserveTokens
33
33
 
34
34
  > **Distribution note:** upstream Pi's default is a fixed 16384-token reserve, which on large windows delays compaction until ~98% usage. This distribution derives the reserve from `triggerPercent` instead. Setting `compaction.reserveTokens` explicitly — globally or per model — still wins and restores upstream's fixed-reserve behavior for that model.
35
35
 
36
- During a multi-turn agent run, Pi checks this threshold after tools finish and their results are appended, before starting the next assistant response. If the threshold is crossed, Pi compacts inside the same agent run and resumes with the summary and retained messages. It skips this between-turn check when the completed tool batch terminates the run and no queued message requires another response. Pi also checks the threshold before a new user prompt and after a low-level agent run ends.
36
+ During a multi-turn agent run, Pi checks this threshold against the canonical session projection after tools finish and their results are appended, before starting the next assistant response. If the threshold is crossed, Pi compacts during `prepareNextTurn`, then performs the existing catch-up steering poll before `turn_start`, and resumes with the summary and retained messages. It skips this between-turn check when the completed tool batch terminates the run and no queued message requires another response. Pi also checks the threshold before a new user prompt and performs final-attempt overflow recovery after a low-level agent run ends.
37
+
38
+ A provider context-overflow error or an early final `stopReason: "length"` can select one compact-and-retry recovery attempt. Length responses with tool calls retain their synthetic failed tool results and follow the ordinary tool/queue scheduler rather than forcing the run to end.
37
39
 
38
40
  This between-turn trigger is separate from **split-turn compaction** below: the trigger determines *when* compaction runs, while split-turn support determines *where* a very long turn can be cut.
39
41
 
@@ -41,8 +43,8 @@ You can also trigger manually with `/compact [instructions]`, where optional ins
41
43
 
42
44
  ### How It Works
43
45
 
44
- 1. **Find cut point**: Walk backwards from newest message until the effective retention target is reached: `keepRecentTokens` (default 20k), capped at half the trigger line when the reserve comes from `triggerPercent`
45
- 2. **Extract messages**: Collect messages from the previous kept boundary (or session start) up to the cut point
46
+ 1. **Find cut point**: Walk backwards through the finalized session projection until the effective retention target is reached: `keepRecentTokens` (default 20k), capped at half the trigger line when the reserve comes from `triggerPercent`
47
+ 2. **Extract messages**: Collect projected messages from the previous kept boundary (or session start) up to the cut point
46
48
  3. **Generate summary**: Call LLM to summarize with structured format, passing the previous summary as iterative context when present
47
49
  4. **Append entry**: Save `CompactionEntry` with summary and `firstKeptEntryId`
48
50
  5. **Rebuilds context**: Session rebuilds the context for the next request, using summary + messages from `firstKeptEntryId` onwards
@@ -83,7 +85,22 @@ What the LLM sees:
83
85
  prompt from cmp messages from firstKeptEntryId
84
86
  ```
85
87
 
86
- On repeated compactions, the summarized span starts at the previous compaction's kept boundary (`firstKeptEntryId`), not at the compaction entry itself, falling back to the entry after the previous compaction if that kept entry cannot be found in the path. This preserves messages that survived the earlier compaction by including them in the next summarization pass as well. Pi also recalculates `tokensBefore` from the rebuilt session context before writing the new `CompactionEntry`, so the token count reflects the actual pre-compaction context being replaced.
88
+ On repeated compactions, the summarized span starts at the previous compaction's kept boundary (`firstKeptEntryId`), not at the compaction entry itself, falling back to the entry after the previous compaction if that kept entry cannot be found in the path. A retain-none compaction records its own ID as `firstKeptEntryId`; repeated compaction starts after that entry. This preserves messages that survived the earlier compaction by including them in the next summarization pass as well. Pi also recalculates `tokensBefore` from the rebuilt, context-edited session projection before writing the new `CompactionEntry`, so the token count reflects the actual pre-compaction context being replaced. Omitted raw entries remain stored but do not affect cut selection, summaries, checkpoints, or token estimates.
89
+
90
+ ### Overflow and Length Recovery Ordering
91
+
92
+ Recovery preserves the existing lifecycle and queue order. The completed attempt remains visible to `turn_end` and `agent_end`; post-run recovery then repairs persisted model context before a fresh retry:
93
+
94
+ ```text
95
+ persist final assistant response
96
+ → extension/public turn_end
97
+ → extension/public agent_end
98
+ → append context_edit omissions for the selected attempt
99
+ → for overflow/length: run session_before_compact and append compaction on success
100
+ → start the retry as a fresh run
101
+ ```
102
+
103
+ If recovery compaction fails or is cancelled, Pi keeps the omission edits, appends no compaction, and schedules no internal retry. Existing queued work remains governed by ordinary steering and follow-up rules. `agent_before_settle` sees the repaired projection after recovery processing. Raw transcript history, exports, billing totals, and history-search extensions can still inspect the omitted attempt.
87
104
 
88
105
  ### Split Turns
89
106
 
@@ -125,6 +142,8 @@ Never cut at tool results (they must stay with their tool call).
125
142
 
126
143
  Because tool results are not valid cut points, a batch of trailing tool results can exceed `keepRecentTokens` on its own, leaving no cut point at or after the entry that crosses the budget. The cut then lands on the last valid cut point — the least the message structure allows Pi to keep. This matters most for sessions with very few cut points, such as a subagent worker, whose whole session is a single turn: one prompt followed by assistant and tool result entries only.
127
144
 
145
+ Preparation advances the kept boundary into a context-invisible suffix only when that suffix contains an omitted assistant attempt and no unomitted context-producing entries. Recovery `context_edit` omissions satisfy this rule; intrinsically context-invisible metadata may coexist with them. Metadata alone and newly appended custom messages do not move the cut. A replacement edit affecting the candidate input or summarized prefix also blocks advancement because the omitted assistant answered the pre-edit input; replacements of suffix entries that are ultimately omitted remain safe. This allows an over-budget recovered input to be summarized while retaining the edits that keep the abandoned attempt omitted, without making bookkeeping change whether new model input is preserved verbatim.
146
+
128
147
  ### CompactionEntry Structure
129
148
 
130
149
  The public `CompactionEntry` type is documented in [Session Format](session-format.md):