UNPKG

donobu

Version:

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

280 lines 11.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.VercelAiGptClient = void 0; const ai_1 = require("ai"); 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 Logger_1 = require("../utils/Logger"); const MiscUtils_1 = require("../utils/MiscUtils"); const GptClient_1 = require("./GptClient"); /** * A GPT client implemented using the Vercel AI SDK. */ class VercelAiGptClient extends GptClient_1.GptClient { /** * Create a new instance. */ constructor(model) { super({ type: 'VERCEL_AI', provider: model.provider, modelName: model.modelId, }); this.model = model; } async ping(options) { try { const abortSignal = options?.signal || AbortSignal.timeout(VercelAiGptClient.REQUEST_TIMEOUT_MILLISECONDS); await (0, ai_1.generateText)({ model: this.model, prompt: 'ping', temperature: 0, abortSignal, experimental_telemetry: { isEnabled: false }, // This is as stupid as it looks. Google will throw server errors if they // do not like your maxTokens value, so for Google, and only Google, we // just say nothing about it. ...(this.model.provider === 'google.generative-ai' ? {} : { maxOutputTokens: 1, }), }); } catch (error) { if (error instanceof Error && error.message.includes('not found')) { throw new GptModelNotFoundException_1.GptModelNotFoundException(this.config.type, this.model.modelId); } else { throw await this.mapErrorToDonobuException(error); } } } async getMessage(messages, options) { try { const abortSignal = options?.signal || AbortSignal.timeout(VercelAiGptClient.REQUEST_TIMEOUT_MILLISECONDS); const coreMessages = this.convertToCoreMessages(messages); const result = await (0, ai_1.generateText)({ model: this.model, messages: coreMessages, temperature: 0, abortSignal, experimental_telemetry: { isEnabled: false }, }); return { type: 'assistant', text: result.text, promptTokensUsed: result.usage.inputTokens ?? 0, completionTokensUsed: result.usage.outputTokens ?? 0, }; } catch (error) { throw await this.mapErrorToDonobuException(error); } } async getStructuredOutput(messages, jsonSchema, options) { try { const abortSignal = options?.signal || AbortSignal.timeout(VercelAiGptClient.REQUEST_TIMEOUT_MILLISECONDS); const coreMessages = this.convertToCoreMessages(messages); const result = await (0, ai_1.generateText)({ model: this.model, messages: coreMessages, temperature: 0, output: ai_1.Output.object({ schema: jsonSchema, }), abortSignal, experimental_telemetry: { isEnabled: false }, }); return { type: 'structured_output', output: result.output, promptTokensUsed: result.usage.inputTokens ?? 0, completionTokensUsed: result.usage.outputTokens ?? 0, }; } catch (error) { throw await this.mapErrorToDonobuException(error); } } async getToolCalls(messages, tools, options) { try { const abortSignal = options?.signal || AbortSignal.timeout(VercelAiGptClient.REQUEST_TIMEOUT_MILLISECONDS); const coreMessages = this.convertToCoreMessages(messages); // Convert ToolOption to Vercel AI SDK Tool format const vercelTools = {}; for (const t of tools) { vercelTools[t.name] = (0, ai_1.tool)({ inputSchema: t.inputSchema, description: t.description, }); } // Generate text with tool calls const result = await (0, ai_1.generateText)({ model: this.model, messages: coreMessages, tools: vercelTools, temperature: 0, toolChoice: 'required', abortSignal, experimental_telemetry: { isEnabled: false }, }); // Map the tool calls to the expected format const proposedCalls = result.toolCalls.map((tc) => { return { name: tc.toolName, parameters: tc.input, toolCallId: tc.toolCallId, }; }); return { type: 'proposed_tool_calls', proposedToolCalls: proposedCalls, promptTokensUsed: result.usage.inputTokens ?? 0, completionTokensUsed: result.usage.outputTokens ?? 0, }; } catch (error) { throw await this.mapErrorToDonobuException(error); } } /** * Convert GptMessage array to CoreMessage array for Vercel AI SDK. * Merges adjacent `tool_call_result` messages into a single `CoreToolMessage`. */ convertToCoreMessages(messages) { const coreMessages = []; let i = 0; while (i < messages.length) { const msg = messages[i]; if (msg.type === 'tool_call_result') { const toolResults = []; // Aggregate all subsequent tool_call_result messages. while (i < messages.length) { const maybeToolCallResult = messages[i]; if (maybeToolCallResult.type === 'tool_call_result') { toolResults.push({ type: 'tool-result', toolCallId: maybeToolCallResult.toolCallId, toolName: maybeToolCallResult.toolName, output: { type: 'text', value: maybeToolCallResult.data, }, }); } else { // Done aggregating adjacent tool_call_result messages. break; } i++; // Move to the next message. } coreMessages.push({ role: 'tool', content: toolResults, }); continue; // Continue to the next iteration of the outer loop } let coreMessage; const msgType = msg.type; if (msgType === 'system') { coreMessage = { role: 'system', content: msg.text, }; } else if (msgType === 'user') { const content = msg.items.length === 1 && msg.items[0].type === 'text' ? msg.items[0].text // If it's just text, use string shorthand : msg.items.map((item) => { if (item.type === 'text') { return { type: 'text', text: item.text, }; } else if ('bytes' in item) { const imageType = MiscUtils_1.MiscUtils.detectImageType(item.bytes); const mimeType = `image/${imageType}`; return { type: 'image', image: item.bytes, mediaType: mimeType, }; } throw new Error(`Unsupported user message item type: ${JSON.stringify(item)}`); }); coreMessage = { role: 'user', content, }; } else if (msgType === 'assistant') { coreMessage = { role: 'assistant', content: msg.text, }; } else if (msgType === 'proposed_tool_calls') { coreMessage = { role: 'assistant', content: msg.proposedToolCalls.map((tc) => ({ type: 'tool-call', toolCallId: tc.toolCallId || '', toolName: tc.name, input: tc.parameters, })), }; } else if (msgType === 'structured_output') { coreMessage = { role: 'assistant', content: JSON.stringify(msg.output, null, 2), }; } else { throw new Error(`Unsupported message type: ${msgType}`); } coreMessages.push(coreMessage); i++; } return coreMessages; } /** * Maps an error to a DonobuException */ async mapErrorToDonobuException(error) { Logger_1.appLogger.error(`Vercel AI SDK error: ${error}`); if (error instanceof Error) { const message = error.message; // Check for authentication errors if (message.includes('authentication') || message.includes('unauthorized') || message.includes('invalid_grant')) { return new GptPlatformAuthenticationFailedException_1.GptPlatformAuthenticationFailedException(this.model.provider, message); } // Check for quota / payment errors if (message.includes('402') || message.includes('quota')) { return new GptPlatformInsufficientQuotaException_1.GptPlatformInsufficientQuotaException(this.config.type); } // Check for connectivity errors if (message.includes('network') || message.includes('connection')) { return new GptPlatformNotReachableException_1.GptPlatformNotReachableException(this.config.type); } } // Default to internal error return new GptPlatformInternalErrorException_1.GptPlatformInternalErrorException(error instanceof Error ? error.message : 'Unknown error with Vercel AI SDK'); } } exports.VercelAiGptClient = VercelAiGptClient; VercelAiGptClient.REQUEST_TIMEOUT_MILLISECONDS = 120000; //# sourceMappingURL=VercelAiGptClient.js.map