@hyperbrowser/agent
Version:
Hyperbrowsers Web Agent
199 lines (196 loc) • 7.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.convertToOpenAIJsonSchema = convertToOpenAIJsonSchema;
exports.convertToAnthropicTool = convertToAnthropicTool;
exports.createAnthropicToolChoice = createAnthropicToolChoice;
exports.convertActionsToAnthropicTools = convertActionsToAnthropicTools;
exports.convertToGeminiResponseSchema = convertToGeminiResponseSchema;
const zod_1 = require("zod");
/**
* Utility functions for converting Zod schemas to provider-specific formats
*/
function convertToOpenAIJsonSchema(schema) {
const jsonSchema = zod_1.z.toJSONSchema(schema, {
target: "draft-7",
io: "output",
});
return {
type: "json_schema",
json_schema: {
name: "structured_output",
strict: true,
schema: jsonSchema,
},
};
}
const THOUGHTS_DESCRIPTION = "Your reasoning about the current state and what needs to be done next based on the task goal and previous actions.";
const MEMORY_DESCRIPTION = "A summary of successful actions completed so far and key state changes (e.g., 'Clicked login button -> login form appeared').";
/**
* Convert a simple Zod schema to an Anthropic tool (for non-agent use cases)
* Wraps the schema in a "result" field for consistent parsing
*/
function convertToAnthropicTool(schema) {
const jsonSchema = zod_1.z.toJSONSchema(schema, {
target: "draft-7",
io: "output",
});
return {
name: "structured_output",
description: "Generate structured output according to the provided schema",
input_schema: {
type: "object",
properties: {
result: jsonSchema,
},
required: ["result"],
},
};
}
/**
* Create tool choice object for Anthropic
*/
function createAnthropicToolChoice(toolName) {
return {
type: "tool",
name: toolName,
};
}
function convertActionsToAnthropicTools(actions) {
return actions.map((action) => {
const paramsSchema = zod_1.z.toJSONSchema(action.actionParams, {
target: "draft-4",
io: "output",
});
// Create enhanced description with structure example
const baseDescription = action.toolDescription ?? action.actionParams.description;
const enhancedDescription = `${baseDescription}
IMPORTANT: Response must have this exact structure:
{
"thoughts": "your reasoning",
"memory": "summary of actions",
"action": {
"type": "${action.type}",
"params": { ...action parameters here... }
}
}
Do NOT put params directly at root level. They MUST be nested inside action.params.`;
return {
name: action.toolName ?? action.type,
description: enhancedDescription,
input_schema: {
type: "object",
additionalProperties: false,
properties: {
thoughts: {
type: "string",
description: THOUGHTS_DESCRIPTION,
},
memory: {
type: "string",
description: MEMORY_DESCRIPTION,
},
action: {
type: "object",
description: `The action object. MUST contain 'type' field set to "${action.type}" and 'params' field with the action parameters.`,
additionalProperties: false,
properties: {
type: {
type: "string",
const: action.type,
description: `Must be exactly "${action.type}"`,
},
params: {
...paramsSchema,
description: `Parameters for the ${action.type} action. These must be nested here, not at the root level.`,
},
},
required: ["type", "params"],
},
},
required: ["thoughts", "memory", "action"],
},
};
});
}
/**
* Convert Zod schema to Gemini's OpenAPI 3.0 Schema format
* Gemini requires: uppercase types, propertyOrdering, no empty objects
*/
function convertToGeminiResponseSchema(schema) {
const jsonSchema = zod_1.z.toJSONSchema(schema, {
target: "draft-7",
io: "output",
});
return convertJsonSchemaToGemini(jsonSchema);
}
/**
* Recursively convert JSON Schema to Gemini's OpenAPI 3.0 format
*/
function convertJsonSchemaToGemini(jsonSchema) {
const result = {};
// Map JSON Schema type to Gemini type (uppercase)
if (jsonSchema.type) {
const type = jsonSchema.type;
result.type = type.toUpperCase();
}
// Handle object properties
if (jsonSchema.properties && typeof jsonSchema.properties === "object") {
const properties = jsonSchema.properties;
// If properties is empty, Gemini rejects it - skip the entire object by returning null placeholder
if (Object.keys(properties).length === 0) {
return {
type: "OBJECT",
properties: {
_placeholder: {
type: "STRING",
description: "Empty object placeholder",
nullable: true,
},
},
propertyOrdering: ["_placeholder"],
required: [],
};
}
const convertedProps = {};
for (const [key, value] of Object.entries(properties)) {
convertedProps[key] = convertJsonSchemaToGemini(value);
}
result.properties = convertedProps;
result.propertyOrdering = Object.keys(properties);
}
// Handle array items
if (jsonSchema.items) {
result.items = convertJsonSchemaToGemini(jsonSchema.items);
}
// Handle union types (anyOf, oneOf)
if (jsonSchema.anyOf && Array.isArray(jsonSchema.anyOf)) {
result.anyOf = jsonSchema.anyOf.map((schema) => convertJsonSchemaToGemini(schema));
}
if (jsonSchema.oneOf && Array.isArray(jsonSchema.oneOf)) {
result.oneOf = jsonSchema.oneOf.map((schema) => convertJsonSchemaToGemini(schema));
}
// Pass through supported fields
if (jsonSchema.required)
result.required = jsonSchema.required;
if (jsonSchema.description)
result.description = jsonSchema.description;
if (jsonSchema.enum)
result.enum = jsonSchema.enum;
// Convert JSON Schema "const" to "enum" for Gemini
if (jsonSchema.const !== undefined) {
result.enum = [jsonSchema.const];
}
if (jsonSchema.format)
result.format = jsonSchema.format;
if (jsonSchema.minimum !== undefined)
result.minimum = jsonSchema.minimum;
if (jsonSchema.maximum !== undefined)
result.maximum = jsonSchema.maximum;
if (jsonSchema.minItems !== undefined)
result.minItems = jsonSchema.minItems;
if (jsonSchema.maxItems !== undefined)
result.maxItems = jsonSchema.maxItems;
if (jsonSchema.nullable !== undefined)
result.nullable = jsonSchema.nullable;
return result;
}