@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.
- package/dist/entrypoint.js +111 -46
- package/dist/index.cjs +112 -45
- package/dist/index.d.cts +12 -1
- package/dist/index.d.ts +13 -2
- package/dist/index.js +112 -47
- package/package.json +4 -4
- package/skills/quickstart.md +2 -2
package/dist/entrypoint.js
CHANGED
|
@@ -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: `
|
|
656
|
-
label: `Search
|
|
657
|
-
description: `Run a hybrid BM25 plus vector search over the local
|
|
654
|
+
name: `search_electric_agents_docs`,
|
|
655
|
+
label: `Search Electric Agents Docs`,
|
|
656
|
+
description: `Run a hybrid BM25 plus vector search over the local Electric Agents documentation index.`,
|
|
658
657
|
parameters: Type.Object({
|
|
659
658
|
query: Type.String({ description: `The docs question or search query to run.` }),
|
|
660
659
|
limit: Type.Optional(Type.Number({
|
|
@@ -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
|
|
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
|
-
|
|
839
|
-
|
|
840
|
-
|
|
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: `${
|
|
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
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
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
|
|
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-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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({
|
|
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
|
|
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
|
|
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: `
|
|
680
|
-
label: `Search
|
|
681
|
-
description: `Run a hybrid BM25 plus vector search over the local
|
|
678
|
+
name: `search_electric_agents_docs`,
|
|
679
|
+
label: `Search Electric Agents Docs`,
|
|
680
|
+
description: `Run a hybrid BM25 plus vector search over the local Electric Agents documentation index.`,
|
|
682
681
|
parameters: __sinclair_typebox.Type.Object({
|
|
683
682
|
query: __sinclair_typebox.Type.String({ description: `The docs question or search query to run.` }),
|
|
684
683
|
limit: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.Number({
|
|
@@ -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
|
|
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
|
-
|
|
863
|
-
|
|
864
|
-
|
|
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: `${
|
|
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
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
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
|
|
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-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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({
|
|
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
|
|
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
|
|
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: `
|
|
656
|
-
label: `Search
|
|
657
|
-
description: `Run a hybrid BM25 plus vector search over the local
|
|
654
|
+
name: `search_electric_agents_docs`,
|
|
655
|
+
label: `Search Electric Agents Docs`,
|
|
656
|
+
description: `Run a hybrid BM25 plus vector search over the local Electric Agents documentation index.`,
|
|
658
657
|
parameters: Type.Object({
|
|
659
658
|
query: Type.String({ description: `The docs question or search query to run.` }),
|
|
660
659
|
limit: Type.Optional(Type.Number({
|
|
@@ -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
|
|
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
|
-
|
|
839
|
-
|
|
840
|
-
|
|
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: `${
|
|
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
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
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
|
|
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-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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({
|
|
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
|
|
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
|
|
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.
|
|
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": "
|
|
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.
|
|
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.
|
|
61
|
+
"typescript": "^5.9.3",
|
|
62
62
|
"vitest": "^4.1.0"
|
|
63
63
|
},
|
|
64
64
|
"files": [
|
package/skills/quickstart.md
CHANGED
|
@@ -100,7 +100,7 @@ IMPORTANT: Never write files until the user explicitly confirms. "Ask to write"
|
|
|
100
100
|
|
|
101
101
|
- **Continue building** — add an HTTP API route and a React frontend to this app so users can interact with the analyzer from the browser.
|
|
102
102
|
- **Start a new app** — use the `agents-chat-starter` template for a full multi-agent chat app with rooms, agent spawning, and a Slack-style UI. Load the init skill or tell them to type `/init`.
|
|
103
|
-
- **Explore the docs** — read about other coordination patterns (blackboard, pipeline, map-reduce), dive into the API reference, or learn about shared state, context assembly, and other advanced features. Use `
|
|
103
|
+
- **Explore the docs** — read about other coordination patterns (blackboard, pipeline, map-reduce), dive into the API reference, or learn about shared state, context assembly, and other advanced features. Use `search_electric_agents_docs` to look things up.
|
|
104
104
|
|
|
105
105
|
Wait for the user to choose. Only proceed to Step 4 if they want to continue building.
|
|
106
106
|
|
|
@@ -116,7 +116,7 @@ Wait for the user to choose. Only proceed to Step 4 if they want to continue bui
|
|
|
116
116
|
- `server.ts` is at the working directory root. Entity files go in `entities/`.
|
|
117
117
|
- Worker spawn args MUST include `tools` array (at least one tool, e.g. `tools: ["bash"]`).
|
|
118
118
|
- Use `edit` tool for small changes (like updating server.ts). Use `write` for full entity file updates.
|
|
119
|
-
- If the user asks a question about Electric Agents concepts, APIs, or patterns between steps, use the `
|
|
119
|
+
- If the user asks a question about Electric Agents concepts, APIs, or patterns between steps, use the `search_electric_agents_docs` tool to look up the answer in the built-in documentation before guessing or searching the web.
|
|
120
120
|
|
|
121
121
|
---
|
|
122
122
|
|