/**
* User Tools
*
* Tools that access user information (require OAuth context).
*/
import { z } from 'zod';
import type { ToolDefinition } from './types';
import { listConversations } from '../lib/memory';
import { createTokenManager } from '../lib/token-manager';
import type { Env } from '../types';
/**
* Get authenticated user's Google account information
*/
export const getUserInfoTool: ToolDefinition<Record<string, never>> = {
name: 'get_user_info',
description: 'Get the authenticated user\'s Google account information.',
schema: {},
handler: async (_args, context) => {
try {
if (!context?.props) {
return {
content: [{ type: 'text', text: 'Not authenticated. Please connect your Google account.' }],
isError: true,
};
}
const { email, name, picture } = context.props;
return {
content: [{
type: 'text',
text: `**User Info**\n\n- Name: ${name}\n- Email: ${email}\n- Picture: ${picture || 'Not available'}`,
}],
};
} catch (error) {
const message = error instanceof Error ? error.message : 'Unknown error';
return {
content: [{ type: 'text', text: `Error: ${message}` }],
isError: true,
};
}
},
metadata: {
category: 'query',
tags: ['google-api', 'user'],
defer_loading: true,
requiresAuth: 'google',
authScopes: ['openid', 'email', 'profile'],
},
};
/**
* List the authenticated user's conversation history
*/
export const listMyConversationsTool: ToolDefinition<{
limit: z.ZodOptional<z.ZodNumber>;
}> = {
name: 'list_my_conversations',
description: 'List your conversation history. Returns recent conversations you can resume with ask_agent.',
schema: {
limit: z.number().min(1).max(50).optional().describe('Max conversations to return (1-50, default: 10)'),
},
handler: async ({ limit }, context) => {
try {
// Check for database access
if (!context?.env?.DB || context.env.ENABLE_CONVERSATION_MEMORY !== 'true') {
return {
content: [{ type: 'text', text: 'Conversation memory is not enabled on this server.' }],
isError: true,
};
}
// Require OAuth authentication
const userEmail = context.props?.email;
if (!userEmail) {
return {
content: [{ type: 'text', text: 'OAuth authentication required to list conversations.' }],
isError: true,
};
}
const conversations = await listConversations(
context.env.DB as D1Database,
userEmail,
limit ?? 10
);
if (conversations.length === 0) {
return {
content: [{ type: 'text', text: 'No conversations found. Start a new conversation with ask_agent.' }],
};
}
const result = {
count: conversations.length,
conversations: conversations.map(c => ({
id: c.id,
messageCount: c.messageCount,
updatedAt: new Date(c.updatedAt * 1000).toISOString(),
})),
};
return {
content: [{
type: 'text',
text: `**Your Conversations** (${conversations.length})\n\n${conversations.map(c =>
`- **${c.id.slice(0, 8)}...** - ${c.messageCount} messages, updated ${new Date(c.updatedAt * 1000).toLocaleDateString()}`
).join('\n')}\n\n_Use the conversation_id with ask_agent to resume._`,
}],
};
} catch (error) {
const message = error instanceof Error ? error.message : 'Unknown error';
return {
content: [{ type: 'text', text: `Error: ${message}` }],
isError: true,
};
}
},
metadata: {
category: 'query',
tags: ['conversation', 'memory', 'history'],
requiresAuth: 'google',
},
};
/**
* List all connected accounts for the authenticated user
*
* Shows all OAuth connections (Google, Microsoft, GitHub) with their aliases,
* display names, scopes, and expiry status. Useful for multi-account management.
*/
export const listConnectedAccountsTool: ToolDefinition<Record<string, never>> = {
name: 'list_connected_accounts',
description: 'List all OAuth accounts connected to your profile (Google, Microsoft, GitHub). Shows aliases, email addresses, scopes, and expiry status for each connection.',
schema: {},
handler: async (_args, context) => {
try {
if (!context?.props?.email) {
return {
content: [{ type: 'text', text: 'Not authenticated. Please connect your account first.' }],
isError: true,
};
}
// Need TOKEN_KV and TOKEN_ENCRYPTION_KEY from env
const env = context.env as Env | undefined;
if (!env?.TOKEN_KV || !env?.TOKEN_ENCRYPTION_KEY) {
return {
content: [{ type: 'text', text: 'Token storage not configured on this server.' }],
isError: true,
};
}
const tokens = createTokenManager(env);
const userId = context.props.email; // Use email as userId
const accounts = await tokens.list({ userId });
if (accounts.length === 0) {
return {
content: [{
type: 'text',
text: '**No Connected Accounts**\n\nYou haven\'t connected any OAuth accounts yet. Use the authentication flow to connect Google, Microsoft, or GitHub.',
}],
};
}
// Format accounts nicely
const formatted = accounts.map(acc => {
const status = acc.expiresAt
? (acc.expiresAt > Date.now() ? '✅ Active' : '⚠️ Expired')
: '✅ Active (no expiry)';
const alias = acc.alias !== 'default' ? ` (${acc.alias})` : '';
const displayName = acc.displayName ? ` - ${acc.displayName}` : '';
const scopes = acc.scopes.length > 0
? `\n Scopes: ${acc.scopes.slice(0, 3).join(', ')}${acc.scopes.length > 3 ? ` (+${acc.scopes.length - 3} more)` : ''}`
: '';
const connected = `\n Connected: ${new Date(acc.connectedAt).toLocaleDateString()}`;
return `- **${acc.provider}${alias}**${displayName}\n Status: ${status}${scopes}${connected}`;
}).join('\n\n');
return {
content: [{
type: 'text',
text: `**Connected Accounts** (${accounts.length})\n\n${formatted}`,
}],
};
} catch (error) {
const message = error instanceof Error ? error.message : 'Unknown error';
return {
content: [{ type: 'text', text: `Error listing accounts: ${message}` }],
isError: true,
};
}
},
metadata: {
category: 'query',
tags: ['oauth', 'accounts', 'user', 'multi-account'],
alwaysVisible: true, // Users should always see this
requiresAuth: 'google', // Must be authenticated
},
};
/**
* All user tools
*/
export const userTools: ToolDefinition[] = [
getUserInfoTool,
listMyConversationsTool,
listConnectedAccountsTool,
];