donobu
Version:
Create browser automations with an LLM agent and replay them as Playwright scripts.
305 lines • 13.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.AnthropicGptClient = void 0;
const v4_1 = require("zod/v4");
const GptModelNotFoundException_1 = require("../exceptions/GptModelNotFoundException");
const GptPlatformAuthenticationFailedException_1 = require("../exceptions/GptPlatformAuthenticationFailedException");
const GptPlatformInsufficientQuotaException_1 = require("../exceptions/GptPlatformInsufficientQuotaException");
const GptPlatformInternalErrorException_1 = require("../exceptions/GptPlatformInternalErrorException");
const GptPlatformNotReachableException_1 = require("../exceptions/GptPlatformNotReachableException");
const InvalidParamValueException_1 = require("../exceptions/InvalidParamValueException");
const JsonUtils_1 = require("../utils/JsonUtils");
const Logger_1 = require("../utils/Logger");
const MiscUtils_1 = require("../utils/MiscUtils");
const GptClient_1 = require("./GptClient");
/**
* A GPT client implemented using the Anthropic API
* @see https://docs.anthropic.com/en/api/messages
*/
class AnthropicGptClient extends GptClient_1.GptClient {
constructor(anthropicConfig) {
super(anthropicConfig);
this.anthropicConfig = anthropicConfig;
if (!/^[\x21-\x7e]{1,128}$/.test(anthropicConfig.apiKey)) {
throw new InvalidParamValueException_1.InvalidParamValueException('apiKey', 'REDACTED', 'it is malformed');
}
this.headers = new Headers({
[AnthropicGptClient.API_KEY_HEADER_NAME]: anthropicConfig.apiKey,
[AnthropicGptClient.API_VERSION_HEADER_NAME]: AnthropicGptClient.API_VERSION_HEADER_VALUE,
'Content-Type': AnthropicGptClient.CONTENT_TYPE_HEADER_VALUE,
});
}
async ping(options) {
const resp = await this.makeRequest(`/v1/models/${this.anthropicConfig.modelName}`, 'GET', undefined, undefined, options?.signal);
if (resp.status === 404) {
throw new GptModelNotFoundException_1.GptModelNotFoundException(this.config.type, this.anthropicConfig.modelName);
}
else if (resp.status !== 200) {
throw await this.mapErrorResponseToDonobuException(resp);
}
}
async getMessage(messages, options) {
messages = MiscUtils_1.MiscUtils.mergeAdjacentUserMessages(messages);
const systemPrompts = messages
.filter((msg) => msg.type === 'system')
.map(AnthropicGptClient.chatRequestMessageFromGptMessage);
const body = {
model: this.anthropicConfig.modelName,
max_tokens: AnthropicGptClient.MAX_TOKENS,
temperature: 0.0,
system: systemPrompts,
messages: messages
.filter((msg) => msg.type !== 'system')
.map(AnthropicGptClient.chatRequestMessageFromGptMessage),
};
const resp = await this.makeRequest('/v1/messages', 'POST', body, undefined, options?.signal);
if (resp.status !== 200) {
throw await this.mapErrorResponseToDonobuException(resp);
}
const data = await resp.json();
const text = data.content[0].text;
const promptTokensUsed = data.usage.input_tokens;
const completionTokensUsed = data.usage.output_tokens;
return {
type: 'assistant',
text,
promptTokensUsed,
completionTokensUsed,
};
}
async getStructuredOutput(messages, zodSchema, options) {
messages = MiscUtils_1.MiscUtils.mergeAdjacentUserMessages(messages);
const systemPrompts = messages
.filter((msg) => msg.type === 'system')
.map(AnthropicGptClient.chatRequestMessageFromGptMessage);
const body = {
model: this.anthropicConfig.modelName,
max_tokens: AnthropicGptClient.MAX_TOKENS,
temperature: 0.0,
system: systemPrompts,
messages: messages
.filter((msg) => msg.type !== 'system')
.map(AnthropicGptClient.chatRequestMessageFromGptMessage),
tools: [
{
name: 'StructuredOutputTool',
description: 'Call this tool with the described parameters',
input_schema: v4_1.z.toJSONSchema(zodSchema),
},
],
tool_choice: {
name: 'StructuredOutputTool',
type: 'tool',
disable_parallel_tool_use: true,
},
};
const resp = await this.makeRequest('/v1/messages', 'POST', body, undefined, options?.signal);
if (resp.status !== 200) {
throw await this.mapErrorResponseToDonobuException(resp);
}
const data = await resp.json();
const item = data.content[0];
const contentType = item.type;
let respObj;
if (contentType === 'tool_use') {
respObj = item.input;
}
else if (contentType === 'text') {
throw new Error('Unsupported content type: text');
}
else {
throw new Error(`Unexpected content type: ${contentType}`);
}
const promptTokensUsed = data.usage.input_tokens;
const completionTokensUsed = data.usage.output_tokens;
const output = (0, GptClient_1.parseOrLogAndThrow)(respObj, zodSchema);
return {
type: 'structured_output',
output: output,
promptTokensUsed,
completionTokensUsed,
};
}
async getToolCalls(messages, tools, options) {
messages = MiscUtils_1.MiscUtils.mergeAdjacentUserMessages(messages);
const systemPrompts = messages
.filter((msg) => msg.type === 'system')
.map(AnthropicGptClient.chatRequestMessageFromGptMessage);
if (systemPrompts.length > 0) {
const lastPrompt = systemPrompts[systemPrompts.length - 1];
lastPrompt.cache_control = { type: 'ephemeral' };
}
const body = {
model: this.anthropicConfig.modelName,
max_tokens: AnthropicGptClient.MAX_TOKENS,
temperature: 0.0,
tool_choice: { type: 'any' },
tools: tools.length
? tools.map(AnthropicGptClient.toolChoiceFromTool)
: undefined,
system: systemPrompts,
messages: messages
.filter((msg) => msg.type !== 'system')
.map(AnthropicGptClient.chatRequestMessageFromGptMessage),
};
// Note the special header for this endpoint
const headers = new Headers(this.headers);
headers.append('anthropic-beta', 'prompt-caching-2024-07-31');
const resp = await this.makeRequest('/v1/messages', 'POST', body, headers, options?.signal);
if (resp.status !== 200) {
throw await this.mapErrorResponseToDonobuException(resp);
}
const data = await resp.json();
const proposedToolCalls = data.content.map((item) => {
const contentType = item.type;
if (contentType === 'tool_use') {
const tool = tools.find((t) => t.name === item.name);
if (!tool) {
throw new Error('Unable to find matching tool for tool call');
}
return {
name: item.name,
parameters: item.input,
toolCallId: item.id,
};
}
else if (contentType === 'text') {
throw new Error('Unsupported content type: text');
}
else {
throw new Error(`Unexpected content type: ${contentType}`);
}
});
const promptTokensUsed = data.usage.input_tokens;
const completionTokensUsed = data.usage.output_tokens;
return {
type: 'proposed_tool_calls',
proposedToolCalls,
promptTokensUsed,
completionTokensUsed,
};
}
async mapErrorResponseToDonobuException(error) {
try {
const errorData = await error.json();
Logger_1.appLogger.error(`Anthropic error response: ${JSON.stringify(JsonUtils_1.JsonUtils.objectToJson(errorData))}`);
if (errorData.error?.code === 'authentication_error') {
return new GptPlatformAuthenticationFailedException_1.GptPlatformAuthenticationFailedException(this.config.type);
}
else if (error.status === 402) {
return new GptPlatformInsufficientQuotaException_1.GptPlatformInsufficientQuotaException(this.config.type);
}
return new GptPlatformInternalErrorException_1.GptPlatformInternalErrorException(errorData.error?.message || `HTTP ${error.status}: ${error.statusText}`);
}
catch (_) {
Logger_1.appLogger.error(`Failed to parse Anthropic error response: HTTP ${error.status}: ${error.statusText}`);
return new GptPlatformInternalErrorException_1.GptPlatformInternalErrorException(`HTTP ${error.status}: ${error.statusText}`);
}
}
async makeRequest(endpoint, method = 'GET', body, customHeaders, signal) {
try {
const abortSignal = signal ||
AbortSignal.timeout(AnthropicGptClient.REQUEST_TIMEOUT_MILLISECONDS);
return await fetch(`${AnthropicGptClient.API_URL}${endpoint}`, {
method,
headers: customHeaders || this.headers,
body: body ? JSON.stringify(body) : undefined,
signal: abortSignal,
});
}
catch (error) {
if (error instanceof TypeError) {
throw new GptPlatformNotReachableException_1.GptPlatformNotReachableException(this.config.type);
}
else {
throw error;
}
}
}
static chatRequestMessageFromGptMessage(gptMessage) {
if (gptMessage.type === 'assistant') {
return {
role: 'assistant',
content: gptMessage.text,
};
}
if (gptMessage.type === 'structured_output') {
const output = gptMessage.output;
return {
role: 'assistant',
content: JSON.stringify(JsonUtils_1.JsonUtils.objectToJson(output), null, 2),
};
}
if (gptMessage.type === 'proposed_tool_calls') {
return {
role: 'assistant',
content: gptMessage.proposedToolCalls.map((tc) => ({
type: 'tool_use',
id: tc.toolCallId,
name: tc.name,
input: JsonUtils_1.JsonUtils.objectToJson(tc.parameters),
})),
};
}
if (gptMessage.type === 'system') {
return {
type: 'text',
text: gptMessage.text,
};
}
if (gptMessage.type === 'user') {
return {
role: 'user',
content: gptMessage.items.map((item) => {
if ('bytes' in item) {
const imageType = MiscUtils_1.MiscUtils.detectImageType(item.bytes);
const mimeType = `image/${imageType}`;
return {
type: 'image',
source: {
type: 'base64',
media_type: mimeType,
data: Buffer.from(item.bytes).toString('base64'),
},
};
}
else {
return {
type: 'text',
text: item.text,
};
}
}),
};
}
if (gptMessage.type === 'tool_call_result') {
return {
role: 'user',
content: [
{
type: 'tool_result',
tool_use_id: gptMessage.toolCallId,
content: gptMessage.data,
},
],
};
}
throw new Error(`Unsupported message type: ${JsonUtils_1.JsonUtils.objectToJson(gptMessage)}`);
}
static toolChoiceFromTool(tool) {
return {
name: tool.name,
description: tool.description,
input_schema: v4_1.z.toJSONSchema(tool.inputSchema),
};
}
}
exports.AnthropicGptClient = AnthropicGptClient;
AnthropicGptClient.API_URL = 'https://api.anthropic.com';
AnthropicGptClient.REQUEST_TIMEOUT_MILLISECONDS = 120000;
AnthropicGptClient.API_KEY_HEADER_NAME = 'x-api-key';
AnthropicGptClient.API_VERSION_HEADER_NAME = 'anthropic-version';
AnthropicGptClient.API_VERSION_HEADER_VALUE = '2023-06-01';
AnthropicGptClient.CONTENT_TYPE_HEADER_VALUE = 'application/json';
AnthropicGptClient.MAX_TOKENS = 8192;
//# sourceMappingURL=AnthropicGptClient.js.map