@xynehq/jaf
Version:
Juspay Agent Framework - A purely functional agent framework with immutable state and composable tools
436 lines • 15.2 kB
JavaScript
/**
* JAF ADK Layer - Type Converters
*
* Functional type conversion between ADK and Core formats
*/
import { z } from 'zod';
import { PartType, ContentRole, Model, ToolParameterType } from '../types.js';
// ========== Content Conversions ==========
export const convertAdkContentToCoreMessage = (content) => {
// Extract text content
const textParts = content.parts.filter(p => String(p.type) === 'text');
const textContent = textParts.map(p => p.text || '').join(' ').trim();
// Extract function calls
const functionCallParts = content.parts.filter(p => String(p.type) === 'function_call');
const toolCalls = functionCallParts
.map(p => p.functionCall)
.filter(fc => fc !== undefined)
.map(fc => ({
id: fc.id,
type: 'function',
function: {
name: fc.name,
arguments: JSON.stringify(fc.args)
}
}));
// Extract function responses
const functionResponseParts = content.parts.filter(p => String(p.type) === 'function_response');
const functionResponse = functionResponseParts.length > 0 ? functionResponseParts[0].functionResponse : undefined;
// Determine role - function responses should always be 'tool' role
const role = functionResponse ? 'tool' : convertAdkRoleToCoreRole(content.role);
const message = {
role: role,
content: textContent
};
if (toolCalls.length > 0) {
message.tool_calls = toolCalls;
}
if (functionResponse) {
message.tool_call_id = functionResponse.id;
message.content = typeof functionResponse.response === 'string'
? functionResponse.response
: JSON.stringify(functionResponse.response);
}
return message;
};
export const convertCoreMessageToAdkContent = (message) => {
const parts = [];
// Add text content
if (message.content && message.content.trim()) {
parts.push({
type: PartType.TEXT,
text: message.content
});
}
// Add tool calls
if (message.tool_calls) {
for (const toolCall of message.tool_calls) {
parts.push({
type: PartType.FUNCTION_CALL,
functionCall: {
id: toolCall.id,
name: toolCall.function.name,
args: safeJsonParse(toolCall.function.arguments)
}
});
}
}
// Add tool responses
if (message.tool_call_id && message.role === 'tool') {
parts.push({
type: PartType.FUNCTION_RESPONSE,
functionResponse: {
id: message.tool_call_id,
name: 'unknown', // Core doesn't track function name in responses
response: safeJsonParse(message.content || ''),
success: true
}
});
}
return {
role: convertCoreRoleToAdkRole(message.role),
parts,
metadata: {}
};
};
// ========== Role Conversions ==========
export const convertAdkRoleToCoreRole = (adkRole) => {
switch (adkRole) {
case ContentRole.USER:
case 'user':
return 'user';
case ContentRole.MODEL:
case 'model':
return 'assistant';
case ContentRole.SYSTEM:
case 'system':
return 'system';
default:
return 'user';
}
};
export const convertCoreRoleToAdkRole = (coreRole) => {
switch (coreRole) {
case 'user':
return ContentRole.USER;
case 'assistant':
return ContentRole.MODEL;
case 'system':
return ContentRole.SYSTEM;
case 'tool':
return ContentRole.MODEL; // Tool responses become model content
default:
return ContentRole.USER;
}
};
// ========== Session Conversions ==========
export const convertAdkSessionToCoreState = (session, newMessage) => {
const messages = session.messages.map(convertAdkContentToCoreMessage);
if (newMessage) {
messages.push(convertAdkContentToCoreMessage(newMessage));
}
return {
runId: session.id, // Type conversion needed
traceId: session.id, // Type conversion needed
messages,
currentAgentName: 'default', // ADK doesn't have explicit agent names in sessions
context: {
userId: session.userId,
sessionId: session.id,
artifacts: session.artifacts,
...session.metadata.properties
},
turnCount: session.messages.length
};
};
export const convertCoreStateToAdkSession = (state) => {
return {
id: String(state.runId),
appName: state.context?.appName || 'default',
userId: state.context?.userId || 'unknown',
messages: state.messages.map(convertCoreMessageToAdkContent),
artifacts: state.context?.artifacts || {},
metadata: {
created: new Date(),
lastAccessed: new Date(),
properties: {
turnCount: state.turnCount,
currentAgentName: state.currentAgentName
}
}
};
};
// ========== Agent Conversions ==========
export const convertAdkAgentToCoreAgent = (adkAgent) => {
return {
name: adkAgent.config.name,
instructions: (state) => adkAgent.config.instruction,
tools: adkAgent.config.tools?.map(convertAdkToolToCoreTool) || [],
modelConfig: {
name: convertAdkModelToCoreModel(adkAgent.config.model),
temperature: 0.7, // Default, can be overridden
maxTokens: 2000 // Default, can be overridden
},
handoffs: [],
outputCodec: adkAgent.config.outputSchema ? createZodFromAdkSchema(adkAgent.config.outputSchema) : undefined
};
};
export const convertCoreAgentToAdkAgent = (coreAgent) => {
return {
id: coreAgent.name,
config: {
name: coreAgent.name,
model: convertCoreModelToAdkModel(coreAgent.modelConfig?.name || 'gpt-4o'),
instruction: typeof coreAgent.instructions === 'function'
? coreAgent.instructions({})
: String(coreAgent.instructions),
description: `Agent: ${coreAgent.name}`,
tools: coreAgent.tools?.map(convertCoreToolToAdkTool) || [],
subAgents: []
},
metadata: {
created: new Date(),
version: '1.0.0'
}
};
};
// ========== Tool Conversions ==========
export const convertAdkToolToCoreTool = (adkTool) => {
return {
schema: {
name: adkTool.name,
description: adkTool.description,
parameters: createZodFromAdkParameters(adkTool.parameters)
},
execute: async (params, context) => {
// Convert Core context to ADK context
const adkContext = convertCoreContextToAdkContext(context);
const result = await adkTool.execute(params, adkContext);
// Ensure result is string or ToolResult format
if (typeof result === 'string') {
return result;
}
// Convert ADK ToolResult to Core format if needed
return result;
}
};
};
export const convertCoreToolToAdkTool = (coreTool) => {
return {
name: coreTool.schema.name,
description: coreTool.schema.description,
parameters: convertZodToAdkParameters(coreTool.schema.parameters),
execute: async (params, context) => {
// Convert ADK context to Core context
const coreContext = convertAdkContextToCoreContext(context);
return await coreTool.execute(params, coreContext);
},
metadata: {
source: 'function',
version: '1.0.0'
}
};
};
// ========== Model Conversions ==========
export const convertAdkModelToCoreModel = (adkModel) => {
// Map specific model enum values to their Core equivalents
switch (adkModel) {
case Model.GEMINI_2_0_FLASH:
case 'gemini-2.0-flash':
return 'gemini-2.0-flash';
case Model.GEMINI_1_5_PRO:
case 'gemini-1.5-pro':
return 'gemini-1.5-pro';
case Model.GEMINI_1_5_FLASH:
case 'gemini-1.5-flash':
return 'gemini-1.5-flash';
case Model.GPT_4_TURBO:
case 'gpt-4-turbo':
return 'gpt-4-turbo';
case Model.GPT_4:
case 'gpt-4':
return 'gpt-4';
case Model.GPT_3_5_TURBO:
case 'gpt-3.5-turbo':
return 'gpt-3.5-turbo';
case Model.CLAUDE_3_OPUS_20240229:
case 'claude-3-opus-20240229':
return 'claude-3-opus';
case Model.CLAUDE_3_5_SONNET_LATEST:
case 'claude-3-5-sonnet-latest':
return 'claude-3-sonnet';
case Model.CLAUDE_3_HAIKU_20240307:
case 'claude-3-haiku-20240307':
return 'claude-3-haiku';
case Model.CUSTOM:
case 'custom':
return 'gpt-4o';
default:
// For any other string model, return as-is
if (typeof adkModel === 'string') {
return adkModel;
}
return 'gpt-4o';
}
};
export const convertCoreModelToAdkModel = (coreModel) => {
switch (coreModel) {
case 'gemini-2.0-flash':
return Model.GEMINI_2_0_FLASH;
case 'gemini-1.5-pro':
return Model.GEMINI_1_5_PRO;
case 'gemini-1.5-flash':
return Model.GEMINI_1_5_FLASH;
case 'gpt-4-turbo':
return Model.GPT_4_TURBO;
case 'gpt-4':
return Model.GPT_4;
case 'gpt-3.5-turbo':
return Model.GPT_3_5_TURBO;
case 'claude-3-opus':
case 'claude-3-opus-20240229':
return Model.CLAUDE_3_OPUS_20240229;
case 'claude-3-sonnet':
case 'claude-3-5-sonnet':
case 'claude-3-5-sonnet-latest':
return Model.CLAUDE_3_5_SONNET_LATEST;
case 'claude-3-haiku':
case 'claude-3-haiku-20240307':
return Model.CLAUDE_3_HAIKU_20240307;
default:
return Model.CUSTOM;
}
};
// ========== Parameter Schema Conversions ==========
export const createZodFromAdkParameters = (parameters) => {
const shape = {};
for (const param of parameters) {
let schema = createZodFromAdkParameter(param);
if (!param.required) {
schema = schema.optional();
}
shape[param.name] = schema;
}
return z.object(shape);
};
export const createZodFromAdkParameter = (param) => {
switch (param.type) {
case ToolParameterType.STRING:
case 'string':
if (param.enum) {
return z.enum(param.enum);
}
return z.string().describe(param.description);
case ToolParameterType.NUMBER:
case 'number':
return z.number().describe(param.description);
case ToolParameterType.BOOLEAN:
case 'boolean':
return z.boolean().describe(param.description);
case ToolParameterType.ARRAY:
case 'array':
if (param.items) {
return z.array(createZodFromAdkParameter(param.items)).describe(param.description);
}
return z.array(z.any()).describe(param.description);
case ToolParameterType.OBJECT:
case 'object':
if (param.properties) {
const shape = {};
for (const [key, value] of Object.entries(param.properties)) {
shape[key] = createZodFromAdkParameter(value);
}
return z.object(shape).describe(param.description);
}
return z.object({}).describe(param.description);
default:
return z.any().describe(param.description);
}
};
export const convertZodToAdkParameters = (zodSchema) => {
// This is a simplified conversion - in practice, you'd need more sophisticated Zod introspection
// For now, return a basic parameter structure
return [{
name: 'input',
type: ToolParameterType.OBJECT,
description: 'Tool input parameters',
required: true
}];
};
export const createZodFromAdkSchema = (adkSchema) => {
// Simple wrapper around ADK schema validation
return z.any().refine((data) => {
const result = adkSchema.validate(data);
return result.success;
});
};
// ========== Context Conversions ==========
export const convertCoreContextToAdkContext = (coreContext) => {
return {
agent: coreContext.agent ? convertCoreAgentToAdkAgent(coreContext.agent) : undefined,
session: coreContext.session ? convertCoreStateToAdkSession(coreContext.session) : undefined,
message: coreContext.message ? convertCoreMessageToAdkContent(coreContext.message) : undefined,
actions: coreContext.actions,
metadata: coreContext.metadata
};
};
export const convertAdkContextToCoreContext = (adkContext) => {
return {
agent: adkContext.agent ? convertAdkAgentToCoreAgent(adkContext.agent) : undefined,
session: adkContext.session ? convertAdkSessionToCoreState(adkContext.session) : undefined,
message: adkContext.message ? convertAdkContentToCoreMessage(adkContext.message) : undefined,
actions: adkContext.actions,
metadata: adkContext.metadata
};
};
// ========== Utility Functions ==========
export const safeJsonParse = (jsonString) => {
try {
return JSON.parse(jsonString);
}
catch {
return jsonString; // Return as string if parsing fails
}
};
export const safeJsonStringify = (obj) => {
try {
return JSON.stringify(obj);
}
catch {
return String(obj);
}
};
// ========== Function Call Utilities ==========
export const extractFunctionCallsFromAdkContent = (content) => {
return content.parts
.filter(p => String(p.type) === 'function_call')
.map(p => p.functionCall)
.filter(fc => fc !== undefined);
};
export const extractFunctionResponsesFromAdkContent = (content) => {
return content.parts
.filter(p => String(p.type) === 'function_response')
.map(p => p.functionResponse)
.filter(fr => fr !== undefined);
};
export const createAdkContentWithText = (text, role = ContentRole.MODEL) => {
return {
role,
parts: [{
type: PartType.TEXT,
text
}],
metadata: {}
};
};
export const createAdkContentWithFunctionCall = (functionCall, role = ContentRole.MODEL) => {
return {
role,
parts: [{
type: PartType.FUNCTION_CALL,
functionCall
}],
metadata: {}
};
};
export const createAdkContentWithFunctionResponse = (functionResponse, role = ContentRole.MODEL) => {
return {
role,
parts: [{
type: PartType.FUNCTION_RESPONSE,
functionResponse
}],
metadata: {}
};
};
//# sourceMappingURL=type-converters.js.map