@electric-ax/agents 0.4.9 → 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,7 +3,7 @@ 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
8
  import { z } from "zod";
9
9
  import { createHash } from "node:crypto";
@@ -774,6 +774,7 @@ function createSpawnWorkerTool(ctx, modelConfig) {
774
774
 
775
775
  //#endregion
776
776
  //#region src/model-catalog.ts
777
+ const MODEL_INPUTS_SCHEMA_DEF = `electricModelInputs`;
777
778
  const REASONING_EFFORT_VALUES = [
778
779
  `auto`,
779
780
  `minimal`,
@@ -785,13 +786,15 @@ const DEFAULT_ANTHROPIC_MODEL = `claude-sonnet-4-6`;
785
786
  const DEFAULT_OPENAI_MODEL = `gpt-4.1`;
786
787
  const DEFAULT_CODEX_MODEL = `gpt-5.4`;
787
788
  const DEFAULT_DEEPSEEK_MODEL = `deepseek-v4-flash`;
789
+ const DEFAULT_MOONSHOT_MODEL = `kimi-k2.6`;
788
790
  function modelValue(provider, id) {
789
791
  return `${provider}:${id}`;
790
792
  }
791
- function providerLabel(provider) {
793
+ function builtinModelProviderLabel(provider) {
792
794
  if (provider === `anthropic`) return `Anthropic`;
793
795
  if (provider === `openai-codex`) return `OpenAI Codex`;
794
796
  if (provider === `deepseek`) return `DeepSeek`;
797
+ if (provider === MOONSHOT_PROVIDER) return `Kimi`;
795
798
  return `OpenAI`;
796
799
  }
797
800
  function configuredProviders() {
@@ -803,7 +806,8 @@ function mockFallbackCatalog() {
803
806
  id: DEFAULT_ANTHROPIC_MODEL,
804
807
  label: `Anthropic ${DEFAULT_ANTHROPIC_MODEL}`,
805
808
  value: modelValue(`anthropic`, DEFAULT_ANTHROPIC_MODEL),
806
- reasoning: true
809
+ reasoning: true,
810
+ input: [`text`, `image`]
807
811
  };
808
812
  return {
809
813
  choices: [fallback],
@@ -821,6 +825,9 @@ async function fetchAvailableModelIds(provider) {
821
825
  }) : provider === `deepseek` ? await fetch(`https://api.deepseek.com/v1/models`, {
822
826
  headers: { authorization: `Bearer ${process.env.DEEPSEEK_API_KEY ?? ``}` },
823
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)
824
831
  }) : await fetch(`https://api.openai.com/v1/models`, {
825
832
  headers: { authorization: `Bearer ${process.env.OPENAI_API_KEY ?? ``}` },
826
833
  signal: AbortSignal.timeout(3e3)
@@ -834,24 +841,42 @@ async function fetchAvailableModelIds(provider) {
834
841
  return null;
835
842
  }
836
843
  }
837
- async function choicesForProvider(provider) {
838
- const knownModels = getModels(provider);
839
- 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 {
840
849
  provider,
841
850
  id: model.id,
842
- label: `${providerLabel(provider)} ${model.name}`,
851
+ label: `${builtinModelProviderLabel(provider)} ${model.name}`,
843
852
  value: modelValue(provider, model.id),
844
- reasoning: model.reasoning
845
- }));
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;
846
863
  const availableIds = await fetchAvailableModelIds(provider);
847
- const models = availableIds === null ? knownModels : knownModels.filter((model) => availableIds.has(model.id));
848
- return models.map((model) => ({
849
- provider,
850
- id: model.id,
851
- label: `${providerLabel(provider)} ${model.name}`,
852
- value: modelValue(provider, model.id),
853
- reasoning: model.reasoning
854
- }));
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;
855
880
  }
856
881
  function withProviderPayloadDefaults(config, choice, reasoningEffort) {
857
882
  if (choice.provider !== `openai` && choice.provider !== `openai-codex` || !choice.reasoning) return config;
@@ -879,9 +904,10 @@ function parseReasoningEffort(value) {
879
904
  async function createBuiltinModelCatalog(options = {}) {
880
905
  const providers = configuredProviders();
881
906
  if (providers.length === 0 && options.allowMockFallback) return mockFallbackCatalog();
882
- 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);
883
909
  if (choices.length === 0) return options.allowMockFallback ? mockFallbackCatalog() : null;
884
- const defaultChoice = choices.find((choice) => choice.provider === `anthropic` && choice.id === DEFAULT_ANTHROPIC_MODEL) ?? choices.find((choice) => choice.provider === `openai` && choice.id === DEFAULT_OPENAI_MODEL) ?? choices.find((choice) => choice.provider === `openai-codex` && choice.id === DEFAULT_CODEX_MODEL) ?? choices.find((choice) => choice.provider === `deepseek` && choice.id === DEFAULT_DEEPSEEK_MODEL) ?? choices[0];
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];
885
911
  return {
886
912
  choices,
887
913
  defaultChoice
@@ -897,13 +923,24 @@ function resolveBuiltinModelConfig(catalog, args) {
897
923
  provider: choice.provider,
898
924
  model: choice.id,
899
925
  ...reasoningEffort && { reasoningEffort },
900
- ...choice.provider === `openai-codex` && { getApiKey: () => readCodexAccessToken() }
926
+ ...choice.provider === `openai-codex` && { getApiKey: () => readCodexAccessToken() },
927
+ ...choice.provider === MOONSHOT_PROVIDER && { getApiKey: () => getMoonshotApiKey() }
901
928
  };
902
929
  return withProviderPayloadDefaults(config, choice, reasoningEffort);
903
930
  }
904
931
  function modelChoiceValues(catalog) {
905
932
  return catalog.choices.map((choice) => choice.value);
906
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
+ }
907
944
 
908
945
  //#endregion
909
946
  //#region src/agents/horton.ts
@@ -1122,10 +1159,40 @@ function payloadToTitleText(payload) {
1122
1159
  }
1123
1160
  return String(payload);
1124
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
+ }
1125
1192
  async function extractFirstUserMessage(ctx) {
1126
1193
  const firstMessage = ctx.db.collections.inbox.toArray.filter((message) => message.from !== `system`).sort((left, right) => messageSeq(left) - messageSeq(right))[0];
1127
1194
  if (!firstMessage) return null;
1128
- const text = payloadToTitleText(firstMessage.payload);
1195
+ const text = messageTitleText(ctx, firstMessage);
1129
1196
  return text.length > 0 ? text : null;
1130
1197
  }
1131
1198
  function messageSeq(message) {
@@ -1165,7 +1232,7 @@ function createAssistantHandler(options) {
1165
1232
  ...mcp.tools()
1166
1233
  ];
1167
1234
  const hasEventSourceTools = tools.some((tool) => getToolName(tool) === `list_event_sources`);
1168
- const titlePromise = ctx.firstWake && !ctx.tags.title ? (async () => {
1235
+ const titlePromise = !ctx.tags.title ? (async () => {
1169
1236
  const firstUserMessage = await extractFirstUserMessage(ctx);
1170
1237
  if (!firstUserMessage) return;
1171
1238
  let title = null;
@@ -1286,7 +1353,7 @@ function registerHorton(registry, options) {
1286
1353
  model: z.enum(modelChoiceValues(modelCatalog)).default(modelCatalog.defaultChoice.value),
1287
1354
  reasoningEffort: z.enum(REASONING_EFFORT_VALUES).default(`auto`).describe(`Reasoning effort for compatible reasoning models. Auto uses a safe provider default.`),
1288
1355
  workingDirectory: z.string().optional().describe(`Working directory for file operations. Defaults to the server's configured cwd.`)
1289
- });
1356
+ }).meta({ $defs: modelInputSchemaDefs(modelCatalog) });
1290
1357
  registry.define(`horton`, {
1291
1358
  description: `Friendly capable assistant — chat, code, research, dispatch`,
1292
1359
  creationSchema: hortonCreationSchema,
@@ -1506,10 +1573,13 @@ function createBuiltinElectricTools(custom) {
1506
1573
  };
1507
1574
  }
1508
1575
  async function createBuiltinAgentHandler(options) {
1509
- const { agentServerUrl, serveEndpoint, workingDirectory, streamFn, createElectricTools, publicUrl, runtimeName, baseSkillsDir: baseSkillsDirOverride, serverHeaders, defaultDispatchPolicyForType } = options;
1510
- 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
+ });
1511
1581
  if (!modelCatalog) {
1512
- 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`);
1513
1583
  return null;
1514
1584
  }
1515
1585
  const cwd = workingDirectory ?? process.cwd();
@@ -1703,9 +1773,10 @@ var BuiltinAgentsServer = class {
1703
1773
  publicUrl,
1704
1774
  runtimeName: `builtin-agents`,
1705
1775
  baseSkillsDir: this.options.baseSkillsDir,
1776
+ enabledModelValues: this.options.enabledModelValues,
1706
1777
  serverHeaders: pullWake.headers
1707
1778
  });
1708
- 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`);
1709
1780
  await registerBuiltinAgentTypes(this.bootstrap);
1710
1781
  const registeredRunner = pullWake.registerRunner ? await this.registerPullWakeRunner(pullWake) : null;
1711
1782
  this.pullWakeRunner = createPullWakeRunner({
package/dist/index.cjs CHANGED
@@ -798,6 +798,7 @@ function createSpawnWorkerTool(ctx, modelConfig) {
798
798
 
799
799
  //#endregion
800
800
  //#region src/model-catalog.ts
801
+ const MODEL_INPUTS_SCHEMA_DEF = `electricModelInputs`;
801
802
  const REASONING_EFFORT_VALUES = [
802
803
  `auto`,
803
804
  `minimal`,
@@ -809,13 +810,15 @@ const DEFAULT_ANTHROPIC_MODEL = `claude-sonnet-4-6`;
809
810
  const DEFAULT_OPENAI_MODEL = `gpt-4.1`;
810
811
  const DEFAULT_CODEX_MODEL = `gpt-5.4`;
811
812
  const DEFAULT_DEEPSEEK_MODEL = `deepseek-v4-flash`;
813
+ const DEFAULT_MOONSHOT_MODEL = `kimi-k2.6`;
812
814
  function modelValue(provider, id) {
813
815
  return `${provider}:${id}`;
814
816
  }
815
- function providerLabel(provider) {
817
+ function builtinModelProviderLabel(provider) {
816
818
  if (provider === `anthropic`) return `Anthropic`;
817
819
  if (provider === `openai-codex`) return `OpenAI Codex`;
818
820
  if (provider === `deepseek`) return `DeepSeek`;
821
+ if (provider === __electric_ax_agents_runtime.MOONSHOT_PROVIDER) return `Kimi`;
819
822
  return `OpenAI`;
820
823
  }
821
824
  function configuredProviders() {
@@ -827,7 +830,8 @@ function mockFallbackCatalog() {
827
830
  id: DEFAULT_ANTHROPIC_MODEL,
828
831
  label: `Anthropic ${DEFAULT_ANTHROPIC_MODEL}`,
829
832
  value: modelValue(`anthropic`, DEFAULT_ANTHROPIC_MODEL),
830
- reasoning: true
833
+ reasoning: true,
834
+ input: [`text`, `image`]
831
835
  };
832
836
  return {
833
837
  choices: [fallback],
@@ -845,6 +849,9 @@ async function fetchAvailableModelIds(provider) {
845
849
  }) : provider === `deepseek` ? await fetch(`https://api.deepseek.com/v1/models`, {
846
850
  headers: { authorization: `Bearer ${process.env.DEEPSEEK_API_KEY ?? ``}` },
847
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)
848
855
  }) : await fetch(`https://api.openai.com/v1/models`, {
849
856
  headers: { authorization: `Bearer ${process.env.OPENAI_API_KEY ?? ``}` },
850
857
  signal: AbortSignal.timeout(3e3)
@@ -858,24 +865,42 @@ async function fetchAvailableModelIds(provider) {
858
865
  return null;
859
866
  }
860
867
  }
861
- async function choicesForProvider(provider) {
862
- const knownModels = (0, __mariozechner_pi_ai.getModels)(provider);
863
- 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 {
864
873
  provider,
865
874
  id: model.id,
866
- label: `${providerLabel(provider)} ${model.name}`,
875
+ label: `${builtinModelProviderLabel(provider)} ${model.name}`,
867
876
  value: modelValue(provider, model.id),
868
- reasoning: model.reasoning
869
- }));
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;
870
887
  const availableIds = await fetchAvailableModelIds(provider);
871
- const models = availableIds === null ? knownModels : knownModels.filter((model) => availableIds.has(model.id));
872
- return models.map((model) => ({
873
- provider,
874
- id: model.id,
875
- label: `${providerLabel(provider)} ${model.name}`,
876
- value: modelValue(provider, model.id),
877
- reasoning: model.reasoning
878
- }));
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;
879
904
  }
880
905
  function withProviderPayloadDefaults(config, choice, reasoningEffort) {
881
906
  if (choice.provider !== `openai` && choice.provider !== `openai-codex` || !choice.reasoning) return config;
@@ -903,9 +928,10 @@ function parseReasoningEffort(value) {
903
928
  async function createBuiltinModelCatalog(options = {}) {
904
929
  const providers = configuredProviders();
905
930
  if (providers.length === 0 && options.allowMockFallback) return mockFallbackCatalog();
906
- 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);
907
933
  if (choices.length === 0) return options.allowMockFallback ? mockFallbackCatalog() : null;
908
- const defaultChoice = choices.find((choice) => choice.provider === `anthropic` && choice.id === DEFAULT_ANTHROPIC_MODEL) ?? choices.find((choice) => choice.provider === `openai` && choice.id === DEFAULT_OPENAI_MODEL) ?? choices.find((choice) => choice.provider === `openai-codex` && choice.id === DEFAULT_CODEX_MODEL) ?? choices.find((choice) => choice.provider === `deepseek` && choice.id === DEFAULT_DEEPSEEK_MODEL) ?? choices[0];
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];
909
935
  return {
910
936
  choices,
911
937
  defaultChoice
@@ -921,13 +947,24 @@ function resolveBuiltinModelConfig(catalog, args) {
921
947
  provider: choice.provider,
922
948
  model: choice.id,
923
949
  ...reasoningEffort && { reasoningEffort },
924
- ...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)() }
925
952
  };
926
953
  return withProviderPayloadDefaults(config, choice, reasoningEffort);
927
954
  }
928
955
  function modelChoiceValues(catalog) {
929
956
  return catalog.choices.map((choice) => choice.value);
930
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
+ }
931
968
 
932
969
  //#endregion
933
970
  //#region src/agents/horton.ts
@@ -1147,10 +1184,40 @@ function payloadToTitleText(payload) {
1147
1184
  }
1148
1185
  return String(payload);
1149
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
+ }
1150
1217
  async function extractFirstUserMessage(ctx) {
1151
1218
  const firstMessage = ctx.db.collections.inbox.toArray.filter((message) => message.from !== `system`).sort((left, right) => messageSeq(left) - messageSeq(right))[0];
1152
1219
  if (!firstMessage) return null;
1153
- const text = payloadToTitleText(firstMessage.payload);
1220
+ const text = messageTitleText(ctx, firstMessage);
1154
1221
  return text.length > 0 ? text : null;
1155
1222
  }
1156
1223
  function messageSeq(message) {
@@ -1190,7 +1257,7 @@ function createAssistantHandler(options) {
1190
1257
  ...__electric_ax_agents_mcp.mcp.tools()
1191
1258
  ];
1192
1259
  const hasEventSourceTools = tools.some((tool) => getToolName(tool) === `list_event_sources`);
1193
- const titlePromise = ctx.firstWake && !ctx.tags.title ? (async () => {
1260
+ const titlePromise = !ctx.tags.title ? (async () => {
1194
1261
  const firstUserMessage = await extractFirstUserMessage(ctx);
1195
1262
  if (!firstUserMessage) return;
1196
1263
  let title = null;
@@ -1311,7 +1378,7 @@ function registerHorton(registry, options) {
1311
1378
  model: zod.z.enum(modelChoiceValues(modelCatalog)).default(modelCatalog.defaultChoice.value),
1312
1379
  reasoningEffort: zod.z.enum(REASONING_EFFORT_VALUES).default(`auto`).describe(`Reasoning effort for compatible reasoning models. Auto uses a safe provider default.`),
1313
1380
  workingDirectory: zod.z.string().optional().describe(`Working directory for file operations. Defaults to the server's configured cwd.`)
1314
- });
1381
+ }).meta({ $defs: modelInputSchemaDefs(modelCatalog) });
1315
1382
  registry.define(`horton`, {
1316
1383
  description: `Friendly capable assistant — chat, code, research, dispatch`,
1317
1384
  creationSchema: hortonCreationSchema,
@@ -1532,10 +1599,13 @@ function createBuiltinElectricTools(custom) {
1532
1599
  };
1533
1600
  }
1534
1601
  async function createBuiltinAgentHandler(options) {
1535
- const { agentServerUrl, serveEndpoint, workingDirectory, streamFn, createElectricTools, publicUrl, runtimeName, baseSkillsDir: baseSkillsDirOverride, serverHeaders, defaultDispatchPolicyForType } = options;
1536
- 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
+ });
1537
1607
  if (!modelCatalog) {
1538
- 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`);
1539
1609
  return null;
1540
1610
  }
1541
1611
  const cwd = workingDirectory ?? process.cwd();
@@ -1739,9 +1809,10 @@ var BuiltinAgentsServer = class {
1739
1809
  publicUrl,
1740
1810
  runtimeName: `builtin-agents`,
1741
1811
  baseSkillsDir: this.options.baseSkillsDir,
1812
+ enabledModelValues: this.options.enabledModelValues,
1742
1813
  serverHeaders: pullWake.headers
1743
1814
  });
1744
- 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`);
1745
1816
  await registerBuiltinAgentTypes(this.bootstrap);
1746
1817
  const registeredRunner = pullWake.registerRunner ? await this.registerPullWakeRunner(pullWake) : null;
1747
1818
  this.pullWakeRunner = (0, __electric_ax_agents_runtime.createPullWakeRunner)({
@@ -1913,6 +1984,7 @@ Object.defineProperty(exports, 'braveSearchTool', {
1913
1984
  }
1914
1985
  });
1915
1986
  exports.buildHortonSystemPrompt = buildHortonSystemPrompt
1987
+ exports.builtinModelProviderLabel = builtinModelProviderLabel
1916
1988
  exports.createAgentHandler = createAgentHandler
1917
1989
  exports.createBuiltinAgentHandler = createBuiltinAgentHandler
1918
1990
  exports.createBuiltinElectricTools = createBuiltinElectricTools
@@ -1920,6 +1992,7 @@ exports.createHortonDocsSupport = createHortonDocsSupport
1920
1992
  exports.createHortonTools = createHortonTools
1921
1993
  exports.createSpawnWorkerTool = createSpawnWorkerTool
1922
1994
  exports.generateTitle = generateTitle
1995
+ exports.listBuiltinModelChoices = listBuiltinModelChoices
1923
1996
  exports.registerAgentTypes = registerAgentTypes
1924
1997
  exports.registerBuiltinAgentTypes = registerBuiltinAgentTypes
1925
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
@@ -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,7 +1,7 @@
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";
@@ -774,6 +774,7 @@ function createSpawnWorkerTool(ctx, modelConfig) {
774
774
 
775
775
  //#endregion
776
776
  //#region src/model-catalog.ts
777
+ const MODEL_INPUTS_SCHEMA_DEF = `electricModelInputs`;
777
778
  const REASONING_EFFORT_VALUES = [
778
779
  `auto`,
779
780
  `minimal`,
@@ -785,13 +786,15 @@ const DEFAULT_ANTHROPIC_MODEL = `claude-sonnet-4-6`;
785
786
  const DEFAULT_OPENAI_MODEL = `gpt-4.1`;
786
787
  const DEFAULT_CODEX_MODEL = `gpt-5.4`;
787
788
  const DEFAULT_DEEPSEEK_MODEL = `deepseek-v4-flash`;
789
+ const DEFAULT_MOONSHOT_MODEL = `kimi-k2.6`;
788
790
  function modelValue(provider, id) {
789
791
  return `${provider}:${id}`;
790
792
  }
791
- function providerLabel(provider) {
793
+ function builtinModelProviderLabel(provider) {
792
794
  if (provider === `anthropic`) return `Anthropic`;
793
795
  if (provider === `openai-codex`) return `OpenAI Codex`;
794
796
  if (provider === `deepseek`) return `DeepSeek`;
797
+ if (provider === MOONSHOT_PROVIDER) return `Kimi`;
795
798
  return `OpenAI`;
796
799
  }
797
800
  function configuredProviders() {
@@ -803,7 +806,8 @@ function mockFallbackCatalog() {
803
806
  id: DEFAULT_ANTHROPIC_MODEL,
804
807
  label: `Anthropic ${DEFAULT_ANTHROPIC_MODEL}`,
805
808
  value: modelValue(`anthropic`, DEFAULT_ANTHROPIC_MODEL),
806
- reasoning: true
809
+ reasoning: true,
810
+ input: [`text`, `image`]
807
811
  };
808
812
  return {
809
813
  choices: [fallback],
@@ -821,6 +825,9 @@ async function fetchAvailableModelIds(provider) {
821
825
  }) : provider === `deepseek` ? await fetch(`https://api.deepseek.com/v1/models`, {
822
826
  headers: { authorization: `Bearer ${process.env.DEEPSEEK_API_KEY ?? ``}` },
823
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)
824
831
  }) : await fetch(`https://api.openai.com/v1/models`, {
825
832
  headers: { authorization: `Bearer ${process.env.OPENAI_API_KEY ?? ``}` },
826
833
  signal: AbortSignal.timeout(3e3)
@@ -834,24 +841,42 @@ async function fetchAvailableModelIds(provider) {
834
841
  return null;
835
842
  }
836
843
  }
837
- async function choicesForProvider(provider) {
838
- const knownModels = getModels(provider);
839
- 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 {
840
849
  provider,
841
850
  id: model.id,
842
- label: `${providerLabel(provider)} ${model.name}`,
851
+ label: `${builtinModelProviderLabel(provider)} ${model.name}`,
843
852
  value: modelValue(provider, model.id),
844
- reasoning: model.reasoning
845
- }));
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;
846
863
  const availableIds = await fetchAvailableModelIds(provider);
847
- const models = availableIds === null ? knownModels : knownModels.filter((model) => availableIds.has(model.id));
848
- return models.map((model) => ({
849
- provider,
850
- id: model.id,
851
- label: `${providerLabel(provider)} ${model.name}`,
852
- value: modelValue(provider, model.id),
853
- reasoning: model.reasoning
854
- }));
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;
855
880
  }
856
881
  function withProviderPayloadDefaults(config, choice, reasoningEffort) {
857
882
  if (choice.provider !== `openai` && choice.provider !== `openai-codex` || !choice.reasoning) return config;
@@ -879,9 +904,10 @@ function parseReasoningEffort(value) {
879
904
  async function createBuiltinModelCatalog(options = {}) {
880
905
  const providers = configuredProviders();
881
906
  if (providers.length === 0 && options.allowMockFallback) return mockFallbackCatalog();
882
- 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);
883
909
  if (choices.length === 0) return options.allowMockFallback ? mockFallbackCatalog() : null;
884
- const defaultChoice = choices.find((choice) => choice.provider === `anthropic` && choice.id === DEFAULT_ANTHROPIC_MODEL) ?? choices.find((choice) => choice.provider === `openai` && choice.id === DEFAULT_OPENAI_MODEL) ?? choices.find((choice) => choice.provider === `openai-codex` && choice.id === DEFAULT_CODEX_MODEL) ?? choices.find((choice) => choice.provider === `deepseek` && choice.id === DEFAULT_DEEPSEEK_MODEL) ?? choices[0];
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];
885
911
  return {
886
912
  choices,
887
913
  defaultChoice
@@ -897,13 +923,24 @@ function resolveBuiltinModelConfig(catalog, args) {
897
923
  provider: choice.provider,
898
924
  model: choice.id,
899
925
  ...reasoningEffort && { reasoningEffort },
900
- ...choice.provider === `openai-codex` && { getApiKey: () => readCodexAccessToken() }
926
+ ...choice.provider === `openai-codex` && { getApiKey: () => readCodexAccessToken() },
927
+ ...choice.provider === MOONSHOT_PROVIDER && { getApiKey: () => getMoonshotApiKey() }
901
928
  };
902
929
  return withProviderPayloadDefaults(config, choice, reasoningEffort);
903
930
  }
904
931
  function modelChoiceValues(catalog) {
905
932
  return catalog.choices.map((choice) => choice.value);
906
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
+ }
907
944
 
908
945
  //#endregion
909
946
  //#region src/agents/horton.ts
@@ -1123,10 +1160,40 @@ function payloadToTitleText(payload) {
1123
1160
  }
1124
1161
  return String(payload);
1125
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
+ }
1126
1193
  async function extractFirstUserMessage(ctx) {
1127
1194
  const firstMessage = ctx.db.collections.inbox.toArray.filter((message) => message.from !== `system`).sort((left, right) => messageSeq(left) - messageSeq(right))[0];
1128
1195
  if (!firstMessage) return null;
1129
- const text = payloadToTitleText(firstMessage.payload);
1196
+ const text = messageTitleText(ctx, firstMessage);
1130
1197
  return text.length > 0 ? text : null;
1131
1198
  }
1132
1199
  function messageSeq(message) {
@@ -1166,7 +1233,7 @@ function createAssistantHandler(options) {
1166
1233
  ...mcp.tools()
1167
1234
  ];
1168
1235
  const hasEventSourceTools = tools.some((tool) => getToolName(tool) === `list_event_sources`);
1169
- const titlePromise = ctx.firstWake && !ctx.tags.title ? (async () => {
1236
+ const titlePromise = !ctx.tags.title ? (async () => {
1170
1237
  const firstUserMessage = await extractFirstUserMessage(ctx);
1171
1238
  if (!firstUserMessage) return;
1172
1239
  let title = null;
@@ -1287,7 +1354,7 @@ function registerHorton(registry, options) {
1287
1354
  model: z.enum(modelChoiceValues(modelCatalog)).default(modelCatalog.defaultChoice.value),
1288
1355
  reasoningEffort: z.enum(REASONING_EFFORT_VALUES).default(`auto`).describe(`Reasoning effort for compatible reasoning models. Auto uses a safe provider default.`),
1289
1356
  workingDirectory: z.string().optional().describe(`Working directory for file operations. Defaults to the server's configured cwd.`)
1290
- });
1357
+ }).meta({ $defs: modelInputSchemaDefs(modelCatalog) });
1291
1358
  registry.define(`horton`, {
1292
1359
  description: `Friendly capable assistant — chat, code, research, dispatch`,
1293
1360
  creationSchema: hortonCreationSchema,
@@ -1508,10 +1575,13 @@ function createBuiltinElectricTools(custom) {
1508
1575
  };
1509
1576
  }
1510
1577
  async function createBuiltinAgentHandler(options) {
1511
- const { agentServerUrl, serveEndpoint, workingDirectory, streamFn, createElectricTools, publicUrl, runtimeName, baseSkillsDir: baseSkillsDirOverride, serverHeaders, defaultDispatchPolicyForType } = options;
1512
- 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
+ });
1513
1583
  if (!modelCatalog) {
1514
- 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`);
1515
1585
  return null;
1516
1586
  }
1517
1587
  const cwd = workingDirectory ?? process.cwd();
@@ -1715,9 +1785,10 @@ var BuiltinAgentsServer = class {
1715
1785
  publicUrl,
1716
1786
  runtimeName: `builtin-agents`,
1717
1787
  baseSkillsDir: this.options.baseSkillsDir,
1788
+ enabledModelValues: this.options.enabledModelValues,
1718
1789
  serverHeaders: pullWake.headers
1719
1790
  });
1720
- 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`);
1721
1792
  await registerBuiltinAgentTypes(this.bootstrap);
1722
1793
  const registeredRunner = pullWake.registerRunner ? await this.registerPullWakeRunner(pullWake) : null;
1723
1794
  this.pullWakeRunner = createPullWakeRunner({
@@ -1878,4 +1949,4 @@ async function runBuiltinAgentsEntrypoint({ env = process.env, cwd = process.cwd
1878
1949
  }
1879
1950
 
1880
1951
  //#endregion
1881
- export { BuiltinAgentsServer, DEFAULT_BUILTIN_AGENT_HANDLER_PATH, HORTON_MODEL, WORKER_TOOL_NAMES, braveSearchTool, buildHortonSystemPrompt, createAgentHandler, createBuiltinAgentHandler, createBuiltinElectricTools, createHortonDocsSupport, createHortonTools, createSpawnWorkerTool, generateTitle, registerAgentTypes, registerBuiltinAgentTypes, registerHorton, registerWorker, resolveBuiltinAgentsEntrypointOptions, runBuiltinAgentsEntrypoint };
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.9",
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.5"
52
+ "@electric-ax/agents-runtime": "0.3.6"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@types/better-sqlite3": "^7.6.13",