@earendil-works/pi-coding-agent 0.82.1 → 0.84.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 (311) hide show
  1. package/CHANGELOG.md +243 -0
  2. package/README.md +6 -1
  3. package/dist/cli/args.d.ts +2 -0
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +27 -1
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/cli/config-selector.d.ts.map +1 -1
  8. package/dist/cli/config-selector.js +2 -2
  9. package/dist/cli/config-selector.js.map +1 -1
  10. package/dist/cli/credential-print.d.ts +23 -0
  11. package/dist/cli/credential-print.d.ts.map +1 -0
  12. package/dist/cli/credential-print.js +117 -0
  13. package/dist/cli/credential-print.js.map +1 -0
  14. package/dist/cli/experimental/auth.d.ts +16 -0
  15. package/dist/cli/experimental/auth.d.ts.map +1 -0
  16. package/dist/cli/experimental/auth.js +13 -0
  17. package/dist/cli/experimental/auth.js.map +1 -0
  18. package/dist/cli/experimental/cli.d.ts +6 -0
  19. package/dist/cli/experimental/cli.d.ts.map +1 -0
  20. package/dist/cli/experimental/cli.js +5 -0
  21. package/dist/cli/experimental/cli.js.map +1 -0
  22. package/dist/cli/experimental/command-options.d.ts +17 -0
  23. package/dist/cli/experimental/command-options.d.ts.map +1 -0
  24. package/dist/cli/experimental/command-options.js +35 -0
  25. package/dist/cli/experimental/command-options.js.map +1 -0
  26. package/dist/cli/experimental/command.d.ts +63 -0
  27. package/dist/cli/experimental/command.d.ts.map +1 -0
  28. package/dist/cli/experimental/command.js +130 -0
  29. package/dist/cli/experimental/command.js.map +1 -0
  30. package/dist/cli/experimental/commands/client.d.ts +13 -0
  31. package/dist/cli/experimental/commands/client.d.ts.map +1 -0
  32. package/dist/cli/experimental/commands/client.js +25 -0
  33. package/dist/cli/experimental/commands/client.js.map +1 -0
  34. package/dist/cli/experimental/commands/pi.d.ts +15 -0
  35. package/dist/cli/experimental/commands/pi.d.ts.map +1 -0
  36. package/dist/cli/experimental/commands/pi.js +28 -0
  37. package/dist/cli/experimental/commands/pi.js.map +1 -0
  38. package/dist/cli/experimental/commands/server.d.ts +13 -0
  39. package/dist/cli/experimental/commands/server.d.ts.map +1 -0
  40. package/dist/cli/experimental/commands/server.js +25 -0
  41. package/dist/cli/experimental/commands/server.js.map +1 -0
  42. package/dist/cli/experimental/transport-address.d.ts +10 -0
  43. package/dist/cli/experimental/transport-address.d.ts.map +1 -0
  44. package/dist/cli/experimental/transport-address.js +38 -0
  45. package/dist/cli/experimental/transport-address.js.map +1 -0
  46. package/dist/cli/list-models.d.ts +1 -1
  47. package/dist/cli/list-models.d.ts.map +1 -1
  48. package/dist/cli/list-models.js +2 -2
  49. package/dist/cli/list-models.js.map +1 -1
  50. package/dist/cli/startup-ui.d.ts +1 -1
  51. package/dist/cli/startup-ui.d.ts.map +1 -1
  52. package/dist/cli/startup-ui.js +2 -2
  53. package/dist/cli/startup-ui.js.map +1 -1
  54. package/dist/cli.d.ts.map +1 -1
  55. package/dist/cli.js +1 -0
  56. package/dist/cli.js.map +1 -1
  57. package/dist/client/index.d.ts +3 -0
  58. package/dist/client/index.d.ts.map +1 -0
  59. package/dist/client/index.js +3 -0
  60. package/dist/client/index.js.map +1 -0
  61. package/dist/client/remote-session.d.ts +53 -0
  62. package/dist/client/remote-session.d.ts.map +1 -0
  63. package/dist/client/remote-session.js +340 -0
  64. package/dist/client/remote-session.js.map +1 -0
  65. package/dist/client/transcript.d.ts +12 -0
  66. package/dist/client/transcript.d.ts.map +1 -0
  67. package/dist/client/transcript.js +98 -0
  68. package/dist/client/transcript.js.map +1 -0
  69. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  70. package/dist/core/agent-session-runtime.js +3 -0
  71. package/dist/core/agent-session-runtime.js.map +1 -1
  72. package/dist/core/agent-session-services.d.ts +1 -0
  73. package/dist/core/agent-session-services.d.ts.map +1 -1
  74. package/dist/core/agent-session-services.js +1 -0
  75. package/dist/core/agent-session-services.js.map +1 -1
  76. package/dist/core/agent-session.d.ts +2 -11
  77. package/dist/core/agent-session.d.ts.map +1 -1
  78. package/dist/core/agent-session.js +85 -76
  79. package/dist/core/agent-session.js.map +1 -1
  80. package/dist/core/auth-storage.d.ts +15 -9
  81. package/dist/core/auth-storage.d.ts.map +1 -1
  82. package/dist/core/auth-storage.js +159 -38
  83. package/dist/core/auth-storage.js.map +1 -1
  84. package/dist/core/extensions/index.d.ts +1 -1
  85. package/dist/core/extensions/index.d.ts.map +1 -1
  86. package/dist/core/extensions/index.js.map +1 -1
  87. package/dist/core/extensions/loader.d.ts.map +1 -1
  88. package/dist/core/extensions/loader.js +43 -20
  89. package/dist/core/extensions/loader.js.map +1 -1
  90. package/dist/core/extensions/runner.d.ts +3 -1
  91. package/dist/core/extensions/runner.d.ts.map +1 -1
  92. package/dist/core/extensions/runner.js +10 -0
  93. package/dist/core/extensions/runner.js.map +1 -1
  94. package/dist/core/extensions/types.d.ts +28 -3
  95. package/dist/core/extensions/types.d.ts.map +1 -1
  96. package/dist/core/extensions/types.js.map +1 -1
  97. package/dist/core/footer-data-provider.d.ts +10 -0
  98. package/dist/core/footer-data-provider.d.ts.map +1 -1
  99. package/dist/core/footer-data-provider.js +1 -1
  100. package/dist/core/footer-data-provider.js.map +1 -1
  101. package/dist/core/http-dispatcher.d.ts.map +1 -1
  102. package/dist/core/http-dispatcher.js +5 -0
  103. package/dist/core/http-dispatcher.js.map +1 -1
  104. package/dist/core/keybindings.d.ts +36 -4
  105. package/dist/core/keybindings.d.ts.map +1 -1
  106. package/dist/core/model-config.d.ts +29 -5
  107. package/dist/core/model-config.d.ts.map +1 -1
  108. package/dist/core/model-config.js +4 -0
  109. package/dist/core/model-config.js.map +1 -1
  110. package/dist/core/model-registry.d.ts +5 -3
  111. package/dist/core/model-registry.d.ts.map +1 -1
  112. package/dist/core/model-registry.js +13 -10
  113. package/dist/core/model-registry.js.map +1 -1
  114. package/dist/core/model-resolver.d.ts +4 -3
  115. package/dist/core/model-resolver.d.ts.map +1 -1
  116. package/dist/core/model-resolver.js +41 -10
  117. package/dist/core/model-resolver.js.map +1 -1
  118. package/dist/core/model-runtime.d.ts +30 -13
  119. package/dist/core/model-runtime.d.ts.map +1 -1
  120. package/dist/core/model-runtime.js +231 -73
  121. package/dist/core/model-runtime.js.map +1 -1
  122. package/dist/core/models-store.d.ts +12 -7
  123. package/dist/core/models-store.d.ts.map +1 -1
  124. package/dist/core/models-store.js +83 -14
  125. package/dist/core/models-store.js.map +1 -1
  126. package/dist/core/package-manager.d.ts +4 -1
  127. package/dist/core/package-manager.d.ts.map +1 -1
  128. package/dist/core/package-manager.js +76 -42
  129. package/dist/core/package-manager.js.map +1 -1
  130. package/dist/core/pi-manifest.d.ts +8 -0
  131. package/dist/core/pi-manifest.d.ts.map +1 -0
  132. package/dist/core/pi-manifest.js +25 -0
  133. package/dist/core/pi-manifest.js.map +1 -0
  134. package/dist/core/provider-composer.d.ts +4 -1
  135. package/dist/core/provider-composer.d.ts.map +1 -1
  136. package/dist/core/provider-composer.js +36 -15
  137. package/dist/core/provider-composer.js.map +1 -1
  138. package/dist/core/remote-catalog-provider.d.ts.map +1 -1
  139. package/dist/core/remote-catalog-provider.js +73 -67
  140. package/dist/core/remote-catalog-provider.js.map +1 -1
  141. package/dist/core/resource-loader.d.ts +15 -0
  142. package/dist/core/resource-loader.d.ts.map +1 -1
  143. package/dist/core/resource-loader.js +64 -10
  144. package/dist/core/resource-loader.js.map +1 -1
  145. package/dist/core/runtime-credentials.d.ts +5 -5
  146. package/dist/core/runtime-credentials.d.ts.map +1 -1
  147. package/dist/core/runtime-credentials.js +11 -8
  148. package/dist/core/runtime-credentials.js.map +1 -1
  149. package/dist/core/session-manager.d.ts.map +1 -1
  150. package/dist/core/session-manager.js +3 -1
  151. package/dist/core/session-manager.js.map +1 -1
  152. package/dist/core/settings-manager.d.ts +12 -0
  153. package/dist/core/settings-manager.d.ts.map +1 -1
  154. package/dist/core/settings-manager.js +40 -16
  155. package/dist/core/settings-manager.js.map +1 -1
  156. package/dist/core/tools/find.d.ts +3 -0
  157. package/dist/core/tools/find.d.ts.map +1 -1
  158. package/dist/core/tools/find.js +11 -18
  159. package/dist/core/tools/find.js.map +1 -1
  160. package/dist/core/tools/read.d.ts.map +1 -1
  161. package/dist/core/tools/read.js +1 -1
  162. package/dist/core/tools/read.js.map +1 -1
  163. package/dist/extensions/llama/index.d.ts.map +1 -1
  164. package/dist/extensions/llama/index.js +11 -2
  165. package/dist/extensions/llama/index.js.map +1 -1
  166. package/dist/extensions/llama/provider.d.ts.map +1 -1
  167. package/dist/extensions/llama/provider.js +22 -8
  168. package/dist/extensions/llama/provider.js.map +1 -1
  169. package/dist/index.d.ts +4 -4
  170. package/dist/index.d.ts.map +1 -1
  171. package/dist/index.js +1 -1
  172. package/dist/index.js.map +1 -1
  173. package/dist/main.d.ts.map +1 -1
  174. package/dist/main.js +57 -5
  175. package/dist/main.js.map +1 -1
  176. package/dist/modes/index.d.ts +1 -0
  177. package/dist/modes/index.d.ts.map +1 -1
  178. package/dist/modes/index.js.map +1 -1
  179. package/dist/modes/interactive/components/assistant-message.d.ts +5 -2
  180. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  181. package/dist/modes/interactive/components/assistant-message.js +13 -4
  182. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  183. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  184. package/dist/modes/interactive/components/custom-editor.js +7 -0
  185. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  186. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  187. package/dist/modes/interactive/components/footer.js +1 -1
  188. package/dist/modes/interactive/components/footer.js.map +1 -1
  189. package/dist/modes/interactive/components/markdown-transform.d.ts +3 -0
  190. package/dist/modes/interactive/components/markdown-transform.d.ts.map +1 -0
  191. package/dist/modes/interactive/components/markdown-transform.js +19 -0
  192. package/dist/modes/interactive/components/markdown-transform.js.map +1 -0
  193. package/dist/modes/interactive/components/mermaid.d.ts +11 -0
  194. package/dist/modes/interactive/components/mermaid.d.ts.map +1 -0
  195. package/dist/modes/interactive/components/mermaid.js +75 -0
  196. package/dist/modes/interactive/components/mermaid.js.map +1 -0
  197. package/dist/modes/interactive/components/model-selector.d.ts +1 -1
  198. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  199. package/dist/modes/interactive/components/model-selector.js +20 -5
  200. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  201. package/dist/modes/interactive/components/scoped-models-selector.d.ts +4 -0
  202. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  203. package/dist/modes/interactive/components/scoped-models-selector.js +26 -0
  204. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  205. package/dist/modes/interactive/components/settings-selector.d.ts +8 -2
  206. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  207. package/dist/modes/interactive/components/settings-selector.js +30 -0
  208. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  209. package/dist/modes/interactive/components/user-message.d.ts +3 -1
  210. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  211. package/dist/modes/interactive/components/user-message.js +9 -2
  212. package/dist/modes/interactive/components/user-message.js.map +1 -1
  213. package/dist/modes/interactive/interactive-mode.d.ts +33 -2
  214. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  215. package/dist/modes/interactive/interactive-mode.js +443 -144
  216. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  217. package/dist/modes/interactive/theme/dark.json +1 -0
  218. package/dist/modes/interactive/theme/light.json +1 -0
  219. package/dist/modes/interactive/theme/theme-controller.d.ts +3 -0
  220. package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -1
  221. package/dist/modes/interactive/theme/theme-controller.js +10 -1
  222. package/dist/modes/interactive/theme/theme-controller.js.map +1 -1
  223. package/dist/modes/interactive/theme/theme-schema.json +5 -1
  224. package/dist/modes/interactive/theme/theme.d.ts +2 -2
  225. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  226. package/dist/modes/interactive/theme/theme.js +13 -3
  227. package/dist/modes/interactive/theme/theme.js.map +1 -1
  228. package/dist/modes/json-event.d.ts +28 -0
  229. package/dist/modes/json-event.d.ts.map +1 -0
  230. package/dist/modes/json-event.js +12 -0
  231. package/dist/modes/json-event.js.map +1 -0
  232. package/dist/modes/print-mode.d.ts.map +1 -1
  233. package/dist/modes/print-mode.js +12 -2
  234. package/dist/modes/print-mode.js.map +1 -1
  235. package/dist/modes/rpc/rpc-client.d.ts +5 -4
  236. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  237. package/dist/modes/rpc/rpc-client.js.map +1 -1
  238. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  239. package/dist/modes/rpc/rpc-mode.js +17 -3
  240. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  241. package/dist/package-manager-cli.d.ts.map +1 -1
  242. package/dist/package-manager-cli.js +11 -9
  243. package/dist/package-manager-cli.js.map +1 -1
  244. package/dist/rpc-entry.d.ts.map +1 -1
  245. package/dist/rpc-entry.js +1 -0
  246. package/dist/rpc-entry.js.map +1 -1
  247. package/dist/utils/abort.d.ts +5 -0
  248. package/dist/utils/abort.d.ts.map +1 -0
  249. package/dist/utils/abort.js +48 -0
  250. package/dist/utils/abort.js.map +1 -0
  251. package/dist/utils/clipboard.d.ts +1 -1
  252. package/dist/utils/clipboard.d.ts.map +1 -1
  253. package/dist/utils/clipboard.js +22 -2
  254. package/dist/utils/clipboard.js.map +1 -1
  255. package/dist/utils/management-http.d.ts +23 -0
  256. package/dist/utils/management-http.d.ts.map +1 -0
  257. package/dist/utils/management-http.js +50 -0
  258. package/dist/utils/management-http.js.map +1 -0
  259. package/dist/utils/paths.d.ts +3 -0
  260. package/dist/utils/paths.d.ts.map +1 -1
  261. package/dist/utils/paths.js +23 -1
  262. package/dist/utils/paths.js.map +1 -1
  263. package/dist/utils/tool-result-images.d.ts +19 -0
  264. package/dist/utils/tool-result-images.d.ts.map +1 -0
  265. package/dist/utils/tool-result-images.js +45 -0
  266. package/dist/utils/tool-result-images.js.map +1 -0
  267. package/dist/utils/tools-manager.d.ts.map +1 -1
  268. package/dist/utils/tools-manager.js +4 -6
  269. package/dist/utils/tools-manager.js.map +1 -1
  270. package/dist/utils/version-check.d.ts +4 -0
  271. package/dist/utils/version-check.d.ts.map +1 -1
  272. package/dist/utils/version-check.js +20 -2
  273. package/dist/utils/version-check.js.map +1 -1
  274. package/docs/compaction.md +2 -2
  275. package/docs/custom-provider.md +16 -8
  276. package/docs/extensions.md +30 -4
  277. package/docs/json.md +19 -14
  278. package/docs/keybindings.md +38 -10
  279. package/docs/models.md +24 -3
  280. package/docs/providers.md +2 -0
  281. package/docs/quickstart.md +2 -0
  282. package/docs/rpc.md +13 -11
  283. package/docs/sdk.md +21 -2
  284. package/docs/security.md +1 -1
  285. package/docs/session-format.md +2 -0
  286. package/docs/settings.md +4 -1
  287. package/docs/themes.md +5 -3
  288. package/docs/usage.md +7 -0
  289. package/examples/extensions/custom-compaction.ts +1 -16
  290. package/examples/extensions/custom-provider-anthropic/index.ts +10 -3
  291. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  292. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  293. package/examples/extensions/custom-provider-gitlab-duo/index.ts +2 -1
  294. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  295. package/examples/extensions/doom-overlay/wad-finder.ts +7 -3
  296. package/examples/extensions/gondolin/package-lock.json +2 -2
  297. package/examples/extensions/gondolin/package.json +1 -1
  298. package/examples/extensions/handoff.ts +2 -11
  299. package/examples/extensions/qna.ts +3 -7
  300. package/examples/extensions/sandbox/package-lock.json +2 -2
  301. package/examples/extensions/sandbox/package.json +1 -1
  302. package/examples/extensions/summarize.ts +7 -18
  303. package/examples/extensions/with-deps/package-lock.json +2 -2
  304. package/examples/extensions/with-deps/package.json +1 -1
  305. package/examples/rpc-extension-ui.ts +11 -2
  306. package/examples/sdk/02-custom-model.ts +1 -2
  307. package/examples/sdk/09-api-keys-and-oauth.ts +1 -1
  308. package/examples/sdk/12-full-control.ts +3 -1
  309. package/examples/sdk/README.md +1 -1
  310. package/npm-shrinkwrap.json +70 -26
  311. package/package.json +14 -7
@@ -331,8 +331,8 @@ pi.registerProvider("corporate-ai", {
331
331
  };
332
332
  },
333
333
 
334
- async refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {
335
- const tokens = await refreshAccessToken(credentials.refresh);
334
+ async refreshToken(credentials: OAuthCredentials, signal: AbortSignal): Promise<OAuthCredentials> {
335
+ const tokens = await refreshAccessToken(credentials.refresh, signal);
336
336
  return {
337
337
  refresh: tokens.refreshToken ?? credentials.refresh,
338
338
  access: tokens.accessToken,
@@ -442,7 +442,7 @@ function streamMyProvider(
442
442
  totalTokens: 0,
443
443
  cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
444
444
  },
445
- stopReason: "stop",
445
+ stopReason: "pending",
446
446
  timestamp: Date.now(),
447
447
  };
448
448
 
@@ -451,12 +451,18 @@ function streamMyProvider(
451
451
  stream.push({ type: "start", partial: output });
452
452
 
453
453
  // Make API request and process response...
454
- // Push content events as they arrive...
454
+ // Push content events as they arrive and set stopReason from the terminal event.
455
+ if (output.stopReason === "pending") {
456
+ throw new Error("Provider stream ended without a stop reason");
457
+ }
458
+ if (output.stopReason === "error" || output.stopReason === "aborted") {
459
+ throw new Error(output.errorMessage || "An unknown error occurred");
460
+ }
455
461
 
456
462
  // Push done event
457
463
  stream.push({
458
464
  type: "done",
459
- reason: output.stopReason as "stop" | "length" | "toolUse",
465
+ reason: output.stopReason,
460
466
  message: output
461
467
  });
462
468
  stream.end();
@@ -682,7 +688,7 @@ interface ProviderConfig {
682
688
  oauth?: {
683
689
  name: string;
684
690
  login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials>;
685
- refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials>;
691
+ refreshToken(credentials: OAuthCredentials, signal: AbortSignal): Promise<OAuthCredentials>;
686
692
  getApiKey(credentials: OAuthCredentials): string;
687
693
  };
688
694
  }
@@ -737,6 +743,7 @@ interface ProviderModelConfig {
737
743
  supportsDeveloperRole?: boolean;
738
744
  supportsReasoningEffort?: boolean;
739
745
  supportsUsageInStreaming?: boolean;
746
+ supportsFinishReason?: boolean;
740
747
  supportsStrictMode?: boolean;
741
748
  supportsOpenAIGrammarTools?: boolean; // openai-completions/openai-responses; false falls back to normal function tools
742
749
  maxTokensField?: "max_completion_tokens" | "max_tokens";
@@ -744,8 +751,9 @@ interface ProviderModelConfig {
744
751
  requiresAssistantAfterToolResult?: boolean;
745
752
  requiresThinkingAsText?: boolean;
746
753
  requiresReasoningContentOnAssistantMessages?: boolean;
747
- thinkingFormat?: "openai" | "openrouter" | "deepseek" | "together" | "zai" | "qwen" | "chat-template" | "qwen-chat-template" | "string-thinking" | "ant-ling";
754
+ thinkingFormat?: "openai" | "openrouter" | "deepseek" | "together" | "baseten" | "zai" | "qwen" | "chat-template" | "qwen-chat-template" | "string-thinking" | "ant-ling";
748
755
  chatTemplateKwargs?: Record<string, string | number | boolean | null | { "$var": "thinking.enabled" | "thinking.effort"; omitWhenOff?: boolean }>;
756
+ chatTemplateArgs?: Record<string, string | number | boolean | null | { "$var": "thinking.enabled" | "thinking.effort"; omitWhenOff?: boolean }>;
749
757
  cacheControlFormat?: "anthropic";
750
758
  sessionAffinityFormat?: "openai" | "openai-nosession" | "openrouter";
751
759
  sendSessionAffinityHeaders?: boolean;
@@ -762,5 +770,5 @@ interface ProviderModelConfig {
762
770
  }
763
771
  ```
764
772
 
765
- `openrouter` sends `reasoning: { effort }`. `deepseek` sends `thinking: { type: "enabled" | "disabled" }` and `reasoning_effort` when enabled. `together` sends `reasoning: { enabled }` and also `reasoning_effort` when `supportsReasoningEffort` is enabled. `qwen` is for DashScope-style top-level `enable_thinking`. Use `qwen-chat-template` for local Qwen-compatible servers that read `chat_template_kwargs.enable_thinking` and need `preserve_thinking`. Use `chat-template` for configurable `chat_template_kwargs`, for example DeepSeek V3.x behind vLLM with `chatTemplateKwargs: { "thinking": { "$var": "thinking.enabled" } }`.
773
+ `openrouter` sends `reasoning: { effort }`. `deepseek` sends `thinking: { type: "enabled" | "disabled" }` and `reasoning_effort` when enabled. `together` sends `reasoning: { enabled }` and also `reasoning_effort` when `supportsReasoningEffort` is enabled. `qwen` is for DashScope-style top-level `enable_thinking`. Use `qwen-chat-template` for local Qwen-compatible servers that read `chat_template_kwargs.enable_thinking` and need `preserve_thinking`. Use `chat-template` for configurable `chat_template_kwargs`, for example DeepSeek V3.x behind vLLM with `chatTemplateKwargs: { "thinking": { "$var": "thinking.enabled" } }`. Use `thinkingFormat: "baseten"` with `chatTemplateArgs` when the provider expects toggle values under `chat_template_args` and optionally supports top-level `reasoning_effort`.
766
774
  `cacheControlFormat: "anthropic"` applies Anthropic-style `cache_control` markers to the system prompt, last tool definition, and last user, assistant, or tool-result text content.
@@ -982,10 +982,12 @@ ctx.sessionManager.buildContextEntries() // Active branch entries with compac
982
982
  ctx.sessionManager.getLeafId() // Current leaf entry ID
983
983
  ```
984
984
 
985
- ### ctx.modelRegistry / ctx.model / ctx.thinkingLevel
985
+ ### ctx.modelRegistry / ctx.model / ctx.thinkingLevel / ctx.scopedModels
986
986
 
987
987
  Access to models, providers, and resolved authentication. `ctx.modelRegistry.getProvider(id)` returns the effective pi-ai provider, while `getProviderAuth(id)` resolves its current API key, headers, base URL, and provider-scoped environment without requiring a loaded model. `ctx.model` is the active model, and `ctx.thinkingLevel` is its current effective thinking level.
988
988
 
989
+ `ctx.scopedModels` is the read-only list of models scoped to the current session — the same set the `/scoped-models` command shows. It is resolved at session start from the `--models` CLI flag and the `enabledModels` setting (matched against the available catalogue with minimatch on `provider/modelId` or a bare `modelId`). It is empty when no scoping is configured, meaning every available model is usable. Each entry is `{ model, thinkingLevel? }`, where `thinkingLevel` is set only when a pattern pinned it (e.g. `anthropic/*:high`). Use it to populate a model picker that mirrors the built-in one instead of enumerating the whole catalogue via `ctx.modelRegistry.getAvailable()`.
990
+
989
991
  ### ctx.signal
990
992
 
991
993
  The current agent abort signal, or `undefined` when no agent turn is active.
@@ -1560,6 +1562,27 @@ mode and would not execute if sent via `prompt`.
1560
1562
 
1561
1563
  Register a custom TUI renderer for custom messages with your `customType`. Custom messages are created with `pi.sendMessage()` and participate in LLM context. See [Custom UI](#custom-ui).
1562
1564
 
1565
+ ### pi.registerMarkdownTransformer(transformer)
1566
+
1567
+ Register a transformer for the Markdown in normal user text, assistant text, and thinking blocks. Transformers run in extension load order, and each transformer receives the Markdown returned by the previous transformer. After the chain finishes, Pi renders the transformed content with its built-in renderer.
1568
+
1569
+ The transformer receives the Markdown string and a context with:
1570
+
1571
+ - `messageType` — `"user"`, `"assistant"`, or `"assistant-thinking"`
1572
+ - `isStreaming` — `true` for partial assistant updates; `false` for user, finalized assistant, and restored messages
1573
+ - `availableWidth` — exact terminal columns available for the transformed Markdown content
1574
+
1575
+ Return the transformed Markdown:
1576
+
1577
+ ```typescript
1578
+ pi.registerMarkdownTransformer((markdown, { messageType, isStreaming }) => {
1579
+ if (isStreaming || messageType === "assistant-thinking") return markdown;
1580
+ return markdown.replaceAll("-->", "→");
1581
+ });
1582
+ ```
1583
+
1584
+ If a transformer throws, Pi keeps the Markdown produced so far and continues with the next transformer. The hook is display-only: the original message remains unchanged in the session and model context. It runs for new user messages, assistant streaming updates, restored session messages, and terminal width changes, so transformers should remain synchronous and inexpensive.
1585
+
1563
1586
  ### pi.registerEntryRenderer(customType, renderer)
1564
1587
 
1565
1588
  Register a custom TUI renderer for custom entries with your `customType`. Custom entries are created with `pi.appendEntry()` and do not participate in LLM context.
@@ -1684,7 +1707,9 @@ Register or override a model provider dynamically. Useful for proxies, custom en
1684
1707
 
1685
1708
  Calls made during the extension factory function are queued and applied once the runner initialises. Calls made after that — for example from a command handler following a user setup flow — take effect immediately without requiring a `/reload`.
1686
1709
 
1687
- Dynamic providers can implement `refreshModels`. Pi calls it during model refresh, publishes the returned list synchronously through the provider, and passes the canonical credential/store/network/signal context. The extension decides whether to persist the catalog through `context.store`; live servers such as llama.cpp can ignore it.
1710
+ Dynamic providers can implement `refreshModels`. Pi calls it during model refresh, publishes the returned list synchronously through the provider, and passes the canonical credential/stored-catalog/network/signal context. The extension decides whether to persist catalog metadata through generation-checked `context.publish({ persist: entry })`; live servers such as llama.cpp can return models without persisting them.
1711
+
1712
+ `context.signal` is always a concrete signal and provider callbacks must pass it to blocking I/O. Public `ModelRuntime.refresh()` and `ModelRegistry.refresh()` calls accept an optional signal and are unbounded when it is omitted; extensions and applications choose their own deadlines. Cancellation stops the caller waiting even if a provider ignores the signal, but cooperation is still required to stop the underlying work.
1688
1713
 
1689
1714
  Extensions that need native provider auth, filtering, refresh, or stream behavior can register a complete `Provider` from `@earendil-works/pi-ai`. The provider becomes the composition base and `models.json` overrides still apply above it.
1690
1715
 
@@ -1774,7 +1799,8 @@ pi.registerProvider("corporate-ai", {
1774
1799
  const code = await callbacks.onPrompt({ message: "Enter code:" });
1775
1800
  return { refresh: code, access: code, expires: Date.now() + 3600000 };
1776
1801
  },
1777
- async refreshToken(credentials) {
1802
+ async refreshToken(credentials, signal) {
1803
+ signal.throwIfAborted();
1778
1804
  // Refresh logic
1779
1805
  return credentials;
1780
1806
  },
@@ -1795,7 +1821,7 @@ The object form accepts a complete pi-ai `Provider`, including native `auth`, `g
1795
1821
  - `headers` - Custom headers to include in requests.
1796
1822
  - `authHeader` - If true, adds `Authorization: Bearer` header automatically.
1797
1823
  - `models` - Array of model definitions. If provided, replaces all existing models for this provider. Model definitions can set `baseUrl` to override the provider endpoint for that model.
1798
- - `refreshModels` - Async dynamic discovery callback. Its returned models replace extension-provided models. Use the scoped `context.store` only when results should persist.
1824
+ - `refreshModels` - Async dynamic discovery callback. Its returned models replace extension-provided models. `context.stored` contains the persisted provider snapshot; use generation-checked `context.publish({ persist: entry })` only when updated catalog data should persist. Use `persist: null` to delete that snapshot.
1799
1825
  - `oauth` - OAuth provider config for `/login` support. When provided, the provider appears in the login menu.
1800
1826
  - `streamSimple` - Custom streaming implementation for non-standard APIs.
1801
1827
 
package/docs/json.md CHANGED
@@ -8,25 +8,25 @@ Outputs all session events as JSON lines to stdout. Useful for integrating pi in
8
8
 
9
9
  ## Event Types
10
10
 
11
- Events are defined in [`AgentSessionEvent`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/agent-session.ts#L102):
11
+ Wire events use `JsonAgentSessionEvent`. It matches
12
+ [`AgentSessionEvent`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/agent-session.ts)
13
+ except that streaming message updates omit cumulative snapshots:
12
14
 
13
15
  ```typescript
14
- type AgentSessionEvent =
15
- | AgentEvent
16
- | { type: "queue_update"; steering: readonly string[]; followUp: readonly string[] }
17
- | { type: "compaction_start"; reason: "manual" | "threshold" | "overflow" }
18
- | { type: "compaction_end"; reason: "manual" | "threshold" | "overflow"; result: CompactionResult | undefined; aborted: boolean; willRetry: boolean; errorMessage?: string }
19
- | { type: "auto_retry_start"; attempt: number; maxAttempts: number; delayMs: number; errorMessage: string }
20
- | { type: "auto_retry_end"; success: boolean; attempt: number; finalError?: string }
21
- | { type: "summarization_retry_scheduled"; attempt: number; maxAttempts: number; delayMs: number; errorMessage: string }
22
- | { type: "summarization_retry_attempt_start"; source: "branchSummary" }
23
- | { type: "summarization_retry_attempt_start"; source: "compaction"; reason: "manual" | "threshold" | "overflow" }
24
- | { type: "summarization_retry_finished" };
16
+ type WithoutPartial<T> = T extends { partial: unknown } ? Omit<T, "partial"> : T;
17
+
18
+ type JsonAgentSessionEvent =
19
+ | Exclude<AgentSessionEvent, { type: "message_update" }>
20
+ | {
21
+ type: "message_update";
22
+ assistantMessageEvent: WithoutPartial<AssistantMessageEvent>;
23
+ };
25
24
  ```
26
25
 
27
26
  `queue_update` emits the full pending steering and follow-up queues whenever they change. `compaction_start` and `compaction_end` cover both manual and automatic compaction.
28
27
 
29
- Base events from [`AgentEvent`](https://github.com/earendil-works/pi-mono/blob/main/packages/agent/src/types.ts#L179):
28
+ Other base events come from
29
+ [`AgentEvent`](https://github.com/earendil-works/pi-mono/blob/main/packages/agent/src/types.ts):
30
30
 
31
31
  ```typescript
32
32
  type AgentEvent =
@@ -73,12 +73,17 @@ Followed by events as they occur:
73
73
  {"type":"agent_start"}
74
74
  {"type":"turn_start"}
75
75
  {"type":"message_start","message":{"role":"assistant","content":[],...}}
76
- {"type":"message_update","message":{...},"assistantMessageEvent":{"type":"text_delta","delta":"Hello",...}}
76
+ {"type":"message_update","assistantMessageEvent":{"type":"text_delta","contentIndex":0,"delta":"Hello"}}
77
77
  {"type":"message_end","message":{...}}
78
78
  {"type":"turn_end","message":{...},"toolResults":[]}
79
79
  {"type":"agent_end","messages":[...]}
80
80
  ```
81
81
 
82
+ `message_update` records are delta-only. They omit both the cumulative `message` field and
83
+ `assistantMessageEvent.partial` to keep stream size linear. Use `contentIndex` and `delta`
84
+ to assemble live text, thinking, or tool-call arguments if needed. `message_end` contains
85
+ the final authoritative message.
86
+
82
87
  ## Example
83
88
 
84
89
  ```bash
@@ -26,18 +26,22 @@ Modifier combinations: `ctrl+shift+x`, `alt+ctrl+x`, `ctrl+shift+alt+x`, `ctrl+1
26
26
 
27
27
  | Keybinding id | Default | Description |
28
28
  |--------|---------|-------------|
29
- | `tui.editor.cursorUp` | `up` | Move cursor up |
30
- | `tui.editor.cursorDown` | `down` | Move cursor down |
29
+ | `tui.editor.cursorUp` | `up` | Move cursor up, browsing older history at the top |
30
+ | `tui.editor.cursorDown` | `down` | Move cursor down, browsing newer history at the bottom |
31
+ | `tui.editor.historyPrevious` | *(none)* | Select the previous prompt history entry |
32
+ | `tui.editor.historyNext` | *(none)* | Select the next prompt history entry |
31
33
  | `tui.editor.cursorLeft` | `left`, `ctrl+b` | Move cursor left |
32
34
  | `tui.editor.cursorRight` | `right`, `ctrl+f` | Move cursor right |
33
35
  | `tui.editor.cursorWordLeft` | `alt+left`, `ctrl+left`, `alt+b` | Move cursor word left |
34
36
  | `tui.editor.cursorWordRight` | `alt+right`, `ctrl+right`, `alt+f` | Move cursor word right |
35
- | `tui.editor.cursorLineStart` | `home`, `ctrl+a` | Move to line start |
36
- | `tui.editor.cursorLineEnd` | `end`, `ctrl+e` | Move to line end |
37
+ | `tui.editor.cursorLineStart` | `home`, `ctrl+home`, `ctrl+a` | Move to line start |
38
+ | `tui.editor.cursorLineEnd` | `end`, `ctrl+end`, `ctrl+e` | Move to line end |
37
39
  | `tui.editor.jumpForward` | `ctrl+]` | Jump forward to character |
38
40
  | `tui.editor.jumpBackward` | `ctrl+alt+]` | Jump backward to character |
39
- | `tui.editor.pageUp` | `pageUp` | Scroll up by page |
40
- | `tui.editor.pageDown` | `pageDown` | Scroll down by page |
41
+ | `tui.editor.pageUp` | `pageUp`, `ctrl+pageUp` | Scroll up by page |
42
+ | `tui.editor.pageDown` | `pageDown`, `ctrl+pageDown` | Scroll down by page |
43
+
44
+ The dedicated history actions always change history entries, regardless of the cursor position in a multiline prompt. Explicit history bindings take precedence over application actions while the main editor is focused, so binding `tui.editor.historyPrevious` to `ctrl+p` overrides model cycling in that context without changing `Ctrl+P` in selectors.
41
45
 
42
46
  ### TUI Editor Deletion
43
47
 
@@ -78,6 +82,30 @@ Modifier combinations: `ctrl+shift+x`, `alt+ctrl+x`, `ctrl+shift+alt+x`, `ctrl+1
78
82
  | `tui.select.confirm` | `enter` | Confirm selection |
79
83
  | `tui.select.cancel` | `escape`, `ctrl+c` | Cancel selection |
80
84
 
85
+ ### TUI Fullscreen Viewport
86
+
87
+ These actions apply when interactive mode uses `--tui-mode fullscreen` and target the primary transcript scroll region. Two-finger trackpad and mouse-wheel input scroll the region under the pointer, falling back to the transcript over the fixed editor/status/footer dock. Clicking an OSC 8 hyperlink opens it in the default handler. Dragging with the primary mouse button selects text and copies it to the clipboard; holding at the transcript's top or bottom edge auto-scrolls into off-screen content.
88
+
89
+ Fullscreen transcript bindings take precedence over editor bindings. The default unmodified navigation keys therefore control the transcript in fullscreen mode, while their `ctrl` variants continue to control the editor. Outside fullscreen mode, both variants control the editor.
90
+
91
+ | Key | Default mode | Fullscreen mode |
92
+ |-----|--------------|-----------------|
93
+ | `home`, `end` | Editor | Transcript |
94
+ | `ctrl+home`, `ctrl+end` | Editor | Editor |
95
+ | `pageUp`, `pageDown` | Editor | Transcript |
96
+ | `ctrl+pageUp`, `ctrl+pageDown` | Editor | Editor |
97
+
98
+ This routing remains configurable through the ordinary action bindings. For example, `"tui.altScreen.pageUp": "ctrl+pageUp"` makes `pageUp` control the editor and `ctrl+pageUp` control the transcript in fullscreen mode. Setting `"tui.altScreen.pageUp": []` disables that transcript shortcut entirely. User bindings replace the defaults for that action.
99
+
100
+ | Keybinding id | Default | Description |
101
+ |--------|---------|-------------|
102
+ | `tui.altScreen.pageUp` | `pageUp` | Scroll the transcript up by one page |
103
+ | `tui.altScreen.pageDown` | `pageDown` | Scroll the transcript down by one page |
104
+ | `tui.altScreen.previousPrompt` | `ctrl+shift+up` | Jump to the previous marked message |
105
+ | `tui.altScreen.nextPrompt` | `ctrl+shift+down` | Jump to the next marked message |
106
+ | `tui.altScreen.top` | `home` | Scroll to the beginning of the transcript |
107
+ | `tui.altScreen.bottom` | `end` | Scroll to the transcript end and follow new output |
108
+
81
109
  ### Application
82
110
 
83
111
  | Keybinding id | Default | Description |
@@ -158,8 +186,8 @@ Create `~/.pi/agent/keybindings.json`:
158
186
 
159
187
  ```json
160
188
  {
161
- "tui.editor.cursorUp": ["up", "ctrl+p"],
162
- "tui.editor.cursorDown": ["down", "ctrl+n"],
189
+ "tui.editor.historyPrevious": "ctrl+p",
190
+ "tui.editor.historyNext": "ctrl+n",
163
191
  "tui.editor.deleteWordBackward": ["ctrl+w", "alt+backspace"]
164
192
  }
165
193
  ```
@@ -172,8 +200,8 @@ On native Windows, `app.suspend` has no default binding because Windows terminal
172
200
 
173
201
  ```json
174
202
  {
175
- "tui.editor.cursorUp": ["up", "ctrl+p"],
176
- "tui.editor.cursorDown": ["down", "ctrl+n"],
203
+ "tui.editor.historyPrevious": "ctrl+p",
204
+ "tui.editor.historyNext": "ctrl+n",
177
205
  "tui.editor.cursorLeft": ["left", "ctrl+b"],
178
206
  "tui.editor.cursorRight": ["right", "ctrl+f"],
179
207
  "tui.editor.cursorWordLeft": ["alt+left", "alt+b"],
package/docs/models.md CHANGED
@@ -206,6 +206,7 @@ If your command is slow, expensive, rate-limited, or should keep using a previou
206
206
  | `input` | No | `["text"]` | Input types: `["text"]` or `["text", "image"]` |
207
207
  | `contextWindow` | No | `128000` | Context window size in tokens |
208
208
  | `maxTokens` | No | `16384` | Maximum output tokens |
209
+ | `samplingParams` | No | omitted | Sampling parameters merged verbatim into every request body (see below) |
209
210
  | `cost` | No | all zeros | Per-million-token rates with optional request-wide input pricing tiers |
210
211
  | `compat` | No | provider `compat` | Provider compatibility overrides. Merged with provider-level `compat` when both are set. |
211
212
 
@@ -235,6 +236,24 @@ Current behavior:
235
236
  - `/model`, `--list-models`, and the interactive footer display entries by model `id`.
236
237
  - The configured `name` is used for model matching and secondary model detail text. It does not replace the footer/status-bar model id.
237
238
 
239
+ ### Sampling Parameters
240
+
241
+ `samplingParams` is a free-form object merged verbatim into every request body for the model, after the fields pi sets itself, so its keys win. Use it to send sampling parameters pi does not model — including server-specific ones like llama.cpp's `min_p` or vLLM's `top_k`:
242
+
243
+ ```json
244
+ {
245
+ "id": "deepseek-v4-flash",
246
+ "samplingParams": {
247
+ "temperature": 1.0,
248
+ "top_p": 0.95,
249
+ "top_k": 0,
250
+ "min_p": 0.0
251
+ }
252
+ }
253
+ ```
254
+
255
+ Only OpenAI-compatible APIs apply it (`openai-completions`, `openai-responses`, `azure-openai-responses`); other APIs ignore it. Keys override pi's named request fields (for example a `temperature` key here beats the request-level temperature), so prefer it as the single source of sampling truth for a model. In `modelOverrides`, `samplingParams` merges per key with the base model's value.
256
+
238
257
  ### Thinking Level Map
239
258
 
240
259
  Use `thinkingLevelMap` on a model to describe model-specific thinking controls. Keys are pi thinking levels: `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`. Maps may contain holes; for example, a model can expose `high` and `max` without exposing `xhigh`.
@@ -338,7 +357,7 @@ Use `modelOverrides` to customize built-in models and matching extension-registe
338
357
  }
339
358
  ```
340
359
 
341
- `modelOverrides` supports these fields per model: `name`, `reasoning`, `thinkingLevelMap`, `input`, `cost` (partial), `contextWindow`, `maxTokens`, `headers`, `compat`.
360
+ `modelOverrides` supports these fields per model: `name`, `reasoning`, `thinkingLevelMap`, `input`, `cost` (partial), `contextWindow`, `maxTokens`, `samplingParams` (merged per key), `headers`, `compat`.
342
361
 
343
362
  Direct OpenAI GPT-5.6 Sol, Terra, and Luna default to a `272000` context window so requests remain within OpenAI's short-context pricing tier. To opt into OpenAI's 1.05M context window, increase it for each model you use:
344
363
 
@@ -441,13 +460,15 @@ For providers with partial OpenAI compatibility, use the `compat` field.
441
460
  | `supportsDeveloperRole` | Use `developer` vs `system` role |
442
461
  | `supportsReasoningEffort` | Support for `reasoning_effort` parameter |
443
462
  | `supportsUsageInStreaming` | Supports `stream_options: { include_usage: true }` (default: `true`) |
463
+ | `supportsFinishReason` | Whether streamed responses include `finish_reason`. When `false`, pi infers `stop` or `toolUse` when the stream ends. Default: `true`. |
444
464
  | `maxTokensField` | Use `max_completion_tokens` or `max_tokens` |
445
465
  | `requiresToolResultName` | Include `name` on tool result messages |
446
466
  | `requiresAssistantAfterToolResult` | Insert an assistant message before a user message after tool results |
447
467
  | `requiresThinkingAsText` | Convert thinking blocks to plain text |
448
468
  | `requiresReasoningContentOnAssistantMessages` | Include empty `reasoning_content` on all replayed assistant messages when reasoning is enabled |
449
- | `thinkingFormat` | Use `reasoning_effort`, `openrouter`, `deepseek`, `together`, `zai`, `qwen`, `chat-template`, or `qwen-chat-template` thinking parameters |
469
+ | `thinkingFormat` | Use `reasoning_effort`, `openrouter`, `deepseek`, `together`, `baseten`, `zai`, `qwen`, `chat-template`, or `qwen-chat-template` thinking parameters |
450
470
  | `chatTemplateKwargs` | `chat_template_kwargs` values for `thinkingFormat: "chat-template"`; use `{ "$var": "thinking.enabled" }` or `{ "$var": "thinking.effort" }` for pi-controlled thinking values |
471
+ | `chatTemplateArgs` | `chat_template_args` values for `thinkingFormat: "baseten"`; use `{ "$var": "thinking.enabled" }` or `{ "$var": "thinking.effort" }` for pi-controlled thinking values |
451
472
  | `cacheControlFormat` | Use Anthropic-style `cache_control` markers on the system prompt, last tool definition, and last user, assistant, or tool-result text content. Currently only `anthropic` is supported. |
452
473
  | `sendSessionAffinityHeaders` | For `openai-completions`, send session-affinity headers from the session id when caching is enabled. Default: `false`. |
453
474
  | `sessionAffinityFormat` | For `openai-completions` and `openai-responses`, the session-affinity header format: `openai` sends `session_id`/`x-client-request-id` (completions also `x-session-affinity`), `openai-nosession` omits the underscore-containing `session_id` header, `openrouter` sends `x-session-id`. Does not affect the `prompt_cache_key` body param. Default: auto-detected. |
@@ -458,7 +479,7 @@ For providers with partial OpenAI compatibility, use the `compat` field.
458
479
  | `openRouterRouting` | OpenRouter provider routing preferences. This object is sent as-is in the `provider` field of the [OpenRouter API request](https://openrouter.ai/docs/guides/routing/provider-selection). |
459
480
  | `vercelGatewayRouting` | Vercel AI Gateway routing config for provider selection (`only`, `order`) |
460
481
 
461
- `openrouter` uses `reasoning: { effort }`. `together` uses `reasoning: { enabled }` and also `reasoning_effort` when `supportsReasoningEffort` is enabled. `qwen` uses top-level `enable_thinking`. Use `qwen-chat-template` for local Qwen-compatible servers that require `chat_template_kwargs.enable_thinking` and `preserve_thinking`. Use `chat-template` for vLLM/Hugging Face chat templates that need configurable `chat_template_kwargs`, such as `chatTemplateKwargs: { "thinking": { "$var": "thinking.enabled" } }` for DeepSeek V3.x templates.
482
+ `openrouter` uses `reasoning: { effort }`. `together` uses `reasoning: { enabled }` and also `reasoning_effort` when `supportsReasoningEffort` is enabled. `qwen` uses top-level `enable_thinking`. Use `qwen-chat-template` for local Qwen-compatible servers that require `chat_template_kwargs.enable_thinking` and `preserve_thinking`. Use `chat-template` for vLLM/Hugging Face chat templates that need configurable `chat_template_kwargs`, such as `chatTemplateKwargs: { "thinking": { "$var": "thinking.enabled" } }` for DeepSeek V3.x templates. Use `thinkingFormat: "baseten"` with `chatTemplateArgs` for providers that expose toggle controls through `chat_template_args` and optionally support top-level `reasoning_effort`.
462
483
 
463
484
  `cacheControlFormat: "anthropic"` is for OpenAI-compatible providers that expose Anthropic-style prompt caching through `cache_control` markers on text content and tool definitions.
464
485
 
package/docs/providers.md CHANGED
@@ -48,6 +48,7 @@ Anthropic subscription auth is active for Claude Pro/Max accounts. Third-party h
48
48
 
49
49
  - Run `/login openrouter`, then select **Sign in with OpenRouter** to open the OpenRouter PKCE authorization flow
50
50
  - The authorization creates a user-controlled OpenRouter API key billed from your OpenRouter credits
51
+ - On remote/headless machines (e.g. over SSH) the browser cannot reach the loopback callback; paste the final redirect URL (or the authorization code) into the login prompt instead
51
52
  - `OPENROUTER_API_KEY` remains available through **Use an API key**
52
53
 
53
54
  ### Radius
@@ -91,6 +92,7 @@ pi
91
92
  | Hugging Face | `HF_TOKEN` | `huggingface` |
92
93
  | Fireworks | `FIREWORKS_API_KEY` | `fireworks` |
93
94
  | Together AI | `TOGETHER_API_KEY` | `together` |
95
+ | Baseten | `BASETEN_API_KEY` | `baseten` |
94
96
  | Kimi For Coding | `KIMI_API_KEY` | `kimi-coding` |
95
97
  | MiniMax | `MINIMAX_API_KEY` | `minimax` |
96
98
  | MiniMax (China) | `MINIMAX_CN_API_KEY` | `minimax-cn` |
@@ -100,6 +100,8 @@ Pi loads:
100
100
  - `~/.pi/agent/AGENTS.md` for global instructions
101
101
  - `AGENTS.md` or `CLAUDE.md` from parent directories and the current directory
102
102
 
103
+ If a directory contains `AGENTS.override.md`, Pi loads it instead of `AGENTS.md` or `CLAUDE.md` from that directory.
104
+
103
105
  Restart pi, or run `/reload`, after changing context files.
104
106
 
105
107
  ## Common things to try
package/docs/rpc.md CHANGED
@@ -914,17 +914,15 @@ Emitted when a message begins and completes. The `message` field contains an `Ag
914
914
 
915
915
  ### message_update (Streaming)
916
916
 
917
- Emitted during streaming of assistant messages. Contains both the partial message and a streaming delta event.
917
+ Emitted during streaming of assistant messages. Contains a delta event without a cumulative message snapshot.
918
918
 
919
919
  ```json
920
920
  {
921
921
  "type": "message_update",
922
- "message": {...},
923
922
  "assistantMessageEvent": {
924
923
  "type": "text_delta",
925
924
  "contentIndex": 0,
926
- "delta": "Hello ",
927
- "partial": {...}
925
+ "delta": "Hello "
928
926
  }
929
927
  }
930
928
  ```
@@ -933,7 +931,6 @@ The `assistantMessageEvent` field contains one of these delta types:
933
931
 
934
932
  | Type | Description |
935
933
  |------|-------------|
936
- | `start` | Message generation started |
937
934
  | `text_start` | Text content block started |
938
935
  | `text_delta` | Text content chunk |
939
936
  | `text_end` | Text content block ended |
@@ -943,17 +940,21 @@ The `assistantMessageEvent` field contains one of these delta types:
943
940
  | `toolcall_start` | Tool call started |
944
941
  | `toolcall_delta` | Tool call arguments chunk |
945
942
  | `toolcall_end` | Tool call ended (includes full `toolCall` object) |
946
- | `done` | Message complete (reason: `"stop"`, `"length"`, `"toolUse"`) |
947
- | `error` | Error occurred (reason: `"aborted"`, `"error"`) |
948
943
 
949
944
  Example streaming a text response:
950
945
  ```json
951
- {"type":"message_update","message":{...},"assistantMessageEvent":{"type":"text_start","contentIndex":0,"partial":{...}}}
952
- {"type":"message_update","message":{...},"assistantMessageEvent":{"type":"text_delta","contentIndex":0,"delta":"Hello","partial":{...}}}
953
- {"type":"message_update","message":{...},"assistantMessageEvent":{"type":"text_delta","contentIndex":0,"delta":" world","partial":{...}}}
954
- {"type":"message_update","message":{...},"assistantMessageEvent":{"type":"text_end","contentIndex":0,"content":"Hello world","partial":{...}}}
946
+ {"type":"message_update","assistantMessageEvent":{"type":"text_start","contentIndex":0}}
947
+ {"type":"message_update","assistantMessageEvent":{"type":"text_delta","contentIndex":0,"delta":"Hello"}}
948
+ {"type":"message_update","assistantMessageEvent":{"type":"text_delta","contentIndex":0,"delta":" world"}}
949
+ {"type":"message_update","assistantMessageEvent":{"type":"text_end","contentIndex":0,"content":"Hello world"}}
955
950
  ```
956
951
 
952
+ `message_update` intentionally omits the former cumulative `message` field and
953
+ `assistantMessageEvent.partial`. Clients that need a live partial message must assemble it
954
+ from `message_start` and subsequent events using `contentIndex`. Treat `message_end.message`
955
+ as authoritative. For tool calls, buffer `toolcall_delta.delta`; `toolcall_end.toolCall`
956
+ contains the completed call.
957
+
957
958
  ### bash_execution_update
958
959
 
959
960
  Emitted once for each output chunk from a direct `bash` command. `id` matches the command's `id`, allowing clients to associate output with the correct command.
@@ -1362,6 +1363,7 @@ Source files:
1362
1363
  - [`packages/ai/src/types.ts`](../../ai/src/types.ts) - `Model`, `UserMessage`, `AssistantMessage`, `ToolResultMessage`
1363
1364
  - [`packages/agent/src/types.ts`](../../agent/src/types.ts) - `AgentMessage`, `AgentEvent`
1364
1365
  - [`src/core/messages.ts`](../src/core/messages.ts) - `BashExecutionMessage`
1366
+ - [`src/modes/json-event.ts`](../src/modes/json-event.ts) - `JsonAgentSessionEvent`
1365
1367
  - [`src/modes/rpc/rpc-types.ts`](../src/modes/rpc/rpc-types.ts) - RPC command/response types, extension UI request/response types
1366
1368
 
1367
1369
  ### Model
package/docs/sdk.md CHANGED
@@ -452,7 +452,7 @@ for (const provider of modelRuntime.getProviders()) {
452
452
  }
453
453
 
454
454
  // Runtime API key override (not persisted to disk)
455
- modelRuntime.setRuntimeApiKey("anthropic", "sk-my-temp-key");
455
+ await modelRuntime.setRuntimeApiKey("anthropic", "sk-my-temp-key");
456
456
 
457
457
  // Custom credential and model locations
458
458
  const customRuntime = await ModelRuntime.create({
@@ -469,6 +469,24 @@ const { session } = await createAgentSession({
469
469
  });
470
470
  ```
471
471
 
472
+ `login()`, `logout()`, `setRuntimeApiKey()`, and `removeRuntimeApiKey()` resolve after the affected provider's cached/built-in catalog, composition, and availability snapshot are locally consistent. They do not wait for remote catalog freshness. If credentials were committed but local synchronization fails, they reject with the exported `CredentialSynchronizationError`; inspect its `providerId`, `operation`, `credential`, and `cause` fields instead of retrying the credential mutation blindly.
473
+
474
+ Public model/auth operations and `ModelRuntime.create({ signal })` accept optional abort signals and are unbounded when omitted. SDK applications own deadline policy for remote catalog freshness:
475
+
476
+ ```typescript
477
+ const signal = AbortSignal.timeout(15_000);
478
+ const result = await modelRuntime.refresh({
479
+ providers: ["anthropic"],
480
+ signal,
481
+ });
482
+ if (result.aborted) console.warn("Catalog refresh timed out; using cached models");
483
+ for (const [providerId, error] of result.errors) {
484
+ console.warn(`Could not refresh ${providerId}:`, error);
485
+ }
486
+ ```
487
+
488
+ A failed or timed-out network refresh does not undo a successful credential operation. `refresh()` starts a new provider generation, so it does not wait behind an older stalled refresh and stale generations cannot publish afterward.
489
+
472
490
  > See [examples/sdk/09-api-keys-and-oauth.ts](../examples/sdk/09-api-keys-and-oauth.ts)
473
491
 
474
492
  ### System Prompt
@@ -938,7 +956,7 @@ const modelRuntime = await ModelRuntime.create({
938
956
  modelsPath: "/custom/agent/models.json",
939
957
  });
940
958
  if (process.env.MY_KEY) {
941
- modelRuntime.setRuntimeApiKey("anthropic", process.env.MY_KEY);
959
+ await modelRuntime.setRuntimeApiKey("anthropic", process.env.MY_KEY);
942
960
  }
943
961
 
944
962
  // Inline tool
@@ -1144,6 +1162,7 @@ AgentSessionRuntime
1144
1162
  // Auth and Models
1145
1163
  ModelRuntime // implements pi-ai Models and owns credential storage
1146
1164
  ModelRegistry // synchronous extension compatibility facade
1165
+ CredentialSynchronizationError
1147
1166
  resolveCliModel
1148
1167
  resolveModelScopeWithDiagnostics
1149
1168
 
package/docs/security.md CHANGED
@@ -24,7 +24,7 @@ Trusting a project allows pi to load project resources that require trust, inclu
24
24
  - missing project packages configured through project settings
25
25
  - project-local extensions and project package-managed extensions
26
26
 
27
- Declining trust skips protected resources. `AGENTS.md` and `CLAUDE.md` context files are loaded regardless of project trust unless context loading is disabled. Before trust is resolved, pi only loads context files, user/global extensions, and CLI `-e` extensions. User/global and CLI extensions can handle the `project_trust` event; the first extension that returns a yes/no decision owns the decision.
27
+ Declining trust skips protected resources. Context files such as `AGENTS.override.md`, `AGENTS.md`, and `CLAUDE.md` are loaded regardless of project trust unless context loading is disabled. Before trust is resolved, pi only loads context files, user/global extensions, and CLI `-e` extensions. User/global and CLI extensions can handle the `project_trust` event; the first extension that returns a yes/no decision owns the decision.
28
28
 
29
29
  Non-interactive modes (`-p`, `--mode json`, and `--mode rpc`) do not show a trust prompt. Without an applicable saved trust decision, `defaultProjectTrust: "ask"` and `"never"` ignore such resources, while `"always"` trusts them. Use `--approve`/`-a` or `--no-approve`/`-na` to override project trust for one run.
30
30
 
@@ -117,6 +117,8 @@ interface Usage {
117
117
  }
118
118
  ```
119
119
 
120
+ The exported pi-ai `StopReason` type also includes `"pending"`, but that value is reserved for partial messages in streaming events. Terminal `done`/`error` messages replace it with a completion reason before pi persists the assistant message, so `"pending"` should never appear in session JSONL.
121
+
120
122
  ### Extended Message Types (from pi-coding-agent)
121
123
 
122
124
  ```typescript
package/docs/settings.md CHANGED
@@ -65,6 +65,8 @@ Use `/trust` in interactive mode to save a project trust decision for future ses
65
65
  | `outputPad` | number | `1` | Horizontal padding for user messages, assistant messages, and thinking (0 or 1) |
66
66
  | `autocompleteMaxVisible` | number | `5` | Max visible items in autocomplete dropdown (3-20) |
67
67
  | `showHardwareCursor` | boolean | `false` | Show the terminal cursor while TUI positions it for IME support |
68
+ | `tuiMode` | string | `"regular"` | Interactive TUI mode: `"regular"` or experimental `"fullscreen"`. Changes from `/settings` apply immediately; `--tui-mode` overrides this setting at startup |
69
+ | `fullscreenScrollbar` | string | `"auto"` | Fullscreen transcript scrollbar: `"auto"` shows it temporarily while scrolling, `"always"` reserves the rightmost column and keeps it visible, and `"hidden"` hides it. Has no effect in regular TUI mode |
68
70
 
69
71
  For VS Code, include `--wait` so pi resumes after the editor exits:
70
72
 
@@ -178,7 +180,7 @@ Keep `retry.provider.maxRetries` at `0` unless provider-level retries are explic
178
180
  | `terminal.showImages` | boolean | `true` | Show images in terminal (if supported) |
179
181
  | `terminal.imageWidthCells` | number | `60` | Preferred inline image width in terminal cells |
180
182
  | `terminal.clearOnShrink` | boolean | `false` | Clear empty rows when content shrinks (can cause flicker) |
181
- | `images.autoResize` | boolean | `true` | Resize images to 2000x2000 max |
183
+ | `images.autoResize` | boolean | `true` | Resize images to 2000x2000 max. Applies to `@file` attachments, `read`, and images returned by tools |
182
184
  | `images.blockImages` | boolean | `false` | Block all images from being sent to LLM |
183
185
 
184
186
  ### Shell
@@ -226,6 +228,7 @@ When multiple sources specify a session directory, precedence is `--session-dir`
226
228
  | Setting | Type | Default | Description |
227
229
  |---------|------|---------|-------------|
228
230
  | `markdown.codeBlockIndent` | string | `" "` | Indentation for code blocks |
231
+ | `markdown.mermaid` | string | `"streaming"` | Mermaid rendering mode: `"off"`, `"final"`, or `"streaming"` |
229
232
 
230
233
  ### Resources
231
234
 
package/docs/themes.md CHANGED
@@ -71,6 +71,7 @@ vim ~/.pi/agent/themes/my-theme.json
71
71
  "text": "",
72
72
  "thinkingText": "secondary",
73
73
  "selectedBg": "#2d2d30",
74
+ "scrollbarThumb": "#555566",
74
75
  "userMessageBg": "#2d2d30",
75
76
  "userMessageText": "",
76
77
  "customMessageBg": "#2d2d30",
@@ -140,13 +141,13 @@ vim ~/.pi/agent/themes/my-theme.json
140
141
 
141
142
  - `name` is required, must be unique, and must not contain `/`.
142
143
  - `vars` is optional. Define reusable colors here, then reference them in `colors`.
143
- - `colors` must define all 51 required tokens. `thinkingMax` is optional and falls back to `thinkingXhigh`.
144
+ - `colors` must define all 51 required tokens. `thinkingMax` is optional and falls back to `thinkingXhigh`; `scrollbarThumb` is optional and falls back to `selectedBg`.
144
145
 
145
146
  The `$schema` field enables editor auto-completion and validation.
146
147
 
147
148
  ## Color Tokens
148
149
 
149
- Every theme must define all 51 required color tokens. `thinkingMax` is optional for compatibility with existing themes; when omitted, it uses `thinkingXhigh`.
150
+ Every theme must define all 51 required color tokens. `thinkingMax` and `scrollbarThumb` are optional for compatibility with existing themes; when omitted, they use `thinkingXhigh` and `selectedBg`, respectively.
150
151
 
151
152
  ### Core UI (11 colors)
152
153
 
@@ -164,11 +165,12 @@ Every theme must define all 51 required color tokens. `thinkingMax` is optional
164
165
  | `text` | Default text (usually `""`) |
165
166
  | `thinkingText` | Thinking block text |
166
167
 
167
- ### Backgrounds & Content (11 colors)
168
+ ### Backgrounds & Content (11 required, 1 optional)
168
169
 
169
170
  | Token | Purpose |
170
171
  |-------|---------|
171
172
  | `selectedBg` | Selected line background |
173
+ | `scrollbarThumb` | Fullscreen scrollbar thumb background; optional, falls back to `selectedBg` |
172
174
  | `userMessageBg` | User message background |
173
175
  | `userMessageText` | User message text |
174
176
  | `customMessageBg` | Extension message background |
package/docs/usage.md CHANGED
@@ -103,6 +103,8 @@ Pi loads `AGENTS.md` or `CLAUDE.md` at startup from:
103
103
  - parent directories, walking up from the current working directory
104
104
  - the current directory
105
105
 
106
+ If a directory contains `AGENTS.override.md`, Pi loads it instead of `AGENTS.md` or `CLAUDE.md` from that directory. Context files from other directories still layer normally.
107
+
106
108
  Use context files for project conventions, commands, safety rules, and preferences. Disable loading with `--no-context-files` or `-nc`.
107
109
 
108
110
  ### System Prompt Files
@@ -239,12 +241,17 @@ pi --no-extensions -e ./my-extension.ts
239
241
  |--------|-------------|
240
242
  | `--system-prompt <text>` | Replace default prompt; context files and skills are still appended |
241
243
  | `--append-system-prompt <text>` | Append to system prompt |
244
+ | `--tui-mode <mode>` | TUI mode: `regular` (default) or experimental `fullscreen` |
242
245
  | `--verbose` | Force verbose startup |
243
246
  | `-a`, `--approve` | Trust project-local files for this run |
244
247
  | `-na`, `--no-approve` | Ignore project-local files for this run |
245
248
  | `-h`, `--help` | Show help |
246
249
  | `-v`, `--version` | Show version |
247
250
 
251
+ In `fullscreen` mode, the transcript scrolls inside the terminal viewport while queued messages, working status, extension widgets, editor, and footer remain fixed at the bottom. Mouse/trackpad input scrolls the region under the pointer; keyboard viewport actions always remain available. Inline images work in terminals that support the Kitty graphics protocol, including Kitty and Ghostty. In iTerm2 they render as text placeholders because its inline-image protocol cannot delete or crop placements during application-owned scrolling. In `regular` mode, pi uses the main screen and terminal-owned scrollback, and iTerm2 inline images continue to render normally.
252
+
253
+ Set **TUI mode** in `/settings` to switch between `regular` and `fullscreen` immediately and choose the default for future sessions.
254
+
248
255
  ### File Arguments
249
256
 
250
257
  Prefix files with `@` to include them in the message: