donobu
Version:
Create browser automations with an LLM agent and replay them as Playwright scripts.
280 lines • 11.6 kB
JavaScript
;
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