@psnext/slingcli 2.4.20260525-4 → 2.4.20260527-2

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 (243) hide show
  1. package/bin/sling.js +4 -1
  2. package/node_modules/@aws-sdk/core/package.json +2 -2
  3. package/node_modules/@aws-sdk/credential-provider-env/package.json +2 -2
  4. package/node_modules/@aws-sdk/credential-provider-http/dist-cjs/fromHttp/fromHttp.js +12 -6
  5. package/node_modules/@aws-sdk/credential-provider-http/dist-es/fromHttp/fromHttp.js +12 -6
  6. package/node_modules/@aws-sdk/credential-provider-http/package.json +3 -2
  7. package/node_modules/@aws-sdk/credential-provider-ini/package.json +9 -9
  8. package/node_modules/@aws-sdk/credential-provider-login/package.json +3 -3
  9. package/node_modules/@aws-sdk/credential-provider-node/package.json +7 -7
  10. package/node_modules/@aws-sdk/credential-provider-process/package.json +2 -2
  11. package/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers/package.json +3 -3
  12. package/node_modules/@aws-sdk/credential-provider-sso/package.json +4 -4
  13. package/node_modules/@aws-sdk/credential-provider-web-identity/package.json +3 -3
  14. package/node_modules/@aws-sdk/middleware-websocket/package.json +2 -2
  15. package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/cognito-identity/index.js +1 -1
  16. package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/signin/index.js +1 -1
  17. package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/sso/index.js +1 -1
  18. package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/sso-oidc/index.js +1 -1
  19. package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/sts/index.js +1 -1
  20. package/node_modules/@aws-sdk/nested-clients/package.json +3 -3
  21. package/node_modules/@aws-sdk/signature-v4-multi-region/package.json +1 -2
  22. package/node_modules/@aws-sdk/xml-builder/package.json +2 -2
  23. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js +18 -24
  24. package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
  25. package/node_modules/@earendil-works/pi-ai/dist/cli.js +14 -0
  26. package/node_modules/@earendil-works/pi-ai/dist/models.generated.js +78 -63
  27. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.js +2 -1
  28. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js +8 -22
  29. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.js +1 -1
  30. package/node_modules/@earendil-works/pi-ai/dist/providers/images/openrouter.js +1 -1
  31. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex-responses.js +60 -40
  32. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.js +1 -1
  33. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.js +1 -1
  34. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/device-code.js +55 -0
  35. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/github-copilot.js +45 -69
  36. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/index.js +1 -0
  37. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js +5 -2
  38. package/node_modules/@earendil-works/pi-ai/package.json +4 -3
  39. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/file-processor.js +2 -3
  40. package/node_modules/@earendil-works/pi-coding-agent/dist/config.js +18 -12
  41. package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session-runtime.js +2 -1
  42. package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session-services.js +3 -2
  43. package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js +8 -2
  44. package/node_modules/@earendil-works/pi-coding-agent/dist/core/auth-storage.js +2 -1
  45. package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/compaction.js +18 -24
  46. package/node_modules/@earendil-works/pi-coding-agent/dist/core/export-html/index.js +8 -6
  47. package/node_modules/@earendil-works/pi-coding-agent/dist/core/export-html/template.js +6 -3
  48. package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/loader.js +12 -29
  49. package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-registry.js +5 -1
  50. package/node_modules/@earendil-works/pi-coding-agent/dist/core/output-guard.js +52 -22
  51. package/node_modules/@earendil-works/pi-coding-agent/dist/core/package-manager.js +63 -36
  52. package/node_modules/@earendil-works/pi-coding-agent/dist/core/prompt-templates.js +6 -20
  53. package/node_modules/@earendil-works/pi-coding-agent/dist/core/resource-loader.js +38 -31
  54. package/node_modules/@earendil-works/pi-coding-agent/dist/core/sdk.js +9 -4
  55. package/node_modules/@earendil-works/pi-coding-agent/dist/core/session-manager.js +32 -24
  56. package/node_modules/@earendil-works/pi-coding-agent/dist/core/settings-manager.js +6 -13
  57. package/node_modules/@earendil-works/pi-coding-agent/dist/core/skills.js +8 -22
  58. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/bash.js +54 -53
  59. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/edit-diff.js +8 -1
  60. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/edit.js +44 -81
  61. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/file-mutation-queue.js +27 -12
  62. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/find.js +2 -3
  63. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/grep.js +3 -3
  64. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/ls.js +5 -5
  65. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/output-accumulator.js +9 -3
  66. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/path-utils.js +39 -21
  67. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/read.js +9 -8
  68. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/truncate.js +12 -2
  69. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/write.js +20 -35
  70. package/node_modules/@earendil-works/pi-coding-agent/dist/main.js +5 -6
  71. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/config-selector.js +1 -1
  72. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/footer.js +14 -5
  73. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/login-dialog.js +29 -4
  74. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/user-message.js +1 -1
  75. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js +4 -4
  76. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/theme/theme.js +37 -28
  77. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-client.js +64 -7
  78. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-mode.js +15 -3
  79. package/node_modules/@earendil-works/pi-coding-agent/dist/utils/clipboard-native.js +14 -8
  80. package/node_modules/@earendil-works/pi-coding-agent/dist/utils/image-resize-core.js +124 -0
  81. package/node_modules/@earendil-works/pi-coding-agent/dist/utils/image-resize-worker.js +31 -0
  82. package/node_modules/@earendil-works/pi-coding-agent/dist/utils/image-resize.js +75 -115
  83. package/node_modules/@earendil-works/pi-coding-agent/dist/utils/paths.js +33 -7
  84. package/node_modules/@earendil-works/pi-coding-agent/npm-shrinkwrap.json +13 -26
  85. package/node_modules/@earendil-works/pi-coding-agent/package.json +6 -6
  86. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +40 -55
  87. package/node_modules/@earendil-works/pi-tui/dist/components/input.js +2 -2
  88. package/node_modules/@earendil-works/pi-tui/dist/components/markdown.js +12 -2
  89. package/node_modules/@earendil-works/pi-tui/dist/native-modifiers.js +53 -0
  90. package/node_modules/@earendil-works/pi-tui/dist/terminal-image.js +4 -1
  91. package/node_modules/@earendil-works/pi-tui/dist/terminal.js +38 -16
  92. package/node_modules/@earendil-works/pi-tui/dist/utils.js +19 -12
  93. package/node_modules/@earendil-works/pi-tui/native/darwin/prebuilds/darwin-arm64/darwin-modifiers.node +0 -0
  94. package/node_modules/@earendil-works/pi-tui/native/darwin/prebuilds/darwin-x64/darwin-modifiers.node +0 -0
  95. package/node_modules/@earendil-works/pi-tui/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
  96. package/node_modules/@earendil-works/pi-tui/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
  97. package/node_modules/@earendil-works/pi-tui/package.json +3 -5
  98. package/node_modules/@smithy/node-http-handler/package.json +2 -2
  99. package/node_modules/undici/lib/api/api-request.js +1 -1
  100. package/node_modules/undici/lib/cache/sqlite-cache-store.js +3 -3
  101. package/node_modules/undici/lib/core/connect.js +16 -0
  102. package/node_modules/undici/lib/core/request.js +17 -2
  103. package/node_modules/undici/lib/core/socks5-client.js +10 -5
  104. package/node_modules/undici/lib/core/socks5-utils.js +17 -22
  105. package/node_modules/undici/lib/dispatcher/client-h1.js +64 -20
  106. package/node_modules/undici/lib/dispatcher/client.js +6 -2
  107. package/node_modules/undici/lib/dispatcher/h2c-client.js +1 -1
  108. package/node_modules/undici/lib/mock/mock-call-history.js +15 -15
  109. package/node_modules/undici/lib/util/cache.js +8 -7
  110. package/node_modules/undici/lib/web/fetch/formdata-parser.js +17 -6
  111. package/node_modules/undici/lib/web/fetch/index.js +5 -2
  112. package/node_modules/undici/lib/web/webidl/index.js +5 -5
  113. package/node_modules/undici/lib/web/websocket/stream/websocketstream.js +1 -7
  114. package/node_modules/undici/package.json +1 -1
  115. package/package.json +6 -6
  116. package/slingshot/index.js +269 -266
  117. package/node_modules/koffi/CHANGELOG.md +0 -1093
  118. package/node_modules/koffi/LICENSE.txt +0 -22
  119. package/node_modules/koffi/README.md +0 -43
  120. package/node_modules/koffi/build/koffi/darwin_arm64/koffi.node +0 -0
  121. package/node_modules/koffi/build/koffi/darwin_x64/koffi.node +0 -0
  122. package/node_modules/koffi/build/koffi/freebsd_arm64/koffi.node +0 -0
  123. package/node_modules/koffi/build/koffi/freebsd_ia32/koffi.node +0 -0
  124. package/node_modules/koffi/build/koffi/freebsd_x64/koffi.node +0 -0
  125. package/node_modules/koffi/build/koffi/linux_arm64/koffi.node +0 -0
  126. package/node_modules/koffi/build/koffi/linux_armhf/koffi.node +0 -0
  127. package/node_modules/koffi/build/koffi/linux_ia32/koffi.node +0 -0
  128. package/node_modules/koffi/build/koffi/linux_loong64/koffi.node +0 -0
  129. package/node_modules/koffi/build/koffi/linux_riscv64d/koffi.node +0 -0
  130. package/node_modules/koffi/build/koffi/linux_x64/koffi.node +0 -0
  131. package/node_modules/koffi/build/koffi/musl_arm64/koffi.node +0 -0
  132. package/node_modules/koffi/build/koffi/musl_x64/koffi.node +0 -0
  133. package/node_modules/koffi/build/koffi/openbsd_ia32/koffi.node +0 -0
  134. package/node_modules/koffi/build/koffi/openbsd_x64/koffi.node +0 -0
  135. package/node_modules/koffi/build/koffi/win32_arm64/koffi.exp +0 -0
  136. package/node_modules/koffi/build/koffi/win32_arm64/koffi.lib +0 -0
  137. package/node_modules/koffi/build/koffi/win32_arm64/koffi.node +0 -0
  138. package/node_modules/koffi/build/koffi/win32_ia32/koffi.exp +0 -0
  139. package/node_modules/koffi/build/koffi/win32_ia32/koffi.lib +0 -0
  140. package/node_modules/koffi/build/koffi/win32_ia32/koffi.node +0 -0
  141. package/node_modules/koffi/build/koffi/win32_x64/koffi.exp +0 -0
  142. package/node_modules/koffi/build/koffi/win32_x64/koffi.lib +0 -0
  143. package/node_modules/koffi/build/koffi/win32_x64/koffi.node +0 -0
  144. package/node_modules/koffi/doc/benchmarks.md +0 -126
  145. package/node_modules/koffi/doc/callbacks.md +0 -210
  146. package/node_modules/koffi/doc/contribute.md +0 -148
  147. package/node_modules/koffi/doc/functions.md +0 -250
  148. package/node_modules/koffi/doc/index.md +0 -61
  149. package/node_modules/koffi/doc/input.md +0 -471
  150. package/node_modules/koffi/doc/migration.md +0 -159
  151. package/node_modules/koffi/doc/misc.md +0 -180
  152. package/node_modules/koffi/doc/output.md +0 -305
  153. package/node_modules/koffi/doc/packaging.md +0 -88
  154. package/node_modules/koffi/doc/platforms.md +0 -36
  155. package/node_modules/koffi/doc/pointers.md +0 -328
  156. package/node_modules/koffi/doc/start.md +0 -118
  157. package/node_modules/koffi/doc/unions.md +0 -186
  158. package/node_modules/koffi/doc/variables.md +0 -102
  159. package/node_modules/koffi/index.d.ts +0 -288
  160. package/node_modules/koffi/index.js +0 -634
  161. package/node_modules/koffi/indirect.js +0 -533
  162. package/node_modules/koffi/lib/native/base/base.cc +0 -11015
  163. package/node_modules/koffi/lib/native/base/base.hh +0 -6003
  164. package/node_modules/koffi/lib/native/base/crc.inc +0 -2214
  165. package/node_modules/koffi/lib/native/base/crc_gen.py +0 -72
  166. package/node_modules/koffi/lib/native/base/mimetypes.inc +0 -1248
  167. package/node_modules/koffi/lib/native/base/mimetypes_gen.py +0 -58
  168. package/node_modules/koffi/lib/native/base/tower.cc +0 -821
  169. package/node_modules/koffi/lib/native/base/tower.hh +0 -81
  170. package/node_modules/koffi/lib/native/base/unicode.inc +0 -408
  171. package/node_modules/koffi/lib/native/base/unicode_gen.py +0 -152
  172. package/node_modules/koffi/package.json +0 -38
  173. package/node_modules/koffi/src/cnoke/LICENSE.txt +0 -22
  174. package/node_modules/koffi/src/cnoke/README.md +0 -99
  175. package/node_modules/koffi/src/cnoke/assets/FindCNoke.cmake +0 -127
  176. package/node_modules/koffi/src/cnoke/assets/toolchains.json +0 -126
  177. package/node_modules/koffi/src/cnoke/assets/win_delay_hook.c +0 -36
  178. package/node_modules/koffi/src/cnoke/cnoke.js +0 -170
  179. package/node_modules/koffi/src/cnoke/package.json +0 -24
  180. package/node_modules/koffi/src/cnoke/src/builder.js +0 -511
  181. package/node_modules/koffi/src/cnoke/src/index.js +0 -10
  182. package/node_modules/koffi/src/cnoke/src/tools.js +0 -407
  183. package/node_modules/koffi/src/koffi/CMakeLists.txt +0 -182
  184. package/node_modules/koffi/src/koffi/src/abi_arm32.cc +0 -1018
  185. package/node_modules/koffi/src/koffi/src/abi_arm32_asm.S +0 -169
  186. package/node_modules/koffi/src/koffi/src/abi_arm64.cc +0 -1295
  187. package/node_modules/koffi/src/koffi/src/abi_arm64_asm.S +0 -195
  188. package/node_modules/koffi/src/koffi/src/abi_arm64_asm.asm +0 -174
  189. package/node_modules/koffi/src/koffi/src/abi_loong64.cc +0 -5
  190. package/node_modules/koffi/src/koffi/src/abi_loong64_asm.S +0 -204
  191. package/node_modules/koffi/src/koffi/src/abi_riscv64.cc +0 -915
  192. package/node_modules/koffi/src/koffi/src/abi_riscv64_asm.S +0 -203
  193. package/node_modules/koffi/src/koffi/src/abi_x64_sysv.cc +0 -939
  194. package/node_modules/koffi/src/koffi/src/abi_x64_sysv_asm.S +0 -231
  195. package/node_modules/koffi/src/koffi/src/abi_x64_win.cc +0 -715
  196. package/node_modules/koffi/src/koffi/src/abi_x64_win_asm.S +0 -166
  197. package/node_modules/koffi/src/koffi/src/abi_x64_win_asm.asm +0 -192
  198. package/node_modules/koffi/src/koffi/src/abi_x86.cc +0 -860
  199. package/node_modules/koffi/src/koffi/src/abi_x86_asm.S +0 -193
  200. package/node_modules/koffi/src/koffi/src/abi_x86_asm.asm +0 -177
  201. package/node_modules/koffi/src/koffi/src/call.cc +0 -1326
  202. package/node_modules/koffi/src/koffi/src/call.hh +0 -179
  203. package/node_modules/koffi/src/koffi/src/errno.inc +0 -462
  204. package/node_modules/koffi/src/koffi/src/ffi.cc +0 -2702
  205. package/node_modules/koffi/src/koffi/src/ffi.hh +0 -354
  206. package/node_modules/koffi/src/koffi/src/init.js +0 -105
  207. package/node_modules/koffi/src/koffi/src/parser.cc +0 -220
  208. package/node_modules/koffi/src/koffi/src/parser.hh +0 -54
  209. package/node_modules/koffi/src/koffi/src/util.cc +0 -1807
  210. package/node_modules/koffi/src/koffi/src/util.hh +0 -221
  211. package/node_modules/koffi/src/koffi/src/uv.cc +0 -193
  212. package/node_modules/koffi/src/koffi/src/uv.def +0 -10
  213. package/node_modules/koffi/src/koffi/src/uv.hh +0 -40
  214. package/node_modules/koffi/src/koffi/src/win32.cc +0 -198
  215. package/node_modules/koffi/src/koffi/src/win32.hh +0 -119
  216. package/node_modules/koffi/src/koffi/tools/write_trampolines.js +0 -77
  217. package/node_modules/koffi/vendor/node-addon-api/LICENSE.md +0 -9
  218. package/node_modules/koffi/vendor/node-addon-api/README.md +0 -95
  219. package/node_modules/koffi/vendor/node-addon-api/napi-inl.deprecated.h +0 -186
  220. package/node_modules/koffi/vendor/node-addon-api/napi-inl.h +0 -7033
  221. package/node_modules/koffi/vendor/node-addon-api/napi.h +0 -3309
  222. package/node_modules/koffi/vendor/node-api-headers/LICENSE +0 -21
  223. package/node_modules/koffi/vendor/node-api-headers/README.md +0 -95
  224. package/node_modules/koffi/vendor/node-api-headers/def/js_native_api.def +0 -125
  225. package/node_modules/koffi/vendor/node-api-headers/def/node_api.def +0 -157
  226. package/node_modules/koffi/vendor/node-api-headers/include/js_native_api.h +0 -591
  227. package/node_modules/koffi/vendor/node-api-headers/include/js_native_api_types.h +0 -210
  228. package/node_modules/koffi/vendor/node-api-headers/include/node_api.h +0 -265
  229. package/node_modules/koffi/vendor/node-api-headers/include/node_api_types.h +0 -58
  230. package/node_modules/koffi/vendor/node-api-headers/include/uv/aix.h +0 -32
  231. package/node_modules/koffi/vendor/node-api-headers/include/uv/bsd.h +0 -34
  232. package/node_modules/koffi/vendor/node-api-headers/include/uv/darwin.h +0 -61
  233. package/node_modules/koffi/vendor/node-api-headers/include/uv/errno.h +0 -483
  234. package/node_modules/koffi/vendor/node-api-headers/include/uv/linux.h +0 -34
  235. package/node_modules/koffi/vendor/node-api-headers/include/uv/os390.h +0 -33
  236. package/node_modules/koffi/vendor/node-api-headers/include/uv/posix.h +0 -31
  237. package/node_modules/koffi/vendor/node-api-headers/include/uv/sunos.h +0 -44
  238. package/node_modules/koffi/vendor/node-api-headers/include/uv/threadpool.h +0 -37
  239. package/node_modules/koffi/vendor/node-api-headers/include/uv/tree.h +0 -521
  240. package/node_modules/koffi/vendor/node-api-headers/include/uv/unix.h +0 -512
  241. package/node_modules/koffi/vendor/node-api-headers/include/uv/version.h +0 -43
  242. package/node_modules/koffi/vendor/node-api-headers/include/uv/win.h +0 -698
  243. package/node_modules/koffi/vendor/node-api-headers/include/uv.h +0 -1990
@@ -29,9 +29,10 @@ import { buildBaseOptions } from "./simple-options.js";
29
29
  // ============================================================================
30
30
  const DEFAULT_CODEX_BASE_URL = "https://chatgpt.com/backend-api";
31
31
  const JWT_CLAIM_PATH = "https://api.openai.com/auth";
32
- const MAX_RETRIES = 3;
32
+ const DEFAULT_MAX_RETRIES = 0;
33
33
  const BASE_DELAY_MS = 1000;
34
- const CODEX_TOOL_CALL_PROVIDERS = new Set(["openai", "openai-codex", "opencode", "slingshot"]);
34
+ const DEFAULT_MAX_RETRY_DELAY_MS = 60_000;
35
+ const CODEX_TOOL_CALL_PROVIDERS = new Set(["openai", "openai-codex", "opencode"]);
35
36
  const WEBSOCKET_MESSAGE_TOO_BIG_CLOSE_CODE = 1009;
36
37
  const CODEX_RESPONSE_STATUSES = new Set([
37
38
  "completed",
@@ -44,12 +45,44 @@ const CODEX_RESPONSE_STATUSES = new Set([
44
45
  // ============================================================================
45
46
  // Retry Helpers
46
47
  // ============================================================================
48
+ function isTerminalRateLimitError(errorText) {
49
+ return /GoUsageLimitError|FreeUsageLimitError|Monthly usage limit reached|available balance|insufficient_quota|out of budget|quota exceeded|billing/i.test(errorText);
50
+ }
47
51
  function isRetryableError(status, errorText) {
52
+ if (status === 429 && isTerminalRateLimitError(errorText)) {
53
+ return false;
54
+ }
48
55
  if (status === 429 || status === 500 || status === 502 || status === 503 || status === 504) {
49
56
  return true;
50
57
  }
51
58
  return /rate.?limit|overloaded|service.?unavailable|upstream.?connect|connection.?refused/i.test(errorText);
52
59
  }
60
+ function getRetryAfterDelayMs(headers) {
61
+ const retryAfterMs = headers.get("retry-after-ms");
62
+ if (retryAfterMs !== null) {
63
+ const millis = Number(retryAfterMs);
64
+ if (Number.isFinite(millis)) {
65
+ return Math.max(0, millis);
66
+ }
67
+ }
68
+ const retryAfter = headers.get("retry-after");
69
+ if (!retryAfter) {
70
+ return undefined;
71
+ }
72
+ const seconds = Number(retryAfter);
73
+ if (Number.isFinite(seconds)) {
74
+ return Math.max(0, seconds * 1000);
75
+ }
76
+ const date = Date.parse(retryAfter);
77
+ if (!Number.isNaN(date)) {
78
+ return Math.max(0, date - Date.now());
79
+ }
80
+ return undefined;
81
+ }
82
+ function capRetryDelayMs(delayMs, options) {
83
+ const maxRetryDelayMs = options?.maxRetryDelayMs ?? DEFAULT_MAX_RETRY_DELAY_MS;
84
+ return maxRetryDelayMs > 0 ? Math.min(delayMs, maxRetryDelayMs) : delayMs;
85
+ }
53
86
  function sleep(ms, signal) {
54
87
  return new Promise((resolve, reject) => {
55
88
  if (signal?.aborted) {
@@ -91,7 +124,7 @@ export const streamOpenAICodexResponses = (model, context, options) => {
91
124
  if (!apiKey) {
92
125
  throw new Error(`No API key for provider: ${model.provider}`);
93
126
  }
94
- const accountId = extractAccountId(apiKey) || "";
127
+ const accountId = extractAccountId(apiKey);
95
128
  let body = buildRequestBody(model, context, options);
96
129
  const nextBody = await options?.onPayload?.(body, model);
97
130
  if (nextBody !== undefined) {
@@ -145,7 +178,8 @@ export const streamOpenAICodexResponses = (model, context, options) => {
145
178
  // Fetch with retry logic for rate limits and transient errors
146
179
  let response;
147
180
  let lastError;
148
- for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
181
+ const maxRetries = options?.maxRetries ?? DEFAULT_MAX_RETRIES;
182
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
149
183
  if (options?.signal?.aborted) {
150
184
  throw new Error("Request was aborted");
151
185
  }
@@ -161,30 +195,13 @@ export const streamOpenAICodexResponses = (model, context, options) => {
161
195
  break;
162
196
  }
163
197
  const errorText = await response.text();
164
- if (attempt < MAX_RETRIES && isRetryableError(response.status, errorText)) {
165
- let delayMs = BASE_DELAY_MS * 2 ** attempt;
166
- const retryAfterMs = response.headers.get("retry-after-ms");
167
- if (retryAfterMs !== null) {
168
- const millis = Number(retryAfterMs);
169
- if (Number.isFinite(millis)) {
170
- delayMs = Math.max(0, millis);
171
- }
172
- }
173
- else {
174
- const retryAfter = response.headers.get("retry-after");
175
- if (retryAfter) {
176
- const seconds = Number(retryAfter);
177
- if (Number.isFinite(seconds)) {
178
- delayMs = Math.max(0, seconds * 1000);
179
- }
180
- else {
181
- const date = Date.parse(retryAfter);
182
- if (!Number.isNaN(date)) {
183
- delayMs = Math.max(0, date - Date.now());
184
- }
185
- }
186
- }
187
- }
198
+ if (attempt < maxRetries && isRetryableError(response.status, errorText)) {
199
+ const retryAfterDelayMs = getRetryAfterDelayMs(response.headers);
200
+ const delayMs = retryAfterDelayMs === undefined
201
+ ? BASE_DELAY_MS * 2 ** attempt
202
+ : response.status === 429
203
+ ? capRetryDelayMs(retryAfterDelayMs, options)
204
+ : retryAfterDelayMs;
188
205
  await sleep(delayMs, options?.signal);
189
206
  continue;
190
207
  }
@@ -204,7 +221,7 @@ export const streamOpenAICodexResponses = (model, context, options) => {
204
221
  }
205
222
  lastError = error instanceof Error ? error : new Error(String(error));
206
223
  // Network errors are retryable
207
- if (attempt < MAX_RETRIES && !lastError.message.includes("usage limit")) {
224
+ if (attempt < maxRetries && !lastError.message.includes("usage limit")) {
208
225
  const delayMs = BASE_DELAY_MS * 2 ** attempt;
209
226
  await sleep(delayMs, options?.signal);
210
227
  continue;
@@ -265,7 +282,7 @@ function buildRequestBody(model, context, options) {
265
282
  stream: true,
266
283
  instructions: context.systemPrompt || "You are a helpful assistant.",
267
284
  input: messages,
268
- text: { verbosity: options?.textVerbosity || "medium" },
285
+ text: { verbosity: options?.textVerbosity || "low" },
269
286
  include: ["reasoning.encrypted_content"],
270
287
  prompt_cache_key: clampOpenAIPromptCacheKey(options?.sessionId),
271
288
  tool_choice: "auto",
@@ -322,7 +339,11 @@ function resolveCodexServiceTier(responseServiceTier, requestServiceTier) {
322
339
  function resolveCodexUrl(baseUrl) {
323
340
  const raw = baseUrl && baseUrl.trim().length > 0 ? baseUrl : DEFAULT_CODEX_BASE_URL;
324
341
  const normalized = raw.replace(/\/+$/, "");
325
- return `${normalized}/responses`;
342
+ if (normalized.endsWith("/codex/responses"))
343
+ return normalized;
344
+ if (normalized.endsWith("/codex"))
345
+ return `${normalized}/responses`;
346
+ return `${normalized}/codex/responses`;
326
347
  }
327
348
  function resolveCodexWebSocketUrl(baseUrl) {
328
349
  const url = new URL(resolveCodexUrl(baseUrl));
@@ -1031,13 +1052,15 @@ function extractAccountId(token) {
1031
1052
  try {
1032
1053
  const parts = token.split(".");
1033
1054
  if (parts.length !== 3)
1034
- return null;
1055
+ throw new Error("Invalid token");
1035
1056
  const payload = JSON.parse(atob(parts[1]));
1036
1057
  const accountId = payload?.[JWT_CLAIM_PATH]?.chatgpt_account_id;
1037
- return accountId || null;
1058
+ if (!accountId)
1059
+ throw new Error("No account ID in token");
1060
+ return accountId;
1038
1061
  }
1039
1062
  catch {
1040
- return null;
1063
+ throw new Error("Failed to extract accountId from token");
1041
1064
  }
1042
1065
  }
1043
1066
  function createCodexRequestId() {
@@ -1052,10 +1075,7 @@ function buildBaseCodexHeaders(initHeaders, additionalHeaders, accountId, token)
1052
1075
  headers.set(key, value);
1053
1076
  }
1054
1077
  headers.set("Authorization", `Bearer ${token}`);
1055
- // Only set chatgpt-account-id if we have one (OpenAI-specific)
1056
- if (accountId) {
1057
- headers.set("chatgpt-account-id", accountId);
1058
- }
1078
+ headers.set("chatgpt-account-id", accountId);
1059
1079
  headers.set("originator", "pi");
1060
1080
  const userAgent = _os ? `pi (${_os.platform()} ${_os.release()}; ${_os.arch()})` : "pi (browser)";
1061
1081
  headers.set("User-Agent", userAgent);
@@ -1067,7 +1087,7 @@ function buildSSEHeaders(initHeaders, additionalHeaders, accountId, token, sessi
1067
1087
  headers.set("accept", "text/event-stream");
1068
1088
  headers.set("content-type", "application/json");
1069
1089
  if (sessionId) {
1070
- headers.set("session_id", sessionId);
1090
+ headers.set("session-id", sessionId);
1071
1091
  headers.set("x-client-request-id", sessionId);
1072
1092
  }
1073
1093
  return headers;
@@ -1080,7 +1100,7 @@ function buildWebSocketHeaders(initHeaders, additionalHeaders, accountId, token,
1080
1100
  headers.delete("openai-beta");
1081
1101
  headers.set("OpenAI-Beta", OPENAI_BETA_RESPONSES_WEBSOCKETS);
1082
1102
  headers.set("x-client-request-id", requestId);
1083
- headers.set("session_id", requestId);
1103
+ headers.set("session-id", requestId);
1084
1104
  return headers;
1085
1105
  }
1086
1106
  //# sourceMappingURL=openai-codex-responses.js.map
@@ -83,7 +83,7 @@ export const streamOpenAICompletions = (model, context, options) => {
83
83
  const requestOptions = {
84
84
  ...(options?.signal ? { signal: options.signal } : {}),
85
85
  ...(options?.timeoutMs !== undefined ? { timeout: options.timeoutMs } : {}),
86
- ...(options?.maxRetries !== undefined ? { maxRetries: options.maxRetries } : {}),
86
+ maxRetries: options?.maxRetries ?? 0,
87
87
  };
88
88
  const { data: openaiStream, response } = await client.chat.completions
89
89
  .create(params, requestOptions)
@@ -85,7 +85,7 @@ export const streamOpenAIResponses = (model, context, options) => {
85
85
  const requestOptions = {
86
86
  ...(options?.signal ? { signal: options.signal } : {}),
87
87
  ...(options?.timeoutMs !== undefined ? { timeout: options.timeoutMs } : {}),
88
- ...(options?.maxRetries !== undefined ? { maxRetries: options.maxRetries } : {}),
88
+ maxRetries: options?.maxRetries ?? 0,
89
89
  };
90
90
  const { data: openaiStream, response } = await client.responses.create(params, requestOptions).withResponse();
91
91
  await options?.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model);
@@ -0,0 +1,55 @@
1
+ const CANCEL_MESSAGE = "Login cancelled";
2
+ const TIMEOUT_MESSAGE = "Device flow timed out";
3
+ const SLOW_DOWN_TIMEOUT_MESSAGE = "Device flow timed out after one or more slow_down responses. This is often caused by clock drift in WSL or VM environments. Please sync or restart the VM clock and try again.";
4
+ const MINIMUM_INTERVAL_MS = 1000;
5
+ // RFC 8628 section 3.2: if the authorization server omits `interval`, the client must use 5 seconds.
6
+ const DEFAULT_POLL_INTERVAL_SECONDS = 5;
7
+ // RFC 8628 section 3.5: `slow_down` means the polling interval must increase by 5 seconds.
8
+ const SLOW_DOWN_INTERVAL_INCREMENT_MS = 5000;
9
+ function abortableSleep(ms, signal, cancelMessage) {
10
+ return new Promise((resolve, reject) => {
11
+ if (signal?.aborted) {
12
+ reject(new Error(cancelMessage));
13
+ return;
14
+ }
15
+ const onAbort = () => {
16
+ clearTimeout(timeout);
17
+ reject(new Error(cancelMessage));
18
+ };
19
+ const timeout = setTimeout(() => {
20
+ signal?.removeEventListener("abort", onAbort);
21
+ resolve();
22
+ }, ms);
23
+ signal?.addEventListener("abort", onAbort, { once: true });
24
+ });
25
+ }
26
+ export async function pollOAuthDeviceCodeFlow(options) {
27
+ const deadline = typeof options.expiresInSeconds === "number"
28
+ ? Date.now() + options.expiresInSeconds * 1000
29
+ : Number.POSITIVE_INFINITY;
30
+ let intervalMs = Math.max(MINIMUM_INTERVAL_MS, Math.floor((options.intervalSeconds ?? DEFAULT_POLL_INTERVAL_SECONDS) * 1000));
31
+ let slowDownResponses = 0;
32
+ while (Date.now() < deadline) {
33
+ if (options.signal?.aborted) {
34
+ throw new Error(CANCEL_MESSAGE);
35
+ }
36
+ const remainingMs = deadline - Date.now();
37
+ await abortableSleep(Math.min(intervalMs, remainingMs), options.signal, CANCEL_MESSAGE);
38
+ const result = await options.poll();
39
+ if (result.status === "complete") {
40
+ return result.accessToken;
41
+ }
42
+ if (result.status === "pending") {
43
+ continue;
44
+ }
45
+ if (result.status === "slow_down") {
46
+ slowDownResponses += 1;
47
+ // RFC 8628 section 3.5: apply this increase to this and all subsequent requests.
48
+ intervalMs = Math.max(MINIMUM_INTERVAL_MS, intervalMs + SLOW_DOWN_INTERVAL_INCREMENT_MS);
49
+ continue;
50
+ }
51
+ throw new Error(result.message);
52
+ }
53
+ throw new Error(slowDownResponses > 0 ? SLOW_DOWN_TIMEOUT_MESSAGE : TIMEOUT_MESSAGE);
54
+ }
55
+ //# sourceMappingURL=device-code.js.map
@@ -2,6 +2,7 @@
2
2
  * GitHub Copilot OAuth flow
3
3
  */
4
4
  import { getModels } from "../../models.js";
5
+ import { pollOAuthDeviceCodeFlow } from "./device-code.js";
5
6
  const decode = (s) => atob(s);
6
7
  const CLIENT_ID = decode("SXYxLmI1MDdhMDhjODdlY2ZlOTg=");
7
8
  const COPILOT_HEADERS = {
@@ -10,8 +11,6 @@ const COPILOT_HEADERS = {
10
11
  "Editor-Plugin-Version": "copilot-chat/0.35.0",
11
12
  "Copilot-Integration-Id": "vscode-chat",
12
13
  };
13
- const INITIAL_POLL_INTERVAL_MULTIPLIER = 1.2;
14
- const SLOW_DOWN_POLL_INTERVAL_MULTIPLIER = 1.4;
15
14
  export function normalizeDomain(input) {
16
15
  const trimmed = input.trim();
17
16
  if (!trimmed)
@@ -90,7 +89,7 @@ async function startDeviceFlow(domain) {
90
89
  if (typeof deviceCode !== "string" ||
91
90
  typeof userCode !== "string" ||
92
91
  typeof verificationUri !== "string" ||
93
- typeof interval !== "number" ||
92
+ (interval !== undefined && typeof interval !== "number") ||
94
93
  typeof expiresIn !== "number") {
95
94
  throw new Error("Invalid device code response fields");
96
95
  }
@@ -102,71 +101,43 @@ async function startDeviceFlow(domain) {
102
101
  expires_in: expiresIn,
103
102
  };
104
103
  }
105
- /**
106
- * Sleep that can be interrupted by an AbortSignal
107
- */
108
- function abortableSleep(ms, signal) {
109
- return new Promise((resolve, reject) => {
110
- if (signal?.aborted) {
111
- reject(new Error("Login cancelled"));
112
- return;
113
- }
114
- const timeout = setTimeout(resolve, ms);
115
- signal?.addEventListener("abort", () => {
116
- clearTimeout(timeout);
117
- reject(new Error("Login cancelled"));
118
- }, { once: true });
119
- });
120
- }
121
- async function pollForGitHubAccessToken(domain, deviceCode, intervalSeconds, expiresIn, signal) {
104
+ async function pollForGitHubAccessToken(domain, device, signal) {
122
105
  const urls = getUrls(domain);
123
- const deadline = Date.now() + expiresIn * 1000;
124
- let intervalMs = Math.max(1000, Math.floor(intervalSeconds * 1000));
125
- let intervalMultiplier = INITIAL_POLL_INTERVAL_MULTIPLIER;
126
- let slowDownResponses = 0;
127
- while (Date.now() < deadline) {
128
- if (signal?.aborted) {
129
- throw new Error("Login cancelled");
130
- }
131
- const remainingMs = deadline - Date.now();
132
- const waitMs = Math.min(Math.ceil(intervalMs * intervalMultiplier), remainingMs);
133
- await abortableSleep(waitMs, signal);
134
- const raw = await fetchJson(urls.accessTokenUrl, {
135
- method: "POST",
136
- headers: {
137
- Accept: "application/json",
138
- "Content-Type": "application/x-www-form-urlencoded",
139
- "User-Agent": "GitHubCopilotChat/0.35.0",
140
- },
141
- body: new URLSearchParams({
142
- client_id: CLIENT_ID,
143
- device_code: deviceCode,
144
- grant_type: "urn:ietf:params:oauth:grant-type:device_code",
145
- }),
146
- });
147
- if (raw && typeof raw === "object" && typeof raw.access_token === "string") {
148
- return raw.access_token;
149
- }
150
- if (raw && typeof raw === "object" && typeof raw.error === "string") {
151
- const { error, error_description: description, interval } = raw;
152
- if (error === "authorization_pending") {
153
- continue;
106
+ return pollOAuthDeviceCodeFlow({
107
+ intervalSeconds: device.interval,
108
+ expiresInSeconds: device.expires_in,
109
+ signal,
110
+ poll: async () => {
111
+ const raw = await fetchJson(urls.accessTokenUrl, {
112
+ method: "POST",
113
+ headers: {
114
+ Accept: "application/json",
115
+ "Content-Type": "application/x-www-form-urlencoded",
116
+ "User-Agent": "GitHubCopilotChat/0.35.0",
117
+ },
118
+ body: new URLSearchParams({
119
+ client_id: CLIENT_ID,
120
+ device_code: device.device_code,
121
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
122
+ }),
123
+ });
124
+ if (raw && typeof raw === "object" && typeof raw.access_token === "string") {
125
+ return { status: "complete", accessToken: raw.access_token };
154
126
  }
155
- if (error === "slow_down") {
156
- slowDownResponses += 1;
157
- intervalMs =
158
- typeof interval === "number" && interval > 0 ? interval * 1000 : Math.max(1000, intervalMs + 5000);
159
- intervalMultiplier = SLOW_DOWN_POLL_INTERVAL_MULTIPLIER;
160
- continue;
127
+ if (raw && typeof raw === "object" && typeof raw.error === "string") {
128
+ const { error, error_description: description } = raw;
129
+ if (error === "authorization_pending") {
130
+ return { status: "pending" };
131
+ }
132
+ if (error === "slow_down") {
133
+ return { status: "slow_down" };
134
+ }
135
+ const descriptionSuffix = description ? `: ${description}` : "";
136
+ return { status: "failed", message: `Device flow failed: ${error}${descriptionSuffix}` };
161
137
  }
162
- const descriptionSuffix = description ? `: ${description}` : "";
163
- throw new Error(`Device flow failed: ${error}${descriptionSuffix}`);
164
- }
165
- }
166
- if (slowDownResponses > 0) {
167
- throw new Error("Device flow timed out after one or more slow_down responses. This is often caused by clock drift in WSL or VM environments. Please sync or restart the VM clock and try again.");
168
- }
169
- throw new Error("Device flow timed out");
138
+ return { status: "failed", message: "Invalid device token response" };
139
+ },
140
+ });
170
141
  }
171
142
  /**
172
143
  * Refresh GitHub Copilot token
@@ -235,7 +206,7 @@ async function enableAllGitHubCopilotModels(token, enterpriseDomain, onProgress)
235
206
  /**
236
207
  * Login with GitHub Copilot OAuth (device code flow)
237
208
  *
238
- * @param options.onAuth - Callback with URL and optional instructions (user code)
209
+ * @param options.onDeviceCode - Callback with URL and user code
239
210
  * @param options.onPrompt - Callback to prompt user for input
240
211
  * @param options.onProgress - Optional progress callback
241
212
  * @param options.signal - Optional AbortSignal for cancellation
@@ -256,8 +227,13 @@ export async function loginGitHubCopilot(options) {
256
227
  }
257
228
  const domain = enterpriseDomain || "github.com";
258
229
  const device = await startDeviceFlow(domain);
259
- options.onAuth(device.verification_uri, `Enter code: ${device.user_code}`);
260
- const githubAccessToken = await pollForGitHubAccessToken(domain, device.device_code, device.interval, device.expires_in, options.signal);
230
+ options.onDeviceCode({
231
+ userCode: device.user_code,
232
+ verificationUri: device.verification_uri,
233
+ intervalSeconds: device.interval,
234
+ expiresInSeconds: device.expires_in,
235
+ });
236
+ const githubAccessToken = await pollForGitHubAccessToken(domain, device, options.signal);
261
237
  const credentials = await refreshGitHubCopilotToken(githubAccessToken, enterpriseDomain ?? undefined);
262
238
  // Enable all models after successful login
263
239
  options.onProgress?.("Enabling models...");
@@ -269,7 +245,7 @@ export const githubCopilotOAuthProvider = {
269
245
  name: "GitHub Copilot",
270
246
  async login(callbacks) {
271
247
  return loginGitHubCopilot({
272
- onAuth: (url, instructions) => callbacks.onAuth({ url, instructions }),
248
+ onDeviceCode: callbacks.onDeviceCode,
273
249
  onPrompt: callbacks.onPrompt,
274
250
  onProgress: callbacks.onProgress,
275
251
  signal: callbacks.signal,
@@ -8,6 +8,7 @@
8
8
  */
9
9
  // Anthropic
10
10
  export { anthropicOAuthProvider, loginAnthropic, refreshAnthropicToken } from "./anthropic.js";
11
+ export * from "./device-code.js";
11
12
  // GitHub Copilot
12
13
  export { getGitHubCopilotBaseUrl, githubCopilotOAuthProvider, loginGitHubCopilot, normalizeDomain, refreshGitHubCopilotToken, } from "./github-copilot.js";
13
14
  // OpenAI Codex (ChatGPT OAuth)
@@ -14,6 +14,7 @@
14
14
  * - xAI: "This model's maximum prompt length is 131072 but the request contains 537812 tokens"
15
15
  * - Groq: "Please reduce the length of the messages or completion"
16
16
  * - OpenRouter: "This endpoint's maximum context length is X tokens. However, you requested about Y tokens"
17
+ * - OpenRouter/Poolside: "Input length X exceeds the maximum allowed input length of Y tokens."
17
18
  * - Together AI: "The input (X tokens) is longer than the model's context length (Y tokens)."
18
19
  * - llama.cpp: "the request exceeds the available context size, try increasing it"
19
20
  * - LM Studio: "tokens to keep from the initial prompt is greater than the context length"
@@ -37,7 +38,8 @@ const OVERFLOW_PATTERNS = [
37
38
  /input token count.*exceeds the maximum/i, // Google (Gemini)
38
39
  /maximum prompt length is \d+/i, // xAI (Grok)
39
40
  /reduce the length of the messages/i, // Groq
40
- /maximum context length is \d+ tokens/i, // OpenRouter (all backends)
41
+ /maximum context length is \d+ tokens/i, // OpenRouter (most backends)
42
+ /exceeds (?:the )?maximum allowed input length of [\d,]+ tokens?/i, // OpenRouter/Poolside
41
43
  /input \(\d+ tokens\) is longer than the model'?s context length \(\d+ tokens\)/i, // Together AI
42
44
  /exceeds the limit of \d+/i, // GitHub Copilot
43
45
  /exceeds the available context size/i, // llama.cpp server
@@ -85,7 +87,8 @@ const NON_OVERFLOW_PATTERNS = [
85
87
  * - Groq: "reduce the length of the messages"
86
88
  * - Cerebras: 400/413 status code (no body)
87
89
  * - Mistral: "Prompt contains X tokens ... too large for model with Y maximum context length"
88
- * - OpenRouter (all backends): "maximum context length is X tokens"
90
+ * - OpenRouter (most backends): "maximum context length is X tokens"
91
+ * - OpenRouter/Poolside: "Input length X exceeds the maximum allowed input length of Y tokens."
89
92
  * - Together AI: "The input (X tokens) is longer than the model's context length (Y tokens)."
90
93
  * - llama.cpp: "exceeds the available context size"
91
94
  * - LM Studio: "greater than the context length"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@earendil-works/pi-ai",
3
- "version": "0.75.4",
3
+ "version": "0.75.5",
4
4
  "description": "Unified LLM API with automatic model discovery and provider configuration",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -60,8 +60,8 @@
60
60
  ],
61
61
  "scripts": {
62
62
  "clean": "shx rm -rf dist",
63
- "generate-models": "tsx scripts/generate-models.ts",
64
- "generate-image-models": "tsx scripts/generate-image-models.ts",
63
+ "generate-models": "node scripts/generate-models.ts",
64
+ "generate-image-models": "node scripts/generate-image-models.ts",
65
65
  "build": "npm run generate-models && npm run generate-image-models && tsgo -p tsconfig.build.json",
66
66
  "test": "vitest --run",
67
67
  "prepublishOnly": "npm run clean && npm run build"
@@ -69,6 +69,7 @@
69
69
  "dependencies": {
70
70
  "@anthropic-ai/sdk": "0.91.1",
71
71
  "@aws-sdk/client-bedrock-runtime": "3.1048.0",
72
+ "@smithy/node-http-handler": "4.7.3",
72
73
  "@google/genai": "1.52.0",
73
74
  "@mistralai/mistralai": "2.2.1",
74
75
  "http-proxy-agent": "7.0.2",
@@ -33,11 +33,10 @@ export async function processFileArguments(fileArgs, options) {
33
33
  if (mimeType) {
34
34
  // Handle image file
35
35
  const content = await readFile(absolutePath);
36
- const base64Content = content.toString("base64");
37
36
  let attachment;
38
37
  let dimensionNote;
39
38
  if (autoResizeImages) {
40
- const resized = await resizeImage({ type: "image", data: base64Content, mimeType });
39
+ const resized = await resizeImage(content, mimeType);
41
40
  if (!resized) {
42
41
  text += `<file name="${absolutePath}">[Image omitted: could not be resized below the inline image size limit.]</file>\n`;
43
42
  continue;
@@ -53,7 +52,7 @@ export async function processFileArguments(fileArgs, options) {
53
52
  attachment = {
54
53
  type: "image",
55
54
  mimeType,
56
- data: base64Content,
55
+ data: content.toString("base64"),
57
56
  };
58
57
  }
59
58
  images.push(attachment);
@@ -3,6 +3,7 @@ import { homedir } from "os";
3
3
  import { basename, dirname, join, resolve, sep, win32 } from "path";
4
4
  import { fileURLToPath } from "url";
5
5
  import { spawnProcessSync } from "./utils/child-process.js";
6
+ import { normalizePath } from "./utils/paths.js";
6
7
  // =============================================================================
7
8
  // Package Detection
8
9
  // =============================================================================
@@ -76,7 +77,13 @@ function getSelfUpdateCommandForMethod(method, installedPackageName, updatePacka
76
77
  case "bun-binary":
77
78
  return undefined;
78
79
  case "pnpm":
79
- return makeSelfUpdateCommand(makeSelfUpdateCommandStep("pnpm", ["install", "-g", "--ignore-scripts", updatePackageName]), updatePackageName === installedPackageName
80
+ return makeSelfUpdateCommand(makeSelfUpdateCommandStep("pnpm", [
81
+ "install",
82
+ "-g",
83
+ "--ignore-scripts",
84
+ "--config.minimumReleaseAge=0",
85
+ updatePackageName,
86
+ ]), updatePackageName === installedPackageName
80
87
  ? undefined
81
88
  : makeSelfUpdateCommandStep("pnpm", ["remove", "-g", installedPackageName]));
82
89
  case "yarn":
@@ -84,7 +91,13 @@ function getSelfUpdateCommandForMethod(method, installedPackageName, updatePacka
84
91
  ? undefined
85
92
  : makeSelfUpdateCommandStep("yarn", ["global", "remove", installedPackageName]));
86
93
  case "bun":
87
- return makeSelfUpdateCommand(makeSelfUpdateCommandStep("bun", ["install", "-g", "--ignore-scripts", updatePackageName]), updatePackageName === installedPackageName
94
+ return makeSelfUpdateCommand(makeSelfUpdateCommandStep("bun", [
95
+ "install",
96
+ "-g",
97
+ "--ignore-scripts",
98
+ "--minimum-release-age=0",
99
+ updatePackageName,
100
+ ]), updatePackageName === installedPackageName
88
101
  ? undefined
89
102
  : makeSelfUpdateCommandStep("bun", ["uninstall", "-g", installedPackageName]));
90
103
  case "npm": {
@@ -96,6 +109,7 @@ function getSelfUpdateCommandForMethod(method, installedPackageName, updatePacka
96
109
  "install",
97
110
  "-g",
98
111
  "--ignore-scripts",
112
+ "--min-release-age=0",
99
113
  updatePackageName,
100
114
  ]);
101
115
  const uninstallStep = updatePackageName === installedPackageName
@@ -261,11 +275,7 @@ export function getPackageDir() {
261
275
  // Allow override via environment variable (useful for Nix/Guix where store paths tokenize poorly)
262
276
  const envDir = process.env.PI_PACKAGE_DIR;
263
277
  if (envDir) {
264
- if (envDir === "~")
265
- return homedir();
266
- if (envDir.startsWith("~/"))
267
- return homedir() + envDir.slice(1);
268
- return envDir;
278
+ return normalizePath(envDir);
269
279
  }
270
280
  if (isBunBinary) {
271
281
  // Bun binary: process.execPath points to the compiled executable
@@ -360,11 +370,7 @@ export const VERSION = pkg.version || "0.0.0";
360
370
  export const ENV_AGENT_DIR = `${APP_NAME.toUpperCase()}_CODING_AGENT_DIR`;
361
371
  export const ENV_SESSION_DIR = `${APP_NAME.toUpperCase()}_CODING_AGENT_SESSION_DIR`;
362
372
  export function expandTildePath(path) {
363
- if (path === "~")
364
- return homedir();
365
- if (path.startsWith("~/"))
366
- return homedir() + path.slice(1);
367
- return path;
373
+ return normalizePath(path);
368
374
  }
369
375
  const DEFAULT_SHARE_VIEWER_URL = "https://pi.dev/session/";
370
376
  /** Get the share viewer URL for a gist ID */
@@ -1,5 +1,6 @@
1
1
  import { copyFileSync, existsSync, mkdirSync } from "node:fs";
2
2
  import { basename, join, resolve } from "node:path";
3
+ import { resolvePath } from "../utils/paths.js";
3
4
  import { emitSessionShutdownEvent } from "./extensions/runner.js";
4
5
  import { assertSessionCwdExists } from "./session-cwd.js";
5
6
  import { SessionManager } from "./session-manager.js";
@@ -246,7 +247,7 @@ export class AgentSessionRuntime {
246
247
  * @throws {MissingSessionCwdError} When the imported session cwd cannot be resolved and no override is provided.
247
248
  */
248
249
  async importFromJsonl(inputPath, cwdOverride) {
249
- const resolvedPath = resolve(inputPath);
250
+ const resolvedPath = resolvePath(inputPath);
250
251
  if (!existsSync(resolvedPath)) {
251
252
  throw new SessionImportFileNotFoundError(resolvedPath);
252
253
  }
@@ -1,5 +1,6 @@
1
1
  import { join } from "node:path";
2
2
  import { getAgentDir } from "../config.js";
3
+ import { resolvePath } from "../utils/paths.js";
3
4
  import { AuthStorage } from "./auth-storage.js";
4
5
  import { ModelRegistry } from "./model-registry.js";
5
6
  import { DefaultResourceLoader } from "./resource-loader.js";
@@ -51,8 +52,8 @@ function applyExtensionFlagValues(resourceLoader, extensionFlagValues) {
51
52
  * Returns services plus diagnostics. It does not create an AgentSession.
52
53
  */
53
54
  export async function createAgentSessionServices(options) {
54
- const cwd = options.cwd;
55
- const agentDir = options.agentDir ?? getAgentDir();
55
+ const cwd = resolvePath(options.cwd);
56
+ const agentDir = options.agentDir ? resolvePath(options.agentDir) : getAgentDir();
56
57
  const authStorage = options.authStorage ?? AuthStorage.create(join(agentDir, "auth.json"));
57
58
  const settingsManager = options.settingsManager ?? SettingsManager.create(cwd, agentDir);
58
59
  const modelRegistry = options.modelRegistry ?? ModelRegistry.create(authStorage, join(agentDir, "models.json"));