UNPKG

donobu

Version:

Create browser automations with an LLM agent and replay them as Playwright scripts.

305 lines 13.2 kB
"use strict"; 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