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(...) and console.log(...) write to tool stdout
  • Network calls use await http.requestAsync(...) (or sync http.request(...)), not fetch
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 🔗