@electric-ax/agents 0.4.6 → 0.4.9
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/dist/entrypoint.js +55 -27
- package/dist/index.cjs +56 -27
- package/dist/index.d.cts +14 -69
- package/dist/index.d.ts +14 -69
- package/dist/index.js +56 -28
- package/package.json +3 -3
- package/skills/quickstart.md +2 -2
package/dist/entrypoint.js
CHANGED
|
@@ -4,7 +4,7 @@ import fs from "node:fs";
|
|
|
4
4
|
import pino from "pino";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { appendPathToUrl, completeWithLowCostModel, createEntityRegistry, createPullWakeRunner, createRuntimeHandler, createSkillTools, createSkillsRegistry, db, detectAvailableProviders, readCodexAccessToken, registerToolProvider, unregisterToolProvider } from "@electric-ax/agents-runtime";
|
|
7
|
-
import {
|
|
7
|
+
import { braveSearchTool, createBashTool, createEditTool, createEventSourceTools, createFetchUrlTool, createReadFileTool, createSendTool, createWriteTool, fetchUrlTool } from "@electric-ax/agents-runtime/tools";
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
import { createHash } from "node:crypto";
|
|
10
10
|
import fs$1 from "node:fs/promises";
|
|
@@ -13,7 +13,6 @@ import { Type } from "@sinclair/typebox";
|
|
|
13
13
|
import { load } from "sqlite-vec";
|
|
14
14
|
import { nanoid } from "nanoid";
|
|
15
15
|
import { getModels } from "@mariozechner/pi-ai";
|
|
16
|
-
import { braveSearchTool, createBashTool, createEditTool, createFetchUrlTool, createReadFileTool, createSendTool, createWriteTool, fetchUrlTool } from "@electric-ax/agents-runtime/tools";
|
|
17
16
|
import { bridgeMcpTool, buildPromptTools, buildResourceTools, createRegistry, keychainPersistence, loadConfig, mcp, watchConfig } from "@electric-ax/agents-mcp";
|
|
18
17
|
|
|
19
18
|
//#region src/log.ts
|
|
@@ -652,9 +651,9 @@ function createHortonDocsSupport(workingDirectory, opts = {}) {
|
|
|
652
651
|
},
|
|
653
652
|
createSearchTool() {
|
|
654
653
|
return {
|
|
655
|
-
name: `
|
|
656
|
-
label: `Search
|
|
657
|
-
description: `Run a hybrid BM25 plus vector search over the local
|
|
654
|
+
name: `search_electric_agents_docs`,
|
|
655
|
+
label: `Search Electric Agents Docs`,
|
|
656
|
+
description: `Run a hybrid BM25 plus vector search over the local Electric Agents documentation index.`,
|
|
658
657
|
parameters: Type.Object({
|
|
659
658
|
query: Type.String({ description: `The docs question or search query to run.` }),
|
|
660
659
|
limit: Type.Optional(Type.Number({
|
|
@@ -785,12 +784,14 @@ const REASONING_EFFORT_VALUES = [
|
|
|
785
784
|
const DEFAULT_ANTHROPIC_MODEL = `claude-sonnet-4-6`;
|
|
786
785
|
const DEFAULT_OPENAI_MODEL = `gpt-4.1`;
|
|
787
786
|
const DEFAULT_CODEX_MODEL = `gpt-5.4`;
|
|
787
|
+
const DEFAULT_DEEPSEEK_MODEL = `deepseek-v4-flash`;
|
|
788
788
|
function modelValue(provider, id) {
|
|
789
789
|
return `${provider}:${id}`;
|
|
790
790
|
}
|
|
791
791
|
function providerLabel(provider) {
|
|
792
792
|
if (provider === `anthropic`) return `Anthropic`;
|
|
793
793
|
if (provider === `openai-codex`) return `OpenAI Codex`;
|
|
794
|
+
if (provider === `deepseek`) return `DeepSeek`;
|
|
794
795
|
return `OpenAI`;
|
|
795
796
|
}
|
|
796
797
|
function configuredProviders() {
|
|
@@ -817,6 +818,9 @@ async function fetchAvailableModelIds(provider) {
|
|
|
817
818
|
"anthropic-version": `2023-06-01`
|
|
818
819
|
},
|
|
819
820
|
signal: AbortSignal.timeout(3e3)
|
|
821
|
+
}) : provider === `deepseek` ? await fetch(`https://api.deepseek.com/v1/models`, {
|
|
822
|
+
headers: { authorization: `Bearer ${process.env.DEEPSEEK_API_KEY ?? ``}` },
|
|
823
|
+
signal: AbortSignal.timeout(3e3)
|
|
820
824
|
}) : await fetch(`https://api.openai.com/v1/models`, {
|
|
821
825
|
headers: { authorization: `Bearer ${process.env.OPENAI_API_KEY ?? ``}` },
|
|
822
826
|
signal: AbortSignal.timeout(3e3)
|
|
@@ -877,7 +881,7 @@ async function createBuiltinModelCatalog(options = {}) {
|
|
|
877
881
|
if (providers.length === 0 && options.allowMockFallback) return mockFallbackCatalog();
|
|
878
882
|
const choices = (await Promise.all(providers.map((provider) => choicesForProvider(provider)))).flat();
|
|
879
883
|
if (choices.length === 0) return options.allowMockFallback ? mockFallbackCatalog() : null;
|
|
880
|
-
const defaultChoice = choices.find((choice) => choice.provider === `anthropic` && choice.id === DEFAULT_ANTHROPIC_MODEL) ?? choices.find((choice) => choice.provider === `openai` && choice.id === DEFAULT_OPENAI_MODEL) ?? choices.find((choice) => choice.provider === `openai-codex` && choice.id === DEFAULT_CODEX_MODEL) ?? choices[0];
|
|
884
|
+
const defaultChoice = choices.find((choice) => choice.provider === `anthropic` && choice.id === DEFAULT_ANTHROPIC_MODEL) ?? choices.find((choice) => choice.provider === `openai` && choice.id === DEFAULT_OPENAI_MODEL) ?? choices.find((choice) => choice.provider === `openai-codex` && choice.id === DEFAULT_CODEX_MODEL) ?? choices.find((choice) => choice.provider === `deepseek` && choice.id === DEFAULT_DEEPSEEK_MODEL) ?? choices[0];
|
|
881
885
|
return {
|
|
882
886
|
choices,
|
|
883
887
|
defaultChoice
|
|
@@ -997,9 +1001,10 @@ async function generateTitle(userMessage, llmCall, onFallback) {
|
|
|
997
1001
|
}
|
|
998
1002
|
}
|
|
999
1003
|
function buildHortonSystemPrompt(workingDirectory, opts = {}) {
|
|
1000
|
-
const docsTools = opts.hasDocsSupport ? `\n-
|
|
1004
|
+
const docsTools = opts.hasDocsSupport ? `\n- search_electric_agents_docs: hybrid search over the built-in Electric Agents docs index` : ``;
|
|
1005
|
+
const eventSourceTools = opts.hasEventSourceTools ? `\n- list_event_sources: list external webhook/event feeds you can subscribe to, including available buckets and parameters\n- subscribe_event_source: subscribe yourself to one of those feeds or buckets so matching future events wake you\n- list_event_source_subscriptions: list your active event source subscriptions\n- unsubscribe_event_source: remove one of your event source subscriptions by id` : ``;
|
|
1001
1006
|
const skillsTools = opts.hasSkills ? `\n- use_skill: load a skill (knowledge, instructions, or a tutorial) into your context to help with the user's request\n- remove_skill: unload a skill from context when you're done with it` : ``;
|
|
1002
|
-
const docsGuidance = opts.hasDocsSupport ? `\n- For ANY question about Electric Agents
|
|
1007
|
+
const docsGuidance = opts.hasDocsSupport ? `\n- For ANY question about Electric Agents or this framework, ALWAYS use search_electric_agents_docs FIRST. Do not use web_search or fetch_url for Electric Agents topics unless the docs search returns no useful results.\n- The search tool returns chunk content directly — you do not need to read the source files.\n- Use repo read/bash tools only for non-doc files or when you need to inspect exact implementation code in the workspace.` : ``;
|
|
1003
1008
|
const skillsGuidance = opts.hasSkills ? `\n# Skills\nYou have access to skills — specialized knowledge and guided workflows you can load on demand. Your context includes a skills catalog listing what's available. When the user's request matches a skill's description or keywords, load it with use_skill.
|
|
1004
1009
|
|
|
1005
1010
|
Some skills are user-invocable — the user can trigger them with a slash command like \`/quickstart\`. When you see a message starting with \`/\` followed by a skill name, load that skill immediately with use_skill. Pass any text after the skill name as args.
|
|
@@ -1018,7 +1023,7 @@ Do NOT load a skill and then ignore its instructions. The skill is there because
|
|
|
1018
1023
|
When a user is new or asks how to get started with Electric Agents, **don't assume a single path**. Present the options and let them choose:
|
|
1019
1024
|
|
|
1020
1025
|
- **Learn the concepts first** → Explain what Electric Agents is, answer questions, point to docs.
|
|
1021
|
-
Use
|
|
1026
|
+
Use search_electric_agents_docs to look up answers. Only load the quickstart skill if the user explicitly asks for a hands-on guided tutorial.
|
|
1022
1027
|
|
|
1023
1028
|
- **Hands-on guided tutorial** → Load the quickstart skill (or tell them to type \`/quickstart\`).
|
|
1024
1029
|
This is a step-by-step build that takes them from zero to a running app.
|
|
@@ -1028,11 +1033,11 @@ When a user is new or asks how to get started with Electric Agents, **don't assu
|
|
|
1028
1033
|
This sets up project structure and orients them in the codebase.
|
|
1029
1034
|
|
|
1030
1035
|
- **Have a specific question?** → Answer it directly.
|
|
1031
|
-
Use
|
|
1036
|
+
Use search_electric_agents_docs first, then fall back to fetch_url or general knowledge if needed.
|
|
1032
1037
|
|
|
1033
1038
|
Don't force onboarding. If someone just wants to chat or code, let them. When in doubt, ask what they'd like to do rather than picking a path for them.`;
|
|
1034
1039
|
const docsUrlGuidance = opts.docsUrl ? `\n# Electric Agents documentation
|
|
1035
|
-
- ${opts.hasDocsSupport ? `If
|
|
1040
|
+
- ${opts.hasDocsSupport ? `If search_electric_agents_docs is available, use it first (faster, hybrid search).` : `Use fetch_url to look up documentation pages.`}
|
|
1036
1041
|
- The Electric Agents docs site is at ${opts.docsUrl}
|
|
1037
1042
|
- The docs site covers: Usage (entity definition, handlers, tools, state, spawning, coordination, waking, shared state, client integration, app setup), Reference (handler context, entity definitions, configurations, tools, state proxies, wake events, registries), Entities (Horton, Worker), and Patterns (Manager-Worker, Pipeline, Map-Reduce, Dispatcher, Blackboard, Reactive Observers).
|
|
1038
1043
|
- For general coding questions unrelated to Electric Agents, use web_search or your own knowledge.` : ``;
|
|
@@ -1051,8 +1056,8 @@ When a user opens with a greeting ("hi", "hello", "hey", etc.) or a broad statem
|
|
|
1051
1056
|
- web_search: search the web
|
|
1052
1057
|
- fetch_url: fetch and convert a URL to markdown
|
|
1053
1058
|
- spawn_worker: dispatch a subagent for an isolated task
|
|
1054
|
-
- send: send a message to an Electric Agent/entity
|
|
1055
|
-
${docsTools}${skillsTools}
|
|
1059
|
+
- send: send a message to an Electric Agent/entity. To schedule future work for yourself, call send with self: true and afterMs.
|
|
1060
|
+
${eventSourceTools}${docsTools}${skillsTools}
|
|
1056
1061
|
|
|
1057
1062
|
# Working with files
|
|
1058
1063
|
- Prefer edit over write when modifying existing files.
|
|
@@ -1085,6 +1090,11 @@ Report outcomes faithfully. If a command failed, say so with the relevant output
|
|
|
1085
1090
|
Working directory: ${workingDirectory}
|
|
1086
1091
|
The current year is ${new Date().getFullYear()}.`;
|
|
1087
1092
|
}
|
|
1093
|
+
function getToolName(tool) {
|
|
1094
|
+
if (typeof tool !== `object` || tool === null) return null;
|
|
1095
|
+
const name = tool.name;
|
|
1096
|
+
return typeof name === `string` ? name : null;
|
|
1097
|
+
}
|
|
1088
1098
|
function createHortonTools(workingDirectory, ctx, readSet, opts = {}) {
|
|
1089
1099
|
return [
|
|
1090
1100
|
createBashTool(workingDirectory),
|
|
@@ -1099,7 +1109,7 @@ function createHortonTools(workingDirectory, ctx, readSet, opts = {}) {
|
|
|
1099
1109
|
logPrefix: opts.logPrefix ?? `[horton]`
|
|
1100
1110
|
})] : [fetchUrlTool],
|
|
1101
1111
|
createSpawnWorkerTool(ctx, opts.modelConfig),
|
|
1102
|
-
createSendTool(ctx.send),
|
|
1112
|
+
createSendTool(ctx.send, { selfEntityUrl: ctx.entityUrl }),
|
|
1103
1113
|
...opts.docsSearchTool ? [opts.docsSearchTool] : []
|
|
1104
1114
|
];
|
|
1105
1115
|
}
|
|
@@ -1113,11 +1123,14 @@ function payloadToTitleText(payload) {
|
|
|
1113
1123
|
return String(payload);
|
|
1114
1124
|
}
|
|
1115
1125
|
async function extractFirstUserMessage(ctx) {
|
|
1116
|
-
const firstMessage =
|
|
1126
|
+
const firstMessage = ctx.db.collections.inbox.toArray.filter((message) => message.from !== `system`).sort((left, right) => messageSeq(left) - messageSeq(right))[0];
|
|
1117
1127
|
if (!firstMessage) return null;
|
|
1118
1128
|
const text = payloadToTitleText(firstMessage.payload);
|
|
1119
1129
|
return text.length > 0 ? text : null;
|
|
1120
1130
|
}
|
|
1131
|
+
function messageSeq(message) {
|
|
1132
|
+
return typeof message._seq === `number` ? message._seq : -1;
|
|
1133
|
+
}
|
|
1121
1134
|
function readAgentsMd(workingDirectory) {
|
|
1122
1135
|
const agentsMdPath = path.join(workingDirectory, `AGENTS.md`);
|
|
1123
1136
|
try {
|
|
@@ -1151,6 +1164,7 @@ function createAssistantHandler(options) {
|
|
|
1151
1164
|
...skillsRegistry && skillsRegistry.catalog.size > 0 ? createSkillTools(skillsRegistry, ctx) : [],
|
|
1152
1165
|
...mcp.tools()
|
|
1153
1166
|
];
|
|
1167
|
+
const hasEventSourceTools = tools.some((tool) => getToolName(tool) === `list_event_sources`);
|
|
1154
1168
|
const titlePromise = ctx.firstWake && !ctx.tags.title ? (async () => {
|
|
1155
1169
|
const firstUserMessage = await extractFirstUserMessage(ctx);
|
|
1156
1170
|
if (!firstUserMessage) return;
|
|
@@ -1237,7 +1251,8 @@ function createAssistantHandler(options) {
|
|
|
1237
1251
|
hasSkills,
|
|
1238
1252
|
docsUrl,
|
|
1239
1253
|
modelProvider: modelConfig.provider,
|
|
1240
|
-
modelId: String(modelConfig.model)
|
|
1254
|
+
modelId: String(modelConfig.model),
|
|
1255
|
+
hasEventSourceTools
|
|
1241
1256
|
}),
|
|
1242
1257
|
...modelConfig,
|
|
1243
1258
|
tools,
|
|
@@ -1277,15 +1292,7 @@ function registerHorton(registry, options) {
|
|
|
1277
1292
|
creationSchema: hortonCreationSchema,
|
|
1278
1293
|
handler: assistantHandler
|
|
1279
1294
|
});
|
|
1280
|
-
|
|
1281
|
-
if (streamFn) {
|
|
1282
|
-
registry.define(`chat`, {
|
|
1283
|
-
description: `Compatibility alias for the built-in assistant type.`,
|
|
1284
|
-
handler: assistantHandler
|
|
1285
|
-
});
|
|
1286
|
-
typeNames.push(`chat`);
|
|
1287
|
-
}
|
|
1288
|
-
return typeNames;
|
|
1295
|
+
return [`horton`];
|
|
1289
1296
|
}
|
|
1290
1297
|
|
|
1291
1298
|
//#endregion
|
|
@@ -1349,7 +1356,7 @@ function buildToolsForWorker(tools, workingDirectory, ctx, readSet) {
|
|
|
1349
1356
|
out.push(createSpawnWorkerTool(ctx));
|
|
1350
1357
|
break;
|
|
1351
1358
|
case `send`:
|
|
1352
|
-
out.push(createSendTool(ctx.send));
|
|
1359
|
+
out.push(createSendTool(ctx.send, { selfEntityUrl: ctx.entityUrl }));
|
|
1353
1360
|
break;
|
|
1354
1361
|
}
|
|
1355
1362
|
return out;
|
|
@@ -1477,6 +1484,27 @@ function registerWorker(registry, options) {
|
|
|
1477
1484
|
|
|
1478
1485
|
//#endregion
|
|
1479
1486
|
//#region src/bootstrap.ts
|
|
1487
|
+
function toolName(tool) {
|
|
1488
|
+
return typeof tool.name === `string` ? tool.name : null;
|
|
1489
|
+
}
|
|
1490
|
+
function dedupeToolsByName(tools) {
|
|
1491
|
+
const seen = new Set();
|
|
1492
|
+
const deduped = [];
|
|
1493
|
+
for (const tool of tools) {
|
|
1494
|
+
const name = toolName(tool);
|
|
1495
|
+
if (name && seen.has(name)) continue;
|
|
1496
|
+
if (name) seen.add(name);
|
|
1497
|
+
deduped.push(tool);
|
|
1498
|
+
}
|
|
1499
|
+
return deduped;
|
|
1500
|
+
}
|
|
1501
|
+
function createBuiltinElectricTools(custom) {
|
|
1502
|
+
return async (context) => {
|
|
1503
|
+
const builtinTools = createEventSourceTools(context);
|
|
1504
|
+
const customTools = custom ? await custom(context) : [];
|
|
1505
|
+
return dedupeToolsByName([...builtinTools, ...customTools]);
|
|
1506
|
+
};
|
|
1507
|
+
}
|
|
1480
1508
|
async function createBuiltinAgentHandler(options) {
|
|
1481
1509
|
const { agentServerUrl, serveEndpoint, workingDirectory, streamFn, createElectricTools, publicUrl, runtimeName, baseSkillsDir: baseSkillsDirOverride, serverHeaders, defaultDispatchPolicyForType } = options;
|
|
1482
1510
|
const modelCatalog = await createBuiltinModelCatalog({ allowMockFallback: Boolean(streamFn) });
|
|
@@ -1519,7 +1547,7 @@ async function createBuiltinAgentHandler(options) {
|
|
|
1519
1547
|
defaultDispatchPolicyForType,
|
|
1520
1548
|
serverHeaders,
|
|
1521
1549
|
idleTimeout: 5 * 6e4,
|
|
1522
|
-
createElectricTools,
|
|
1550
|
+
createElectricTools: createBuiltinElectricTools(createElectricTools),
|
|
1523
1551
|
publicUrl,
|
|
1524
1552
|
name: runtimeName ?? `builtin-agents`
|
|
1525
1553
|
});
|
package/dist/index.cjs
CHANGED
|
@@ -26,9 +26,9 @@ const require_server_headers = require('./server-headers-65vIhxvJ.cjs');
|
|
|
26
26
|
const node_path = __toESM(require("node:path"));
|
|
27
27
|
const node_url = __toESM(require("node:url"));
|
|
28
28
|
const __electric_ax_agents_runtime = __toESM(require("@electric-ax/agents-runtime"));
|
|
29
|
+
const __electric_ax_agents_runtime_tools = __toESM(require("@electric-ax/agents-runtime/tools"));
|
|
29
30
|
const node_fs = __toESM(require("node:fs"));
|
|
30
31
|
const pino = __toESM(require("pino"));
|
|
31
|
-
const __durable_streams_state = __toESM(require("@durable-streams/state"));
|
|
32
32
|
const zod = __toESM(require("zod"));
|
|
33
33
|
const node_crypto = __toESM(require("node:crypto"));
|
|
34
34
|
const node_fs_promises = __toESM(require("node:fs/promises"));
|
|
@@ -37,7 +37,6 @@ const __sinclair_typebox = __toESM(require("@sinclair/typebox"));
|
|
|
37
37
|
const sqlite_vec = __toESM(require("sqlite-vec"));
|
|
38
38
|
const nanoid = __toESM(require("nanoid"));
|
|
39
39
|
const __mariozechner_pi_ai = __toESM(require("@mariozechner/pi-ai"));
|
|
40
|
-
const __electric_ax_agents_runtime_tools = __toESM(require("@electric-ax/agents-runtime/tools"));
|
|
41
40
|
const __electric_ax_agents_mcp = __toESM(require("@electric-ax/agents-mcp"));
|
|
42
41
|
|
|
43
42
|
//#region src/log.ts
|
|
@@ -676,9 +675,9 @@ function createHortonDocsSupport(workingDirectory, opts = {}) {
|
|
|
676
675
|
},
|
|
677
676
|
createSearchTool() {
|
|
678
677
|
return {
|
|
679
|
-
name: `
|
|
680
|
-
label: `Search
|
|
681
|
-
description: `Run a hybrid BM25 plus vector search over the local
|
|
678
|
+
name: `search_electric_agents_docs`,
|
|
679
|
+
label: `Search Electric Agents Docs`,
|
|
680
|
+
description: `Run a hybrid BM25 plus vector search over the local Electric Agents documentation index.`,
|
|
682
681
|
parameters: __sinclair_typebox.Type.Object({
|
|
683
682
|
query: __sinclair_typebox.Type.String({ description: `The docs question or search query to run.` }),
|
|
684
683
|
limit: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.Number({
|
|
@@ -809,12 +808,14 @@ const REASONING_EFFORT_VALUES = [
|
|
|
809
808
|
const DEFAULT_ANTHROPIC_MODEL = `claude-sonnet-4-6`;
|
|
810
809
|
const DEFAULT_OPENAI_MODEL = `gpt-4.1`;
|
|
811
810
|
const DEFAULT_CODEX_MODEL = `gpt-5.4`;
|
|
811
|
+
const DEFAULT_DEEPSEEK_MODEL = `deepseek-v4-flash`;
|
|
812
812
|
function modelValue(provider, id) {
|
|
813
813
|
return `${provider}:${id}`;
|
|
814
814
|
}
|
|
815
815
|
function providerLabel(provider) {
|
|
816
816
|
if (provider === `anthropic`) return `Anthropic`;
|
|
817
817
|
if (provider === `openai-codex`) return `OpenAI Codex`;
|
|
818
|
+
if (provider === `deepseek`) return `DeepSeek`;
|
|
818
819
|
return `OpenAI`;
|
|
819
820
|
}
|
|
820
821
|
function configuredProviders() {
|
|
@@ -841,6 +842,9 @@ async function fetchAvailableModelIds(provider) {
|
|
|
841
842
|
"anthropic-version": `2023-06-01`
|
|
842
843
|
},
|
|
843
844
|
signal: AbortSignal.timeout(3e3)
|
|
845
|
+
}) : provider === `deepseek` ? await fetch(`https://api.deepseek.com/v1/models`, {
|
|
846
|
+
headers: { authorization: `Bearer ${process.env.DEEPSEEK_API_KEY ?? ``}` },
|
|
847
|
+
signal: AbortSignal.timeout(3e3)
|
|
844
848
|
}) : await fetch(`https://api.openai.com/v1/models`, {
|
|
845
849
|
headers: { authorization: `Bearer ${process.env.OPENAI_API_KEY ?? ``}` },
|
|
846
850
|
signal: AbortSignal.timeout(3e3)
|
|
@@ -901,7 +905,7 @@ async function createBuiltinModelCatalog(options = {}) {
|
|
|
901
905
|
if (providers.length === 0 && options.allowMockFallback) return mockFallbackCatalog();
|
|
902
906
|
const choices = (await Promise.all(providers.map((provider) => choicesForProvider(provider)))).flat();
|
|
903
907
|
if (choices.length === 0) return options.allowMockFallback ? mockFallbackCatalog() : null;
|
|
904
|
-
const defaultChoice = choices.find((choice) => choice.provider === `anthropic` && choice.id === DEFAULT_ANTHROPIC_MODEL) ?? choices.find((choice) => choice.provider === `openai` && choice.id === DEFAULT_OPENAI_MODEL) ?? choices.find((choice) => choice.provider === `openai-codex` && choice.id === DEFAULT_CODEX_MODEL) ?? choices[0];
|
|
908
|
+
const defaultChoice = choices.find((choice) => choice.provider === `anthropic` && choice.id === DEFAULT_ANTHROPIC_MODEL) ?? choices.find((choice) => choice.provider === `openai` && choice.id === DEFAULT_OPENAI_MODEL) ?? choices.find((choice) => choice.provider === `openai-codex` && choice.id === DEFAULT_CODEX_MODEL) ?? choices.find((choice) => choice.provider === `deepseek` && choice.id === DEFAULT_DEEPSEEK_MODEL) ?? choices[0];
|
|
905
909
|
return {
|
|
906
910
|
choices,
|
|
907
911
|
defaultChoice
|
|
@@ -1022,9 +1026,10 @@ async function generateTitle(userMessage, llmCall, onFallback) {
|
|
|
1022
1026
|
}
|
|
1023
1027
|
}
|
|
1024
1028
|
function buildHortonSystemPrompt(workingDirectory, opts = {}) {
|
|
1025
|
-
const docsTools = opts.hasDocsSupport ? `\n-
|
|
1029
|
+
const docsTools = opts.hasDocsSupport ? `\n- search_electric_agents_docs: hybrid search over the built-in Electric Agents docs index` : ``;
|
|
1030
|
+
const eventSourceTools = opts.hasEventSourceTools ? `\n- list_event_sources: list external webhook/event feeds you can subscribe to, including available buckets and parameters\n- subscribe_event_source: subscribe yourself to one of those feeds or buckets so matching future events wake you\n- list_event_source_subscriptions: list your active event source subscriptions\n- unsubscribe_event_source: remove one of your event source subscriptions by id` : ``;
|
|
1026
1031
|
const skillsTools = opts.hasSkills ? `\n- use_skill: load a skill (knowledge, instructions, or a tutorial) into your context to help with the user's request\n- remove_skill: unload a skill from context when you're done with it` : ``;
|
|
1027
|
-
const docsGuidance = opts.hasDocsSupport ? `\n- For ANY question about Electric Agents
|
|
1032
|
+
const docsGuidance = opts.hasDocsSupport ? `\n- For ANY question about Electric Agents or this framework, ALWAYS use search_electric_agents_docs FIRST. Do not use web_search or fetch_url for Electric Agents topics unless the docs search returns no useful results.\n- The search tool returns chunk content directly — you do not need to read the source files.\n- Use repo read/bash tools only for non-doc files or when you need to inspect exact implementation code in the workspace.` : ``;
|
|
1028
1033
|
const skillsGuidance = opts.hasSkills ? `\n# Skills\nYou have access to skills — specialized knowledge and guided workflows you can load on demand. Your context includes a skills catalog listing what's available. When the user's request matches a skill's description or keywords, load it with use_skill.
|
|
1029
1034
|
|
|
1030
1035
|
Some skills are user-invocable — the user can trigger them with a slash command like \`/quickstart\`. When you see a message starting with \`/\` followed by a skill name, load that skill immediately with use_skill. Pass any text after the skill name as args.
|
|
@@ -1043,7 +1048,7 @@ Do NOT load a skill and then ignore its instructions. The skill is there because
|
|
|
1043
1048
|
When a user is new or asks how to get started with Electric Agents, **don't assume a single path**. Present the options and let them choose:
|
|
1044
1049
|
|
|
1045
1050
|
- **Learn the concepts first** → Explain what Electric Agents is, answer questions, point to docs.
|
|
1046
|
-
Use
|
|
1051
|
+
Use search_electric_agents_docs to look up answers. Only load the quickstart skill if the user explicitly asks for a hands-on guided tutorial.
|
|
1047
1052
|
|
|
1048
1053
|
- **Hands-on guided tutorial** → Load the quickstart skill (or tell them to type \`/quickstart\`).
|
|
1049
1054
|
This is a step-by-step build that takes them from zero to a running app.
|
|
@@ -1053,11 +1058,11 @@ When a user is new or asks how to get started with Electric Agents, **don't assu
|
|
|
1053
1058
|
This sets up project structure and orients them in the codebase.
|
|
1054
1059
|
|
|
1055
1060
|
- **Have a specific question?** → Answer it directly.
|
|
1056
|
-
Use
|
|
1061
|
+
Use search_electric_agents_docs first, then fall back to fetch_url or general knowledge if needed.
|
|
1057
1062
|
|
|
1058
1063
|
Don't force onboarding. If someone just wants to chat or code, let them. When in doubt, ask what they'd like to do rather than picking a path for them.`;
|
|
1059
1064
|
const docsUrlGuidance = opts.docsUrl ? `\n# Electric Agents documentation
|
|
1060
|
-
- ${opts.hasDocsSupport ? `If
|
|
1065
|
+
- ${opts.hasDocsSupport ? `If search_electric_agents_docs is available, use it first (faster, hybrid search).` : `Use fetch_url to look up documentation pages.`}
|
|
1061
1066
|
- The Electric Agents docs site is at ${opts.docsUrl}
|
|
1062
1067
|
- The docs site covers: Usage (entity definition, handlers, tools, state, spawning, coordination, waking, shared state, client integration, app setup), Reference (handler context, entity definitions, configurations, tools, state proxies, wake events, registries), Entities (Horton, Worker), and Patterns (Manager-Worker, Pipeline, Map-Reduce, Dispatcher, Blackboard, Reactive Observers).
|
|
1063
1068
|
- For general coding questions unrelated to Electric Agents, use web_search or your own knowledge.` : ``;
|
|
@@ -1076,8 +1081,8 @@ When a user opens with a greeting ("hi", "hello", "hey", etc.) or a broad statem
|
|
|
1076
1081
|
- web_search: search the web
|
|
1077
1082
|
- fetch_url: fetch and convert a URL to markdown
|
|
1078
1083
|
- spawn_worker: dispatch a subagent for an isolated task
|
|
1079
|
-
- send: send a message to an Electric Agent/entity
|
|
1080
|
-
${docsTools}${skillsTools}
|
|
1084
|
+
- send: send a message to an Electric Agent/entity. To schedule future work for yourself, call send with self: true and afterMs.
|
|
1085
|
+
${eventSourceTools}${docsTools}${skillsTools}
|
|
1081
1086
|
|
|
1082
1087
|
# Working with files
|
|
1083
1088
|
- Prefer edit over write when modifying existing files.
|
|
@@ -1110,6 +1115,11 @@ Report outcomes faithfully. If a command failed, say so with the relevant output
|
|
|
1110
1115
|
Working directory: ${workingDirectory}
|
|
1111
1116
|
The current year is ${new Date().getFullYear()}.`;
|
|
1112
1117
|
}
|
|
1118
|
+
function getToolName(tool) {
|
|
1119
|
+
if (typeof tool !== `object` || tool === null) return null;
|
|
1120
|
+
const name = tool.name;
|
|
1121
|
+
return typeof name === `string` ? name : null;
|
|
1122
|
+
}
|
|
1113
1123
|
function createHortonTools(workingDirectory, ctx, readSet, opts = {}) {
|
|
1114
1124
|
return [
|
|
1115
1125
|
(0, __electric_ax_agents_runtime_tools.createBashTool)(workingDirectory),
|
|
@@ -1124,7 +1134,7 @@ function createHortonTools(workingDirectory, ctx, readSet, opts = {}) {
|
|
|
1124
1134
|
logPrefix: opts.logPrefix ?? `[horton]`
|
|
1125
1135
|
})] : [__electric_ax_agents_runtime_tools.fetchUrlTool],
|
|
1126
1136
|
createSpawnWorkerTool(ctx, opts.modelConfig),
|
|
1127
|
-
(0, __electric_ax_agents_runtime_tools.createSendTool)(ctx.send),
|
|
1137
|
+
(0, __electric_ax_agents_runtime_tools.createSendTool)(ctx.send, { selfEntityUrl: ctx.entityUrl }),
|
|
1128
1138
|
...opts.docsSearchTool ? [opts.docsSearchTool] : []
|
|
1129
1139
|
];
|
|
1130
1140
|
}
|
|
@@ -1138,11 +1148,14 @@ function payloadToTitleText(payload) {
|
|
|
1138
1148
|
return String(payload);
|
|
1139
1149
|
}
|
|
1140
1150
|
async function extractFirstUserMessage(ctx) {
|
|
1141
|
-
const firstMessage =
|
|
1151
|
+
const firstMessage = ctx.db.collections.inbox.toArray.filter((message) => message.from !== `system`).sort((left, right) => messageSeq(left) - messageSeq(right))[0];
|
|
1142
1152
|
if (!firstMessage) return null;
|
|
1143
1153
|
const text = payloadToTitleText(firstMessage.payload);
|
|
1144
1154
|
return text.length > 0 ? text : null;
|
|
1145
1155
|
}
|
|
1156
|
+
function messageSeq(message) {
|
|
1157
|
+
return typeof message._seq === `number` ? message._seq : -1;
|
|
1158
|
+
}
|
|
1146
1159
|
function readAgentsMd(workingDirectory) {
|
|
1147
1160
|
const agentsMdPath = node_path.default.join(workingDirectory, `AGENTS.md`);
|
|
1148
1161
|
try {
|
|
@@ -1176,6 +1189,7 @@ function createAssistantHandler(options) {
|
|
|
1176
1189
|
...skillsRegistry && skillsRegistry.catalog.size > 0 ? (0, __electric_ax_agents_runtime.createSkillTools)(skillsRegistry, ctx) : [],
|
|
1177
1190
|
...__electric_ax_agents_mcp.mcp.tools()
|
|
1178
1191
|
];
|
|
1192
|
+
const hasEventSourceTools = tools.some((tool) => getToolName(tool) === `list_event_sources`);
|
|
1179
1193
|
const titlePromise = ctx.firstWake && !ctx.tags.title ? (async () => {
|
|
1180
1194
|
const firstUserMessage = await extractFirstUserMessage(ctx);
|
|
1181
1195
|
if (!firstUserMessage) return;
|
|
@@ -1262,7 +1276,8 @@ function createAssistantHandler(options) {
|
|
|
1262
1276
|
hasSkills,
|
|
1263
1277
|
docsUrl,
|
|
1264
1278
|
modelProvider: modelConfig.provider,
|
|
1265
|
-
modelId: String(modelConfig.model)
|
|
1279
|
+
modelId: String(modelConfig.model),
|
|
1280
|
+
hasEventSourceTools
|
|
1266
1281
|
}),
|
|
1267
1282
|
...modelConfig,
|
|
1268
1283
|
tools,
|
|
@@ -1302,15 +1317,7 @@ function registerHorton(registry, options) {
|
|
|
1302
1317
|
creationSchema: hortonCreationSchema,
|
|
1303
1318
|
handler: assistantHandler
|
|
1304
1319
|
});
|
|
1305
|
-
|
|
1306
|
-
if (streamFn) {
|
|
1307
|
-
registry.define(`chat`, {
|
|
1308
|
-
description: `Compatibility alias for the built-in assistant type.`,
|
|
1309
|
-
handler: assistantHandler
|
|
1310
|
-
});
|
|
1311
|
-
typeNames.push(`chat`);
|
|
1312
|
-
}
|
|
1313
|
-
return typeNames;
|
|
1320
|
+
return [`horton`];
|
|
1314
1321
|
}
|
|
1315
1322
|
|
|
1316
1323
|
//#endregion
|
|
@@ -1374,7 +1381,7 @@ function buildToolsForWorker(tools, workingDirectory, ctx, readSet) {
|
|
|
1374
1381
|
out.push(createSpawnWorkerTool(ctx));
|
|
1375
1382
|
break;
|
|
1376
1383
|
case `send`:
|
|
1377
|
-
out.push((0, __electric_ax_agents_runtime_tools.createSendTool)(ctx.send));
|
|
1384
|
+
out.push((0, __electric_ax_agents_runtime_tools.createSendTool)(ctx.send, { selfEntityUrl: ctx.entityUrl }));
|
|
1378
1385
|
break;
|
|
1379
1386
|
}
|
|
1380
1387
|
return out;
|
|
@@ -1503,6 +1510,27 @@ function registerWorker(registry, options) {
|
|
|
1503
1510
|
//#endregion
|
|
1504
1511
|
//#region src/bootstrap.ts
|
|
1505
1512
|
const DEFAULT_BUILTIN_AGENT_HANDLER_PATH = `/_electric/builtin-agent-handler`;
|
|
1513
|
+
function toolName(tool) {
|
|
1514
|
+
return typeof tool.name === `string` ? tool.name : null;
|
|
1515
|
+
}
|
|
1516
|
+
function dedupeToolsByName(tools) {
|
|
1517
|
+
const seen = new Set();
|
|
1518
|
+
const deduped = [];
|
|
1519
|
+
for (const tool of tools) {
|
|
1520
|
+
const name = toolName(tool);
|
|
1521
|
+
if (name && seen.has(name)) continue;
|
|
1522
|
+
if (name) seen.add(name);
|
|
1523
|
+
deduped.push(tool);
|
|
1524
|
+
}
|
|
1525
|
+
return deduped;
|
|
1526
|
+
}
|
|
1527
|
+
function createBuiltinElectricTools(custom) {
|
|
1528
|
+
return async (context) => {
|
|
1529
|
+
const builtinTools = (0, __electric_ax_agents_runtime_tools.createEventSourceTools)(context);
|
|
1530
|
+
const customTools = custom ? await custom(context) : [];
|
|
1531
|
+
return dedupeToolsByName([...builtinTools, ...customTools]);
|
|
1532
|
+
};
|
|
1533
|
+
}
|
|
1506
1534
|
async function createBuiltinAgentHandler(options) {
|
|
1507
1535
|
const { agentServerUrl, serveEndpoint, workingDirectory, streamFn, createElectricTools, publicUrl, runtimeName, baseSkillsDir: baseSkillsDirOverride, serverHeaders, defaultDispatchPolicyForType } = options;
|
|
1508
1536
|
const modelCatalog = await createBuiltinModelCatalog({ allowMockFallback: Boolean(streamFn) });
|
|
@@ -1545,7 +1573,7 @@ async function createBuiltinAgentHandler(options) {
|
|
|
1545
1573
|
defaultDispatchPolicyForType,
|
|
1546
1574
|
serverHeaders,
|
|
1547
1575
|
idleTimeout: 5 * 6e4,
|
|
1548
|
-
createElectricTools,
|
|
1576
|
+
createElectricTools: createBuiltinElectricTools(createElectricTools),
|
|
1549
1577
|
publicUrl,
|
|
1550
1578
|
name: runtimeName ?? `builtin-agents`
|
|
1551
1579
|
});
|
|
@@ -1887,6 +1915,7 @@ Object.defineProperty(exports, 'braveSearchTool', {
|
|
|
1887
1915
|
exports.buildHortonSystemPrompt = buildHortonSystemPrompt
|
|
1888
1916
|
exports.createAgentHandler = createAgentHandler
|
|
1889
1917
|
exports.createBuiltinAgentHandler = createBuiltinAgentHandler
|
|
1918
|
+
exports.createBuiltinElectricTools = createBuiltinElectricTools
|
|
1890
1919
|
exports.createHortonDocsSupport = createHortonDocsSupport
|
|
1891
1920
|
exports.createHortonTools = createHortonTools
|
|
1892
1921
|
exports.createSpawnWorkerTool = createSpawnWorkerTool
|
package/dist/index.d.cts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { AgentConfig, AgentTool, AvailableProvider, DispatchPolicy, EntityRegistry,
|
|
2
|
-
import { ChangeEvent } from "@durable-streams/state";
|
|
1
|
+
import { AgentConfig, AgentTool, AvailableProvider, DispatchPolicy, EntityRegistry, HandlerContext, HeadersProvider, ProcessWakeConfig, PullWakeRunnerConfig, RuntimeHandler, SkillsRegistry, WakeEvent } from "@electric-ax/agents-runtime";
|
|
3
2
|
import { AgentTool as AgentTool$1, StreamFn } from "@mariozechner/pi-agent-core";
|
|
4
3
|
import { IncomingMessage, ServerResponse } from "node:http";
|
|
5
4
|
import { ListedEntry as McpListedEntry, McpConfig, McpServerConfig, McpServerConfig as McpServerConfig$1, Registry, Registry as McpRegistry, RegistrySnapshot, RegistrySubscriber } from "@electric-ax/agents-mcp";
|
|
5
|
+
import { ChangeEvent } from "@durable-streams/state";
|
|
6
6
|
import { braveSearchTool } from "@electric-ax/agents-runtime/tools";
|
|
7
7
|
|
|
8
8
|
//#region src/bootstrap.d.ts
|
|
@@ -14,6 +14,7 @@ interface AgentHandlerResult {
|
|
|
14
14
|
typeNames: Array<string>;
|
|
15
15
|
skillsRegistry: SkillsRegistry | null;
|
|
16
16
|
}
|
|
17
|
+
type BuiltinElectricToolsFactory = NonNullable<ProcessWakeConfig[`createElectricTools`]>;
|
|
17
18
|
interface BuiltinAgentHandlerOptions {
|
|
18
19
|
agentServerUrl: string;
|
|
19
20
|
serveEndpoint?: string;
|
|
@@ -25,43 +26,15 @@ interface BuiltinAgentHandlerOptions {
|
|
|
25
26
|
baseSkillsDir?: string;
|
|
26
27
|
serverHeaders?: HeadersProvider;
|
|
27
28
|
defaultDispatchPolicyForType?: (typeName: string) => DispatchPolicy | undefined;
|
|
28
|
-
createElectricTools?:
|
|
29
|
-
entityUrl: string;
|
|
30
|
-
entityType: string;
|
|
31
|
-
args: Readonly<Record<string, unknown>>;
|
|
32
|
-
db: EntityStreamDBWithActions;
|
|
33
|
-
events: Array<ChangeEvent>;
|
|
34
|
-
upsertCronSchedule: (opts: {
|
|
35
|
-
id: string;
|
|
36
|
-
expression: string;
|
|
37
|
-
timezone?: string;
|
|
38
|
-
payload?: unknown;
|
|
39
|
-
debounceMs?: number;
|
|
40
|
-
timeoutMs?: number;
|
|
41
|
-
}) => Promise<{
|
|
42
|
-
txid: string;
|
|
43
|
-
}>;
|
|
44
|
-
upsertFutureSendSchedule: (opts: {
|
|
45
|
-
id: string;
|
|
46
|
-
payload: unknown;
|
|
47
|
-
targetUrl?: string;
|
|
48
|
-
fireAt: string;
|
|
49
|
-
from?: string;
|
|
50
|
-
messageType?: string;
|
|
51
|
-
}) => Promise<{
|
|
52
|
-
txid: string;
|
|
53
|
-
}>;
|
|
54
|
-
deleteSchedule: (opts: {
|
|
55
|
-
id: string;
|
|
56
|
-
}) => Promise<{
|
|
57
|
-
txid: string;
|
|
58
|
-
}>;
|
|
59
|
-
}) => Array<AgentTool> | Promise<Array<AgentTool>>;
|
|
29
|
+
createElectricTools?: BuiltinElectricToolsFactory;
|
|
60
30
|
}
|
|
31
|
+
declare function createBuiltinElectricTools(custom?: BuiltinElectricToolsFactory): BuiltinElectricToolsFactory;
|
|
61
32
|
declare function createBuiltinAgentHandler(options: BuiltinAgentHandlerOptions): Promise<AgentHandlerResult | null>;
|
|
62
33
|
declare function createAgentHandler(agentServerUrl: string, workingDirectory?: string, streamFn?: StreamFn, createElectricTools?: BuiltinAgentHandlerOptions[`createElectricTools`], serveEndpoint?: string): Promise<AgentHandlerResult | null>;
|
|
63
34
|
declare function registerBuiltinAgentTypes(bootstrap: AgentHandlerResult): Promise<void>;
|
|
64
|
-
declare const registerAgentTypes: typeof registerBuiltinAgentTypes;
|
|
35
|
+
declare const registerAgentTypes: typeof registerBuiltinAgentTypes;
|
|
36
|
+
|
|
37
|
+
//#endregion
|
|
65
38
|
//#region src/server.d.ts
|
|
66
39
|
interface BuiltinAgentsServerOptions {
|
|
67
40
|
agentServerUrl: string;
|
|
@@ -106,38 +79,7 @@ interface BuiltinAgentsServerOptions {
|
|
|
106
79
|
loadProjectMcpConfig?: boolean;
|
|
107
80
|
/** Override for the built-in skills directory; required when embedders bundle this package. */
|
|
108
81
|
baseSkillsDir?: string;
|
|
109
|
-
createElectricTools?:
|
|
110
|
-
entityUrl: string;
|
|
111
|
-
entityType: string;
|
|
112
|
-
args: Readonly<Record<string, unknown>>;
|
|
113
|
-
db: EntityStreamDBWithActions;
|
|
114
|
-
events: Array<ChangeEvent>;
|
|
115
|
-
upsertCronSchedule: (opts: {
|
|
116
|
-
id: string;
|
|
117
|
-
expression: string;
|
|
118
|
-
timezone?: string;
|
|
119
|
-
payload?: unknown;
|
|
120
|
-
debounceMs?: number;
|
|
121
|
-
timeoutMs?: number;
|
|
122
|
-
}) => Promise<{
|
|
123
|
-
txid: string;
|
|
124
|
-
}>;
|
|
125
|
-
upsertFutureSendSchedule: (opts: {
|
|
126
|
-
id: string;
|
|
127
|
-
payload: unknown;
|
|
128
|
-
targetUrl?: string;
|
|
129
|
-
fireAt: string;
|
|
130
|
-
from?: string;
|
|
131
|
-
messageType?: string;
|
|
132
|
-
}) => Promise<{
|
|
133
|
-
txid: string;
|
|
134
|
-
}>;
|
|
135
|
-
deleteSchedule: (opts: {
|
|
136
|
-
id: string;
|
|
137
|
-
}) => Promise<{
|
|
138
|
-
txid: string;
|
|
139
|
-
}>;
|
|
140
|
-
}) => Array<AgentTool> | Promise<Array<AgentTool>>;
|
|
82
|
+
createElectricTools?: NonNullable<ProcessWakeConfig[`createElectricTools`]>;
|
|
141
83
|
}
|
|
142
84
|
declare class BuiltinAgentsServer {
|
|
143
85
|
private bootstrap;
|
|
@@ -154,7 +96,9 @@ declare class BuiltinAgentsServer {
|
|
|
154
96
|
start(): Promise<string>;
|
|
155
97
|
stop(): Promise<void>;
|
|
156
98
|
private registerPullWakeRunner;
|
|
157
|
-
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
//#endregion
|
|
158
102
|
//#region src/entrypoint-lib.d.ts
|
|
159
103
|
type EnvSource = Record<string, string | undefined>;
|
|
160
104
|
interface BuiltinAgentsEntrypointOptions extends BuiltinAgentsServerOptions {}
|
|
@@ -206,6 +150,7 @@ declare const HORTON_MODEL = "claude-sonnet-4-6";
|
|
|
206
150
|
declare function generateTitle(userMessage: string, llmCall: (prompt: string) => Promise<string>, onFallback?: (reason: string) => void): Promise<string>;
|
|
207
151
|
declare function buildHortonSystemPrompt(workingDirectory: string, opts?: {
|
|
208
152
|
hasDocsSupport?: boolean;
|
|
153
|
+
hasEventSourceTools?: boolean;
|
|
209
154
|
hasSkills?: boolean;
|
|
210
155
|
docsUrl?: string;
|
|
211
156
|
modelProvider?: string;
|
|
@@ -258,4 +203,4 @@ declare function createHortonDocsSupport(workingDirectory: string, opts?: {
|
|
|
258
203
|
}): HortonDocsSupport | null;
|
|
259
204
|
|
|
260
205
|
//#endregion
|
|
261
|
-
export { AgentHandlerResult, BuiltinAgentHandlerOptions, BuiltinAgentsEntrypointOptions, BuiltinAgentsEntrypointServer, BuiltinAgentsServer, BuiltinAgentsServerOptions, DEFAULT_BUILTIN_AGENT_HANDLER_PATH, HORTON_MODEL, McpConfig, McpListedEntry, McpRegistry, McpServerConfig, RegistrySnapshot, RegistrySubscriber, RunBuiltinAgentsEntrypointOptions, WORKER_TOOL_NAMES, WorkerToolName, braveSearchTool, buildHortonSystemPrompt, createAgentHandler, createBuiltinAgentHandler, createHortonDocsSupport, createHortonTools, createSpawnWorkerTool, generateTitle, registerAgentTypes, registerBuiltinAgentTypes, registerHorton, registerWorker, resolveBuiltinAgentsEntrypointOptions, runBuiltinAgentsEntrypoint };
|
|
206
|
+
export { AgentHandlerResult, BuiltinAgentHandlerOptions, BuiltinAgentsEntrypointOptions, BuiltinAgentsEntrypointServer, BuiltinAgentsServer, BuiltinAgentsServerOptions, BuiltinElectricToolsFactory, DEFAULT_BUILTIN_AGENT_HANDLER_PATH, HORTON_MODEL, McpConfig, McpListedEntry, McpRegistry, McpServerConfig, RegistrySnapshot, RegistrySubscriber, RunBuiltinAgentsEntrypointOptions, WORKER_TOOL_NAMES, WorkerToolName, braveSearchTool, buildHortonSystemPrompt, createAgentHandler, createBuiltinAgentHandler, createBuiltinElectricTools, createHortonDocsSupport, createHortonTools, createSpawnWorkerTool, generateTitle, registerAgentTypes, registerBuiltinAgentTypes, registerHorton, registerWorker, resolveBuiltinAgentsEntrypointOptions, runBuiltinAgentsEntrypoint };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { AgentConfig, AgentTool, AvailableProvider, DispatchPolicy, EntityRegistry,
|
|
2
|
-
import { ChangeEvent } from "@durable-streams/state";
|
|
1
|
+
import { AgentConfig, AgentTool, AvailableProvider, DispatchPolicy, EntityRegistry, HandlerContext, HeadersProvider, ProcessWakeConfig, PullWakeRunnerConfig, RuntimeHandler, SkillsRegistry, WakeEvent } from "@electric-ax/agents-runtime";
|
|
3
2
|
import { braveSearchTool } from "@electric-ax/agents-runtime/tools";
|
|
4
3
|
import { ListedEntry as McpListedEntry, McpConfig, McpServerConfig, McpServerConfig as McpServerConfig$1, Registry, Registry as McpRegistry, RegistrySnapshot, RegistrySubscriber } from "@electric-ax/agents-mcp";
|
|
5
4
|
import { AgentTool as AgentTool$1, StreamFn } from "@mariozechner/pi-agent-core";
|
|
6
5
|
import { IncomingMessage, ServerResponse } from "node:http";
|
|
6
|
+
import { ChangeEvent } from "@durable-streams/state";
|
|
7
7
|
|
|
8
8
|
//#region src/bootstrap.d.ts
|
|
9
9
|
declare const DEFAULT_BUILTIN_AGENT_HANDLER_PATH = "/_electric/builtin-agent-handler";
|
|
@@ -14,6 +14,7 @@ interface AgentHandlerResult {
|
|
|
14
14
|
typeNames: Array<string>;
|
|
15
15
|
skillsRegistry: SkillsRegistry | null;
|
|
16
16
|
}
|
|
17
|
+
type BuiltinElectricToolsFactory = NonNullable<ProcessWakeConfig[`createElectricTools`]>;
|
|
17
18
|
interface BuiltinAgentHandlerOptions {
|
|
18
19
|
agentServerUrl: string;
|
|
19
20
|
serveEndpoint?: string;
|
|
@@ -25,43 +26,15 @@ interface BuiltinAgentHandlerOptions {
|
|
|
25
26
|
baseSkillsDir?: string;
|
|
26
27
|
serverHeaders?: HeadersProvider;
|
|
27
28
|
defaultDispatchPolicyForType?: (typeName: string) => DispatchPolicy | undefined;
|
|
28
|
-
createElectricTools?:
|
|
29
|
-
entityUrl: string;
|
|
30
|
-
entityType: string;
|
|
31
|
-
args: Readonly<Record<string, unknown>>;
|
|
32
|
-
db: EntityStreamDBWithActions;
|
|
33
|
-
events: Array<ChangeEvent>;
|
|
34
|
-
upsertCronSchedule: (opts: {
|
|
35
|
-
id: string;
|
|
36
|
-
expression: string;
|
|
37
|
-
timezone?: string;
|
|
38
|
-
payload?: unknown;
|
|
39
|
-
debounceMs?: number;
|
|
40
|
-
timeoutMs?: number;
|
|
41
|
-
}) => Promise<{
|
|
42
|
-
txid: string;
|
|
43
|
-
}>;
|
|
44
|
-
upsertFutureSendSchedule: (opts: {
|
|
45
|
-
id: string;
|
|
46
|
-
payload: unknown;
|
|
47
|
-
targetUrl?: string;
|
|
48
|
-
fireAt: string;
|
|
49
|
-
from?: string;
|
|
50
|
-
messageType?: string;
|
|
51
|
-
}) => Promise<{
|
|
52
|
-
txid: string;
|
|
53
|
-
}>;
|
|
54
|
-
deleteSchedule: (opts: {
|
|
55
|
-
id: string;
|
|
56
|
-
}) => Promise<{
|
|
57
|
-
txid: string;
|
|
58
|
-
}>;
|
|
59
|
-
}) => Array<AgentTool> | Promise<Array<AgentTool>>;
|
|
29
|
+
createElectricTools?: BuiltinElectricToolsFactory;
|
|
60
30
|
}
|
|
31
|
+
declare function createBuiltinElectricTools(custom?: BuiltinElectricToolsFactory): BuiltinElectricToolsFactory;
|
|
61
32
|
declare function createBuiltinAgentHandler(options: BuiltinAgentHandlerOptions): Promise<AgentHandlerResult | null>;
|
|
62
33
|
declare function createAgentHandler(agentServerUrl: string, workingDirectory?: string, streamFn?: StreamFn, createElectricTools?: BuiltinAgentHandlerOptions[`createElectricTools`], serveEndpoint?: string): Promise<AgentHandlerResult | null>;
|
|
63
34
|
declare function registerBuiltinAgentTypes(bootstrap: AgentHandlerResult): Promise<void>;
|
|
64
|
-
declare const registerAgentTypes: typeof registerBuiltinAgentTypes;
|
|
35
|
+
declare const registerAgentTypes: typeof registerBuiltinAgentTypes;
|
|
36
|
+
|
|
37
|
+
//#endregion
|
|
65
38
|
//#region src/server.d.ts
|
|
66
39
|
interface BuiltinAgentsServerOptions {
|
|
67
40
|
agentServerUrl: string;
|
|
@@ -106,38 +79,7 @@ interface BuiltinAgentsServerOptions {
|
|
|
106
79
|
loadProjectMcpConfig?: boolean;
|
|
107
80
|
/** Override for the built-in skills directory; required when embedders bundle this package. */
|
|
108
81
|
baseSkillsDir?: string;
|
|
109
|
-
createElectricTools?:
|
|
110
|
-
entityUrl: string;
|
|
111
|
-
entityType: string;
|
|
112
|
-
args: Readonly<Record<string, unknown>>;
|
|
113
|
-
db: EntityStreamDBWithActions;
|
|
114
|
-
events: Array<ChangeEvent>;
|
|
115
|
-
upsertCronSchedule: (opts: {
|
|
116
|
-
id: string;
|
|
117
|
-
expression: string;
|
|
118
|
-
timezone?: string;
|
|
119
|
-
payload?: unknown;
|
|
120
|
-
debounceMs?: number;
|
|
121
|
-
timeoutMs?: number;
|
|
122
|
-
}) => Promise<{
|
|
123
|
-
txid: string;
|
|
124
|
-
}>;
|
|
125
|
-
upsertFutureSendSchedule: (opts: {
|
|
126
|
-
id: string;
|
|
127
|
-
payload: unknown;
|
|
128
|
-
targetUrl?: string;
|
|
129
|
-
fireAt: string;
|
|
130
|
-
from?: string;
|
|
131
|
-
messageType?: string;
|
|
132
|
-
}) => Promise<{
|
|
133
|
-
txid: string;
|
|
134
|
-
}>;
|
|
135
|
-
deleteSchedule: (opts: {
|
|
136
|
-
id: string;
|
|
137
|
-
}) => Promise<{
|
|
138
|
-
txid: string;
|
|
139
|
-
}>;
|
|
140
|
-
}) => Array<AgentTool> | Promise<Array<AgentTool>>;
|
|
82
|
+
createElectricTools?: NonNullable<ProcessWakeConfig[`createElectricTools`]>;
|
|
141
83
|
}
|
|
142
84
|
declare class BuiltinAgentsServer {
|
|
143
85
|
private bootstrap;
|
|
@@ -154,7 +96,9 @@ declare class BuiltinAgentsServer {
|
|
|
154
96
|
start(): Promise<string>;
|
|
155
97
|
stop(): Promise<void>;
|
|
156
98
|
private registerPullWakeRunner;
|
|
157
|
-
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
//#endregion
|
|
158
102
|
//#region src/entrypoint-lib.d.ts
|
|
159
103
|
type EnvSource = Record<string, string | undefined>;
|
|
160
104
|
interface BuiltinAgentsEntrypointOptions extends BuiltinAgentsServerOptions {}
|
|
@@ -206,6 +150,7 @@ declare const HORTON_MODEL = "claude-sonnet-4-6";
|
|
|
206
150
|
declare function generateTitle(userMessage: string, llmCall: (prompt: string) => Promise<string>, onFallback?: (reason: string) => void): Promise<string>;
|
|
207
151
|
declare function buildHortonSystemPrompt(workingDirectory: string, opts?: {
|
|
208
152
|
hasDocsSupport?: boolean;
|
|
153
|
+
hasEventSourceTools?: boolean;
|
|
209
154
|
hasSkills?: boolean;
|
|
210
155
|
docsUrl?: string;
|
|
211
156
|
modelProvider?: string;
|
|
@@ -258,4 +203,4 @@ declare function createHortonDocsSupport(workingDirectory: string, opts?: {
|
|
|
258
203
|
}): HortonDocsSupport | null;
|
|
259
204
|
|
|
260
205
|
//#endregion
|
|
261
|
-
export { AgentHandlerResult, BuiltinAgentHandlerOptions, BuiltinAgentsEntrypointOptions, BuiltinAgentsEntrypointServer, BuiltinAgentsServer, BuiltinAgentsServerOptions, DEFAULT_BUILTIN_AGENT_HANDLER_PATH, HORTON_MODEL, McpConfig, McpListedEntry, McpRegistry, McpServerConfig, RegistrySnapshot, RegistrySubscriber, RunBuiltinAgentsEntrypointOptions, WORKER_TOOL_NAMES, WorkerToolName, braveSearchTool, buildHortonSystemPrompt, createAgentHandler, createBuiltinAgentHandler, createHortonDocsSupport, createHortonTools, createSpawnWorkerTool, generateTitle, registerAgentTypes, registerBuiltinAgentTypes, registerHorton, registerWorker, resolveBuiltinAgentsEntrypointOptions, runBuiltinAgentsEntrypoint };
|
|
206
|
+
export { AgentHandlerResult, BuiltinAgentHandlerOptions, BuiltinAgentsEntrypointOptions, BuiltinAgentsEntrypointServer, BuiltinAgentsServer, BuiltinAgentsServerOptions, BuiltinElectricToolsFactory, DEFAULT_BUILTIN_AGENT_HANDLER_PATH, HORTON_MODEL, McpConfig, McpListedEntry, McpRegistry, McpServerConfig, RegistrySnapshot, RegistrySubscriber, RunBuiltinAgentsEntrypointOptions, WORKER_TOOL_NAMES, WorkerToolName, braveSearchTool, buildHortonSystemPrompt, createAgentHandler, createBuiltinAgentHandler, createBuiltinElectricTools, createHortonDocsSupport, createHortonTools, createSpawnWorkerTool, generateTitle, registerAgentTypes, registerBuiltinAgentTypes, registerHorton, registerWorker, resolveBuiltinAgentsEntrypointOptions, runBuiltinAgentsEntrypoint };
|
package/dist/index.js
CHANGED
|
@@ -2,9 +2,9 @@ import { mergeElectricPrincipalHeader } from "./server-headers-KD5yHFYT.js";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { appendPathToUrl, completeWithLowCostModel, createEntityRegistry, createPullWakeRunner, createRuntimeHandler, createSkillTools, createSkillsRegistry, db, detectAvailableProviders, readCodexAccessToken, registerToolProvider, unregisterToolProvider } from "@electric-ax/agents-runtime";
|
|
5
|
+
import { braveSearchTool, braveSearchTool as braveSearchTool$1, createBashTool, createEditTool, createEventSourceTools, createFetchUrlTool, createReadFileTool, createSendTool, createWriteTool, fetchUrlTool } from "@electric-ax/agents-runtime/tools";
|
|
5
6
|
import fs from "node:fs";
|
|
6
7
|
import pino from "pino";
|
|
7
|
-
import { eq, not, queryOnce } from "@durable-streams/state";
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
import { createHash } from "node:crypto";
|
|
10
10
|
import fs$1 from "node:fs/promises";
|
|
@@ -13,7 +13,6 @@ import { Type } from "@sinclair/typebox";
|
|
|
13
13
|
import { load } from "sqlite-vec";
|
|
14
14
|
import { nanoid } from "nanoid";
|
|
15
15
|
import { getModels } from "@mariozechner/pi-ai";
|
|
16
|
-
import { braveSearchTool, braveSearchTool as braveSearchTool$1, createBashTool, createEditTool, createFetchUrlTool, createReadFileTool, createSendTool, createWriteTool, fetchUrlTool } from "@electric-ax/agents-runtime/tools";
|
|
17
16
|
import { bridgeMcpTool, buildPromptTools, buildResourceTools, createRegistry, keychainPersistence, loadConfig, mcp, watchConfig } from "@electric-ax/agents-mcp";
|
|
18
17
|
|
|
19
18
|
//#region src/log.ts
|
|
@@ -652,9 +651,9 @@ function createHortonDocsSupport(workingDirectory, opts = {}) {
|
|
|
652
651
|
},
|
|
653
652
|
createSearchTool() {
|
|
654
653
|
return {
|
|
655
|
-
name: `
|
|
656
|
-
label: `Search
|
|
657
|
-
description: `Run a hybrid BM25 plus vector search over the local
|
|
654
|
+
name: `search_electric_agents_docs`,
|
|
655
|
+
label: `Search Electric Agents Docs`,
|
|
656
|
+
description: `Run a hybrid BM25 plus vector search over the local Electric Agents documentation index.`,
|
|
658
657
|
parameters: Type.Object({
|
|
659
658
|
query: Type.String({ description: `The docs question or search query to run.` }),
|
|
660
659
|
limit: Type.Optional(Type.Number({
|
|
@@ -785,12 +784,14 @@ const REASONING_EFFORT_VALUES = [
|
|
|
785
784
|
const DEFAULT_ANTHROPIC_MODEL = `claude-sonnet-4-6`;
|
|
786
785
|
const DEFAULT_OPENAI_MODEL = `gpt-4.1`;
|
|
787
786
|
const DEFAULT_CODEX_MODEL = `gpt-5.4`;
|
|
787
|
+
const DEFAULT_DEEPSEEK_MODEL = `deepseek-v4-flash`;
|
|
788
788
|
function modelValue(provider, id) {
|
|
789
789
|
return `${provider}:${id}`;
|
|
790
790
|
}
|
|
791
791
|
function providerLabel(provider) {
|
|
792
792
|
if (provider === `anthropic`) return `Anthropic`;
|
|
793
793
|
if (provider === `openai-codex`) return `OpenAI Codex`;
|
|
794
|
+
if (provider === `deepseek`) return `DeepSeek`;
|
|
794
795
|
return `OpenAI`;
|
|
795
796
|
}
|
|
796
797
|
function configuredProviders() {
|
|
@@ -817,6 +818,9 @@ async function fetchAvailableModelIds(provider) {
|
|
|
817
818
|
"anthropic-version": `2023-06-01`
|
|
818
819
|
},
|
|
819
820
|
signal: AbortSignal.timeout(3e3)
|
|
821
|
+
}) : provider === `deepseek` ? await fetch(`https://api.deepseek.com/v1/models`, {
|
|
822
|
+
headers: { authorization: `Bearer ${process.env.DEEPSEEK_API_KEY ?? ``}` },
|
|
823
|
+
signal: AbortSignal.timeout(3e3)
|
|
820
824
|
}) : await fetch(`https://api.openai.com/v1/models`, {
|
|
821
825
|
headers: { authorization: `Bearer ${process.env.OPENAI_API_KEY ?? ``}` },
|
|
822
826
|
signal: AbortSignal.timeout(3e3)
|
|
@@ -877,7 +881,7 @@ async function createBuiltinModelCatalog(options = {}) {
|
|
|
877
881
|
if (providers.length === 0 && options.allowMockFallback) return mockFallbackCatalog();
|
|
878
882
|
const choices = (await Promise.all(providers.map((provider) => choicesForProvider(provider)))).flat();
|
|
879
883
|
if (choices.length === 0) return options.allowMockFallback ? mockFallbackCatalog() : null;
|
|
880
|
-
const defaultChoice = choices.find((choice) => choice.provider === `anthropic` && choice.id === DEFAULT_ANTHROPIC_MODEL) ?? choices.find((choice) => choice.provider === `openai` && choice.id === DEFAULT_OPENAI_MODEL) ?? choices.find((choice) => choice.provider === `openai-codex` && choice.id === DEFAULT_CODEX_MODEL) ?? choices[0];
|
|
884
|
+
const defaultChoice = choices.find((choice) => choice.provider === `anthropic` && choice.id === DEFAULT_ANTHROPIC_MODEL) ?? choices.find((choice) => choice.provider === `openai` && choice.id === DEFAULT_OPENAI_MODEL) ?? choices.find((choice) => choice.provider === `openai-codex` && choice.id === DEFAULT_CODEX_MODEL) ?? choices.find((choice) => choice.provider === `deepseek` && choice.id === DEFAULT_DEEPSEEK_MODEL) ?? choices[0];
|
|
881
885
|
return {
|
|
882
886
|
choices,
|
|
883
887
|
defaultChoice
|
|
@@ -998,9 +1002,10 @@ async function generateTitle(userMessage, llmCall, onFallback) {
|
|
|
998
1002
|
}
|
|
999
1003
|
}
|
|
1000
1004
|
function buildHortonSystemPrompt(workingDirectory, opts = {}) {
|
|
1001
|
-
const docsTools = opts.hasDocsSupport ? `\n-
|
|
1005
|
+
const docsTools = opts.hasDocsSupport ? `\n- search_electric_agents_docs: hybrid search over the built-in Electric Agents docs index` : ``;
|
|
1006
|
+
const eventSourceTools = opts.hasEventSourceTools ? `\n- list_event_sources: list external webhook/event feeds you can subscribe to, including available buckets and parameters\n- subscribe_event_source: subscribe yourself to one of those feeds or buckets so matching future events wake you\n- list_event_source_subscriptions: list your active event source subscriptions\n- unsubscribe_event_source: remove one of your event source subscriptions by id` : ``;
|
|
1002
1007
|
const skillsTools = opts.hasSkills ? `\n- use_skill: load a skill (knowledge, instructions, or a tutorial) into your context to help with the user's request\n- remove_skill: unload a skill from context when you're done with it` : ``;
|
|
1003
|
-
const docsGuidance = opts.hasDocsSupport ? `\n- For ANY question about Electric Agents
|
|
1008
|
+
const docsGuidance = opts.hasDocsSupport ? `\n- For ANY question about Electric Agents or this framework, ALWAYS use search_electric_agents_docs FIRST. Do not use web_search or fetch_url for Electric Agents topics unless the docs search returns no useful results.\n- The search tool returns chunk content directly — you do not need to read the source files.\n- Use repo read/bash tools only for non-doc files or when you need to inspect exact implementation code in the workspace.` : ``;
|
|
1004
1009
|
const skillsGuidance = opts.hasSkills ? `\n# Skills\nYou have access to skills — specialized knowledge and guided workflows you can load on demand. Your context includes a skills catalog listing what's available. When the user's request matches a skill's description or keywords, load it with use_skill.
|
|
1005
1010
|
|
|
1006
1011
|
Some skills are user-invocable — the user can trigger them with a slash command like \`/quickstart\`. When you see a message starting with \`/\` followed by a skill name, load that skill immediately with use_skill. Pass any text after the skill name as args.
|
|
@@ -1019,7 +1024,7 @@ Do NOT load a skill and then ignore its instructions. The skill is there because
|
|
|
1019
1024
|
When a user is new or asks how to get started with Electric Agents, **don't assume a single path**. Present the options and let them choose:
|
|
1020
1025
|
|
|
1021
1026
|
- **Learn the concepts first** → Explain what Electric Agents is, answer questions, point to docs.
|
|
1022
|
-
Use
|
|
1027
|
+
Use search_electric_agents_docs to look up answers. Only load the quickstart skill if the user explicitly asks for a hands-on guided tutorial.
|
|
1023
1028
|
|
|
1024
1029
|
- **Hands-on guided tutorial** → Load the quickstart skill (or tell them to type \`/quickstart\`).
|
|
1025
1030
|
This is a step-by-step build that takes them from zero to a running app.
|
|
@@ -1029,11 +1034,11 @@ When a user is new or asks how to get started with Electric Agents, **don't assu
|
|
|
1029
1034
|
This sets up project structure and orients them in the codebase.
|
|
1030
1035
|
|
|
1031
1036
|
- **Have a specific question?** → Answer it directly.
|
|
1032
|
-
Use
|
|
1037
|
+
Use search_electric_agents_docs first, then fall back to fetch_url or general knowledge if needed.
|
|
1033
1038
|
|
|
1034
1039
|
Don't force onboarding. If someone just wants to chat or code, let them. When in doubt, ask what they'd like to do rather than picking a path for them.`;
|
|
1035
1040
|
const docsUrlGuidance = opts.docsUrl ? `\n# Electric Agents documentation
|
|
1036
|
-
- ${opts.hasDocsSupport ? `If
|
|
1041
|
+
- ${opts.hasDocsSupport ? `If search_electric_agents_docs is available, use it first (faster, hybrid search).` : `Use fetch_url to look up documentation pages.`}
|
|
1037
1042
|
- The Electric Agents docs site is at ${opts.docsUrl}
|
|
1038
1043
|
- The docs site covers: Usage (entity definition, handlers, tools, state, spawning, coordination, waking, shared state, client integration, app setup), Reference (handler context, entity definitions, configurations, tools, state proxies, wake events, registries), Entities (Horton, Worker), and Patterns (Manager-Worker, Pipeline, Map-Reduce, Dispatcher, Blackboard, Reactive Observers).
|
|
1039
1044
|
- For general coding questions unrelated to Electric Agents, use web_search or your own knowledge.` : ``;
|
|
@@ -1052,8 +1057,8 @@ When a user opens with a greeting ("hi", "hello", "hey", etc.) or a broad statem
|
|
|
1052
1057
|
- web_search: search the web
|
|
1053
1058
|
- fetch_url: fetch and convert a URL to markdown
|
|
1054
1059
|
- spawn_worker: dispatch a subagent for an isolated task
|
|
1055
|
-
- send: send a message to an Electric Agent/entity
|
|
1056
|
-
${docsTools}${skillsTools}
|
|
1060
|
+
- send: send a message to an Electric Agent/entity. To schedule future work for yourself, call send with self: true and afterMs.
|
|
1061
|
+
${eventSourceTools}${docsTools}${skillsTools}
|
|
1057
1062
|
|
|
1058
1063
|
# Working with files
|
|
1059
1064
|
- Prefer edit over write when modifying existing files.
|
|
@@ -1086,6 +1091,11 @@ Report outcomes faithfully. If a command failed, say so with the relevant output
|
|
|
1086
1091
|
Working directory: ${workingDirectory}
|
|
1087
1092
|
The current year is ${new Date().getFullYear()}.`;
|
|
1088
1093
|
}
|
|
1094
|
+
function getToolName(tool) {
|
|
1095
|
+
if (typeof tool !== `object` || tool === null) return null;
|
|
1096
|
+
const name = tool.name;
|
|
1097
|
+
return typeof name === `string` ? name : null;
|
|
1098
|
+
}
|
|
1089
1099
|
function createHortonTools(workingDirectory, ctx, readSet, opts = {}) {
|
|
1090
1100
|
return [
|
|
1091
1101
|
createBashTool(workingDirectory),
|
|
@@ -1100,7 +1110,7 @@ function createHortonTools(workingDirectory, ctx, readSet, opts = {}) {
|
|
|
1100
1110
|
logPrefix: opts.logPrefix ?? `[horton]`
|
|
1101
1111
|
})] : [fetchUrlTool],
|
|
1102
1112
|
createSpawnWorkerTool(ctx, opts.modelConfig),
|
|
1103
|
-
createSendTool(ctx.send),
|
|
1113
|
+
createSendTool(ctx.send, { selfEntityUrl: ctx.entityUrl }),
|
|
1104
1114
|
...opts.docsSearchTool ? [opts.docsSearchTool] : []
|
|
1105
1115
|
];
|
|
1106
1116
|
}
|
|
@@ -1114,11 +1124,14 @@ function payloadToTitleText(payload) {
|
|
|
1114
1124
|
return String(payload);
|
|
1115
1125
|
}
|
|
1116
1126
|
async function extractFirstUserMessage(ctx) {
|
|
1117
|
-
const firstMessage =
|
|
1127
|
+
const firstMessage = ctx.db.collections.inbox.toArray.filter((message) => message.from !== `system`).sort((left, right) => messageSeq(left) - messageSeq(right))[0];
|
|
1118
1128
|
if (!firstMessage) return null;
|
|
1119
1129
|
const text = payloadToTitleText(firstMessage.payload);
|
|
1120
1130
|
return text.length > 0 ? text : null;
|
|
1121
1131
|
}
|
|
1132
|
+
function messageSeq(message) {
|
|
1133
|
+
return typeof message._seq === `number` ? message._seq : -1;
|
|
1134
|
+
}
|
|
1122
1135
|
function readAgentsMd(workingDirectory) {
|
|
1123
1136
|
const agentsMdPath = path.join(workingDirectory, `AGENTS.md`);
|
|
1124
1137
|
try {
|
|
@@ -1152,6 +1165,7 @@ function createAssistantHandler(options) {
|
|
|
1152
1165
|
...skillsRegistry && skillsRegistry.catalog.size > 0 ? createSkillTools(skillsRegistry, ctx) : [],
|
|
1153
1166
|
...mcp.tools()
|
|
1154
1167
|
];
|
|
1168
|
+
const hasEventSourceTools = tools.some((tool) => getToolName(tool) === `list_event_sources`);
|
|
1155
1169
|
const titlePromise = ctx.firstWake && !ctx.tags.title ? (async () => {
|
|
1156
1170
|
const firstUserMessage = await extractFirstUserMessage(ctx);
|
|
1157
1171
|
if (!firstUserMessage) return;
|
|
@@ -1238,7 +1252,8 @@ function createAssistantHandler(options) {
|
|
|
1238
1252
|
hasSkills,
|
|
1239
1253
|
docsUrl,
|
|
1240
1254
|
modelProvider: modelConfig.provider,
|
|
1241
|
-
modelId: String(modelConfig.model)
|
|
1255
|
+
modelId: String(modelConfig.model),
|
|
1256
|
+
hasEventSourceTools
|
|
1242
1257
|
}),
|
|
1243
1258
|
...modelConfig,
|
|
1244
1259
|
tools,
|
|
@@ -1278,15 +1293,7 @@ function registerHorton(registry, options) {
|
|
|
1278
1293
|
creationSchema: hortonCreationSchema,
|
|
1279
1294
|
handler: assistantHandler
|
|
1280
1295
|
});
|
|
1281
|
-
|
|
1282
|
-
if (streamFn) {
|
|
1283
|
-
registry.define(`chat`, {
|
|
1284
|
-
description: `Compatibility alias for the built-in assistant type.`,
|
|
1285
|
-
handler: assistantHandler
|
|
1286
|
-
});
|
|
1287
|
-
typeNames.push(`chat`);
|
|
1288
|
-
}
|
|
1289
|
-
return typeNames;
|
|
1296
|
+
return [`horton`];
|
|
1290
1297
|
}
|
|
1291
1298
|
|
|
1292
1299
|
//#endregion
|
|
@@ -1350,7 +1357,7 @@ function buildToolsForWorker(tools, workingDirectory, ctx, readSet) {
|
|
|
1350
1357
|
out.push(createSpawnWorkerTool(ctx));
|
|
1351
1358
|
break;
|
|
1352
1359
|
case `send`:
|
|
1353
|
-
out.push(createSendTool(ctx.send));
|
|
1360
|
+
out.push(createSendTool(ctx.send, { selfEntityUrl: ctx.entityUrl }));
|
|
1354
1361
|
break;
|
|
1355
1362
|
}
|
|
1356
1363
|
return out;
|
|
@@ -1479,6 +1486,27 @@ function registerWorker(registry, options) {
|
|
|
1479
1486
|
//#endregion
|
|
1480
1487
|
//#region src/bootstrap.ts
|
|
1481
1488
|
const DEFAULT_BUILTIN_AGENT_HANDLER_PATH = `/_electric/builtin-agent-handler`;
|
|
1489
|
+
function toolName(tool) {
|
|
1490
|
+
return typeof tool.name === `string` ? tool.name : null;
|
|
1491
|
+
}
|
|
1492
|
+
function dedupeToolsByName(tools) {
|
|
1493
|
+
const seen = new Set();
|
|
1494
|
+
const deduped = [];
|
|
1495
|
+
for (const tool of tools) {
|
|
1496
|
+
const name = toolName(tool);
|
|
1497
|
+
if (name && seen.has(name)) continue;
|
|
1498
|
+
if (name) seen.add(name);
|
|
1499
|
+
deduped.push(tool);
|
|
1500
|
+
}
|
|
1501
|
+
return deduped;
|
|
1502
|
+
}
|
|
1503
|
+
function createBuiltinElectricTools(custom) {
|
|
1504
|
+
return async (context) => {
|
|
1505
|
+
const builtinTools = createEventSourceTools(context);
|
|
1506
|
+
const customTools = custom ? await custom(context) : [];
|
|
1507
|
+
return dedupeToolsByName([...builtinTools, ...customTools]);
|
|
1508
|
+
};
|
|
1509
|
+
}
|
|
1482
1510
|
async function createBuiltinAgentHandler(options) {
|
|
1483
1511
|
const { agentServerUrl, serveEndpoint, workingDirectory, streamFn, createElectricTools, publicUrl, runtimeName, baseSkillsDir: baseSkillsDirOverride, serverHeaders, defaultDispatchPolicyForType } = options;
|
|
1484
1512
|
const modelCatalog = await createBuiltinModelCatalog({ allowMockFallback: Boolean(streamFn) });
|
|
@@ -1521,7 +1549,7 @@ async function createBuiltinAgentHandler(options) {
|
|
|
1521
1549
|
defaultDispatchPolicyForType,
|
|
1522
1550
|
serverHeaders,
|
|
1523
1551
|
idleTimeout: 5 * 6e4,
|
|
1524
|
-
createElectricTools,
|
|
1552
|
+
createElectricTools: createBuiltinElectricTools(createElectricTools),
|
|
1525
1553
|
publicUrl,
|
|
1526
1554
|
name: runtimeName ?? `builtin-agents`
|
|
1527
1555
|
});
|
|
@@ -1850,4 +1878,4 @@ async function runBuiltinAgentsEntrypoint({ env = process.env, cwd = process.cwd
|
|
|
1850
1878
|
}
|
|
1851
1879
|
|
|
1852
1880
|
//#endregion
|
|
1853
|
-
export { BuiltinAgentsServer, DEFAULT_BUILTIN_AGENT_HANDLER_PATH, HORTON_MODEL, WORKER_TOOL_NAMES, braveSearchTool, buildHortonSystemPrompt, createAgentHandler, createBuiltinAgentHandler, createHortonDocsSupport, createHortonTools, createSpawnWorkerTool, generateTitle, registerAgentTypes, registerBuiltinAgentTypes, registerHorton, registerWorker, resolveBuiltinAgentsEntrypointOptions, runBuiltinAgentsEntrypoint };
|
|
1881
|
+
export { BuiltinAgentsServer, DEFAULT_BUILTIN_AGENT_HANDLER_PATH, HORTON_MODEL, WORKER_TOOL_NAMES, braveSearchTool, buildHortonSystemPrompt, createAgentHandler, createBuiltinAgentHandler, createBuiltinElectricTools, createHortonDocsSupport, createHortonTools, createSpawnWorkerTool, generateTitle, registerAgentTypes, registerBuiltinAgentTypes, registerHorton, registerWorker, resolveBuiltinAgentsEntrypointOptions, runBuiltinAgentsEntrypoint };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@electric-ax/agents",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.9",
|
|
4
4
|
"description": "Built-in Electric Agents runtimes such as Horton and worker",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"sqlite-vec": "^0.1.9",
|
|
50
50
|
"zod": "^4.3.6",
|
|
51
51
|
"@electric-ax/agents-mcp": "0.2.2",
|
|
52
|
-
"@electric-ax/agents-runtime": "0.3.
|
|
52
|
+
"@electric-ax/agents-runtime": "0.3.5"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@types/better-sqlite3": "^7.6.13",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"cross-env": "^10.1.0",
|
|
59
59
|
"tsdown": "^0.9.0",
|
|
60
60
|
"tsx": "^4.19.0",
|
|
61
|
-
"typescript": "^5.
|
|
61
|
+
"typescript": "^5.9.3",
|
|
62
62
|
"vitest": "^4.1.0"
|
|
63
63
|
},
|
|
64
64
|
"files": [
|
package/skills/quickstart.md
CHANGED
|
@@ -100,7 +100,7 @@ IMPORTANT: Never write files until the user explicitly confirms. "Ask to write"
|
|
|
100
100
|
|
|
101
101
|
- **Continue building** — add an HTTP API route and a React frontend to this app so users can interact with the analyzer from the browser.
|
|
102
102
|
- **Start a new app** — use the `agents-chat-starter` template for a full multi-agent chat app with rooms, agent spawning, and a Slack-style UI. Load the init skill or tell them to type `/init`.
|
|
103
|
-
- **Explore the docs** — read about other coordination patterns (blackboard, pipeline, map-reduce), dive into the API reference, or learn about shared state, context assembly, and other advanced features. Use `
|
|
103
|
+
- **Explore the docs** — read about other coordination patterns (blackboard, pipeline, map-reduce), dive into the API reference, or learn about shared state, context assembly, and other advanced features. Use `search_electric_agents_docs` to look things up.
|
|
104
104
|
|
|
105
105
|
Wait for the user to choose. Only proceed to Step 4 if they want to continue building.
|
|
106
106
|
|
|
@@ -116,7 +116,7 @@ Wait for the user to choose. Only proceed to Step 4 if they want to continue bui
|
|
|
116
116
|
- `server.ts` is at the working directory root. Entity files go in `entities/`.
|
|
117
117
|
- Worker spawn args MUST include `tools` array (at least one tool, e.g. `tools: ["bash"]`).
|
|
118
118
|
- Use `edit` tool for small changes (like updating server.ts). Use `write` for full entity file updates.
|
|
119
|
-
- If the user asks a question about Electric Agents concepts, APIs, or patterns between steps, use the `
|
|
119
|
+
- If the user asks a question about Electric Agents concepts, APIs, or patterns between steps, use the `search_electric_agents_docs` tool to look up the answer in the built-in documentation before guessing or searching the web.
|
|
120
120
|
|
|
121
121
|
---
|
|
122
122
|
|