@vfarcic/dot-ai
Version:
AI-powered development productivity platform that enhances software development workflows through intelligent automation and AI-driven assistance
149 lines (148 loc) • 4.49 kB
JavaScript
"use strict";
/**
* Plugin Client for dot-ai Plugin System
*
* HTTP client for communicating with agentic plugins.
* Handles describe and invoke hooks via POST /execute endpoint.
*
* PRD #343: kubectl Plugin Migration
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.PluginClient = exports.PluginClientError = void 0;
/**
* Error thrown when plugin communication fails
*/
class PluginClientError extends Error {
pluginName;
pluginUrl;
cause;
constructor(message, pluginName, pluginUrl, cause) {
super(message);
this.pluginName = pluginName;
this.pluginUrl = pluginUrl;
this.cause = cause;
this.name = 'PluginClientError';
}
}
exports.PluginClientError = PluginClientError;
/**
* HTTP client for a single plugin
*/
class PluginClient {
config;
logger;
timeout;
constructor(config, logger) {
this.config = config;
this.logger = logger;
// PRD #343: Increased default timeout from 30s to 5m for Helm operations
// Helm install/upgrade with --wait can take several minutes for complex charts
this.timeout = config.timeout ?? 300000;
}
/**
* Get plugin name
*/
get name() {
return this.config.name;
}
/**
* Get plugin URL
*/
get url() {
return this.config.url;
}
/**
* Call the describe hook to get tool definitions
*/
async describe() {
const request = {
hook: 'describe',
};
this.logger.debug('Calling plugin describe hook', {
plugin: this.config.name,
url: this.config.url,
});
const response = await this.execute(request);
this.logger.debug('Plugin describe response', {
plugin: this.config.name,
version: response.version,
toolCount: response.tools.length,
});
return response;
}
/**
* Call the invoke hook to execute a tool
*/
async invoke(tool, args, state = {}, sessionId) {
const payload = {
tool,
args,
state,
};
const request = {
hook: 'invoke',
sessionId,
payload,
};
this.logger.debug('Calling plugin invoke hook', {
plugin: this.config.name,
tool,
sessionId,
});
const response = await this.execute(request);
this.logger.debug('Plugin invoke response', {
plugin: this.config.name,
tool,
success: response.success,
});
return response;
}
/**
* Check if plugin is healthy/reachable
*/
async healthCheck() {
try {
await this.describe();
return true;
}
catch {
return false;
}
}
/**
* Execute a request to the plugin's /execute endpoint
*/
async execute(request) {
const executeUrl = `${this.config.url}/execute`;
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
const response = await fetch(executeUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(request),
signal: controller.signal,
});
clearTimeout(timeoutId);
if (!response.ok) {
const errorText = await response.text().catch(() => 'Unknown error');
throw new PluginClientError(`Plugin returned HTTP ${response.status}: ${errorText}`, this.config.name, this.config.url);
}
const data = await response.json();
return data;
}
catch (error) {
if (error instanceof PluginClientError) {
throw error;
}
const cause = error instanceof Error ? error : new Error(String(error));
if (cause.name === 'AbortError') {
throw new PluginClientError(`Plugin request timed out after ${this.timeout}ms`, this.config.name, this.config.url, cause);
}
throw new PluginClientError(`Failed to communicate with plugin: ${cause.message}`, this.config.name, this.config.url, cause);
}
}
}
exports.PluginClient = PluginClient;