@electric-ax/agents 0.3.0 → 0.4.1
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 +225 -209
- package/dist/index.cjs +223 -205
- package/dist/index.d.cts +21 -11
- package/dist/index.d.ts +22 -12
- package/dist/index.js +224 -206
- package/docs/index.md +3 -3
- package/docs/reference/built-in-collections.md +1 -1
- package/docs/reference/wake-event.md +4 -4
- package/docs/usage/spawning-and-coordinating.md +1 -1
- package/docs/usage/waking-entities.md +5 -5
- package/docs/usage/writing-handlers.md +1 -1
- package/package.json +5 -5
package/dist/index.cjs
CHANGED
|
@@ -38,15 +38,18 @@ const nanoid = __toESM(require("nanoid"));
|
|
|
38
38
|
const __mariozechner_pi_ai = __toESM(require("@mariozechner/pi-ai"));
|
|
39
39
|
const __electric_ax_agents_runtime_tools = __toESM(require("@electric-ax/agents-runtime/tools"));
|
|
40
40
|
const __electric_ax_agents_mcp = __toESM(require("@electric-ax/agents-mcp"));
|
|
41
|
-
const node_http = __toESM(require("node:http"));
|
|
42
41
|
|
|
43
42
|
//#region src/log.ts
|
|
44
43
|
const LOG_DIR = process.env.ELECTRIC_AGENTS_LOG_DIR ?? node_path.default.resolve(process.cwd(), `logs`);
|
|
45
44
|
node_fs.default.mkdirSync(LOG_DIR, { recursive: true });
|
|
46
45
|
const LOG_FILE = node_path.default.join(LOG_DIR, `builtin-agents-${Date.now()}.jsonl`);
|
|
47
46
|
const LOG_LEVEL = process.env.ELECTRIC_AGENTS_LOG_LEVEL ?? `info`;
|
|
48
|
-
const
|
|
49
|
-
const
|
|
47
|
+
const IS_ELECTRON_MAIN = Boolean(process.versions.electron);
|
|
48
|
+
const USE_PRETTY_LOGS = LOG_LEVEL !== `silent` && !process.env.VITEST && !IS_ELECTRON_MAIN;
|
|
49
|
+
const streams = [{ stream: pino.default.destination({
|
|
50
|
+
dest: LOG_FILE,
|
|
51
|
+
sync: IS_ELECTRON_MAIN
|
|
52
|
+
}) }];
|
|
50
53
|
if (USE_PRETTY_LOGS) streams.push({ stream: pino.default.transport({
|
|
51
54
|
target: `pino-pretty`,
|
|
52
55
|
options: {
|
|
@@ -645,8 +648,8 @@ function createHortonDocsSupport(workingDirectory, opts = {}) {
|
|
|
645
648
|
logPrefix: `[horton-docs]`
|
|
646
649
|
});
|
|
647
650
|
function resolveCurrentQuestion(wake, events, inbox) {
|
|
648
|
-
if (wake.type === `
|
|
649
|
-
const eventQuestion = findLatestQuestion(events.filter((event) => event.type === `
|
|
651
|
+
if (wake.type === `inbox`) {
|
|
652
|
+
const eventQuestion = findLatestQuestion(events.filter((event) => event.type === `inbox`).map((event) => event.value));
|
|
650
653
|
if (eventQuestion) return eventQuestion;
|
|
651
654
|
}
|
|
652
655
|
const wakeQuestion = payloadToText(wake.payload).trim();
|
|
@@ -1447,7 +1450,8 @@ function registerHorton(registry, options) {
|
|
|
1447
1450
|
const { workingDirectory, streamFn, skillsRegistry = null, modelCatalog } = options;
|
|
1448
1451
|
const docsUrl = options.docsUrl ?? process.env.HORTON_DOCS_URL;
|
|
1449
1452
|
if (process.env.BRAVE_SEARCH_API_KEY) serverLog.info(`[horton] Web search: using Brave Search API`);
|
|
1450
|
-
else serverLog.warn(`[horton] BRAVE_SEARCH_API_KEY not set — web search will fall back to Anthropic built-in search
|
|
1453
|
+
else if (process.env.ANTHROPIC_API_KEY) serverLog.warn(`[horton] BRAVE_SEARCH_API_KEY not set — web search will fall back to Anthropic built-in search`);
|
|
1454
|
+
else serverLog.warn(`[horton] BRAVE_SEARCH_API_KEY and ANTHROPIC_API_KEY not set — web search tool will be unavailable`);
|
|
1451
1455
|
const docsSupport = createHortonDocsSupport(workingDirectory);
|
|
1452
1456
|
const docsSearchTool = docsSupport?.createSearchTool();
|
|
1453
1457
|
docsSupport?.ensureReady().catch((error) => {
|
|
@@ -1924,7 +1928,7 @@ function truncate(str, max) {
|
|
|
1924
1928
|
//#region src/bootstrap.ts
|
|
1925
1929
|
const DEFAULT_BUILTIN_AGENT_HANDLER_PATH = `/_electric/builtin-agent-handler`;
|
|
1926
1930
|
async function createBuiltinAgentHandler(options) {
|
|
1927
|
-
const { agentServerUrl, serveEndpoint
|
|
1931
|
+
const { agentServerUrl, serveEndpoint, workingDirectory, streamFn, createElectricTools, publicUrl, runtimeName, baseSkillsDir: baseSkillsDirOverride, serverHeaders, defaultDispatchPolicyForType } = options;
|
|
1928
1932
|
const modelCatalog = await createBuiltinModelCatalog({ allowMockFallback: Boolean(streamFn) });
|
|
1929
1933
|
if (!modelCatalog) {
|
|
1930
1934
|
serverLog.warn(`[builtin-agents] no supported model provider API key found — set ANTHROPIC_API_KEY or OPENAI_API_KEY`);
|
|
@@ -1932,7 +1936,7 @@ async function createBuiltinAgentHandler(options) {
|
|
|
1932
1936
|
}
|
|
1933
1937
|
const cwd = workingDirectory ?? process.cwd();
|
|
1934
1938
|
const here = node_path.default.dirname((0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
|
|
1935
|
-
const baseSkillsDir = node_path.default.resolve(here, `../skills`);
|
|
1939
|
+
const baseSkillsDir = baseSkillsDirOverride ?? node_path.default.resolve(here, `../skills`);
|
|
1936
1940
|
let skillsRegistry = null;
|
|
1937
1941
|
try {
|
|
1938
1942
|
skillsRegistry = await createSkillsRegistry({
|
|
@@ -1962,6 +1966,8 @@ async function createBuiltinAgentHandler(options) {
|
|
|
1962
1966
|
serveEndpoint,
|
|
1963
1967
|
registry,
|
|
1964
1968
|
subscriptionPathForType: (name) => `/${name}/*/main`,
|
|
1969
|
+
defaultDispatchPolicyForType,
|
|
1970
|
+
serverHeaders,
|
|
1965
1971
|
idleTimeout: 5e3,
|
|
1966
1972
|
createElectricTools,
|
|
1967
1973
|
publicUrl,
|
|
@@ -1993,15 +1999,13 @@ const registerAgentTypes = registerBuiltinAgentTypes;
|
|
|
1993
1999
|
//#endregion
|
|
1994
2000
|
//#region src/server.ts
|
|
1995
2001
|
var BuiltinAgentsServer = class {
|
|
1996
|
-
server = null;
|
|
1997
2002
|
bootstrap = null;
|
|
1998
|
-
_url = null;
|
|
1999
|
-
publicBaseUrl = null;
|
|
2000
2003
|
_mcpRegistry = null;
|
|
2001
2004
|
mcpWatcherCloser = null;
|
|
2002
2005
|
mcpToolProviderName = null;
|
|
2003
2006
|
mcpApplyInFlight = new Set();
|
|
2004
2007
|
mcpStopping = false;
|
|
2008
|
+
pullWakeRunner = null;
|
|
2005
2009
|
options;
|
|
2006
2010
|
constructor(options) {
|
|
2007
2011
|
this.options = options;
|
|
@@ -2010,171 +2014,167 @@ var BuiltinAgentsServer = class {
|
|
|
2010
2014
|
get mcpRegistry() {
|
|
2011
2015
|
return this._mcpRegistry;
|
|
2012
2016
|
}
|
|
2013
|
-
get url() {
|
|
2014
|
-
if (!this._url) throw new Error(`Builtin agents server not started`);
|
|
2015
|
-
return this._url;
|
|
2016
|
-
}
|
|
2017
|
-
get registeredBaseUrl() {
|
|
2018
|
-
if (!this.publicBaseUrl) throw new Error(`Builtin agents server not started`);
|
|
2019
|
-
return this.publicBaseUrl;
|
|
2020
|
-
}
|
|
2021
2017
|
async start() {
|
|
2022
|
-
if (this.
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
}
|
|
2031
|
-
});
|
|
2018
|
+
if (this.bootstrap || this.pullWakeRunner) throw new Error(`Builtin agents runtime already started`);
|
|
2019
|
+
const pullWake = this.options.pullWake;
|
|
2020
|
+
if (!pullWake?.runnerId) throw new Error(`Builtin agents require a pull-wake runner id`);
|
|
2021
|
+
try {
|
|
2022
|
+
const publicUrl = this.options.mcpOAuthRedirectBase ?? this.options.agentServerUrl;
|
|
2023
|
+
const mcpRegistry = (0, __electric_ax_agents_mcp.createRegistry)({
|
|
2024
|
+
publicUrl,
|
|
2025
|
+
openAuthorizeUrl: this.options.openAuthorizeUrl
|
|
2032
2026
|
});
|
|
2033
|
-
this.
|
|
2034
|
-
const
|
|
2035
|
-
this.
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
const serveEndpoint = new URL(webhookPath, this.publicBaseUrl.endsWith(`/`) ? this.publicBaseUrl : `${this.publicBaseUrl}/`).toString();
|
|
2046
|
-
const publicUrl = this.options.mcpOAuthRedirectBase ?? this.publicBaseUrl;
|
|
2047
|
-
const mcpRegistry = (0, __electric_ax_agents_mcp.createRegistry)({
|
|
2048
|
-
publicUrl,
|
|
2049
|
-
openAuthorizeUrl: this.options.openAuthorizeUrl
|
|
2050
|
-
});
|
|
2051
|
-
this._mcpRegistry = mcpRegistry;
|
|
2052
|
-
const mcpConfigPath = this.options.loadProjectMcpConfig ? node_path.default.resolve(this.options.workingDirectory ?? process.cwd(), `mcp.json`) : null;
|
|
2053
|
-
const extras = this.options.extraMcpServers ?? [];
|
|
2054
|
-
const wirePersistence = async (cfg) => {
|
|
2055
|
-
const servers = [];
|
|
2056
|
-
for (const s of cfg.servers) if (s.transport === `http` && s.auth?.mode === `authorizationCode`) {
|
|
2057
|
-
const persist = await (0, __electric_ax_agents_mcp.keychainPersistence)({ server: s.name });
|
|
2058
|
-
servers.push({
|
|
2059
|
-
...s,
|
|
2060
|
-
auth: {
|
|
2061
|
-
...s.auth,
|
|
2062
|
-
...persist
|
|
2063
|
-
}
|
|
2064
|
-
});
|
|
2065
|
-
} else servers.push(s);
|
|
2066
|
-
return {
|
|
2067
|
-
...cfg,
|
|
2068
|
-
servers
|
|
2069
|
-
};
|
|
2070
|
-
};
|
|
2071
|
-
const merge = (jsonCfg) => {
|
|
2072
|
-
const jsonServers = jsonCfg?.servers ?? [];
|
|
2073
|
-
const jsonNames = new Set(jsonServers.map((s) => s.name));
|
|
2074
|
-
const filteredExtras = extras.filter((s) => !jsonNames.has(s.name));
|
|
2075
|
-
return {
|
|
2076
|
-
servers: [...filteredExtras, ...jsonServers],
|
|
2077
|
-
raw: jsonCfg?.raw
|
|
2078
|
-
};
|
|
2079
|
-
};
|
|
2080
|
-
const onConfigError = this.options.onConfigError;
|
|
2081
|
-
const runApply = async (jsonCfg) => {
|
|
2082
|
-
if (this.mcpStopping) return;
|
|
2083
|
-
try {
|
|
2084
|
-
const wired = await wirePersistence(merge(jsonCfg));
|
|
2085
|
-
if (this.mcpStopping) return;
|
|
2086
|
-
await mcpRegistry.applyConfig(wired);
|
|
2087
|
-
} catch (e) {
|
|
2088
|
-
serverLog.error(`[mcp] applyConfig:`, e);
|
|
2089
|
-
try {
|
|
2090
|
-
onConfigError?.(e);
|
|
2091
|
-
} catch (cbErr) {
|
|
2092
|
-
serverLog.error(`[mcp] onConfigError callback failed:`, cbErr);
|
|
2093
|
-
}
|
|
2094
|
-
}
|
|
2095
|
-
};
|
|
2096
|
-
const applyMerged = (jsonCfg) => {
|
|
2097
|
-
const p = runApply(jsonCfg);
|
|
2098
|
-
this.mcpApplyInFlight.add(p);
|
|
2099
|
-
p.finally(() => this.mcpApplyInFlight.delete(p));
|
|
2100
|
-
return p;
|
|
2101
|
-
};
|
|
2102
|
-
if (mcpConfigPath) {
|
|
2103
|
-
try {
|
|
2104
|
-
const cfg = await (0, __electric_ax_agents_mcp.loadConfig)(mcpConfigPath, process.env);
|
|
2105
|
-
applyMerged(cfg);
|
|
2106
|
-
} catch (err) {
|
|
2107
|
-
if (err.code !== `ENOENT`) throw err;
|
|
2108
|
-
if (extras.length === 0) serverLog.info(`[mcp] no ${mcpConfigPath} — starting with no servers`);
|
|
2109
|
-
else serverLog.info(`[mcp] no ${mcpConfigPath} — starting with ${extras.length} server(s) from extras`);
|
|
2110
|
-
applyMerged(null);
|
|
2111
|
-
}
|
|
2112
|
-
try {
|
|
2113
|
-
this.mcpWatcherCloser = await (0, __electric_ax_agents_mcp.watchConfig)(mcpConfigPath, {
|
|
2114
|
-
onChange: (cfg) => void applyMerged(cfg),
|
|
2115
|
-
onError: (e) => serverLog.error(`[mcp] config error:`, e)
|
|
2116
|
-
});
|
|
2117
|
-
} catch (e) {
|
|
2118
|
-
serverLog.error(`[mcp] config watcher failed to start:`, e);
|
|
2119
|
-
}
|
|
2120
|
-
} else {
|
|
2121
|
-
if (extras.length > 0) serverLog.info(`[mcp] starting with ${extras.length} server(s) from extras`);
|
|
2122
|
-
applyMerged(null);
|
|
2123
|
-
}
|
|
2124
|
-
this.mcpToolProviderName = `mcp`;
|
|
2125
|
-
(0, __electric_ax_agents_runtime.registerToolProvider)({
|
|
2126
|
-
name: `mcp`,
|
|
2127
|
-
tools: () => {
|
|
2128
|
-
const tools = [];
|
|
2129
|
-
for (const entry of mcpRegistry.list()) {
|
|
2130
|
-
if (entry.status !== `ready`) continue;
|
|
2131
|
-
const live = mcpRegistry.get(entry.name);
|
|
2132
|
-
if (!live?.transport) continue;
|
|
2133
|
-
for (const t of entry.tools) tools.push((0, __electric_ax_agents_mcp.bridgeMcpTool)({
|
|
2134
|
-
server: entry.name,
|
|
2135
|
-
tool: t,
|
|
2136
|
-
client: live.transport.client,
|
|
2137
|
-
timeoutMs: live.config.timeoutMs
|
|
2138
|
-
}));
|
|
2139
|
-
const caps = live.transport.client.getServerCapabilities?.();
|
|
2140
|
-
if (caps?.resources) tools.push(...(0, __electric_ax_agents_mcp.buildResourceTools)({
|
|
2141
|
-
server: entry.name,
|
|
2142
|
-
client: live.transport.client,
|
|
2143
|
-
timeoutMs: live.config.timeoutMs
|
|
2144
|
-
}));
|
|
2145
|
-
if (caps?.prompts) tools.push(...(0, __electric_ax_agents_mcp.buildPromptTools)({
|
|
2146
|
-
server: entry.name,
|
|
2147
|
-
client: live.transport.client,
|
|
2148
|
-
timeoutMs: live.config.timeoutMs
|
|
2149
|
-
}));
|
|
2150
|
-
}
|
|
2151
|
-
return tools;
|
|
2027
|
+
this._mcpRegistry = mcpRegistry;
|
|
2028
|
+
const mcpConfigPath = this.options.loadProjectMcpConfig ? node_path.default.resolve(this.options.workingDirectory ?? process.cwd(), `mcp.json`) : null;
|
|
2029
|
+
const extras = this.options.extraMcpServers ?? [];
|
|
2030
|
+
const wirePersistence = async (cfg) => {
|
|
2031
|
+
const servers = [];
|
|
2032
|
+
for (const s of cfg.servers) if (s.transport === `http` && s.auth?.mode === `authorizationCode`) {
|
|
2033
|
+
const persist = await (0, __electric_ax_agents_mcp.keychainPersistence)({ server: s.name });
|
|
2034
|
+
servers.push({
|
|
2035
|
+
...s,
|
|
2036
|
+
auth: {
|
|
2037
|
+
...s.auth,
|
|
2038
|
+
...persist
|
|
2152
2039
|
}
|
|
2153
2040
|
});
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2041
|
+
} else servers.push(s);
|
|
2042
|
+
return {
|
|
2043
|
+
...cfg,
|
|
2044
|
+
servers
|
|
2045
|
+
};
|
|
2046
|
+
};
|
|
2047
|
+
const merge = (jsonCfg) => {
|
|
2048
|
+
const jsonServers = jsonCfg?.servers ?? [];
|
|
2049
|
+
const jsonNames = new Set(jsonServers.map((s) => s.name));
|
|
2050
|
+
const filteredExtras = extras.filter((s) => !jsonNames.has(s.name));
|
|
2051
|
+
return {
|
|
2052
|
+
servers: [...filteredExtras, ...jsonServers],
|
|
2053
|
+
raw: jsonCfg?.raw
|
|
2054
|
+
};
|
|
2055
|
+
};
|
|
2056
|
+
const onConfigError = this.options.onConfigError;
|
|
2057
|
+
const runApply = async (jsonCfg) => {
|
|
2058
|
+
if (this.mcpStopping) return;
|
|
2059
|
+
try {
|
|
2060
|
+
const wired = await wirePersistence(merge(jsonCfg));
|
|
2061
|
+
if (this.mcpStopping) return;
|
|
2062
|
+
await mcpRegistry.applyConfig(wired);
|
|
2063
|
+
} catch (e) {
|
|
2064
|
+
serverLog.error(`[mcp] applyConfig:`, e);
|
|
2065
|
+
try {
|
|
2066
|
+
onConfigError?.(e);
|
|
2067
|
+
} catch (cbErr) {
|
|
2068
|
+
serverLog.error(`[mcp] onConfigError callback failed:`, cbErr);
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
};
|
|
2072
|
+
const applyMerged = (jsonCfg) => {
|
|
2073
|
+
const p = runApply(jsonCfg);
|
|
2074
|
+
this.mcpApplyInFlight.add(p);
|
|
2075
|
+
p.finally(() => this.mcpApplyInFlight.delete(p));
|
|
2076
|
+
return p;
|
|
2077
|
+
};
|
|
2078
|
+
if (mcpConfigPath) {
|
|
2079
|
+
try {
|
|
2080
|
+
const cfg = await (0, __electric_ax_agents_mcp.loadConfig)(mcpConfigPath, process.env);
|
|
2081
|
+
applyMerged(cfg);
|
|
2082
|
+
} catch (err) {
|
|
2083
|
+
if (err.code !== `ENOENT`) throw err;
|
|
2084
|
+
if (extras.length === 0) serverLog.info(`[mcp] no ${mcpConfigPath} — starting with no servers`);
|
|
2085
|
+
else serverLog.info(`[mcp] no ${mcpConfigPath} — starting with ${extras.length} server(s) from extras`);
|
|
2086
|
+
applyMerged(null);
|
|
2087
|
+
}
|
|
2088
|
+
try {
|
|
2089
|
+
this.mcpWatcherCloser = await (0, __electric_ax_agents_mcp.watchConfig)(mcpConfigPath, {
|
|
2090
|
+
onChange: (cfg) => void applyMerged(cfg),
|
|
2091
|
+
onError: (e) => serverLog.error(`[mcp] config error:`, e)
|
|
2162
2092
|
});
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2093
|
+
} catch (e) {
|
|
2094
|
+
serverLog.error(`[mcp] config watcher failed to start:`, e);
|
|
2095
|
+
}
|
|
2096
|
+
} else {
|
|
2097
|
+
if (extras.length > 0) serverLog.info(`[mcp] starting with ${extras.length} server(s) from extras`);
|
|
2098
|
+
applyMerged(null);
|
|
2099
|
+
}
|
|
2100
|
+
this.mcpToolProviderName = `mcp`;
|
|
2101
|
+
(0, __electric_ax_agents_runtime.registerToolProvider)({
|
|
2102
|
+
name: `mcp`,
|
|
2103
|
+
tools: () => {
|
|
2104
|
+
const tools = [];
|
|
2105
|
+
for (const entry of mcpRegistry.list()) {
|
|
2106
|
+
if (entry.status !== `ready`) continue;
|
|
2107
|
+
const live = mcpRegistry.get(entry.name);
|
|
2108
|
+
if (!live?.transport) continue;
|
|
2109
|
+
for (const t of entry.tools) tools.push((0, __electric_ax_agents_mcp.bridgeMcpTool)({
|
|
2110
|
+
server: entry.name,
|
|
2111
|
+
tool: t,
|
|
2112
|
+
client: live.transport.client,
|
|
2113
|
+
timeoutMs: live.config.timeoutMs
|
|
2114
|
+
}));
|
|
2115
|
+
const caps = live.transport.client.getServerCapabilities?.();
|
|
2116
|
+
if (caps?.resources) tools.push(...(0, __electric_ax_agents_mcp.buildResourceTools)({
|
|
2117
|
+
server: entry.name,
|
|
2118
|
+
client: live.transport.client,
|
|
2119
|
+
timeoutMs: live.config.timeoutMs
|
|
2120
|
+
}));
|
|
2121
|
+
if (caps?.prompts) tools.push(...(0, __electric_ax_agents_mcp.buildPromptTools)({
|
|
2122
|
+
server: entry.name,
|
|
2123
|
+
client: live.transport.client,
|
|
2124
|
+
timeoutMs: live.config.timeoutMs
|
|
2125
|
+
}));
|
|
2126
|
+
}
|
|
2127
|
+
return tools;
|
|
2170
2128
|
}
|
|
2171
2129
|
});
|
|
2172
|
-
|
|
2130
|
+
this.bootstrap = await createBuiltinAgentHandler({
|
|
2131
|
+
agentServerUrl: this.options.agentServerUrl,
|
|
2132
|
+
workingDirectory: this.options.workingDirectory,
|
|
2133
|
+
streamFn: this.options.mockStreamFn,
|
|
2134
|
+
createElectricTools: this.options.createElectricTools,
|
|
2135
|
+
publicUrl,
|
|
2136
|
+
runtimeName: `builtin-agents`,
|
|
2137
|
+
baseSkillsDir: this.options.baseSkillsDir,
|
|
2138
|
+
serverHeaders: pullWake.headers
|
|
2139
|
+
});
|
|
2140
|
+
if (!this.bootstrap) throw new Error(`ANTHROPIC_API_KEY or OPENAI_API_KEY must be set before starting builtin agents`);
|
|
2141
|
+
await registerBuiltinAgentTypes(this.bootstrap);
|
|
2142
|
+
const registeredRunner = pullWake.registerRunner ? await this.registerPullWakeRunner(pullWake) : null;
|
|
2143
|
+
this.pullWakeRunner = (0, __electric_ax_agents_runtime.createPullWakeRunner)({
|
|
2144
|
+
baseUrl: this.options.agentServerUrl,
|
|
2145
|
+
runnerId: pullWake.runnerId,
|
|
2146
|
+
runtime: this.bootstrap.runtime,
|
|
2147
|
+
headers: pullWake.headers,
|
|
2148
|
+
claimHeaders: pullWake.claimHeaders,
|
|
2149
|
+
claimTokenHeader: pullWake.claimTokenHeader,
|
|
2150
|
+
heartbeatIntervalMs: pullWake.heartbeatIntervalMs,
|
|
2151
|
+
leaseMs: pullWake.leaseMs,
|
|
2152
|
+
offset: registeredRunner?.wake_stream_offset,
|
|
2153
|
+
onError: (error) => {
|
|
2154
|
+
serverLog.error(`[builtin-agents] pull-wake runner failed`, error);
|
|
2155
|
+
return true;
|
|
2156
|
+
}
|
|
2157
|
+
});
|
|
2158
|
+
this.pullWakeRunner.start();
|
|
2159
|
+
serverLog.info(`[builtin-agents] pull-wake runner started: ${pullWake.runnerId}`);
|
|
2160
|
+
return `pull-wake:${pullWake.runnerId}`;
|
|
2161
|
+
} catch (error) {
|
|
2162
|
+
await this.stop().catch(() => {});
|
|
2163
|
+
throw error;
|
|
2164
|
+
}
|
|
2173
2165
|
}
|
|
2174
2166
|
async stop() {
|
|
2167
|
+
if (this.pullWakeRunner) {
|
|
2168
|
+
await this.pullWakeRunner.stop().catch((e) => {
|
|
2169
|
+
serverLog.error(`[builtin-agents] pull-wake runner stop failed`, e);
|
|
2170
|
+
});
|
|
2171
|
+
this.pullWakeRunner = null;
|
|
2172
|
+
}
|
|
2175
2173
|
if (this.bootstrap) {
|
|
2176
2174
|
this.bootstrap.runtime.abortWakes();
|
|
2177
|
-
await Promise.race([this.bootstrap.runtime.drainWakes().catch(() => {
|
|
2175
|
+
await Promise.race([this.bootstrap.runtime.drainWakes().catch((err) => {
|
|
2176
|
+
serverLog.error(`[builtin-agents] drainWakes failed during shutdown:`, err);
|
|
2177
|
+
}), new Promise((resolve) => setTimeout(resolve, 5e3))]);
|
|
2178
2178
|
this.bootstrap = null;
|
|
2179
2179
|
}
|
|
2180
2180
|
this.mcpStopping = true;
|
|
@@ -2197,39 +2197,29 @@ var BuiltinAgentsServer = class {
|
|
|
2197
2197
|
});
|
|
2198
2198
|
this._mcpRegistry = null;
|
|
2199
2199
|
}
|
|
2200
|
-
if (this.server) {
|
|
2201
|
-
const server = this.server;
|
|
2202
|
-
await new Promise((resolve) => {
|
|
2203
|
-
server.close(() => resolve());
|
|
2204
|
-
});
|
|
2205
|
-
this.server = null;
|
|
2206
|
-
}
|
|
2207
2200
|
this.mcpStopping = false;
|
|
2208
|
-
this._url = null;
|
|
2209
|
-
this.publicBaseUrl = null;
|
|
2210
2201
|
}
|
|
2211
|
-
async
|
|
2212
|
-
const
|
|
2213
|
-
|
|
2214
|
-
const
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2202
|
+
async registerPullWakeRunner(pullWake) {
|
|
2203
|
+
const headers = new Headers(typeof pullWake.headers === `function` ? await pullWake.headers() : pullWake.headers);
|
|
2204
|
+
headers.set(`content-type`, `application/json`);
|
|
2205
|
+
const response = await fetch((0, __electric_ax_agents_runtime.appendPathToUrl)(this.options.agentServerUrl, `/_electric/runners`), {
|
|
2206
|
+
method: `POST`,
|
|
2207
|
+
headers,
|
|
2208
|
+
body: JSON.stringify({
|
|
2209
|
+
id: pullWake.runnerId,
|
|
2210
|
+
owner_user_id: pullWake.ownerUserId,
|
|
2211
|
+
label: pullWake.label ?? `Built-in agents`,
|
|
2212
|
+
kind: `local`,
|
|
2213
|
+
admin_status: `enabled`
|
|
2214
|
+
})
|
|
2215
|
+
});
|
|
2216
|
+
if (!response.ok) throw new Error(`Failed to register pull-wake runner ${pullWake.runnerId}: ${response.status} ${await response.text()}`);
|
|
2217
|
+
return await response.json();
|
|
2226
2218
|
}
|
|
2227
2219
|
};
|
|
2228
2220
|
|
|
2229
2221
|
//#endregion
|
|
2230
2222
|
//#region src/entrypoint-lib.ts
|
|
2231
|
-
const DEFAULT_HOST = `127.0.0.1`;
|
|
2232
|
-
const DEFAULT_PORT = 4448;
|
|
2233
2223
|
function readEnv(env, names) {
|
|
2234
2224
|
for (const name of names) {
|
|
2235
2225
|
const value = env[name]?.trim();
|
|
@@ -2242,13 +2232,6 @@ function readRequiredEnv(env, names, description) {
|
|
|
2242
2232
|
if (value) return value;
|
|
2243
2233
|
throw new Error(`Missing ${description}. Set one of: ${names.map((name) => `"${name}"`).join(`, `)}`);
|
|
2244
2234
|
}
|
|
2245
|
-
function readPort(env) {
|
|
2246
|
-
const raw = readEnv(env, [`ELECTRIC_AGENTS_BUILTIN_PORT`, `PORT`]);
|
|
2247
|
-
if (!raw) return DEFAULT_PORT;
|
|
2248
|
-
const port = Number(raw);
|
|
2249
|
-
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error(`Invalid builtin agents port "${raw}". Expected an integer between 1 and 65535.`);
|
|
2250
|
-
return port;
|
|
2251
|
-
}
|
|
2252
2235
|
function validateUrl(name, value) {
|
|
2253
2236
|
try {
|
|
2254
2237
|
new URL(value);
|
|
@@ -2257,20 +2240,55 @@ function validateUrl(name, value) {
|
|
|
2257
2240
|
throw new Error(`Invalid ${name}: "${value}"`);
|
|
2258
2241
|
}
|
|
2259
2242
|
}
|
|
2243
|
+
function parseAdditionalServerHeaders(env) {
|
|
2244
|
+
const raw = readEnv(env, [`ELECTRIC_AGENTS_SERVER_HEADERS`]);
|
|
2245
|
+
if (!raw) return void 0;
|
|
2246
|
+
let parsed;
|
|
2247
|
+
try {
|
|
2248
|
+
parsed = JSON.parse(raw);
|
|
2249
|
+
} catch {
|
|
2250
|
+
throw new Error(`Invalid ELECTRIC_AGENTS_SERVER_HEADERS: expected JSON`);
|
|
2251
|
+
}
|
|
2252
|
+
if (!parsed || typeof parsed !== `object` || Array.isArray(parsed)) throw new Error(`Invalid ELECTRIC_AGENTS_SERVER_HEADERS: expected a JSON object`);
|
|
2253
|
+
const headers = new Headers();
|
|
2254
|
+
for (const [name, value] of Object.entries(parsed)) {
|
|
2255
|
+
if (typeof value !== `string`) throw new Error(`Invalid ELECTRIC_AGENTS_SERVER_HEADERS: header "${name}" must be a string`);
|
|
2256
|
+
headers.set(name, value);
|
|
2257
|
+
}
|
|
2258
|
+
const normalized = Object.fromEntries(headers.entries());
|
|
2259
|
+
return Object.keys(normalized).length > 0 ? normalized : void 0;
|
|
2260
|
+
}
|
|
2261
|
+
function mergeHeaders(...sources) {
|
|
2262
|
+
const headers = new Headers();
|
|
2263
|
+
for (const source of sources) {
|
|
2264
|
+
if (!source) continue;
|
|
2265
|
+
new Headers(source).forEach((value, key) => headers.set(key, value));
|
|
2266
|
+
}
|
|
2267
|
+
const merged = Object.fromEntries(headers.entries());
|
|
2268
|
+
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
2269
|
+
}
|
|
2270
|
+
function hasHeader(headers, name) {
|
|
2271
|
+
return headers ? new Headers(headers).has(name) : false;
|
|
2272
|
+
}
|
|
2260
2273
|
function resolveBuiltinAgentsEntrypointOptions(env = process.env, cwd = process.cwd()) {
|
|
2261
2274
|
const agentServerUrl = validateUrl(`agent server URL`, readRequiredEnv(env, [`ELECTRIC_AGENTS_SERVER_URL`, `ELECTRIC_AGENTS_BASE_URL`], `agent server base URL`));
|
|
2262
|
-
const
|
|
2275
|
+
const runnerId = readRequiredEnv(env, [`ELECTRIC_AGENTS_PULL_WAKE_RUNNER_ID`, `PULL_WAKE_RUNNER_ID`], `pull-wake runner id`);
|
|
2276
|
+
const serverHeaders = mergeHeaders(parseAdditionalServerHeaders(env));
|
|
2263
2277
|
return {
|
|
2264
2278
|
agentServerUrl,
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2279
|
+
workingDirectory: readEnv(env, [`ELECTRIC_AGENTS_WORKING_DIRECTORY`, `WORKING_DIRECTORY`]) ?? cwd,
|
|
2280
|
+
pullWake: {
|
|
2281
|
+
runnerId,
|
|
2282
|
+
registerRunner: readEnv(env, [`ELECTRIC_AGENTS_REGISTER_PULL_WAKE_RUNNER`]) === `true` || readEnv(env, [`ELECTRIC_AGENTS_REGISTER_PULL_WAKE_RUNNER`]) === `1`,
|
|
2283
|
+
headers: serverHeaders,
|
|
2284
|
+
claimHeaders: serverHeaders,
|
|
2285
|
+
claimTokenHeader: hasHeader(serverHeaders, `authorization`) ? `electric-claim-token` : void 0
|
|
2286
|
+
}
|
|
2269
2287
|
};
|
|
2270
2288
|
}
|
|
2271
|
-
async function runBuiltinAgentsEntrypoint({ env = process.env, cwd = process.cwd(), createServer
|
|
2289
|
+
async function runBuiltinAgentsEntrypoint({ env = process.env, cwd = process.cwd(), createServer = (options$1) => new BuiltinAgentsServer(options$1) } = {}) {
|
|
2272
2290
|
const options = resolveBuiltinAgentsEntrypointOptions(env, cwd);
|
|
2273
|
-
const server = createServer
|
|
2291
|
+
const server = createServer(options);
|
|
2274
2292
|
const url = await server.start();
|
|
2275
2293
|
return {
|
|
2276
2294
|
options,
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AgentConfig, AgentTool, AvailableProvider, EntityRegistry, EntityStreamDBWithActions, HandlerContext, RuntimeHandler, WakeEvent } from "@electric-ax/agents-runtime";
|
|
1
|
+
import { AgentConfig, AgentTool, AvailableProvider, DispatchPolicy, EntityRegistry, EntityStreamDBWithActions, HandlerContext, HeadersProvider, PullWakeRunnerConfig, RuntimeHandler, WakeEvent } from "@electric-ax/agents-runtime";
|
|
2
2
|
import { ChangeEvent } from "@durable-streams/state";
|
|
3
3
|
import { AgentTool as AgentTool$1, StreamFn } from "@mariozechner/pi-agent-core";
|
|
4
4
|
import { IncomingMessage, ServerResponse } from "node:http";
|
|
@@ -43,6 +43,10 @@ interface BuiltinAgentHandlerOptions {
|
|
|
43
43
|
streamFn?: StreamFn;
|
|
44
44
|
publicUrl?: string;
|
|
45
45
|
runtimeName?: string;
|
|
46
|
+
/** Override for the built-in skills directory; required when embedders bundle this package. */
|
|
47
|
+
baseSkillsDir?: string;
|
|
48
|
+
serverHeaders?: HeadersProvider;
|
|
49
|
+
defaultDispatchPolicyForType?: (typeName: string) => DispatchPolicy | undefined;
|
|
46
50
|
createElectricTools?: (context: {
|
|
47
51
|
entityUrl: string;
|
|
48
52
|
entityType: string;
|
|
@@ -85,12 +89,20 @@ declare const registerAgentTypes: typeof registerBuiltinAgentTypes;
|
|
|
85
89
|
//#region src/server.d.ts
|
|
86
90
|
interface BuiltinAgentsServerOptions {
|
|
87
91
|
agentServerUrl: string;
|
|
88
|
-
baseUrl?: string;
|
|
89
|
-
port: number;
|
|
90
|
-
host?: string;
|
|
91
92
|
workingDirectory?: string;
|
|
92
93
|
mockStreamFn?: StreamFn;
|
|
93
|
-
|
|
94
|
+
/** Pull-wake runner configuration for built-in agents. */
|
|
95
|
+
pullWake: {
|
|
96
|
+
runnerId: string;
|
|
97
|
+
ownerUserId?: string;
|
|
98
|
+
label?: string;
|
|
99
|
+
registerRunner?: boolean;
|
|
100
|
+
headers?: PullWakeRunnerConfig[`headers`];
|
|
101
|
+
claimHeaders?: PullWakeRunnerConfig[`claimHeaders`];
|
|
102
|
+
claimTokenHeader?: PullWakeRunnerConfig[`claimTokenHeader`];
|
|
103
|
+
heartbeatIntervalMs?: PullWakeRunnerConfig[`heartbeatIntervalMs`];
|
|
104
|
+
leaseMs?: PullWakeRunnerConfig[`leaseMs`];
|
|
105
|
+
};
|
|
94
106
|
/** Invoked when an `authorizationCode` server needs user consent. */
|
|
95
107
|
openAuthorizeUrl?: (url: string, server: string) => void;
|
|
96
108
|
/**
|
|
@@ -115,6 +127,8 @@ interface BuiltinAgentsServerOptions {
|
|
|
115
127
|
* so the embedder must opt in.
|
|
116
128
|
*/
|
|
117
129
|
loadProjectMcpConfig?: boolean;
|
|
130
|
+
/** Override for the built-in skills directory; required when embedders bundle this package. */
|
|
131
|
+
baseSkillsDir?: string;
|
|
118
132
|
createElectricTools?: (context: {
|
|
119
133
|
entityUrl: string;
|
|
120
134
|
entityType: string;
|
|
@@ -149,24 +163,20 @@ interface BuiltinAgentsServerOptions {
|
|
|
149
163
|
}) => Array<AgentTool> | Promise<Array<AgentTool>>;
|
|
150
164
|
}
|
|
151
165
|
declare class BuiltinAgentsServer {
|
|
152
|
-
private server;
|
|
153
166
|
private bootstrap;
|
|
154
|
-
private _url;
|
|
155
|
-
private publicBaseUrl;
|
|
156
167
|
private _mcpRegistry;
|
|
157
168
|
private mcpWatcherCloser;
|
|
158
169
|
private mcpToolProviderName;
|
|
159
170
|
private mcpApplyInFlight;
|
|
160
171
|
private mcpStopping;
|
|
172
|
+
private pullWakeRunner;
|
|
161
173
|
readonly options: BuiltinAgentsServerOptions;
|
|
162
174
|
constructor(options: BuiltinAgentsServerOptions);
|
|
163
175
|
/** Embedded MCP registry. `null` until `start()` has run. */
|
|
164
176
|
get mcpRegistry(): Registry | null;
|
|
165
|
-
get url(): string;
|
|
166
|
-
get registeredBaseUrl(): string;
|
|
167
177
|
start(): Promise<string>;
|
|
168
178
|
stop(): Promise<void>;
|
|
169
|
-
private
|
|
179
|
+
private registerPullWakeRunner;
|
|
170
180
|
}
|
|
171
181
|
|
|
172
182
|
//#endregion
|