bazilion 0.0.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 (260) hide show
  1. package/.understand-anything/.understandignore +25 -0
  2. package/.understand-anything/fingerprints.json +14267 -0
  3. package/.understand-anything/knowledge-graph.json +18128 -0
  4. package/.understand-anything/meta.json +6 -0
  5. package/CLAUDE.md +164 -0
  6. package/LICENSE +21 -0
  7. package/README.md +195 -0
  8. package/apps/cli/package.json +21 -0
  9. package/apps/cli/src/auth-file.ts +18 -0
  10. package/apps/cli/src/client.ts +37 -0
  11. package/apps/cli/src/columnize.ts +32 -0
  12. package/apps/cli/src/commands/agent.ts +574 -0
  13. package/apps/cli/src/commands/auth.ts +110 -0
  14. package/apps/cli/src/commands/backup.ts +135 -0
  15. package/apps/cli/src/commands/completion.ts +155 -0
  16. package/apps/cli/src/commands/config.ts +95 -0
  17. package/apps/cli/src/commands/doctor.ts +131 -0
  18. package/apps/cli/src/commands/group.ts +132 -0
  19. package/apps/cli/src/commands/inbox.ts +82 -0
  20. package/apps/cli/src/commands/login.ts +73 -0
  21. package/apps/cli/src/commands/memory.ts +106 -0
  22. package/apps/cli/src/commands/profile.ts +259 -0
  23. package/apps/cli/src/commands/provider.ts +170 -0
  24. package/apps/cli/src/commands/send.ts +24 -0
  25. package/apps/cli/src/commands/serve.ts +89 -0
  26. package/apps/cli/src/commands/skill.ts +120 -0
  27. package/apps/cli/src/commands/token.ts +148 -0
  28. package/apps/cli/src/commands/trigger.ts +129 -0
  29. package/apps/cli/src/commands/uninstall.ts +156 -0
  30. package/apps/cli/src/index.ts +196 -0
  31. package/apps/cli/src/paths.ts +12 -0
  32. package/apps/cli/test/agent.test.ts +213 -0
  33. package/apps/cli/test/backup.test.ts +95 -0
  34. package/apps/cli/test/chat.test.ts +539 -0
  35. package/apps/cli/test/columnize.test.ts +29 -0
  36. package/apps/cli/test/completion.test.ts +45 -0
  37. package/apps/cli/test/config-page.test.ts +151 -0
  38. package/apps/cli/test/group.test.ts +74 -0
  39. package/apps/cli/test/helpers.ts +70 -0
  40. package/apps/cli/test/inbox-autodeliver.test.ts +143 -0
  41. package/apps/cli/test/inbox.test.ts +147 -0
  42. package/apps/cli/test/memory.test.ts +69 -0
  43. package/apps/cli/test/profile.test.ts +110 -0
  44. package/apps/cli/test/send.test.ts +30 -0
  45. package/apps/cli/test/server-fixture.ts +212 -0
  46. package/apps/cli/test/session-head.test.ts +45 -0
  47. package/apps/cli/test/skill.test.ts +128 -0
  48. package/apps/cli/test/token.test.ts +109 -0
  49. package/apps/cli/test/trigger.test.ts +245 -0
  50. package/apps/cli/tsconfig.json +4 -0
  51. package/apps/daemon/package.json +31 -0
  52. package/apps/daemon/src/app.ts +41 -0
  53. package/apps/daemon/src/core/agent/archive.ts +8 -0
  54. package/apps/daemon/src/core/agent/delete.ts +30 -0
  55. package/apps/daemon/src/core/agent/resolve.ts +31 -0
  56. package/apps/daemon/src/core/agent/spawn.ts +95 -0
  57. package/apps/daemon/src/core/agent/unarchive.ts +11 -0
  58. package/apps/daemon/src/core/availableModels.ts +58 -0
  59. package/apps/daemon/src/core/db/client.ts +113 -0
  60. package/apps/daemon/src/core/db/migrate.ts +41 -0
  61. package/apps/daemon/src/core/db/migrations/0001_init.sql +179 -0
  62. package/apps/daemon/src/core/group/delete.ts +21 -0
  63. package/apps/daemon/src/core/group/register.ts +68 -0
  64. package/apps/daemon/src/core/index.ts +71 -0
  65. package/apps/daemon/src/core/paths.ts +56 -0
  66. package/apps/daemon/src/core/profile/create.ts +78 -0
  67. package/apps/daemon/src/core/profile/delete.ts +26 -0
  68. package/apps/daemon/src/core/profile/identity.ts +70 -0
  69. package/apps/daemon/src/core/profile/load.ts +45 -0
  70. package/apps/daemon/src/core/profile/seed.ts +74 -0
  71. package/apps/daemon/src/core/profile/templates.ts +60 -0
  72. package/apps/daemon/src/core/profile/update.ts +57 -0
  73. package/apps/daemon/src/core/profile/validate.ts +9 -0
  74. package/apps/daemon/src/core/repos/agents.ts +202 -0
  75. package/apps/daemon/src/core/repos/config.ts +78 -0
  76. package/apps/daemon/src/core/repos/groups.ts +55 -0
  77. package/apps/daemon/src/core/repos/messages.ts +127 -0
  78. package/apps/daemon/src/core/repos/profiles.ts +83 -0
  79. package/apps/daemon/src/core/repos/providerModels.ts +58 -0
  80. package/apps/daemon/src/core/repos/providerState.ts +37 -0
  81. package/apps/daemon/src/core/repos/secrets.ts +145 -0
  82. package/apps/daemon/src/core/repos/skillMeta.ts +49 -0
  83. package/apps/daemon/src/core/repos/triggers.ts +101 -0
  84. package/apps/daemon/src/core/repos/webTokens.ts +87 -0
  85. package/apps/daemon/src/core/secrets.ts +65 -0
  86. package/apps/daemon/src/core/services.ts +264 -0
  87. package/apps/daemon/src/core/skills/discover.ts +28 -0
  88. package/apps/daemon/src/core/skills/import.ts +136 -0
  89. package/apps/daemon/src/core/skills/parse.ts +52 -0
  90. package/apps/daemon/src/core/skills/resolve.ts +50 -0
  91. package/apps/daemon/src/index.ts +45 -0
  92. package/apps/daemon/src/lib/agent-cancel.ts +48 -0
  93. package/apps/daemon/src/lib/agent-id.ts +13 -0
  94. package/apps/daemon/src/lib/agent-turn.ts +56 -0
  95. package/apps/daemon/src/lib/api-key.ts +56 -0
  96. package/apps/daemon/src/lib/auth.ts +41 -0
  97. package/apps/daemon/src/lib/cron.ts +93 -0
  98. package/apps/daemon/src/lib/ctx.ts +80 -0
  99. package/apps/daemon/src/lib/messaging-host.ts +34 -0
  100. package/apps/daemon/src/lib/middleware-auth.ts +52 -0
  101. package/apps/daemon/src/lib/scheduler.ts +294 -0
  102. package/apps/daemon/src/routes/agents.ts +772 -0
  103. package/apps/daemon/src/routes/auth-login.ts +193 -0
  104. package/apps/daemon/src/routes/config.ts +267 -0
  105. package/apps/daemon/src/routes/groups.ts +133 -0
  106. package/apps/daemon/src/routes/messages.ts +29 -0
  107. package/apps/daemon/src/routes/misc.ts +239 -0
  108. package/apps/daemon/src/routes/profiles.ts +197 -0
  109. package/apps/daemon/src/routes/skills.ts +123 -0
  110. package/apps/daemon/src/routes/triggers.ts +29 -0
  111. package/apps/daemon/src/runtime/auth/openai-codex.ts +121 -0
  112. package/apps/daemon/src/runtime/auto-reply/heartbeat.ts +31 -0
  113. package/apps/daemon/src/runtime/index.ts +77 -0
  114. package/apps/daemon/src/runtime/memory/files.ts +103 -0
  115. package/apps/daemon/src/runtime/memory/qmd.ts +152 -0
  116. package/apps/daemon/src/runtime/memory/types.ts +16 -0
  117. package/apps/daemon/src/runtime/pi/events.ts +173 -0
  118. package/apps/daemon/src/runtime/pi/session.ts +536 -0
  119. package/apps/daemon/src/runtime/pi/tools.ts +85 -0
  120. package/apps/daemon/src/runtime/providers/catalog.ts +145 -0
  121. package/apps/daemon/src/runtime/providers/pi-adapter.ts +272 -0
  122. package/apps/daemon/src/runtime/providers/registry.ts +374 -0
  123. package/apps/daemon/src/runtime/providers/retry.ts +176 -0
  124. package/apps/daemon/src/runtime/providers/types.ts +33 -0
  125. package/apps/daemon/src/runtime/session/prompt.ts +83 -0
  126. package/apps/daemon/src/runtime/tools/bootstrap.ts +22 -0
  127. package/apps/daemon/src/runtime/tools/home.ts +114 -0
  128. package/apps/daemon/src/runtime/tools/memory.ts +81 -0
  129. package/apps/daemon/src/runtime/tools/messaging.ts +127 -0
  130. package/apps/daemon/src/runtime/tools/registry.ts +29 -0
  131. package/apps/daemon/src/runtime/tools/types.ts +13 -0
  132. package/apps/daemon/src/runtime/tools/web-extract.ts +110 -0
  133. package/apps/daemon/src/runtime/tools/web-ssrf.ts +245 -0
  134. package/apps/daemon/src/runtime/tools/web.ts +273 -0
  135. package/apps/daemon/src/runtime/worker/entry.ts +221 -0
  136. package/apps/daemon/src/runtime/worker/ipc-protocol.ts +75 -0
  137. package/apps/daemon/src/runtime/worker/spawn.ts +249 -0
  138. package/apps/daemon/test/core/agents.test.ts +319 -0
  139. package/apps/daemon/test/core/available-models.test.ts +63 -0
  140. package/apps/daemon/test/core/config.test.ts +99 -0
  141. package/apps/daemon/test/core/groups.test.ts +63 -0
  142. package/apps/daemon/test/core/helpers.ts +50 -0
  143. package/apps/daemon/test/core/identity.test.ts +85 -0
  144. package/apps/daemon/test/core/migrations.test.ts +83 -0
  145. package/apps/daemon/test/core/profiles.test.ts +182 -0
  146. package/apps/daemon/test/core/provider-models.test.ts +57 -0
  147. package/apps/daemon/test/core/provider-state.test.ts +36 -0
  148. package/apps/daemon/test/core/skill-meta.test.ts +45 -0
  149. package/apps/daemon/test/core/skills.test.ts +271 -0
  150. package/apps/daemon/test/core/triggers.test.ts +182 -0
  151. package/apps/daemon/test/core/web-tokens.test.ts +79 -0
  152. package/apps/daemon/test/cron.test.ts +90 -0
  153. package/apps/daemon/test/runtime/heartbeat.test.ts +34 -0
  154. package/apps/daemon/test/runtime/memory-qmd.test.ts +97 -0
  155. package/apps/daemon/test/runtime/memory.test.ts +78 -0
  156. package/apps/daemon/test/runtime/messaging.test.ts +213 -0
  157. package/apps/daemon/test/runtime/mock-server.ts +65 -0
  158. package/apps/daemon/test/runtime/openai-codex-auth.test.ts +116 -0
  159. package/apps/daemon/test/runtime/providers.test.ts +306 -0
  160. package/apps/daemon/test/runtime/retry.test.ts +191 -0
  161. package/apps/daemon/test/runtime/session-head.test.ts +90 -0
  162. package/apps/daemon/test/runtime/tools-home.test.ts +102 -0
  163. package/apps/daemon/test/runtime/tools-web.test.ts +206 -0
  164. package/apps/daemon/tsconfig.json +4 -0
  165. package/apps/mobile/README.md +60 -0
  166. package/apps/mobile/app/_layout.tsx +58 -0
  167. package/apps/mobile/app/agents/[id]/chat.tsx +486 -0
  168. package/apps/mobile/app/agents/[id]/index.tsx +166 -0
  169. package/apps/mobile/app/agents/index.tsx +212 -0
  170. package/apps/mobile/app/index.tsx +21 -0
  171. package/apps/mobile/app/pair.tsx +226 -0
  172. package/apps/mobile/app/settings.tsx +419 -0
  173. package/apps/mobile/app.json +49 -0
  174. package/apps/mobile/assets/adaptive-icon.png +0 -0
  175. package/apps/mobile/assets/favicon.png +0 -0
  176. package/apps/mobile/assets/icon.png +0 -0
  177. package/apps/mobile/assets/splash-icon.png +0 -0
  178. package/apps/mobile/babel.config.js +6 -0
  179. package/apps/mobile/metro.config.js +28 -0
  180. package/apps/mobile/package.json +44 -0
  181. package/apps/mobile/src/auth.ts +66 -0
  182. package/apps/mobile/src/pair-url.ts +48 -0
  183. package/apps/mobile/src/theme-context.tsx +88 -0
  184. package/apps/mobile/src/theme.ts +135 -0
  185. package/apps/mobile/test/pair-url.test.ts +46 -0
  186. package/apps/mobile/tsconfig.json +23 -0
  187. package/apps/web/components.json +25 -0
  188. package/apps/web/package.json +39 -0
  189. package/apps/web/public/baziu.svg +8 -0
  190. package/apps/web/src/components/AgentTabs.tsx +45 -0
  191. package/apps/web/src/components/BaziuLogo.tsx +21 -0
  192. package/apps/web/src/components/ChatPane.tsx +1033 -0
  193. package/apps/web/src/components/ConfigTabs.tsx +29 -0
  194. package/apps/web/src/components/CopyButton.tsx +68 -0
  195. package/apps/web/src/components/CreateGroupDialog.tsx +127 -0
  196. package/apps/web/src/components/FieldRow.tsx +94 -0
  197. package/apps/web/src/components/Footer.tsx +10 -0
  198. package/apps/web/src/components/PawIcon.tsx +15 -0
  199. package/apps/web/src/components/Sidebar.tsx +287 -0
  200. package/apps/web/src/components/SpawnDialog.tsx +129 -0
  201. package/apps/web/src/components/ThemeToggle.tsx +75 -0
  202. package/apps/web/src/components/TopNav.tsx +34 -0
  203. package/apps/web/src/components/ui/button.tsx +67 -0
  204. package/apps/web/src/components/ui/card.tsx +103 -0
  205. package/apps/web/src/components/ui/checkbox.tsx +31 -0
  206. package/apps/web/src/components/ui/dialog.tsx +168 -0
  207. package/apps/web/src/components/ui/input.tsx +19 -0
  208. package/apps/web/src/components/ui/label.tsx +22 -0
  209. package/apps/web/src/components/ui/radio-group.tsx +44 -0
  210. package/apps/web/src/components/ui/select.tsx +192 -0
  211. package/apps/web/src/components/ui/separator.tsx +26 -0
  212. package/apps/web/src/components/ui/table.tsx +116 -0
  213. package/apps/web/src/components/ui/tabs.tsx +88 -0
  214. package/apps/web/src/components/ui/textarea.tsx +18 -0
  215. package/apps/web/src/lib/auth.ts +50 -0
  216. package/apps/web/src/lib/daemon-client.ts +34 -0
  217. package/apps/web/src/lib/md.ts +45 -0
  218. package/apps/web/src/lib/utils.ts +6 -0
  219. package/apps/web/src/lib/wire-constants.ts +27 -0
  220. package/apps/web/src/routeTree.gen.ts +408 -0
  221. package/apps/web/src/router.tsx +20 -0
  222. package/apps/web/src/routes/__root.tsx +123 -0
  223. package/apps/web/src/routes/agents/$id/inbox.tsx +207 -0
  224. package/apps/web/src/routes/agents/$id/index.tsx +527 -0
  225. package/apps/web/src/routes/agents/$id/triggers.tsx +239 -0
  226. package/apps/web/src/routes/agents/index.tsx +265 -0
  227. package/apps/web/src/routes/api/$.ts +88 -0
  228. package/apps/web/src/routes/config/index.tsx +315 -0
  229. package/apps/web/src/routes/config/services.tsx +49 -0
  230. package/apps/web/src/routes/config/tokens.tsx +192 -0
  231. package/apps/web/src/routes/groups/$id/index.tsx +153 -0
  232. package/apps/web/src/routes/groups/$id/memory.tsx +321 -0
  233. package/apps/web/src/routes/groups/index.tsx +191 -0
  234. package/apps/web/src/routes/index.tsx +133 -0
  235. package/apps/web/src/routes/login.tsx +63 -0
  236. package/apps/web/src/routes/profiles/$id.tsx +549 -0
  237. package/apps/web/src/routes/profiles/index.tsx +458 -0
  238. package/apps/web/src/routes/skills/index.tsx +297 -0
  239. package/apps/web/src/routes/welcome.tsx +61 -0
  240. package/apps/web/src/styles.css +449 -0
  241. package/apps/web/tsconfig.json +25 -0
  242. package/apps/web/vite.config.ts +25 -0
  243. package/biome.json +23 -0
  244. package/docs/agent-engine.md +219 -0
  245. package/docs/architecture.md +627 -0
  246. package/docs/backlog/README.md +42 -0
  247. package/docs/backlog/draft/BAZ-001-a2a-federation-spike.md +125 -0
  248. package/docs/openclaw-reference.md +210 -0
  249. package/package.json +38 -0
  250. package/packages/api-types/package.json +11 -0
  251. package/packages/api-types/src/entities.ts +146 -0
  252. package/packages/api-types/src/events.ts +55 -0
  253. package/packages/api-types/src/index.ts +488 -0
  254. package/packages/api-types/src/memory.ts +15 -0
  255. package/packages/client/package.json +13 -0
  256. package/packages/client/src/index.ts +117 -0
  257. package/pnpm-workspace.yaml +3 -0
  258. package/tsconfig.base.json +23 -0
  259. package/tsconfig.json +11 -0
  260. package/vitest.config.ts +22 -0
@@ -0,0 +1,536 @@
1
+ // Bazilion → pi-coding-agent session bridge.
2
+ //
3
+ // `createBazilionSession` returns a fully-wired `AgentSession` suitable for
4
+ // calling `session.prompt(text)` / `session.compact(instructions)` / etc.
5
+ //
6
+ // What we take ownership of (and hand to pi):
7
+ // - cwd: the agent's default workspace path (or agent.dir as a degenerate
8
+ // fallback when no workspace is mounted). Pi's built-in `read/bash/edit/
9
+ // write/grep/find/ls` tools are rooted here.
10
+ // - agentDir: `<bazilion-home>/pi` — pi writes transient state here
11
+ // (settings overrides, resource caches). We don't share it with the
12
+ // user's global `~/.pi/agent` so a Bazilion install never clobbers an
13
+ // independent pi CLI install.
14
+ // - authStorage: `InMemoryAuthStorageBackend` pre-seeded with the resolved
15
+ // API key for the agent's current provider. We never let pi read/write
16
+ // its own auth file — secrets live in the daemon-owned `secrets` table
17
+ // and reach us via `opts.apiKey` (initial) + `opts.refreshApiKey`
18
+ // (OAuth refresher for long turns).
19
+ // - modelRegistry: in-memory. Native providers (anthropic/openai/google/…)
20
+ // come from pi's bundled catalog. For Bazilion-only providers
21
+ // (`lmstudio`, `ollama`) we call `registerProvider(name, {baseUrl,
22
+ // api: 'openai-completions', authHeader: false})` — matches the
23
+ // openai-completions shim our pi-adapter has been using.
24
+ // - sessionManager: `SessionManager.create(cwd, <agentDir>/sessions)`
25
+ // writing JSONL to `~/.bazilion/agents/<id>/sessions/<sessionId>.jsonl`.
26
+ // Crash-survival, append-only, branching, compaction entries — all owned
27
+ // by pi now. Replaces our `agents.chat_messages` blob.
28
+ // - settingsManager: in-memory. Bazilion controls auto-compaction
29
+ // (disabled — we compact manually on user request via /compact) and
30
+ // retry (enabled with Bazilion-tuned caps).
31
+ //
32
+ // What stays outside pi's purview:
33
+ // - spawning agents / profiles / skills discovery (core/)
34
+ // - workspaces registry & mount tracking (core/)
35
+ // - inter-agent messaging, triggers, scheduler (core + apps/web)
36
+ // - memory backend (we wrap it as a pi customTool via `createBazilionCustomTools`)
37
+
38
+ import { existsSync, mkdirSync, readdirSync, statSync } from 'node:fs'
39
+ import { basename, join } from 'node:path'
40
+ import type { ResolvedAgent } from '@bazilion/api-types'
41
+ import type { AgentMessage, ThinkingLevel } from '@mariozechner/pi-agent-core'
42
+ import {
43
+ type AgentSession,
44
+ AuthStorage,
45
+ createAgentSession,
46
+ createExtensionRuntime,
47
+ ModelRegistry,
48
+ type ResourceLoader,
49
+ SessionManager,
50
+ SettingsManager,
51
+ } from '@mariozechner/pi-coding-agent'
52
+ import type { BazilionDb, Paths } from '../../core/index.ts'
53
+ import { providerStateRepo } from '../../core/index.ts'
54
+ import type { MemoryBackend } from '../memory/types.ts'
55
+ import { resolveModel as resolvePiModel } from '../providers/pi-adapter.ts'
56
+ import { createProviderRegistry, loadProviderConfigFromEnv } from '../providers/registry.ts'
57
+ import { buildSystemPrompt } from '../session/prompt.ts'
58
+ import type { MessagingHost } from '../worker/ipc-protocol.ts'
59
+ import { createBazilionCustomTools } from './tools.ts'
60
+
61
+ const BUILTIN_TOOL_NAMES = ['read', 'bash', 'edit', 'write', 'grep', 'find', 'ls'] as const
62
+
63
+ export interface CreateBazilionSessionOptions {
64
+ agent: ResolvedAgent
65
+ paths: Paths
66
+ /** Merged env (process.env + secrets) — produced via `mergeSecretsIntoEnv`. */
67
+ env: NodeJS.ProcessEnv
68
+ memory: MemoryBackend
69
+ /**
70
+ * Names of providers the user has explicitly enabled in /config.
71
+ * Empty set means "no per-provider gating configured" — all providers pass.
72
+ * Pre-computed by the daemon and handed in so the session never has to
73
+ * touch the SQLite `provider_state` table itself.
74
+ */
75
+ enabledProviders: Set<string>
76
+ /**
77
+ * Optional host for inter-agent messaging. Wired from the worker's IPC
78
+ * channel back to the daemon — workers no longer hold a SQLite handle of
79
+ * their own. Omit to disable the messaging tools entirely (e.g. unit
80
+ * tests that don't exercise inbox flows).
81
+ */
82
+ messagingHost?: MessagingHost
83
+ /**
84
+ * Optional explicit API key for the agent's provider. Wins over any value
85
+ * derived from `env`. Required for OAuth-backed providers (`openai-codex`)
86
+ * since their credentials live in the daemon-owned `secrets` table, not
87
+ * in env vars.
88
+ */
89
+ apiKey?: string
90
+ /**
91
+ * Optional callback for OAuth-backed providers whose access tokens may
92
+ * expire mid-turn. When provided, pi calls it to refresh the JWT during
93
+ * long tool-execution loops. Daemon-side callers (compact/context/truncate)
94
+ * wire this directly against the secrets repo; worker turns currently
95
+ * skip it (the initial token from `apiKey` carries the whole turn).
96
+ */
97
+ refreshApiKey?: (providerName: string) => Promise<string>
98
+ /**
99
+ * Session id to resume. When omitted, pi starts a fresh session file.
100
+ * `/reset` passes `undefined` to rotate; normal chat passes the agent's
101
+ * current session id (persisted on the Bazilion side as `agents.session_id`
102
+ * if we later add that column — today we just restore the most recent
103
+ * session file, which pi's SessionManager locates automatically).
104
+ */
105
+ sessionId?: string
106
+ }
107
+
108
+ export interface BazilionSessionHandle {
109
+ session: AgentSession
110
+ /** Call when done — disposes listeners + closes the pi session. */
111
+ dispose(): void
112
+ }
113
+
114
+ /**
115
+ * Build a pi `AgentSession` using Bazilion's resolved agent + provider state.
116
+ * The returned session is ready for `prompt()` / `compact()` / `reset()`.
117
+ */
118
+ export async function createBazilionSession(
119
+ opts: CreateBazilionSessionOptions,
120
+ ): Promise<BazilionSessionHandle> {
121
+ const { agent, paths, env, memory, enabledProviders, messagingHost, refreshApiKey } = opts
122
+
123
+ const { providerName, modelId } = splitModelString(agent.model)
124
+
125
+ // Enabled-set gate — mirrors createProviderRegistry's check. We keep the
126
+ // Bazilion-side enabled/disabled /config toggles authoritative even though
127
+ // pi does its own provider resolution: we simply refuse to build a session
128
+ // for a disabled provider. The set is pre-computed by the daemon (the
129
+ // worker has no SQLite handle of its own).
130
+ if (enabledProviders.size > 0 && !enabledProviders.has(providerName)) {
131
+ throw new Error(`${providerName} provider is disabled — enable it on the /config page`)
132
+ }
133
+
134
+ // Build the pi Model<Api>. This reuses the same catalog-lookup + literal-
135
+ // fallback that the pi-adapter uses for Provider.chat today, so `lmstudio:
136
+ // any-model` / unreleased OpenAI models / etc. keep working.
137
+ const piProviderName = mapProviderName(providerName)
138
+ const model = resolvePiModel(
139
+ {
140
+ providerName,
141
+ piProviderName,
142
+ fallbackApi: pickFallbackApi(providerName),
143
+ baseUrl: resolveBaseUrl(providerName, env),
144
+ },
145
+ modelId,
146
+ )
147
+
148
+ // Resolve the API key. Caller-supplied `opts.apiKey` wins (the daemon
149
+ // passes pre-fetched OAuth tokens for `openai-codex` here); otherwise
150
+ // fall back to the env-derived key. Pi's AuthStorage is in-memory only —
151
+ // we never write `auth.json`. `setRuntimeApiKey` is the process-scoped
152
+ // override hook AuthStorage exposes for exactly this.
153
+ const apiKey = opts.apiKey ?? resolveApiKey(providerName, env)
154
+
155
+ const authStorage = AuthStorage.inMemory()
156
+ if (apiKey) {
157
+ authStorage.setRuntimeApiKey(piProviderName, apiKey)
158
+ }
159
+
160
+ const modelRegistry = ModelRegistry.inMemory(authStorage)
161
+ // Bazilion-only providers aren't in pi's bundled catalog; register them
162
+ // dynamically so ModelRegistry accepts the Model<> object + resolves auth.
163
+ if (providerName === 'lmstudio' || providerName === 'ollama') {
164
+ modelRegistry.registerProvider(piProviderName, {
165
+ baseUrl: model.baseUrl,
166
+ api: 'openai-completions',
167
+ authHeader: false,
168
+ apiKey: apiKey ?? 'dummy',
169
+ })
170
+ }
171
+
172
+ // cwd for pi's coding tools is the agent's group directory. Every agent
173
+ // belongs to exactly one group; the group's filesystem root is where work
174
+ // product lives and where the agent's `read`/`bash`/`edit`/`write` are
175
+ // rooted. Private identity/soul files live in `agent.dir` and are reached
176
+ // through the scoped `home_*` tools, not via cwd.
177
+ const cwd = agent.group.path
178
+ if (!existsSync(cwd)) mkdirSync(cwd, { recursive: true })
179
+
180
+ // Session file under the agent's own directory. Keeping it under
181
+ // `agents/<id>/sessions/` makes `bazilion uninstall` (data tier) already
182
+ // clean them up without changes.
183
+ //
184
+ // Resume-or-create: pi's SessionManager has no built-in "latest session"
185
+ // opener. We walk the session dir for the newest `.jsonl` and open it;
186
+ // fall back to `create()` when none exists (fresh agent or post-/reset).
187
+ // This is what makes turn-to-turn continuity work: each worker turn picks
188
+ // up where the last one left off.
189
+ const sessionDir = join(paths.agentDir(agent.agent.id), 'sessions')
190
+ mkdirSync(sessionDir, { recursive: true })
191
+ const existing = findMostRecent(sessionDir)
192
+ const sessionManager = existing
193
+ ? SessionManager.open(existing, sessionDir, cwd)
194
+ : SessionManager.create(cwd, sessionDir)
195
+
196
+ // In-memory settings: auto-compaction off (we trigger compaction manually
197
+ // via /compact), retry on with Bazilion-tuned caps matching what withRetry
198
+ // used to apply before pi-adoption.
199
+ const settingsManager = SettingsManager.inMemory({
200
+ compaction: { enabled: false },
201
+ retry: { enabled: true, maxRetries: 2, baseDelayMs: 500, maxDelayMs: 8_000 },
202
+ })
203
+
204
+ // Bazilion-authored system prompt becomes an `appendSystemPrompt` entry.
205
+ // Pi keeps its default base (which lists built-in tools + guidelines), our
206
+ // profile content (SOUL.md / IDENTITY.md / workspaces / memory hint) is
207
+ // concatenated after it. This is the same injection hook pi extensions use.
208
+ const bazilionPrompt = buildSystemPrompt(agent)
209
+ const resourceLoader = createBazilionResourceLoader(bazilionPrompt)
210
+ await resourceLoader.reload()
211
+
212
+ // Tool allowlist: pi's `tools` option is exclusive when provided — only
213
+ // the listed names are enabled, regardless of what's in `customTools`.
214
+ // So we have to enumerate both pi's built-in coding tools *and* every
215
+ // Bazilion custom tool we want the LLM to see. Missing the custom names
216
+ // from the allowlist would silently drop memory/messaging/web/bootstrap
217
+ // tools from the agent's surface.
218
+ const customTools = createBazilionCustomTools({ agent, memory, messagingHost, env })
219
+ const allowedTools = [...BUILTIN_TOOL_NAMES, ...customTools.map((t) => t.name)]
220
+
221
+ const { session } = await createAgentSession({
222
+ cwd,
223
+ agentDir: join(paths.home, 'pi'),
224
+ model,
225
+ thinkingLevel: toPiThinkingLevel(agent.reasoningLevel),
226
+ tools: allowedTools,
227
+ customTools,
228
+ sessionManager,
229
+ settingsManager,
230
+ authStorage,
231
+ modelRegistry,
232
+ resourceLoader,
233
+ })
234
+
235
+ // OAuth providers: wire pi's per-request `getApiKey` callback so the JWT
236
+ // gets refreshed *during* a long tool-execution loop, not just at the
237
+ // start of the turn. This is exactly the use case pi-agent-core's doc
238
+ // calls out for this hook ("short-lived OAuth tokens that may expire
239
+ // during long-running tool execution phases"). Caller supplies the
240
+ // refresher because only they have access to the secrets table.
241
+ if (refreshApiKey) {
242
+ session.agent.getApiKey = async (requestedProvider) => {
243
+ if (requestedProvider !== piProviderName) return undefined
244
+ try {
245
+ return await refreshApiKey(providerName)
246
+ } catch {
247
+ // Stale/removed credentials mid-session → return undefined so pi
248
+ // surfaces a "no auth" error cleanly instead of us throwing out of
249
+ // the provider callback (which would drag down the whole turn).
250
+ return undefined
251
+ }
252
+ }
253
+ }
254
+
255
+ return {
256
+ session,
257
+ dispose() {
258
+ session.dispose()
259
+ },
260
+ }
261
+ }
262
+
263
+ // --- helpers ---
264
+
265
+ function splitModelString(s: string): { providerName: string; modelId: string } {
266
+ const idx = s.indexOf(':')
267
+ if (idx === -1) {
268
+ throw new Error(`invalid model string "${s}": expected "provider:model"`)
269
+ }
270
+ return { providerName: s.slice(0, idx), modelId: s.slice(idx + 1) }
271
+ }
272
+
273
+ /**
274
+ * Map Bazilion provider names to pi's canonical `piProviderName` for catalog
275
+ * lookups. The split exists because Bazilion was registering e.g. `bedrock`
276
+ * but pi catalogs it as `amazon-bedrock`.
277
+ */
278
+ function mapProviderName(name: string): string {
279
+ if (name === 'bedrock') return 'amazon-bedrock'
280
+ return name
281
+ }
282
+
283
+ function pickFallbackApi(providerName: string): string {
284
+ switch (providerName) {
285
+ case 'anthropic':
286
+ return 'anthropic-messages'
287
+ case 'google':
288
+ return 'google-generative-ai'
289
+ case 'google-vertex':
290
+ return 'google-vertex'
291
+ case 'azure-openai':
292
+ return 'azure-openai-responses'
293
+ case 'bedrock':
294
+ return 'bedrock-converse-stream'
295
+ case 'openai-codex':
296
+ return 'openai-codex-responses'
297
+ default:
298
+ return 'openai-completions'
299
+ }
300
+ }
301
+
302
+ function resolveBaseUrl(providerName: string, env: NodeJS.ProcessEnv): string | undefined {
303
+ if (providerName === 'lmstudio') return env.LMSTUDIO_URL ?? 'http://127.0.0.1:1234/v1'
304
+ if (providerName === 'ollama') return env.OLLAMA_URL ?? 'http://127.0.0.1:11434/v1'
305
+ return undefined
306
+ }
307
+
308
+ function resolveApiKey(providerName: string, env: NodeJS.ProcessEnv): string | undefined {
309
+ // Hand-written table mirroring loadProviderConfigFromEnv — cheaper than
310
+ // spinning up a whole ProviderRegistry just to pluck one field.
311
+ switch (providerName) {
312
+ case 'anthropic':
313
+ return env.ANTHROPIC_OAUTH_TOKEN ?? env.ANTHROPIC_API_KEY
314
+ case 'openai':
315
+ return env.OPENAI_API_KEY
316
+ case 'google':
317
+ return env.GEMINI_API_KEY
318
+ case 'mistral':
319
+ return env.MISTRAL_API_KEY
320
+ case 'groq':
321
+ return env.GROQ_API_KEY
322
+ case 'cerebras':
323
+ return env.CEREBRAS_API_KEY
324
+ case 'xai':
325
+ return env.XAI_API_KEY
326
+ case 'zai':
327
+ return env.ZAI_API_KEY
328
+ case 'huggingface':
329
+ return env.HF_TOKEN
330
+ case 'openrouter':
331
+ return env.OPENROUTER_API_KEY
332
+ case 'vercel-ai-gateway':
333
+ return env.AI_GATEWAY_API_KEY
334
+ case 'azure-openai':
335
+ return env.AZURE_OPENAI_API_KEY
336
+ case 'lmstudio':
337
+ return env.LMSTUDIO_API_KEY ?? 'lm-studio'
338
+ case 'ollama':
339
+ return env.OLLAMA_API_KEY ?? 'ollama'
340
+ default:
341
+ return undefined
342
+ }
343
+ }
344
+
345
+ function toPiThinkingLevel(level: string): ThinkingLevel {
346
+ switch (level) {
347
+ case 'off':
348
+ case 'minimal':
349
+ case 'low':
350
+ case 'medium':
351
+ case 'high':
352
+ case 'xhigh':
353
+ return level
354
+ default:
355
+ return 'medium'
356
+ }
357
+ }
358
+
359
+ /**
360
+ * Minimal `ResourceLoader` implementation — feeds pi our Bazilion-authored
361
+ * system prompt block via `getAppendSystemPrompt` and returns empty collections
362
+ * for everything else. Pi's default loader reads skill/prompt/theme markdown
363
+ * from the workspace cwd; we intentionally opt out because Bazilion owns skill
364
+ * discovery at the platform level (see `apps/daemon/src/core/skills`).
365
+ */
366
+ function createBazilionResourceLoader(appendSystemPrompt: string): ResourceLoader {
367
+ const extensions = { extensions: [], errors: [], runtime: createExtensionRuntime() }
368
+ return {
369
+ getExtensions: () => extensions,
370
+ getSkills: () => ({ skills: [], diagnostics: [] }),
371
+ getPrompts: () => ({ prompts: [], diagnostics: [] }),
372
+ getThemes: () => ({ themes: [], diagnostics: [] }),
373
+ getAgentsFiles: () => ({ agentsFiles: [] }),
374
+ getSystemPrompt: () => undefined,
375
+ getAppendSystemPrompt: () => (appendSystemPrompt ? [appendSystemPrompt] : []),
376
+ extendResources: () => {},
377
+ async reload() {},
378
+ }
379
+ }
380
+
381
+ /**
382
+ * Re-exported for callers that want to check whether a provider is
383
+ * Bazilion-enabled before even trying to spawn a session (e.g. /context
384
+ * endpoint which builds a session just to enumerate tools).
385
+ */
386
+ export function isProviderEnabled(db: BazilionDb, providerName: string): boolean {
387
+ const enabled = providerStateRepo.listEnabled(db)
388
+ return enabled.size === 0 || enabled.has(providerName)
389
+ }
390
+
391
+ /**
392
+ * Escape hatch for callers that need the raw provider registry (e.g. the
393
+ * current /api/providers/test endpoint). Keeps that one endpoint on our
394
+ * existing non-pi path until we migrate it in a follow-up.
395
+ */
396
+ export function loadEnabledRegistry(db: BazilionDb, authToken: string, env: NodeJS.ProcessEnv) {
397
+ return createProviderRegistry(loadProviderConfigFromEnv(env, { db, authToken }), {
398
+ enabledSet: providerStateRepo.listEnabled(db),
399
+ })
400
+ }
401
+
402
+ /**
403
+ * Read the most recent session file for an agent *without* spawning a full
404
+ * AgentSession, and return the resolved provider-message view. Used for SSR
405
+ * page loads that only need to render the canonical transcript — no need
406
+ * to boot pi just to inspect the transcript.
407
+ *
408
+ * Returns an empty array when the agent has no prior session (fresh spawn,
409
+ * or post-/reset).
410
+ */
411
+ export function loadInitialMessages(agent: ResolvedAgent, paths: Paths): AgentMessage[] {
412
+ const sessionDir = join(paths.agentDir(agent.agent.id), 'sessions')
413
+ if (!existsSync(sessionDir)) return []
414
+ const cwd = agent.group.path
415
+ if (!existsSync(cwd)) return []
416
+ const recent = findMostRecent(sessionDir)
417
+ if (!recent) return []
418
+ try {
419
+ const sm = SessionManager.open(recent, sessionDir)
420
+ const ctx = sm.buildSessionContext()
421
+ return ctx.messages
422
+ } catch (err) {
423
+ // Corrupt session file, stale format, or pi version bump — log loud
424
+ // enough that an operator noticing a blank chat can find the cause in
425
+ // server logs. The turn loop itself starts a fresh session on the
426
+ // next message, so this isn't load-bearing for writes, only reads.
427
+ console.error(
428
+ `[session] loadInitialMessages failed for agent ${agent.agent.id} (${recent}):`,
429
+ err instanceof Error ? (err.stack ?? err.message) : err,
430
+ )
431
+ return []
432
+ }
433
+ }
434
+
435
+ /**
436
+ * Cheap "has the session changed?" probe for polling clients (the web chat
437
+ * stale-tab banner). Returns the most recent session file's basename plus
438
+ * byte size — append-only JSONL, so either value moving means new activity.
439
+ * Returns `{ file: null, size: 0 }` for agents that have never had a turn.
440
+ */
441
+ export function loadSessionHead(
442
+ agent: ResolvedAgent,
443
+ paths: Paths,
444
+ ): { file: string | null; size: number } {
445
+ const sessionDir = join(paths.agentDir(agent.agent.id), 'sessions')
446
+ if (!existsSync(sessionDir)) return { file: null, size: 0 }
447
+ const recent = findMostRecent(sessionDir)
448
+ if (!recent) return { file: null, size: 0 }
449
+ try {
450
+ const s = statSync(recent)
451
+ return { file: basename(recent), size: s.size }
452
+ } catch {
453
+ return { file: null, size: 0 }
454
+ }
455
+ }
456
+
457
+ /**
458
+ * Test helper: seed a pi session file for an agent with `n` synthetic
459
+ * user/assistant message pairs. Writes a real JSONL entry tree via
460
+ * SessionManager so round-tripping through pi's own reader stays honest.
461
+ * Exported from runtime rather than lived in tests because tests in apps/cli
462
+ * can't directly import pi packages (not a direct dep).
463
+ */
464
+ export function seedSessionForTest(
465
+ agent: ResolvedAgent,
466
+ paths: Paths,
467
+ messages: Array<{ role: 'user' | 'assistant'; text: string }>,
468
+ ): void {
469
+ const cwd = agent.group.path
470
+ if (!existsSync(cwd)) mkdirSync(cwd, { recursive: true })
471
+ const sessionDir = join(paths.agentDir(agent.agent.id), 'sessions')
472
+ mkdirSync(sessionDir, { recursive: true })
473
+ const sm = SessionManager.create(cwd, sessionDir)
474
+ const now = Date.now()
475
+ messages.forEach((m, i) => {
476
+ if (m.role === 'user') {
477
+ sm.appendMessage({
478
+ role: 'user',
479
+ content: [{ type: 'text', text: m.text }],
480
+ timestamp: now + i,
481
+ })
482
+ } else {
483
+ sm.appendMessage({
484
+ role: 'assistant',
485
+ content: [{ type: 'text', text: m.text }],
486
+ api: 'openai-completions',
487
+ provider: 'lmstudio',
488
+ model: 'test-model',
489
+ usage: {
490
+ input: 0,
491
+ output: 0,
492
+ cacheRead: 0,
493
+ cacheWrite: 0,
494
+ totalTokens: 0,
495
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
496
+ },
497
+ stopReason: 'stop',
498
+ timestamp: now + i,
499
+ })
500
+ }
501
+ })
502
+ }
503
+
504
+ /**
505
+ * Test helper: count message entries on the current leaf's branch of the
506
+ * agent's most-recent session file. Returns 0 when no session file exists.
507
+ */
508
+ export function countSessionMessagesForTest(agent: ResolvedAgent, paths: Paths): number {
509
+ const sessionDir = join(paths.agentDir(agent.agent.id), 'sessions')
510
+ const recent = findMostRecent(sessionDir)
511
+ if (!recent) return 0
512
+ const cwd = agent.group.path
513
+ try {
514
+ const sm = SessionManager.open(recent, sessionDir, cwd)
515
+ return sm.getBranch().filter((e) => e.type === 'message').length
516
+ } catch {
517
+ return 0
518
+ }
519
+ }
520
+
521
+ /** Newest `.jsonl` in a pi session directory by mtime, or null if empty. */
522
+ function findMostRecent(sessionDir: string): string | null {
523
+ if (!existsSync(sessionDir)) return null
524
+ let newest: { path: string; mtimeMs: number } | null = null
525
+ for (const entry of readdirSync(sessionDir)) {
526
+ if (!entry.endsWith('.jsonl')) continue
527
+ const path = join(sessionDir, entry)
528
+ try {
529
+ const s = statSync(path)
530
+ if (!newest || s.mtimeMs > newest.mtimeMs) newest = { path, mtimeMs: s.mtimeMs }
531
+ } catch {
532
+ // ignore races
533
+ }
534
+ }
535
+ return newest?.path ?? null
536
+ }
@@ -0,0 +1,85 @@
1
+ // Adapter: Bazilion ToolHandler → pi-coding-agent ToolDefinition.
2
+ //
3
+ // Pi expects tools to return `AgentToolResult<TDetails>` =
4
+ // `{ content: (TextContent | ImageContent)[]; details: TDetails; terminate?: boolean }`.
5
+ // Our handlers return plain strings. The adapter wraps the string into a single
6
+ // text content block with empty details — the same fidelity we had before.
7
+ //
8
+ // Pi's tool execute contract: throw on failure. The agent loop wraps thrown
9
+ // errors as tool-result messages with `isError: true`. We preserve this shape
10
+ // directly because our handlers already throw on bad args / runtime failures.
11
+ //
12
+ // What's in this module:
13
+ // - `ourToolToPiTool` — single-handler wrapper.
14
+ // - `createBazilionCustomTools` — composed suite of the Bazilion-specific
15
+ // tools (memory_*, messaging, bootstrap_done, web_search/fetch). File I/O
16
+ // tools are *not* here — pi's createCodingTools(cwd, …) replaces them.
17
+
18
+ import type { ResolvedAgent } from '@bazilion/api-types'
19
+ import type { ToolDefinition } from '@mariozechner/pi-coding-agent'
20
+ import { Type } from 'typebox'
21
+ import type { MemoryBackend } from '../memory/types.ts'
22
+ import { bootstrapTool } from '../tools/bootstrap.ts'
23
+ import { homeTools } from '../tools/home.ts'
24
+ import { memoryTools } from '../tools/memory.ts'
25
+ import { messagingTools } from '../tools/messaging.ts'
26
+ import type { ToolHandler } from '../tools/types.ts'
27
+ import { webTools } from '../tools/web.ts'
28
+ import type { MessagingHost } from '../worker/ipc-protocol.ts'
29
+
30
+ /**
31
+ * Wrap a Bazilion `ToolHandler` as a pi `ToolDefinition` so it can be passed
32
+ * through `customTools` to `createAgentSession`.
33
+ *
34
+ * Design note: we keep the same tool name + description + parameter JSON schema
35
+ * that the handler already declares. Pi wants a `TypeBox` schema; we pass the
36
+ * JSONSchema through `Type.Unsafe` so the LLM validation happens on pi's side
37
+ * without us having to re-author schemas in typebox syntax.
38
+ */
39
+ export function ourToolToPiTool(h: ToolHandler): ToolDefinition {
40
+ return {
41
+ name: h.def.name,
42
+ label: h.def.name,
43
+ description: h.def.description,
44
+ parameters: Type.Unsafe<Record<string, unknown>>(h.def.parameters as Record<string, unknown>),
45
+ async execute(_toolCallId, params) {
46
+ const text = await h.invoke(params as Record<string, unknown>)
47
+ return {
48
+ content: [{ type: 'text', text }],
49
+ details: {},
50
+ }
51
+ },
52
+ }
53
+ }
54
+
55
+ export interface BazilionCustomToolsOpts {
56
+ agent: ResolvedAgent
57
+ memory: MemoryBackend
58
+ /** If provided, enables inter-agent messaging tools. */
59
+ messagingHost?: MessagingHost
60
+ /** Merged env (process.env + secrets). */
61
+ env?: NodeJS.ProcessEnv
62
+ }
63
+
64
+ /**
65
+ * Build the list of Bazilion-specific custom tools in the shape pi expects.
66
+ *
67
+ * Excludes file-I/O tools (`read`/`bash`/`edit`/`write`/`grep`/`find`/`ls`) —
68
+ * those come from pi's `createCodingTools(cwd, …)` now that we've adopted the
69
+ * richer toolset. Also excludes the legacy `workspace_list/read/write`
70
+ * triumvirate: pi's tools work against a single cwd (the agent's default
71
+ * workspace), and mounted non-default workspaces are reachable via absolute
72
+ * paths through pi's `bash`/`read`/`edit`.
73
+ */
74
+ export function createBazilionCustomTools(opts: BazilionCustomToolsOpts): ToolDefinition[] {
75
+ const handlers: ToolHandler[] = [
76
+ ...memoryTools(opts.memory),
77
+ ...homeTools(opts.agent.agent.dir),
78
+ bootstrapTool(opts.agent.agent.dir),
79
+ ...webTools({ env: opts.env }),
80
+ ]
81
+ if (opts.messagingHost) {
82
+ handlers.push(...messagingTools(opts.messagingHost, opts.agent.agent.id))
83
+ }
84
+ return handlers.map(ourToolToPiTool)
85
+ }