bazilion 0.0.0 → 0.1.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/README.md +53 -152
- package/dist/cli.js +2730 -0
- package/dist/cli.js.map +1 -0
- package/dist/daemon.js +5888 -0
- package/dist/daemon.js.map +1 -0
- package/dist/worker.js +1830 -0
- package/dist/worker.js.map +1 -0
- package/package.json +57 -28
- package/.understand-anything/.understandignore +0 -25
- package/.understand-anything/fingerprints.json +0 -14267
- package/.understand-anything/knowledge-graph.json +0 -18128
- package/.understand-anything/meta.json +0 -6
- package/CLAUDE.md +0 -164
- package/apps/cli/package.json +0 -21
- package/apps/cli/src/auth-file.ts +0 -18
- package/apps/cli/src/client.ts +0 -37
- package/apps/cli/src/columnize.ts +0 -32
- package/apps/cli/src/commands/agent.ts +0 -574
- package/apps/cli/src/commands/auth.ts +0 -110
- package/apps/cli/src/commands/backup.ts +0 -135
- package/apps/cli/src/commands/completion.ts +0 -155
- package/apps/cli/src/commands/config.ts +0 -95
- package/apps/cli/src/commands/doctor.ts +0 -131
- package/apps/cli/src/commands/group.ts +0 -132
- package/apps/cli/src/commands/inbox.ts +0 -82
- package/apps/cli/src/commands/login.ts +0 -73
- package/apps/cli/src/commands/memory.ts +0 -106
- package/apps/cli/src/commands/profile.ts +0 -259
- package/apps/cli/src/commands/provider.ts +0 -170
- package/apps/cli/src/commands/send.ts +0 -24
- package/apps/cli/src/commands/serve.ts +0 -89
- package/apps/cli/src/commands/skill.ts +0 -120
- package/apps/cli/src/commands/token.ts +0 -148
- package/apps/cli/src/commands/trigger.ts +0 -129
- package/apps/cli/src/commands/uninstall.ts +0 -156
- package/apps/cli/src/index.ts +0 -196
- package/apps/cli/src/paths.ts +0 -12
- package/apps/cli/test/agent.test.ts +0 -213
- package/apps/cli/test/backup.test.ts +0 -95
- package/apps/cli/test/chat.test.ts +0 -539
- package/apps/cli/test/columnize.test.ts +0 -29
- package/apps/cli/test/completion.test.ts +0 -45
- package/apps/cli/test/config-page.test.ts +0 -151
- package/apps/cli/test/group.test.ts +0 -74
- package/apps/cli/test/helpers.ts +0 -70
- package/apps/cli/test/inbox-autodeliver.test.ts +0 -143
- package/apps/cli/test/inbox.test.ts +0 -147
- package/apps/cli/test/memory.test.ts +0 -69
- package/apps/cli/test/profile.test.ts +0 -110
- package/apps/cli/test/send.test.ts +0 -30
- package/apps/cli/test/server-fixture.ts +0 -212
- package/apps/cli/test/session-head.test.ts +0 -45
- package/apps/cli/test/skill.test.ts +0 -128
- package/apps/cli/test/token.test.ts +0 -109
- package/apps/cli/test/trigger.test.ts +0 -245
- package/apps/cli/tsconfig.json +0 -4
- package/apps/daemon/package.json +0 -31
- package/apps/daemon/src/app.ts +0 -41
- package/apps/daemon/src/core/agent/archive.ts +0 -8
- package/apps/daemon/src/core/agent/delete.ts +0 -30
- package/apps/daemon/src/core/agent/resolve.ts +0 -31
- package/apps/daemon/src/core/agent/spawn.ts +0 -95
- package/apps/daemon/src/core/agent/unarchive.ts +0 -11
- package/apps/daemon/src/core/availableModels.ts +0 -58
- package/apps/daemon/src/core/db/client.ts +0 -113
- package/apps/daemon/src/core/db/migrate.ts +0 -41
- package/apps/daemon/src/core/db/migrations/0001_init.sql +0 -179
- package/apps/daemon/src/core/group/delete.ts +0 -21
- package/apps/daemon/src/core/group/register.ts +0 -68
- package/apps/daemon/src/core/index.ts +0 -71
- package/apps/daemon/src/core/paths.ts +0 -56
- package/apps/daemon/src/core/profile/create.ts +0 -78
- package/apps/daemon/src/core/profile/delete.ts +0 -26
- package/apps/daemon/src/core/profile/identity.ts +0 -70
- package/apps/daemon/src/core/profile/load.ts +0 -45
- package/apps/daemon/src/core/profile/seed.ts +0 -74
- package/apps/daemon/src/core/profile/templates.ts +0 -60
- package/apps/daemon/src/core/profile/update.ts +0 -57
- package/apps/daemon/src/core/profile/validate.ts +0 -9
- package/apps/daemon/src/core/repos/agents.ts +0 -202
- package/apps/daemon/src/core/repos/config.ts +0 -78
- package/apps/daemon/src/core/repos/groups.ts +0 -55
- package/apps/daemon/src/core/repos/messages.ts +0 -127
- package/apps/daemon/src/core/repos/profiles.ts +0 -83
- package/apps/daemon/src/core/repos/providerModels.ts +0 -58
- package/apps/daemon/src/core/repos/providerState.ts +0 -37
- package/apps/daemon/src/core/repos/secrets.ts +0 -145
- package/apps/daemon/src/core/repos/skillMeta.ts +0 -49
- package/apps/daemon/src/core/repos/triggers.ts +0 -101
- package/apps/daemon/src/core/repos/webTokens.ts +0 -87
- package/apps/daemon/src/core/secrets.ts +0 -65
- package/apps/daemon/src/core/services.ts +0 -264
- package/apps/daemon/src/core/skills/discover.ts +0 -28
- package/apps/daemon/src/core/skills/import.ts +0 -136
- package/apps/daemon/src/core/skills/parse.ts +0 -52
- package/apps/daemon/src/core/skills/resolve.ts +0 -50
- package/apps/daemon/src/index.ts +0 -45
- package/apps/daemon/src/lib/agent-cancel.ts +0 -48
- package/apps/daemon/src/lib/agent-id.ts +0 -13
- package/apps/daemon/src/lib/agent-turn.ts +0 -56
- package/apps/daemon/src/lib/api-key.ts +0 -56
- package/apps/daemon/src/lib/auth.ts +0 -41
- package/apps/daemon/src/lib/cron.ts +0 -93
- package/apps/daemon/src/lib/ctx.ts +0 -80
- package/apps/daemon/src/lib/messaging-host.ts +0 -34
- package/apps/daemon/src/lib/middleware-auth.ts +0 -52
- package/apps/daemon/src/lib/scheduler.ts +0 -294
- package/apps/daemon/src/routes/agents.ts +0 -772
- package/apps/daemon/src/routes/auth-login.ts +0 -193
- package/apps/daemon/src/routes/config.ts +0 -267
- package/apps/daemon/src/routes/groups.ts +0 -133
- package/apps/daemon/src/routes/messages.ts +0 -29
- package/apps/daemon/src/routes/misc.ts +0 -239
- package/apps/daemon/src/routes/profiles.ts +0 -197
- package/apps/daemon/src/routes/skills.ts +0 -123
- package/apps/daemon/src/routes/triggers.ts +0 -29
- package/apps/daemon/src/runtime/auth/openai-codex.ts +0 -121
- package/apps/daemon/src/runtime/auto-reply/heartbeat.ts +0 -31
- package/apps/daemon/src/runtime/index.ts +0 -77
- package/apps/daemon/src/runtime/memory/files.ts +0 -103
- package/apps/daemon/src/runtime/memory/qmd.ts +0 -152
- package/apps/daemon/src/runtime/memory/types.ts +0 -16
- package/apps/daemon/src/runtime/pi/events.ts +0 -173
- package/apps/daemon/src/runtime/pi/session.ts +0 -536
- package/apps/daemon/src/runtime/pi/tools.ts +0 -85
- package/apps/daemon/src/runtime/providers/catalog.ts +0 -145
- package/apps/daemon/src/runtime/providers/pi-adapter.ts +0 -272
- package/apps/daemon/src/runtime/providers/registry.ts +0 -374
- package/apps/daemon/src/runtime/providers/retry.ts +0 -176
- package/apps/daemon/src/runtime/providers/types.ts +0 -33
- package/apps/daemon/src/runtime/session/prompt.ts +0 -83
- package/apps/daemon/src/runtime/tools/bootstrap.ts +0 -22
- package/apps/daemon/src/runtime/tools/home.ts +0 -114
- package/apps/daemon/src/runtime/tools/memory.ts +0 -81
- package/apps/daemon/src/runtime/tools/messaging.ts +0 -127
- package/apps/daemon/src/runtime/tools/registry.ts +0 -29
- package/apps/daemon/src/runtime/tools/types.ts +0 -13
- package/apps/daemon/src/runtime/tools/web-extract.ts +0 -110
- package/apps/daemon/src/runtime/tools/web-ssrf.ts +0 -245
- package/apps/daemon/src/runtime/tools/web.ts +0 -273
- package/apps/daemon/src/runtime/worker/entry.ts +0 -221
- package/apps/daemon/src/runtime/worker/ipc-protocol.ts +0 -75
- package/apps/daemon/src/runtime/worker/spawn.ts +0 -249
- package/apps/daemon/test/core/agents.test.ts +0 -319
- package/apps/daemon/test/core/available-models.test.ts +0 -63
- package/apps/daemon/test/core/config.test.ts +0 -99
- package/apps/daemon/test/core/groups.test.ts +0 -63
- package/apps/daemon/test/core/helpers.ts +0 -50
- package/apps/daemon/test/core/identity.test.ts +0 -85
- package/apps/daemon/test/core/migrations.test.ts +0 -83
- package/apps/daemon/test/core/profiles.test.ts +0 -182
- package/apps/daemon/test/core/provider-models.test.ts +0 -57
- package/apps/daemon/test/core/provider-state.test.ts +0 -36
- package/apps/daemon/test/core/skill-meta.test.ts +0 -45
- package/apps/daemon/test/core/skills.test.ts +0 -271
- package/apps/daemon/test/core/triggers.test.ts +0 -182
- package/apps/daemon/test/core/web-tokens.test.ts +0 -79
- package/apps/daemon/test/cron.test.ts +0 -90
- package/apps/daemon/test/runtime/heartbeat.test.ts +0 -34
- package/apps/daemon/test/runtime/memory-qmd.test.ts +0 -97
- package/apps/daemon/test/runtime/memory.test.ts +0 -78
- package/apps/daemon/test/runtime/messaging.test.ts +0 -213
- package/apps/daemon/test/runtime/mock-server.ts +0 -65
- package/apps/daemon/test/runtime/openai-codex-auth.test.ts +0 -116
- package/apps/daemon/test/runtime/providers.test.ts +0 -306
- package/apps/daemon/test/runtime/retry.test.ts +0 -191
- package/apps/daemon/test/runtime/session-head.test.ts +0 -90
- package/apps/daemon/test/runtime/tools-home.test.ts +0 -102
- package/apps/daemon/test/runtime/tools-web.test.ts +0 -206
- package/apps/daemon/tsconfig.json +0 -4
- package/apps/mobile/README.md +0 -60
- package/apps/mobile/app/_layout.tsx +0 -58
- package/apps/mobile/app/agents/[id]/chat.tsx +0 -486
- package/apps/mobile/app/agents/[id]/index.tsx +0 -166
- package/apps/mobile/app/agents/index.tsx +0 -212
- package/apps/mobile/app/index.tsx +0 -21
- package/apps/mobile/app/pair.tsx +0 -226
- package/apps/mobile/app/settings.tsx +0 -419
- package/apps/mobile/app.json +0 -49
- 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 +0 -6
- package/apps/mobile/metro.config.js +0 -28
- package/apps/mobile/package.json +0 -44
- package/apps/mobile/src/auth.ts +0 -66
- package/apps/mobile/src/pair-url.ts +0 -48
- package/apps/mobile/src/theme-context.tsx +0 -88
- package/apps/mobile/src/theme.ts +0 -135
- package/apps/mobile/test/pair-url.test.ts +0 -46
- package/apps/mobile/tsconfig.json +0 -23
- package/apps/web/components.json +0 -25
- package/apps/web/package.json +0 -39
- package/apps/web/public/baziu.svg +0 -8
- package/apps/web/src/components/AgentTabs.tsx +0 -45
- package/apps/web/src/components/BaziuLogo.tsx +0 -21
- package/apps/web/src/components/ChatPane.tsx +0 -1033
- package/apps/web/src/components/ConfigTabs.tsx +0 -29
- package/apps/web/src/components/CopyButton.tsx +0 -68
- package/apps/web/src/components/CreateGroupDialog.tsx +0 -127
- package/apps/web/src/components/FieldRow.tsx +0 -94
- package/apps/web/src/components/Footer.tsx +0 -10
- package/apps/web/src/components/PawIcon.tsx +0 -15
- package/apps/web/src/components/Sidebar.tsx +0 -287
- package/apps/web/src/components/SpawnDialog.tsx +0 -129
- package/apps/web/src/components/ThemeToggle.tsx +0 -75
- package/apps/web/src/components/TopNav.tsx +0 -34
- package/apps/web/src/components/ui/button.tsx +0 -67
- package/apps/web/src/components/ui/card.tsx +0 -103
- package/apps/web/src/components/ui/checkbox.tsx +0 -31
- package/apps/web/src/components/ui/dialog.tsx +0 -168
- package/apps/web/src/components/ui/input.tsx +0 -19
- package/apps/web/src/components/ui/label.tsx +0 -22
- package/apps/web/src/components/ui/radio-group.tsx +0 -44
- package/apps/web/src/components/ui/select.tsx +0 -192
- package/apps/web/src/components/ui/separator.tsx +0 -26
- package/apps/web/src/components/ui/table.tsx +0 -116
- package/apps/web/src/components/ui/tabs.tsx +0 -88
- package/apps/web/src/components/ui/textarea.tsx +0 -18
- package/apps/web/src/lib/auth.ts +0 -50
- package/apps/web/src/lib/daemon-client.ts +0 -34
- package/apps/web/src/lib/md.ts +0 -45
- package/apps/web/src/lib/utils.ts +0 -6
- package/apps/web/src/lib/wire-constants.ts +0 -27
- package/apps/web/src/routeTree.gen.ts +0 -408
- package/apps/web/src/router.tsx +0 -20
- package/apps/web/src/routes/__root.tsx +0 -123
- package/apps/web/src/routes/agents/$id/inbox.tsx +0 -207
- package/apps/web/src/routes/agents/$id/index.tsx +0 -527
- package/apps/web/src/routes/agents/$id/triggers.tsx +0 -239
- package/apps/web/src/routes/agents/index.tsx +0 -265
- package/apps/web/src/routes/api/$.ts +0 -88
- package/apps/web/src/routes/config/index.tsx +0 -315
- package/apps/web/src/routes/config/services.tsx +0 -49
- package/apps/web/src/routes/config/tokens.tsx +0 -192
- package/apps/web/src/routes/groups/$id/index.tsx +0 -153
- package/apps/web/src/routes/groups/$id/memory.tsx +0 -321
- package/apps/web/src/routes/groups/index.tsx +0 -191
- package/apps/web/src/routes/index.tsx +0 -133
- package/apps/web/src/routes/login.tsx +0 -63
- package/apps/web/src/routes/profiles/$id.tsx +0 -549
- package/apps/web/src/routes/profiles/index.tsx +0 -458
- package/apps/web/src/routes/skills/index.tsx +0 -297
- package/apps/web/src/routes/welcome.tsx +0 -61
- package/apps/web/src/styles.css +0 -449
- package/apps/web/tsconfig.json +0 -25
- package/apps/web/vite.config.ts +0 -25
- package/biome.json +0 -23
- package/docs/agent-engine.md +0 -219
- package/docs/architecture.md +0 -627
- package/docs/backlog/README.md +0 -42
- package/docs/backlog/draft/BAZ-001-a2a-federation-spike.md +0 -125
- package/docs/openclaw-reference.md +0 -210
- package/packages/api-types/package.json +0 -11
- package/packages/api-types/src/entities.ts +0 -146
- package/packages/api-types/src/events.ts +0 -55
- package/packages/api-types/src/index.ts +0 -488
- package/packages/api-types/src/memory.ts +0 -15
- package/packages/client/package.json +0 -13
- package/packages/client/src/index.ts +0 -117
- package/pnpm-workspace.yaml +0 -3
- package/tsconfig.base.json +0 -23
- package/tsconfig.json +0 -11
- package/vitest.config.ts +0 -22
package/dist/worker.js
ADDED
|
@@ -0,0 +1,1830 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// ../daemon/src/runtime/worker/entry.ts
|
|
4
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
5
|
+
import { join as join14 } from "path";
|
|
6
|
+
|
|
7
|
+
// ../daemon/src/core/agent/delete.ts
|
|
8
|
+
import { existsSync, rmSync } from "fs";
|
|
9
|
+
|
|
10
|
+
// ../daemon/src/core/agent/spawn.ts
|
|
11
|
+
import { randomUUID } from "crypto";
|
|
12
|
+
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
13
|
+
import { join as join4 } from "path";
|
|
14
|
+
|
|
15
|
+
// ../daemon/src/core/profile/load.ts
|
|
16
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
17
|
+
import { join } from "path";
|
|
18
|
+
|
|
19
|
+
// ../daemon/src/core/profile/identity.ts
|
|
20
|
+
import { readFileSync } from "fs";
|
|
21
|
+
|
|
22
|
+
// ../daemon/src/core/group/register.ts
|
|
23
|
+
import { existsSync as existsSync3, mkdirSync, statSync, symlinkSync } from "fs";
|
|
24
|
+
import { resolve } from "path";
|
|
25
|
+
|
|
26
|
+
// ../daemon/src/core/profile/create.ts
|
|
27
|
+
import { mkdirSync as mkdirSync2, writeFileSync } from "fs";
|
|
28
|
+
import { join as join2 } from "path";
|
|
29
|
+
|
|
30
|
+
// ../daemon/src/core/skills/discover.ts
|
|
31
|
+
import { existsSync as existsSync4, readdirSync } from "fs";
|
|
32
|
+
import { join as join3 } from "path";
|
|
33
|
+
|
|
34
|
+
// ../daemon/src/core/db/client.ts
|
|
35
|
+
import { DatabaseSync } from "sqlite";
|
|
36
|
+
|
|
37
|
+
// ../daemon/src/core/db/migrate.ts
|
|
38
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
|
|
39
|
+
import { dirname, join as join5 } from "path";
|
|
40
|
+
import { fileURLToPath } from "url";
|
|
41
|
+
var migrationsDir = join5(dirname(fileURLToPath(import.meta.url)), "migrations");
|
|
42
|
+
|
|
43
|
+
// ../daemon/src/core/paths.ts
|
|
44
|
+
import { homedir } from "os";
|
|
45
|
+
import { join as join6 } from "path";
|
|
46
|
+
function resolvePaths(home) {
|
|
47
|
+
const root = home ?? process.env.BAZILION_HOME ?? join6(homedir(), ".bazilion");
|
|
48
|
+
return {
|
|
49
|
+
home: root,
|
|
50
|
+
db: join6(root, "bazilion.db"),
|
|
51
|
+
authFile: join6(root, "auth.json"),
|
|
52
|
+
profilesDir: join6(root, "profiles"),
|
|
53
|
+
agentsDir: join6(root, "agents"),
|
|
54
|
+
skillsDir: join6(root, "skills"),
|
|
55
|
+
groupsDir: join6(root, "groups"),
|
|
56
|
+
logsDir: join6(root, "logs"),
|
|
57
|
+
profileDir(id) {
|
|
58
|
+
return join6(root, "profiles", id);
|
|
59
|
+
},
|
|
60
|
+
agentDir(id) {
|
|
61
|
+
return join6(root, "agents", id);
|
|
62
|
+
},
|
|
63
|
+
skillDir(name) {
|
|
64
|
+
return join6(root, "skills", name);
|
|
65
|
+
},
|
|
66
|
+
groupDir(slug) {
|
|
67
|
+
return join6(root, "groups", slug);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ../daemon/src/core/profile/delete.ts
|
|
73
|
+
import { existsSync as existsSync5, rmSync as rmSync2 } from "fs";
|
|
74
|
+
|
|
75
|
+
// ../daemon/src/core/profile/update.ts
|
|
76
|
+
import { writeFileSync as writeFileSync3 } from "fs";
|
|
77
|
+
import { join as join7 } from "path";
|
|
78
|
+
|
|
79
|
+
// ../daemon/src/core/services.ts
|
|
80
|
+
var SERVICES = [
|
|
81
|
+
// --- LLM providers (configured via API keys / URLs) ---
|
|
82
|
+
{
|
|
83
|
+
id: "anthropic",
|
|
84
|
+
displayName: "Anthropic",
|
|
85
|
+
category: "provider",
|
|
86
|
+
hint: "Claude models \xB7 console.anthropic.com",
|
|
87
|
+
fields: [
|
|
88
|
+
{ envVar: "ANTHROPIC_API_KEY", kind: "secret", label: "API key", placeholder: "sk-ant-..." },
|
|
89
|
+
{
|
|
90
|
+
envVar: "ANTHROPIC_OAUTH_TOKEN",
|
|
91
|
+
kind: "secret",
|
|
92
|
+
label: "OAuth token (alternative to API key)",
|
|
93
|
+
description: "Takes precedence over ANTHROPIC_API_KEY when set"
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
id: "openai",
|
|
99
|
+
displayName: "OpenAI",
|
|
100
|
+
category: "provider",
|
|
101
|
+
hint: "GPT models \xB7 platform.openai.com",
|
|
102
|
+
fields: [{ envVar: "OPENAI_API_KEY", kind: "secret", label: "API key", placeholder: "sk-..." }]
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: "openai-codex",
|
|
106
|
+
displayName: "OpenAI ChatGPT (OAuth)",
|
|
107
|
+
category: "provider",
|
|
108
|
+
hint: "Use your ChatGPT Plus/Pro/Team account (same login as Codex CLI)",
|
|
109
|
+
// No form fields — credentials come from an OAuth flow. The /config page
|
|
110
|
+
// renders a Connect/Disconnect card using /api/auth/openai instead of the
|
|
111
|
+
// standard field inputs.
|
|
112
|
+
fields: []
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
id: "google",
|
|
116
|
+
displayName: "Google (Gemini)",
|
|
117
|
+
category: "provider",
|
|
118
|
+
hint: "Gemini models \xB7 ai.google.dev (free tier available)",
|
|
119
|
+
fields: [
|
|
120
|
+
{ envVar: "GEMINI_API_KEY", kind: "secret", label: "API key", placeholder: "AIza..." }
|
|
121
|
+
]
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
id: "google-vertex",
|
|
125
|
+
displayName: "Google Vertex AI",
|
|
126
|
+
category: "provider",
|
|
127
|
+
hint: "Authenticates via `gcloud auth application-default login`",
|
|
128
|
+
fields: [
|
|
129
|
+
{
|
|
130
|
+
envVar: "GOOGLE_CLOUD_PROJECT",
|
|
131
|
+
kind: "config",
|
|
132
|
+
label: "GCP project ID",
|
|
133
|
+
placeholder: "my-project-123456"
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
envVar: "GOOGLE_CLOUD_LOCATION",
|
|
137
|
+
kind: "config",
|
|
138
|
+
label: "GCP region",
|
|
139
|
+
placeholder: "us-central1"
|
|
140
|
+
}
|
|
141
|
+
]
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
id: "azure-openai",
|
|
145
|
+
displayName: "Azure OpenAI",
|
|
146
|
+
category: "provider",
|
|
147
|
+
fields: [{ envVar: "AZURE_OPENAI_API_KEY", kind: "secret", label: "API key" }]
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
id: "bedrock",
|
|
151
|
+
displayName: "Amazon Bedrock",
|
|
152
|
+
category: "provider",
|
|
153
|
+
hint: "Authenticates via AWS SDK env (AWS_PROFILE or AWS_ACCESS_KEY_ID/SECRET)",
|
|
154
|
+
fields: []
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
id: "mistral",
|
|
158
|
+
displayName: "Mistral",
|
|
159
|
+
category: "provider",
|
|
160
|
+
hint: "mistral.ai",
|
|
161
|
+
fields: [{ envVar: "MISTRAL_API_KEY", kind: "secret", label: "API key" }]
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
id: "groq",
|
|
165
|
+
displayName: "Groq",
|
|
166
|
+
category: "provider",
|
|
167
|
+
hint: "Fast inference \xB7 groq.com",
|
|
168
|
+
fields: [{ envVar: "GROQ_API_KEY", kind: "secret", label: "API key" }]
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
id: "cerebras",
|
|
172
|
+
displayName: "Cerebras",
|
|
173
|
+
category: "provider",
|
|
174
|
+
fields: [{ envVar: "CEREBRAS_API_KEY", kind: "secret", label: "API key" }]
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
id: "xai",
|
|
178
|
+
displayName: "xAI",
|
|
179
|
+
category: "provider",
|
|
180
|
+
hint: "Grok \xB7 x.ai",
|
|
181
|
+
fields: [{ envVar: "XAI_API_KEY", kind: "secret", label: "API key" }]
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
id: "zai",
|
|
185
|
+
displayName: "zAI",
|
|
186
|
+
category: "provider",
|
|
187
|
+
fields: [{ envVar: "ZAI_API_KEY", kind: "secret", label: "API key" }]
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
id: "huggingface",
|
|
191
|
+
displayName: "Hugging Face",
|
|
192
|
+
category: "provider",
|
|
193
|
+
hint: "Inference endpoints \xB7 huggingface.co",
|
|
194
|
+
fields: [{ envVar: "HF_TOKEN", kind: "secret", label: "Access token", placeholder: "hf_..." }]
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
id: "openrouter",
|
|
198
|
+
displayName: "OpenRouter",
|
|
199
|
+
category: "provider",
|
|
200
|
+
hint: "Proxy for 200+ models \xB7 openrouter.ai",
|
|
201
|
+
fields: [
|
|
202
|
+
{ envVar: "OPENROUTER_API_KEY", kind: "secret", label: "API key", placeholder: "sk-or-..." }
|
|
203
|
+
]
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
id: "vercel-ai-gateway",
|
|
207
|
+
displayName: "Vercel AI Gateway",
|
|
208
|
+
category: "provider",
|
|
209
|
+
fields: [
|
|
210
|
+
{ envVar: "AI_GATEWAY_API_KEY", kind: "secret", label: "API key" },
|
|
211
|
+
{
|
|
212
|
+
envVar: "AI_GATEWAY_BASE_URL",
|
|
213
|
+
kind: "config",
|
|
214
|
+
label: "Base URL (optional)",
|
|
215
|
+
placeholder: "https://ai-gateway.vercel.sh/v1"
|
|
216
|
+
}
|
|
217
|
+
]
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
id: "lmstudio",
|
|
221
|
+
displayName: "LM Studio",
|
|
222
|
+
category: "provider",
|
|
223
|
+
hint: "Local inference \xB7 lmstudio.ai",
|
|
224
|
+
fields: [
|
|
225
|
+
{
|
|
226
|
+
envVar: "LMSTUDIO_URL",
|
|
227
|
+
kind: "config",
|
|
228
|
+
label: "Endpoint URL",
|
|
229
|
+
placeholder: "http://127.0.0.1:1234/v1"
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
envVar: "LMSTUDIO_API_KEY",
|
|
233
|
+
kind: "secret",
|
|
234
|
+
label: "API key (rarely needed)"
|
|
235
|
+
}
|
|
236
|
+
]
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
id: "ollama",
|
|
240
|
+
displayName: "Ollama",
|
|
241
|
+
category: "provider",
|
|
242
|
+
hint: "Local inference \xB7 ollama.com",
|
|
243
|
+
fields: [
|
|
244
|
+
{
|
|
245
|
+
envVar: "OLLAMA_URL",
|
|
246
|
+
kind: "config",
|
|
247
|
+
label: "Endpoint URL",
|
|
248
|
+
placeholder: "http://127.0.0.1:11434/v1"
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
envVar: "OLLAMA_API_KEY",
|
|
252
|
+
kind: "secret",
|
|
253
|
+
label: "API key (rarely needed)"
|
|
254
|
+
}
|
|
255
|
+
]
|
|
256
|
+
},
|
|
257
|
+
// --- Ancillary services (web search, etc) ---
|
|
258
|
+
{
|
|
259
|
+
id: "brave-search",
|
|
260
|
+
displayName: "Brave Search",
|
|
261
|
+
category: "service",
|
|
262
|
+
hint: "Web search tool \xB7 free tier at brave.com/search/api/",
|
|
263
|
+
fields: [{ envVar: "BRAVE_API_KEY", kind: "secret", label: "API key", placeholder: "BSA..." }]
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
id: "searxng",
|
|
267
|
+
displayName: "SearXNG",
|
|
268
|
+
category: "service",
|
|
269
|
+
hint: "Self-hosted meta-search engine \xB7 searxng.org",
|
|
270
|
+
fields: [
|
|
271
|
+
{
|
|
272
|
+
envVar: "SEARXNG_URL",
|
|
273
|
+
kind: "config",
|
|
274
|
+
label: "Instance URL",
|
|
275
|
+
placeholder: "https://searxng.example.com"
|
|
276
|
+
}
|
|
277
|
+
]
|
|
278
|
+
}
|
|
279
|
+
];
|
|
280
|
+
var FIELD_INDEX = (() => {
|
|
281
|
+
const m = /* @__PURE__ */ new Map();
|
|
282
|
+
for (const service of SERVICES) {
|
|
283
|
+
for (const field of service.fields) {
|
|
284
|
+
m.set(field.envVar, { service, field });
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return m;
|
|
288
|
+
})();
|
|
289
|
+
|
|
290
|
+
// ../daemon/src/core/repos/config.ts
|
|
291
|
+
var CONFIG_KEYS = SERVICES.flatMap(
|
|
292
|
+
(s) => s.fields.filter((f) => f.kind === "config").map((f) => f.envVar)
|
|
293
|
+
);
|
|
294
|
+
var CONFIG_KEY_SET = new Set(CONFIG_KEYS);
|
|
295
|
+
|
|
296
|
+
// ../daemon/src/core/repos/messages.ts
|
|
297
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
298
|
+
|
|
299
|
+
// ../daemon/src/core/repos/secrets.ts
|
|
300
|
+
import { createCipheriv, createDecipheriv, pbkdf2Sync, randomBytes } from "crypto";
|
|
301
|
+
|
|
302
|
+
// ../daemon/src/core/repos/triggers.ts
|
|
303
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
304
|
+
|
|
305
|
+
// ../daemon/src/core/repos/webTokens.ts
|
|
306
|
+
import { createHash, randomBytes as randomBytes2, randomUUID as randomUUID4 } from "crypto";
|
|
307
|
+
|
|
308
|
+
// ../daemon/src/core/secrets.ts
|
|
309
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
|
|
310
|
+
|
|
311
|
+
// ../daemon/src/core/skills/import.ts
|
|
312
|
+
import { cpSync, existsSync as existsSync7, mkdtempSync, readdirSync as readdirSync3, rmSync as rmSync3, statSync as statSync2 } from "fs";
|
|
313
|
+
import { tmpdir } from "os";
|
|
314
|
+
import { basename, join as join8, resolve as resolve2, sep } from "path";
|
|
315
|
+
import AdmZip from "adm-zip";
|
|
316
|
+
|
|
317
|
+
// ../daemon/src/core/skills/parse.ts
|
|
318
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
319
|
+
import { parse as parseYaml } from "yaml";
|
|
320
|
+
|
|
321
|
+
// ../daemon/src/runtime/memory/qmd.ts
|
|
322
|
+
import {
|
|
323
|
+
existsSync as existsSync8,
|
|
324
|
+
mkdirSync as mkdirSync4,
|
|
325
|
+
readdirSync as readdirSync4,
|
|
326
|
+
readFileSync as readFileSync6,
|
|
327
|
+
rmSync as rmSync4,
|
|
328
|
+
statSync as statSync3,
|
|
329
|
+
writeFileSync as writeFileSync4
|
|
330
|
+
} from "fs";
|
|
331
|
+
import { dirname as dirname2, join as join9 } from "path";
|
|
332
|
+
import { createStore, extractSnippet } from "@tobilu/qmd";
|
|
333
|
+
var INDEX_FILENAME = ".qmd-index.sqlite";
|
|
334
|
+
var COLLECTION_NAME = "memory";
|
|
335
|
+
var PATTERN = "**/*.md";
|
|
336
|
+
var storeCache = /* @__PURE__ */ new Map();
|
|
337
|
+
function getStore(dir) {
|
|
338
|
+
let p = storeCache.get(dir);
|
|
339
|
+
if (!p) {
|
|
340
|
+
p = createStore({
|
|
341
|
+
dbPath: join9(dir, INDEX_FILENAME),
|
|
342
|
+
config: {
|
|
343
|
+
collections: {
|
|
344
|
+
[COLLECTION_NAME]: { path: dir, pattern: PATTERN }
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
storeCache.set(dir, p);
|
|
349
|
+
}
|
|
350
|
+
return p;
|
|
351
|
+
}
|
|
352
|
+
function safeKey(root, key) {
|
|
353
|
+
if (key.includes("..") || key.startsWith("/") || key.includes("\0")) {
|
|
354
|
+
throw new Error(`unsafe memory key: ${key}`);
|
|
355
|
+
}
|
|
356
|
+
return join9(root, key);
|
|
357
|
+
}
|
|
358
|
+
function walkMd(dir, prefix, out) {
|
|
359
|
+
if (!existsSync8(dir)) return;
|
|
360
|
+
for (const e of readdirSync4(dir, { withFileTypes: true })) {
|
|
361
|
+
if (e.name.startsWith(".")) continue;
|
|
362
|
+
const full = join9(dir, e.name);
|
|
363
|
+
const key = prefix ? `${prefix}/${e.name}` : e.name;
|
|
364
|
+
if (e.isDirectory()) {
|
|
365
|
+
walkMd(full, key, out);
|
|
366
|
+
} else if (e.isFile() && e.name.endsWith(".md")) {
|
|
367
|
+
const stats = statSync3(full);
|
|
368
|
+
out.push({
|
|
369
|
+
key,
|
|
370
|
+
content: readFileSync6(full, "utf8"),
|
|
371
|
+
updatedAt: stats.mtimeMs
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
function qmdBackend(root) {
|
|
377
|
+
return {
|
|
378
|
+
async init() {
|
|
379
|
+
mkdirSync4(root, { recursive: true });
|
|
380
|
+
const store = await getStore(root);
|
|
381
|
+
await store.update();
|
|
382
|
+
},
|
|
383
|
+
async read(key) {
|
|
384
|
+
const path = safeKey(root, key);
|
|
385
|
+
if (!existsSync8(path)) {
|
|
386
|
+
throw new Error(`memory entry not found: ${key}`);
|
|
387
|
+
}
|
|
388
|
+
const stats = statSync3(path);
|
|
389
|
+
return {
|
|
390
|
+
key,
|
|
391
|
+
content: readFileSync6(path, "utf8"),
|
|
392
|
+
updatedAt: stats.mtimeMs
|
|
393
|
+
};
|
|
394
|
+
},
|
|
395
|
+
async write(key, content) {
|
|
396
|
+
const path = safeKey(root, key);
|
|
397
|
+
mkdirSync4(dirname2(path), { recursive: true });
|
|
398
|
+
writeFileSync4(path, content);
|
|
399
|
+
const stats = statSync3(path);
|
|
400
|
+
const store = await getStore(root);
|
|
401
|
+
await store.update();
|
|
402
|
+
return { key, content, updatedAt: stats.mtimeMs };
|
|
403
|
+
},
|
|
404
|
+
async search(query, opts) {
|
|
405
|
+
const limit = opts?.limit ?? 10;
|
|
406
|
+
const store = await getStore(root);
|
|
407
|
+
const results = await store.searchLex(query, {
|
|
408
|
+
limit,
|
|
409
|
+
collection: COLLECTION_NAME
|
|
410
|
+
});
|
|
411
|
+
const hits = [];
|
|
412
|
+
for (const r of results) {
|
|
413
|
+
const prefix = `${COLLECTION_NAME}/`;
|
|
414
|
+
const key = r.displayPath.startsWith(prefix) ? r.displayPath.slice(prefix.length) : r.displayPath;
|
|
415
|
+
let content = r.body ?? "";
|
|
416
|
+
if (!content) {
|
|
417
|
+
try {
|
|
418
|
+
content = readFileSync6(join9(root, key), "utf8");
|
|
419
|
+
} catch {
|
|
420
|
+
content = "";
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
const snippet = extractSnippet(content, query).snippet;
|
|
424
|
+
hits.push({ key, snippet, score: r.score });
|
|
425
|
+
}
|
|
426
|
+
return hits;
|
|
427
|
+
},
|
|
428
|
+
async list() {
|
|
429
|
+
const out = [];
|
|
430
|
+
walkMd(root, "", out);
|
|
431
|
+
return out.sort((a, b) => a.key.localeCompare(b.key));
|
|
432
|
+
},
|
|
433
|
+
async remove(key) {
|
|
434
|
+
const path = safeKey(root, key);
|
|
435
|
+
if (existsSync8(path)) rmSync4(path);
|
|
436
|
+
const store = await getStore(root);
|
|
437
|
+
await store.update();
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// ../daemon/src/runtime/pi/events.ts
|
|
443
|
+
function translatePiEvent(e) {
|
|
444
|
+
switch (e.type) {
|
|
445
|
+
case "message_start": {
|
|
446
|
+
if (e.message.role === "user") {
|
|
447
|
+
return [{ type: "user_message", text: stringifyContent(e.message.content) }];
|
|
448
|
+
}
|
|
449
|
+
return [];
|
|
450
|
+
}
|
|
451
|
+
case "message_update": {
|
|
452
|
+
const inner = e.assistantMessageEvent;
|
|
453
|
+
if (inner.type === "text_delta") {
|
|
454
|
+
return [{ type: "assistant_delta", delta: inner.delta }];
|
|
455
|
+
}
|
|
456
|
+
return [];
|
|
457
|
+
}
|
|
458
|
+
case "message_end": {
|
|
459
|
+
const m = e.message;
|
|
460
|
+
if (m.role !== "assistant") return [];
|
|
461
|
+
const out = [];
|
|
462
|
+
const text = extractAssistantText(m);
|
|
463
|
+
if (text) out.push({ type: "assistant_message", text });
|
|
464
|
+
for (const block of m.content ?? []) {
|
|
465
|
+
if (block.type === "toolCall") {
|
|
466
|
+
out.push({
|
|
467
|
+
type: "tool_call",
|
|
468
|
+
id: block.id,
|
|
469
|
+
name: block.name,
|
|
470
|
+
arguments: JSON.stringify(block.arguments ?? {})
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
const stopReason = m.stopReason;
|
|
475
|
+
if (stopReason === "aborted" || stopReason === "error") {
|
|
476
|
+
const errText = m.errorMessage ?? stopReason;
|
|
477
|
+
out.push({ type: "error", error: errText });
|
|
478
|
+
}
|
|
479
|
+
return out;
|
|
480
|
+
}
|
|
481
|
+
case "tool_execution_end": {
|
|
482
|
+
const text = extractToolResultText(e.result);
|
|
483
|
+
if (e.isError) {
|
|
484
|
+
return [{ type: "tool_error", id: e.toolCallId, name: e.toolName, error: text }];
|
|
485
|
+
}
|
|
486
|
+
return [{ type: "tool_result", id: e.toolCallId, name: e.toolName, result: text }];
|
|
487
|
+
}
|
|
488
|
+
default:
|
|
489
|
+
return [];
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
function extractAssistantText(m) {
|
|
493
|
+
let out = "";
|
|
494
|
+
for (const block of m.content ?? []) {
|
|
495
|
+
if (block.type === "text") out += block.text;
|
|
496
|
+
}
|
|
497
|
+
return out;
|
|
498
|
+
}
|
|
499
|
+
function extractAssistantToolCalls(m) {
|
|
500
|
+
const out = [];
|
|
501
|
+
for (const block of m.content ?? []) {
|
|
502
|
+
if (block.type === "toolCall") {
|
|
503
|
+
out.push({ id: block.id, name: block.name, arguments: JSON.stringify(block.arguments ?? {}) });
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return out;
|
|
507
|
+
}
|
|
508
|
+
function extractToolResultText(result) {
|
|
509
|
+
const r = result;
|
|
510
|
+
if (!r?.content) return "";
|
|
511
|
+
let out = "";
|
|
512
|
+
for (const block of r.content) {
|
|
513
|
+
if (block.type === "text") out += block.text;
|
|
514
|
+
}
|
|
515
|
+
return out;
|
|
516
|
+
}
|
|
517
|
+
function stringifyContent(content) {
|
|
518
|
+
if (typeof content === "string") return content;
|
|
519
|
+
if (!Array.isArray(content)) return "";
|
|
520
|
+
let out = "";
|
|
521
|
+
for (const block of content) {
|
|
522
|
+
if (block.type === "text" && typeof block.text === "string") out += block.text;
|
|
523
|
+
}
|
|
524
|
+
return out;
|
|
525
|
+
}
|
|
526
|
+
function piMessagesToProviderView(messages) {
|
|
527
|
+
const out = [];
|
|
528
|
+
for (const m of messages) {
|
|
529
|
+
switch (m.role) {
|
|
530
|
+
case "user": {
|
|
531
|
+
out.push({ role: "user", content: stringifyContent(m.content) });
|
|
532
|
+
break;
|
|
533
|
+
}
|
|
534
|
+
case "assistant": {
|
|
535
|
+
const am = m;
|
|
536
|
+
const text = extractAssistantText(am);
|
|
537
|
+
const toolCalls = extractAssistantToolCalls(am);
|
|
538
|
+
const msg = { role: "assistant", content: text };
|
|
539
|
+
if (toolCalls.length > 0) msg.toolCalls = toolCalls;
|
|
540
|
+
out.push(msg);
|
|
541
|
+
break;
|
|
542
|
+
}
|
|
543
|
+
case "toolResult": {
|
|
544
|
+
const tr = m;
|
|
545
|
+
out.push({
|
|
546
|
+
role: "tool",
|
|
547
|
+
content: stringifyContent(tr.content),
|
|
548
|
+
toolCallId: tr.toolCallId,
|
|
549
|
+
toolName: tr.toolName
|
|
550
|
+
});
|
|
551
|
+
break;
|
|
552
|
+
}
|
|
553
|
+
default:
|
|
554
|
+
break;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return out;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// ../daemon/src/runtime/pi/session.ts
|
|
561
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync5, readdirSync as readdirSync6, statSync as statSync5 } from "fs";
|
|
562
|
+
import { basename as basename2, join as join13 } from "path";
|
|
563
|
+
import {
|
|
564
|
+
AuthStorage,
|
|
565
|
+
createAgentSession,
|
|
566
|
+
createExtensionRuntime,
|
|
567
|
+
ModelRegistry,
|
|
568
|
+
SessionManager,
|
|
569
|
+
SettingsManager
|
|
570
|
+
} from "@mariozechner/pi-coding-agent";
|
|
571
|
+
|
|
572
|
+
// ../daemon/src/runtime/providers/pi-adapter.ts
|
|
573
|
+
import {
|
|
574
|
+
getModel,
|
|
575
|
+
streamSimple,
|
|
576
|
+
Type
|
|
577
|
+
} from "@mariozechner/pi-ai";
|
|
578
|
+
function defaultBaseUrlFor(providerName) {
|
|
579
|
+
switch (providerName) {
|
|
580
|
+
case "lmstudio":
|
|
581
|
+
return process.env.LMSTUDIO_URL ?? "http://127.0.0.1:1234/v1";
|
|
582
|
+
case "ollama":
|
|
583
|
+
return process.env.OLLAMA_URL ?? "http://127.0.0.1:11434/v1";
|
|
584
|
+
default:
|
|
585
|
+
return "";
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
function buildModelLiteral(cfg, modelId) {
|
|
589
|
+
return {
|
|
590
|
+
id: modelId,
|
|
591
|
+
name: modelId,
|
|
592
|
+
api: cfg.fallbackApi,
|
|
593
|
+
provider: cfg.providerName,
|
|
594
|
+
baseUrl: cfg.baseUrl ?? defaultBaseUrlFor(cfg.providerName),
|
|
595
|
+
reasoning: false,
|
|
596
|
+
input: ["text"],
|
|
597
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
598
|
+
contextWindow: 32768,
|
|
599
|
+
maxTokens: 4096
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
function resolveModel(cfg, modelId) {
|
|
603
|
+
const lookupName = cfg.piProviderName ?? cfg.providerName;
|
|
604
|
+
try {
|
|
605
|
+
const known = getModel(
|
|
606
|
+
lookupName,
|
|
607
|
+
modelId
|
|
608
|
+
);
|
|
609
|
+
if (known && typeof known === "object" && "api" in known) {
|
|
610
|
+
if (cfg.baseUrl) return { ...known, baseUrl: cfg.baseUrl };
|
|
611
|
+
return known;
|
|
612
|
+
}
|
|
613
|
+
} catch {
|
|
614
|
+
}
|
|
615
|
+
return buildModelLiteral(cfg, modelId);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// ../daemon/src/runtime/auth/openai-codex.ts
|
|
619
|
+
import { loginOpenAICodex, refreshOpenAICodexToken } from "@mariozechner/pi-ai/oauth";
|
|
620
|
+
|
|
621
|
+
// ../daemon/src/runtime/session/prompt.ts
|
|
622
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
|
|
623
|
+
import { join as join10 } from "path";
|
|
624
|
+
var CONTEXT_FILE_ORDER = [
|
|
625
|
+
"AGENTS.md",
|
|
626
|
+
"SOUL.md",
|
|
627
|
+
"TOOLS.md",
|
|
628
|
+
"IDENTITY.md",
|
|
629
|
+
"HEARTBEAT.md",
|
|
630
|
+
"BOOTSTRAP.md"
|
|
631
|
+
];
|
|
632
|
+
function buildSystemPrompt(agent) {
|
|
633
|
+
const parts = [];
|
|
634
|
+
const contextBlocks = [];
|
|
635
|
+
let bootstrapPresent = false;
|
|
636
|
+
for (const file of CONTEXT_FILE_ORDER) {
|
|
637
|
+
const path = join10(agent.agent.dir, file);
|
|
638
|
+
if (!existsSync9(path)) continue;
|
|
639
|
+
const content = readFileSync7(path, "utf8").trimEnd();
|
|
640
|
+
if (!content) continue;
|
|
641
|
+
contextBlocks.push(`## ${file}
|
|
642
|
+
|
|
643
|
+
${content}`);
|
|
644
|
+
if (file === "BOOTSTRAP.md") bootstrapPresent = true;
|
|
645
|
+
}
|
|
646
|
+
if (contextBlocks.length > 0) {
|
|
647
|
+
parts.push(`# Project Context
|
|
648
|
+
|
|
649
|
+
${contextBlocks.join("\n\n")}`);
|
|
650
|
+
}
|
|
651
|
+
if (bootstrapPresent) {
|
|
652
|
+
parts.push(
|
|
653
|
+
"NOTE: This is your first session. After you have completed the bootstrap conversation, call the `bootstrap_done` tool to delete BOOTSTRAP.md."
|
|
654
|
+
);
|
|
655
|
+
}
|
|
656
|
+
parts.push(
|
|
657
|
+
[
|
|
658
|
+
"# Agent Home",
|
|
659
|
+
"",
|
|
660
|
+
"Your private home holds who you are \u2014 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.",
|
|
661
|
+
"",
|
|
662
|
+
"- To change who you are (name, vibe, personality, how you behave): use `home_write`.",
|
|
663
|
+
"- To inspect exact wording of your own files: use `home_read` or `home_list`.",
|
|
664
|
+
"- To remember facts the user told you or things you learned: use `memory_write` \u2014 NOT `home_write`.",
|
|
665
|
+
"- To produce work output (code, docs, artefacts): use `write` / `edit` \u2014 those land in your workspace, not your home."
|
|
666
|
+
].join("\n")
|
|
667
|
+
);
|
|
668
|
+
if (agent.skills.length > 0) {
|
|
669
|
+
parts.push(
|
|
670
|
+
`# Available Skills
|
|
671
|
+
|
|
672
|
+
You have access to the following skills: ${agent.skills.join(", ")}.`
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
const groupLines = [
|
|
676
|
+
"# Group",
|
|
677
|
+
"",
|
|
678
|
+
`- ${agent.group.id} (${agent.group.name}): ${agent.group.path}`,
|
|
679
|
+
"",
|
|
680
|
+
"Your group is where work product lives \u2014 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 \u2014 those live in your home and are reached via `home_write` / `home_read`."
|
|
681
|
+
];
|
|
682
|
+
parts.push(groupLines.join("\n"));
|
|
683
|
+
if (agent.group.userMd.trim()) {
|
|
684
|
+
parts.push(
|
|
685
|
+
`# About the User
|
|
686
|
+
|
|
687
|
+
Read-only context about the human you're working with in this group. You cannot edit this \u2014 if it's wrong, say so and they will update it.
|
|
688
|
+
|
|
689
|
+
${agent.group.userMd.trim()}`
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
parts.push(
|
|
693
|
+
"# 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 \u2014 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."
|
|
694
|
+
);
|
|
695
|
+
return parts.join("\n\n---\n\n");
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// ../daemon/src/runtime/pi/tools.ts
|
|
699
|
+
import { Type as Type2 } from "typebox";
|
|
700
|
+
|
|
701
|
+
// ../daemon/src/runtime/tools/bootstrap.ts
|
|
702
|
+
import { existsSync as existsSync10, rmSync as rmSync5 } from "fs";
|
|
703
|
+
import { join as join11 } from "path";
|
|
704
|
+
function bootstrapTool(agentDir) {
|
|
705
|
+
return {
|
|
706
|
+
def: {
|
|
707
|
+
name: "bootstrap_done",
|
|
708
|
+
description: "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.",
|
|
709
|
+
parameters: { type: "object", properties: {} }
|
|
710
|
+
},
|
|
711
|
+
async invoke() {
|
|
712
|
+
const path = join11(agentDir, "BOOTSTRAP.md");
|
|
713
|
+
if (existsSync10(path)) {
|
|
714
|
+
rmSync5(path);
|
|
715
|
+
return "BOOTSTRAP.md removed. Bootstrap is complete.";
|
|
716
|
+
}
|
|
717
|
+
return "BOOTSTRAP.md was already removed.";
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// ../daemon/src/runtime/tools/home.ts
|
|
723
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync8, statSync as statSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
724
|
+
import { join as join12 } from "path";
|
|
725
|
+
var HOME_FILES_READABLE = [
|
|
726
|
+
"IDENTITY.md",
|
|
727
|
+
"SOUL.md",
|
|
728
|
+
"BOOTSTRAP.md",
|
|
729
|
+
"AGENTS.md",
|
|
730
|
+
"TOOLS.md",
|
|
731
|
+
"HEARTBEAT.md"
|
|
732
|
+
];
|
|
733
|
+
var HOME_FILES_WRITABLE = [
|
|
734
|
+
"IDENTITY.md",
|
|
735
|
+
"SOUL.md",
|
|
736
|
+
"AGENTS.md",
|
|
737
|
+
"TOOLS.md",
|
|
738
|
+
"HEARTBEAT.md"
|
|
739
|
+
];
|
|
740
|
+
function homeTools(agentDir) {
|
|
741
|
+
return [
|
|
742
|
+
{
|
|
743
|
+
def: {
|
|
744
|
+
name: "home_read",
|
|
745
|
+
description: "Read one of your own home files \u2014 your identity, soul, or behaviour rules. These files are private to you and are also injected into your system prompt; read them when you need to quote exact wording or check current state.",
|
|
746
|
+
parameters: {
|
|
747
|
+
type: "object",
|
|
748
|
+
properties: {
|
|
749
|
+
file: { type: "string", enum: [...HOME_FILES_READABLE] }
|
|
750
|
+
},
|
|
751
|
+
required: ["file"]
|
|
752
|
+
}
|
|
753
|
+
},
|
|
754
|
+
async invoke(args) {
|
|
755
|
+
const file = String(args.file ?? "");
|
|
756
|
+
if (!HOME_FILES_READABLE.includes(file)) {
|
|
757
|
+
throw new Error(
|
|
758
|
+
`home_read: "file" must be one of ${HOME_FILES_READABLE.join(", ")}; got "${file}"`
|
|
759
|
+
);
|
|
760
|
+
}
|
|
761
|
+
const path = join12(agentDir, file);
|
|
762
|
+
try {
|
|
763
|
+
return readFileSync8(path, "utf8");
|
|
764
|
+
} catch (err) {
|
|
765
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
766
|
+
throw new Error(`home_read: could not read ${file}: ${msg}`);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
},
|
|
770
|
+
{
|
|
771
|
+
def: {
|
|
772
|
+
name: "home_write",
|
|
773
|
+
description: "Overwrite one of your own home files. Use this to update your name, personality, or persistent self-definition. Do NOT use this for work output (use `write` / `edit` \u2014 those land in your group's shared directory) or for facts you want to remember (use `memory_write`). BOOTSTRAP.md is not writable here; call `bootstrap_done` to retire it.",
|
|
774
|
+
parameters: {
|
|
775
|
+
type: "object",
|
|
776
|
+
properties: {
|
|
777
|
+
file: { type: "string", enum: [...HOME_FILES_WRITABLE] },
|
|
778
|
+
content: { type: "string", description: "new full file content" }
|
|
779
|
+
},
|
|
780
|
+
required: ["file", "content"]
|
|
781
|
+
}
|
|
782
|
+
},
|
|
783
|
+
async invoke(args) {
|
|
784
|
+
const file = String(args.file ?? "");
|
|
785
|
+
if (!HOME_FILES_WRITABLE.includes(file)) {
|
|
786
|
+
throw new Error(
|
|
787
|
+
`home_write: "file" must be one of ${HOME_FILES_WRITABLE.join(", ")}; got "${file}"`
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
const content = typeof args.content === "string" ? args.content : "";
|
|
791
|
+
const path = join12(agentDir, file);
|
|
792
|
+
writeFileSync5(path, content, "utf8");
|
|
793
|
+
return `wrote ${file} (${Buffer.byteLength(content, "utf8")} bytes)`;
|
|
794
|
+
}
|
|
795
|
+
},
|
|
796
|
+
{
|
|
797
|
+
def: {
|
|
798
|
+
name: "home_list",
|
|
799
|
+
description: "List your home files with their sizes.",
|
|
800
|
+
parameters: { type: "object", properties: {} }
|
|
801
|
+
},
|
|
802
|
+
async invoke() {
|
|
803
|
+
const entries = [];
|
|
804
|
+
for (const file of HOME_FILES_READABLE) {
|
|
805
|
+
const path = join12(agentDir, file);
|
|
806
|
+
try {
|
|
807
|
+
const s = statSync4(path);
|
|
808
|
+
entries.push(`${file} (${s.size}b)`);
|
|
809
|
+
} catch {
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
if (entries.length === 0) {
|
|
813
|
+
const dirEntries = (() => {
|
|
814
|
+
try {
|
|
815
|
+
return readdirSync5(agentDir);
|
|
816
|
+
} catch {
|
|
817
|
+
return [];
|
|
818
|
+
}
|
|
819
|
+
})();
|
|
820
|
+
return `(no home files found; agent dir contains: ${dirEntries.join(", ") || "nothing"})`;
|
|
821
|
+
}
|
|
822
|
+
return entries.join("\n");
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
];
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// ../daemon/src/runtime/tools/memory.ts
|
|
829
|
+
function memoryTools(memory) {
|
|
830
|
+
return [
|
|
831
|
+
{
|
|
832
|
+
def: {
|
|
833
|
+
name: "memory_write",
|
|
834
|
+
description: 'Write or update a memory note in the GROUP-SHARED memory. All agents in this group can read what you write. Use it for project knowledge, codebase notes, decisions, and findings \u2014 anything other agents in the group should benefit from. For personal notes about yourself (preferences, persona), use `home_write` on IDENTITY.md instead. Key is a path-like string with a markdown extension, e.g. "auth-flow.md" or "people/alice.md".',
|
|
835
|
+
parameters: {
|
|
836
|
+
type: "object",
|
|
837
|
+
properties: {
|
|
838
|
+
key: { type: "string", description: "memory key (relative path)" },
|
|
839
|
+
content: { type: "string", description: "note content (plain text or markdown)" }
|
|
840
|
+
},
|
|
841
|
+
required: ["key", "content"]
|
|
842
|
+
}
|
|
843
|
+
},
|
|
844
|
+
async invoke(args) {
|
|
845
|
+
const key = String(args.key ?? "");
|
|
846
|
+
const content = String(args.content ?? "");
|
|
847
|
+
if (!key) throw new Error('memory_write: "key" is required');
|
|
848
|
+
const entry = await memory.write(key, content);
|
|
849
|
+
return `wrote ${entry.key} (${entry.content.length} bytes)`;
|
|
850
|
+
}
|
|
851
|
+
},
|
|
852
|
+
{
|
|
853
|
+
def: {
|
|
854
|
+
name: "memory_search",
|
|
855
|
+
description: "Search the group-shared memory by substring. Returns matching entry keys with short snippets around the match.",
|
|
856
|
+
parameters: {
|
|
857
|
+
type: "object",
|
|
858
|
+
properties: {
|
|
859
|
+
query: { type: "string" },
|
|
860
|
+
limit: { type: "number", description: "max results (default 10)" }
|
|
861
|
+
},
|
|
862
|
+
required: ["query"]
|
|
863
|
+
}
|
|
864
|
+
},
|
|
865
|
+
async invoke(args) {
|
|
866
|
+
const query = String(args.query ?? "");
|
|
867
|
+
if (!query) throw new Error('memory_search: "query" is required');
|
|
868
|
+
const limit = typeof args.limit === "number" ? args.limit : 10;
|
|
869
|
+
const hits = await memory.search(query, { limit });
|
|
870
|
+
if (hits.length === 0) return "no matches";
|
|
871
|
+
return hits.map((h) => `${h.key}: ${h.snippet.replaceAll("\n", " ")}`).join("\n");
|
|
872
|
+
}
|
|
873
|
+
},
|
|
874
|
+
{
|
|
875
|
+
def: {
|
|
876
|
+
name: "memory_read",
|
|
877
|
+
description: "Read a single entry from the group-shared memory by key.",
|
|
878
|
+
parameters: {
|
|
879
|
+
type: "object",
|
|
880
|
+
properties: { key: { type: "string" } },
|
|
881
|
+
required: ["key"]
|
|
882
|
+
}
|
|
883
|
+
},
|
|
884
|
+
async invoke(args) {
|
|
885
|
+
const key = String(args.key ?? "");
|
|
886
|
+
if (!key) throw new Error('memory_read: "key" is required');
|
|
887
|
+
const entry = await memory.read(key);
|
|
888
|
+
return entry.content;
|
|
889
|
+
}
|
|
890
|
+
},
|
|
891
|
+
{
|
|
892
|
+
def: {
|
|
893
|
+
name: "memory_list",
|
|
894
|
+
description: "List every entry in the group-shared memory with its byte size.",
|
|
895
|
+
parameters: { type: "object", properties: {} }
|
|
896
|
+
},
|
|
897
|
+
async invoke() {
|
|
898
|
+
const all = await memory.list();
|
|
899
|
+
if (all.length === 0) return "(empty)";
|
|
900
|
+
return all.map((e) => `${e.key} (${e.content.length}b)`).join("\n");
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
];
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// ../daemon/src/runtime/tools/messaging.ts
|
|
907
|
+
function decodeText(payload) {
|
|
908
|
+
try {
|
|
909
|
+
const parsed = JSON.parse(payload);
|
|
910
|
+
if (parsed && typeof parsed.text === "string") return parsed.text;
|
|
911
|
+
} catch {
|
|
912
|
+
}
|
|
913
|
+
return payload;
|
|
914
|
+
}
|
|
915
|
+
function messagingTools(host, fromAgentId) {
|
|
916
|
+
return [
|
|
917
|
+
{
|
|
918
|
+
def: {
|
|
919
|
+
name: "send_message",
|
|
920
|
+
description: "Send a message to another agent. Use the recipient's agent id (UUID).",
|
|
921
|
+
parameters: {
|
|
922
|
+
type: "object",
|
|
923
|
+
properties: {
|
|
924
|
+
to: { type: "string", description: "Recipient agent id" },
|
|
925
|
+
text: { type: "string", description: "Message text" },
|
|
926
|
+
reply_to: {
|
|
927
|
+
type: "string",
|
|
928
|
+
description: "Optional: id of the message you are replying to"
|
|
929
|
+
}
|
|
930
|
+
},
|
|
931
|
+
required: ["to", "text"]
|
|
932
|
+
}
|
|
933
|
+
},
|
|
934
|
+
async invoke(args) {
|
|
935
|
+
const to = String(args.to ?? "");
|
|
936
|
+
const text = String(args.text ?? "");
|
|
937
|
+
if (!to) throw new Error('send_message: "to" is required');
|
|
938
|
+
if (!text) throw new Error('send_message: "text" is required');
|
|
939
|
+
if (!await host.agentExists(to)) {
|
|
940
|
+
throw new Error(`send_message: agent not found: ${to}`);
|
|
941
|
+
}
|
|
942
|
+
const replyTo = typeof args.reply_to === "string" ? args.reply_to : null;
|
|
943
|
+
const { messageId } = await host.sendMessage({
|
|
944
|
+
from: fromAgentId,
|
|
945
|
+
to,
|
|
946
|
+
payload: JSON.stringify({ text }),
|
|
947
|
+
replyTo
|
|
948
|
+
});
|
|
949
|
+
return `sent message ${messageId}`;
|
|
950
|
+
}
|
|
951
|
+
},
|
|
952
|
+
{
|
|
953
|
+
def: {
|
|
954
|
+
name: "read_inbox",
|
|
955
|
+
description: "Read messages addressed to you. Marks unread messages as read by default.",
|
|
956
|
+
parameters: {
|
|
957
|
+
type: "object",
|
|
958
|
+
properties: {
|
|
959
|
+
include_read: {
|
|
960
|
+
type: "boolean",
|
|
961
|
+
description: "Also include already-read messages (default false)"
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
},
|
|
966
|
+
async invoke(args) {
|
|
967
|
+
const includeRead = args.include_read === true;
|
|
968
|
+
const messages = await host.listInbox(fromAgentId, { unreadOnly: !includeRead });
|
|
969
|
+
if (messages.length === 0) return "(no messages)";
|
|
970
|
+
const lines = [];
|
|
971
|
+
for (const m of messages) {
|
|
972
|
+
lines.push(`from ${m.fromAgentId} [${m.id}]: ${decodeText(m.payload)}`);
|
|
973
|
+
if (!m.readAt) await host.markRead(m.id);
|
|
974
|
+
}
|
|
975
|
+
return lines.join("\n");
|
|
976
|
+
}
|
|
977
|
+
},
|
|
978
|
+
{
|
|
979
|
+
def: {
|
|
980
|
+
name: "wait_for_reply",
|
|
981
|
+
description: "Block until a reply to a message you sent arrives, or until the timeout expires.",
|
|
982
|
+
parameters: {
|
|
983
|
+
type: "object",
|
|
984
|
+
properties: {
|
|
985
|
+
message_id: {
|
|
986
|
+
type: "string",
|
|
987
|
+
description: "id of the message you sent"
|
|
988
|
+
},
|
|
989
|
+
timeout_ms: {
|
|
990
|
+
type: "number",
|
|
991
|
+
description: "max wait in milliseconds (default 30000)"
|
|
992
|
+
},
|
|
993
|
+
poll_ms: {
|
|
994
|
+
type: "number",
|
|
995
|
+
description: "poll interval in milliseconds (default 200)"
|
|
996
|
+
}
|
|
997
|
+
},
|
|
998
|
+
required: ["message_id"]
|
|
999
|
+
}
|
|
1000
|
+
},
|
|
1001
|
+
async invoke(args) {
|
|
1002
|
+
const messageId = String(args.message_id ?? "");
|
|
1003
|
+
if (!messageId) throw new Error('wait_for_reply: "message_id" is required');
|
|
1004
|
+
const timeout = typeof args.timeout_ms === "number" ? args.timeout_ms : 3e4;
|
|
1005
|
+
const poll = typeof args.poll_ms === "number" ? args.poll_ms : 200;
|
|
1006
|
+
const start = Date.now();
|
|
1007
|
+
while (Date.now() - start < timeout) {
|
|
1008
|
+
const replies = await host.findReplies(fromAgentId, messageId);
|
|
1009
|
+
if (replies.length > 0) {
|
|
1010
|
+
const r = replies[0];
|
|
1011
|
+
if (r) {
|
|
1012
|
+
if (!r.readAt) await host.markRead(r.id);
|
|
1013
|
+
return `reply from ${r.fromAgentId} [${r.id}]: ${decodeText(r.payload)}`;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
await new Promise((r) => setTimeout(r, poll));
|
|
1017
|
+
}
|
|
1018
|
+
return `no reply within ${timeout}ms`;
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
];
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
// ../daemon/src/runtime/tools/web-extract.ts
|
|
1025
|
+
import { Readability } from "@mozilla/readability";
|
|
1026
|
+
import { parseHTML } from "linkedom";
|
|
1027
|
+
function decodeEntities(value) {
|
|
1028
|
+
return value.replace(/ /gi, " ").replace(/&/gi, "&").replace(/"/gi, '"').replace(/'/gi, "'").replace(/</gi, "<").replace(/>/gi, ">").replace(/&#x([0-9a-f]+);/gi, (_, hex) => String.fromCharCode(Number.parseInt(hex, 16))).replace(/&#(\d+);/gi, (_, dec) => String.fromCharCode(Number.parseInt(dec, 10)));
|
|
1029
|
+
}
|
|
1030
|
+
function stripTags(value) {
|
|
1031
|
+
return decodeEntities(value.replace(/<[^>]+>/g, ""));
|
|
1032
|
+
}
|
|
1033
|
+
function normalizeWhitespace(value) {
|
|
1034
|
+
return value.replace(/\r/g, "").replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").replace(/[ \t]{2,}/g, " ").trim();
|
|
1035
|
+
}
|
|
1036
|
+
function htmlToMarkdown(html) {
|
|
1037
|
+
const titleMatch = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
|
|
1038
|
+
const title = titleMatch ? normalizeWhitespace(stripTags(titleMatch[1] ?? "")) : void 0;
|
|
1039
|
+
let text = html.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<noscript[\s\S]*?<\/noscript>/gi, "").replace(/<nav[\s\S]*?<\/nav>/gi, "").replace(/<header[\s\S]*?<\/header>/gi, "").replace(/<footer[\s\S]*?<\/footer>/gi, "");
|
|
1040
|
+
text = text.replace(/<a\s+[^>]*href=["']([^"']+)["'][^>]*>([\s\S]*?)<\/a>/gi, (_, href, body) => {
|
|
1041
|
+
const label = normalizeWhitespace(stripTags(body));
|
|
1042
|
+
return label ? `[${label}](${href})` : href;
|
|
1043
|
+
});
|
|
1044
|
+
text = text.replace(/<h([1-6])[^>]*>([\s\S]*?)<\/h\1>/gi, (_, level, body) => {
|
|
1045
|
+
const n = Math.max(1, Math.min(6, Number.parseInt(level, 10)));
|
|
1046
|
+
return `
|
|
1047
|
+
${"#".repeat(n)} ${normalizeWhitespace(stripTags(body))}
|
|
1048
|
+
`;
|
|
1049
|
+
});
|
|
1050
|
+
text = text.replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, (_, body) => {
|
|
1051
|
+
const label = normalizeWhitespace(stripTags(body));
|
|
1052
|
+
return label ? `
|
|
1053
|
+
- ${label}` : "";
|
|
1054
|
+
});
|
|
1055
|
+
text = text.replace(/<(br|hr)\s*\/?>/gi, "\n").replace(/<\/(p|div|section|article|tr|ul|ol|table|blockquote)>/gi, "\n");
|
|
1056
|
+
text = stripTags(text);
|
|
1057
|
+
return { text: normalizeWhitespace(text), title };
|
|
1058
|
+
}
|
|
1059
|
+
function markdownToPlain(md) {
|
|
1060
|
+
let t = md;
|
|
1061
|
+
t = t.replace(/!\[[^\]]*]\([^)]+\)/g, "");
|
|
1062
|
+
t = t.replace(/\[([^\]]+)]\([^)]+\)/g, "$1");
|
|
1063
|
+
t = t.replace(
|
|
1064
|
+
/```[\s\S]*?```/g,
|
|
1065
|
+
(block) => block.replace(/```[^\n]*\n?/g, "").replace(/```/g, "")
|
|
1066
|
+
);
|
|
1067
|
+
t = t.replace(/`([^`]+)`/g, "$1");
|
|
1068
|
+
t = t.replace(/^#{1,6}\s+/gm, "");
|
|
1069
|
+
t = t.replace(/^\s*[-*+]\s+/gm, "");
|
|
1070
|
+
t = t.replace(/^\s*\d+\.\s+/gm, "");
|
|
1071
|
+
return normalizeWhitespace(t);
|
|
1072
|
+
}
|
|
1073
|
+
function extractReadable(html, url, mode) {
|
|
1074
|
+
const fallback = () => {
|
|
1075
|
+
const r = htmlToMarkdown(html);
|
|
1076
|
+
return mode === "text" ? { text: markdownToPlain(r.text), title: r.title } : r;
|
|
1077
|
+
};
|
|
1078
|
+
try {
|
|
1079
|
+
const { document } = parseHTML(html);
|
|
1080
|
+
try {
|
|
1081
|
+
;
|
|
1082
|
+
document.baseURI = url;
|
|
1083
|
+
} catch {
|
|
1084
|
+
}
|
|
1085
|
+
const parsed = new Readability(document, {
|
|
1086
|
+
charThreshold: 0
|
|
1087
|
+
}).parse();
|
|
1088
|
+
if (!parsed?.content) return fallback();
|
|
1089
|
+
const title = parsed.title || void 0;
|
|
1090
|
+
if (mode === "text") {
|
|
1091
|
+
const text = normalizeWhitespace(parsed.textContent ?? "");
|
|
1092
|
+
return text ? { text, title } : fallback();
|
|
1093
|
+
}
|
|
1094
|
+
const rendered = htmlToMarkdown(parsed.content);
|
|
1095
|
+
return { text: rendered.text, title: title ?? rendered.title };
|
|
1096
|
+
} catch {
|
|
1097
|
+
return fallback();
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
// ../daemon/src/runtime/tools/web-ssrf.ts
|
|
1102
|
+
import { lookup as dnsLookupCb } from "dns";
|
|
1103
|
+
import { lookup as dnsLookup } from "dns/promises";
|
|
1104
|
+
import { Agent } from "undici";
|
|
1105
|
+
var SsrFBlockedError = class extends Error {
|
|
1106
|
+
constructor(message) {
|
|
1107
|
+
super(message);
|
|
1108
|
+
this.name = "SsrFBlockedError";
|
|
1109
|
+
}
|
|
1110
|
+
};
|
|
1111
|
+
var BLOCKED_HOSTNAMES = /* @__PURE__ */ new Set(["localhost", "metadata.google.internal"]);
|
|
1112
|
+
var PRIVATE_IPV6_PREFIXES = ["fe80:", "fec0:", "fc", "fd"];
|
|
1113
|
+
function normalizeHostname(hostname) {
|
|
1114
|
+
let h = hostname.trim().toLowerCase().replace(/\.$/, "");
|
|
1115
|
+
if (h.startsWith("[") && h.endsWith("]")) h = h.slice(1, -1);
|
|
1116
|
+
return h;
|
|
1117
|
+
}
|
|
1118
|
+
function parseIpv4(address) {
|
|
1119
|
+
const parts = address.split(".");
|
|
1120
|
+
if (parts.length !== 4) return null;
|
|
1121
|
+
const nums = parts.map((p) => Number.parseInt(p, 10));
|
|
1122
|
+
if (nums.some((n) => Number.isNaN(n) || n < 0 || n > 255)) return null;
|
|
1123
|
+
return nums;
|
|
1124
|
+
}
|
|
1125
|
+
function isPrivateIpv4(parts) {
|
|
1126
|
+
const [a, b] = parts;
|
|
1127
|
+
if (a === 0 || a === 10 || a === 127) return true;
|
|
1128
|
+
if (a === 169 && b === 254) return true;
|
|
1129
|
+
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
1130
|
+
if (a === 192 && b === 168) return true;
|
|
1131
|
+
if (a === 100 && b >= 64 && b <= 127) return true;
|
|
1132
|
+
return false;
|
|
1133
|
+
}
|
|
1134
|
+
function isPrivateIpAddress(address) {
|
|
1135
|
+
let norm = address.trim().toLowerCase();
|
|
1136
|
+
if (norm.startsWith("[") && norm.endsWith("]")) norm = norm.slice(1, -1);
|
|
1137
|
+
if (!norm) return false;
|
|
1138
|
+
if (norm.startsWith("::ffff:")) {
|
|
1139
|
+
const mapped = norm.slice("::ffff:".length);
|
|
1140
|
+
const ipv42 = parseIpv4(mapped);
|
|
1141
|
+
if (ipv42) return isPrivateIpv4(ipv42);
|
|
1142
|
+
}
|
|
1143
|
+
if (norm.includes(":")) {
|
|
1144
|
+
if (norm === "::" || norm === "::1") return true;
|
|
1145
|
+
return PRIVATE_IPV6_PREFIXES.some((p) => norm.startsWith(p));
|
|
1146
|
+
}
|
|
1147
|
+
const ipv4 = parseIpv4(norm);
|
|
1148
|
+
return ipv4 ? isPrivateIpv4(ipv4) : false;
|
|
1149
|
+
}
|
|
1150
|
+
function isBlockedHostname(hostname) {
|
|
1151
|
+
const h = normalizeHostname(hostname);
|
|
1152
|
+
if (!h) return false;
|
|
1153
|
+
if (BLOCKED_HOSTNAMES.has(h)) return true;
|
|
1154
|
+
return h.endsWith(".localhost") || h.endsWith(".local") || h.endsWith(".internal");
|
|
1155
|
+
}
|
|
1156
|
+
function createPinnedLookup(hostname, addresses) {
|
|
1157
|
+
const normalized = normalizeHostname(hostname);
|
|
1158
|
+
const records = addresses.map((address) => ({
|
|
1159
|
+
address,
|
|
1160
|
+
family: address.includes(":") ? 6 : 4
|
|
1161
|
+
}));
|
|
1162
|
+
let index = 0;
|
|
1163
|
+
return ((host, options, callback) => {
|
|
1164
|
+
const cb = typeof options === "function" ? options : callback;
|
|
1165
|
+
if (!cb) return;
|
|
1166
|
+
if (normalizeHostname(host) !== normalized) {
|
|
1167
|
+
if (typeof options === "function" || options === void 0) {
|
|
1168
|
+
return dnsLookupCb(host, cb);
|
|
1169
|
+
}
|
|
1170
|
+
return dnsLookupCb(
|
|
1171
|
+
host,
|
|
1172
|
+
options,
|
|
1173
|
+
cb
|
|
1174
|
+
);
|
|
1175
|
+
}
|
|
1176
|
+
const opts = typeof options === "object" && options !== null ? options : {};
|
|
1177
|
+
const family = typeof options === "number" ? options : opts.family ?? 0;
|
|
1178
|
+
const candidates = family === 4 || family === 6 ? records.filter((r) => r.family === family) : records;
|
|
1179
|
+
const usable = candidates.length > 0 ? candidates : records;
|
|
1180
|
+
if (opts.all) {
|
|
1181
|
+
cb(null, usable);
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
const chosen = usable[index % usable.length];
|
|
1185
|
+
if (!chosen) return;
|
|
1186
|
+
index += 1;
|
|
1187
|
+
cb(null, chosen.address, chosen.family);
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
async function resolveAndCheck(hostname) {
|
|
1191
|
+
const norm = normalizeHostname(hostname);
|
|
1192
|
+
if (!norm) throw new SsrFBlockedError("Invalid hostname");
|
|
1193
|
+
if (isBlockedHostname(norm)) throw new SsrFBlockedError(`Blocked hostname: ${hostname}`);
|
|
1194
|
+
if (isPrivateIpAddress(norm)) throw new SsrFBlockedError("Blocked: private IP literal");
|
|
1195
|
+
const results = await dnsLookup(norm, { all: true });
|
|
1196
|
+
if (results.length === 0) throw new SsrFBlockedError(`Cannot resolve: ${hostname}`);
|
|
1197
|
+
for (const r of results) {
|
|
1198
|
+
if (isPrivateIpAddress(r.address)) throw new SsrFBlockedError("Blocked: resolves to private IP");
|
|
1199
|
+
}
|
|
1200
|
+
return Array.from(new Set(results.map((r) => r.address)));
|
|
1201
|
+
}
|
|
1202
|
+
function isRedirectStatus(s) {
|
|
1203
|
+
return s === 301 || s === 302 || s === 303 || s === 307 || s === 308;
|
|
1204
|
+
}
|
|
1205
|
+
async function closeDispatcher(d) {
|
|
1206
|
+
if (!d) return;
|
|
1207
|
+
try {
|
|
1208
|
+
await d.close();
|
|
1209
|
+
} catch {
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
async function guardedFetch(opts) {
|
|
1213
|
+
const fetcher = opts.fetchImpl ?? globalThis.fetch;
|
|
1214
|
+
const maxRedirects = opts.maxRedirects ?? 3;
|
|
1215
|
+
const abortController = new AbortController();
|
|
1216
|
+
const timeoutId = opts.timeoutMs ? setTimeout(() => abortController.abort(new Error("timeout")), opts.timeoutMs) : null;
|
|
1217
|
+
if (opts.signal) {
|
|
1218
|
+
if (opts.signal.aborted) abortController.abort(opts.signal.reason);
|
|
1219
|
+
else
|
|
1220
|
+
opts.signal.addEventListener("abort", () => abortController.abort(opts.signal?.reason), {
|
|
1221
|
+
once: true
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
let current = opts.url;
|
|
1225
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1226
|
+
let redirects = 0;
|
|
1227
|
+
let dispatcher = null;
|
|
1228
|
+
const release = async () => {
|
|
1229
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
1230
|
+
await closeDispatcher(dispatcher);
|
|
1231
|
+
dispatcher = null;
|
|
1232
|
+
};
|
|
1233
|
+
while (true) {
|
|
1234
|
+
let parsed;
|
|
1235
|
+
try {
|
|
1236
|
+
parsed = new URL(current);
|
|
1237
|
+
} catch {
|
|
1238
|
+
await release();
|
|
1239
|
+
throw new Error("Invalid URL");
|
|
1240
|
+
}
|
|
1241
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
1242
|
+
await release();
|
|
1243
|
+
throw new Error("Invalid URL: must be http or https");
|
|
1244
|
+
}
|
|
1245
|
+
const canPin = !opts.fetchImpl;
|
|
1246
|
+
if (!opts.allowPrivate) {
|
|
1247
|
+
const addrs = await resolveAndCheck(parsed.hostname);
|
|
1248
|
+
await closeDispatcher(dispatcher);
|
|
1249
|
+
dispatcher = canPin ? new Agent({ connect: { lookup: createPinnedLookup(parsed.hostname, addrs) } }) : null;
|
|
1250
|
+
}
|
|
1251
|
+
const init = {
|
|
1252
|
+
...opts.init ?? {},
|
|
1253
|
+
redirect: "manual",
|
|
1254
|
+
signal: abortController.signal
|
|
1255
|
+
};
|
|
1256
|
+
if (dispatcher) init.dispatcher = dispatcher;
|
|
1257
|
+
let res;
|
|
1258
|
+
try {
|
|
1259
|
+
res = await fetcher(parsed.toString(), init);
|
|
1260
|
+
} catch (err) {
|
|
1261
|
+
await release();
|
|
1262
|
+
throw err;
|
|
1263
|
+
}
|
|
1264
|
+
if (isRedirectStatus(res.status)) {
|
|
1265
|
+
const loc = res.headers.get("location");
|
|
1266
|
+
if (!loc) {
|
|
1267
|
+
await release();
|
|
1268
|
+
throw new Error(`Redirect ${res.status} missing Location header`);
|
|
1269
|
+
}
|
|
1270
|
+
redirects += 1;
|
|
1271
|
+
if (redirects > maxRedirects) {
|
|
1272
|
+
await release();
|
|
1273
|
+
throw new Error(`Too many redirects (> ${maxRedirects})`);
|
|
1274
|
+
}
|
|
1275
|
+
const next = new URL(loc, parsed).toString();
|
|
1276
|
+
if (visited.has(next)) {
|
|
1277
|
+
await release();
|
|
1278
|
+
throw new Error("Redirect loop");
|
|
1279
|
+
}
|
|
1280
|
+
visited.add(next);
|
|
1281
|
+
void res.body?.cancel();
|
|
1282
|
+
current = next;
|
|
1283
|
+
continue;
|
|
1284
|
+
}
|
|
1285
|
+
return { response: res, finalUrl: parsed.toString(), release };
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
// ../daemon/src/runtime/tools/web.ts
|
|
1290
|
+
var DEFAULT_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36";
|
|
1291
|
+
var DEFAULT_CACHE_TTL_MS = 15 * 6e4;
|
|
1292
|
+
var DEFAULT_CACHE_MAX = 100;
|
|
1293
|
+
var DEFAULT_MAX_LENGTH = 2e4;
|
|
1294
|
+
var DEFAULT_TIMEOUT_MS = 2e4;
|
|
1295
|
+
function stripHtml(s) {
|
|
1296
|
+
return s.replace(/<[^>]*>/g, "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/ /g, " ").trim();
|
|
1297
|
+
}
|
|
1298
|
+
async function braveSearch(query, limit, apiKey, fetchFn) {
|
|
1299
|
+
const params = new URLSearchParams({ q: query, count: String(limit) });
|
|
1300
|
+
const res = await fetchFn(`https://api.search.brave.com/res/v1/web/search?${params}`, {
|
|
1301
|
+
headers: {
|
|
1302
|
+
accept: "application/json",
|
|
1303
|
+
"accept-encoding": "gzip",
|
|
1304
|
+
"x-subscription-token": apiKey
|
|
1305
|
+
}
|
|
1306
|
+
});
|
|
1307
|
+
if (!res.ok) throw new Error(`Brave Search: ${res.status} ${await res.text()}`);
|
|
1308
|
+
const data = await res.json();
|
|
1309
|
+
return (data.web?.results ?? []).slice(0, limit).map((r) => ({
|
|
1310
|
+
title: r.title ?? "",
|
|
1311
|
+
url: r.url ?? "",
|
|
1312
|
+
snippet: r.description ? stripHtml(r.description) : ""
|
|
1313
|
+
}));
|
|
1314
|
+
}
|
|
1315
|
+
async function searxngSearch(query, limit, baseURL, fetchFn) {
|
|
1316
|
+
const params = new URLSearchParams({ q: query, format: "json" });
|
|
1317
|
+
const res = await fetchFn(`${baseURL}/search?${params}`, {
|
|
1318
|
+
headers: { accept: "application/json" }
|
|
1319
|
+
});
|
|
1320
|
+
if (!res.ok) throw new Error(`SearXNG: ${res.status} ${await res.text()}`);
|
|
1321
|
+
const data = await res.json();
|
|
1322
|
+
return (data.results ?? []).slice(0, limit).map((r) => ({
|
|
1323
|
+
title: r.title ?? "",
|
|
1324
|
+
url: r.url ?? "",
|
|
1325
|
+
snippet: r.content ?? ""
|
|
1326
|
+
}));
|
|
1327
|
+
}
|
|
1328
|
+
function cacheGet(cache, key) {
|
|
1329
|
+
const entry = cache.get(key);
|
|
1330
|
+
if (!entry) return null;
|
|
1331
|
+
if (entry.expiresAt < Date.now()) {
|
|
1332
|
+
cache.delete(key);
|
|
1333
|
+
return null;
|
|
1334
|
+
}
|
|
1335
|
+
cache.delete(key);
|
|
1336
|
+
cache.set(key, entry);
|
|
1337
|
+
return entry.value;
|
|
1338
|
+
}
|
|
1339
|
+
function cacheSet(cache, key, value, ttlMs, maxEntries) {
|
|
1340
|
+
cache.set(key, { value, expiresAt: Date.now() + ttlMs });
|
|
1341
|
+
while (cache.size > maxEntries) {
|
|
1342
|
+
const oldest = cache.keys().next().value;
|
|
1343
|
+
if (!oldest) break;
|
|
1344
|
+
cache.delete(oldest);
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
function webTools(opts) {
|
|
1348
|
+
const fetchFn = opts?.fetchImpl ?? fetch;
|
|
1349
|
+
const env = opts?.env ?? process.env;
|
|
1350
|
+
const allowPrivate = opts?.allowPrivate ?? false;
|
|
1351
|
+
const cacheTtlMs = opts?.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
|
|
1352
|
+
const cacheMax = opts?.cacheMax ?? DEFAULT_CACHE_MAX;
|
|
1353
|
+
const timeoutMs = opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
1354
|
+
const cache = /* @__PURE__ */ new Map();
|
|
1355
|
+
return [
|
|
1356
|
+
{
|
|
1357
|
+
def: {
|
|
1358
|
+
name: "web_search",
|
|
1359
|
+
description: "Search the internet. Returns a list of titles, URLs, and snippets. Requires BRAVE_API_KEY or SEARXNG_URL to be configured.",
|
|
1360
|
+
parameters: {
|
|
1361
|
+
type: "object",
|
|
1362
|
+
properties: {
|
|
1363
|
+
query: { type: "string", description: "Search query" },
|
|
1364
|
+
limit: { type: "number", description: "Max results to return (default 5)" }
|
|
1365
|
+
},
|
|
1366
|
+
required: ["query"]
|
|
1367
|
+
}
|
|
1368
|
+
},
|
|
1369
|
+
async invoke(args) {
|
|
1370
|
+
const query = String(args.query ?? "");
|
|
1371
|
+
if (!query) throw new Error("web_search: query is required");
|
|
1372
|
+
const limit = typeof args.limit === "number" ? args.limit : 5;
|
|
1373
|
+
const braveKey = env.BRAVE_API_KEY;
|
|
1374
|
+
if (braveKey) {
|
|
1375
|
+
const results = await braveSearch(query, limit, braveKey, fetchFn);
|
|
1376
|
+
return results.length === 0 ? "no results" : formatResults(results);
|
|
1377
|
+
}
|
|
1378
|
+
const searxngUrl = env.SEARXNG_URL;
|
|
1379
|
+
if (searxngUrl) {
|
|
1380
|
+
const results = await searxngSearch(query, limit, searxngUrl, fetchFn);
|
|
1381
|
+
return results.length === 0 ? "no results" : formatResults(results);
|
|
1382
|
+
}
|
|
1383
|
+
throw new Error(
|
|
1384
|
+
"web_search: no search backend configured. Set BRAVE_API_KEY (free at https://brave.com/search/api/) or SEARXNG_URL."
|
|
1385
|
+
);
|
|
1386
|
+
}
|
|
1387
|
+
},
|
|
1388
|
+
{
|
|
1389
|
+
def: {
|
|
1390
|
+
name: "web_fetch",
|
|
1391
|
+
description: "Fetch a URL and return its readable content. HTML is extracted via Readability and converted to markdown. Results are cached for 15 minutes.",
|
|
1392
|
+
parameters: {
|
|
1393
|
+
type: "object",
|
|
1394
|
+
properties: {
|
|
1395
|
+
url: { type: "string", description: "The URL to fetch (http or https)" },
|
|
1396
|
+
max_length: {
|
|
1397
|
+
type: "number",
|
|
1398
|
+
description: "Max characters to return (default 20000)"
|
|
1399
|
+
},
|
|
1400
|
+
extract_mode: {
|
|
1401
|
+
type: "string",
|
|
1402
|
+
enum: ["markdown", "text"],
|
|
1403
|
+
description: 'Output format for HTML pages. Default "markdown".'
|
|
1404
|
+
}
|
|
1405
|
+
},
|
|
1406
|
+
required: ["url"]
|
|
1407
|
+
}
|
|
1408
|
+
},
|
|
1409
|
+
async invoke(args) {
|
|
1410
|
+
const url = String(args.url ?? "");
|
|
1411
|
+
if (!url) throw new Error("web_fetch: url is required");
|
|
1412
|
+
const maxLen = typeof args.max_length === "number" ? args.max_length : DEFAULT_MAX_LENGTH;
|
|
1413
|
+
const mode = args.extract_mode === "text" ? "text" : "markdown";
|
|
1414
|
+
const cacheKey = `${mode}|${url}`;
|
|
1415
|
+
const cached = cacheGet(cache, cacheKey);
|
|
1416
|
+
if (cached) return formatOutput(cached, maxLen);
|
|
1417
|
+
let result;
|
|
1418
|
+
try {
|
|
1419
|
+
result = await guardedFetch({
|
|
1420
|
+
url,
|
|
1421
|
+
fetchImpl: opts?.fetchImpl,
|
|
1422
|
+
allowPrivate,
|
|
1423
|
+
timeoutMs,
|
|
1424
|
+
init: {
|
|
1425
|
+
headers: {
|
|
1426
|
+
"user-agent": DEFAULT_USER_AGENT,
|
|
1427
|
+
accept: "text/html,application/xhtml+xml,application/json,text/plain,*/*",
|
|
1428
|
+
"accept-language": "en-US,en;q=0.9"
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
});
|
|
1432
|
+
} catch (err) {
|
|
1433
|
+
if (err instanceof SsrFBlockedError) throw new Error(`web_fetch: ${err.message}`);
|
|
1434
|
+
throw err;
|
|
1435
|
+
}
|
|
1436
|
+
try {
|
|
1437
|
+
if (!result.response.ok) {
|
|
1438
|
+
throw new Error(`web_fetch: ${result.response.status} ${result.response.statusText}`);
|
|
1439
|
+
}
|
|
1440
|
+
const ct = result.response.headers.get("content-type") ?? "";
|
|
1441
|
+
const body = await result.response.text();
|
|
1442
|
+
let extracted;
|
|
1443
|
+
if (ct.includes("text/html") || ct.includes("xhtml")) {
|
|
1444
|
+
extracted = extractReadable(body, result.finalUrl, mode);
|
|
1445
|
+
} else if (ct.includes("application/json")) {
|
|
1446
|
+
try {
|
|
1447
|
+
extracted = { text: JSON.stringify(JSON.parse(body), null, 2) };
|
|
1448
|
+
} catch {
|
|
1449
|
+
extracted = { text: body };
|
|
1450
|
+
}
|
|
1451
|
+
} else {
|
|
1452
|
+
extracted = { text: body };
|
|
1453
|
+
}
|
|
1454
|
+
cacheSet(cache, cacheKey, extracted, cacheTtlMs, cacheMax);
|
|
1455
|
+
return formatOutput(extracted, maxLen);
|
|
1456
|
+
} finally {
|
|
1457
|
+
await result.release();
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
];
|
|
1462
|
+
}
|
|
1463
|
+
function formatOutput(r, maxLen) {
|
|
1464
|
+
const body = r.title ? `# ${r.title}
|
|
1465
|
+
|
|
1466
|
+
${r.text}` : r.text;
|
|
1467
|
+
if (body.length > maxLen) return `${body.slice(0, maxLen)}
|
|
1468
|
+
|
|
1469
|
+
[truncated at ${maxLen} chars]`;
|
|
1470
|
+
return body;
|
|
1471
|
+
}
|
|
1472
|
+
function formatResults(results) {
|
|
1473
|
+
return results.map((r, i) => `${i + 1}. ${r.title}
|
|
1474
|
+
${r.url}
|
|
1475
|
+
${r.snippet}`).join("\n\n");
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
// ../daemon/src/runtime/pi/tools.ts
|
|
1479
|
+
function ourToolToPiTool(h) {
|
|
1480
|
+
return {
|
|
1481
|
+
name: h.def.name,
|
|
1482
|
+
label: h.def.name,
|
|
1483
|
+
description: h.def.description,
|
|
1484
|
+
parameters: Type2.Unsafe(h.def.parameters),
|
|
1485
|
+
async execute(_toolCallId, params) {
|
|
1486
|
+
const text = await h.invoke(params);
|
|
1487
|
+
return {
|
|
1488
|
+
content: [{ type: "text", text }],
|
|
1489
|
+
details: {}
|
|
1490
|
+
};
|
|
1491
|
+
}
|
|
1492
|
+
};
|
|
1493
|
+
}
|
|
1494
|
+
function createBazilionCustomTools(opts) {
|
|
1495
|
+
const handlers = [
|
|
1496
|
+
...memoryTools(opts.memory),
|
|
1497
|
+
...homeTools(opts.agent.agent.dir),
|
|
1498
|
+
bootstrapTool(opts.agent.agent.dir),
|
|
1499
|
+
...webTools({ env: opts.env })
|
|
1500
|
+
];
|
|
1501
|
+
if (opts.messagingHost) {
|
|
1502
|
+
handlers.push(...messagingTools(opts.messagingHost, opts.agent.agent.id));
|
|
1503
|
+
}
|
|
1504
|
+
return handlers.map(ourToolToPiTool);
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
// ../daemon/src/runtime/pi/session.ts
|
|
1508
|
+
var BUILTIN_TOOL_NAMES = ["read", "bash", "edit", "write", "grep", "find", "ls"];
|
|
1509
|
+
async function createBazilionSession(opts) {
|
|
1510
|
+
const { agent, paths, env, memory, enabledProviders, messagingHost, refreshApiKey } = opts;
|
|
1511
|
+
const { providerName, modelId } = splitModelString(agent.model);
|
|
1512
|
+
if (enabledProviders.size > 0 && !enabledProviders.has(providerName)) {
|
|
1513
|
+
throw new Error(`${providerName} provider is disabled \u2014 enable it on the /config page`);
|
|
1514
|
+
}
|
|
1515
|
+
const piProviderName = mapProviderName(providerName);
|
|
1516
|
+
const model = resolveModel(
|
|
1517
|
+
{
|
|
1518
|
+
providerName,
|
|
1519
|
+
piProviderName,
|
|
1520
|
+
fallbackApi: pickFallbackApi(providerName),
|
|
1521
|
+
baseUrl: resolveBaseUrl(providerName, env)
|
|
1522
|
+
},
|
|
1523
|
+
modelId
|
|
1524
|
+
);
|
|
1525
|
+
const apiKey = opts.apiKey ?? resolveApiKey(providerName, env);
|
|
1526
|
+
const authStorage = AuthStorage.inMemory();
|
|
1527
|
+
if (apiKey) {
|
|
1528
|
+
authStorage.setRuntimeApiKey(piProviderName, apiKey);
|
|
1529
|
+
}
|
|
1530
|
+
const modelRegistry = ModelRegistry.inMemory(authStorage);
|
|
1531
|
+
if (providerName === "lmstudio" || providerName === "ollama") {
|
|
1532
|
+
modelRegistry.registerProvider(piProviderName, {
|
|
1533
|
+
baseUrl: model.baseUrl,
|
|
1534
|
+
api: "openai-completions",
|
|
1535
|
+
authHeader: false,
|
|
1536
|
+
apiKey: apiKey ?? "dummy"
|
|
1537
|
+
});
|
|
1538
|
+
}
|
|
1539
|
+
const cwd = agent.group.path;
|
|
1540
|
+
if (!existsSync11(cwd)) mkdirSync5(cwd, { recursive: true });
|
|
1541
|
+
const sessionDir = join13(paths.agentDir(agent.agent.id), "sessions");
|
|
1542
|
+
mkdirSync5(sessionDir, { recursive: true });
|
|
1543
|
+
const existing = findMostRecent(sessionDir);
|
|
1544
|
+
const sessionManager = existing ? SessionManager.open(existing, sessionDir, cwd) : SessionManager.create(cwd, sessionDir);
|
|
1545
|
+
const settingsManager = SettingsManager.inMemory({
|
|
1546
|
+
compaction: { enabled: false },
|
|
1547
|
+
retry: { enabled: true, maxRetries: 2, baseDelayMs: 500, maxDelayMs: 8e3 }
|
|
1548
|
+
});
|
|
1549
|
+
const bazilionPrompt = buildSystemPrompt(agent);
|
|
1550
|
+
const resourceLoader = createBazilionResourceLoader(bazilionPrompt);
|
|
1551
|
+
await resourceLoader.reload();
|
|
1552
|
+
const customTools = createBazilionCustomTools({ agent, memory, messagingHost, env });
|
|
1553
|
+
const allowedTools = [...BUILTIN_TOOL_NAMES, ...customTools.map((t) => t.name)];
|
|
1554
|
+
const { session } = await createAgentSession({
|
|
1555
|
+
cwd,
|
|
1556
|
+
agentDir: join13(paths.home, "pi"),
|
|
1557
|
+
model,
|
|
1558
|
+
thinkingLevel: toPiThinkingLevel(agent.reasoningLevel),
|
|
1559
|
+
tools: allowedTools,
|
|
1560
|
+
customTools,
|
|
1561
|
+
sessionManager,
|
|
1562
|
+
settingsManager,
|
|
1563
|
+
authStorage,
|
|
1564
|
+
modelRegistry,
|
|
1565
|
+
resourceLoader
|
|
1566
|
+
});
|
|
1567
|
+
if (refreshApiKey) {
|
|
1568
|
+
session.agent.getApiKey = async (requestedProvider) => {
|
|
1569
|
+
if (requestedProvider !== piProviderName) return void 0;
|
|
1570
|
+
try {
|
|
1571
|
+
return await refreshApiKey(providerName);
|
|
1572
|
+
} catch {
|
|
1573
|
+
return void 0;
|
|
1574
|
+
}
|
|
1575
|
+
};
|
|
1576
|
+
}
|
|
1577
|
+
return {
|
|
1578
|
+
session,
|
|
1579
|
+
dispose() {
|
|
1580
|
+
session.dispose();
|
|
1581
|
+
}
|
|
1582
|
+
};
|
|
1583
|
+
}
|
|
1584
|
+
function splitModelString(s) {
|
|
1585
|
+
const idx = s.indexOf(":");
|
|
1586
|
+
if (idx === -1) {
|
|
1587
|
+
throw new Error(`invalid model string "${s}": expected "provider:model"`);
|
|
1588
|
+
}
|
|
1589
|
+
return { providerName: s.slice(0, idx), modelId: s.slice(idx + 1) };
|
|
1590
|
+
}
|
|
1591
|
+
function mapProviderName(name) {
|
|
1592
|
+
if (name === "bedrock") return "amazon-bedrock";
|
|
1593
|
+
return name;
|
|
1594
|
+
}
|
|
1595
|
+
function pickFallbackApi(providerName) {
|
|
1596
|
+
switch (providerName) {
|
|
1597
|
+
case "anthropic":
|
|
1598
|
+
return "anthropic-messages";
|
|
1599
|
+
case "google":
|
|
1600
|
+
return "google-generative-ai";
|
|
1601
|
+
case "google-vertex":
|
|
1602
|
+
return "google-vertex";
|
|
1603
|
+
case "azure-openai":
|
|
1604
|
+
return "azure-openai-responses";
|
|
1605
|
+
case "bedrock":
|
|
1606
|
+
return "bedrock-converse-stream";
|
|
1607
|
+
case "openai-codex":
|
|
1608
|
+
return "openai-codex-responses";
|
|
1609
|
+
default:
|
|
1610
|
+
return "openai-completions";
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
function resolveBaseUrl(providerName, env) {
|
|
1614
|
+
if (providerName === "lmstudio") return env.LMSTUDIO_URL ?? "http://127.0.0.1:1234/v1";
|
|
1615
|
+
if (providerName === "ollama") return env.OLLAMA_URL ?? "http://127.0.0.1:11434/v1";
|
|
1616
|
+
return void 0;
|
|
1617
|
+
}
|
|
1618
|
+
function resolveApiKey(providerName, env) {
|
|
1619
|
+
switch (providerName) {
|
|
1620
|
+
case "anthropic":
|
|
1621
|
+
return env.ANTHROPIC_OAUTH_TOKEN ?? env.ANTHROPIC_API_KEY;
|
|
1622
|
+
case "openai":
|
|
1623
|
+
return env.OPENAI_API_KEY;
|
|
1624
|
+
case "google":
|
|
1625
|
+
return env.GEMINI_API_KEY;
|
|
1626
|
+
case "mistral":
|
|
1627
|
+
return env.MISTRAL_API_KEY;
|
|
1628
|
+
case "groq":
|
|
1629
|
+
return env.GROQ_API_KEY;
|
|
1630
|
+
case "cerebras":
|
|
1631
|
+
return env.CEREBRAS_API_KEY;
|
|
1632
|
+
case "xai":
|
|
1633
|
+
return env.XAI_API_KEY;
|
|
1634
|
+
case "zai":
|
|
1635
|
+
return env.ZAI_API_KEY;
|
|
1636
|
+
case "huggingface":
|
|
1637
|
+
return env.HF_TOKEN;
|
|
1638
|
+
case "openrouter":
|
|
1639
|
+
return env.OPENROUTER_API_KEY;
|
|
1640
|
+
case "vercel-ai-gateway":
|
|
1641
|
+
return env.AI_GATEWAY_API_KEY;
|
|
1642
|
+
case "azure-openai":
|
|
1643
|
+
return env.AZURE_OPENAI_API_KEY;
|
|
1644
|
+
case "lmstudio":
|
|
1645
|
+
return env.LMSTUDIO_API_KEY ?? "lm-studio";
|
|
1646
|
+
case "ollama":
|
|
1647
|
+
return env.OLLAMA_API_KEY ?? "ollama";
|
|
1648
|
+
default:
|
|
1649
|
+
return void 0;
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
function toPiThinkingLevel(level) {
|
|
1653
|
+
switch (level) {
|
|
1654
|
+
case "off":
|
|
1655
|
+
case "minimal":
|
|
1656
|
+
case "low":
|
|
1657
|
+
case "medium":
|
|
1658
|
+
case "high":
|
|
1659
|
+
case "xhigh":
|
|
1660
|
+
return level;
|
|
1661
|
+
default:
|
|
1662
|
+
return "medium";
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
function createBazilionResourceLoader(appendSystemPrompt) {
|
|
1666
|
+
const extensions = { extensions: [], errors: [], runtime: createExtensionRuntime() };
|
|
1667
|
+
return {
|
|
1668
|
+
getExtensions: () => extensions,
|
|
1669
|
+
getSkills: () => ({ skills: [], diagnostics: [] }),
|
|
1670
|
+
getPrompts: () => ({ prompts: [], diagnostics: [] }),
|
|
1671
|
+
getThemes: () => ({ themes: [], diagnostics: [] }),
|
|
1672
|
+
getAgentsFiles: () => ({ agentsFiles: [] }),
|
|
1673
|
+
getSystemPrompt: () => void 0,
|
|
1674
|
+
getAppendSystemPrompt: () => appendSystemPrompt ? [appendSystemPrompt] : [],
|
|
1675
|
+
extendResources: () => {
|
|
1676
|
+
},
|
|
1677
|
+
async reload() {
|
|
1678
|
+
}
|
|
1679
|
+
};
|
|
1680
|
+
}
|
|
1681
|
+
function findMostRecent(sessionDir) {
|
|
1682
|
+
if (!existsSync11(sessionDir)) return null;
|
|
1683
|
+
let newest = null;
|
|
1684
|
+
for (const entry of readdirSync6(sessionDir)) {
|
|
1685
|
+
if (!entry.endsWith(".jsonl")) continue;
|
|
1686
|
+
const path = join13(sessionDir, entry);
|
|
1687
|
+
try {
|
|
1688
|
+
const s = statSync5(path);
|
|
1689
|
+
if (!newest || s.mtimeMs > newest.mtimeMs) newest = { path, mtimeMs: s.mtimeMs };
|
|
1690
|
+
} catch {
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
return newest?.path ?? null;
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
// ../daemon/src/runtime/worker/entry.ts
|
|
1697
|
+
function emit(frame) {
|
|
1698
|
+
process.stdout.write(`${JSON.stringify(frame)}
|
|
1699
|
+
`);
|
|
1700
|
+
}
|
|
1701
|
+
async function readInput() {
|
|
1702
|
+
const chunks = [];
|
|
1703
|
+
for await (const c of process.stdin) chunks.push(c);
|
|
1704
|
+
const raw = Buffer.concat(chunks).toString("utf8").trim();
|
|
1705
|
+
if (!raw) throw new Error("worker: empty stdin input");
|
|
1706
|
+
const parsed = JSON.parse(raw);
|
|
1707
|
+
if (!parsed.agent || typeof parsed.message !== "string" || !Array.isArray(parsed.enabledProviders)) {
|
|
1708
|
+
throw new Error("worker: stdin must be {agent, message, enabledProviders}");
|
|
1709
|
+
}
|
|
1710
|
+
return parsed;
|
|
1711
|
+
}
|
|
1712
|
+
function createIpcMessagingHost() {
|
|
1713
|
+
const pending = /* @__PURE__ */ new Map();
|
|
1714
|
+
process.on("message", (msg) => {
|
|
1715
|
+
if (!isIpcReply(msg)) return;
|
|
1716
|
+
const entry = pending.get(msg.id);
|
|
1717
|
+
if (!entry) return;
|
|
1718
|
+
pending.delete(msg.id);
|
|
1719
|
+
if (msg.ok) entry.resolve(msg.result);
|
|
1720
|
+
else entry.reject(new Error(msg.error));
|
|
1721
|
+
});
|
|
1722
|
+
process.on("disconnect", () => {
|
|
1723
|
+
for (const [, entry] of pending) entry.reject(new Error("worker IPC channel disconnected"));
|
|
1724
|
+
pending.clear();
|
|
1725
|
+
});
|
|
1726
|
+
function call(method, args) {
|
|
1727
|
+
if (!process.send) {
|
|
1728
|
+
return Promise.reject(new Error("worker: no IPC channel \u2014 daemon must spawn with stdio:ipc"));
|
|
1729
|
+
}
|
|
1730
|
+
const id = randomUUID5();
|
|
1731
|
+
const message = { type: "rpc", id, method, args };
|
|
1732
|
+
return new Promise((resolve3, reject) => {
|
|
1733
|
+
pending.set(id, {
|
|
1734
|
+
resolve: (v) => resolve3(v),
|
|
1735
|
+
reject
|
|
1736
|
+
});
|
|
1737
|
+
process.send?.(message, void 0, void 0, (err) => {
|
|
1738
|
+
if (err) {
|
|
1739
|
+
pending.delete(id);
|
|
1740
|
+
reject(err);
|
|
1741
|
+
}
|
|
1742
|
+
});
|
|
1743
|
+
});
|
|
1744
|
+
}
|
|
1745
|
+
return {
|
|
1746
|
+
agentExists: (agentId) => call("agentExists", { agentId }),
|
|
1747
|
+
sendMessage: (input) => call("sendMessage", input),
|
|
1748
|
+
listInbox: (agentId, opts) => call("listInbox", { agentId, unreadOnly: opts.unreadOnly }),
|
|
1749
|
+
markRead: async (messageId) => {
|
|
1750
|
+
await call("markRead", { messageId });
|
|
1751
|
+
},
|
|
1752
|
+
findReplies: (agentId, replyTo) => call("findReplies", { agentId, replyTo })
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
function isIpcReply(msg) {
|
|
1756
|
+
if (!msg || typeof msg !== "object") return false;
|
|
1757
|
+
const m = msg;
|
|
1758
|
+
return m.type === "rpc-reply" && typeof m.id === "string" && typeof m.ok === "boolean";
|
|
1759
|
+
}
|
|
1760
|
+
async function main() {
|
|
1761
|
+
let aborted = false;
|
|
1762
|
+
let abortSession = null;
|
|
1763
|
+
const onSignal = () => {
|
|
1764
|
+
aborted = true;
|
|
1765
|
+
if (abortSession) {
|
|
1766
|
+
abortSession();
|
|
1767
|
+
} else {
|
|
1768
|
+
try {
|
|
1769
|
+
emit({ kind: "event", event: { type: "error", error: "cancelled" } });
|
|
1770
|
+
} catch {
|
|
1771
|
+
}
|
|
1772
|
+
process.exit(0);
|
|
1773
|
+
}
|
|
1774
|
+
};
|
|
1775
|
+
process.on("SIGTERM", onSignal);
|
|
1776
|
+
process.on("SIGINT", onSignal);
|
|
1777
|
+
const { agent, message, enabledProviders, apiKey } = await readInput();
|
|
1778
|
+
const paths = resolvePaths();
|
|
1779
|
+
const memory = qmdBackend(join14(agent.group.path, "memory"));
|
|
1780
|
+
await memory.init();
|
|
1781
|
+
const messagingHost = createIpcMessagingHost();
|
|
1782
|
+
const { session, dispose } = await createBazilionSession({
|
|
1783
|
+
agent,
|
|
1784
|
+
paths,
|
|
1785
|
+
env: process.env,
|
|
1786
|
+
memory,
|
|
1787
|
+
enabledProviders: new Set(enabledProviders),
|
|
1788
|
+
messagingHost,
|
|
1789
|
+
apiKey
|
|
1790
|
+
});
|
|
1791
|
+
abortSession = () => {
|
|
1792
|
+
void session.abort();
|
|
1793
|
+
};
|
|
1794
|
+
const unsubscribe = session.subscribe((piEvent) => {
|
|
1795
|
+
for (const ev of translatePiEvent(piEvent)) {
|
|
1796
|
+
emit({ kind: "event", event: ev });
|
|
1797
|
+
}
|
|
1798
|
+
});
|
|
1799
|
+
try {
|
|
1800
|
+
await session.prompt(message);
|
|
1801
|
+
await session.agent.waitForIdle();
|
|
1802
|
+
emit({
|
|
1803
|
+
kind: "done",
|
|
1804
|
+
messages: piMessagesToProviderView(session.agent.state.messages)
|
|
1805
|
+
});
|
|
1806
|
+
} catch (err) {
|
|
1807
|
+
if (!aborted) {
|
|
1808
|
+
emit({ kind: "event", event: { type: "error", error: err.message } });
|
|
1809
|
+
}
|
|
1810
|
+
emit({ kind: "fatal", error: err.message });
|
|
1811
|
+
process.exitCode = 1;
|
|
1812
|
+
} finally {
|
|
1813
|
+
unsubscribe();
|
|
1814
|
+
process.off("SIGTERM", onSignal);
|
|
1815
|
+
process.off("SIGINT", onSignal);
|
|
1816
|
+
dispose();
|
|
1817
|
+
try {
|
|
1818
|
+
process.disconnect?.();
|
|
1819
|
+
} catch {
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
main().catch((err) => {
|
|
1824
|
+
try {
|
|
1825
|
+
emit({ kind: "fatal", error: err?.message ?? String(err) });
|
|
1826
|
+
} catch {
|
|
1827
|
+
}
|
|
1828
|
+
process.exit(1);
|
|
1829
|
+
});
|
|
1830
|
+
//# sourceMappingURL=worker.js.map
|