@humanlayer/sdk
Version:
typescript client for humanlayer.dev
265 lines (261 loc) • 10.9 kB
JavaScript
"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;