@electric-ax/agents 0.3.0 → 0.4.0
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 +227 -205
- package/dist/index.cjs +225 -201
- package/dist/index.d.cts +17 -11
- package/dist/index.d.ts +18 -12
- package/dist/index.js +226 -202
- package/package.json +4 -4
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: {
|
|
@@ -1924,7 +1927,7 @@ function truncate(str, max) {
|
|
|
1924
1927
|
//#region src/bootstrap.ts
|
|
1925
1928
|
const DEFAULT_BUILTIN_AGENT_HANDLER_PATH = `/_electric/builtin-agent-handler`;
|
|
1926
1929
|
async function createBuiltinAgentHandler(options) {
|
|
1927
|
-
const { agentServerUrl, serveEndpoint
|
|
1930
|
+
const { agentServerUrl, serveEndpoint, workingDirectory, streamFn, createElectricTools, publicUrl, runtimeName, serverHeaders, defaultDispatchPolicyForType } = options;
|
|
1928
1931
|
const modelCatalog = await createBuiltinModelCatalog({ allowMockFallback: Boolean(streamFn) });
|
|
1929
1932
|
if (!modelCatalog) {
|
|
1930
1933
|
serverLog.warn(`[builtin-agents] no supported model provider API key found — set ANTHROPIC_API_KEY or OPENAI_API_KEY`);
|
|
@@ -1962,6 +1965,8 @@ async function createBuiltinAgentHandler(options) {
|
|
|
1962
1965
|
serveEndpoint,
|
|
1963
1966
|
registry,
|
|
1964
1967
|
subscriptionPathForType: (name) => `/${name}/*/main`,
|
|
1968
|
+
defaultDispatchPolicyForType,
|
|
1969
|
+
serverHeaders,
|
|
1965
1970
|
idleTimeout: 5e3,
|
|
1966
1971
|
createElectricTools,
|
|
1967
1972
|
publicUrl,
|
|
@@ -1993,15 +1998,13 @@ const registerAgentTypes = registerBuiltinAgentTypes;
|
|
|
1993
1998
|
//#endregion
|
|
1994
1999
|
//#region src/server.ts
|
|
1995
2000
|
var BuiltinAgentsServer = class {
|
|
1996
|
-
server = null;
|
|
1997
2001
|
bootstrap = null;
|
|
1998
|
-
_url = null;
|
|
1999
|
-
publicBaseUrl = null;
|
|
2000
2002
|
_mcpRegistry = null;
|
|
2001
2003
|
mcpWatcherCloser = null;
|
|
2002
2004
|
mcpToolProviderName = null;
|
|
2003
2005
|
mcpApplyInFlight = new Set();
|
|
2004
2006
|
mcpStopping = false;
|
|
2007
|
+
pullWakeRunner = null;
|
|
2005
2008
|
options;
|
|
2006
2009
|
constructor(options) {
|
|
2007
2010
|
this.options = options;
|
|
@@ -2010,171 +2013,166 @@ var BuiltinAgentsServer = class {
|
|
|
2010
2013
|
get mcpRegistry() {
|
|
2011
2014
|
return this._mcpRegistry;
|
|
2012
2015
|
}
|
|
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
2016
|
async start() {
|
|
2022
|
-
if (this.
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
}
|
|
2031
|
-
});
|
|
2017
|
+
if (this.bootstrap || this.pullWakeRunner) throw new Error(`Builtin agents runtime already started`);
|
|
2018
|
+
const pullWake = this.options.pullWake;
|
|
2019
|
+
if (!pullWake?.runnerId) throw new Error(`Builtin agents require a pull-wake runner id`);
|
|
2020
|
+
try {
|
|
2021
|
+
const publicUrl = this.options.mcpOAuthRedirectBase ?? this.options.agentServerUrl;
|
|
2022
|
+
const mcpRegistry = (0, __electric_ax_agents_mcp.createRegistry)({
|
|
2023
|
+
publicUrl,
|
|
2024
|
+
openAuthorizeUrl: this.options.openAuthorizeUrl
|
|
2032
2025
|
});
|
|
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;
|
|
2026
|
+
this._mcpRegistry = mcpRegistry;
|
|
2027
|
+
const mcpConfigPath = this.options.loadProjectMcpConfig ? node_path.default.resolve(this.options.workingDirectory ?? process.cwd(), `mcp.json`) : null;
|
|
2028
|
+
const extras = this.options.extraMcpServers ?? [];
|
|
2029
|
+
const wirePersistence = async (cfg) => {
|
|
2030
|
+
const servers = [];
|
|
2031
|
+
for (const s of cfg.servers) if (s.transport === `http` && s.auth?.mode === `authorizationCode`) {
|
|
2032
|
+
const persist = await (0, __electric_ax_agents_mcp.keychainPersistence)({ server: s.name });
|
|
2033
|
+
servers.push({
|
|
2034
|
+
...s,
|
|
2035
|
+
auth: {
|
|
2036
|
+
...s.auth,
|
|
2037
|
+
...persist
|
|
2152
2038
|
}
|
|
2153
2039
|
});
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2040
|
+
} else servers.push(s);
|
|
2041
|
+
return {
|
|
2042
|
+
...cfg,
|
|
2043
|
+
servers
|
|
2044
|
+
};
|
|
2045
|
+
};
|
|
2046
|
+
const merge = (jsonCfg) => {
|
|
2047
|
+
const jsonServers = jsonCfg?.servers ?? [];
|
|
2048
|
+
const jsonNames = new Set(jsonServers.map((s) => s.name));
|
|
2049
|
+
const filteredExtras = extras.filter((s) => !jsonNames.has(s.name));
|
|
2050
|
+
return {
|
|
2051
|
+
servers: [...filteredExtras, ...jsonServers],
|
|
2052
|
+
raw: jsonCfg?.raw
|
|
2053
|
+
};
|
|
2054
|
+
};
|
|
2055
|
+
const onConfigError = this.options.onConfigError;
|
|
2056
|
+
const runApply = async (jsonCfg) => {
|
|
2057
|
+
if (this.mcpStopping) return;
|
|
2058
|
+
try {
|
|
2059
|
+
const wired = await wirePersistence(merge(jsonCfg));
|
|
2060
|
+
if (this.mcpStopping) return;
|
|
2061
|
+
await mcpRegistry.applyConfig(wired);
|
|
2062
|
+
} catch (e) {
|
|
2063
|
+
serverLog.error(`[mcp] applyConfig:`, e);
|
|
2064
|
+
try {
|
|
2065
|
+
onConfigError?.(e);
|
|
2066
|
+
} catch (cbErr) {
|
|
2067
|
+
serverLog.error(`[mcp] onConfigError callback failed:`, cbErr);
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
};
|
|
2071
|
+
const applyMerged = (jsonCfg) => {
|
|
2072
|
+
const p = runApply(jsonCfg);
|
|
2073
|
+
this.mcpApplyInFlight.add(p);
|
|
2074
|
+
p.finally(() => this.mcpApplyInFlight.delete(p));
|
|
2075
|
+
return p;
|
|
2076
|
+
};
|
|
2077
|
+
if (mcpConfigPath) {
|
|
2078
|
+
try {
|
|
2079
|
+
const cfg = await (0, __electric_ax_agents_mcp.loadConfig)(mcpConfigPath, process.env);
|
|
2080
|
+
applyMerged(cfg);
|
|
2081
|
+
} catch (err) {
|
|
2082
|
+
if (err.code !== `ENOENT`) throw err;
|
|
2083
|
+
if (extras.length === 0) serverLog.info(`[mcp] no ${mcpConfigPath} — starting with no servers`);
|
|
2084
|
+
else serverLog.info(`[mcp] no ${mcpConfigPath} — starting with ${extras.length} server(s) from extras`);
|
|
2085
|
+
applyMerged(null);
|
|
2086
|
+
}
|
|
2087
|
+
try {
|
|
2088
|
+
this.mcpWatcherCloser = await (0, __electric_ax_agents_mcp.watchConfig)(mcpConfigPath, {
|
|
2089
|
+
onChange: (cfg) => void applyMerged(cfg),
|
|
2090
|
+
onError: (e) => serverLog.error(`[mcp] config error:`, e)
|
|
2162
2091
|
});
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2092
|
+
} catch (e) {
|
|
2093
|
+
serverLog.error(`[mcp] config watcher failed to start:`, e);
|
|
2094
|
+
}
|
|
2095
|
+
} else {
|
|
2096
|
+
if (extras.length > 0) serverLog.info(`[mcp] starting with ${extras.length} server(s) from extras`);
|
|
2097
|
+
applyMerged(null);
|
|
2098
|
+
}
|
|
2099
|
+
this.mcpToolProviderName = `mcp`;
|
|
2100
|
+
(0, __electric_ax_agents_runtime.registerToolProvider)({
|
|
2101
|
+
name: `mcp`,
|
|
2102
|
+
tools: () => {
|
|
2103
|
+
const tools = [];
|
|
2104
|
+
for (const entry of mcpRegistry.list()) {
|
|
2105
|
+
if (entry.status !== `ready`) continue;
|
|
2106
|
+
const live = mcpRegistry.get(entry.name);
|
|
2107
|
+
if (!live?.transport) continue;
|
|
2108
|
+
for (const t of entry.tools) tools.push((0, __electric_ax_agents_mcp.bridgeMcpTool)({
|
|
2109
|
+
server: entry.name,
|
|
2110
|
+
tool: t,
|
|
2111
|
+
client: live.transport.client,
|
|
2112
|
+
timeoutMs: live.config.timeoutMs
|
|
2113
|
+
}));
|
|
2114
|
+
const caps = live.transport.client.getServerCapabilities?.();
|
|
2115
|
+
if (caps?.resources) tools.push(...(0, __electric_ax_agents_mcp.buildResourceTools)({
|
|
2116
|
+
server: entry.name,
|
|
2117
|
+
client: live.transport.client,
|
|
2118
|
+
timeoutMs: live.config.timeoutMs
|
|
2119
|
+
}));
|
|
2120
|
+
if (caps?.prompts) tools.push(...(0, __electric_ax_agents_mcp.buildPromptTools)({
|
|
2121
|
+
server: entry.name,
|
|
2122
|
+
client: live.transport.client,
|
|
2123
|
+
timeoutMs: live.config.timeoutMs
|
|
2124
|
+
}));
|
|
2125
|
+
}
|
|
2126
|
+
return tools;
|
|
2170
2127
|
}
|
|
2171
2128
|
});
|
|
2172
|
-
|
|
2129
|
+
this.bootstrap = await createBuiltinAgentHandler({
|
|
2130
|
+
agentServerUrl: this.options.agentServerUrl,
|
|
2131
|
+
workingDirectory: this.options.workingDirectory,
|
|
2132
|
+
streamFn: this.options.mockStreamFn,
|
|
2133
|
+
createElectricTools: this.options.createElectricTools,
|
|
2134
|
+
publicUrl,
|
|
2135
|
+
runtimeName: `builtin-agents`,
|
|
2136
|
+
serverHeaders: pullWake.headers
|
|
2137
|
+
});
|
|
2138
|
+
if (!this.bootstrap) throw new Error(`ANTHROPIC_API_KEY or OPENAI_API_KEY must be set before starting builtin agents`);
|
|
2139
|
+
await registerBuiltinAgentTypes(this.bootstrap);
|
|
2140
|
+
const registeredRunner = pullWake.registerRunner ? await this.registerPullWakeRunner(pullWake) : null;
|
|
2141
|
+
this.pullWakeRunner = (0, __electric_ax_agents_runtime.createPullWakeRunner)({
|
|
2142
|
+
baseUrl: this.options.agentServerUrl,
|
|
2143
|
+
runnerId: pullWake.runnerId,
|
|
2144
|
+
runtime: this.bootstrap.runtime,
|
|
2145
|
+
headers: pullWake.headers,
|
|
2146
|
+
claimHeaders: pullWake.claimHeaders,
|
|
2147
|
+
claimTokenHeader: pullWake.claimTokenHeader,
|
|
2148
|
+
heartbeatIntervalMs: pullWake.heartbeatIntervalMs,
|
|
2149
|
+
leaseMs: pullWake.leaseMs,
|
|
2150
|
+
offset: registeredRunner?.wake_stream_offset,
|
|
2151
|
+
onError: (error) => {
|
|
2152
|
+
serverLog.error(`[builtin-agents] pull-wake runner failed`, error);
|
|
2153
|
+
return true;
|
|
2154
|
+
}
|
|
2155
|
+
});
|
|
2156
|
+
this.pullWakeRunner.start();
|
|
2157
|
+
serverLog.info(`[builtin-agents] pull-wake runner started: ${pullWake.runnerId}`);
|
|
2158
|
+
return `pull-wake:${pullWake.runnerId}`;
|
|
2159
|
+
} catch (error) {
|
|
2160
|
+
await this.stop().catch(() => {});
|
|
2161
|
+
throw error;
|
|
2162
|
+
}
|
|
2173
2163
|
}
|
|
2174
2164
|
async stop() {
|
|
2165
|
+
if (this.pullWakeRunner) {
|
|
2166
|
+
await this.pullWakeRunner.stop().catch((e) => {
|
|
2167
|
+
serverLog.error(`[builtin-agents] pull-wake runner stop failed`, e);
|
|
2168
|
+
});
|
|
2169
|
+
this.pullWakeRunner = null;
|
|
2170
|
+
}
|
|
2175
2171
|
if (this.bootstrap) {
|
|
2176
2172
|
this.bootstrap.runtime.abortWakes();
|
|
2177
|
-
await Promise.race([this.bootstrap.runtime.drainWakes().catch(() => {
|
|
2173
|
+
await Promise.race([this.bootstrap.runtime.drainWakes().catch((err) => {
|
|
2174
|
+
serverLog.error(`[builtin-agents] drainWakes failed during shutdown:`, err);
|
|
2175
|
+
}), new Promise((resolve) => setTimeout(resolve, 5e3))]);
|
|
2178
2176
|
this.bootstrap = null;
|
|
2179
2177
|
}
|
|
2180
2178
|
this.mcpStopping = true;
|
|
@@ -2197,39 +2195,29 @@ var BuiltinAgentsServer = class {
|
|
|
2197
2195
|
});
|
|
2198
2196
|
this._mcpRegistry = null;
|
|
2199
2197
|
}
|
|
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
2198
|
this.mcpStopping = false;
|
|
2208
|
-
this._url = null;
|
|
2209
|
-
this.publicBaseUrl = null;
|
|
2210
2199
|
}
|
|
2211
|
-
async
|
|
2212
|
-
const
|
|
2213
|
-
|
|
2214
|
-
const
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2200
|
+
async registerPullWakeRunner(pullWake) {
|
|
2201
|
+
const headers = new Headers(typeof pullWake.headers === `function` ? await pullWake.headers() : pullWake.headers);
|
|
2202
|
+
headers.set(`content-type`, `application/json`);
|
|
2203
|
+
const response = await fetch((0, __electric_ax_agents_runtime.appendPathToUrl)(this.options.agentServerUrl, `/_electric/runners`), {
|
|
2204
|
+
method: `POST`,
|
|
2205
|
+
headers,
|
|
2206
|
+
body: JSON.stringify({
|
|
2207
|
+
id: pullWake.runnerId,
|
|
2208
|
+
owner_user_id: pullWake.ownerUserId,
|
|
2209
|
+
label: pullWake.label ?? `Built-in agents`,
|
|
2210
|
+
kind: `local`,
|
|
2211
|
+
admin_status: `enabled`
|
|
2212
|
+
})
|
|
2213
|
+
});
|
|
2214
|
+
if (!response.ok) throw new Error(`Failed to register pull-wake runner ${pullWake.runnerId}: ${response.status} ${await response.text()}`);
|
|
2215
|
+
return await response.json();
|
|
2226
2216
|
}
|
|
2227
2217
|
};
|
|
2228
2218
|
|
|
2229
2219
|
//#endregion
|
|
2230
2220
|
//#region src/entrypoint-lib.ts
|
|
2231
|
-
const DEFAULT_HOST = `127.0.0.1`;
|
|
2232
|
-
const DEFAULT_PORT = 4448;
|
|
2233
2221
|
function readEnv(env, names) {
|
|
2234
2222
|
for (const name of names) {
|
|
2235
2223
|
const value = env[name]?.trim();
|
|
@@ -2242,13 +2230,6 @@ function readRequiredEnv(env, names, description) {
|
|
|
2242
2230
|
if (value) return value;
|
|
2243
2231
|
throw new Error(`Missing ${description}. Set one of: ${names.map((name) => `"${name}"`).join(`, `)}`);
|
|
2244
2232
|
}
|
|
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
2233
|
function validateUrl(name, value) {
|
|
2253
2234
|
try {
|
|
2254
2235
|
new URL(value);
|
|
@@ -2257,20 +2238,63 @@ function validateUrl(name, value) {
|
|
|
2257
2238
|
throw new Error(`Invalid ${name}: "${value}"`);
|
|
2258
2239
|
}
|
|
2259
2240
|
}
|
|
2241
|
+
function buildAssertedAuthHeaders(env) {
|
|
2242
|
+
const headers = {};
|
|
2243
|
+
const email = readEnv(env, [`ELECTRIC_ASSERTED_AUTH_EMAIL`]);
|
|
2244
|
+
const name = readEnv(env, [`ELECTRIC_ASSERTED_AUTH_NAME`]);
|
|
2245
|
+
if (email) headers[`X-Electric-Asserted-Email`] = email;
|
|
2246
|
+
if (name) headers[`X-Electric-Asserted-Name`] = name;
|
|
2247
|
+
return Object.keys(headers).length > 0 ? headers : void 0;
|
|
2248
|
+
}
|
|
2249
|
+
function parseAdditionalServerHeaders(env) {
|
|
2250
|
+
const raw = readEnv(env, [`ELECTRIC_AGENTS_SERVER_HEADERS`]);
|
|
2251
|
+
if (!raw) return void 0;
|
|
2252
|
+
let parsed;
|
|
2253
|
+
try {
|
|
2254
|
+
parsed = JSON.parse(raw);
|
|
2255
|
+
} catch {
|
|
2256
|
+
throw new Error(`Invalid ELECTRIC_AGENTS_SERVER_HEADERS: expected JSON`);
|
|
2257
|
+
}
|
|
2258
|
+
if (!parsed || typeof parsed !== `object` || Array.isArray(parsed)) throw new Error(`Invalid ELECTRIC_AGENTS_SERVER_HEADERS: expected a JSON object`);
|
|
2259
|
+
const headers = new Headers();
|
|
2260
|
+
for (const [name, value] of Object.entries(parsed)) {
|
|
2261
|
+
if (typeof value !== `string`) throw new Error(`Invalid ELECTRIC_AGENTS_SERVER_HEADERS: header "${name}" must be a string`);
|
|
2262
|
+
headers.set(name, value);
|
|
2263
|
+
}
|
|
2264
|
+
const normalized = Object.fromEntries(headers.entries());
|
|
2265
|
+
return Object.keys(normalized).length > 0 ? normalized : void 0;
|
|
2266
|
+
}
|
|
2267
|
+
function mergeHeaders(...sources) {
|
|
2268
|
+
const headers = new Headers();
|
|
2269
|
+
for (const source of sources) {
|
|
2270
|
+
if (!source) continue;
|
|
2271
|
+
new Headers(source).forEach((value, key) => headers.set(key, value));
|
|
2272
|
+
}
|
|
2273
|
+
const merged = Object.fromEntries(headers.entries());
|
|
2274
|
+
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
2275
|
+
}
|
|
2276
|
+
function hasHeader(headers, name) {
|
|
2277
|
+
return headers ? new Headers(headers).has(name) : false;
|
|
2278
|
+
}
|
|
2260
2279
|
function resolveBuiltinAgentsEntrypointOptions(env = process.env, cwd = process.cwd()) {
|
|
2261
2280
|
const agentServerUrl = validateUrl(`agent server URL`, readRequiredEnv(env, [`ELECTRIC_AGENTS_SERVER_URL`, `ELECTRIC_AGENTS_BASE_URL`], `agent server base URL`));
|
|
2262
|
-
const
|
|
2281
|
+
const runnerId = readRequiredEnv(env, [`ELECTRIC_AGENTS_PULL_WAKE_RUNNER_ID`, `PULL_WAKE_RUNNER_ID`], `pull-wake runner id`);
|
|
2282
|
+
const serverHeaders = mergeHeaders(buildAssertedAuthHeaders(env), parseAdditionalServerHeaders(env));
|
|
2263
2283
|
return {
|
|
2264
2284
|
agentServerUrl,
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2285
|
+
workingDirectory: readEnv(env, [`ELECTRIC_AGENTS_WORKING_DIRECTORY`, `WORKING_DIRECTORY`]) ?? cwd,
|
|
2286
|
+
pullWake: {
|
|
2287
|
+
runnerId,
|
|
2288
|
+
registerRunner: readEnv(env, [`ELECTRIC_AGENTS_REGISTER_PULL_WAKE_RUNNER`]) === `true` || readEnv(env, [`ELECTRIC_AGENTS_REGISTER_PULL_WAKE_RUNNER`]) === `1`,
|
|
2289
|
+
headers: serverHeaders,
|
|
2290
|
+
claimHeaders: serverHeaders,
|
|
2291
|
+
claimTokenHeader: hasHeader(serverHeaders, `authorization`) ? `electric-claim-token` : void 0
|
|
2292
|
+
}
|
|
2269
2293
|
};
|
|
2270
2294
|
}
|
|
2271
|
-
async function runBuiltinAgentsEntrypoint({ env = process.env, cwd = process.cwd(), createServer
|
|
2295
|
+
async function runBuiltinAgentsEntrypoint({ env = process.env, cwd = process.cwd(), createServer = (options$1) => new BuiltinAgentsServer(options$1) } = {}) {
|
|
2272
2296
|
const options = resolveBuiltinAgentsEntrypointOptions(env, cwd);
|
|
2273
|
-
const server = createServer
|
|
2297
|
+
const server = createServer(options);
|
|
2274
2298
|
const url = await server.start();
|
|
2275
2299
|
return {
|
|
2276
2300
|
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,8 @@ interface BuiltinAgentHandlerOptions {
|
|
|
43
43
|
streamFn?: StreamFn;
|
|
44
44
|
publicUrl?: string;
|
|
45
45
|
runtimeName?: string;
|
|
46
|
+
serverHeaders?: HeadersProvider;
|
|
47
|
+
defaultDispatchPolicyForType?: (typeName: string) => DispatchPolicy | undefined;
|
|
46
48
|
createElectricTools?: (context: {
|
|
47
49
|
entityUrl: string;
|
|
48
50
|
entityType: string;
|
|
@@ -85,12 +87,20 @@ declare const registerAgentTypes: typeof registerBuiltinAgentTypes;
|
|
|
85
87
|
//#region src/server.d.ts
|
|
86
88
|
interface BuiltinAgentsServerOptions {
|
|
87
89
|
agentServerUrl: string;
|
|
88
|
-
baseUrl?: string;
|
|
89
|
-
port: number;
|
|
90
|
-
host?: string;
|
|
91
90
|
workingDirectory?: string;
|
|
92
91
|
mockStreamFn?: StreamFn;
|
|
93
|
-
|
|
92
|
+
/** Pull-wake runner configuration for built-in agents. */
|
|
93
|
+
pullWake: {
|
|
94
|
+
runnerId: string;
|
|
95
|
+
ownerUserId?: string;
|
|
96
|
+
label?: string;
|
|
97
|
+
registerRunner?: boolean;
|
|
98
|
+
headers?: PullWakeRunnerConfig[`headers`];
|
|
99
|
+
claimHeaders?: PullWakeRunnerConfig[`claimHeaders`];
|
|
100
|
+
claimTokenHeader?: PullWakeRunnerConfig[`claimTokenHeader`];
|
|
101
|
+
heartbeatIntervalMs?: PullWakeRunnerConfig[`heartbeatIntervalMs`];
|
|
102
|
+
leaseMs?: PullWakeRunnerConfig[`leaseMs`];
|
|
103
|
+
};
|
|
94
104
|
/** Invoked when an `authorizationCode` server needs user consent. */
|
|
95
105
|
openAuthorizeUrl?: (url: string, server: string) => void;
|
|
96
106
|
/**
|
|
@@ -149,24 +159,20 @@ interface BuiltinAgentsServerOptions {
|
|
|
149
159
|
}) => Array<AgentTool> | Promise<Array<AgentTool>>;
|
|
150
160
|
}
|
|
151
161
|
declare class BuiltinAgentsServer {
|
|
152
|
-
private server;
|
|
153
162
|
private bootstrap;
|
|
154
|
-
private _url;
|
|
155
|
-
private publicBaseUrl;
|
|
156
163
|
private _mcpRegistry;
|
|
157
164
|
private mcpWatcherCloser;
|
|
158
165
|
private mcpToolProviderName;
|
|
159
166
|
private mcpApplyInFlight;
|
|
160
167
|
private mcpStopping;
|
|
168
|
+
private pullWakeRunner;
|
|
161
169
|
readonly options: BuiltinAgentsServerOptions;
|
|
162
170
|
constructor(options: BuiltinAgentsServerOptions);
|
|
163
171
|
/** Embedded MCP registry. `null` until `start()` has run. */
|
|
164
172
|
get mcpRegistry(): Registry | null;
|
|
165
|
-
get url(): string;
|
|
166
|
-
get registeredBaseUrl(): string;
|
|
167
173
|
start(): Promise<string>;
|
|
168
174
|
stop(): Promise<void>;
|
|
169
|
-
private
|
|
175
|
+
private registerPullWakeRunner;
|
|
170
176
|
}
|
|
171
177
|
|
|
172
178
|
//#endregion
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
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 { braveSearchTool } from "@electric-ax/agents-runtime/tools";
|
|
4
4
|
import { ListedEntry as McpListedEntry, McpConfig, McpServerConfig, McpServerConfig as McpServerConfig$1, Registry, Registry as McpRegistry, RegistrySnapshot, RegistrySubscriber } from "@electric-ax/agents-mcp";
|
|
5
|
-
import { IncomingMessage, ServerResponse } from "node:http";
|
|
6
5
|
import { AgentTool as AgentTool$1, StreamFn } from "@mariozechner/pi-agent-core";
|
|
6
|
+
import { IncomingMessage, ServerResponse } from "node:http";
|
|
7
7
|
|
|
8
8
|
//#region src/skills/types.d.ts
|
|
9
9
|
interface SkillMeta {
|
|
@@ -43,6 +43,8 @@ interface BuiltinAgentHandlerOptions {
|
|
|
43
43
|
streamFn?: StreamFn;
|
|
44
44
|
publicUrl?: string;
|
|
45
45
|
runtimeName?: string;
|
|
46
|
+
serverHeaders?: HeadersProvider;
|
|
47
|
+
defaultDispatchPolicyForType?: (typeName: string) => DispatchPolicy | undefined;
|
|
46
48
|
createElectricTools?: (context: {
|
|
47
49
|
entityUrl: string;
|
|
48
50
|
entityType: string;
|
|
@@ -85,12 +87,20 @@ declare const registerAgentTypes: typeof registerBuiltinAgentTypes;
|
|
|
85
87
|
//#region src/server.d.ts
|
|
86
88
|
interface BuiltinAgentsServerOptions {
|
|
87
89
|
agentServerUrl: string;
|
|
88
|
-
baseUrl?: string;
|
|
89
|
-
port: number;
|
|
90
|
-
host?: string;
|
|
91
90
|
workingDirectory?: string;
|
|
92
91
|
mockStreamFn?: StreamFn;
|
|
93
|
-
|
|
92
|
+
/** Pull-wake runner configuration for built-in agents. */
|
|
93
|
+
pullWake: {
|
|
94
|
+
runnerId: string;
|
|
95
|
+
ownerUserId?: string;
|
|
96
|
+
label?: string;
|
|
97
|
+
registerRunner?: boolean;
|
|
98
|
+
headers?: PullWakeRunnerConfig[`headers`];
|
|
99
|
+
claimHeaders?: PullWakeRunnerConfig[`claimHeaders`];
|
|
100
|
+
claimTokenHeader?: PullWakeRunnerConfig[`claimTokenHeader`];
|
|
101
|
+
heartbeatIntervalMs?: PullWakeRunnerConfig[`heartbeatIntervalMs`];
|
|
102
|
+
leaseMs?: PullWakeRunnerConfig[`leaseMs`];
|
|
103
|
+
};
|
|
94
104
|
/** Invoked when an `authorizationCode` server needs user consent. */
|
|
95
105
|
openAuthorizeUrl?: (url: string, server: string) => void;
|
|
96
106
|
/**
|
|
@@ -149,24 +159,20 @@ interface BuiltinAgentsServerOptions {
|
|
|
149
159
|
}) => Array<AgentTool> | Promise<Array<AgentTool>>;
|
|
150
160
|
}
|
|
151
161
|
declare class BuiltinAgentsServer {
|
|
152
|
-
private server;
|
|
153
162
|
private bootstrap;
|
|
154
|
-
private _url;
|
|
155
|
-
private publicBaseUrl;
|
|
156
163
|
private _mcpRegistry;
|
|
157
164
|
private mcpWatcherCloser;
|
|
158
165
|
private mcpToolProviderName;
|
|
159
166
|
private mcpApplyInFlight;
|
|
160
167
|
private mcpStopping;
|
|
168
|
+
private pullWakeRunner;
|
|
161
169
|
readonly options: BuiltinAgentsServerOptions;
|
|
162
170
|
constructor(options: BuiltinAgentsServerOptions);
|
|
163
171
|
/** Embedded MCP registry. `null` until `start()` has run. */
|
|
164
172
|
get mcpRegistry(): Registry | null;
|
|
165
|
-
get url(): string;
|
|
166
|
-
get registeredBaseUrl(): string;
|
|
167
173
|
start(): Promise<string>;
|
|
168
174
|
stop(): Promise<void>;
|
|
169
|
-
private
|
|
175
|
+
private registerPullWakeRunner;
|
|
170
176
|
}
|
|
171
177
|
|
|
172
178
|
//#endregion
|