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.
- package/.understand-anything/.understandignore +25 -0
- package/.understand-anything/fingerprints.json +14267 -0
- package/.understand-anything/knowledge-graph.json +18128 -0
- package/.understand-anything/meta.json +6 -0
- package/CLAUDE.md +164 -0
- package/LICENSE +21 -0
- package/README.md +195 -0
- package/apps/cli/package.json +21 -0
- package/apps/cli/src/auth-file.ts +18 -0
- package/apps/cli/src/client.ts +37 -0
- package/apps/cli/src/columnize.ts +32 -0
- package/apps/cli/src/commands/agent.ts +574 -0
- package/apps/cli/src/commands/auth.ts +110 -0
- package/apps/cli/src/commands/backup.ts +135 -0
- package/apps/cli/src/commands/completion.ts +155 -0
- package/apps/cli/src/commands/config.ts +95 -0
- package/apps/cli/src/commands/doctor.ts +131 -0
- package/apps/cli/src/commands/group.ts +132 -0
- package/apps/cli/src/commands/inbox.ts +82 -0
- package/apps/cli/src/commands/login.ts +73 -0
- package/apps/cli/src/commands/memory.ts +106 -0
- package/apps/cli/src/commands/profile.ts +259 -0
- package/apps/cli/src/commands/provider.ts +170 -0
- package/apps/cli/src/commands/send.ts +24 -0
- package/apps/cli/src/commands/serve.ts +89 -0
- package/apps/cli/src/commands/skill.ts +120 -0
- package/apps/cli/src/commands/token.ts +148 -0
- package/apps/cli/src/commands/trigger.ts +129 -0
- package/apps/cli/src/commands/uninstall.ts +156 -0
- package/apps/cli/src/index.ts +196 -0
- package/apps/cli/src/paths.ts +12 -0
- package/apps/cli/test/agent.test.ts +213 -0
- package/apps/cli/test/backup.test.ts +95 -0
- package/apps/cli/test/chat.test.ts +539 -0
- package/apps/cli/test/columnize.test.ts +29 -0
- package/apps/cli/test/completion.test.ts +45 -0
- package/apps/cli/test/config-page.test.ts +151 -0
- package/apps/cli/test/group.test.ts +74 -0
- package/apps/cli/test/helpers.ts +70 -0
- package/apps/cli/test/inbox-autodeliver.test.ts +143 -0
- package/apps/cli/test/inbox.test.ts +147 -0
- package/apps/cli/test/memory.test.ts +69 -0
- package/apps/cli/test/profile.test.ts +110 -0
- package/apps/cli/test/send.test.ts +30 -0
- package/apps/cli/test/server-fixture.ts +212 -0
- package/apps/cli/test/session-head.test.ts +45 -0
- package/apps/cli/test/skill.test.ts +128 -0
- package/apps/cli/test/token.test.ts +109 -0
- package/apps/cli/test/trigger.test.ts +245 -0
- package/apps/cli/tsconfig.json +4 -0
- package/apps/daemon/package.json +31 -0
- package/apps/daemon/src/app.ts +41 -0
- package/apps/daemon/src/core/agent/archive.ts +8 -0
- package/apps/daemon/src/core/agent/delete.ts +30 -0
- package/apps/daemon/src/core/agent/resolve.ts +31 -0
- package/apps/daemon/src/core/agent/spawn.ts +95 -0
- package/apps/daemon/src/core/agent/unarchive.ts +11 -0
- package/apps/daemon/src/core/availableModels.ts +58 -0
- package/apps/daemon/src/core/db/client.ts +113 -0
- package/apps/daemon/src/core/db/migrate.ts +41 -0
- package/apps/daemon/src/core/db/migrations/0001_init.sql +179 -0
- package/apps/daemon/src/core/group/delete.ts +21 -0
- package/apps/daemon/src/core/group/register.ts +68 -0
- package/apps/daemon/src/core/index.ts +71 -0
- package/apps/daemon/src/core/paths.ts +56 -0
- package/apps/daemon/src/core/profile/create.ts +78 -0
- package/apps/daemon/src/core/profile/delete.ts +26 -0
- package/apps/daemon/src/core/profile/identity.ts +70 -0
- package/apps/daemon/src/core/profile/load.ts +45 -0
- package/apps/daemon/src/core/profile/seed.ts +74 -0
- package/apps/daemon/src/core/profile/templates.ts +60 -0
- package/apps/daemon/src/core/profile/update.ts +57 -0
- package/apps/daemon/src/core/profile/validate.ts +9 -0
- package/apps/daemon/src/core/repos/agents.ts +202 -0
- package/apps/daemon/src/core/repos/config.ts +78 -0
- package/apps/daemon/src/core/repos/groups.ts +55 -0
- package/apps/daemon/src/core/repos/messages.ts +127 -0
- package/apps/daemon/src/core/repos/profiles.ts +83 -0
- package/apps/daemon/src/core/repos/providerModels.ts +58 -0
- package/apps/daemon/src/core/repos/providerState.ts +37 -0
- package/apps/daemon/src/core/repos/secrets.ts +145 -0
- package/apps/daemon/src/core/repos/skillMeta.ts +49 -0
- package/apps/daemon/src/core/repos/triggers.ts +101 -0
- package/apps/daemon/src/core/repos/webTokens.ts +87 -0
- package/apps/daemon/src/core/secrets.ts +65 -0
- package/apps/daemon/src/core/services.ts +264 -0
- package/apps/daemon/src/core/skills/discover.ts +28 -0
- package/apps/daemon/src/core/skills/import.ts +136 -0
- package/apps/daemon/src/core/skills/parse.ts +52 -0
- package/apps/daemon/src/core/skills/resolve.ts +50 -0
- package/apps/daemon/src/index.ts +45 -0
- package/apps/daemon/src/lib/agent-cancel.ts +48 -0
- package/apps/daemon/src/lib/agent-id.ts +13 -0
- package/apps/daemon/src/lib/agent-turn.ts +56 -0
- package/apps/daemon/src/lib/api-key.ts +56 -0
- package/apps/daemon/src/lib/auth.ts +41 -0
- package/apps/daemon/src/lib/cron.ts +93 -0
- package/apps/daemon/src/lib/ctx.ts +80 -0
- package/apps/daemon/src/lib/messaging-host.ts +34 -0
- package/apps/daemon/src/lib/middleware-auth.ts +52 -0
- package/apps/daemon/src/lib/scheduler.ts +294 -0
- package/apps/daemon/src/routes/agents.ts +772 -0
- package/apps/daemon/src/routes/auth-login.ts +193 -0
- package/apps/daemon/src/routes/config.ts +267 -0
- package/apps/daemon/src/routes/groups.ts +133 -0
- package/apps/daemon/src/routes/messages.ts +29 -0
- package/apps/daemon/src/routes/misc.ts +239 -0
- package/apps/daemon/src/routes/profiles.ts +197 -0
- package/apps/daemon/src/routes/skills.ts +123 -0
- package/apps/daemon/src/routes/triggers.ts +29 -0
- package/apps/daemon/src/runtime/auth/openai-codex.ts +121 -0
- package/apps/daemon/src/runtime/auto-reply/heartbeat.ts +31 -0
- package/apps/daemon/src/runtime/index.ts +77 -0
- package/apps/daemon/src/runtime/memory/files.ts +103 -0
- package/apps/daemon/src/runtime/memory/qmd.ts +152 -0
- package/apps/daemon/src/runtime/memory/types.ts +16 -0
- package/apps/daemon/src/runtime/pi/events.ts +173 -0
- package/apps/daemon/src/runtime/pi/session.ts +536 -0
- package/apps/daemon/src/runtime/pi/tools.ts +85 -0
- package/apps/daemon/src/runtime/providers/catalog.ts +145 -0
- package/apps/daemon/src/runtime/providers/pi-adapter.ts +272 -0
- package/apps/daemon/src/runtime/providers/registry.ts +374 -0
- package/apps/daemon/src/runtime/providers/retry.ts +176 -0
- package/apps/daemon/src/runtime/providers/types.ts +33 -0
- package/apps/daemon/src/runtime/session/prompt.ts +83 -0
- package/apps/daemon/src/runtime/tools/bootstrap.ts +22 -0
- package/apps/daemon/src/runtime/tools/home.ts +114 -0
- package/apps/daemon/src/runtime/tools/memory.ts +81 -0
- package/apps/daemon/src/runtime/tools/messaging.ts +127 -0
- package/apps/daemon/src/runtime/tools/registry.ts +29 -0
- package/apps/daemon/src/runtime/tools/types.ts +13 -0
- package/apps/daemon/src/runtime/tools/web-extract.ts +110 -0
- package/apps/daemon/src/runtime/tools/web-ssrf.ts +245 -0
- package/apps/daemon/src/runtime/tools/web.ts +273 -0
- package/apps/daemon/src/runtime/worker/entry.ts +221 -0
- package/apps/daemon/src/runtime/worker/ipc-protocol.ts +75 -0
- package/apps/daemon/src/runtime/worker/spawn.ts +249 -0
- package/apps/daemon/test/core/agents.test.ts +319 -0
- package/apps/daemon/test/core/available-models.test.ts +63 -0
- package/apps/daemon/test/core/config.test.ts +99 -0
- package/apps/daemon/test/core/groups.test.ts +63 -0
- package/apps/daemon/test/core/helpers.ts +50 -0
- package/apps/daemon/test/core/identity.test.ts +85 -0
- package/apps/daemon/test/core/migrations.test.ts +83 -0
- package/apps/daemon/test/core/profiles.test.ts +182 -0
- package/apps/daemon/test/core/provider-models.test.ts +57 -0
- package/apps/daemon/test/core/provider-state.test.ts +36 -0
- package/apps/daemon/test/core/skill-meta.test.ts +45 -0
- package/apps/daemon/test/core/skills.test.ts +271 -0
- package/apps/daemon/test/core/triggers.test.ts +182 -0
- package/apps/daemon/test/core/web-tokens.test.ts +79 -0
- package/apps/daemon/test/cron.test.ts +90 -0
- package/apps/daemon/test/runtime/heartbeat.test.ts +34 -0
- package/apps/daemon/test/runtime/memory-qmd.test.ts +97 -0
- package/apps/daemon/test/runtime/memory.test.ts +78 -0
- package/apps/daemon/test/runtime/messaging.test.ts +213 -0
- package/apps/daemon/test/runtime/mock-server.ts +65 -0
- package/apps/daemon/test/runtime/openai-codex-auth.test.ts +116 -0
- package/apps/daemon/test/runtime/providers.test.ts +306 -0
- package/apps/daemon/test/runtime/retry.test.ts +191 -0
- package/apps/daemon/test/runtime/session-head.test.ts +90 -0
- package/apps/daemon/test/runtime/tools-home.test.ts +102 -0
- package/apps/daemon/test/runtime/tools-web.test.ts +206 -0
- package/apps/daemon/tsconfig.json +4 -0
- package/apps/mobile/README.md +60 -0
- package/apps/mobile/app/_layout.tsx +58 -0
- package/apps/mobile/app/agents/[id]/chat.tsx +486 -0
- package/apps/mobile/app/agents/[id]/index.tsx +166 -0
- package/apps/mobile/app/agents/index.tsx +212 -0
- package/apps/mobile/app/index.tsx +21 -0
- package/apps/mobile/app/pair.tsx +226 -0
- package/apps/mobile/app/settings.tsx +419 -0
- package/apps/mobile/app.json +49 -0
- package/apps/mobile/assets/adaptive-icon.png +0 -0
- package/apps/mobile/assets/favicon.png +0 -0
- package/apps/mobile/assets/icon.png +0 -0
- package/apps/mobile/assets/splash-icon.png +0 -0
- package/apps/mobile/babel.config.js +6 -0
- package/apps/mobile/metro.config.js +28 -0
- package/apps/mobile/package.json +44 -0
- package/apps/mobile/src/auth.ts +66 -0
- package/apps/mobile/src/pair-url.ts +48 -0
- package/apps/mobile/src/theme-context.tsx +88 -0
- package/apps/mobile/src/theme.ts +135 -0
- package/apps/mobile/test/pair-url.test.ts +46 -0
- package/apps/mobile/tsconfig.json +23 -0
- package/apps/web/components.json +25 -0
- package/apps/web/package.json +39 -0
- package/apps/web/public/baziu.svg +8 -0
- package/apps/web/src/components/AgentTabs.tsx +45 -0
- package/apps/web/src/components/BaziuLogo.tsx +21 -0
- package/apps/web/src/components/ChatPane.tsx +1033 -0
- package/apps/web/src/components/ConfigTabs.tsx +29 -0
- package/apps/web/src/components/CopyButton.tsx +68 -0
- package/apps/web/src/components/CreateGroupDialog.tsx +127 -0
- package/apps/web/src/components/FieldRow.tsx +94 -0
- package/apps/web/src/components/Footer.tsx +10 -0
- package/apps/web/src/components/PawIcon.tsx +15 -0
- package/apps/web/src/components/Sidebar.tsx +287 -0
- package/apps/web/src/components/SpawnDialog.tsx +129 -0
- package/apps/web/src/components/ThemeToggle.tsx +75 -0
- package/apps/web/src/components/TopNav.tsx +34 -0
- package/apps/web/src/components/ui/button.tsx +67 -0
- package/apps/web/src/components/ui/card.tsx +103 -0
- package/apps/web/src/components/ui/checkbox.tsx +31 -0
- package/apps/web/src/components/ui/dialog.tsx +168 -0
- package/apps/web/src/components/ui/input.tsx +19 -0
- package/apps/web/src/components/ui/label.tsx +22 -0
- package/apps/web/src/components/ui/radio-group.tsx +44 -0
- package/apps/web/src/components/ui/select.tsx +192 -0
- package/apps/web/src/components/ui/separator.tsx +26 -0
- package/apps/web/src/components/ui/table.tsx +116 -0
- package/apps/web/src/components/ui/tabs.tsx +88 -0
- package/apps/web/src/components/ui/textarea.tsx +18 -0
- package/apps/web/src/lib/auth.ts +50 -0
- package/apps/web/src/lib/daemon-client.ts +34 -0
- package/apps/web/src/lib/md.ts +45 -0
- package/apps/web/src/lib/utils.ts +6 -0
- package/apps/web/src/lib/wire-constants.ts +27 -0
- package/apps/web/src/routeTree.gen.ts +408 -0
- package/apps/web/src/router.tsx +20 -0
- package/apps/web/src/routes/__root.tsx +123 -0
- package/apps/web/src/routes/agents/$id/inbox.tsx +207 -0
- package/apps/web/src/routes/agents/$id/index.tsx +527 -0
- package/apps/web/src/routes/agents/$id/triggers.tsx +239 -0
- package/apps/web/src/routes/agents/index.tsx +265 -0
- package/apps/web/src/routes/api/$.ts +88 -0
- package/apps/web/src/routes/config/index.tsx +315 -0
- package/apps/web/src/routes/config/services.tsx +49 -0
- package/apps/web/src/routes/config/tokens.tsx +192 -0
- package/apps/web/src/routes/groups/$id/index.tsx +153 -0
- package/apps/web/src/routes/groups/$id/memory.tsx +321 -0
- package/apps/web/src/routes/groups/index.tsx +191 -0
- package/apps/web/src/routes/index.tsx +133 -0
- package/apps/web/src/routes/login.tsx +63 -0
- package/apps/web/src/routes/profiles/$id.tsx +549 -0
- package/apps/web/src/routes/profiles/index.tsx +458 -0
- package/apps/web/src/routes/skills/index.tsx +297 -0
- package/apps/web/src/routes/welcome.tsx +61 -0
- package/apps/web/src/styles.css +449 -0
- package/apps/web/tsconfig.json +25 -0
- package/apps/web/vite.config.ts +25 -0
- package/biome.json +23 -0
- package/docs/agent-engine.md +219 -0
- package/docs/architecture.md +627 -0
- package/docs/backlog/README.md +42 -0
- package/docs/backlog/draft/BAZ-001-a2a-federation-spike.md +125 -0
- package/docs/openclaw-reference.md +210 -0
- package/package.json +38 -0
- package/packages/api-types/package.json +11 -0
- package/packages/api-types/src/entities.ts +146 -0
- package/packages/api-types/src/events.ts +55 -0
- package/packages/api-types/src/index.ts +488 -0
- package/packages/api-types/src/memory.ts +15 -0
- package/packages/client/package.json +13 -0
- package/packages/client/src/index.ts +117 -0
- package/pnpm-workspace.yaml +3 -0
- package/tsconfig.base.json +23 -0
- package/tsconfig.json +11 -0
- 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
|
+
}
|