@robota-sdk/google
Version:
Google AI integration for Robota SDK - Gemini Pro, Gemini Flash, function calling, and tool integration with Google's Generative AI
346 lines (342 loc) • 10.5 kB
JavaScript
import '@google/generative-ai';
import { BaseAIProvider } from '@robota-sdk/core';
// src/provider.ts
// src/adapter.ts
var GoogleConversationAdapter = class {
/**
* Convert UniversalMessage array to Google AI message format
*/
static toGoogleFormat(messages) {
return messages.filter((msg) => msg.role !== "system").map((msg) => this.convertMessage(msg));
}
/**
* Convert a single UniversalMessage to Google AI format
*/
static convertMessage(msg) {
const messageRole = msg.role;
if (messageRole === "user") {
const userMsg = msg;
return {
role: "user",
parts: [{ text: userMsg.content }]
};
}
if (messageRole === "assistant") {
const assistantMsg = msg;
if (assistantMsg.toolCalls) {
const parts = [{ text: assistantMsg.content || "" }];
const toolCalls = assistantMsg.toolCalls;
for (const tc of toolCalls) {
parts.push({
functionCall: {
name: tc.function.name,
args: JSON.parse(tc.function.arguments)
}
});
}
return {
role: "model",
parts
};
}
return {
role: "model",
parts: [{ text: assistantMsg.content || "" }]
};
}
if (messageRole === "tool") {
const toolMsg = msg;
return {
role: "function",
parts: [
{
functionResponse: {
name: toolMsg.name || "unknown",
response: toolMsg.content
}
}
]
};
}
if (messageRole === "system") {
const systemMsg = msg;
return {
role: "user",
parts: [{ text: `[System]: ${systemMsg.content}` }]
};
}
const _exhaustiveCheck = msg;
return _exhaustiveCheck;
}
/**
* Extract system messages and combine them as system instruction
*/
static extractSystemInstruction(messages, fallbackSystemPrompt) {
const systemMessages = messages.filter((msg) => msg.role === "system");
if (systemMessages.length > 0) {
return systemMessages.map((msg) => msg.content).join("\n\n");
}
return fallbackSystemPrompt;
}
/**
* Complete message conversion pipeline
*/
static processMessages(messages, systemPrompt) {
const systemInstruction = this.extractSystemInstruction(messages, systemPrompt);
const contents = this.toGoogleFormat(messages);
return {
contents,
systemInstruction
};
}
};
// src/provider.ts
var GoogleProvider = class extends BaseAIProvider {
/**
* Provider identifier name
* @readonly
*/
name = "google";
/**
* Google AI client instance
* @internal
*/
client;
/**
* Provider configuration options
* @readonly
*/
options;
/**
* Create a new Google AI provider instance
*
* @param options - Configuration options for the Google provider
*
* @throws {Error} When client is not provided in options
*/
constructor(options) {
super();
this.options = {
temperature: 0.7,
maxTokens: void 0,
...options
};
if (!options.client) {
throw new Error("Google AI client is not injected. The client option is required.");
}
this.client = options.client;
}
/**
* Send a chat request to Google AI and receive a complete response
*
* @param model - Model name to use (e.g., 'gemini-1.5-pro', 'gemini-1.5-flash')
* @param context - Context object containing messages and system prompt
* @param options - Optional generation parameters and tools
* @returns Promise resolving to the model's response
*
* @throws {Error} When context is invalid
* @throws {Error} When messages array is invalid
* @throws {Error} When Google AI API call fails
*/
async chat(model, context, options) {
this.validateContext(context);
const { messages, systemPrompt } = context;
try {
const { contents, systemInstruction } = GoogleConversationAdapter.processMessages(
messages,
systemPrompt
);
const toolConfig = this.configureTools(options?.tools);
const modelConfig = {
model: model || this.options.model || "gemini-1.5-flash",
systemInstruction
};
if (toolConfig) {
modelConfig.tools = toolConfig.tools;
}
const generativeModel = this.client.getGenerativeModel(modelConfig);
const generationConfig = {
temperature: options?.temperature ?? this.options.temperature,
maxOutputTokens: options?.maxTokens ?? this.options.maxTokens,
...this.options.responseMimeType && {
responseMimeType: this.options.responseMimeType
},
...this.options.responseSchema && {
responseSchema: this.options.responseSchema
}
};
const result = await generativeModel.generateContent({
contents,
generationConfig
});
return this.parseResponse(result);
} catch (error) {
this.handleApiError(error, "chat");
}
}
/**
* Send a streaming chat request to Google AI and receive response chunks
*
* Generates an async iterator that yields response chunks as they arrive.
* Useful for real-time display of responses or handling large responses incrementally.
*
* @param model - Model name to use
* @param context - Context object containing messages and system prompt
* @param options - Optional generation parameters and tools
* @returns Async generator yielding response chunks
*
* @throws {Error} When context is invalid
* @throws {Error} When messages array is invalid
* @throws {Error} When Google AI API streaming call fails
*/
async *chatStream(model, context, options) {
this.validateContext(context);
const { messages, systemPrompt } = context;
try {
const { contents, systemInstruction } = GoogleConversationAdapter.processMessages(
messages,
systemPrompt
);
const toolConfig = this.configureTools(options?.tools);
const modelConfig = {
model: model || this.options.model || "gemini-1.5-flash",
systemInstruction
};
if (toolConfig) {
modelConfig.tools = toolConfig.tools;
}
const generativeModel = this.client.getGenerativeModel(modelConfig);
const generationConfig = {
temperature: options?.temperature ?? this.options.temperature,
maxOutputTokens: options?.maxTokens ?? this.options.maxTokens,
...this.options.responseMimeType && {
responseMimeType: this.options.responseMimeType
},
...this.options.responseSchema && {
responseSchema: this.options.responseSchema
}
};
const result = await generativeModel.generateContentStream({
contents,
generationConfig
});
for await (const chunk of result.stream) {
yield this.parseStreamingChunk(chunk);
}
} catch (error) {
this.handleApiError(error, "chatStream");
}
}
/**
* Configure tools for Google AI API request
*
* Google AI supports function calling with Gemini models.
* Transforms function schemas into Google AI tool format.
*
* @param tools - Array of function schemas
* @returns Google AI tool configuration object or undefined
*/
configureTools(tools) {
if (!tools || !Array.isArray(tools)) {
return void 0;
}
return {
tools: [{
functionDeclarations: tools.map((fn) => ({
name: fn.name,
description: fn.description || "",
parameters: fn.parameters
}))
}]
};
}
/**
* Parse Google AI response into universal ModelResponse format
*
* Extracts content, usage information, and metadata from the Google AI response
* and converts it to the standard format used across all providers.
* Supports function calling with Gemini models.
*
* @param response - Raw response from Google AI API
* @returns Parsed model response in universal format
*
* @internal
*/
parseResponse(response) {
let content = "";
const toolCalls = [];
const candidate = response.response?.candidates?.[0];
if (candidate?.content?.parts) {
for (const part of candidate.content.parts) {
if (part.text) {
content += part.text;
} else if (part.functionCall) {
toolCalls.push({
id: `call_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
type: "function",
function: {
name: part.functionCall.name,
arguments: JSON.stringify(part.functionCall.args || {})
}
});
}
}
}
const usageMetadata = response.response?.usageMetadata;
const usage = usageMetadata ? {
promptTokens: usageMetadata.promptTokenCount || 0,
completionTokens: usageMetadata.candidatesTokenCount || 0,
totalTokens: usageMetadata.totalTokenCount || 0
} : {
promptTokens: 0,
completionTokens: 0,
totalTokens: 0
};
const result = {
content: content || void 0,
usage,
metadata: {
model: response.response?.model,
finishReason: candidate?.finishReason,
safetyRatings: candidate?.safetyRatings
}
};
if (toolCalls.length > 0) {
result.toolCalls = toolCalls;
}
return result;
}
/**
* Parse Google AI streaming response chunk into universal format
*
* Converts individual chunks from the streaming response into the standard
* StreamingResponseChunk format used across all providers.
*
* @param chunk - Raw chunk from Google AI streaming API
* @returns Parsed streaming response chunk
*
* @internal
*/
parseStreamingChunk(chunk) {
const text = chunk.text() || "";
const candidate = chunk.candidates?.[0];
const isComplete = candidate?.finishReason !== void 0 && candidate.finishReason !== null;
return {
content: text,
isComplete
};
}
/**
* Release resources and close connections
*
* Performs cleanup operations when the provider is no longer needed.
* Google AI client doesn't require explicit cleanup, so this is a no-op.
*
* @returns Promise that resolves when cleanup is complete
*/
async close() {
}
};
export { GoogleConversationAdapter, GoogleProvider };
//# sourceMappingURL=index.js.map
//# sourceMappingURL=index.js.map