UNPKG

@humanlayer/sdk

Version:

typescript client for humanlayer.dev

265 lines (261 loc) 10.9 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.HumanLayer = exports.humanlayer = exports.ApprovalMethod = void 0; const crypto_1 = __importDefault(require("crypto")); const protocol_1 = require("./protocol"); const cloud_1 = require("./cloud"); const logger_1 = require("./logger"); var ApprovalMethod; (function (ApprovalMethod) { ApprovalMethod["cli"] = "cli"; ApprovalMethod["backend"] = "backend"; })(ApprovalMethod || (exports.ApprovalMethod = ApprovalMethod = {})); const nullIsh = (value) => value === null || typeof value === 'undefined'; /** * sure this'll work for now */ const defaultGenid = (prefix) => { return `${prefix}-${crypto_1.default.randomUUID().slice(0, 8)}`; }; const humanlayer = (params) => { return new HumanLayer(params); }; exports.humanlayer = humanlayer; class HumanLayer { constructor(params) { const { runId, approvalMethod, backend, agentName, genid = defaultGenid, sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)), contactChannel, apiKey, apiBaseUrl, verbose = false, httpTimeoutSeconds = parseInt(process.env.HUMANLAYER_HTTP_TIMEOUT_SECONDS || '10'), } = params || {}; this.genid = genid; this.sleep = sleep; this.verbose = verbose; this.contactChannel = contactChannel; // Simplified approval method logic this.approvalMethod = approvalMethod || (backend || apiKey || process.env.HUMANLAYER_API_KEY ? ApprovalMethod.backend : ApprovalMethod.cli); // Initialize backend if approval method is backend if (this.approvalMethod === ApprovalMethod.backend) { this.backend = backend || new cloud_1.CloudHumanLayerBackend(new cloud_1.HumanLayerCloudConnection(apiKey || process.env.HUMANLAYER_API_KEY, apiBaseUrl || process.env.HUMANLAYER_API_BASE)); } this.agentName = agentName || 'agent'; this.genid = genid || defaultGenid; this.runId = runId || this.genid(this.agentName); } static cloud(params) { let { connection, apiKey, apiBaseUrl } = params || {}; if (!connection) { connection = new cloud_1.HumanLayerCloudConnection(apiKey, apiBaseUrl); } return new HumanLayer({ approvalMethod: ApprovalMethod.backend, backend: new cloud_1.CloudHumanLayerBackend(connection), }); } static cli() { return new HumanLayer({ approvalMethod: ApprovalMethod.cli, }); } requireApproval(contactChannel) { return (fn) => { if (this.approvalMethod === ApprovalMethod.cli) { return this.approveCli(fn); } return this.approveWithBackend(fn, contactChannel); }; } approveCli(fn) { const name = fn.name; // todo fix the types here const f = async (kwargs) => { logger_1.logger.info(`Agent ${this.runId} wants to call ${fn.name}(${JSON.stringify(kwargs, null, 2)}) ${kwargs.length ? ' with args: ' + JSON.stringify(kwargs, null, 2) : ''}`); const readline = require('readline').createInterface({ input: process.stdin, output: process.stdout, }); const feedback = await new Promise(resolve => { readline.question('Hit ENTER to proceed, or provide feedback to the agent to deny: \n\n', (answer) => { readline.close(); resolve(answer); }); }); if (feedback !== null && feedback !== '') { return `User denied ${fn.name} with feedback: ${feedback}`; } try { return await fn(kwargs); } catch (e) { return `Error running ${fn.name}: ${e}`; } }; Object.defineProperty(f, 'name', { value: name, writable: false }); return f; } approveWithBackend(fn, contactChannel) { const channel = contactChannel || this.contactChannel; const name = fn.name; // todo fix the types here const f = async (kwargs) => { var _a, _b, _c, _d; const backend = this.backend; const callId = this.genid('call'); await backend.functions().add({ run_id: this.runId, call_id: callId, spec: { fn: fn.name, kwargs: kwargs, channel: channel, }, }); if (this.verbose) { logger_1.logger.info(`HumanLayer: Requested approval for function ${name}`); } while (true) { await this.sleep(3000); const functionCall = await backend.functions().get(callId); if (((_a = functionCall.status) === null || _a === void 0 ? void 0 : _a.approved) === null || typeof ((_b = functionCall.status) === null || _b === void 0 ? void 0 : _b.approved) === 'undefined') { continue; } if ((_c = functionCall.status) === null || _c === void 0 ? void 0 : _c.approved) { if (this.verbose) { logger_1.logger.info(`HumanLayer: User approved function ${functionCall.spec.fn}`); } return fn(kwargs); } else { return `User denied function ${functionCall.spec.fn} with comment: ${(_d = functionCall.status) === null || _d === void 0 ? void 0 : _d.comment}`; } } }; Object.defineProperty(f, 'name', { value: name, writable: false }); return f; } humanAsTool(contactChannel) { if (this.approvalMethod === ApprovalMethod.cli) { return this.humanAsToolCli(); } return this.humanAsToolBackend(contactChannel); } humanAsToolCli() { return async ({ message }) => { logger_1.logger.info(`Agent ${this.runId} requests assistance: ${message} `); const feedback = prompt('Please enter a response: \n\n'); return feedback || ''; }; } humanAsToolBackend(contactChannel) { const channel = contactChannel || this.contactChannel; return async ({ message }) => { return this.fetchHumanResponse({ spec: { msg: message, channel: channel, }, }); }; } async fetchHumanResponse({ spec }) { var _a; spec.channel = nullIsh(spec.channel) ? this.contactChannel : spec.channel; let humanContact = await this.createHumanContact({ spec }); if (this.verbose) { logger_1.logger.info(`HumanLayer: Requested human response from HumanLayer cloud`); } while (true) { await this.sleep(3000); humanContact = await this.getHumanContact(humanContact.call_id); if (!((_a = humanContact.status) === null || _a === void 0 ? void 0 : _a.response)) { continue; } if (this.verbose) { logger_1.logger.info(`HumanLayer: Received human response: ${humanContact.status.response}`); } return humanContact.status.response; } } async createHumanContact({ spec }) { spec.channel = nullIsh(spec.channel) ? this.contactChannel : spec.channel; if (!this.backend) { throw new protocol_1.HumanLayerException('createHumanContact requires a backend'); } const callId = this.genid('human_call'); const ret = await this.backend.contacts().add({ run_id: this.runId, call_id: callId, spec: spec, }); return ret; } async escalateEmailHumanContact(call_id, escalation) { if (!this.backend) { throw new protocol_1.HumanLayerException('escalateEmailFunctionCall requires a backend'); } return this.backend.contacts().escalateEmail(call_id, escalation); } getHumanContact(call_id) { if (!this.backend) { throw new protocol_1.HumanLayerException('getHumanContact requires a backend'); } return this.backend.contacts().get(call_id); } async fetchHumanApproval({ spec }) { var _a, _b, _c, _d, _e; spec.channel = nullIsh(spec.channel) ? this.contactChannel : spec.channel; let functionCall = await this.createFunctionCall({ spec: spec, }); if (this.verbose) { logger_1.logger.info(`HumanLayer: Requested human approval from HumanLayer cloud`); } while (true) { await this.sleep(3000); functionCall = await this.getFunctionCall(functionCall.call_id); if (((_a = functionCall.status) === null || _a === void 0 ? void 0 : _a.approved) === null || typeof ((_b = functionCall.status) === null || _b === void 0 ? void 0 : _b.approved) === 'undefined') { continue; } if (this.verbose) { logger_1.logger.info(`HumanLayer: Received response ${((_c = functionCall.status) === null || _c === void 0 ? void 0 : _c.approved) ? ' (approved)' : ' (denied)'} ${((_d = functionCall.status) === null || _d === void 0 ? void 0 : _d.comment) ? `with comment: ${(_e = functionCall.status) === null || _e === void 0 ? void 0 : _e.comment}` : ''}`); } return functionCall.status; } } async createFunctionCall({ spec }) { spec.channel = nullIsh(spec.channel) ? this.contactChannel : spec.channel; if (!this.backend) { throw new protocol_1.HumanLayerException('createFunctionCall requires a backend'); } const callId = this.genid('call'); return this.backend.functions().add({ run_id: this.runId, call_id: callId, spec: spec, }); } async escalateEmailFunctionCall(call_id, escalation) { if (!this.backend) { throw new protocol_1.HumanLayerException('escalateEmailFunctionCall requires a backend'); } return this.backend.functions().escalateEmail(call_id, escalation); } async getFunctionCall(call_id) { if (!this.backend) { throw new protocol_1.HumanLayerException('getFunctionCall requires a backend'); } return this.backend.functions().get(call_id); } } exports.HumanLayer = HumanLayer;