@visual-z/xeno 0.8.4 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (350) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +31 -746
  3. package/bin/xeno.exe +26 -0
  4. package/install.cjs +88 -0
  5. package/package.json +18 -48
  6. package/docs/app-server.md +0 -115
  7. package/docs/foundations.md +0 -88
  8. package/docs/install.md +0 -244
  9. package/docs/providers.md +0 -172
  10. package/docs/research/foundation-clients-2026-10-09.md +0 -27
  11. package/docs/research/foundation-editing-2026-10-09.md +0 -56
  12. package/docs/research/foundation-network-2026-10-09.md +0 -37
  13. package/docs/research/foundation-workflow-2026-10-09.md +0 -37
  14. package/src/apps/acp-server.ts +0 -41
  15. package/src/apps/agent-profiles.ts +0 -19
  16. package/src/apps/app-server.ts +0 -30
  17. package/src/apps/approve.ts +0 -49
  18. package/src/apps/cli.ts +0 -99
  19. package/src/apps/commands.ts +0 -319
  20. package/src/apps/completions.ts +0 -87
  21. package/src/apps/control-transport.ts +0 -90
  22. package/src/apps/export-cli.ts +0 -24
  23. package/src/apps/goal-flow.ts +0 -70
  24. package/src/apps/image-draft.ts +0 -46
  25. package/src/apps/input.ts +0 -312
  26. package/src/apps/model-catalog.ts +0 -69
  27. package/src/apps/preferences.ts +0 -20
  28. package/src/apps/project-trust.ts +0 -9
  29. package/src/apps/provider-connect.ts +0 -346
  30. package/src/apps/question-ui.ts +0 -27
  31. package/src/apps/render.ts +0 -181
  32. package/src/apps/resources.ts +0 -66
  33. package/src/apps/review.ts +0 -109
  34. package/src/apps/session-hub.ts +0 -96
  35. package/src/apps/session-lifecycle.ts +0 -64
  36. package/src/apps/shell.ts +0 -2071
  37. package/src/apps/startup-input.ts +0 -135
  38. package/src/apps/startup.ts +0 -1677
  39. package/src/apps/ui/app.ts +0 -1818
  40. package/src/apps/ui/control-model.ts +0 -43
  41. package/src/apps/ui/extension-host.ts +0 -194
  42. package/src/apps/ui/image-cache.ts +0 -121
  43. package/src/apps/ui/palette.ts +0 -88
  44. package/src/apps/ui/project.ts +0 -127
  45. package/src/apps/ui/standalone.ts +0 -148
  46. package/src/apps/ui/status-model.ts +0 -68
  47. package/src/apps/ui/surface.ts +0 -91
  48. package/src/apps/ui/theme.ts +0 -86
  49. package/src/apps/update-check.ts +0 -112
  50. package/src/apps/workflow.ts +0 -58
  51. package/src/core/acceptance.ts +0 -139
  52. package/src/core/action-defaults.ts +0 -58
  53. package/src/core/activity-status.ts +0 -52
  54. package/src/core/agent-profile.ts +0 -130
  55. package/src/core/attachment-input.ts +0 -17
  56. package/src/core/blind-checks.ts +0 -72
  57. package/src/core/boundary.ts +0 -174
  58. package/src/core/branch-receipt.ts +0 -26
  59. package/src/core/catalog-merge.ts +0 -19
  60. package/src/core/checkpoints.ts +0 -85
  61. package/src/core/churn.ts +0 -61
  62. package/src/core/command-artifacts.ts +0 -94
  63. package/src/core/command-contract.ts +0 -151
  64. package/src/core/completion-action.ts +0 -20
  65. package/src/core/completion-review.ts +0 -98
  66. package/src/core/context-detail.ts +0 -37
  67. package/src/core/context-policy.ts +0 -133
  68. package/src/core/context-window.ts +0 -54
  69. package/src/core/continuation.ts +0 -130
  70. package/src/core/control-protocol.ts +0 -69
  71. package/src/core/control-view.ts +0 -39
  72. package/src/core/cycle.ts +0 -17
  73. package/src/core/data-query.ts +0 -201
  74. package/src/core/deadline.ts +0 -40
  75. package/src/core/derive.ts +0 -516
  76. package/src/core/draft-images.ts +0 -56
  77. package/src/core/edit-plan.ts +0 -200
  78. package/src/core/environment.ts +0 -181
  79. package/src/core/event-validation.ts +0 -220
  80. package/src/core/events.ts +0 -670
  81. package/src/core/evidence-ladder.ts +0 -62
  82. package/src/core/evidence-links.ts +0 -68
  83. package/src/core/evidence.ts +0 -240
  84. package/src/core/execution.ts +0 -18
  85. package/src/core/exhaustive.ts +0 -18
  86. package/src/core/export.ts +0 -179
  87. package/src/core/extension-flags.ts +0 -14
  88. package/src/core/external-effects.ts +0 -488
  89. package/src/core/failure-recovery.ts +0 -173
  90. package/src/core/file-precondition.ts +0 -78
  91. package/src/core/file-references.ts +0 -35
  92. package/src/core/frontmatter.ts +0 -213
  93. package/src/core/git-status.ts +0 -29
  94. package/src/core/goal-view.ts +0 -64
  95. package/src/core/goal.ts +0 -200
  96. package/src/core/handoff.ts +0 -479
  97. package/src/core/history-query.ts +0 -139
  98. package/src/core/history.ts +0 -237
  99. package/src/core/image-limits.ts +0 -72
  100. package/src/core/images.ts +0 -46
  101. package/src/core/integration-health.ts +0 -34
  102. package/src/core/json-event.ts +0 -131
  103. package/src/core/json-pointer.ts +0 -46
  104. package/src/core/keymap.ts +0 -153
  105. package/src/core/lsp-framing.ts +0 -34
  106. package/src/core/mcp-config.ts +0 -68
  107. package/src/core/model-picker.ts +0 -64
  108. package/src/core/model-preferences.ts +0 -28
  109. package/src/core/model-replay.ts +0 -22
  110. package/src/core/model-scope.ts +0 -53
  111. package/src/core/note-guidance.ts +0 -34
  112. package/src/core/oauth-fields.ts +0 -44
  113. package/src/core/observation-view.ts +0 -152
  114. package/src/core/outcome.ts +0 -44
  115. package/src/core/overflow.ts +0 -59
  116. package/src/core/packages.ts +0 -47
  117. package/src/core/patch.ts +0 -130
  118. package/src/core/path-suggestions.ts +0 -59
  119. package/src/core/permissions.ts +0 -410
  120. package/src/core/process-receipt.ts +0 -105
  121. package/src/core/process-state.ts +0 -37
  122. package/src/core/program-status.ts +0 -40
  123. package/src/core/progress.ts +0 -150
  124. package/src/core/project.ts +0 -431
  125. package/src/core/prompt-policy.ts +0 -46
  126. package/src/core/prompts.ts +0 -111
  127. package/src/core/question.ts +0 -94
  128. package/src/core/queue.ts +0 -78
  129. package/src/core/read-bounds.ts +0 -24
  130. package/src/core/read-observation.ts +0 -106
  131. package/src/core/recovery-sources.ts +0 -74
  132. package/src/core/regex-dialect.ts +0 -25
  133. package/src/core/request-budget.ts +0 -23
  134. package/src/core/request-progress.ts +0 -87
  135. package/src/core/request-state.ts +0 -273
  136. package/src/core/resource-selection.ts +0 -36
  137. package/src/core/response-recovery.ts +0 -59
  138. package/src/core/resume.ts +0 -59
  139. package/src/core/review.ts +0 -68
  140. package/src/core/ripgrep-release.ts +0 -64
  141. package/src/core/runtime-target.ts +0 -94
  142. package/src/core/scheduling.ts +0 -8
  143. package/src/core/search-observation.ts +0 -97
  144. package/src/core/secrets.ts +0 -188
  145. package/src/core/session-archive.ts +0 -4
  146. package/src/core/session-browser.ts +0 -82
  147. package/src/core/session-entry.ts +0 -61
  148. package/src/core/session-exit.ts +0 -42
  149. package/src/core/session-format.ts +0 -169
  150. package/src/core/session-position.ts +0 -26
  151. package/src/core/session-tree.ts +0 -147
  152. package/src/core/settings.ts +0 -192
  153. package/src/core/skills.ts +0 -165
  154. package/src/core/sql-query.ts +0 -211
  155. package/src/core/stall.ts +0 -128
  156. package/src/core/stop-report.ts +0 -68
  157. package/src/core/text-normalize.ts +0 -81
  158. package/src/core/theme.ts +0 -166
  159. package/src/core/thinking.ts +0 -26
  160. package/src/core/tokens.ts +0 -151
  161. package/src/core/tool-contract.ts +0 -153
  162. package/src/core/tool-discovery.ts +0 -87
  163. package/src/core/tool-failure.ts +0 -81
  164. package/src/core/tool-input.ts +0 -44
  165. package/src/core/transcript.ts +0 -108
  166. package/src/core/tui-mode.ts +0 -29
  167. package/src/core/update-check.ts +0 -157
  168. package/src/core/version.ts +0 -55
  169. package/src/core/web-content.ts +0 -80
  170. package/src/core/work-state.ts +0 -154
  171. package/src/core/workflow-mode.ts +0 -34
  172. package/src/core/working-set.ts +0 -119
  173. package/src/core/yaml.ts +0 -282
  174. package/src/runtime/acp-agent.ts +0 -289
  175. package/src/runtime/agent-profiles.ts +0 -21
  176. package/src/runtime/auth.ts +0 -367
  177. package/src/runtime/blind-check.ts +0 -36
  178. package/src/runtime/branch-jobs.ts +0 -36
  179. package/src/runtime/catalog.ts +0 -285
  180. package/src/runtime/clipboard.ts +0 -125
  181. package/src/runtime/command-artifacts.ts +0 -64
  182. package/src/runtime/connection-reuse.ts +0 -40
  183. package/src/runtime/control-client.ts +0 -149
  184. package/src/runtime/control-connection.ts +0 -139
  185. package/src/runtime/counter-check.ts +0 -74
  186. package/src/runtime/environment-tools.ts +0 -60
  187. package/src/runtime/evidence-notes.ts +0 -35
  188. package/src/runtime/exec-sessions.ts +0 -456
  189. package/src/runtime/execution-control.ts +0 -40
  190. package/src/runtime/extension-loader.ts +0 -37
  191. package/src/runtime/extension-ui.ts +0 -47
  192. package/src/runtime/extensions.ts +0 -370
  193. package/src/runtime/formatter-tool.ts +0 -38
  194. package/src/runtime/formatter.ts +0 -50
  195. package/src/runtime/foundation.ts +0 -116
  196. package/src/runtime/history-cache.ts +0 -57
  197. package/src/runtime/history-tools.ts +0 -168
  198. package/src/runtime/images.ts +0 -80
  199. package/src/runtime/integrations.ts +0 -216
  200. package/src/runtime/loop.ts +0 -3108
  201. package/src/runtime/lsp-client.ts +0 -201
  202. package/src/runtime/mcp-client.ts +0 -264
  203. package/src/runtime/mcp-oauth.ts +0 -168
  204. package/src/runtime/mcp-schema.ts +0 -51
  205. package/src/runtime/model-config.ts +0 -232
  206. package/src/runtime/model-data.LICENSE +0 -27
  207. package/src/runtime/model-data.ts +0 -21
  208. package/src/runtime/model-discovery.ts +0 -208
  209. package/src/runtime/model-replay.ts +0 -10
  210. package/src/runtime/model-scope.ts +0 -20
  211. package/src/runtime/model-sources.ts +0 -151
  212. package/src/runtime/model.ts +0 -686
  213. package/src/runtime/models-fetch.ts +0 -57
  214. package/src/runtime/oauth-device.ts +0 -86
  215. package/src/runtime/oauth.ts +0 -853
  216. package/src/runtime/open-image.ts +0 -42
  217. package/src/runtime/openai-responses.ts +0 -304
  218. package/src/runtime/openai.ts +0 -697
  219. package/src/runtime/output-schema.ts +0 -26
  220. package/src/runtime/package-manager.ts +0 -125
  221. package/src/runtime/patch-tool.ts +0 -38
  222. package/src/runtime/permission-resources.ts +0 -18
  223. package/src/runtime/pi-provider.ts +0 -423
  224. package/src/runtime/program-status.ts +0 -38
  225. package/src/runtime/progress.ts +0 -26
  226. package/src/runtime/provider-access.ts +0 -21
  227. package/src/runtime/provider-connect.ts +0 -109
  228. package/src/runtime/provider-presets.ts +0 -98
  229. package/src/runtime/provider-protocols.ts +0 -29
  230. package/src/runtime/provider-request.ts +0 -87
  231. package/src/runtime/provider-tool-policy.ts +0 -33
  232. package/src/runtime/provider.ts +0 -580
  233. package/src/runtime/question-tool.ts +0 -62
  234. package/src/runtime/retry.ts +0 -447
  235. package/src/runtime/review-tool.ts +0 -48
  236. package/src/runtime/rich-text.ts +0 -36
  237. package/src/runtime/runtime-target.ts +0 -149
  238. package/src/runtime/secrets.ts +0 -79
  239. package/src/runtime/self-update.ts +0 -74
  240. package/src/runtime/session-control.ts +0 -265
  241. package/src/runtime/session-hub.ts +0 -85
  242. package/src/runtime/stream-idle.ts +0 -80
  243. package/src/runtime/tool-broker.ts +0 -137
  244. package/src/runtime/tool-discovery.ts +0 -58
  245. package/src/runtime/tool-queue.ts +0 -77
  246. package/src/runtime/tools.ts +0 -1820
  247. package/src/runtime/turn-effects.ts +0 -31
  248. package/src/runtime/update-check.ts +0 -48
  249. package/src/runtime/usage.ts +0 -253
  250. package/src/runtime/version.ts +0 -12
  251. package/src/runtime/web-http.ts +0 -56
  252. package/src/runtime/web-tools.ts +0 -88
  253. package/src/runtime/working-notes.ts +0 -60
  254. package/src/runtime/workspace-lifecycle.ts +0 -141
  255. package/src/store/acceptance.ts +0 -15
  256. package/src/store/agent-profile-files.ts +0 -13
  257. package/src/store/atomic.ts +0 -31
  258. package/src/store/auth-store.ts +0 -50
  259. package/src/store/blind-checks.ts +0 -32
  260. package/src/store/blobs.ts +0 -143
  261. package/src/store/checkpoint-history.ts +0 -32
  262. package/src/store/command-artifacts.ts +0 -96
  263. package/src/store/command.ts +0 -111
  264. package/src/store/config.ts +0 -69
  265. package/src/store/context-files.ts +0 -436
  266. package/src/store/file-checkpoints.ts +0 -108
  267. package/src/store/file-index.ts +0 -174
  268. package/src/store/foundation-config.ts +0 -24
  269. package/src/store/git-checkpoints.ts +0 -67
  270. package/src/store/git-status.ts +0 -15
  271. package/src/store/history-search.ts +0 -19
  272. package/src/store/log.ts +0 -725
  273. package/src/store/lsp-document.ts +0 -30
  274. package/src/store/mcp-credentials.ts +0 -85
  275. package/src/store/models.ts +0 -60
  276. package/src/store/observations.ts +0 -224
  277. package/src/store/packages.ts +0 -98
  278. package/src/store/patch.ts +0 -92
  279. package/src/store/permission-paths.ts +0 -34
  280. package/src/store/process-receipts.ts +0 -34
  281. package/src/store/query-source.ts +0 -49
  282. package/src/store/recovery.ts +0 -44
  283. package/src/store/replay.ts +0 -76
  284. package/src/store/resource-index.ts +0 -170
  285. package/src/store/resources.ts +0 -285
  286. package/src/store/restore.ts +0 -110
  287. package/src/store/review.ts +0 -109
  288. package/src/store/runtime-target.ts +0 -64
  289. package/src/store/search.ts +0 -728
  290. package/src/store/secrets.ts +0 -44
  291. package/src/store/serial.ts +0 -13
  292. package/src/store/session-archive.ts +0 -165
  293. package/src/store/session-browser.ts +0 -68
  294. package/src/store/session-position.ts +0 -27
  295. package/src/store/sessions.ts +0 -525
  296. package/src/store/source-cancellation.ts +0 -6
  297. package/src/store/tool-binaries.ts +0 -90
  298. package/src/store/trust.ts +0 -82
  299. package/src/store/update-cache.ts +0 -36
  300. package/src/store/user-settings.ts +0 -30
  301. package/src/store/workspace-clone.ts +0 -42
  302. package/src/store/workspace-observation.ts +0 -113
  303. package/src/store/workspace.ts +0 -115
  304. package/src/store/worktree.ts +0 -278
  305. package/src/tui/ansi-spans.ts +0 -126
  306. package/src/tui/diff.ts +0 -160
  307. package/src/tui/exhaustive.ts +0 -14
  308. package/src/tui/fuzzy.ts +0 -75
  309. package/src/tui/style.ts +0 -208
  310. package/src/tui/ui/atomic-draft.ts +0 -121
  311. package/src/tui/ui/branch-panel.ts +0 -47
  312. package/src/tui/ui/canvas.ts +0 -96
  313. package/src/tui/ui/colors.ts +0 -70
  314. package/src/tui/ui/composer.ts +0 -199
  315. package/src/tui/ui/control-panel.ts +0 -25
  316. package/src/tui/ui/control-sidebar.ts +0 -49
  317. package/src/tui/ui/dialog.ts +0 -398
  318. package/src/tui/ui/draft.ts +0 -23
  319. package/src/tui/ui/duration.ts +0 -16
  320. package/src/tui/ui/edge.ts +0 -186
  321. package/src/tui/ui/image-size.ts +0 -44
  322. package/src/tui/ui/ink.ts +0 -210
  323. package/src/tui/ui/items.ts +0 -42
  324. package/src/tui/ui/keys.ts +0 -40
  325. package/src/tui/ui/layout.ts +0 -74
  326. package/src/tui/ui/list-model.ts +0 -86
  327. package/src/tui/ui/list-view.ts +0 -172
  328. package/src/tui/ui/mascot.ts +0 -67
  329. package/src/tui/ui/messages.ts +0 -518
  330. package/src/tui/ui/motion.ts +0 -28
  331. package/src/tui/ui/paths.ts +0 -10
  332. package/src/tui/ui/pictures.ts +0 -108
  333. package/src/tui/ui/prompt-history.ts +0 -60
  334. package/src/tui/ui/screen.ts +0 -579
  335. package/src/tui/ui/status.ts +0 -403
  336. package/src/tui/ui/styled.ts +0 -85
  337. package/src/tui/ui/tool-card.ts +0 -150
  338. package/src/tui/width.ts +0 -567
  339. package/src/vendor/pi-rich/LICENSE.upstream +0 -21
  340. package/src/vendor/pi-rich/UPSTREAM.json +0 -17
  341. package/src/vendor/pi-rich/clipboard-command.ts +0 -44
  342. package/src/vendor/pi-rich/exif-orientation.ts +0 -182
  343. package/src/vendor/pi-rich/html.ts +0 -51
  344. package/src/vendor/pi-rich/image-convert.ts +0 -49
  345. package/src/vendor/pi-rich/image-resize-core.ts +0 -164
  346. package/src/vendor/pi-rich/image-resize-worker.ts +0 -42
  347. package/src/vendor/pi-rich/image-resize.ts +0 -123
  348. package/src/vendor/pi-rich/mime.ts +0 -116
  349. package/src/vendor/pi-rich/photon.ts +0 -139
  350. package/src/vendor/pi-rich/syntax-highlight.ts +0 -214
@@ -1,26 +0,0 @@
1
- /** Ajv owns JSON Schema semantics; validation never repairs model output (D11/D13). */
2
- import { Ajv } from "ajv";
3
- import { Ajv2020 } from "ajv/dist/2020.js";
4
- import { Ajv2019 } from "ajv/dist/2019.js";
5
- import { addFormats } from "@modelcontextprotocol/client/validators/ajv";
6
-
7
- export type OutputValidation = { ok: true; value: unknown } | { ok: false; error: string };
8
- export function outputValidator(schema: unknown): (text: string) => OutputValidation {
9
- if (typeof schema !== "boolean" && (schema === null || typeof schema !== "object" || Array.isArray(schema))) throw Error("output schema must be a JSON Schema object or boolean");
10
- const dialect = typeof schema === "object" && schema !== null && "$schema" in schema ? String(schema.$schema) : "";
11
- const options = { allErrors: true, strictSchema: true, strictTypes: false, strictTuples: false, strictRequired: false, validateFormats: true,
12
- coerceTypes: false, useDefaults: false, removeAdditional: false, logger: false as const };
13
- const ajv = dialect === "https://json-schema.org/draft/2020-12/schema" ? new Ajv2020(options)
14
- : dialect === "https://json-schema.org/draft/2019-09/schema" ? new Ajv2019(options)
15
- : dialect === "" || dialect === "http://json-schema.org/draft-07/schema#" || dialect === "http://json-schema.org/draft-07/schema" ? new Ajv(options) : undefined;
16
- if (!ajv) throw Error("unsupported output schema dialect");
17
- // The approved MCP client exposes Ajv's standard formats; share them rather than silently ignoring formats.
18
- addFormats(ajv);
19
- const validate = ajv.compile(schema);
20
- return text => {
21
- let value: unknown;
22
- try { value = JSON.parse(text); } catch { return { ok: false, error: "final output is not JSON" }; }
23
- if (!validate(value)) return { ok: false, error: ajv.errorsText(validate.errors, { separator: "; " }).slice(0, 2048) };
24
- return { ok: true, value };
25
- };
26
- }
@@ -1,125 +0,0 @@
1
- /** Explicit package operations, isolated from agent sessions and provider credentials. */
2
- import { mkdir, mkdtemp, readdir, rm, stat } from "node:fs/promises";
3
- import { join, relative, resolve } from "node:path";
4
- import { packageConfig, type PackageDeclaration } from "../core/packages.ts";
5
- import { configDir, readJsonFile } from "../store/config.ts";
6
- import { serialized } from "../store/serial.ts";
7
- import { projectTrust } from "../store/trust.ts";
8
- import { packageSource, packageHome, packageKey, packageRecords, installedPackage, configuredPackages, savePackageDeclaration, savePackageRecord, removePackageRecords, packageManifest, type PackageRecord } from "../store/packages.ts";
9
-
10
- export type PackageOperation = "install" | "remove" | "update" | "list";
11
- export interface PackageCommand { operation: PackageOperation; sources: string[]; local?: boolean }
12
- export type PackageExecutor = (argv: string[], options: { cwd: string; signal?: AbortSignal }) => Promise<{ code: number; stdout: string; stderr: string }>;
13
- export interface PackageOptions { workspace: string; userDir?: string; run?: PackageExecutor; signal?: AbortSignal; say?: (text: string) => void; trusted?: boolean }
14
- export interface ListedPackage { source: string; identity: string; scope: "user" | "project"; path?: string; pinned: boolean; filtered: boolean }
15
-
16
- export const executePackageCommand: PackageExecutor = async (argv, options) => {
17
- if (options.signal?.aborted) throw Error("Package operation cancelled");
18
- const child=Bun.spawn(argv,{cwd:options.cwd,stdin:"ignore",stdout:"pipe",stderr:"pipe"});
19
- const abort=()=>{child.kill();}; options.signal?.addEventListener("abort",abort,{once:true});
20
- try {
21
- const [stdout,stderr,code]=await Promise.all([new Response(child.stdout).text(),new Response(child.stderr).text(),child.exited]);
22
- if(options.signal?.aborted)throw Error("Package operation cancelled"); return {stdout,stderr,code};
23
- } finally { options.signal?.removeEventListener("abort",abort); }
24
- };
25
-
26
- export class PackageManager {
27
- readonly options: PackageOptions;
28
- constructor(options: PackageOptions) { this.options={...options,workspace:resolve(options.workspace)}; }
29
- private base(local=false):string { return local?join(this.options.workspace,".xeno"):this.options.userDir??configDir(); }
30
- private async command(argv:string[],cwd:string):Promise<string> {
31
- const result=await (this.options.run??executePackageCommand)(argv,{cwd,...(this.options.signal?{signal:this.options.signal}:{})});
32
- if(result.code!==0)throw Error(`${argv[0]} exited ${result.code}: ${result.stderr.trim()||result.stdout.trim()}`);
33
- return result.stdout.trim();
34
- }
35
- async list():Promise<ListedPackage[]> {
36
- const entries:ListedPackage[]=[];
37
- for(const scope of ["user","project"] as const){const base=this.base(scope==="project");for(const declaration of await configuredPackages(base)){
38
- const config=packageConfig(declaration),source=packageSource(config.source,base),path=await installedPackage(source,base);
39
- entries.push({source:config.source,identity:source.id,scope,...(path?{path}:{}),pinned:source.pinned,filtered:typeof declaration!=="string"});
40
- }}
41
- return entries;
42
- }
43
- async install(raw:string,options:{local?:boolean;persist?:boolean;base?:string;declaration?:PackageDeclaration}={}):Promise<string> {
44
- if(this.options.signal?.aborted)throw Error("Package operation cancelled");
45
- const base=options.base??this.base(options.local),source=packageSource(raw,this.options.workspace);
46
- return serialized(join(packageHome(base),"operation"),async()=>{
47
- // Parse existing metadata before any network/installation side effect.
48
- await configuredPackages(base); const records=await packageRecords(base),previous=records[packageKey(source)];
49
- const declaration=options.declaration??(source.kind==="local"?relative(base,source.path)||".":source.source);
50
- if(source.kind==="local") {
51
- if(!await stat(source.path).then(()=>true,()=>false))throw Error(`Package path does not exist: ${source.path}`);
52
- await packageManifest(source.path);
53
- if(options.persist!==false)await savePackageDeclaration(base,source,declaration);
54
- return source.path;
55
- }
56
- const versions=join(packageHome(base),"versions"); await mkdir(versions,{recursive:true});
57
- const staging=await mkdtemp(join(versions,packageKey(source)+"-")); let published=false;
58
- try {
59
- let root:string,revision:string|undefined;
60
- if(source.kind==="npm") {
61
- await Bun.write(join(staging,"package.json"),JSON.stringify({name:"xeno-package-install",private:true}));
62
- await this.command([process.execPath,"add","--exact","--production","--omit","peer",source.spec],staging);
63
- root=join(staging,"node_modules",source.name);
64
- } else {
65
- root=join(staging,"repo"); await this.command(["git","clone","--",source.repo,root],staging);
66
- if(source.ref)await this.command(["git","checkout","--detach",source.ref],root);
67
- revision=await this.command(["git","rev-parse","HEAD"],root);
68
- if(await Bun.file(join(root,"package.json")).exists()) await this.command([process.execPath,"install","--production","--omit","peer"],root);
69
- }
70
- if(!await stat(root).then(s=>s.isDirectory(),()=>false))throw Error("Package installation produced no package directory");
71
- await packageManifest(root);
72
- const manifest=await readJsonFile<{version?:string}>(join(root,"package.json"));if(manifest.error)throw Error(manifest.error);
73
- const record:PackageRecord={source:source.source,identity:source.id,path:relative(packageHome(base),root),...(manifest.value?.version?{version:manifest.value.version}:{}),...(revision?{revision}:{})};
74
- await savePackageRecord(base,source,record);
75
- try { if(options.persist!==false)await savePackageDeclaration(base,source,declaration); }
76
- catch(error){await savePackageRecord(base,source,previous);throw error;}
77
- // Old versions remain available to already-running extension generations.
78
- published=true; return root;
79
- } finally { if(!published)await rm(staging,{recursive:true,force:true}); }
80
- });
81
- }
82
- async remove(raw:string,local=false):Promise<boolean> {
83
- const base=this.base(local),requested=packageSource(raw,this.options.workspace);
84
- return serialized(join(packageHome(base),"operation"),async()=>{
85
- const declarations=await configuredPackages(base),match=declarations.find(entry=>{const source=packageSource(packageConfig(entry).source,base);return source.id===requested.id||source.source===raw||source.kind==="npm"&&source.name===raw;});
86
- if(!match)return false;
87
- const source=packageSource(packageConfig(match).source,base);
88
- await packageRecords(base);
89
- await savePackageDeclaration(base,source,undefined);const keys=await removePackageRecords(base,source.id);
90
- if(source.kind!=="local") {
91
- const versions=join(packageHome(base),"versions"),names=await readdir(versions).catch(()=>[]);
92
- for(const name of names)if(keys.some(key=>name.startsWith(key+"-")))await rm(join(versions,name),{recursive:true,force:true});
93
- }
94
- return true;
95
- });
96
- }
97
- async update(raw?:string,local?:boolean):Promise<number> {
98
- let count=0;
99
- const trusted=this.options.trusted??(await projectTrust(this.options.workspace)).trusted;
100
- for(const scope of [false,true]) {
101
- if(local!==undefined&&scope!==local)continue;
102
- if(scope&&local!==true&&!trusted)continue;
103
- const base=this.base(scope);
104
- for(const declaration of await configuredPackages(base)) {
105
- const source=packageSource(packageConfig(declaration).source,base);
106
- if(raw&&source.source!==raw&&source.id!==packageSource(raw,this.options.workspace).id&&!(source.kind==="npm"&&source.name===raw))continue;
107
- if(source.kind==="local")continue;
108
- await this.install(source.source,{base,persist:false,declaration});count++;
109
- }
110
- }
111
- if(raw&&count===0)throw Error(`No installed remote package matches ${raw}`);return count;
112
- }
113
- async temporary(raw:string):Promise<string> { return this.install(raw,{base:join(this.base(),"temporary"),persist:false}); }
114
- }
115
-
116
- export async function runPackageCommand(command:PackageCommand,options:PackageOptions):Promise<void> {
117
- const manager=new PackageManager(options),say=options.say??console.log;
118
- if(!await stat(manager.options.workspace).then(s=>s.isDirectory(),()=>false))throw Error(`Workspace does not exist: ${options.workspace}`);
119
- if(command.operation==="list"&&command.sources.length||command.operation==="update"&&command.sources.length>1)throw Error(`Unexpected sources for ${command.operation}`);
120
- if(command.operation==="list") { for(const entry of await manager.list())say(`${entry.scope} ${entry.source}${entry.pinned?" (pinned)":""} · ${entry.path??"not installed"}`); return; }
121
- if(command.operation==="update") { say(`Updated ${await manager.update(command.sources[0],command.local)} package(s)`); return; }
122
- if(command.sources.length===0)throw Error(`${command.operation} requires a package source`);
123
- for(const source of command.sources) if(command.operation==="install") say(`Installed ${source} · ${await manager.install(source,{...(command.local===undefined?{}:{local:command.local})})}`);
124
- else say(await manager.remove(source,command.local)?`Removed ${source}`:`Package not configured: ${source}`);
125
- }
@@ -1,38 +0,0 @@
1
- /** One durable mutation owner for multi-file Patch, independent of provider syntax. */
2
- import { changedPaths } from "../core/checkpoints.ts";
3
- import { NOT_STARTED, READ_ATTEMPTED, UNKNOWN_EFFECT, failureDiagnostic, failureFromError } from "../core/tool-failure.ts";
4
- import { preparePatch, applyPreparedPatch } from "../store/patch.ts";
5
- import { presentObservation, type ToolDefinition } from "./tools.ts";
6
-
7
- export function createPatchTool(): ToolDefinition {
8
- return {
9
- name: "apply_patch", access: "write", execution: "exclusive", branch: "write",
10
- description: "Apply a Codex-style multi-file patch containing Add File, Update File, optional Move to, and Delete File. Existing sources require a prior session read or explicit expected_sha256. All targets and exact unique hunks are preflighted before changes; add/move destinations must be absent. Uses durable file checkpoints and compensates partial application without overwriting external changes. Preserves original BOM, line endings and modes. Return contains resulting file SHA-256 versions.",
11
- input_schema: { type: "object", required: ["patchText"], properties: {
12
- patchText: { type: "string", description: "Full *** Begin Patch ... *** End Patch text; update hunks start with @@ and lines use space/+/- prefixes." },
13
- expected_sha256: { type: "object", additionalProperties: { type: "string", pattern: "^[a-f0-9]{64}$" }, description: "Optional source path to exact SHA-256 premise; otherwise use observed session versions." },
14
- } },
15
- run: async (ctx, input) => {
16
- if (!ctx.workspace) return { ok: false, content: "Patch requires the session's durable workspace checkpoint owner.", diagnostic: failureDiagnostic("environment_missing", "Patch requires the session's durable workspace checkpoint owner.", NOT_STARTED) };
17
- let admitted = false;
18
- try {
19
- const expected = input["expected_sha256"];
20
- if (expected !== undefined && (expected === null || typeof expected !== "object" || Array.isArray(expected))) throw new Error("expected_sha256 must be an object of source digests");
21
- const prepared = await preparePatch(ctx.root, ctx.workspace.blobs, String(input["patchText"]), {
22
- ...(ctx.observedFileVersion === undefined ? {} : { observedFileVersion: ctx.observedFileVersion }),
23
- ...(expected === undefined ? {} : { expectedSha256: expected as Record<string, string> }),
24
- ...(ctx.signal === undefined ? {} : { signal: ctx.signal }),
25
- });
26
- admitted = true;
27
- await ctx.workspace.patch(prepared.changes, () => applyPreparedPatch(ctx.root, ctx.workspace!.blobs, prepared, ctx.signal), ctx.signal);
28
- const paths = changedPaths(prepared.changes);
29
- const summary = paths.map(path => `${path}: ${prepared.changes[path]!.after?.sha256 ?? "deleted"}`).join("\n");
30
- const data = paths.map(path => ({ path, before: prepared.changes[path]!.before?.sha256 ?? null, after: prepared.changes[path]!.after?.sha256 ?? null }));
31
- return { ...await presentObservation(ctx, `Applied patch to ${paths.length} file path(s).\n${summary}\n\n${prepared.diffs.map(diff => diff.diff).join("\n")}`), ok: true, data: { files: data } };
32
- } catch (error) {
33
- const diagnostic = failureFromError(error, admitted ? UNKNOWN_EFFECT : READ_ATTEMPTED);
34
- return { ok: false, content: diagnostic.message, diagnostic };
35
- }
36
- },
37
- };
38
- }
@@ -1,18 +0,0 @@
1
- /** R13: an indirect content reader must retain the same read-resource policy. */
2
- import { explainDecision, type DecisionExplanation, type PermissionAction, type PermissionRule } from "../core/permissions.ts";
3
- import { permissionPaths } from "../store/permission-paths.ts";
4
-
5
- export async function permissionResourceAction(root: string, path: string, configured: readonly PermissionRule[],
6
- remembered: readonly PermissionRule[] = [], tool = "read"): Promise<PermissionAction> {
7
- return (await permissionResourceDecision(root, path, configured, remembered, tool)).action;
8
- }
9
-
10
- /** The same verdict, with the rule that produced it, for the durable approval record. */
11
- export async function permissionResourceDecision(root: string, path: string, configured: readonly PermissionRule[],
12
- remembered: readonly PermissionRule[] = [], tool = "read"): Promise<DecisionExplanation> {
13
- const paths = await permissionPaths(root, [path]);
14
- const read = explainDecision(configured, remembered, { tool: "read", paths });
15
- const specific = explainDecision(configured, remembered, { tool, paths });
16
- const rank = (action: PermissionAction) => action === "deny" ? 2 : action === "ask" ? 1 : 0;
17
- return rank(specific.action) > rank(read.action) ? specific : read;
18
- }
@@ -1,423 +0,0 @@
1
- /** Pi owns wire protocols; xeno owns retries, durable events, projection and credential precedence. */
2
- import type { AssistantMessage, Context, Message as PiMessage, Model, Tool, ProviderStreams, SimpleStreamOptions } from "@earendil-works/pi-ai";
3
- import { normalizeContext } from "@earendil-works/pi-ai/utils/transcript";
4
- import { anthropicMessagesApi } from "@earendil-works/pi-ai/api/anthropic-messages.lazy";
5
- import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";
6
- import { openAIResponsesApi } from "@earendil-works/pi-ai/api/openai-responses.lazy";
7
- import { azureOpenAIResponsesApi } from "@earendil-works/pi-ai/api/azure-openai-responses.lazy";
8
- import { openAICodexResponsesApi } from "@earendil-works/pi-ai/api/openai-codex-responses.lazy";
9
- import { googleGenerativeAIApi } from "@earendil-works/pi-ai/api/google-generative-ai.lazy";
10
- import { googleVertexApi } from "@earendil-works/pi-ai/api/google-vertex.lazy";
11
- import { bedrockConverseStreamApi } from "@earendil-works/pi-ai/api/bedrock-converse-stream.lazy";
12
- import { mistralConversationsApi } from "@earendil-works/pi-ai/api/mistral-conversations.lazy";
13
- import { piMessagesApi } from "@earendil-works/pi-ai/api/pi-messages.lazy";
14
- import { assertNever } from "../core/exhaustive.ts";
15
- import { completedToolInput } from "../core/tool-input.ts";
16
- import type { AssistantBlock } from "../core/events.ts";
17
- import { toolCallIdentityViolations, type FrozenPrefix, type Message } from "../core/project.ts";
18
- import type { Api, ModelSpec } from "./model.ts";
19
- import { providerRequest } from "./provider-request.ts";
20
- import { grokVersionHint } from "./oauth.ts";
21
- import type { ResolveEffects } from "./auth.ts";
22
- import { imageData } from "./images.ts";
23
- import { coherentTurn, FailedResponseError, type OnDelta, type Provider, type ProviderConfig, type ToolSchema, type Turn } from "./provider.ts";
24
- import { DEFAULT_RETRY, retryLabel, withRetry, type RetryPolicy } from "./retry.ts";
25
- import { ConnectionReuse } from "./connection-reuse.ts";
26
- import { watchIdle, type IdleWatch } from "./stream-idle.ts";
27
-
28
- import { HttpError, StreamError } from "./openai.ts";
29
- import type { UsageSample } from "./usage.ts";
30
- import { disablePayloadTools, type ProviderSendOptions } from "./provider-tool-policy.ts";
31
-
32
- const APIS: Record<Api, ProviderStreams> = {
33
- "anthropic-messages": anthropicMessagesApi(), "openai-completions": openAICompletionsApi(),
34
- "openai-responses": openAIResponsesApi(), "azure-openai-responses": azureOpenAIResponsesApi(),
35
- "openai-codex-responses": openAICodexResponsesApi(), "google-generative-ai": googleGenerativeAIApi(),
36
- "google-vertex": googleVertexApi(), "bedrock-converse-stream": bedrockConverseStreamApi(),
37
- "mistral-conversations": mistralConversationsApi(), "pi-messages": piMessagesApi(),
38
- };
39
- function record(value: unknown): value is Record<string, unknown> { return value !== null && typeof value === "object" && !Array.isArray(value); }
40
- const EMPTY_USAGE: AssistantMessage["usage"] = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } };
41
-
42
- /** D11: SDK defaults cannot certify a receipt; native input/output counters can, including measured zeros. */
43
- function nativeUsageReported(api: Api, event: unknown): boolean {
44
- if (!record(event)) return false;
45
- let usage: unknown;
46
- if (api.includes("responses")) {
47
- if (event["type"] !== "response.completed" && event["type"] !== "response.incomplete") return false;
48
- usage = record(event["response"]) ? event["response"]["usage"] : undefined;
49
- } else if (api === "anthropic-messages") {
50
- if (event["type"] === "message_start") usage = record(event["message"]) ? event["message"]["usage"] : undefined;
51
- else if (event["type"] === "message_delta") usage = event["usage"];
52
- else return false;
53
- } else if (api === "openai-completions" || api === "mistral-conversations") usage = event["usage"];
54
- else return false;
55
- if (!record(usage)) return false;
56
- const fields = api === "openai-completions" || api === "mistral-conversations" ? ["prompt_tokens", "completion_tokens"] : ["input_tokens", "output_tokens"];
57
- return fields.some(field => typeof usage[field] === "number" && Number.isSafeInteger(usage[field]) && usage[field] >= 0);
58
- }
59
-
60
- /** O.3: Pi's streaming repair is presentation, never evidence that final arguments were valid JSON. */
61
- class OriginalToolArguments {
62
- readonly values = new Map<string, string>();
63
- private readonly slots = new Map<string, { id: unknown; argumentId: string | null; raw: string; position: number }>();
64
- private readonly faults = new Set<string>();
65
- private readonly api: Api;
66
- constructor(api: Api) { this.api = api; }
67
-
68
- private slotKey(index: unknown, id?: unknown): string | null {
69
- if (typeof index === "number" && Number.isSafeInteger(index) && index >= 0) return `index:${index}`;
70
- // Mistral accepts a complete call keyed by ID without an index. A fragment
71
- // with neither cannot be assigned to a call without inventing its identity.
72
- if (this.api === "mistral-conversations" && typeof id === "string" && id.trim() && id !== "null") return `id:${id}`;
73
- this.faults.add("native tool call has no stable stream slot");
74
- return null;
75
- }
76
-
77
- private slot(key: string, id: unknown, argumentId: string | null, supplied: boolean) {
78
- const existing = this.slots.get(key);
79
- if (existing) {
80
- if (supplied && id !== existing.id) this.faults.add(`native call ID changed in stream slot ${existing.position}`);
81
- if (argumentId !== null && argumentId !== existing.argumentId) this.faults.add(`native argument identity changed in stream slot ${existing.position}`);
82
- return existing;
83
- }
84
- const slot = { id, argumentId, raw: "", position: this.slots.size };
85
- this.slots.set(key, slot);
86
- return slot;
87
- }
88
-
89
- private publish(slot: { argumentId: string | null; raw: string }): void {
90
- if (slot.argumentId !== null) this.values.set(slot.argumentId, slot.raw);
91
- }
92
-
93
- observe(value: unknown): void {
94
- if (!record(value)) return;
95
- if (this.api.includes("responses")) {
96
- const item = value["item"];
97
- if (record(item) && (item["type"] === "function_call" || item["type"] === "custom_tool_call")) {
98
- const key = this.slotKey(value["output_index"]);
99
- if (key === null) return;
100
- const id = item["call_id"], argumentId = typeof id === "string" ? `${id}|${String(item["id"])}` : null;
101
- const slot = this.slot(key, id, argumentId, true);
102
- if (item["type"] === "function_call") {
103
- if (typeof item["arguments"] === "string") slot.raw = item["arguments"];
104
- this.publish(slot);
105
- }
106
- }
107
- const slot = typeof value["output_index"] === "number" ? this.slots.get(`index:${value["output_index"]}`) : undefined;
108
- if (slot && value["type"] === "response.function_call_arguments.delta" && typeof value["delta"] === "string") slot.raw += value["delta"];
109
- if (slot && value["type"] === "response.function_call_arguments.done" && typeof value["arguments"] === "string") slot.raw = value["arguments"];
110
- if (slot && ["response.function_call_arguments.delta", "response.function_call_arguments.done"].includes(String(value["type"]))) this.publish(slot);
111
- return;
112
- }
113
- if (this.api === "anthropic-messages") {
114
- const block = value["content_block"];
115
- const index = value["index"];
116
- if (record(block) && block["type"] === "tool_use") {
117
- const key = this.slotKey(index);
118
- if (key === null) return;
119
- const id = block["id"], slot = this.slot(key, id, typeof id === "string" ? id : null, true);
120
- if (slot.argumentId !== null) this.values.set(slot.argumentId, JSON.stringify(block["input"] ?? {}));
121
- }
122
- const slot = typeof index === "number" ? this.slots.get(`index:${index}`) : undefined, delta = value["delta"];
123
- if (slot && record(delta) && delta["type"] === "input_json_delta" && typeof delta["partial_json"] === "string") {
124
- slot.raw += delta["partial_json"];
125
- this.publish(slot);
126
- }
127
- return;
128
- }
129
- // Mistral's native adapter uses the same completions stream vocabulary.
130
- if (this.api !== "openai-completions" && this.api !== "mistral-conversations") return;
131
- const choice = Array.isArray(value["choices"]) ? value["choices"][0] : undefined;
132
- const delta = record(choice) && record(choice["delta"]) ? choice["delta"] : undefined;
133
- if (!delta || !Array.isArray(delta["tool_calls"])) return;
134
- // One native delta contains one entry per slot. Continuations may reuse a
135
- // slot in later chunks, but merging two entries here could execute neither
136
- // original declaration's arguments under a silently collapsed identity.
137
- const chunkSlots = new Set<string>();
138
- for (const call of delta["tool_calls"]) {
139
- if (!record(call)) continue;
140
- const id = call["id"], key = this.slotKey(call["index"], id);
141
- if (key === null) continue;
142
- if (chunkSlots.has(key)) { this.faults.add("native stream chunk repeats a tool call slot"); continue; }
143
- chunkSlots.add(key);
144
- // Native continuation chunks may spell omitted IDs as null. An indexed
145
- // slot retains its original identity; a first slot still needs a real ID.
146
- const supplied = id !== null && id !== undefined;
147
- const slot = this.slot(key, id, typeof id === "string" ? id : null, supplied);
148
- if (!record(call["function"])) continue;
149
- const fragment = call["function"]["arguments"];
150
- if (typeof fragment === "string") slot.raw += fragment;
151
- else if (this.api === "mistral-conversations" && fragment !== undefined) slot.raw += JSON.stringify(fragment);
152
- this.publish(slot);
153
- }
154
- }
155
-
156
- identityErrors(): string[] {
157
- // D5: SDK block IDs may merge Completions calls or replace missing Mistral
158
- // IDs. Only the protocol's original slots can prove one receipt per call.
159
- const native = [...this.slots.values()].map(slot => slot.id);
160
- const malformed = native.flatMap((id, index) => typeof id === "string" && !(this.api === "mistral-conversations" && id === "null")
161
- ? [] : [`native call ID at slot ${index} must be a real string identity`]);
162
- const calls: AssistantBlock[] = native.filter((id): id is string => typeof id === "string")
163
- .map(call_id => ({ type: "tool_use", call_id, name: "", input: null }));
164
- return [...this.faults, ...malformed, ...toolCallIdentityViolations(calls).map(violation => violation.detail)];
165
- }
166
- }
167
-
168
- /** Complete usage survives a rejected response without making malformed calls durable conversation history. */
169
- export class ProviderIntegrityError extends FailedResponseError {
170
- readonly code = "invalid_tool_call_identity";
171
- constructor(details: readonly string[], usage?: UsageSample) {
172
- super(`Invalid provider tool call identity: ${details.join("; ")}`, usage);
173
- this.name = "ProviderIntegrityError";
174
- }
175
- }
176
-
177
- /** Native envelopes keep their SDK-owned tools/system/signal when adding nested generation options. */
178
- function mergePayload(payload: Record<string, unknown>, patch: Record<string, unknown>): Record<string, unknown> {
179
- const next = { ...payload };
180
- for (const [key, value] of Object.entries(patch)) {
181
- const current = next[key];
182
- next[key] = record(current) && record(value) ? mergePayload(current, value) : value;
183
- }
184
- return next;
185
- }
186
-
187
- /** Google exposes structured HTTP failures as JSON in its public stream error, without a fetch hook. */
188
- function nativeStatus(message: string): number | undefined {
189
- try {
190
- const body: unknown = JSON.parse(message);
191
- const code = record(body) && record(body["error"]) ? body["error"]["code"] : undefined;
192
- return typeof code === "number" && Number.isInteger(code) && code >= 400 && code <= 599 ? code : undefined;
193
- } catch { return undefined; }
194
- }
195
-
196
- function piModel(spec: ModelSpec, baseUrl: string): Model<Api> {
197
- return { id: spec.wireId ?? spec.id, provider: spec.provider, name: spec.name, api: spec.api, baseUrl,
198
- reasoning: spec.reasoning, input: [...(spec.input ?? ["text"])],
199
- // Required SDK metadata defaults are operational only; xeno still reports absent prices as unknown.
200
- contextWindow: spec.contextWindow ?? 131_072, maxTokens: spec.maxTokens ?? 16_000,
201
- cost: { input: spec.price?.input ?? 0, output: spec.price?.output ?? 0, cacheRead: spec.price?.cacheRead ?? 0, cacheWrite: spec.price?.cacheWrite ?? 0 },
202
- ...(spec.thinkingLevelMap ? { thinkingLevelMap: spec.thinkingLevelMap } : {}),
203
- ...(spec.nativeCompat || spec.compat || spec.api === "anthropic-messages"
204
- ? { compat: { ...spec.compat, ...spec.nativeCompat, ...(spec.api === "anthropic-messages" ? { allowEmptySignature: true } : {}) } } : {}),
205
- ...(spec.samplingParams ? { samplingParams: spec.samplingParams } : {}) } as Model<Api>;
206
- }
207
-
208
- type PiBlock = AssistantMessage["content"][number];
209
- function thinkingOf(raw: unknown): Extract<PiBlock, { type: "thinking" }> | null {
210
- if (!record(raw)) return null;
211
- if (record(raw["pi"]) && raw["pi"]["type"] === "thinking" && typeof raw["pi"]["thinking"] === "string") {
212
- return { ...structuredClone(raw["pi"]), type: "thinking", thinking: raw["pi"]["thinking"] };
213
- }
214
- if (raw["type"] === "thinking") return { type: "thinking", thinking: typeof raw["thinking"] === "string" ? raw["thinking"] : "", ...(typeof raw["signature"] === "string" ? { thinkingSignature: raw["signature"] } : {}) };
215
- if (raw["type"] === "redacted_thinking" && typeof raw["data"] === "string") return { type: "thinking", thinking: "", redacted: true, thinkingSignature: raw["data"] };
216
- if (record(raw["responses_item"])) return { type: "thinking", thinking: "", thinkingSignature: JSON.stringify(raw["responses_item"]) };
217
- if (typeof raw["reasoning_content"] === "string") return { type: "thinking", thinking: raw["reasoning_content"], ...(raw["reasoning_details"] !== undefined ? { thinkingSignature: JSON.stringify(raw["reasoning_details"]) } : {}) };
218
- return null;
219
- }
220
-
221
- /**
222
- * Reasoning never enters the visible channel. Pi turns thinking it cannot replay natively into a
223
- * text part, and a model shown its own reasoning as earlier visible output starts writing its
224
- * reasoning as visible output (an unsigned Gemini block via an Anthropic gateway snowballed into
225
- * monologue in every later reply). An unsigned block here was sent that way by the server --
226
- * interrupted streams throw and are never logged -- so anthropic-messages replays it verbatim
227
- * (`allowEmptySignature` in piModel, as the native adapter does). Bedrock has no unsigned
228
- * reasoning form, so there the block is dropped. Cross-model text conversion cannot occur: a
229
- * model switch cuts the node (Session.switchModel) and every replayed message names this model.
230
- */
231
- function replaysAsReasoning(api: Api, block: Extract<PiBlock, { type: "thinking" }>): boolean {
232
- return api !== "bedrock-converse-stream" || block.redacted === true || !!block.thinkingSignature?.trim();
233
- }
234
-
235
- /** Pure conversion of an already hydrated projection; no second transcript is retained. */
236
- export function piContext(frozen: FrozenPrefix, messages: readonly Message[], spec: ModelSpec): Context {
237
- const out: PiMessage[] = [];
238
- const names = new Map<string, string>();
239
- const ids = new Map<string, string>();
240
- for (const message of messages) {
241
- if (message.role === "assistant") {
242
- const content: PiBlock[] = [];
243
- for (const block of message.content) {
244
- switch (block.type) {
245
- case "text": {
246
- const wire = record(block.wire) ? block.wire : {};
247
- content.push({ type: "text", text: block.text, ...(typeof wire["textSignature"] === "string" ? { textSignature: wire["textSignature"] } : {}) }); break;
248
- }
249
- case "thinking": { const thinking = thinkingOf(block.raw); if (thinking && replaysAsReasoning(spec.api, thinking)) content.push(thinking); break; }
250
- case "tool_use": {
251
- names.set(block.id, block.name);
252
- const wire = record(block.wire) ? block.wire : {};
253
- const native = record(wire["pi"]) ? wire["pi"] : {};
254
- const extra = record(wire["extra_content"]) && record(wire["extra_content"]["google"]) ? wire["extra_content"]["google"] : {};
255
- const signature = native["thoughtSignature"] ?? extra["thought_signature"];
256
- const id = typeof wire["item_id"] === "string" && spec.api.includes("responses") ? `${block.id}|${wire["item_id"]}` : block.id;
257
- ids.set(block.id, id);
258
- content.push({ type: "toolCall", id, name: block.name, arguments: structuredClone(block.input) as Extract<PiBlock, { type: "toolCall" }>["arguments"],
259
- ...(typeof signature === "string" ? { thoughtSignature: signature } : {}), ...(typeof native["namespace"] === "string" ? { namespace: native["namespace"] } : {}) }); break;
260
- }
261
- case "tool_result": case "image": break;
262
- default: assertNever(block, "pi assistant block");
263
- }
264
- }
265
- out.push({ role: "assistant", content, api: spec.api, provider: spec.provider, model: spec.wireId ?? spec.id,
266
- timestamp: 0, usage: structuredClone(EMPTY_USAGE), stopReason: content.some(block => block.type === "toolCall") ? "toolUse" : "stop" });
267
- } else {
268
- let content: Extract<PiMessage, { role: "user" }>["content"] = [];
269
- let result: Extract<PiMessage, { role: "toolResult" }> | undefined;
270
- const flush = () => { if (content.length) out.push({ role: "user", content, timestamp: 0 }); content = []; };
271
- for (const block of message.content) {
272
- if (block.type === "tool_result") {
273
- flush(); result = { role: "toolResult", toolCallId: ids.get(block.tool_use_id) ?? block.tool_use_id, toolName: names.get(block.tool_use_id) ?? "tool",
274
- content: [{ type: "text", text: block.content }], isError: block.is_error ?? false, timestamp: 0 };
275
- out.push(result);
276
- } else if (block.type === "text") { content.push({ type: "text", text: block.text }); result = undefined; }
277
- else if (block.type === "image") {
278
- // D5: the projection puts each image after its paired text receipt. Keep that ownership;
279
- // Pi emits complete result batches before fallback user images when a protocol requires it.
280
- const image = { type: "image" as const, data: imageData(block), mimeType: block.image.mimeType };
281
- if (result) result.content.push(image); else content.push(image);
282
- }
283
- }
284
- flush();
285
- }
286
- }
287
- return { systemPrompt: frozen.system, messages: out, tools: frozen.tools.map(value => {
288
- const schema = value as ToolSchema;
289
- return { name: schema.name, description: schema.description, parameters: schema.input_schema as Tool["parameters"] };
290
- }) };
291
- }
292
-
293
- export function piBlocks(message: AssistantMessage, originalArguments: ReadonlyMap<string, string> = new Map()): AssistantBlock[] {
294
- return message.content.map((block): AssistantBlock => {
295
- switch (block.type) {
296
- case "text": return { type: "text", text: block.text, ...(block.textSignature ? { wire: { textSignature: block.textSignature } } : {}) };
297
- case "thinking": {
298
- let raw: unknown = { pi: structuredClone(block) };
299
- if (message.api === "anthropic-messages") raw = block.redacted ? { type: "redacted_thinking", data: block.thinkingSignature }
300
- : { type: "thinking", thinking: block.thinking, signature: block.thinkingSignature ?? "" };
301
- else if (message.api.includes("responses") && block.thinkingSignature) { try { raw = { responses_item: JSON.parse(block.thinkingSignature) }; } catch { /* An opaque signature is still preserved by the envelope. */ } }
302
- else if (message.api === "openai-completions") {
303
- let details: unknown;
304
- try { if (block.thinkingSignature) details = JSON.parse(block.thinkingSignature); } catch { /* The Pi envelope preserves non-JSON signatures. */ }
305
- raw = { pi: structuredClone(block), reasoning_content: block.thinking, ...(details !== undefined ? { reasoning_details: details } : {}) };
306
- }
307
- return { type: "thinking", text: block.thinking, raw };
308
- }
309
- case "toolCall": return { type: "tool_use", call_id: block.id, name: block.name,
310
- input: originalArguments.has(block.id) ? completedToolInput(originalArguments.get(block.id)!) : structuredClone(block.arguments),
311
- ...(block.thoughtSignature || block.namespace ? { wire: { pi: { thoughtSignature: block.thoughtSignature, namespace: block.namespace } } } : {}) };
312
- default: return assertNever(block, "pi response block");
313
- }
314
- });
315
- }
316
-
317
- export class PiProvider implements Provider {
318
- readonly config: ProviderConfig;
319
- private readonly apiKey: string;
320
- private readonly connections = new ConnectionReuse();
321
- private readonly options: {
322
- fetch?: typeof fetch; effects?: ResolveEffects; oauth?: boolean; sessionId?: string;
323
- retry?: RetryPolicy; onRetry?: (attempt: number, delayMs: number, error: unknown) => void;
324
- /** Silence after which one attempt is abandoned; tests shorten it (stream-idle.ts). */
325
- streamIdleMs?: number;
326
- };
327
- constructor(config: ProviderConfig, apiKey: string, options: PiProvider["options"] = {}) {
328
- this.config = config; this.apiKey = apiKey; this.options = options;
329
- }
330
-
331
- send(frozen: FrozenPrefix, messages: readonly Message[], node: number, signal?: AbortSignal, onDelta?: OnDelta, policy?: ProviderSendOptions): Promise<Turn> {
332
- // Bedrock's Pi adapter leaves retry ownership with the AWS SDK; never multiply that loop.
333
- const retry = this.options.retry ?? (this.config.spec.api === "bedrock-converse-stream" ? { ...DEFAULT_RETRY, maxRetries: 0 } : DEFAULT_RETRY);
334
- return withRetry(() => watchIdle(signal, this.options.streamIdleMs, idle => this.attempt(frozen, messages, node, idle, signal, onDelta, policy)).catch((error: unknown) => { this.connections.note(error); throw error; }), retry, undefined,
335
- (attempt, delayMs, error) => { onDelta?.({ kind: "discard" }); onDelta?.({ kind: "retry", attempt, delayMs, max: retry.maxRetries, reason: retryLabel(error) }); this.options.onRetry?.(attempt, delayMs, error); }, signal);
336
- }
337
-
338
- private async attempt(frozen: FrozenPrefix, messages: readonly Message[], node: number, idle: IdleWatch, signal?: AbortSignal, onDelta?: OnDelta, policy?: ProviderSendOptions): Promise<Turn> {
339
- signal?.throwIfAborted();
340
- const started = Date.now();
341
- // Wrapped innermost: providerRequest rebuilds the call as a Request and
342
- // passes no init, so a keepalive set any further out would be dropped.
343
- const request = providerRequest(this.config.spec, this.apiKey, this.options.effects,
344
- this.connections.wrap(this.options.fetch ?? globalThis.fetch), this.options.oauth);
345
- const model = piModel(this.config.spec, request.baseUrl);
346
- let status: number | undefined;
347
- let responseHeaders = new Headers();
348
- const observedFetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
349
- const response = await request.fetch(input, init);
350
- status = response.status; responseHeaders = new Headers(response.headers);
351
- return response;
352
- }) as typeof fetch;
353
- const nativeHttp = ["google-generative-ai", "google-vertex", "bedrock-converse-stream"].includes(model.api);
354
- const originalArguments = new OriginalToolArguments(model.api);
355
- let hasKnownUsage = false;
356
- const options: SimpleStreamOptions = { ...request.options, ...(request.key ? { apiKey: request.key } : {}),
357
- // Cache routing follows a root session and its forks without introducing server transcript state (D8).
358
- ...(this.options.sessionId === undefined ? {} : { sessionId: this.options.sessionId }),
359
- env: request.env, headers: request.headers, ...(nativeHttp ? {} : { fetch: observedFetch }), maxRetries: 0, transport: "sse",
360
- maxTokens: this.config.maxTokens, ...(this.config.effort !== "off" ? { reasoning: this.config.effort } : {}),
361
- signal: idle.signal, onResponse: response => { idle.touch(); status = response.status; responseHeaders = new Headers(response.headers); },
362
- onProviderStreamEvent: event => {
363
- idle.touch();
364
- originalArguments.observe(event);
365
- hasKnownUsage ||= nativeUsageReported(model.api, event);
366
- },
367
- onPayload: payload => {
368
- if (!record(payload)) return payload;
369
- const next = mergePayload(payload, request.body);
370
- // Stateless Responses is required by D8: never replace the recomputable projection with server state.
371
- if (model.api.includes("responses")) { next["store"] = false; delete next["previous_response_id"]; }
372
- return policy?.toolUse === "disabled" ? disablePayloadTools(model.api, next) : next;
373
- } };
374
- let completed: AssistantMessage | undefined;
375
- // Pi emits fragments; xeno's progress contract replaces the displayed argument buffer.
376
- // Keep buffers per content block so interleaved calls and retries cannot share partial JSON.
377
- const toolArguments = new Map<number, string>();
378
- for await (const event of idle.events(APIS[model.api].streamSimple(model, normalizeContext(piContext(frozen, messages, this.config.spec)), options))) {
379
- switch (event.type) {
380
- case "text_delta": onDelta?.({ kind: "text", text: event.delta }); break;
381
- case "thinking_delta": onDelta?.({ kind: "thinking", text: event.delta }); break;
382
- case "toolcall_start": {
383
- const block = event.partial.content[event.contentIndex];
384
- toolArguments.set(event.contentIndex, "");
385
- if (block?.type === "toolCall") onDelta?.({ kind: "tool", id: block.id, name: block.name, arguments: "" }); break;
386
- }
387
- case "toolcall_delta": {
388
- const block = event.partial.content[event.contentIndex];
389
- const argumentsText = (toolArguments.get(event.contentIndex) ?? "") + event.delta;
390
- toolArguments.set(event.contentIndex, argumentsText);
391
- if (block?.type === "toolCall") onDelta?.({ kind: "tool", id: block.id, name: block.name, arguments: argumentsText }); break;
392
- }
393
- case "toolcall_end": {
394
- const block = event.toolCall;
395
- onDelta?.({ kind: "tool", id: block.id, name: block.name, arguments: JSON.stringify(block.arguments) });
396
- toolArguments.delete(event.contentIndex); break;
397
- }
398
- case "done": completed = event.message; break;
399
- case "error":
400
- if (event.reason === "aborted" || signal?.aborted) throw new DOMException("Request aborted", "AbortError");
401
- status ??= nativeStatus(event.error.errorMessage ?? "");
402
- if (status && status >= 400) {
403
- // The proxy's own text says "run `grok update`", which a xeno user cannot act on.
404
- const hint = grokVersionHint(status, this.config.spec.provider);
405
- throw new HttpError(status, responseHeaders, (event.error.errorMessage ?? "Provider request failed") + (hint ? `\n ${hint}` : ""));
406
- }
407
- throw new StreamError(event.error.errorMessage ?? "Provider stream failed");
408
- case "start": case "text_start": case "text_end": case "thinking_start": case "thinking_end": break;
409
- default: assertNever(event, "pi stream event");
410
- }
411
- }
412
- signal?.throwIfAborted();
413
- if (!completed || completed.stopReason === "deferred") throw new StreamError("Provider did not complete a synchronous agent turn");
414
- const usage: UsageSample = { input_tokens: completed.usage.input, output_tokens: completed.usage.output,
415
- cache_read_input_tokens: completed.usage.cacheRead, cache_creation_input_tokens: completed.usage.cacheWrite, node, ms: Date.now() - started };
416
- const identityErrors = originalArguments.identityErrors();
417
- if (identityErrors.length) throw new ProviderIntegrityError(identityErrors, hasKnownUsage ? usage : undefined);
418
- // The SDK has already classified native STOP/end_turn/toolUse/length values. The loop consumes
419
- // xeno's closed completion vocabulary, never a raw native spelling that could skip tool execution.
420
- return coherentTurn({ blocks: piBlocks(completed, originalArguments.values), stopReason: completed.stopReason === "length" ? "max_tokens" : completed.stopReason === "toolUse" ? "tool_use" : "end_turn",
421
- usage }, hasKnownUsage);
422
- }
423
- }