Developer Rules & Guidelines
- Export Structure:
handler.jsmust export aruntimeobject containing ahandlerfunction. - Arguments: The
handlerfunction accepts a single object argument containing parameters defined in theplugin.jsonentrypointproperty. - Return Value: Must return a
string. Any other type will break the agent invocation or loop indefinitely. - Imports: Use
requirefor 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
awaitfor external API or service calls. - Error Handling: Wrap the handler body in a
try/catchblock 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 tonull.
- Returns:
Promise<{ approved: boolean, message: string }>approved(trueif approved or in a non-interactive context like scheduled runs;falseon 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}¤t_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);
},
};