@electric-ax/agents 0.4.5 → 0.4.8

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.
@@ -4,6 +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 { braveSearchTool, createBashTool, createEditTool, createEventSourceTools, createFetchUrlTool, createReadFileTool, createSendTool, createWriteTool, fetchUrlTool } from "@electric-ax/agents-runtime/tools";
7
8
  import { eq, not, queryOnce } from "@durable-streams/state";
8
9
  import { z } from "zod";
9
10
  import { createHash } from "node:crypto";
@@ -13,7 +14,6 @@ import { Type } from "@sinclair/typebox";
13
14
  import { load } from "sqlite-vec";
14
15
  import { nanoid } from "nanoid";
15
16
  import { getModels } from "@mariozechner/pi-ai";
16
- import { braveSearchTool, createBashTool, createEditTool, createFetchUrlTool, createReadFileTool, createSendTool, createWriteTool, fetchUrlTool } from "@electric-ax/agents-runtime/tools";
17
17
  import { bridgeMcpTool, buildPromptTools, buildResourceTools, createRegistry, keychainPersistence, loadConfig, mcp, watchConfig } from "@electric-ax/agents-mcp";
18
18
 
19
19
  //#region src/log.ts
@@ -785,12 +785,14 @@ const REASONING_EFFORT_VALUES = [
785
785
  const DEFAULT_ANTHROPIC_MODEL = `claude-sonnet-4-6`;
786
786
  const DEFAULT_OPENAI_MODEL = `gpt-4.1`;
787
787
  const DEFAULT_CODEX_MODEL = `gpt-5.4`;
788
+ const DEFAULT_DEEPSEEK_MODEL = `deepseek-v4-flash`;
788
789
  function modelValue(provider, id) {
789
790
  return `${provider}:${id}`;
790
791
  }
791
792
  function providerLabel(provider) {
792
793
  if (provider === `anthropic`) return `Anthropic`;
793
794
  if (provider === `openai-codex`) return `OpenAI Codex`;
795
+ if (provider === `deepseek`) return `DeepSeek`;
794
796
  return `OpenAI`;
795
797
  }
796
798
  function configuredProviders() {
@@ -817,6 +819,9 @@ async function fetchAvailableModelIds(provider) {
817
819
  "anthropic-version": `2023-06-01`
818
820
  },
819
821
  signal: AbortSignal.timeout(3e3)
822
+ }) : provider === `deepseek` ? await fetch(`https://api.deepseek.com/v1/models`, {
823
+ headers: { authorization: `Bearer ${process.env.DEEPSEEK_API_KEY ?? ``}` },
824
+ signal: AbortSignal.timeout(3e3)
820
825
  }) : await fetch(`https://api.openai.com/v1/models`, {
821
826
  headers: { authorization: `Bearer ${process.env.OPENAI_API_KEY ?? ``}` },
822
827
  signal: AbortSignal.timeout(3e3)
@@ -877,7 +882,7 @@ async function createBuiltinModelCatalog(options = {}) {
877
882
  if (providers.length === 0 && options.allowMockFallback) return mockFallbackCatalog();
878
883
  const choices = (await Promise.all(providers.map((provider) => choicesForProvider(provider)))).flat();
879
884
  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];
885
+ 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
886
  return {
882
887
  choices,
883
888
  defaultChoice
@@ -998,6 +1003,7 @@ async function generateTitle(userMessage, llmCall, onFallback) {
998
1003
  }
999
1004
  function buildHortonSystemPrompt(workingDirectory, opts = {}) {
1000
1005
  const docsTools = opts.hasDocsSupport ? `\n- search_durable_agents_docs: hybrid search over the built-in Durable 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` : ``;
1001
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` : ``;
1002
1008
  const docsGuidance = opts.hasDocsSupport ? `\n- For ANY question about Electric Agents, Durable Agents, or this framework, ALWAYS use search_durable_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
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.
@@ -1051,8 +1057,8 @@ When a user opens with a greeting ("hi", "hello", "hey", etc.) or a broad statem
1051
1057
  - web_search: search the web
1052
1058
  - fetch_url: fetch and convert a URL to markdown
1053
1059
  - spawn_worker: dispatch a subagent for an isolated task
1054
- - send: send a message to an Electric Agent/entity by entity URL
1055
- ${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}
1056
1062
 
1057
1063
  # Working with files
1058
1064
  - Prefer edit over write when modifying existing files.
@@ -1085,6 +1091,11 @@ Report outcomes faithfully. If a command failed, say so with the relevant output
1085
1091
  Working directory: ${workingDirectory}
1086
1092
  The current year is ${new Date().getFullYear()}.`;
1087
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
+ }
1088
1099
  function createHortonTools(workingDirectory, ctx, readSet, opts = {}) {
1089
1100
  return [
1090
1101
  createBashTool(workingDirectory),
@@ -1099,7 +1110,7 @@ function createHortonTools(workingDirectory, ctx, readSet, opts = {}) {
1099
1110
  logPrefix: opts.logPrefix ?? `[horton]`
1100
1111
  })] : [fetchUrlTool],
1101
1112
  createSpawnWorkerTool(ctx, opts.modelConfig),
1102
- createSendTool(ctx.send),
1113
+ createSendTool(ctx.send, { selfEntityUrl: ctx.entityUrl }),
1103
1114
  ...opts.docsSearchTool ? [opts.docsSearchTool] : []
1104
1115
  ];
1105
1116
  }
@@ -1151,6 +1162,7 @@ function createAssistantHandler(options) {
1151
1162
  ...skillsRegistry && skillsRegistry.catalog.size > 0 ? createSkillTools(skillsRegistry, ctx) : [],
1152
1163
  ...mcp.tools()
1153
1164
  ];
1165
+ const hasEventSourceTools = tools.some((tool) => getToolName(tool) === `list_event_sources`);
1154
1166
  const titlePromise = ctx.firstWake && !ctx.tags.title ? (async () => {
1155
1167
  const firstUserMessage = await extractFirstUserMessage(ctx);
1156
1168
  if (!firstUserMessage) return;
@@ -1237,7 +1249,8 @@ function createAssistantHandler(options) {
1237
1249
  hasSkills,
1238
1250
  docsUrl,
1239
1251
  modelProvider: modelConfig.provider,
1240
- modelId: String(modelConfig.model)
1252
+ modelId: String(modelConfig.model),
1253
+ hasEventSourceTools
1241
1254
  }),
1242
1255
  ...modelConfig,
1243
1256
  tools,
@@ -1349,7 +1362,7 @@ function buildToolsForWorker(tools, workingDirectory, ctx, readSet) {
1349
1362
  out.push(createSpawnWorkerTool(ctx));
1350
1363
  break;
1351
1364
  case `send`:
1352
- out.push(createSendTool(ctx.send));
1365
+ out.push(createSendTool(ctx.send, { selfEntityUrl: ctx.entityUrl }));
1353
1366
  break;
1354
1367
  }
1355
1368
  return out;
@@ -1477,6 +1490,27 @@ function registerWorker(registry, options) {
1477
1490
 
1478
1491
  //#endregion
1479
1492
  //#region src/bootstrap.ts
1493
+ function toolName(tool) {
1494
+ return typeof tool.name === `string` ? tool.name : null;
1495
+ }
1496
+ function dedupeToolsByName(tools) {
1497
+ const seen = new Set();
1498
+ const deduped = [];
1499
+ for (const tool of tools) {
1500
+ const name = toolName(tool);
1501
+ if (name && seen.has(name)) continue;
1502
+ if (name) seen.add(name);
1503
+ deduped.push(tool);
1504
+ }
1505
+ return deduped;
1506
+ }
1507
+ function createBuiltinElectricTools(custom) {
1508
+ return async (context) => {
1509
+ const builtinTools = createEventSourceTools(context);
1510
+ const customTools = custom ? await custom(context) : [];
1511
+ return dedupeToolsByName([...builtinTools, ...customTools]);
1512
+ };
1513
+ }
1480
1514
  async function createBuiltinAgentHandler(options) {
1481
1515
  const { agentServerUrl, serveEndpoint, workingDirectory, streamFn, createElectricTools, publicUrl, runtimeName, baseSkillsDir: baseSkillsDirOverride, serverHeaders, defaultDispatchPolicyForType } = options;
1482
1516
  const modelCatalog = await createBuiltinModelCatalog({ allowMockFallback: Boolean(streamFn) });
@@ -1519,7 +1553,7 @@ async function createBuiltinAgentHandler(options) {
1519
1553
  defaultDispatchPolicyForType,
1520
1554
  serverHeaders,
1521
1555
  idleTimeout: 5 * 6e4,
1522
- createElectricTools,
1556
+ createElectricTools: createBuiltinElectricTools(createElectricTools),
1523
1557
  publicUrl,
1524
1558
  name: runtimeName ?? `builtin-agents`
1525
1559
  });
package/dist/index.cjs CHANGED
@@ -26,6 +26,7 @@ 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
32
  const __durable_streams_state = __toESM(require("@durable-streams/state"));
@@ -37,7 +38,6 @@ const __sinclair_typebox = __toESM(require("@sinclair/typebox"));
37
38
  const sqlite_vec = __toESM(require("sqlite-vec"));
38
39
  const nanoid = __toESM(require("nanoid"));
39
40
  const __mariozechner_pi_ai = __toESM(require("@mariozechner/pi-ai"));
40
- const __electric_ax_agents_runtime_tools = __toESM(require("@electric-ax/agents-runtime/tools"));
41
41
  const __electric_ax_agents_mcp = __toESM(require("@electric-ax/agents-mcp"));
42
42
 
43
43
  //#region src/log.ts
@@ -809,12 +809,14 @@ const REASONING_EFFORT_VALUES = [
809
809
  const DEFAULT_ANTHROPIC_MODEL = `claude-sonnet-4-6`;
810
810
  const DEFAULT_OPENAI_MODEL = `gpt-4.1`;
811
811
  const DEFAULT_CODEX_MODEL = `gpt-5.4`;
812
+ const DEFAULT_DEEPSEEK_MODEL = `deepseek-v4-flash`;
812
813
  function modelValue(provider, id) {
813
814
  return `${provider}:${id}`;
814
815
  }
815
816
  function providerLabel(provider) {
816
817
  if (provider === `anthropic`) return `Anthropic`;
817
818
  if (provider === `openai-codex`) return `OpenAI Codex`;
819
+ if (provider === `deepseek`) return `DeepSeek`;
818
820
  return `OpenAI`;
819
821
  }
820
822
  function configuredProviders() {
@@ -841,6 +843,9 @@ async function fetchAvailableModelIds(provider) {
841
843
  "anthropic-version": `2023-06-01`
842
844
  },
843
845
  signal: AbortSignal.timeout(3e3)
846
+ }) : provider === `deepseek` ? await fetch(`https://api.deepseek.com/v1/models`, {
847
+ headers: { authorization: `Bearer ${process.env.DEEPSEEK_API_KEY ?? ``}` },
848
+ signal: AbortSignal.timeout(3e3)
844
849
  }) : await fetch(`https://api.openai.com/v1/models`, {
845
850
  headers: { authorization: `Bearer ${process.env.OPENAI_API_KEY ?? ``}` },
846
851
  signal: AbortSignal.timeout(3e3)
@@ -901,7 +906,7 @@ async function createBuiltinModelCatalog(options = {}) {
901
906
  if (providers.length === 0 && options.allowMockFallback) return mockFallbackCatalog();
902
907
  const choices = (await Promise.all(providers.map((provider) => choicesForProvider(provider)))).flat();
903
908
  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];
909
+ 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
910
  return {
906
911
  choices,
907
912
  defaultChoice
@@ -1023,6 +1028,7 @@ async function generateTitle(userMessage, llmCall, onFallback) {
1023
1028
  }
1024
1029
  function buildHortonSystemPrompt(workingDirectory, opts = {}) {
1025
1030
  const docsTools = opts.hasDocsSupport ? `\n- search_durable_agents_docs: hybrid search over the built-in Durable Agents docs index` : ``;
1031
+ 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
1032
  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
1033
  const docsGuidance = opts.hasDocsSupport ? `\n- For ANY question about Electric Agents, Durable Agents, or this framework, ALWAYS use search_durable_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
1034
  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.
@@ -1076,8 +1082,8 @@ When a user opens with a greeting ("hi", "hello", "hey", etc.) or a broad statem
1076
1082
  - web_search: search the web
1077
1083
  - fetch_url: fetch and convert a URL to markdown
1078
1084
  - spawn_worker: dispatch a subagent for an isolated task
1079
- - send: send a message to an Electric Agent/entity by entity URL
1080
- ${docsTools}${skillsTools}
1085
+ - send: send a message to an Electric Agent/entity. To schedule future work for yourself, call send with self: true and afterMs.
1086
+ ${eventSourceTools}${docsTools}${skillsTools}
1081
1087
 
1082
1088
  # Working with files
1083
1089
  - Prefer edit over write when modifying existing files.
@@ -1110,6 +1116,11 @@ Report outcomes faithfully. If a command failed, say so with the relevant output
1110
1116
  Working directory: ${workingDirectory}
1111
1117
  The current year is ${new Date().getFullYear()}.`;
1112
1118
  }
1119
+ function getToolName(tool) {
1120
+ if (typeof tool !== `object` || tool === null) return null;
1121
+ const name = tool.name;
1122
+ return typeof name === `string` ? name : null;
1123
+ }
1113
1124
  function createHortonTools(workingDirectory, ctx, readSet, opts = {}) {
1114
1125
  return [
1115
1126
  (0, __electric_ax_agents_runtime_tools.createBashTool)(workingDirectory),
@@ -1124,7 +1135,7 @@ function createHortonTools(workingDirectory, ctx, readSet, opts = {}) {
1124
1135
  logPrefix: opts.logPrefix ?? `[horton]`
1125
1136
  })] : [__electric_ax_agents_runtime_tools.fetchUrlTool],
1126
1137
  createSpawnWorkerTool(ctx, opts.modelConfig),
1127
- (0, __electric_ax_agents_runtime_tools.createSendTool)(ctx.send),
1138
+ (0, __electric_ax_agents_runtime_tools.createSendTool)(ctx.send, { selfEntityUrl: ctx.entityUrl }),
1128
1139
  ...opts.docsSearchTool ? [opts.docsSearchTool] : []
1129
1140
  ];
1130
1141
  }
@@ -1176,6 +1187,7 @@ function createAssistantHandler(options) {
1176
1187
  ...skillsRegistry && skillsRegistry.catalog.size > 0 ? (0, __electric_ax_agents_runtime.createSkillTools)(skillsRegistry, ctx) : [],
1177
1188
  ...__electric_ax_agents_mcp.mcp.tools()
1178
1189
  ];
1190
+ const hasEventSourceTools = tools.some((tool) => getToolName(tool) === `list_event_sources`);
1179
1191
  const titlePromise = ctx.firstWake && !ctx.tags.title ? (async () => {
1180
1192
  const firstUserMessage = await extractFirstUserMessage(ctx);
1181
1193
  if (!firstUserMessage) return;
@@ -1262,7 +1274,8 @@ function createAssistantHandler(options) {
1262
1274
  hasSkills,
1263
1275
  docsUrl,
1264
1276
  modelProvider: modelConfig.provider,
1265
- modelId: String(modelConfig.model)
1277
+ modelId: String(modelConfig.model),
1278
+ hasEventSourceTools
1266
1279
  }),
1267
1280
  ...modelConfig,
1268
1281
  tools,
@@ -1374,7 +1387,7 @@ function buildToolsForWorker(tools, workingDirectory, ctx, readSet) {
1374
1387
  out.push(createSpawnWorkerTool(ctx));
1375
1388
  break;
1376
1389
  case `send`:
1377
- out.push((0, __electric_ax_agents_runtime_tools.createSendTool)(ctx.send));
1390
+ out.push((0, __electric_ax_agents_runtime_tools.createSendTool)(ctx.send, { selfEntityUrl: ctx.entityUrl }));
1378
1391
  break;
1379
1392
  }
1380
1393
  return out;
@@ -1503,6 +1516,27 @@ function registerWorker(registry, options) {
1503
1516
  //#endregion
1504
1517
  //#region src/bootstrap.ts
1505
1518
  const DEFAULT_BUILTIN_AGENT_HANDLER_PATH = `/_electric/builtin-agent-handler`;
1519
+ function toolName(tool) {
1520
+ return typeof tool.name === `string` ? tool.name : null;
1521
+ }
1522
+ function dedupeToolsByName(tools) {
1523
+ const seen = new Set();
1524
+ const deduped = [];
1525
+ for (const tool of tools) {
1526
+ const name = toolName(tool);
1527
+ if (name && seen.has(name)) continue;
1528
+ if (name) seen.add(name);
1529
+ deduped.push(tool);
1530
+ }
1531
+ return deduped;
1532
+ }
1533
+ function createBuiltinElectricTools(custom) {
1534
+ return async (context) => {
1535
+ const builtinTools = (0, __electric_ax_agents_runtime_tools.createEventSourceTools)(context);
1536
+ const customTools = custom ? await custom(context) : [];
1537
+ return dedupeToolsByName([...builtinTools, ...customTools]);
1538
+ };
1539
+ }
1506
1540
  async function createBuiltinAgentHandler(options) {
1507
1541
  const { agentServerUrl, serveEndpoint, workingDirectory, streamFn, createElectricTools, publicUrl, runtimeName, baseSkillsDir: baseSkillsDirOverride, serverHeaders, defaultDispatchPolicyForType } = options;
1508
1542
  const modelCatalog = await createBuiltinModelCatalog({ allowMockFallback: Boolean(streamFn) });
@@ -1545,7 +1579,7 @@ async function createBuiltinAgentHandler(options) {
1545
1579
  defaultDispatchPolicyForType,
1546
1580
  serverHeaders,
1547
1581
  idleTimeout: 5 * 6e4,
1548
- createElectricTools,
1582
+ createElectricTools: createBuiltinElectricTools(createElectricTools),
1549
1583
  publicUrl,
1550
1584
  name: runtimeName ?? `builtin-agents`
1551
1585
  });
@@ -1887,6 +1921,7 @@ Object.defineProperty(exports, 'braveSearchTool', {
1887
1921
  exports.buildHortonSystemPrompt = buildHortonSystemPrompt
1888
1922
  exports.createAgentHandler = createAgentHandler
1889
1923
  exports.createBuiltinAgentHandler = createBuiltinAgentHandler
1924
+ exports.createBuiltinElectricTools = createBuiltinElectricTools
1890
1925
  exports.createHortonDocsSupport = createHortonDocsSupport
1891
1926
  exports.createHortonTools = createHortonTools
1892
1927
  exports.createSpawnWorkerTool = createSpawnWorkerTool
package/dist/index.d.cts CHANGED
@@ -1,8 +1,8 @@
1
- import { AgentConfig, AgentTool, AvailableProvider, DispatchPolicy, EntityRegistry, EntityStreamDBWithActions, HandlerContext, HeadersProvider, PullWakeRunnerConfig, RuntimeHandler, SkillsRegistry, WakeEvent } from "@electric-ax/agents-runtime";
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?: (context: {
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; //#endregion
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?: (context: {
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
- } //#endregion
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,6 +1,6 @@
1
- import { AgentConfig, AgentTool, AvailableProvider, DispatchPolicy, EntityRegistry, EntityStreamDBWithActions, HandlerContext, HeadersProvider, PullWakeRunnerConfig, RuntimeHandler, SkillsRegistry, WakeEvent } from "@electric-ax/agents-runtime";
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";
3
+ import { ChangeEvent } from "@durable-streams/state";
4
4
  import { ListedEntry as McpListedEntry, McpConfig, McpServerConfig, McpServerConfig as McpServerConfig$1, Registry, Registry as McpRegistry, RegistrySnapshot, RegistrySubscriber } from "@electric-ax/agents-mcp";
5
5
  import { AgentTool as AgentTool$1, StreamFn } from "@mariozechner/pi-agent-core";
6
6
  import { IncomingMessage, ServerResponse } from "node:http";
@@ -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?: (context: {
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; //#endregion
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?: (context: {
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
- } //#endregion
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,6 +2,7 @@ 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
8
  import { eq, not, queryOnce } from "@durable-streams/state";
@@ -13,7 +14,6 @@ import { Type } from "@sinclair/typebox";
13
14
  import { load } from "sqlite-vec";
14
15
  import { nanoid } from "nanoid";
15
16
  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
17
  import { bridgeMcpTool, buildPromptTools, buildResourceTools, createRegistry, keychainPersistence, loadConfig, mcp, watchConfig } from "@electric-ax/agents-mcp";
18
18
 
19
19
  //#region src/log.ts
@@ -785,12 +785,14 @@ const REASONING_EFFORT_VALUES = [
785
785
  const DEFAULT_ANTHROPIC_MODEL = `claude-sonnet-4-6`;
786
786
  const DEFAULT_OPENAI_MODEL = `gpt-4.1`;
787
787
  const DEFAULT_CODEX_MODEL = `gpt-5.4`;
788
+ const DEFAULT_DEEPSEEK_MODEL = `deepseek-v4-flash`;
788
789
  function modelValue(provider, id) {
789
790
  return `${provider}:${id}`;
790
791
  }
791
792
  function providerLabel(provider) {
792
793
  if (provider === `anthropic`) return `Anthropic`;
793
794
  if (provider === `openai-codex`) return `OpenAI Codex`;
795
+ if (provider === `deepseek`) return `DeepSeek`;
794
796
  return `OpenAI`;
795
797
  }
796
798
  function configuredProviders() {
@@ -817,6 +819,9 @@ async function fetchAvailableModelIds(provider) {
817
819
  "anthropic-version": `2023-06-01`
818
820
  },
819
821
  signal: AbortSignal.timeout(3e3)
822
+ }) : provider === `deepseek` ? await fetch(`https://api.deepseek.com/v1/models`, {
823
+ headers: { authorization: `Bearer ${process.env.DEEPSEEK_API_KEY ?? ``}` },
824
+ signal: AbortSignal.timeout(3e3)
820
825
  }) : await fetch(`https://api.openai.com/v1/models`, {
821
826
  headers: { authorization: `Bearer ${process.env.OPENAI_API_KEY ?? ``}` },
822
827
  signal: AbortSignal.timeout(3e3)
@@ -877,7 +882,7 @@ async function createBuiltinModelCatalog(options = {}) {
877
882
  if (providers.length === 0 && options.allowMockFallback) return mockFallbackCatalog();
878
883
  const choices = (await Promise.all(providers.map((provider) => choicesForProvider(provider)))).flat();
879
884
  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];
885
+ 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
886
  return {
882
887
  choices,
883
888
  defaultChoice
@@ -999,6 +1004,7 @@ async function generateTitle(userMessage, llmCall, onFallback) {
999
1004
  }
1000
1005
  function buildHortonSystemPrompt(workingDirectory, opts = {}) {
1001
1006
  const docsTools = opts.hasDocsSupport ? `\n- search_durable_agents_docs: hybrid search over the built-in Durable Agents docs index` : ``;
1007
+ 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
1008
  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
1009
  const docsGuidance = opts.hasDocsSupport ? `\n- For ANY question about Electric Agents, Durable Agents, or this framework, ALWAYS use search_durable_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
1010
  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.
@@ -1052,8 +1058,8 @@ When a user opens with a greeting ("hi", "hello", "hey", etc.) or a broad statem
1052
1058
  - web_search: search the web
1053
1059
  - fetch_url: fetch and convert a URL to markdown
1054
1060
  - spawn_worker: dispatch a subagent for an isolated task
1055
- - send: send a message to an Electric Agent/entity by entity URL
1056
- ${docsTools}${skillsTools}
1061
+ - send: send a message to an Electric Agent/entity. To schedule future work for yourself, call send with self: true and afterMs.
1062
+ ${eventSourceTools}${docsTools}${skillsTools}
1057
1063
 
1058
1064
  # Working with files
1059
1065
  - Prefer edit over write when modifying existing files.
@@ -1086,6 +1092,11 @@ Report outcomes faithfully. If a command failed, say so with the relevant output
1086
1092
  Working directory: ${workingDirectory}
1087
1093
  The current year is ${new Date().getFullYear()}.`;
1088
1094
  }
1095
+ function getToolName(tool) {
1096
+ if (typeof tool !== `object` || tool === null) return null;
1097
+ const name = tool.name;
1098
+ return typeof name === `string` ? name : null;
1099
+ }
1089
1100
  function createHortonTools(workingDirectory, ctx, readSet, opts = {}) {
1090
1101
  return [
1091
1102
  createBashTool(workingDirectory),
@@ -1100,7 +1111,7 @@ function createHortonTools(workingDirectory, ctx, readSet, opts = {}) {
1100
1111
  logPrefix: opts.logPrefix ?? `[horton]`
1101
1112
  })] : [fetchUrlTool],
1102
1113
  createSpawnWorkerTool(ctx, opts.modelConfig),
1103
- createSendTool(ctx.send),
1114
+ createSendTool(ctx.send, { selfEntityUrl: ctx.entityUrl }),
1104
1115
  ...opts.docsSearchTool ? [opts.docsSearchTool] : []
1105
1116
  ];
1106
1117
  }
@@ -1152,6 +1163,7 @@ function createAssistantHandler(options) {
1152
1163
  ...skillsRegistry && skillsRegistry.catalog.size > 0 ? createSkillTools(skillsRegistry, ctx) : [],
1153
1164
  ...mcp.tools()
1154
1165
  ];
1166
+ const hasEventSourceTools = tools.some((tool) => getToolName(tool) === `list_event_sources`);
1155
1167
  const titlePromise = ctx.firstWake && !ctx.tags.title ? (async () => {
1156
1168
  const firstUserMessage = await extractFirstUserMessage(ctx);
1157
1169
  if (!firstUserMessage) return;
@@ -1238,7 +1250,8 @@ function createAssistantHandler(options) {
1238
1250
  hasSkills,
1239
1251
  docsUrl,
1240
1252
  modelProvider: modelConfig.provider,
1241
- modelId: String(modelConfig.model)
1253
+ modelId: String(modelConfig.model),
1254
+ hasEventSourceTools
1242
1255
  }),
1243
1256
  ...modelConfig,
1244
1257
  tools,
@@ -1350,7 +1363,7 @@ function buildToolsForWorker(tools, workingDirectory, ctx, readSet) {
1350
1363
  out.push(createSpawnWorkerTool(ctx));
1351
1364
  break;
1352
1365
  case `send`:
1353
- out.push(createSendTool(ctx.send));
1366
+ out.push(createSendTool(ctx.send, { selfEntityUrl: ctx.entityUrl }));
1354
1367
  break;
1355
1368
  }
1356
1369
  return out;
@@ -1479,6 +1492,27 @@ function registerWorker(registry, options) {
1479
1492
  //#endregion
1480
1493
  //#region src/bootstrap.ts
1481
1494
  const DEFAULT_BUILTIN_AGENT_HANDLER_PATH = `/_electric/builtin-agent-handler`;
1495
+ function toolName(tool) {
1496
+ return typeof tool.name === `string` ? tool.name : null;
1497
+ }
1498
+ function dedupeToolsByName(tools) {
1499
+ const seen = new Set();
1500
+ const deduped = [];
1501
+ for (const tool of tools) {
1502
+ const name = toolName(tool);
1503
+ if (name && seen.has(name)) continue;
1504
+ if (name) seen.add(name);
1505
+ deduped.push(tool);
1506
+ }
1507
+ return deduped;
1508
+ }
1509
+ function createBuiltinElectricTools(custom) {
1510
+ return async (context) => {
1511
+ const builtinTools = createEventSourceTools(context);
1512
+ const customTools = custom ? await custom(context) : [];
1513
+ return dedupeToolsByName([...builtinTools, ...customTools]);
1514
+ };
1515
+ }
1482
1516
  async function createBuiltinAgentHandler(options) {
1483
1517
  const { agentServerUrl, serveEndpoint, workingDirectory, streamFn, createElectricTools, publicUrl, runtimeName, baseSkillsDir: baseSkillsDirOverride, serverHeaders, defaultDispatchPolicyForType } = options;
1484
1518
  const modelCatalog = await createBuiltinModelCatalog({ allowMockFallback: Boolean(streamFn) });
@@ -1521,7 +1555,7 @@ async function createBuiltinAgentHandler(options) {
1521
1555
  defaultDispatchPolicyForType,
1522
1556
  serverHeaders,
1523
1557
  idleTimeout: 5 * 6e4,
1524
- createElectricTools,
1558
+ createElectricTools: createBuiltinElectricTools(createElectricTools),
1525
1559
  publicUrl,
1526
1560
  name: runtimeName ?? `builtin-agents`
1527
1561
  });
@@ -1850,4 +1884,4 @@ async function runBuiltinAgentsEntrypoint({ env = process.env, cwd = process.cwd
1850
1884
  }
1851
1885
 
1852
1886
  //#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 };
1887
+ 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.5",
3
+ "version": "0.4.8",
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.1"
52
+ "@electric-ax/agents-runtime": "0.3.4"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@types/better-sqlite3": "^7.6.13",