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,145 @@
1
+ // Catalog lookup for config UI + CLI: "what models does provider X offer?"
2
+ //
3
+ // Two sources, merged and de-duplicated:
4
+ // 1. pi-ai's typed catalog (`getModels(provider)`) — static list of
5
+ // known cloud models with cost + context-window metadata, bundled with
6
+ // pi-ai. Works for the 10ish KnownProvider names; empty for local /
7
+ // alias providers (lmstudio, ollama, bedrock-via-our-alias).
8
+ // 2. For OpenAI-compat endpoints (lmstudio, ollama, openrouter,
9
+ // vercel-ai-gateway, openai with custom baseURL), we query
10
+ // `GET {baseURL}/models` and extract `data[].id`. This picks up
11
+ // newly released models, user-loaded local models, etc.
12
+ //
13
+ // `listCatalogModels(providerName)` returns the union. Errors from live
14
+ // fetches are surfaced so callers can show "couldn't reach lmstudio" rather
15
+ // than a silent empty list.
16
+
17
+ import { getModels as piGetModels } from '@mariozechner/pi-ai'
18
+
19
+ // piProviderName: what pi-ai's catalog indexes by. For bedrock we use the
20
+ // Bazilion-registry-key 'bedrock' externally but pi uses 'amazon-bedrock'.
21
+ const REGISTRY_TO_PI: Record<string, string> = {
22
+ bedrock: 'amazon-bedrock',
23
+ 'azure-openai': 'azure-openai-responses',
24
+ }
25
+
26
+ // baseURL for providers whose /v1/models we can probe. Omitted for the
27
+ // big cloud APIs that require a real key and generally aren't "explore me"
28
+ // — those come from the pi catalog. `process.env` reads pick up the same
29
+ // secrets/config merge the rest of the runtime uses.
30
+ function liveEndpointFor(providerName: string, env: NodeJS.ProcessEnv): string | null {
31
+ switch (providerName) {
32
+ case 'lmstudio':
33
+ return env.LMSTUDIO_URL ?? 'http://127.0.0.1:1234/v1'
34
+ case 'ollama':
35
+ // Ollama has its own /api/tags for model list; its OpenAI-compat /v1
36
+ // endpoint supports /models though, so use that.
37
+ return env.OLLAMA_URL ?? 'http://127.0.0.1:11434/v1'
38
+ case 'openrouter':
39
+ return 'https://openrouter.ai/api/v1'
40
+ case 'vercel-ai-gateway':
41
+ return env.AI_GATEWAY_BASE_URL ?? 'https://ai-gateway.vercel.sh/v1'
42
+ case 'groq':
43
+ return 'https://api.groq.com/openai/v1'
44
+ case 'cerebras':
45
+ return 'https://api.cerebras.ai/v1'
46
+ case 'xai':
47
+ return 'https://api.x.ai/v1'
48
+ case 'mistral':
49
+ return 'https://api.mistral.ai/v1'
50
+ default:
51
+ return null
52
+ }
53
+ }
54
+
55
+ function piModels(providerName: string): string[] {
56
+ const piName = REGISTRY_TO_PI[providerName] ?? providerName
57
+ try {
58
+ const models = (piGetModels as unknown as (p: string) => { id: string }[] | undefined)(piName)
59
+ if (!models) return []
60
+ return models.map((m) => m.id)
61
+ } catch {
62
+ return []
63
+ }
64
+ }
65
+
66
+ export interface LiveFetchResult {
67
+ models: string[]
68
+ /** Only populated on failure; caller renders for UX. */
69
+ error?: string
70
+ }
71
+
72
+ async function fetchModelsFrom(
73
+ baseURL: string,
74
+ apiKey: string | undefined,
75
+ signal?: AbortSignal,
76
+ ): Promise<LiveFetchResult> {
77
+ try {
78
+ const headers: Record<string, string> = { accept: 'application/json' }
79
+ if (apiKey) headers.authorization = `Bearer ${apiKey}`
80
+ const res = await fetch(`${baseURL.replace(/\/$/, '')}/models`, { headers, signal })
81
+ if (!res.ok) return { models: [], error: `${res.status} ${res.statusText}` }
82
+ const body = (await res.json()) as { data?: Array<{ id?: string }> } | null
83
+ const ids = (body?.data ?? []).map((m) => m.id).filter((id): id is string => !!id)
84
+ return { models: ids }
85
+ } catch (err) {
86
+ return { models: [], error: (err as Error).message }
87
+ }
88
+ }
89
+
90
+ function apiKeyFor(providerName: string, env: NodeJS.ProcessEnv): string | undefined {
91
+ switch (providerName) {
92
+ case 'lmstudio':
93
+ return env.LMSTUDIO_API_KEY ?? 'lm-studio'
94
+ case 'ollama':
95
+ return env.OLLAMA_API_KEY ?? 'ollama'
96
+ case 'openrouter':
97
+ return env.OPENROUTER_API_KEY
98
+ case 'vercel-ai-gateway':
99
+ return env.AI_GATEWAY_API_KEY
100
+ case 'groq':
101
+ return env.GROQ_API_KEY
102
+ case 'cerebras':
103
+ return env.CEREBRAS_API_KEY
104
+ case 'xai':
105
+ return env.XAI_API_KEY
106
+ case 'mistral':
107
+ return env.MISTRAL_API_KEY
108
+ default:
109
+ return undefined
110
+ }
111
+ }
112
+
113
+ export interface CatalogResult {
114
+ /** Models from pi-ai's typed catalog — stable, no network. */
115
+ catalog: string[]
116
+ /** Live `/v1/models` query — populated only for openai-compat endpoints we know how to probe. */
117
+ live?: LiveFetchResult
118
+ }
119
+
120
+ /**
121
+ * List known models for a provider. Combines pi's static catalog with a live
122
+ * `/v1/models` query for endpoints that expose one. Returns both separately
123
+ * so the UI can surface which ones come from where.
124
+ *
125
+ * `live` is omitted (not just empty) when the provider doesn't expose a
126
+ * `/v1/models` endpoint — e.g. Anthropic or Bedrock. Those rely on the
127
+ * catalog only.
128
+ */
129
+ export async function listCatalogModels(
130
+ providerName: string,
131
+ env: NodeJS.ProcessEnv = process.env,
132
+ signal?: AbortSignal,
133
+ ): Promise<CatalogResult> {
134
+ const catalog = piModels(providerName)
135
+ const endpoint = liveEndpointFor(providerName, env)
136
+ if (!endpoint) return { catalog }
137
+ const key = apiKeyFor(providerName, env)
138
+ const live = await fetchModelsFrom(endpoint, key, signal)
139
+ return { catalog, live }
140
+ }
141
+
142
+ /** Synchronous variant for contexts that only want the pi catalog slice. */
143
+ export function listCatalogModelsSync(providerName: string): string[] {
144
+ return piModels(providerName)
145
+ }
@@ -0,0 +1,272 @@
1
+ // Adapter from Bazilion's Provider interface → pi-ai's streamSimple.
2
+ //
3
+ // Pi-ai (`@mariozechner/pi-ai`) is Mario Zechner's unified LLM SDK — 15+
4
+ // providers behind one event-stream API. This file is the only place in the
5
+ // codebase that touches pi-ai directly; everything else downstream
6
+ // (`runTurnStream`, `persistRun`, the worker entry, CLI, web) sees the same
7
+ // `Provider.chat(ProviderRequest): Promise<ProviderResponse>` contract it
8
+ // always has. That keeps the wire format, DB schema, and chat UI stable while
9
+ // giving us cost/usage, thinking levels, prompt caching, and every provider
10
+ // pi supports — for free.
11
+ //
12
+ // Model resolution: we prefer pi's typed catalog via `getModel(provider, id)`
13
+ // which carries cost + context-window metadata; for anything outside the
14
+ // catalog (local models, newly-released models, custom OpenAI-compat
15
+ // endpoints) we construct a `Model<>` literal with sensible defaults. This
16
+ // preserves Bazilion's "any model string" flexibility.
17
+
18
+ import type { ProviderMessage, ReasoningLevel, ToolCall, ToolDef } from '@bazilion/api-types'
19
+ import {
20
+ type AssistantMessage,
21
+ getModel,
22
+ type Model,
23
+ type Message as PiMessage,
24
+ type Tool as PiTool,
25
+ type ToolCall as PiToolCall,
26
+ streamSimple,
27
+ type TextContent,
28
+ Type,
29
+ } from '@mariozechner/pi-ai'
30
+ import type { Provider, ProviderRequest, ProviderResponse, StopReason } from './types.ts'
31
+
32
+ export interface PiProviderConfig {
33
+ /** Display name on the returned Provider; also the registry key (e.g. 'bedrock', 'azure-openai'). */
34
+ providerName: string
35
+ /** Pi's canonical provider name for catalog lookup (e.g. 'amazon-bedrock', 'azure-openai-responses'). Defaults to providerName. */
36
+ piProviderName?: string
37
+ /** Override baseUrl for openai-compat endpoints (lmstudio, ollama, custom). */
38
+ baseUrl?: string
39
+ /**
40
+ * Static key or an async supplier. Suppliers are called at the top of each
41
+ * chat() so OAuth-backed providers can refresh expiring tokens without
42
+ * rebuilding the Provider instance (which the registry caches).
43
+ */
44
+ apiKey?: string | (() => string | Promise<string>)
45
+ /** Which pi `Api` to use when the model isn't in pi's catalog. */
46
+ fallbackApi: string
47
+ }
48
+
49
+ function defaultBaseUrlFor(providerName: string): string {
50
+ switch (providerName) {
51
+ case 'lmstudio':
52
+ return process.env.LMSTUDIO_URL ?? 'http://127.0.0.1:1234/v1'
53
+ case 'ollama':
54
+ return process.env.OLLAMA_URL ?? 'http://127.0.0.1:11434/v1'
55
+ default:
56
+ return ''
57
+ }
58
+ }
59
+
60
+ function buildModelLiteral(cfg: PiProviderConfig, modelId: string): Model<string> {
61
+ return {
62
+ id: modelId,
63
+ name: modelId,
64
+ api: cfg.fallbackApi,
65
+ provider: cfg.providerName,
66
+ baseUrl: cfg.baseUrl ?? defaultBaseUrlFor(cfg.providerName),
67
+ reasoning: false,
68
+ input: ['text'],
69
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
70
+ contextWindow: 32_768,
71
+ maxTokens: 4_096,
72
+ }
73
+ }
74
+
75
+ export function resolveModel(cfg: PiProviderConfig, modelId: string): Model<string> {
76
+ const lookupName = cfg.piProviderName ?? cfg.providerName
77
+ // Try pi's typed catalog first — gets us cost, context window, reasoning flags
78
+ // for free on the known providers' known models.
79
+ try {
80
+ // getModel's type signature is catalog-constrained, but at runtime it just
81
+ // indexes MODELS[provider][id]. Cast through unknown so unknown ids fall
82
+ // through to the literal builder instead of tripping the compiler.
83
+ const known = (getModel as unknown as (p: string, m: string) => Model<string> | undefined)(
84
+ lookupName,
85
+ modelId,
86
+ )
87
+ if (known && typeof known === 'object' && 'api' in known) {
88
+ // Override baseUrl + provider for local / compat endpoints — pi's catalog
89
+ // doesn't know about lmstudio/ollama but if a user types
90
+ // `openai:gpt-4o` with a custom OPENAI_BASE_URL, honor that here.
91
+ if (cfg.baseUrl) return { ...known, baseUrl: cfg.baseUrl }
92
+ return known
93
+ }
94
+ } catch {
95
+ // Fall through.
96
+ }
97
+ return buildModelLiteral(cfg, modelId)
98
+ }
99
+
100
+ function convertMessages(messages: ProviderMessage[]): PiMessage[] {
101
+ const out: PiMessage[] = []
102
+ const now = Date.now()
103
+ for (const m of messages) {
104
+ if (m.role === 'system') continue // pi takes system prompt separately
105
+ if (m.role === 'user') {
106
+ out.push({ role: 'user', content: m.content, timestamp: now })
107
+ continue
108
+ }
109
+ if (m.role === 'assistant') {
110
+ const content: AssistantMessage['content'] = []
111
+ if (m.content) content.push({ type: 'text', text: m.content } satisfies TextContent)
112
+ if (m.toolCalls) {
113
+ for (const tc of m.toolCalls) {
114
+ let parsed: Record<string, unknown> = {}
115
+ try {
116
+ parsed = JSON.parse(tc.arguments) as Record<string, unknown>
117
+ } catch {
118
+ // leave empty
119
+ }
120
+ content.push({
121
+ type: 'toolCall',
122
+ id: tc.id,
123
+ name: tc.name,
124
+ arguments: parsed,
125
+ } satisfies PiToolCall)
126
+ }
127
+ }
128
+ // Synthesize the AssistantMessage fields pi expects on replays — these
129
+ // are only load-bearing for the LLM that gets the transcript; since we
130
+ // don't persist usage/stopReason in Bazilion's message store, defaults
131
+ // are fine.
132
+ out.push({
133
+ role: 'assistant',
134
+ content,
135
+ api: 'anthropic-messages',
136
+ provider: 'anthropic',
137
+ model: 'unknown',
138
+ usage: {
139
+ input: 0,
140
+ output: 0,
141
+ cacheRead: 0,
142
+ cacheWrite: 0,
143
+ totalTokens: 0,
144
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
145
+ },
146
+ stopReason: 'stop',
147
+ timestamp: now,
148
+ })
149
+ continue
150
+ }
151
+ if (m.role === 'tool') {
152
+ out.push({
153
+ role: 'toolResult',
154
+ toolCallId: m.toolCallId ?? '',
155
+ toolName: m.toolName ?? 'tool',
156
+ content: [{ type: 'text', text: m.content }],
157
+ isError: false,
158
+ timestamp: now,
159
+ })
160
+ }
161
+ }
162
+ return out
163
+ }
164
+
165
+ function convertTools(tools: ToolDef[] | undefined): PiTool[] | undefined {
166
+ if (!tools || tools.length === 0) return undefined
167
+ return tools.map((t) => ({
168
+ name: t.name,
169
+ description: t.description,
170
+ // Type.Unsafe lets us pass raw JSON Schema through without re-authoring in
171
+ // typebox. Providers validate against the schema, not typebox's TSchema.
172
+ parameters: Type.Unsafe<unknown>(t.parameters as Record<string, unknown>),
173
+ }))
174
+ }
175
+
176
+ function toBazilionStopReason(reason: string): StopReason {
177
+ switch (reason) {
178
+ case 'stop':
179
+ return 'stop'
180
+ case 'length':
181
+ return 'length'
182
+ case 'toolUse':
183
+ return 'tool_use'
184
+ default:
185
+ return 'error'
186
+ }
187
+ }
188
+
189
+ function extractFinalResponse(msg: AssistantMessage): ProviderResponse {
190
+ let text = ''
191
+ const toolCalls: ToolCall[] = []
192
+ for (const block of msg.content) {
193
+ if (block.type === 'text') {
194
+ text += block.text
195
+ } else if (block.type === 'toolCall') {
196
+ toolCalls.push({
197
+ id: block.id,
198
+ name: block.name,
199
+ arguments: JSON.stringify(block.arguments ?? {}),
200
+ })
201
+ }
202
+ }
203
+ const res: ProviderResponse = {
204
+ content: text,
205
+ toolCalls,
206
+ stopReason: toBazilionStopReason(msg.stopReason),
207
+ }
208
+ if (msg.usage) {
209
+ res.usage = {
210
+ promptTokens: msg.usage.input,
211
+ completionTokens: msg.usage.output,
212
+ }
213
+ }
214
+ return res
215
+ }
216
+
217
+ function mapReasoning(
218
+ r: ReasoningLevel | undefined,
219
+ ): 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | undefined {
220
+ if (!r || r === 'off') return undefined
221
+ return r
222
+ }
223
+
224
+ async function resolveApiKey(cfg: PiProviderConfig): Promise<string | undefined> {
225
+ if (typeof cfg.apiKey === 'function') return await cfg.apiKey()
226
+ return cfg.apiKey
227
+ }
228
+
229
+ export function piProvider(cfg: PiProviderConfig): Provider {
230
+ return {
231
+ name: cfg.providerName,
232
+ async chat(req: ProviderRequest): Promise<ProviderResponse> {
233
+ const model = resolveModel(cfg, req.model)
234
+ const apiKey = await resolveApiKey(cfg)
235
+
236
+ const stream = streamSimple(
237
+ model,
238
+ {
239
+ systemPrompt: req.system ?? '',
240
+ messages: convertMessages(req.messages),
241
+ tools: convertTools(req.tools),
242
+ },
243
+ {
244
+ signal: req.signal,
245
+ apiKey,
246
+ reasoning: mapReasoning(req.reasoning),
247
+ maxTokens: req.maxTokens,
248
+ temperature: req.temperature,
249
+ },
250
+ )
251
+
252
+ let finalMessage: AssistantMessage | null = null
253
+ for await (const event of stream) {
254
+ if (event.type === 'text_delta' && req.onDelta) {
255
+ req.onDelta(event.delta)
256
+ } else if (event.type === 'done') {
257
+ finalMessage = event.message
258
+ } else if (event.type === 'error') {
259
+ finalMessage = event.error
260
+ }
261
+ }
262
+ if (!finalMessage) {
263
+ throw new Error(`pi provider ${cfg.providerName} returned no terminal event`)
264
+ }
265
+ if (finalMessage.stopReason === 'aborted' || finalMessage.stopReason === 'error') {
266
+ const msg = finalMessage.errorMessage ?? 'provider error'
267
+ throw new Error(msg)
268
+ }
269
+ return extractFinalResponse(finalMessage)
270
+ },
271
+ }
272
+ }