Developer Rules & Guidelines

  • Export Structure: handler.js must export a runtime object containing a handler function.
  • Arguments: The handler function accepts a single object argument containing parameters defined in the plugin.json entrypoint property.
  • Return Value: Must return a string. Any other type will break the agent invocation or loop indefinitely.
  • Imports: Use require for NodeJS standard library or bundled modules. Import modules within the function scope rather than the global scope. Do not require modules outside the plugin folder.
  • Asynchronous Calls: Use await for external API or service calls.
  • Error Handling: Wrap the handler body in a try/catch block and return the error message as a string.

Runtime Properties & Methods

this.runtimeArgs

Accesses arguments passed to setup_args in plugin.json.

// Definition in plugin.json:
// "setup_args": { "OPEN_METEO_API_KEY": { "value": "sk-key-for-service", ... } }
 
const apiKey = this.runtimeArgs["OPEN_METEO_API_KEY"]; // Returns 'sk-key-for-service'

this.introspect(message)

Logs thoughts/observations directly to the user interface.

  • message (string)
this.introspect("Analyzing data..."); 

this.logger(message)

Logs debugging messages to the system console.

  • message (string)
this.logger("Debugging checklist initiated.");

this.config

Metadata object for the custom skill.

const name = this.config.name;       // e.g., 'Get Weather'
const hubId = this.config.hubId;     // e.g., 'open-meteo-weather-api'
const version = this.config.version; // e.g., '1.0.0'

this.requestToolApproval({ payload, description })

Pauses the agent execution to request user confirmation for high-impact/destructive actions.

  • Arguments:
    • payload (object, optional): Arbitrary metadata to display. Defaults to {}.
    • description (string, optional): Context message shown to the user. Defaults to null.
  • Returns: Promise<{ approved: boolean, message: string }>
    • approved (true if approved or in a non-interactive context like scheduled runs; false on rejection or after a 120-second timeout).
    • message (Status string. Return this value to the agent if rejected).
const approval = await this.requestToolApproval({
  payload: { recordId },
  description: `Permanently delete record ${recordId}? This cannot be undone.`,
});
 
if (!approval.approved) return approval.message;

Example implementation

module.exports.runtime = {
  handler: async function ({ latitude, longitude }) {
    const callerId = `${this.config.name}-v${this.config.version}`;
    try {
      this.introspect(`${callerId} called with lat:${latitude} long:${longitude}...`);
      
      const response = await fetch(
        `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&current_weather=true&hourly=temperature_2m,relativehumidity_2m,windspeed_10m`
      );
      const data = await response.json();
      
      const averageTemperature = this._getAverage(data, "temperature_2m");
      const averageHumidity = this._getAverage(data, "relativehumidity_2m");
      const averageWindSpeed = this._getAverage(data, "windspeed_10m");
      
      return JSON.stringify({
        averageTemperature,
        averageHumidity,
        averageWindSpeed,
      });
    } catch (e) {
      this.introspect(`${callerId} failed. Reason: ${e.message}`);
      this.logger(`${callerId} failed: ${e.message}`);
      return `The tool failed to run. Reason: ${e.message}`;
    }
  },
 
  _getAverage(data, property) {
    return (
      data.hourly[property].reduce((a, b) => a + b, 0) /
      data.hourly[property].length
    );
  },
 
  _doExternalApiCall(myProp) {
    const _ScopedExternalCaller = require("./external-api-caller.js");
    return _ScopedExternalCaller.doSomething(myProp);
  },
};