User-defined Tools
README: executable custom JavaScript tools for Aegis
Overview
Aegis supports defining custom tools with runnable JavaScript. Tools are model-callable functions with your JSON schema and runtime code.
This gives you full customization and lets you connect either local APIs running on your own infrastructure or cloud APIs.
- In app: Settings -> Add Tool
- Custom tools must include executable
runtime.code - Network access is opt-in and host-allowlisted
Runtime Contract
- Input object is available as
input - Set final output in
result print(...)andconsole.log(...)write to tool stdout- Network calls use
await http.requestAsync(...)(or synchttp.request(...)), notfetch
const response = await http.requestAsync({
url: "https://api.weather.gov/points/37.7749,-122.4194",
method: "GET",
headers: {
"Accept": "application/geo+json",
"User-Agent": "Aegis Custom Tool/1.0"
}
});
const body = response.body ? JSON.parse(response.body) : {};
result = { ok: true, body }; National Weather Service API example
This example mirrors the built-in NWS behavior using a custom defined tool.
{
"id": "weather.nws_forecast_custom",
"name": "Weather: NWS Forecast (Custom JS)",
"source": "custom",
"enabled": true,
"description": "Get US weather forecast and active alerts from NOAA/NWS via custom JavaScript runtime.",
"promptMarkdown": "Use for US weather forecasts and active alerts. Required: latitude, longitude. Optional: forecastType (daily/hourly), periods, includeAlerts.",
"parameters": {
"type": "object",
"properties": {
"latitude": {
"type": "number",
"description": "Latitude in decimal degrees."
},
"longitude": {
"type": "number",
"description": "Longitude in decimal degrees."
},
"forecastType": {
"type": "string",
"enum": [
"daily",
"hourly"
]
},
"periods": {
"type": "number",
"description": "Forecast periods (default 5, max 12)."
},
"includeAlerts": {
"type": "boolean",
"description": "Include active alerts (default true)."
}
},
"required": [
"latitude",
"longitude"
],
"additionalProperties": false
},
"runtime": {
"language": "javascript",
"timeoutMs": 7000,
"network": {
"allowHosts": [
"api.weather.gov"
],
"timeoutMs": 5000,
"maxResponseBytes": 200000,
"maxRequestBodyBytes": 10000
},
"code": "const latitude = Number(input.latitude);\nconst longitude = Number(input.longitude);\nif (!Number.isFinite(latitude) || !Number.isFinite(longitude)) throw new Error('latitude and longitude are required numbers.');\nconst forecastType = String(input.forecastType || 'daily').toLowerCase() === 'hourly' ? 'hourly' : 'daily';\nconst periodsRaw = Number(input.periods);\nconst periods = Number.isFinite(periodsRaw) ? Math.max(1, Math.min(12, Math.round(periodsRaw))) : 5;\nconst includeAlerts = input.includeAlerts !== false;\nconst roundedLat = Number(latitude.toFixed(4));\nconst roundedLon = Number(longitude.toFixed(4));\nasync function getJson(url) {\n const response = await http.requestAsync({\n url,\n method: 'GET',\n headers: { Accept: 'application/geo+json', 'User-Agent': 'Aegis Custom Tool/1.0' }\n });\n return response.body && String(response.body).trim() ? JSON.parse(response.body) : {};\n}\nconst pointsPayload = await getJson('https://api.weather.gov/points/' + roundedLat + ',' + roundedLon);\nconst pointProps = pointsPayload && pointsPayload.properties ? pointsPayload.properties : {};\nconst forecastUrl = forecastType === 'hourly' ? String(pointProps.forecastHourly || '') : String(pointProps.forecast || '');\nif (!forecastUrl) throw new Error('NWS did not return a forecast URL for this point.');\nconst forecastPayload = await getJson(forecastUrl);\nconst forecastProps = forecastPayload && forecastPayload.properties ? forecastPayload.properties : {};\nconst periodRows = Array.isArray(forecastProps.periods) ? forecastProps.periods.slice(0, periods) : [];\nlet alerts = [];\nlet alertsError = '';\nif (includeAlerts) {\n try {\n const alertsPayload = await getJson('https://api.weather.gov/alerts/active?point=' + roundedLat + ',' + roundedLon);\n const features = Array.isArray(alertsPayload.features) ? alertsPayload.features : [];\n alerts = features.slice(0, 10).map((feature) => {\n const props = feature && feature.properties ? feature.properties : {};\n return {\n id: String(feature && feature.id ? feature.id : ''),\n event: String(props.event || ''),\n severity: String(props.severity || ''),\n urgency: String(props.urgency || ''),\n certainty: String(props.certainty || ''),\n headline: String(props.headline || ''),\n onset: String(props.onset || ''),\n expires: String(props.expires || '')\n };\n });\n } catch (error) {\n alertsError = String(error && error.message ? error.message : error);\n }\n}\nresult = {\n source: 'NOAA National Weather Service',\n requested: { latitude: roundedLat, longitude: roundedLon, forecastType, periods, includeAlerts },\n updated: String(forecastProps.updated || ''),\n timezone: String(pointProps.timeZone || ''),\n forecastUrl,\n count: periodRows.length,\n periods: periodRows.map((row) => {\n const precip = row && row.probabilityOfPrecipitation ? row.probabilityOfPrecipitation : {};\n return {\n number: row && row.number,\n name: String(row && row.name ? row.name : ''),\n startTime: String(row && row.startTime ? row.startTime : ''),\n endTime: String(row && row.endTime ? row.endTime : ''),\n isDaytime: !!(row && row.isDaytime),\n temperature: row && row.temperature,\n temperatureUnit: String(row && row.temperatureUnit ? row.temperatureUnit : ''),\n windSpeed: String(row && row.windSpeed ? row.windSpeed : ''),\n windDirection: String(row && row.windDirection ? row.windDirection : ''),\n shortForecast: String(row && row.shortForecast ? row.shortForecast : ''),\n detailedForecast: String(row && row.detailedForecast ? row.detailedForecast : ''),\n precipitationChancePercent: precip && precip.value\n };\n }),\n alerts,\n alertCount: alerts.length,\n ...(alertsError ? { alertsError } : {})\n};"
}
} Warning
You are responsible for code you execute on your phone. Please make sure you trust the source or have reviewed it.
Notes
- Use multi-step tools for long-running jobs (for example Flux create + poll).
- Keep network allowlists narrow by hostname.
- Use small response/body limits unless needed.
- Canonical schema URL 🔗