import { OpenRouter, tool } from '@openrouter/agent';
import type { ConversationState, StateAccessor } from '@openrouter/agent';
import { z } from 'zod';
// 1. Define a tool with approval required
const sendEmailTool = tool({
name: 'send_email',
description: 'Send an email',
inputSchema: z.object({
to: z.string().email(),
subject: z.string(),
body: z.string(),
}),
outputSchema: z.object({ sent: z.boolean(), messageId: z.string() }),
requireApproval: true,
execute: async (params) => {
const result = await sendEmail(params);
return { sent: true, messageId: result.id };
},
});
// 2. Create a state accessor (in-memory for this example)
const store = new Map<string, ConversationState>();
const conversationId = 'conv-123';
const state: StateAccessor = {
load: async () => store.get(conversationId) ?? null,
save: async (s) => { store.set(conversationId, s); },
};
const openrouter = new OpenRouter({ apiKey: process.env.OPENROUTER_API_KEY });
// 3. First callModel — model will try to call the tool
const result = openrouter.callModel({
model: 'openai/gpt-4o',
input: 'Send a welcome email to alice@example.com',
tools: [sendEmailTool] as const,
state,
});
// 4. Check if approval is needed
if (await result.requiresApproval()) {
const pending = await result.getPendingToolCalls();
for (const call of pending) {
console.log(`Tool: ${call.name}`);
console.log(`To: ${call.arguments.to}`);
console.log(`Subject: ${call.arguments.subject}`);
console.log(`ID: ${call.id}`);
}
// 5. Present to user for decision, then resume
const approved = await askUserForApproval(pending);
const approvedIds = approved.filter(a => a.decision === 'approve').map(a => a.id);
const rejectedIds = approved.filter(a => a.decision === 'reject').map(a => a.id);
// 6. Second callModel — resume with approval decisions
const resumed = openrouter.callModel({
model: 'openai/gpt-4o',
input: [], // No new user input needed for resumption
tools: [sendEmailTool] as const,
state,
approveToolCalls: approvedIds,
rejectToolCalls: rejectedIds,
});
// 7. Get the final response
const text = await resumed.getText();
console.log(text);
// "I've sent the welcome email to alice@example.com."
} else {
// No approval needed — tool ran automatically
const text = await result.getText();
console.log(text);
}