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,374 @@
|
|
|
1
|
+
import type { BazilionDb } from '../../core/index.ts'
|
|
2
|
+
import {
|
|
3
|
+
hasCredentials as hasOpenAICodexCredentials,
|
|
4
|
+
loadAccessToken as loadOpenAICodexAccessToken,
|
|
5
|
+
} from '../auth/openai-codex.ts'
|
|
6
|
+
import { piProvider } from './pi-adapter.ts'
|
|
7
|
+
import { type RetryOptions, withRetry } from './retry.ts'
|
|
8
|
+
import type { Provider } from './types.ts'
|
|
9
|
+
|
|
10
|
+
export interface ProviderConfig {
|
|
11
|
+
anthropic?: { apiKey: string; baseURL?: string }
|
|
12
|
+
openai?: { apiKey: string; baseURL?: string }
|
|
13
|
+
/** ChatGPT/Codex OAuth. The apiKey is fetched+refreshed lazily from secrets. */
|
|
14
|
+
openaiCodex?: { db: BazilionDb; authToken: string }
|
|
15
|
+
google?: { apiKey: string; baseURL?: string }
|
|
16
|
+
azureOpenai?: { apiKey: string; baseURL?: string }
|
|
17
|
+
bedrock?: { apiKey?: string } // auth via AWS SDK env (AWS_PROFILE / AWS_ACCESS_KEY_ID / ...)
|
|
18
|
+
googleVertex?: Record<string, never> // auth via ADC + GOOGLE_CLOUD_PROJECT
|
|
19
|
+
mistral?: { apiKey: string; baseURL?: string }
|
|
20
|
+
groq?: { apiKey: string; baseURL?: string }
|
|
21
|
+
cerebras?: { apiKey: string; baseURL?: string }
|
|
22
|
+
xai?: { apiKey: string; baseURL?: string }
|
|
23
|
+
zai?: { apiKey: string; baseURL?: string }
|
|
24
|
+
huggingface?: { apiKey: string; baseURL?: string }
|
|
25
|
+
openrouter?: { apiKey: string; baseURL?: string }
|
|
26
|
+
vercelAiGateway?: { apiKey: string; baseURL?: string }
|
|
27
|
+
lmstudio?: { baseURL?: string; apiKey?: string }
|
|
28
|
+
ollama?: { baseURL?: string; apiKey?: string }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ResolvedModel {
|
|
32
|
+
provider: Provider
|
|
33
|
+
model: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Env var → provider config. Empty / missing vars leave that provider unconfigured.
|
|
38
|
+
*
|
|
39
|
+
* Pass `oauth` (the daemon's `{db, authToken}` pair) to also pick up
|
|
40
|
+
* OAuth-backed providers whose credentials live in the `secrets` table
|
|
41
|
+
* (currently: `openai-codex` / ChatGPT). Env-only callers can omit it —
|
|
42
|
+
* those providers just won't be configured.
|
|
43
|
+
*/
|
|
44
|
+
export function loadProviderConfigFromEnv(
|
|
45
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
46
|
+
oauth?: { db: BazilionDb; authToken: string },
|
|
47
|
+
): ProviderConfig {
|
|
48
|
+
const config: ProviderConfig = {
|
|
49
|
+
lmstudio: {
|
|
50
|
+
...(env.LMSTUDIO_URL !== undefined ? { baseURL: env.LMSTUDIO_URL } : {}),
|
|
51
|
+
...(env.LMSTUDIO_API_KEY !== undefined ? { apiKey: env.LMSTUDIO_API_KEY } : {}),
|
|
52
|
+
},
|
|
53
|
+
ollama: {
|
|
54
|
+
...(env.OLLAMA_URL !== undefined ? { baseURL: env.OLLAMA_URL } : {}),
|
|
55
|
+
...(env.OLLAMA_API_KEY !== undefined ? { apiKey: env.OLLAMA_API_KEY } : {}),
|
|
56
|
+
},
|
|
57
|
+
}
|
|
58
|
+
if (env.ANTHROPIC_API_KEY || env.ANTHROPIC_OAUTH_TOKEN) {
|
|
59
|
+
config.anthropic = { apiKey: env.ANTHROPIC_OAUTH_TOKEN ?? env.ANTHROPIC_API_KEY ?? '' }
|
|
60
|
+
}
|
|
61
|
+
if (env.OPENAI_API_KEY) config.openai = { apiKey: env.OPENAI_API_KEY }
|
|
62
|
+
if (env.GEMINI_API_KEY) config.google = { apiKey: env.GEMINI_API_KEY }
|
|
63
|
+
if (env.AZURE_OPENAI_API_KEY) config.azureOpenai = { apiKey: env.AZURE_OPENAI_API_KEY }
|
|
64
|
+
if (
|
|
65
|
+
env.AWS_PROFILE ||
|
|
66
|
+
env.AWS_BEARER_TOKEN_BEDROCK ||
|
|
67
|
+
(env.AWS_ACCESS_KEY_ID && env.AWS_SECRET_ACCESS_KEY)
|
|
68
|
+
) {
|
|
69
|
+
config.bedrock = {}
|
|
70
|
+
}
|
|
71
|
+
if (env.GOOGLE_CLOUD_PROJECT && env.GOOGLE_CLOUD_LOCATION) {
|
|
72
|
+
config.googleVertex = {}
|
|
73
|
+
}
|
|
74
|
+
if (env.MISTRAL_API_KEY) config.mistral = { apiKey: env.MISTRAL_API_KEY }
|
|
75
|
+
if (env.GROQ_API_KEY) config.groq = { apiKey: env.GROQ_API_KEY }
|
|
76
|
+
if (env.CEREBRAS_API_KEY) config.cerebras = { apiKey: env.CEREBRAS_API_KEY }
|
|
77
|
+
if (env.XAI_API_KEY) config.xai = { apiKey: env.XAI_API_KEY }
|
|
78
|
+
if (env.ZAI_API_KEY) config.zai = { apiKey: env.ZAI_API_KEY }
|
|
79
|
+
if (env.HF_TOKEN) config.huggingface = { apiKey: env.HF_TOKEN }
|
|
80
|
+
if (env.OPENROUTER_API_KEY) config.openrouter = { apiKey: env.OPENROUTER_API_KEY }
|
|
81
|
+
if (env.AI_GATEWAY_API_KEY) config.vercelAiGateway = { apiKey: env.AI_GATEWAY_API_KEY }
|
|
82
|
+
if (oauth && hasOpenAICodexCredentials(oauth.db, oauth.authToken)) {
|
|
83
|
+
config.openaiCodex = oauth
|
|
84
|
+
}
|
|
85
|
+
return config
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface ProviderRegistry {
|
|
89
|
+
resolve(modelString: string): ResolvedModel
|
|
90
|
+
list(): string[]
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface ProviderRegistryOptions {
|
|
94
|
+
/** If provided, resolve() refuses any provider not in the set with "disabled by admin". */
|
|
95
|
+
enabledSet?: ReadonlySet<string>
|
|
96
|
+
/** Retry policy applied uniformly to every provider; omit for built-in defaults. */
|
|
97
|
+
retry?: RetryOptions
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
interface ProviderEntry {
|
|
101
|
+
configured: (c: ProviderConfig) => boolean
|
|
102
|
+
build: (c: ProviderConfig) => Provider
|
|
103
|
+
/** Helpful error hint when the caller references this provider but env isn't set. */
|
|
104
|
+
hint: string
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const PROVIDERS: Record<string, ProviderEntry> = {
|
|
108
|
+
anthropic: {
|
|
109
|
+
configured: (c) => !!c.anthropic,
|
|
110
|
+
build: (c) =>
|
|
111
|
+
piProvider({
|
|
112
|
+
providerName: 'anthropic',
|
|
113
|
+
fallbackApi: 'anthropic-messages',
|
|
114
|
+
apiKey: c.anthropic?.apiKey,
|
|
115
|
+
baseUrl: c.anthropic?.baseURL,
|
|
116
|
+
}),
|
|
117
|
+
hint: 'ANTHROPIC_API_KEY or ANTHROPIC_OAUTH_TOKEN',
|
|
118
|
+
},
|
|
119
|
+
openai: {
|
|
120
|
+
configured: (c) => !!c.openai,
|
|
121
|
+
build: (c) =>
|
|
122
|
+
piProvider({
|
|
123
|
+
providerName: 'openai',
|
|
124
|
+
fallbackApi: 'openai-completions',
|
|
125
|
+
apiKey: c.openai?.apiKey,
|
|
126
|
+
baseUrl: c.openai?.baseURL,
|
|
127
|
+
}),
|
|
128
|
+
hint: 'OPENAI_API_KEY',
|
|
129
|
+
},
|
|
130
|
+
'openai-codex': {
|
|
131
|
+
configured: (c) => !!c.openaiCodex,
|
|
132
|
+
// Pi-ai's `openai-codex-responses` speaks the ChatGPT backend's Responses
|
|
133
|
+
// API (https://chatgpt.com/backend-api) using a JWT access token as the
|
|
134
|
+
// apiKey. We pass a supplier that refreshes lazily via the OAuth refresh
|
|
135
|
+
// token, so the registry-cached Provider instance stays valid across
|
|
136
|
+
// expiries without rebuild.
|
|
137
|
+
build: (c) => {
|
|
138
|
+
const openaiCodex = c.openaiCodex
|
|
139
|
+
if (!openaiCodex) throw new Error('openai-codex not configured')
|
|
140
|
+
return piProvider({
|
|
141
|
+
providerName: 'openai-codex',
|
|
142
|
+
fallbackApi: 'openai-codex-responses',
|
|
143
|
+
apiKey: () => loadOpenAICodexAccessToken(openaiCodex.db, openaiCodex.authToken),
|
|
144
|
+
})
|
|
145
|
+
},
|
|
146
|
+
hint: 'run `bazilion auth openai login` (or click Connect on /config)',
|
|
147
|
+
},
|
|
148
|
+
google: {
|
|
149
|
+
configured: (c) => !!c.google,
|
|
150
|
+
build: (c) =>
|
|
151
|
+
piProvider({
|
|
152
|
+
providerName: 'google',
|
|
153
|
+
fallbackApi: 'google-generative-ai',
|
|
154
|
+
apiKey: c.google?.apiKey,
|
|
155
|
+
baseUrl: c.google?.baseURL,
|
|
156
|
+
}),
|
|
157
|
+
hint: 'GEMINI_API_KEY',
|
|
158
|
+
},
|
|
159
|
+
'google-vertex': {
|
|
160
|
+
configured: (c) => !!c.googleVertex,
|
|
161
|
+
build: () => piProvider({ providerName: 'google-vertex', fallbackApi: 'google-vertex' }),
|
|
162
|
+
hint: 'GOOGLE_CLOUD_PROJECT + GOOGLE_CLOUD_LOCATION + ADC (gcloud auth)',
|
|
163
|
+
},
|
|
164
|
+
'azure-openai': {
|
|
165
|
+
configured: (c) => !!c.azureOpenai,
|
|
166
|
+
build: (c) =>
|
|
167
|
+
piProvider({
|
|
168
|
+
providerName: 'azure-openai',
|
|
169
|
+
fallbackApi: 'azure-openai-responses',
|
|
170
|
+
apiKey: c.azureOpenai?.apiKey,
|
|
171
|
+
baseUrl: c.azureOpenai?.baseURL,
|
|
172
|
+
}),
|
|
173
|
+
hint: 'AZURE_OPENAI_API_KEY',
|
|
174
|
+
},
|
|
175
|
+
bedrock: {
|
|
176
|
+
configured: (c) => !!c.bedrock,
|
|
177
|
+
build: () =>
|
|
178
|
+
piProvider({
|
|
179
|
+
providerName: 'bedrock',
|
|
180
|
+
piProviderName: 'amazon-bedrock',
|
|
181
|
+
fallbackApi: 'bedrock-converse-stream',
|
|
182
|
+
}),
|
|
183
|
+
hint: 'AWS_PROFILE or AWS_ACCESS_KEY_ID+AWS_SECRET_ACCESS_KEY',
|
|
184
|
+
},
|
|
185
|
+
mistral: {
|
|
186
|
+
configured: (c) => !!c.mistral,
|
|
187
|
+
build: (c) =>
|
|
188
|
+
piProvider({
|
|
189
|
+
providerName: 'mistral',
|
|
190
|
+
fallbackApi: 'openai-completions',
|
|
191
|
+
apiKey: c.mistral?.apiKey,
|
|
192
|
+
baseUrl: c.mistral?.baseURL,
|
|
193
|
+
}),
|
|
194
|
+
hint: 'MISTRAL_API_KEY',
|
|
195
|
+
},
|
|
196
|
+
groq: {
|
|
197
|
+
configured: (c) => !!c.groq,
|
|
198
|
+
build: (c) =>
|
|
199
|
+
piProvider({
|
|
200
|
+
providerName: 'groq',
|
|
201
|
+
fallbackApi: 'openai-completions',
|
|
202
|
+
apiKey: c.groq?.apiKey,
|
|
203
|
+
baseUrl: c.groq?.baseURL,
|
|
204
|
+
}),
|
|
205
|
+
hint: 'GROQ_API_KEY',
|
|
206
|
+
},
|
|
207
|
+
cerebras: {
|
|
208
|
+
configured: (c) => !!c.cerebras,
|
|
209
|
+
build: (c) =>
|
|
210
|
+
piProvider({
|
|
211
|
+
providerName: 'cerebras',
|
|
212
|
+
fallbackApi: 'openai-completions',
|
|
213
|
+
apiKey: c.cerebras?.apiKey,
|
|
214
|
+
baseUrl: c.cerebras?.baseURL,
|
|
215
|
+
}),
|
|
216
|
+
hint: 'CEREBRAS_API_KEY',
|
|
217
|
+
},
|
|
218
|
+
xai: {
|
|
219
|
+
configured: (c) => !!c.xai,
|
|
220
|
+
build: (c) =>
|
|
221
|
+
piProvider({
|
|
222
|
+
providerName: 'xai',
|
|
223
|
+
fallbackApi: 'openai-completions',
|
|
224
|
+
apiKey: c.xai?.apiKey,
|
|
225
|
+
baseUrl: c.xai?.baseURL,
|
|
226
|
+
}),
|
|
227
|
+
hint: 'XAI_API_KEY',
|
|
228
|
+
},
|
|
229
|
+
zai: {
|
|
230
|
+
configured: (c) => !!c.zai,
|
|
231
|
+
build: (c) =>
|
|
232
|
+
piProvider({
|
|
233
|
+
providerName: 'zai',
|
|
234
|
+
fallbackApi: 'openai-completions',
|
|
235
|
+
apiKey: c.zai?.apiKey,
|
|
236
|
+
baseUrl: c.zai?.baseURL,
|
|
237
|
+
}),
|
|
238
|
+
hint: 'ZAI_API_KEY',
|
|
239
|
+
},
|
|
240
|
+
huggingface: {
|
|
241
|
+
configured: (c) => !!c.huggingface,
|
|
242
|
+
build: (c) =>
|
|
243
|
+
piProvider({
|
|
244
|
+
providerName: 'huggingface',
|
|
245
|
+
fallbackApi: 'openai-completions',
|
|
246
|
+
apiKey: c.huggingface?.apiKey,
|
|
247
|
+
baseUrl: c.huggingface?.baseURL,
|
|
248
|
+
}),
|
|
249
|
+
hint: 'HF_TOKEN',
|
|
250
|
+
},
|
|
251
|
+
openrouter: {
|
|
252
|
+
configured: (c) => !!c.openrouter,
|
|
253
|
+
build: (c) =>
|
|
254
|
+
piProvider({
|
|
255
|
+
providerName: 'openrouter',
|
|
256
|
+
fallbackApi: 'openai-completions',
|
|
257
|
+
apiKey: c.openrouter?.apiKey,
|
|
258
|
+
baseUrl: c.openrouter?.baseURL,
|
|
259
|
+
}),
|
|
260
|
+
hint: 'OPENROUTER_API_KEY',
|
|
261
|
+
},
|
|
262
|
+
'vercel-ai-gateway': {
|
|
263
|
+
configured: (c) => !!c.vercelAiGateway,
|
|
264
|
+
build: (c) =>
|
|
265
|
+
piProvider({
|
|
266
|
+
providerName: 'vercel-ai-gateway',
|
|
267
|
+
fallbackApi: 'openai-completions',
|
|
268
|
+
apiKey: c.vercelAiGateway?.apiKey,
|
|
269
|
+
baseUrl: c.vercelAiGateway?.baseURL,
|
|
270
|
+
}),
|
|
271
|
+
hint: 'AI_GATEWAY_API_KEY',
|
|
272
|
+
},
|
|
273
|
+
lmstudio: {
|
|
274
|
+
configured: () => true,
|
|
275
|
+
build: (c) =>
|
|
276
|
+
piProvider({
|
|
277
|
+
providerName: 'lmstudio',
|
|
278
|
+
fallbackApi: 'openai-completions',
|
|
279
|
+
apiKey: c.lmstudio?.apiKey ?? 'lm-studio',
|
|
280
|
+
baseUrl: c.lmstudio?.baseURL ?? 'http://127.0.0.1:1234/v1',
|
|
281
|
+
}),
|
|
282
|
+
hint: 'LMSTUDIO_URL (default http://127.0.0.1:1234/v1)',
|
|
283
|
+
},
|
|
284
|
+
ollama: {
|
|
285
|
+
configured: () => true,
|
|
286
|
+
build: (c) =>
|
|
287
|
+
piProvider({
|
|
288
|
+
providerName: 'ollama',
|
|
289
|
+
fallbackApi: 'openai-completions',
|
|
290
|
+
apiKey: c.ollama?.apiKey ?? 'ollama',
|
|
291
|
+
baseUrl: c.ollama?.baseURL ?? 'http://127.0.0.1:11434/v1',
|
|
292
|
+
}),
|
|
293
|
+
hint: 'OLLAMA_URL (default http://127.0.0.1:11434/v1)',
|
|
294
|
+
},
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Model strings are `provider:model`, e.g.:
|
|
299
|
+
* - `anthropic:claude-opus-4-6`
|
|
300
|
+
* - `openai:gpt-4o`
|
|
301
|
+
* - `google:gemini-2.0-flash-exp`
|
|
302
|
+
* - `groq:llama-3.3-70b-versatile`
|
|
303
|
+
* - `lmstudio:my-loaded-model`
|
|
304
|
+
* - `ollama:llama2`
|
|
305
|
+
*/
|
|
306
|
+
export function createProviderRegistry(
|
|
307
|
+
config: ProviderConfig,
|
|
308
|
+
opts: ProviderRegistryOptions = {},
|
|
309
|
+
): ProviderRegistry {
|
|
310
|
+
const cache = new Map<string, Provider>()
|
|
311
|
+
const enabledSet = opts.enabledSet
|
|
312
|
+
|
|
313
|
+
function get(name: string): Provider {
|
|
314
|
+
const cached = cache.get(name)
|
|
315
|
+
if (cached) return cached
|
|
316
|
+
const entry = PROVIDERS[name]
|
|
317
|
+
if (!entry) throw new Error(`unknown provider: ${name}`)
|
|
318
|
+
if (enabledSet && !enabledSet.has(name)) {
|
|
319
|
+
throw new Error(`${name} provider is disabled — enable it on the /config page`)
|
|
320
|
+
}
|
|
321
|
+
if (!entry.configured(config)) {
|
|
322
|
+
throw new Error(`${name} provider not configured (set ${entry.hint})`)
|
|
323
|
+
}
|
|
324
|
+
const raw = entry.build(config)
|
|
325
|
+
const provider = withRetry(raw, {
|
|
326
|
+
...(opts.retry ?? {}),
|
|
327
|
+
onRetry: (info) => {
|
|
328
|
+
opts.retry?.onRetry?.(info)
|
|
329
|
+
console.warn(
|
|
330
|
+
`[provider/${name}] transient error on attempt ${info.attempt}, retrying in ${info.delayMs}ms: ${info.error.message.slice(0, 160)}`,
|
|
331
|
+
)
|
|
332
|
+
},
|
|
333
|
+
})
|
|
334
|
+
cache.set(name, provider)
|
|
335
|
+
return provider
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return {
|
|
339
|
+
resolve(modelString: string): ResolvedModel {
|
|
340
|
+
const idx = modelString.indexOf(':')
|
|
341
|
+
if (idx === -1) {
|
|
342
|
+
throw new Error(`invalid model string "${modelString}": expected "provider:model"`)
|
|
343
|
+
}
|
|
344
|
+
const providerName = modelString.slice(0, idx)
|
|
345
|
+
const model = modelString.slice(idx + 1)
|
|
346
|
+
return { provider: get(providerName), model }
|
|
347
|
+
},
|
|
348
|
+
list() {
|
|
349
|
+
return Object.entries(PROVIDERS)
|
|
350
|
+
.filter(([name, entry]) => {
|
|
351
|
+
if (!entry.configured(config)) return false
|
|
352
|
+
if (enabledSet && !enabledSet.has(name)) return false
|
|
353
|
+
return true
|
|
354
|
+
})
|
|
355
|
+
.map(([name]) => name)
|
|
356
|
+
},
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export interface ProviderMeta {
|
|
361
|
+
name: string
|
|
362
|
+
enabled: boolean
|
|
363
|
+
/** Hint shown when the provider isn't configured — the env var(s) required. */
|
|
364
|
+
envHint: string
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/** List every provider the registry knows about, plus whether each is configured. */
|
|
368
|
+
export function listAllProviders(config: ProviderConfig): ProviderMeta[] {
|
|
369
|
+
return Object.entries(PROVIDERS).map(([name, entry]) => ({
|
|
370
|
+
name,
|
|
371
|
+
enabled: entry.configured(config),
|
|
372
|
+
envHint: entry.hint,
|
|
373
|
+
}))
|
|
374
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// Transient-error retry wrapper for Provider.chat().
|
|
2
|
+
//
|
|
3
|
+
// Applied uniformly in `createProviderRegistry` so every provider (anthropic,
|
|
4
|
+
// openai, openai-codex, lmstudio, ollama, …) gets the same retry policy. A
|
|
5
|
+
// one-shot upstream 5xx or rate-limit shouldn't kill the agent's turn — the
|
|
6
|
+
// runtime marks the run `failed` and the user is stuck re-sending the same
|
|
7
|
+
// message by hand, which is hostile UX.
|
|
8
|
+
//
|
|
9
|
+
// What counts as retryable is a small allowlist (server 5xx, rate-limit, a
|
|
10
|
+
// handful of network errnos). Auth errors, invalid-request errors, context
|
|
11
|
+
// overflows, and user-triggered aborts bypass retry entirely — they won't
|
|
12
|
+
// resolve by trying again and the fast failure is the right signal.
|
|
13
|
+
//
|
|
14
|
+
// One hard rule: if the underlying chat already streamed text back via
|
|
15
|
+
// onDelta, we can't retry — a second attempt would emit duplicated text into
|
|
16
|
+
// the UI. The wrapper detects this by shadowing onDelta and tracking whether
|
|
17
|
+
// the callback fired.
|
|
18
|
+
|
|
19
|
+
import type { Provider, ProviderRequest, ProviderResponse } from './types.ts'
|
|
20
|
+
|
|
21
|
+
export interface RetryOptions {
|
|
22
|
+
/** How many *extra* attempts beyond the first. Default 2 → up to 3 tries total. */
|
|
23
|
+
maxRetries?: number
|
|
24
|
+
/** First backoff delay in ms. Doubles each retry up to maxDelayMs. Default 500. */
|
|
25
|
+
initialDelayMs?: number
|
|
26
|
+
/** Upper bound on a single backoff delay. Default 8000. */
|
|
27
|
+
maxDelayMs?: number
|
|
28
|
+
/** Optional callback invoked before each retry (for logging / telemetry). */
|
|
29
|
+
onRetry?: (info: { attempt: number; delayMs: number; error: Error }) => void
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Lowercased substrings that mark an error as worth retrying. Checked with a
|
|
34
|
+
* simple `includes` so we don't need to parse the upstream JSON shapes.
|
|
35
|
+
*/
|
|
36
|
+
const RETRYABLE_MARKERS: readonly string[] = [
|
|
37
|
+
'server_error',
|
|
38
|
+
'internal_server',
|
|
39
|
+
'rate_limit',
|
|
40
|
+
'rate limit',
|
|
41
|
+
'too many requests',
|
|
42
|
+
'overloaded', // covers 'overloaded_error' (Anthropic)
|
|
43
|
+
'service_unavailable',
|
|
44
|
+
'service unavailable',
|
|
45
|
+
'gateway_timeout',
|
|
46
|
+
'gateway timeout',
|
|
47
|
+
'bad_gateway',
|
|
48
|
+
'bad gateway',
|
|
49
|
+
'econnreset',
|
|
50
|
+
'etimedout',
|
|
51
|
+
'econnrefused',
|
|
52
|
+
'enotfound',
|
|
53
|
+
'eai_again',
|
|
54
|
+
'socket hang up',
|
|
55
|
+
'fetch failed',
|
|
56
|
+
'network error',
|
|
57
|
+
'connection reset',
|
|
58
|
+
'status 429',
|
|
59
|
+
'status 500',
|
|
60
|
+
'status 502',
|
|
61
|
+
'status 503',
|
|
62
|
+
'status 504',
|
|
63
|
+
'"status":429',
|
|
64
|
+
'"status":500',
|
|
65
|
+
'"status":502',
|
|
66
|
+
'"status":503',
|
|
67
|
+
'"status":504',
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Non-retryable markers win over retryable ones — if an error mentions
|
|
72
|
+
* authentication or a 4xx (other than 429), retrying won't help.
|
|
73
|
+
*/
|
|
74
|
+
const NON_RETRYABLE_MARKERS: readonly string[] = [
|
|
75
|
+
'invalid_api_key',
|
|
76
|
+
'invalid api key',
|
|
77
|
+
'incorrect_api_key',
|
|
78
|
+
'authentication',
|
|
79
|
+
'unauthorized',
|
|
80
|
+
'permission_denied',
|
|
81
|
+
'permission denied',
|
|
82
|
+
'forbidden',
|
|
83
|
+
'invalid_request',
|
|
84
|
+
'invalid request',
|
|
85
|
+
'not_found',
|
|
86
|
+
'model_not_found',
|
|
87
|
+
'context_length_exceeded',
|
|
88
|
+
'context length',
|
|
89
|
+
'content_filter',
|
|
90
|
+
'quota_exceeded',
|
|
91
|
+
'insufficient_quota',
|
|
92
|
+
'billing',
|
|
93
|
+
'status 400',
|
|
94
|
+
'status 401',
|
|
95
|
+
'status 403',
|
|
96
|
+
'status 404',
|
|
97
|
+
'status 422',
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
export function isRetryableError(err: unknown): boolean {
|
|
101
|
+
const raw = err instanceof Error ? err.message : typeof err === 'string' ? err : String(err)
|
|
102
|
+
const msg = raw.toLowerCase()
|
|
103
|
+
for (const deny of NON_RETRYABLE_MARKERS) {
|
|
104
|
+
if (msg.includes(deny)) return false
|
|
105
|
+
}
|
|
106
|
+
for (const allow of RETRYABLE_MARKERS) {
|
|
107
|
+
if (msg.includes(allow)) return true
|
|
108
|
+
}
|
|
109
|
+
return false
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function sleepWithAbort(ms: number, signal?: AbortSignal): Promise<void> {
|
|
113
|
+
return new Promise((resolve, reject) => {
|
|
114
|
+
if (signal?.aborted) {
|
|
115
|
+
reject(new Error('aborted'))
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
const t = setTimeout(() => {
|
|
119
|
+
if (signal) signal.removeEventListener('abort', onAbort)
|
|
120
|
+
resolve()
|
|
121
|
+
}, ms)
|
|
122
|
+
const onAbort = () => {
|
|
123
|
+
clearTimeout(t)
|
|
124
|
+
reject(new Error('aborted'))
|
|
125
|
+
}
|
|
126
|
+
signal?.addEventListener('abort', onAbort, { once: true })
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function withRetry(provider: Provider, opts: RetryOptions = {}): Provider {
|
|
131
|
+
const maxRetries = opts.maxRetries ?? 2
|
|
132
|
+
const initialDelayMs = opts.initialDelayMs ?? 500
|
|
133
|
+
const maxDelayMs = opts.maxDelayMs ?? 8_000
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
name: provider.name,
|
|
137
|
+
async chat(req: ProviderRequest): Promise<ProviderResponse> {
|
|
138
|
+
let attempt = 0
|
|
139
|
+
// Use `let` so each retry gets a fresh shadow; we need to know whether
|
|
140
|
+
// onDelta fired on the *most recent* attempt.
|
|
141
|
+
let lastError: Error | null = null
|
|
142
|
+
while (true) {
|
|
143
|
+
let streamed = false
|
|
144
|
+
const wrappedReq: ProviderRequest = req.onDelta
|
|
145
|
+
? {
|
|
146
|
+
...req,
|
|
147
|
+
onDelta: (delta: string) => {
|
|
148
|
+
streamed = true
|
|
149
|
+
req.onDelta?.(delta)
|
|
150
|
+
},
|
|
151
|
+
}
|
|
152
|
+
: req
|
|
153
|
+
try {
|
|
154
|
+
return await provider.chat(wrappedReq)
|
|
155
|
+
} catch (err) {
|
|
156
|
+
lastError = err instanceof Error ? err : new Error(String(err))
|
|
157
|
+
if (req.signal?.aborted) throw lastError
|
|
158
|
+
if (streamed) throw lastError
|
|
159
|
+
if (attempt >= maxRetries) throw lastError
|
|
160
|
+
if (!isRetryableError(lastError)) throw lastError
|
|
161
|
+
const delayMs = Math.min(initialDelayMs * 2 ** attempt, maxDelayMs)
|
|
162
|
+
opts.onRetry?.({ attempt: attempt + 1, delayMs, error: lastError })
|
|
163
|
+
try {
|
|
164
|
+
await sleepWithAbort(delayMs, req.signal)
|
|
165
|
+
} catch {
|
|
166
|
+
// Aborted during backoff — surface the original provider error
|
|
167
|
+
// rather than the synthetic abort so the run's failure reason
|
|
168
|
+
// still points at what actually went wrong.
|
|
169
|
+
throw lastError
|
|
170
|
+
}
|
|
171
|
+
attempt++
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
}
|
|
176
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ProviderMessage, ReasoningLevel, ToolCall, ToolDef } from '@bazilion/api-types'
|
|
2
|
+
|
|
3
|
+
export interface ProviderRequest {
|
|
4
|
+
model: string
|
|
5
|
+
system?: string
|
|
6
|
+
messages: ProviderMessage[]
|
|
7
|
+
tools?: ToolDef[]
|
|
8
|
+
temperature?: number
|
|
9
|
+
maxTokens?: number
|
|
10
|
+
/** Reasoning/thinking level — only honored by providers/models that support it. */
|
|
11
|
+
reasoning?: ReasoningLevel
|
|
12
|
+
/** Cancels the in-flight provider call. Providers forward it to fetch(). */
|
|
13
|
+
signal?: AbortSignal
|
|
14
|
+
/** Optional token-delta callback. Fires once per streamed text chunk as the response assembles. */
|
|
15
|
+
onDelta?: (delta: string) => void
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type StopReason = 'stop' | 'tool_use' | 'length' | 'error'
|
|
19
|
+
|
|
20
|
+
export interface ProviderResponse {
|
|
21
|
+
content: string
|
|
22
|
+
toolCalls: ToolCall[]
|
|
23
|
+
stopReason: StopReason
|
|
24
|
+
usage?: {
|
|
25
|
+
promptTokens: number
|
|
26
|
+
completionTokens: number
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface Provider {
|
|
31
|
+
name: string
|
|
32
|
+
chat(request: ProviderRequest): Promise<ProviderResponse>
|
|
33
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Bazilion system-prompt builder. Feeds pi's `getAppendSystemPrompt()` hook
|
|
2
|
+
// so the agent sees its persona + skills + workspaces + memory guidance
|
|
3
|
+
// stacked on top of pi's built-in base prompt (which lists coding tools and
|
|
4
|
+
// general guidelines). Pure filesystem read — no LLM, no DB.
|
|
5
|
+
|
|
6
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
7
|
+
import { join } from 'node:path'
|
|
8
|
+
import type { ResolvedAgent } from '@bazilion/api-types'
|
|
9
|
+
|
|
10
|
+
// Prompt order: peers first (who else is around), then persona, then tooling
|
|
11
|
+
// hints, then self-knowledge, then the wake-up playbook, then the one-shot
|
|
12
|
+
// bootstrap intro.
|
|
13
|
+
const CONTEXT_FILE_ORDER = [
|
|
14
|
+
'AGENTS.md',
|
|
15
|
+
'SOUL.md',
|
|
16
|
+
'TOOLS.md',
|
|
17
|
+
'IDENTITY.md',
|
|
18
|
+
'HEARTBEAT.md',
|
|
19
|
+
'BOOTSTRAP.md',
|
|
20
|
+
] as const
|
|
21
|
+
|
|
22
|
+
export function buildSystemPrompt(agent: ResolvedAgent): string {
|
|
23
|
+
const parts: string[] = []
|
|
24
|
+
|
|
25
|
+
const contextBlocks: string[] = []
|
|
26
|
+
let bootstrapPresent = false
|
|
27
|
+
for (const file of CONTEXT_FILE_ORDER) {
|
|
28
|
+
const path = join(agent.agent.dir, file)
|
|
29
|
+
if (!existsSync(path)) continue
|
|
30
|
+
const content = readFileSync(path, 'utf8').trimEnd()
|
|
31
|
+
if (!content) continue
|
|
32
|
+
contextBlocks.push(`## ${file}\n\n${content}`)
|
|
33
|
+
if (file === 'BOOTSTRAP.md') bootstrapPresent = true
|
|
34
|
+
}
|
|
35
|
+
if (contextBlocks.length > 0) {
|
|
36
|
+
parts.push(`# Project Context\n\n${contextBlocks.join('\n\n')}`)
|
|
37
|
+
}
|
|
38
|
+
if (bootstrapPresent) {
|
|
39
|
+
parts.push(
|
|
40
|
+
'NOTE: This is your first session. After you have completed the bootstrap conversation, call the `bootstrap_done` tool to delete BOOTSTRAP.md.',
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
parts.push(
|
|
45
|
+
[
|
|
46
|
+
'# Agent Home',
|
|
47
|
+
'',
|
|
48
|
+
'Your private home holds who you are — identity, soul, behaviour rules, wake-up routine. It is not shared with other agents and cannot be overwritten by them. The files above (IDENTITY.md, SOUL.md, AGENTS.md, TOOLS.md, HEARTBEAT.md, BOOTSTRAP.md) live in this home.',
|
|
49
|
+
'',
|
|
50
|
+
'- To change who you are (name, vibe, personality, how you behave): use `home_write`.',
|
|
51
|
+
'- To inspect exact wording of your own files: use `home_read` or `home_list`.',
|
|
52
|
+
'- To remember facts the user told you or things you learned: use `memory_write` — NOT `home_write`.',
|
|
53
|
+
'- To produce work output (code, docs, artefacts): use `write` / `edit` — those land in your workspace, not your home.',
|
|
54
|
+
].join('\n'),
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
if (agent.skills.length > 0) {
|
|
58
|
+
parts.push(
|
|
59
|
+
`# Available Skills\n\nYou have access to the following skills: ${agent.skills.join(', ')}.`,
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const groupLines = [
|
|
64
|
+
'# Group',
|
|
65
|
+
'',
|
|
66
|
+
`- ${agent.group.id} (${agent.group.name}): ${agent.group.path}`,
|
|
67
|
+
'',
|
|
68
|
+
'Your group is where work product lives — code, docs, artefacts, shared scratch. It may be shared with other agents in the same group. Your coding tools (`read`, `bash`, `edit`, `write`, `grep`, `find`, `ls`) are rooted at the group directory. Never use these tools to edit your identity/soul/behaviour files — those live in your home and are reached via `home_write` / `home_read`.',
|
|
69
|
+
]
|
|
70
|
+
parts.push(groupLines.join('\n'))
|
|
71
|
+
|
|
72
|
+
if (agent.group.userMd.trim()) {
|
|
73
|
+
parts.push(
|
|
74
|
+
`# About the User\n\nRead-only context about the human you're working with in this group. You cannot edit this — if it's wrong, say so and they will update it.\n\n${agent.group.userMd.trim()}`,
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
parts.push(
|
|
79
|
+
'# Memory\n\nYou share a persistent memory backend with every other agent in this group. Use `memory_write` to remember things across sessions, and `memory_search` / `memory_read` / `memory_list` to recall them. This memory is for project knowledge — codebase notes, decisions, things the user told you about the work. For personal notes about yourself (preferences, persona quirks), use `home_write` on IDENTITY.md instead. Always check memory at the start of a session: another agent in the group may have already learned something useful.',
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
return parts.join('\n\n---\n\n')
|
|
83
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { existsSync, rmSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import type { ToolHandler } from './types.ts'
|
|
4
|
+
|
|
5
|
+
export function bootstrapTool(agentDir: string): ToolHandler {
|
|
6
|
+
return {
|
|
7
|
+
def: {
|
|
8
|
+
name: 'bootstrap_done',
|
|
9
|
+
description:
|
|
10
|
+
'Call this once you have finished your bootstrap conversation (introduced yourself, learned the user, updated IDENTITY.md). Removes BOOTSTRAP.md so it does not appear in future sessions.',
|
|
11
|
+
parameters: { type: 'object', properties: {} },
|
|
12
|
+
},
|
|
13
|
+
async invoke() {
|
|
14
|
+
const path = join(agentDir, 'BOOTSTRAP.md')
|
|
15
|
+
if (existsSync(path)) {
|
|
16
|
+
rmSync(path)
|
|
17
|
+
return 'BOOTSTRAP.md removed. Bootstrap is complete.'
|
|
18
|
+
}
|
|
19
|
+
return 'BOOTSTRAP.md was already removed.'
|
|
20
|
+
},
|
|
21
|
+
}
|
|
22
|
+
}
|