@electric-ax/agents 0.4.8 → 0.4.10

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.
@@ -3,9 +3,8 @@ import path from "node:path";
3
3
  import fs from "node:fs";
4
4
  import pino from "pino";
5
5
  import { fileURLToPath } from "node:url";
6
- import { appendPathToUrl, completeWithLowCostModel, createEntityRegistry, createPullWakeRunner, createRuntimeHandler, createSkillTools, createSkillsRegistry, db, detectAvailableProviders, readCodexAccessToken, registerToolProvider, unregisterToolProvider } from "@electric-ax/agents-runtime";
6
+ import { MOONSHOT_API_BASE_URL, MOONSHOT_PROVIDER, appendPathToUrl, completeWithLowCostModel, createEntityRegistry, createPullWakeRunner, createRuntimeHandler, createSkillTools, createSkillsRegistry, db, detectAvailableProviders, getMoonshotApiKey, getMoonshotModels, readCodexAccessToken, registerToolProvider, unregisterToolProvider } from "@electric-ax/agents-runtime";
7
7
  import { braveSearchTool, createBashTool, createEditTool, createEventSourceTools, createFetchUrlTool, createReadFileTool, createSendTool, createWriteTool, fetchUrlTool } from "@electric-ax/agents-runtime/tools";
8
- import { eq, not, queryOnce } from "@durable-streams/state";
9
8
  import { z } from "zod";
10
9
  import { createHash } from "node:crypto";
11
10
  import fs$1 from "node:fs/promises";
@@ -652,9 +651,9 @@ function createHortonDocsSupport(workingDirectory, opts = {}) {
652
651
  },
653
652
  createSearchTool() {
654
653
  return {
655
- name: `search_durable_agents_docs`,
656
- label: `Search Durable Agents Docs`,
657
- description: `Run a hybrid BM25 plus vector search over the local Durable Agents documentation index.`,
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({
@@ -775,6 +774,7 @@ function createSpawnWorkerTool(ctx, modelConfig) {
775
774
 
776
775
  //#endregion
777
776
  //#region src/model-catalog.ts
777
+ const MODEL_INPUTS_SCHEMA_DEF = `electricModelInputs`;
778
778
  const REASONING_EFFORT_VALUES = [
779
779
  `auto`,
780
780
  `minimal`,
@@ -786,13 +786,15 @@ 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
788
  const DEFAULT_DEEPSEEK_MODEL = `deepseek-v4-flash`;
789
+ const DEFAULT_MOONSHOT_MODEL = `kimi-k2.6`;
789
790
  function modelValue(provider, id) {
790
791
  return `${provider}:${id}`;
791
792
  }
792
- function providerLabel(provider) {
793
+ function builtinModelProviderLabel(provider) {
793
794
  if (provider === `anthropic`) return `Anthropic`;
794
795
  if (provider === `openai-codex`) return `OpenAI Codex`;
795
796
  if (provider === `deepseek`) return `DeepSeek`;
797
+ if (provider === MOONSHOT_PROVIDER) return `Kimi`;
796
798
  return `OpenAI`;
797
799
  }
798
800
  function configuredProviders() {
@@ -804,7 +806,8 @@ function mockFallbackCatalog() {
804
806
  id: DEFAULT_ANTHROPIC_MODEL,
805
807
  label: `Anthropic ${DEFAULT_ANTHROPIC_MODEL}`,
806
808
  value: modelValue(`anthropic`, DEFAULT_ANTHROPIC_MODEL),
807
- reasoning: true
809
+ reasoning: true,
810
+ input: [`text`, `image`]
808
811
  };
809
812
  return {
810
813
  choices: [fallback],
@@ -822,6 +825,9 @@ async function fetchAvailableModelIds(provider) {
822
825
  }) : provider === `deepseek` ? await fetch(`https://api.deepseek.com/v1/models`, {
823
826
  headers: { authorization: `Bearer ${process.env.DEEPSEEK_API_KEY ?? ``}` },
824
827
  signal: AbortSignal.timeout(3e3)
828
+ }) : provider === MOONSHOT_PROVIDER ? await fetch(`${MOONSHOT_API_BASE_URL}/models`, {
829
+ headers: { authorization: `Bearer ${getMoonshotApiKey() ?? ``}` },
830
+ signal: AbortSignal.timeout(3e3)
825
831
  }) : await fetch(`https://api.openai.com/v1/models`, {
826
832
  headers: { authorization: `Bearer ${process.env.OPENAI_API_KEY ?? ``}` },
827
833
  signal: AbortSignal.timeout(3e3)
@@ -835,24 +841,42 @@ async function fetchAvailableModelIds(provider) {
835
841
  return null;
836
842
  }
837
843
  }
838
- async function choicesForProvider(provider) {
839
- const knownModels = getModels(provider);
840
- if (provider === `openai-codex`) return knownModels.map((model) => ({
844
+ function knownModelsForProvider(provider) {
845
+ return provider === MOONSHOT_PROVIDER ? getMoonshotModels() : getModels(provider);
846
+ }
847
+ function choiceForKnownModel(provider, model) {
848
+ return {
841
849
  provider,
842
850
  id: model.id,
843
- label: `${providerLabel(provider)} ${model.name}`,
851
+ label: `${builtinModelProviderLabel(provider)} ${model.name}`,
844
852
  value: modelValue(provider, model.id),
845
- reasoning: model.reasoning
846
- }));
853
+ reasoning: model.reasoning,
854
+ input: model.input
855
+ };
856
+ }
857
+ function listBuiltinModelChoices(providers) {
858
+ return providers.flatMap((provider) => knownModelsForProvider(provider).map((model) => choiceForKnownModel(provider, model)));
859
+ }
860
+ async function choicesForProvider(provider) {
861
+ const knownChoices = listBuiltinModelChoices([provider]);
862
+ if (provider === `openai-codex`) return knownChoices;
847
863
  const availableIds = await fetchAvailableModelIds(provider);
848
- const models = availableIds === null ? knownModels : knownModels.filter((model) => availableIds.has(model.id));
849
- return models.map((model) => ({
850
- provider,
851
- id: model.id,
852
- label: `${providerLabel(provider)} ${model.name}`,
853
- value: modelValue(provider, model.id),
854
- reasoning: model.reasoning
855
- }));
864
+ return availableIds === null ? knownChoices : knownChoices.filter((choice) => availableIds.has(choice.id));
865
+ }
866
+ function enabledModelSet(values) {
867
+ if (!values) return null;
868
+ const enabled = new Set();
869
+ for (const value of values) {
870
+ const trimmed = value.trim();
871
+ if (trimmed) enabled.add(trimmed);
872
+ }
873
+ return enabled.size > 0 ? enabled : null;
874
+ }
875
+ function filterChoicesByEnabledModels(choices, values) {
876
+ const enabled = enabledModelSet(values);
877
+ if (!enabled) return choices;
878
+ const filtered = choices.filter((choice) => enabled.has(choice.value));
879
+ return filtered.length > 0 ? filtered : choices;
856
880
  }
857
881
  function withProviderPayloadDefaults(config, choice, reasoningEffort) {
858
882
  if (choice.provider !== `openai` && choice.provider !== `openai-codex` || !choice.reasoning) return config;
@@ -880,9 +904,10 @@ function parseReasoningEffort(value) {
880
904
  async function createBuiltinModelCatalog(options = {}) {
881
905
  const providers = configuredProviders();
882
906
  if (providers.length === 0 && options.allowMockFallback) return mockFallbackCatalog();
883
- const choices = (await Promise.all(providers.map((provider) => choicesForProvider(provider)))).flat();
907
+ const providerChoices = (await Promise.all(providers.map((provider) => choicesForProvider(provider)))).flat();
908
+ const choices = filterChoicesByEnabledModels(providerChoices, options.enabledModelValues);
884
909
  if (choices.length === 0) return options.allowMockFallback ? mockFallbackCatalog() : null;
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];
910
+ 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.find((choice) => choice.provider === MOONSHOT_PROVIDER && choice.id === DEFAULT_MOONSHOT_MODEL) ?? choices[0];
886
911
  return {
887
912
  choices,
888
913
  defaultChoice
@@ -898,13 +923,24 @@ function resolveBuiltinModelConfig(catalog, args) {
898
923
  provider: choice.provider,
899
924
  model: choice.id,
900
925
  ...reasoningEffort && { reasoningEffort },
901
- ...choice.provider === `openai-codex` && { getApiKey: () => readCodexAccessToken() }
926
+ ...choice.provider === `openai-codex` && { getApiKey: () => readCodexAccessToken() },
927
+ ...choice.provider === MOONSHOT_PROVIDER && { getApiKey: () => getMoonshotApiKey() }
902
928
  };
903
929
  return withProviderPayloadDefaults(config, choice, reasoningEffort);
904
930
  }
905
931
  function modelChoiceValues(catalog) {
906
932
  return catalog.choices.map((choice) => choice.value);
907
933
  }
934
+ function modelInputSchemaDefs(catalog) {
935
+ return { [MODEL_INPUTS_SCHEMA_DEF]: {
936
+ type: `object`,
937
+ properties: Object.fromEntries(catalog.choices.map((choice) => [choice.value, {
938
+ type: `array`,
939
+ items: { enum: [`text`, `image`] },
940
+ default: choice.input
941
+ }]))
942
+ } };
943
+ }
908
944
 
909
945
  //#endregion
910
946
  //#region src/agents/horton.ts
@@ -1002,10 +1038,10 @@ async function generateTitle(userMessage, llmCall, onFallback) {
1002
1038
  }
1003
1039
  }
1004
1040
  function buildHortonSystemPrompt(workingDirectory, opts = {}) {
1005
- const docsTools = opts.hasDocsSupport ? `\n- search_durable_agents_docs: hybrid search over the built-in Durable Agents docs index` : ``;
1041
+ const docsTools = opts.hasDocsSupport ? `\n- search_electric_agents_docs: hybrid search over the built-in Electric Agents docs index` : ``;
1006
1042
  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` : ``;
1007
1043
  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` : ``;
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.` : ``;
1044
+ 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.` : ``;
1009
1045
  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.
1010
1046
 
1011
1047
  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.
@@ -1024,7 +1060,7 @@ Do NOT load a skill and then ignore its instructions. The skill is there because
1024
1060
  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:
1025
1061
 
1026
1062
  - **Learn the concepts first** → Explain what Electric Agents is, answer questions, point to docs.
1027
- Use search_durable_agents_docs to look up answers. Only load the quickstart skill if the user explicitly asks for a hands-on guided tutorial.
1063
+ 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.
1028
1064
 
1029
1065
  - **Hands-on guided tutorial** → Load the quickstart skill (or tell them to type \`/quickstart\`).
1030
1066
  This is a step-by-step build that takes them from zero to a running app.
@@ -1034,11 +1070,11 @@ When a user is new or asks how to get started with Electric Agents, **don't assu
1034
1070
  This sets up project structure and orients them in the codebase.
1035
1071
 
1036
1072
  - **Have a specific question?** → Answer it directly.
1037
- Use search_durable_agents_docs first, then fall back to fetch_url or general knowledge if needed.
1073
+ Use search_electric_agents_docs first, then fall back to fetch_url or general knowledge if needed.
1038
1074
 
1039
1075
  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.`;
1040
1076
  const docsUrlGuidance = opts.docsUrl ? `\n# Electric Agents documentation
1041
- - ${opts.hasDocsSupport ? `If search_durable_agents_docs is available, use it first (faster, hybrid search).` : `Use fetch_url to look up documentation pages.`}
1077
+ - ${opts.hasDocsSupport ? `If search_electric_agents_docs is available, use it first (faster, hybrid search).` : `Use fetch_url to look up documentation pages.`}
1042
1078
  - The Electric Agents docs site is at ${opts.docsUrl}
1043
1079
  - 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).
1044
1080
  - For general coding questions unrelated to Electric Agents, use web_search or your own knowledge.` : ``;
@@ -1123,12 +1159,45 @@ function payloadToTitleText(payload) {
1123
1159
  }
1124
1160
  return String(payload);
1125
1161
  }
1162
+ function attachmentTitleText(attachment) {
1163
+ const mimeType = typeof attachment.mimeType === `string` ? attachment.mimeType : ``;
1164
+ const filename = typeof attachment.filename === `string` && attachment.filename.trim() ? attachment.filename.trim() : typeof attachment.id === `string` ? attachment.id : `attachment`;
1165
+ const kind = mimeType.startsWith(`image/`) ? `image` : `file`;
1166
+ return `Attached ${kind}: ${filename}`;
1167
+ }
1168
+ function attachmentsForInboxMessage(ctx, inboxKey) {
1169
+ const manifests = ctx.db.collections.manifests?.toArray;
1170
+ if (!Array.isArray(manifests)) return [];
1171
+ return manifests.filter((entry) => {
1172
+ if (!entry || typeof entry !== `object`) return false;
1173
+ const attachment = entry;
1174
+ if (attachment.kind !== `attachment`) return false;
1175
+ if (attachment.role !== `input`) return false;
1176
+ const subject = attachment.subject;
1177
+ return subject !== null && typeof subject === `object` && !Array.isArray(subject) && subject.type === `inbox` && subject.key === inboxKey;
1178
+ });
1179
+ }
1180
+ function messageTitleText(ctx, message) {
1181
+ const pieces = [];
1182
+ const text = payloadToTitleText(message.payload).trim();
1183
+ if (text) pieces.push(text);
1184
+ const key = typeof message.key === `string` ? message.key : null;
1185
+ const attachments = key ? attachmentsForInboxMessage(ctx, key) : [];
1186
+ for (const attachment of attachments) {
1187
+ const attachmentText = attachmentTitleText(attachment);
1188
+ if (attachmentText) pieces.push(attachmentText);
1189
+ }
1190
+ return pieces.join(`\n`);
1191
+ }
1126
1192
  async function extractFirstUserMessage(ctx) {
1127
- const firstMessage = await queryOnce((q) => q.from({ inbox: ctx.db.collections.inbox }).where(({ inbox }) => not(eq(inbox.from, `system`))).orderBy(({ inbox }) => inbox._seq, `asc`).findOne());
1193
+ const firstMessage = ctx.db.collections.inbox.toArray.filter((message) => message.from !== `system`).sort((left, right) => messageSeq(left) - messageSeq(right))[0];
1128
1194
  if (!firstMessage) return null;
1129
- const text = payloadToTitleText(firstMessage.payload);
1195
+ const text = messageTitleText(ctx, firstMessage);
1130
1196
  return text.length > 0 ? text : null;
1131
1197
  }
1198
+ function messageSeq(message) {
1199
+ return typeof message._seq === `number` ? message._seq : -1;
1200
+ }
1132
1201
  function readAgentsMd(workingDirectory) {
1133
1202
  const agentsMdPath = path.join(workingDirectory, `AGENTS.md`);
1134
1203
  try {
@@ -1163,7 +1232,7 @@ function createAssistantHandler(options) {
1163
1232
  ...mcp.tools()
1164
1233
  ];
1165
1234
  const hasEventSourceTools = tools.some((tool) => getToolName(tool) === `list_event_sources`);
1166
- const titlePromise = ctx.firstWake && !ctx.tags.title ? (async () => {
1235
+ const titlePromise = !ctx.tags.title ? (async () => {
1167
1236
  const firstUserMessage = await extractFirstUserMessage(ctx);
1168
1237
  if (!firstUserMessage) return;
1169
1238
  let title = null;
@@ -1284,21 +1353,13 @@ function registerHorton(registry, options) {
1284
1353
  model: z.enum(modelChoiceValues(modelCatalog)).default(modelCatalog.defaultChoice.value),
1285
1354
  reasoningEffort: z.enum(REASONING_EFFORT_VALUES).default(`auto`).describe(`Reasoning effort for compatible reasoning models. Auto uses a safe provider default.`),
1286
1355
  workingDirectory: z.string().optional().describe(`Working directory for file operations. Defaults to the server's configured cwd.`)
1287
- });
1356
+ }).meta({ $defs: modelInputSchemaDefs(modelCatalog) });
1288
1357
  registry.define(`horton`, {
1289
1358
  description: `Friendly capable assistant — chat, code, research, dispatch`,
1290
1359
  creationSchema: hortonCreationSchema,
1291
1360
  handler: assistantHandler
1292
1361
  });
1293
- const typeNames = [`horton`];
1294
- if (streamFn) {
1295
- registry.define(`chat`, {
1296
- description: `Compatibility alias for the built-in assistant type.`,
1297
- handler: assistantHandler
1298
- });
1299
- typeNames.push(`chat`);
1300
- }
1301
- return typeNames;
1362
+ return [`horton`];
1302
1363
  }
1303
1364
 
1304
1365
  //#endregion
@@ -1512,10 +1573,13 @@ function createBuiltinElectricTools(custom) {
1512
1573
  };
1513
1574
  }
1514
1575
  async function createBuiltinAgentHandler(options) {
1515
- const { agentServerUrl, serveEndpoint, workingDirectory, streamFn, createElectricTools, publicUrl, runtimeName, baseSkillsDir: baseSkillsDirOverride, serverHeaders, defaultDispatchPolicyForType } = options;
1516
- const modelCatalog = await createBuiltinModelCatalog({ allowMockFallback: Boolean(streamFn) });
1576
+ const { agentServerUrl, serveEndpoint, workingDirectory, streamFn, enabledModelValues, createElectricTools, publicUrl, runtimeName, baseSkillsDir: baseSkillsDirOverride, serverHeaders, defaultDispatchPolicyForType } = options;
1577
+ const modelCatalog = await createBuiltinModelCatalog({
1578
+ allowMockFallback: Boolean(streamFn),
1579
+ enabledModelValues
1580
+ });
1517
1581
  if (!modelCatalog) {
1518
- serverLog.warn(`[builtin-agents] no supported model provider API key found — set ANTHROPIC_API_KEY or OPENAI_API_KEY`);
1582
+ serverLog.warn(`[builtin-agents] no supported model provider API key found — set ANTHROPIC_API_KEY, OPENAI_API_KEY, DEEPSEEK_API_KEY, or MOONSHOT_API_KEY`);
1519
1583
  return null;
1520
1584
  }
1521
1585
  const cwd = workingDirectory ?? process.cwd();
@@ -1709,9 +1773,10 @@ var BuiltinAgentsServer = class {
1709
1773
  publicUrl,
1710
1774
  runtimeName: `builtin-agents`,
1711
1775
  baseSkillsDir: this.options.baseSkillsDir,
1776
+ enabledModelValues: this.options.enabledModelValues,
1712
1777
  serverHeaders: pullWake.headers
1713
1778
  });
1714
- if (!this.bootstrap) throw new Error(`ANTHROPIC_API_KEY or OPENAI_API_KEY must be set before starting builtin agents`);
1779
+ if (!this.bootstrap) throw new Error(`ANTHROPIC_API_KEY, OPENAI_API_KEY, DEEPSEEK_API_KEY, or MOONSHOT_API_KEY must be set before starting builtin agents`);
1715
1780
  await registerBuiltinAgentTypes(this.bootstrap);
1716
1781
  const registeredRunner = pullWake.registerRunner ? await this.registerPullWakeRunner(pullWake) : null;
1717
1782
  this.pullWakeRunner = createPullWakeRunner({
package/dist/index.cjs CHANGED
@@ -29,7 +29,6 @@ const __electric_ax_agents_runtime = __toESM(require("@electric-ax/agents-runtim
29
29
  const __electric_ax_agents_runtime_tools = __toESM(require("@electric-ax/agents-runtime/tools"));
30
30
  const node_fs = __toESM(require("node:fs"));
31
31
  const pino = __toESM(require("pino"));
32
- const __durable_streams_state = __toESM(require("@durable-streams/state"));
33
32
  const zod = __toESM(require("zod"));
34
33
  const node_crypto = __toESM(require("node:crypto"));
35
34
  const node_fs_promises = __toESM(require("node:fs/promises"));
@@ -676,9 +675,9 @@ function createHortonDocsSupport(workingDirectory, opts = {}) {
676
675
  },
677
676
  createSearchTool() {
678
677
  return {
679
- name: `search_durable_agents_docs`,
680
- label: `Search Durable Agents Docs`,
681
- description: `Run a hybrid BM25 plus vector search over the local Durable Agents documentation index.`,
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({
@@ -799,6 +798,7 @@ function createSpawnWorkerTool(ctx, modelConfig) {
799
798
 
800
799
  //#endregion
801
800
  //#region src/model-catalog.ts
801
+ const MODEL_INPUTS_SCHEMA_DEF = `electricModelInputs`;
802
802
  const REASONING_EFFORT_VALUES = [
803
803
  `auto`,
804
804
  `minimal`,
@@ -810,13 +810,15 @@ 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
812
  const DEFAULT_DEEPSEEK_MODEL = `deepseek-v4-flash`;
813
+ const DEFAULT_MOONSHOT_MODEL = `kimi-k2.6`;
813
814
  function modelValue(provider, id) {
814
815
  return `${provider}:${id}`;
815
816
  }
816
- function providerLabel(provider) {
817
+ function builtinModelProviderLabel(provider) {
817
818
  if (provider === `anthropic`) return `Anthropic`;
818
819
  if (provider === `openai-codex`) return `OpenAI Codex`;
819
820
  if (provider === `deepseek`) return `DeepSeek`;
821
+ if (provider === __electric_ax_agents_runtime.MOONSHOT_PROVIDER) return `Kimi`;
820
822
  return `OpenAI`;
821
823
  }
822
824
  function configuredProviders() {
@@ -828,7 +830,8 @@ function mockFallbackCatalog() {
828
830
  id: DEFAULT_ANTHROPIC_MODEL,
829
831
  label: `Anthropic ${DEFAULT_ANTHROPIC_MODEL}`,
830
832
  value: modelValue(`anthropic`, DEFAULT_ANTHROPIC_MODEL),
831
- reasoning: true
833
+ reasoning: true,
834
+ input: [`text`, `image`]
832
835
  };
833
836
  return {
834
837
  choices: [fallback],
@@ -846,6 +849,9 @@ async function fetchAvailableModelIds(provider) {
846
849
  }) : provider === `deepseek` ? await fetch(`https://api.deepseek.com/v1/models`, {
847
850
  headers: { authorization: `Bearer ${process.env.DEEPSEEK_API_KEY ?? ``}` },
848
851
  signal: AbortSignal.timeout(3e3)
852
+ }) : provider === __electric_ax_agents_runtime.MOONSHOT_PROVIDER ? await fetch(`${__electric_ax_agents_runtime.MOONSHOT_API_BASE_URL}/models`, {
853
+ headers: { authorization: `Bearer ${(0, __electric_ax_agents_runtime.getMoonshotApiKey)() ?? ``}` },
854
+ signal: AbortSignal.timeout(3e3)
849
855
  }) : await fetch(`https://api.openai.com/v1/models`, {
850
856
  headers: { authorization: `Bearer ${process.env.OPENAI_API_KEY ?? ``}` },
851
857
  signal: AbortSignal.timeout(3e3)
@@ -859,24 +865,42 @@ async function fetchAvailableModelIds(provider) {
859
865
  return null;
860
866
  }
861
867
  }
862
- async function choicesForProvider(provider) {
863
- const knownModels = (0, __mariozechner_pi_ai.getModels)(provider);
864
- if (provider === `openai-codex`) return knownModels.map((model) => ({
868
+ function knownModelsForProvider(provider) {
869
+ return provider === __electric_ax_agents_runtime.MOONSHOT_PROVIDER ? (0, __electric_ax_agents_runtime.getMoonshotModels)() : (0, __mariozechner_pi_ai.getModels)(provider);
870
+ }
871
+ function choiceForKnownModel(provider, model) {
872
+ return {
865
873
  provider,
866
874
  id: model.id,
867
- label: `${providerLabel(provider)} ${model.name}`,
875
+ label: `${builtinModelProviderLabel(provider)} ${model.name}`,
868
876
  value: modelValue(provider, model.id),
869
- reasoning: model.reasoning
870
- }));
877
+ reasoning: model.reasoning,
878
+ input: model.input
879
+ };
880
+ }
881
+ function listBuiltinModelChoices(providers) {
882
+ return providers.flatMap((provider) => knownModelsForProvider(provider).map((model) => choiceForKnownModel(provider, model)));
883
+ }
884
+ async function choicesForProvider(provider) {
885
+ const knownChoices = listBuiltinModelChoices([provider]);
886
+ if (provider === `openai-codex`) return knownChoices;
871
887
  const availableIds = await fetchAvailableModelIds(provider);
872
- const models = availableIds === null ? knownModels : knownModels.filter((model) => availableIds.has(model.id));
873
- return models.map((model) => ({
874
- provider,
875
- id: model.id,
876
- label: `${providerLabel(provider)} ${model.name}`,
877
- value: modelValue(provider, model.id),
878
- reasoning: model.reasoning
879
- }));
888
+ return availableIds === null ? knownChoices : knownChoices.filter((choice) => availableIds.has(choice.id));
889
+ }
890
+ function enabledModelSet(values) {
891
+ if (!values) return null;
892
+ const enabled = new Set();
893
+ for (const value of values) {
894
+ const trimmed = value.trim();
895
+ if (trimmed) enabled.add(trimmed);
896
+ }
897
+ return enabled.size > 0 ? enabled : null;
898
+ }
899
+ function filterChoicesByEnabledModels(choices, values) {
900
+ const enabled = enabledModelSet(values);
901
+ if (!enabled) return choices;
902
+ const filtered = choices.filter((choice) => enabled.has(choice.value));
903
+ return filtered.length > 0 ? filtered : choices;
880
904
  }
881
905
  function withProviderPayloadDefaults(config, choice, reasoningEffort) {
882
906
  if (choice.provider !== `openai` && choice.provider !== `openai-codex` || !choice.reasoning) return config;
@@ -904,9 +928,10 @@ function parseReasoningEffort(value) {
904
928
  async function createBuiltinModelCatalog(options = {}) {
905
929
  const providers = configuredProviders();
906
930
  if (providers.length === 0 && options.allowMockFallback) return mockFallbackCatalog();
907
- const choices = (await Promise.all(providers.map((provider) => choicesForProvider(provider)))).flat();
931
+ const providerChoices = (await Promise.all(providers.map((provider) => choicesForProvider(provider)))).flat();
932
+ const choices = filterChoicesByEnabledModels(providerChoices, options.enabledModelValues);
908
933
  if (choices.length === 0) return options.allowMockFallback ? mockFallbackCatalog() : null;
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];
934
+ 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.find((choice) => choice.provider === __electric_ax_agents_runtime.MOONSHOT_PROVIDER && choice.id === DEFAULT_MOONSHOT_MODEL) ?? choices[0];
910
935
  return {
911
936
  choices,
912
937
  defaultChoice
@@ -922,13 +947,24 @@ function resolveBuiltinModelConfig(catalog, args) {
922
947
  provider: choice.provider,
923
948
  model: choice.id,
924
949
  ...reasoningEffort && { reasoningEffort },
925
- ...choice.provider === `openai-codex` && { getApiKey: () => (0, __electric_ax_agents_runtime.readCodexAccessToken)() }
950
+ ...choice.provider === `openai-codex` && { getApiKey: () => (0, __electric_ax_agents_runtime.readCodexAccessToken)() },
951
+ ...choice.provider === __electric_ax_agents_runtime.MOONSHOT_PROVIDER && { getApiKey: () => (0, __electric_ax_agents_runtime.getMoonshotApiKey)() }
926
952
  };
927
953
  return withProviderPayloadDefaults(config, choice, reasoningEffort);
928
954
  }
929
955
  function modelChoiceValues(catalog) {
930
956
  return catalog.choices.map((choice) => choice.value);
931
957
  }
958
+ function modelInputSchemaDefs(catalog) {
959
+ return { [MODEL_INPUTS_SCHEMA_DEF]: {
960
+ type: `object`,
961
+ properties: Object.fromEntries(catalog.choices.map((choice) => [choice.value, {
962
+ type: `array`,
963
+ items: { enum: [`text`, `image`] },
964
+ default: choice.input
965
+ }]))
966
+ } };
967
+ }
932
968
 
933
969
  //#endregion
934
970
  //#region src/agents/horton.ts
@@ -1027,10 +1063,10 @@ async function generateTitle(userMessage, llmCall, onFallback) {
1027
1063
  }
1028
1064
  }
1029
1065
  function buildHortonSystemPrompt(workingDirectory, opts = {}) {
1030
- const docsTools = opts.hasDocsSupport ? `\n- search_durable_agents_docs: hybrid search over the built-in Durable Agents docs index` : ``;
1066
+ const docsTools = opts.hasDocsSupport ? `\n- search_electric_agents_docs: hybrid search over the built-in Electric Agents docs index` : ``;
1031
1067
  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` : ``;
1032
1068
  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` : ``;
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.` : ``;
1069
+ 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.` : ``;
1034
1070
  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.
1035
1071
 
1036
1072
  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.
@@ -1049,7 +1085,7 @@ Do NOT load a skill and then ignore its instructions. The skill is there because
1049
1085
  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:
1050
1086
 
1051
1087
  - **Learn the concepts first** → Explain what Electric Agents is, answer questions, point to docs.
1052
- Use search_durable_agents_docs to look up answers. Only load the quickstart skill if the user explicitly asks for a hands-on guided tutorial.
1088
+ 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.
1053
1089
 
1054
1090
  - **Hands-on guided tutorial** → Load the quickstart skill (or tell them to type \`/quickstart\`).
1055
1091
  This is a step-by-step build that takes them from zero to a running app.
@@ -1059,11 +1095,11 @@ When a user is new or asks how to get started with Electric Agents, **don't assu
1059
1095
  This sets up project structure and orients them in the codebase.
1060
1096
 
1061
1097
  - **Have a specific question?** → Answer it directly.
1062
- Use search_durable_agents_docs first, then fall back to fetch_url or general knowledge if needed.
1098
+ Use search_electric_agents_docs first, then fall back to fetch_url or general knowledge if needed.
1063
1099
 
1064
1100
  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.`;
1065
1101
  const docsUrlGuidance = opts.docsUrl ? `\n# Electric Agents documentation
1066
- - ${opts.hasDocsSupport ? `If search_durable_agents_docs is available, use it first (faster, hybrid search).` : `Use fetch_url to look up documentation pages.`}
1102
+ - ${opts.hasDocsSupport ? `If search_electric_agents_docs is available, use it first (faster, hybrid search).` : `Use fetch_url to look up documentation pages.`}
1067
1103
  - The Electric Agents docs site is at ${opts.docsUrl}
1068
1104
  - 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).
1069
1105
  - For general coding questions unrelated to Electric Agents, use web_search or your own knowledge.` : ``;
@@ -1148,12 +1184,45 @@ function payloadToTitleText(payload) {
1148
1184
  }
1149
1185
  return String(payload);
1150
1186
  }
1187
+ function attachmentTitleText(attachment) {
1188
+ const mimeType = typeof attachment.mimeType === `string` ? attachment.mimeType : ``;
1189
+ const filename = typeof attachment.filename === `string` && attachment.filename.trim() ? attachment.filename.trim() : typeof attachment.id === `string` ? attachment.id : `attachment`;
1190
+ const kind = mimeType.startsWith(`image/`) ? `image` : `file`;
1191
+ return `Attached ${kind}: ${filename}`;
1192
+ }
1193
+ function attachmentsForInboxMessage(ctx, inboxKey) {
1194
+ const manifests = ctx.db.collections.manifests?.toArray;
1195
+ if (!Array.isArray(manifests)) return [];
1196
+ return manifests.filter((entry) => {
1197
+ if (!entry || typeof entry !== `object`) return false;
1198
+ const attachment = entry;
1199
+ if (attachment.kind !== `attachment`) return false;
1200
+ if (attachment.role !== `input`) return false;
1201
+ const subject = attachment.subject;
1202
+ return subject !== null && typeof subject === `object` && !Array.isArray(subject) && subject.type === `inbox` && subject.key === inboxKey;
1203
+ });
1204
+ }
1205
+ function messageTitleText(ctx, message) {
1206
+ const pieces = [];
1207
+ const text = payloadToTitleText(message.payload).trim();
1208
+ if (text) pieces.push(text);
1209
+ const key = typeof message.key === `string` ? message.key : null;
1210
+ const attachments = key ? attachmentsForInboxMessage(ctx, key) : [];
1211
+ for (const attachment of attachments) {
1212
+ const attachmentText = attachmentTitleText(attachment);
1213
+ if (attachmentText) pieces.push(attachmentText);
1214
+ }
1215
+ return pieces.join(`\n`);
1216
+ }
1151
1217
  async function extractFirstUserMessage(ctx) {
1152
- const firstMessage = await (0, __durable_streams_state.queryOnce)((q) => q.from({ inbox: ctx.db.collections.inbox }).where(({ inbox }) => (0, __durable_streams_state.not)((0, __durable_streams_state.eq)(inbox.from, `system`))).orderBy(({ inbox }) => inbox._seq, `asc`).findOne());
1218
+ const firstMessage = ctx.db.collections.inbox.toArray.filter((message) => message.from !== `system`).sort((left, right) => messageSeq(left) - messageSeq(right))[0];
1153
1219
  if (!firstMessage) return null;
1154
- const text = payloadToTitleText(firstMessage.payload);
1220
+ const text = messageTitleText(ctx, firstMessage);
1155
1221
  return text.length > 0 ? text : null;
1156
1222
  }
1223
+ function messageSeq(message) {
1224
+ return typeof message._seq === `number` ? message._seq : -1;
1225
+ }
1157
1226
  function readAgentsMd(workingDirectory) {
1158
1227
  const agentsMdPath = node_path.default.join(workingDirectory, `AGENTS.md`);
1159
1228
  try {
@@ -1188,7 +1257,7 @@ function createAssistantHandler(options) {
1188
1257
  ...__electric_ax_agents_mcp.mcp.tools()
1189
1258
  ];
1190
1259
  const hasEventSourceTools = tools.some((tool) => getToolName(tool) === `list_event_sources`);
1191
- const titlePromise = ctx.firstWake && !ctx.tags.title ? (async () => {
1260
+ const titlePromise = !ctx.tags.title ? (async () => {
1192
1261
  const firstUserMessage = await extractFirstUserMessage(ctx);
1193
1262
  if (!firstUserMessage) return;
1194
1263
  let title = null;
@@ -1309,21 +1378,13 @@ function registerHorton(registry, options) {
1309
1378
  model: zod.z.enum(modelChoiceValues(modelCatalog)).default(modelCatalog.defaultChoice.value),
1310
1379
  reasoningEffort: zod.z.enum(REASONING_EFFORT_VALUES).default(`auto`).describe(`Reasoning effort for compatible reasoning models. Auto uses a safe provider default.`),
1311
1380
  workingDirectory: zod.z.string().optional().describe(`Working directory for file operations. Defaults to the server's configured cwd.`)
1312
- });
1381
+ }).meta({ $defs: modelInputSchemaDefs(modelCatalog) });
1313
1382
  registry.define(`horton`, {
1314
1383
  description: `Friendly capable assistant — chat, code, research, dispatch`,
1315
1384
  creationSchema: hortonCreationSchema,
1316
1385
  handler: assistantHandler
1317
1386
  });
1318
- const typeNames = [`horton`];
1319
- if (streamFn) {
1320
- registry.define(`chat`, {
1321
- description: `Compatibility alias for the built-in assistant type.`,
1322
- handler: assistantHandler
1323
- });
1324
- typeNames.push(`chat`);
1325
- }
1326
- return typeNames;
1387
+ return [`horton`];
1327
1388
  }
1328
1389
 
1329
1390
  //#endregion
@@ -1538,10 +1599,13 @@ function createBuiltinElectricTools(custom) {
1538
1599
  };
1539
1600
  }
1540
1601
  async function createBuiltinAgentHandler(options) {
1541
- const { agentServerUrl, serveEndpoint, workingDirectory, streamFn, createElectricTools, publicUrl, runtimeName, baseSkillsDir: baseSkillsDirOverride, serverHeaders, defaultDispatchPolicyForType } = options;
1542
- const modelCatalog = await createBuiltinModelCatalog({ allowMockFallback: Boolean(streamFn) });
1602
+ const { agentServerUrl, serveEndpoint, workingDirectory, streamFn, enabledModelValues, createElectricTools, publicUrl, runtimeName, baseSkillsDir: baseSkillsDirOverride, serverHeaders, defaultDispatchPolicyForType } = options;
1603
+ const modelCatalog = await createBuiltinModelCatalog({
1604
+ allowMockFallback: Boolean(streamFn),
1605
+ enabledModelValues
1606
+ });
1543
1607
  if (!modelCatalog) {
1544
- serverLog.warn(`[builtin-agents] no supported model provider API key found — set ANTHROPIC_API_KEY or OPENAI_API_KEY`);
1608
+ serverLog.warn(`[builtin-agents] no supported model provider API key found — set ANTHROPIC_API_KEY, OPENAI_API_KEY, DEEPSEEK_API_KEY, or MOONSHOT_API_KEY`);
1545
1609
  return null;
1546
1610
  }
1547
1611
  const cwd = workingDirectory ?? process.cwd();
@@ -1745,9 +1809,10 @@ var BuiltinAgentsServer = class {
1745
1809
  publicUrl,
1746
1810
  runtimeName: `builtin-agents`,
1747
1811
  baseSkillsDir: this.options.baseSkillsDir,
1812
+ enabledModelValues: this.options.enabledModelValues,
1748
1813
  serverHeaders: pullWake.headers
1749
1814
  });
1750
- if (!this.bootstrap) throw new Error(`ANTHROPIC_API_KEY or OPENAI_API_KEY must be set before starting builtin agents`);
1815
+ if (!this.bootstrap) throw new Error(`ANTHROPIC_API_KEY, OPENAI_API_KEY, DEEPSEEK_API_KEY, or MOONSHOT_API_KEY must be set before starting builtin agents`);
1751
1816
  await registerBuiltinAgentTypes(this.bootstrap);
1752
1817
  const registeredRunner = pullWake.registerRunner ? await this.registerPullWakeRunner(pullWake) : null;
1753
1818
  this.pullWakeRunner = (0, __electric_ax_agents_runtime.createPullWakeRunner)({
@@ -1919,6 +1984,7 @@ Object.defineProperty(exports, 'braveSearchTool', {
1919
1984
  }
1920
1985
  });
1921
1986
  exports.buildHortonSystemPrompt = buildHortonSystemPrompt
1987
+ exports.builtinModelProviderLabel = builtinModelProviderLabel
1922
1988
  exports.createAgentHandler = createAgentHandler
1923
1989
  exports.createBuiltinAgentHandler = createBuiltinAgentHandler
1924
1990
  exports.createBuiltinElectricTools = createBuiltinElectricTools
@@ -1926,6 +1992,7 @@ exports.createHortonDocsSupport = createHortonDocsSupport
1926
1992
  exports.createHortonTools = createHortonTools
1927
1993
  exports.createSpawnWorkerTool = createSpawnWorkerTool
1928
1994
  exports.generateTitle = generateTitle
1995
+ exports.listBuiltinModelChoices = listBuiltinModelChoices
1929
1996
  exports.registerAgentTypes = registerAgentTypes
1930
1997
  exports.registerBuiltinAgentTypes = registerBuiltinAgentTypes
1931
1998
  exports.registerHorton = registerHorton
package/dist/index.d.cts CHANGED
@@ -20,6 +20,7 @@ interface BuiltinAgentHandlerOptions {
20
20
  serveEndpoint?: string;
21
21
  workingDirectory?: string;
22
22
  streamFn?: StreamFn;
23
+ enabledModelValues?: ReadonlyArray<string> | null;
23
24
  publicUrl?: string;
24
25
  runtimeName?: string;
25
26
  /** Override for the built-in skills directory; required when embedders bundle this package. */
@@ -79,6 +80,8 @@ interface BuiltinAgentsServerOptions {
79
80
  loadProjectMcpConfig?: boolean;
80
81
  /** Override for the built-in skills directory; required when embedders bundle this package. */
81
82
  baseSkillsDir?: string;
83
+ /** Restrict the model values exposed by built-in agent creation schemas. */
84
+ enabledModelValues?: ReadonlyArray<string> | null;
82
85
  createElectricTools?: NonNullable<ProcessWakeConfig[`createElectricTools`]>;
83
86
  }
84
87
  declare class BuiltinAgentsServer {
@@ -125,23 +128,31 @@ declare function runBuiltinAgentsEntrypoint({
125
128
  //#endregion
126
129
  //#region src/model-catalog.d.ts
127
130
  type BuiltinModelProvider = AvailableProvider;
131
+ type BuiltinModelInput = `text` | `image`;
128
132
  interface BuiltinModelChoice {
129
133
  provider: BuiltinModelProvider;
130
134
  id: string;
131
135
  label: string;
132
136
  value: string;
133
137
  reasoning: boolean;
138
+ input: Array<BuiltinModelInput>;
134
139
  }
135
140
  interface BuiltinModelCatalog {
136
141
  choices: Array<BuiltinModelChoice>;
137
142
  defaultChoice: BuiltinModelChoice;
138
143
  }
144
+ interface BuiltinModelCatalogOptions {
145
+ allowMockFallback?: boolean;
146
+ enabledModelValues?: ReadonlyArray<string> | null;
147
+ }
139
148
  declare const REASONING_EFFORT_VALUES: readonly ["auto", "minimal", "low", "medium", "high"];
140
149
  type BuiltinReasoningEffort = (typeof REASONING_EFFORT_VALUES)[number];
141
150
  type ExplicitReasoningEffort = Exclude<BuiltinReasoningEffort, `auto`>;
142
151
  type BuiltinAgentModelConfig = Pick<AgentConfig, `model` | `provider` | `onPayload` | `getApiKey`> & {
143
152
  reasoningEffort?: ExplicitReasoningEffort;
144
153
  };
154
+ declare function builtinModelProviderLabel(provider: BuiltinModelProvider): string;
155
+ declare function listBuiltinModelChoices(providers: ReadonlyArray<BuiltinModelProvider>): Array<BuiltinModelChoice>;
145
156
  declare function resolveBuiltinModelConfig(catalog: BuiltinModelCatalog, args: Readonly<Record<string, unknown>>): BuiltinAgentModelConfig;
146
157
 
147
158
  //#endregion
@@ -203,4 +214,4 @@ declare function createHortonDocsSupport(workingDirectory: string, opts?: {
203
214
  }): HortonDocsSupport | null;
204
215
 
205
216
  //#endregion
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 };
217
+ export { AgentHandlerResult, BuiltinAgentHandlerOptions, BuiltinAgentsEntrypointOptions, BuiltinAgentsEntrypointServer, BuiltinAgentsServer, BuiltinAgentsServerOptions, BuiltinElectricToolsFactory, BuiltinModelCatalogOptions, BuiltinModelChoice, BuiltinModelProvider, DEFAULT_BUILTIN_AGENT_HANDLER_PATH, HORTON_MODEL, McpConfig, McpListedEntry, McpRegistry, McpServerConfig, RegistrySnapshot, RegistrySubscriber, RunBuiltinAgentsEntrypointOptions, WORKER_TOOL_NAMES, WorkerToolName, braveSearchTool, buildHortonSystemPrompt, builtinModelProviderLabel, createAgentHandler, createBuiltinAgentHandler, createBuiltinElectricTools, createHortonDocsSupport, createHortonTools, createSpawnWorkerTool, generateTitle, listBuiltinModelChoices, registerAgentTypes, registerBuiltinAgentTypes, registerHorton, registerWorker, resolveBuiltinAgentsEntrypointOptions, runBuiltinAgentsEntrypoint };
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import { AgentConfig, AgentTool, AvailableProvider, DispatchPolicy, EntityRegistry, HandlerContext, HeadersProvider, ProcessWakeConfig, PullWakeRunnerConfig, RuntimeHandler, SkillsRegistry, WakeEvent } from "@electric-ax/agents-runtime";
2
2
  import { braveSearchTool } from "@electric-ax/agents-runtime/tools";
3
- import { ChangeEvent } from "@durable-streams/state";
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";
@@ -20,6 +20,7 @@ interface BuiltinAgentHandlerOptions {
20
20
  serveEndpoint?: string;
21
21
  workingDirectory?: string;
22
22
  streamFn?: StreamFn;
23
+ enabledModelValues?: ReadonlyArray<string> | null;
23
24
  publicUrl?: string;
24
25
  runtimeName?: string;
25
26
  /** Override for the built-in skills directory; required when embedders bundle this package. */
@@ -79,6 +80,8 @@ interface BuiltinAgentsServerOptions {
79
80
  loadProjectMcpConfig?: boolean;
80
81
  /** Override for the built-in skills directory; required when embedders bundle this package. */
81
82
  baseSkillsDir?: string;
83
+ /** Restrict the model values exposed by built-in agent creation schemas. */
84
+ enabledModelValues?: ReadonlyArray<string> | null;
82
85
  createElectricTools?: NonNullable<ProcessWakeConfig[`createElectricTools`]>;
83
86
  }
84
87
  declare class BuiltinAgentsServer {
@@ -125,23 +128,31 @@ declare function runBuiltinAgentsEntrypoint({
125
128
  //#endregion
126
129
  //#region src/model-catalog.d.ts
127
130
  type BuiltinModelProvider = AvailableProvider;
131
+ type BuiltinModelInput = `text` | `image`;
128
132
  interface BuiltinModelChoice {
129
133
  provider: BuiltinModelProvider;
130
134
  id: string;
131
135
  label: string;
132
136
  value: string;
133
137
  reasoning: boolean;
138
+ input: Array<BuiltinModelInput>;
134
139
  }
135
140
  interface BuiltinModelCatalog {
136
141
  choices: Array<BuiltinModelChoice>;
137
142
  defaultChoice: BuiltinModelChoice;
138
143
  }
144
+ interface BuiltinModelCatalogOptions {
145
+ allowMockFallback?: boolean;
146
+ enabledModelValues?: ReadonlyArray<string> | null;
147
+ }
139
148
  declare const REASONING_EFFORT_VALUES: readonly ["auto", "minimal", "low", "medium", "high"];
140
149
  type BuiltinReasoningEffort = (typeof REASONING_EFFORT_VALUES)[number];
141
150
  type ExplicitReasoningEffort = Exclude<BuiltinReasoningEffort, `auto`>;
142
151
  type BuiltinAgentModelConfig = Pick<AgentConfig, `model` | `provider` | `onPayload` | `getApiKey`> & {
143
152
  reasoningEffort?: ExplicitReasoningEffort;
144
153
  };
154
+ declare function builtinModelProviderLabel(provider: BuiltinModelProvider): string;
155
+ declare function listBuiltinModelChoices(providers: ReadonlyArray<BuiltinModelProvider>): Array<BuiltinModelChoice>;
145
156
  declare function resolveBuiltinModelConfig(catalog: BuiltinModelCatalog, args: Readonly<Record<string, unknown>>): BuiltinAgentModelConfig;
146
157
 
147
158
  //#endregion
@@ -203,4 +214,4 @@ declare function createHortonDocsSupport(workingDirectory: string, opts?: {
203
214
  }): HortonDocsSupport | null;
204
215
 
205
216
  //#endregion
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 };
217
+ export { AgentHandlerResult, BuiltinAgentHandlerOptions, BuiltinAgentsEntrypointOptions, BuiltinAgentsEntrypointServer, BuiltinAgentsServer, BuiltinAgentsServerOptions, BuiltinElectricToolsFactory, BuiltinModelCatalogOptions, BuiltinModelChoice, BuiltinModelProvider, DEFAULT_BUILTIN_AGENT_HANDLER_PATH, HORTON_MODEL, McpConfig, McpListedEntry, McpRegistry, McpServerConfig, RegistrySnapshot, RegistrySubscriber, RunBuiltinAgentsEntrypointOptions, WORKER_TOOL_NAMES, WorkerToolName, braveSearchTool, buildHortonSystemPrompt, builtinModelProviderLabel, createAgentHandler, createBuiltinAgentHandler, createBuiltinElectricTools, createHortonDocsSupport, createHortonTools, createSpawnWorkerTool, generateTitle, listBuiltinModelChoices, registerAgentTypes, registerBuiltinAgentTypes, registerHorton, registerWorker, resolveBuiltinAgentsEntrypointOptions, runBuiltinAgentsEntrypoint };
package/dist/index.js CHANGED
@@ -1,11 +1,10 @@
1
1
  import { mergeElectricPrincipalHeader } from "./server-headers-KD5yHFYT.js";
2
2
  import path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
- import { appendPathToUrl, completeWithLowCostModel, createEntityRegistry, createPullWakeRunner, createRuntimeHandler, createSkillTools, createSkillsRegistry, db, detectAvailableProviders, readCodexAccessToken, registerToolProvider, unregisterToolProvider } from "@electric-ax/agents-runtime";
4
+ import { MOONSHOT_API_BASE_URL, MOONSHOT_PROVIDER, appendPathToUrl, completeWithLowCostModel, createEntityRegistry, createPullWakeRunner, createRuntimeHandler, createSkillTools, createSkillsRegistry, db, detectAvailableProviders, getMoonshotApiKey, getMoonshotModels, readCodexAccessToken, registerToolProvider, unregisterToolProvider } from "@electric-ax/agents-runtime";
5
5
  import { braveSearchTool, braveSearchTool as braveSearchTool$1, createBashTool, createEditTool, createEventSourceTools, createFetchUrlTool, createReadFileTool, createSendTool, createWriteTool, fetchUrlTool } from "@electric-ax/agents-runtime/tools";
6
6
  import fs from "node:fs";
7
7
  import pino from "pino";
8
- import { eq, not, queryOnce } from "@durable-streams/state";
9
8
  import { z } from "zod";
10
9
  import { createHash } from "node:crypto";
11
10
  import fs$1 from "node:fs/promises";
@@ -652,9 +651,9 @@ function createHortonDocsSupport(workingDirectory, opts = {}) {
652
651
  },
653
652
  createSearchTool() {
654
653
  return {
655
- name: `search_durable_agents_docs`,
656
- label: `Search Durable Agents Docs`,
657
- description: `Run a hybrid BM25 plus vector search over the local Durable Agents documentation index.`,
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({
@@ -775,6 +774,7 @@ function createSpawnWorkerTool(ctx, modelConfig) {
775
774
 
776
775
  //#endregion
777
776
  //#region src/model-catalog.ts
777
+ const MODEL_INPUTS_SCHEMA_DEF = `electricModelInputs`;
778
778
  const REASONING_EFFORT_VALUES = [
779
779
  `auto`,
780
780
  `minimal`,
@@ -786,13 +786,15 @@ 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
788
  const DEFAULT_DEEPSEEK_MODEL = `deepseek-v4-flash`;
789
+ const DEFAULT_MOONSHOT_MODEL = `kimi-k2.6`;
789
790
  function modelValue(provider, id) {
790
791
  return `${provider}:${id}`;
791
792
  }
792
- function providerLabel(provider) {
793
+ function builtinModelProviderLabel(provider) {
793
794
  if (provider === `anthropic`) return `Anthropic`;
794
795
  if (provider === `openai-codex`) return `OpenAI Codex`;
795
796
  if (provider === `deepseek`) return `DeepSeek`;
797
+ if (provider === MOONSHOT_PROVIDER) return `Kimi`;
796
798
  return `OpenAI`;
797
799
  }
798
800
  function configuredProviders() {
@@ -804,7 +806,8 @@ function mockFallbackCatalog() {
804
806
  id: DEFAULT_ANTHROPIC_MODEL,
805
807
  label: `Anthropic ${DEFAULT_ANTHROPIC_MODEL}`,
806
808
  value: modelValue(`anthropic`, DEFAULT_ANTHROPIC_MODEL),
807
- reasoning: true
809
+ reasoning: true,
810
+ input: [`text`, `image`]
808
811
  };
809
812
  return {
810
813
  choices: [fallback],
@@ -822,6 +825,9 @@ async function fetchAvailableModelIds(provider) {
822
825
  }) : provider === `deepseek` ? await fetch(`https://api.deepseek.com/v1/models`, {
823
826
  headers: { authorization: `Bearer ${process.env.DEEPSEEK_API_KEY ?? ``}` },
824
827
  signal: AbortSignal.timeout(3e3)
828
+ }) : provider === MOONSHOT_PROVIDER ? await fetch(`${MOONSHOT_API_BASE_URL}/models`, {
829
+ headers: { authorization: `Bearer ${getMoonshotApiKey() ?? ``}` },
830
+ signal: AbortSignal.timeout(3e3)
825
831
  }) : await fetch(`https://api.openai.com/v1/models`, {
826
832
  headers: { authorization: `Bearer ${process.env.OPENAI_API_KEY ?? ``}` },
827
833
  signal: AbortSignal.timeout(3e3)
@@ -835,24 +841,42 @@ async function fetchAvailableModelIds(provider) {
835
841
  return null;
836
842
  }
837
843
  }
838
- async function choicesForProvider(provider) {
839
- const knownModels = getModels(provider);
840
- if (provider === `openai-codex`) return knownModels.map((model) => ({
844
+ function knownModelsForProvider(provider) {
845
+ return provider === MOONSHOT_PROVIDER ? getMoonshotModels() : getModels(provider);
846
+ }
847
+ function choiceForKnownModel(provider, model) {
848
+ return {
841
849
  provider,
842
850
  id: model.id,
843
- label: `${providerLabel(provider)} ${model.name}`,
851
+ label: `${builtinModelProviderLabel(provider)} ${model.name}`,
844
852
  value: modelValue(provider, model.id),
845
- reasoning: model.reasoning
846
- }));
853
+ reasoning: model.reasoning,
854
+ input: model.input
855
+ };
856
+ }
857
+ function listBuiltinModelChoices(providers) {
858
+ return providers.flatMap((provider) => knownModelsForProvider(provider).map((model) => choiceForKnownModel(provider, model)));
859
+ }
860
+ async function choicesForProvider(provider) {
861
+ const knownChoices = listBuiltinModelChoices([provider]);
862
+ if (provider === `openai-codex`) return knownChoices;
847
863
  const availableIds = await fetchAvailableModelIds(provider);
848
- const models = availableIds === null ? knownModels : knownModels.filter((model) => availableIds.has(model.id));
849
- return models.map((model) => ({
850
- provider,
851
- id: model.id,
852
- label: `${providerLabel(provider)} ${model.name}`,
853
- value: modelValue(provider, model.id),
854
- reasoning: model.reasoning
855
- }));
864
+ return availableIds === null ? knownChoices : knownChoices.filter((choice) => availableIds.has(choice.id));
865
+ }
866
+ function enabledModelSet(values) {
867
+ if (!values) return null;
868
+ const enabled = new Set();
869
+ for (const value of values) {
870
+ const trimmed = value.trim();
871
+ if (trimmed) enabled.add(trimmed);
872
+ }
873
+ return enabled.size > 0 ? enabled : null;
874
+ }
875
+ function filterChoicesByEnabledModels(choices, values) {
876
+ const enabled = enabledModelSet(values);
877
+ if (!enabled) return choices;
878
+ const filtered = choices.filter((choice) => enabled.has(choice.value));
879
+ return filtered.length > 0 ? filtered : choices;
856
880
  }
857
881
  function withProviderPayloadDefaults(config, choice, reasoningEffort) {
858
882
  if (choice.provider !== `openai` && choice.provider !== `openai-codex` || !choice.reasoning) return config;
@@ -880,9 +904,10 @@ function parseReasoningEffort(value) {
880
904
  async function createBuiltinModelCatalog(options = {}) {
881
905
  const providers = configuredProviders();
882
906
  if (providers.length === 0 && options.allowMockFallback) return mockFallbackCatalog();
883
- const choices = (await Promise.all(providers.map((provider) => choicesForProvider(provider)))).flat();
907
+ const providerChoices = (await Promise.all(providers.map((provider) => choicesForProvider(provider)))).flat();
908
+ const choices = filterChoicesByEnabledModels(providerChoices, options.enabledModelValues);
884
909
  if (choices.length === 0) return options.allowMockFallback ? mockFallbackCatalog() : null;
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];
910
+ 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.find((choice) => choice.provider === MOONSHOT_PROVIDER && choice.id === DEFAULT_MOONSHOT_MODEL) ?? choices[0];
886
911
  return {
887
912
  choices,
888
913
  defaultChoice
@@ -898,13 +923,24 @@ function resolveBuiltinModelConfig(catalog, args) {
898
923
  provider: choice.provider,
899
924
  model: choice.id,
900
925
  ...reasoningEffort && { reasoningEffort },
901
- ...choice.provider === `openai-codex` && { getApiKey: () => readCodexAccessToken() }
926
+ ...choice.provider === `openai-codex` && { getApiKey: () => readCodexAccessToken() },
927
+ ...choice.provider === MOONSHOT_PROVIDER && { getApiKey: () => getMoonshotApiKey() }
902
928
  };
903
929
  return withProviderPayloadDefaults(config, choice, reasoningEffort);
904
930
  }
905
931
  function modelChoiceValues(catalog) {
906
932
  return catalog.choices.map((choice) => choice.value);
907
933
  }
934
+ function modelInputSchemaDefs(catalog) {
935
+ return { [MODEL_INPUTS_SCHEMA_DEF]: {
936
+ type: `object`,
937
+ properties: Object.fromEntries(catalog.choices.map((choice) => [choice.value, {
938
+ type: `array`,
939
+ items: { enum: [`text`, `image`] },
940
+ default: choice.input
941
+ }]))
942
+ } };
943
+ }
908
944
 
909
945
  //#endregion
910
946
  //#region src/agents/horton.ts
@@ -1003,10 +1039,10 @@ async function generateTitle(userMessage, llmCall, onFallback) {
1003
1039
  }
1004
1040
  }
1005
1041
  function buildHortonSystemPrompt(workingDirectory, opts = {}) {
1006
- const docsTools = opts.hasDocsSupport ? `\n- search_durable_agents_docs: hybrid search over the built-in Durable Agents docs index` : ``;
1042
+ const docsTools = opts.hasDocsSupport ? `\n- search_electric_agents_docs: hybrid search over the built-in Electric Agents docs index` : ``;
1007
1043
  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` : ``;
1008
1044
  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` : ``;
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.` : ``;
1045
+ 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.` : ``;
1010
1046
  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.
1011
1047
 
1012
1048
  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.
@@ -1025,7 +1061,7 @@ Do NOT load a skill and then ignore its instructions. The skill is there because
1025
1061
  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:
1026
1062
 
1027
1063
  - **Learn the concepts first** → Explain what Electric Agents is, answer questions, point to docs.
1028
- Use search_durable_agents_docs to look up answers. Only load the quickstart skill if the user explicitly asks for a hands-on guided tutorial.
1064
+ 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.
1029
1065
 
1030
1066
  - **Hands-on guided tutorial** → Load the quickstart skill (or tell them to type \`/quickstart\`).
1031
1067
  This is a step-by-step build that takes them from zero to a running app.
@@ -1035,11 +1071,11 @@ When a user is new or asks how to get started with Electric Agents, **don't assu
1035
1071
  This sets up project structure and orients them in the codebase.
1036
1072
 
1037
1073
  - **Have a specific question?** → Answer it directly.
1038
- Use search_durable_agents_docs first, then fall back to fetch_url or general knowledge if needed.
1074
+ Use search_electric_agents_docs first, then fall back to fetch_url or general knowledge if needed.
1039
1075
 
1040
1076
  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.`;
1041
1077
  const docsUrlGuidance = opts.docsUrl ? `\n# Electric Agents documentation
1042
- - ${opts.hasDocsSupport ? `If search_durable_agents_docs is available, use it first (faster, hybrid search).` : `Use fetch_url to look up documentation pages.`}
1078
+ - ${opts.hasDocsSupport ? `If search_electric_agents_docs is available, use it first (faster, hybrid search).` : `Use fetch_url to look up documentation pages.`}
1043
1079
  - The Electric Agents docs site is at ${opts.docsUrl}
1044
1080
  - 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).
1045
1081
  - For general coding questions unrelated to Electric Agents, use web_search or your own knowledge.` : ``;
@@ -1124,12 +1160,45 @@ function payloadToTitleText(payload) {
1124
1160
  }
1125
1161
  return String(payload);
1126
1162
  }
1163
+ function attachmentTitleText(attachment) {
1164
+ const mimeType = typeof attachment.mimeType === `string` ? attachment.mimeType : ``;
1165
+ const filename = typeof attachment.filename === `string` && attachment.filename.trim() ? attachment.filename.trim() : typeof attachment.id === `string` ? attachment.id : `attachment`;
1166
+ const kind = mimeType.startsWith(`image/`) ? `image` : `file`;
1167
+ return `Attached ${kind}: ${filename}`;
1168
+ }
1169
+ function attachmentsForInboxMessage(ctx, inboxKey) {
1170
+ const manifests = ctx.db.collections.manifests?.toArray;
1171
+ if (!Array.isArray(manifests)) return [];
1172
+ return manifests.filter((entry) => {
1173
+ if (!entry || typeof entry !== `object`) return false;
1174
+ const attachment = entry;
1175
+ if (attachment.kind !== `attachment`) return false;
1176
+ if (attachment.role !== `input`) return false;
1177
+ const subject = attachment.subject;
1178
+ return subject !== null && typeof subject === `object` && !Array.isArray(subject) && subject.type === `inbox` && subject.key === inboxKey;
1179
+ });
1180
+ }
1181
+ function messageTitleText(ctx, message) {
1182
+ const pieces = [];
1183
+ const text = payloadToTitleText(message.payload).trim();
1184
+ if (text) pieces.push(text);
1185
+ const key = typeof message.key === `string` ? message.key : null;
1186
+ const attachments = key ? attachmentsForInboxMessage(ctx, key) : [];
1187
+ for (const attachment of attachments) {
1188
+ const attachmentText = attachmentTitleText(attachment);
1189
+ if (attachmentText) pieces.push(attachmentText);
1190
+ }
1191
+ return pieces.join(`\n`);
1192
+ }
1127
1193
  async function extractFirstUserMessage(ctx) {
1128
- const firstMessage = await queryOnce((q) => q.from({ inbox: ctx.db.collections.inbox }).where(({ inbox }) => not(eq(inbox.from, `system`))).orderBy(({ inbox }) => inbox._seq, `asc`).findOne());
1194
+ const firstMessage = ctx.db.collections.inbox.toArray.filter((message) => message.from !== `system`).sort((left, right) => messageSeq(left) - messageSeq(right))[0];
1129
1195
  if (!firstMessage) return null;
1130
- const text = payloadToTitleText(firstMessage.payload);
1196
+ const text = messageTitleText(ctx, firstMessage);
1131
1197
  return text.length > 0 ? text : null;
1132
1198
  }
1199
+ function messageSeq(message) {
1200
+ return typeof message._seq === `number` ? message._seq : -1;
1201
+ }
1133
1202
  function readAgentsMd(workingDirectory) {
1134
1203
  const agentsMdPath = path.join(workingDirectory, `AGENTS.md`);
1135
1204
  try {
@@ -1164,7 +1233,7 @@ function createAssistantHandler(options) {
1164
1233
  ...mcp.tools()
1165
1234
  ];
1166
1235
  const hasEventSourceTools = tools.some((tool) => getToolName(tool) === `list_event_sources`);
1167
- const titlePromise = ctx.firstWake && !ctx.tags.title ? (async () => {
1236
+ const titlePromise = !ctx.tags.title ? (async () => {
1168
1237
  const firstUserMessage = await extractFirstUserMessage(ctx);
1169
1238
  if (!firstUserMessage) return;
1170
1239
  let title = null;
@@ -1285,21 +1354,13 @@ function registerHorton(registry, options) {
1285
1354
  model: z.enum(modelChoiceValues(modelCatalog)).default(modelCatalog.defaultChoice.value),
1286
1355
  reasoningEffort: z.enum(REASONING_EFFORT_VALUES).default(`auto`).describe(`Reasoning effort for compatible reasoning models. Auto uses a safe provider default.`),
1287
1356
  workingDirectory: z.string().optional().describe(`Working directory for file operations. Defaults to the server's configured cwd.`)
1288
- });
1357
+ }).meta({ $defs: modelInputSchemaDefs(modelCatalog) });
1289
1358
  registry.define(`horton`, {
1290
1359
  description: `Friendly capable assistant — chat, code, research, dispatch`,
1291
1360
  creationSchema: hortonCreationSchema,
1292
1361
  handler: assistantHandler
1293
1362
  });
1294
- const typeNames = [`horton`];
1295
- if (streamFn) {
1296
- registry.define(`chat`, {
1297
- description: `Compatibility alias for the built-in assistant type.`,
1298
- handler: assistantHandler
1299
- });
1300
- typeNames.push(`chat`);
1301
- }
1302
- return typeNames;
1363
+ return [`horton`];
1303
1364
  }
1304
1365
 
1305
1366
  //#endregion
@@ -1514,10 +1575,13 @@ function createBuiltinElectricTools(custom) {
1514
1575
  };
1515
1576
  }
1516
1577
  async function createBuiltinAgentHandler(options) {
1517
- const { agentServerUrl, serveEndpoint, workingDirectory, streamFn, createElectricTools, publicUrl, runtimeName, baseSkillsDir: baseSkillsDirOverride, serverHeaders, defaultDispatchPolicyForType } = options;
1518
- const modelCatalog = await createBuiltinModelCatalog({ allowMockFallback: Boolean(streamFn) });
1578
+ const { agentServerUrl, serveEndpoint, workingDirectory, streamFn, enabledModelValues, createElectricTools, publicUrl, runtimeName, baseSkillsDir: baseSkillsDirOverride, serverHeaders, defaultDispatchPolicyForType } = options;
1579
+ const modelCatalog = await createBuiltinModelCatalog({
1580
+ allowMockFallback: Boolean(streamFn),
1581
+ enabledModelValues
1582
+ });
1519
1583
  if (!modelCatalog) {
1520
- serverLog.warn(`[builtin-agents] no supported model provider API key found — set ANTHROPIC_API_KEY or OPENAI_API_KEY`);
1584
+ serverLog.warn(`[builtin-agents] no supported model provider API key found — set ANTHROPIC_API_KEY, OPENAI_API_KEY, DEEPSEEK_API_KEY, or MOONSHOT_API_KEY`);
1521
1585
  return null;
1522
1586
  }
1523
1587
  const cwd = workingDirectory ?? process.cwd();
@@ -1721,9 +1785,10 @@ var BuiltinAgentsServer = class {
1721
1785
  publicUrl,
1722
1786
  runtimeName: `builtin-agents`,
1723
1787
  baseSkillsDir: this.options.baseSkillsDir,
1788
+ enabledModelValues: this.options.enabledModelValues,
1724
1789
  serverHeaders: pullWake.headers
1725
1790
  });
1726
- if (!this.bootstrap) throw new Error(`ANTHROPIC_API_KEY or OPENAI_API_KEY must be set before starting builtin agents`);
1791
+ if (!this.bootstrap) throw new Error(`ANTHROPIC_API_KEY, OPENAI_API_KEY, DEEPSEEK_API_KEY, or MOONSHOT_API_KEY must be set before starting builtin agents`);
1727
1792
  await registerBuiltinAgentTypes(this.bootstrap);
1728
1793
  const registeredRunner = pullWake.registerRunner ? await this.registerPullWakeRunner(pullWake) : null;
1729
1794
  this.pullWakeRunner = createPullWakeRunner({
@@ -1884,4 +1949,4 @@ async function runBuiltinAgentsEntrypoint({ env = process.env, cwd = process.cwd
1884
1949
  }
1885
1950
 
1886
1951
  //#endregion
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 };
1952
+ export { BuiltinAgentsServer, DEFAULT_BUILTIN_AGENT_HANDLER_PATH, HORTON_MODEL, WORKER_TOOL_NAMES, braveSearchTool, buildHortonSystemPrompt, builtinModelProviderLabel, createAgentHandler, createBuiltinAgentHandler, createBuiltinElectricTools, createHortonDocsSupport, createHortonTools, createSpawnWorkerTool, generateTitle, listBuiltinModelChoices, 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.8",
3
+ "version": "0.4.10",
4
4
  "description": "Built-in Electric Agents runtimes such as Horton and worker",
5
5
  "repository": {
6
6
  "type": "git",
@@ -38,7 +38,7 @@
38
38
  "./package.json": "./package.json"
39
39
  },
40
40
  "dependencies": {
41
- "@durable-streams/state": "https://pkg.pr.new/durable-streams/durable-streams/@durable-streams/state@5d5c217",
41
+ "@durable-streams/state": "^0.2.9",
42
42
  "@mariozechner/pi-agent-core": "^0.70.2",
43
43
  "@mariozechner/pi-ai": "^0.70.2",
44
44
  "@sinclair/typebox": "^0.34.48",
@@ -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.4"
52
+ "@electric-ax/agents-runtime": "0.3.6"
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.0.0",
61
+ "typescript": "^5.9.3",
62
62
  "vitest": "^4.1.0"
63
63
  },
64
64
  "files": [
@@ -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 `search_durable_agents_docs` to look things up.
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 `search_durable_agents_docs` tool to look up the answer in the built-in documentation before guessing or searching the web.
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