mirror of https://github.com/kortix-ai/suna.git
557 lines
17 KiB
TypeScript
557 lines
17 KiB
TypeScript
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
import { createClient } from '@/lib/supabase/client';
|
|
|
|
const API_URL = process.env.NEXT_PUBLIC_BACKEND_URL || '';
|
|
|
|
export interface MCPCredential {
|
|
credential_id: string;
|
|
mcp_qualified_name: string;
|
|
display_name: string;
|
|
config_keys: string[];
|
|
is_active: boolean;
|
|
last_used_at?: string;
|
|
created_at: string;
|
|
updated_at: string;
|
|
}
|
|
|
|
export interface StoreCredentialRequest {
|
|
mcp_qualified_name: string;
|
|
display_name: string;
|
|
config: Record<string, any>;
|
|
}
|
|
|
|
export interface TestCredentialResponse {
|
|
success: boolean;
|
|
message: string;
|
|
error_details?: string;
|
|
}
|
|
|
|
export interface UsageExampleMessage {
|
|
role: 'user' | 'assistant';
|
|
content: string;
|
|
tool_calls?: Array<{
|
|
name: string;
|
|
arguments?: Record<string, any>;
|
|
}>;
|
|
}
|
|
|
|
export interface AgentTemplate {
|
|
template_id: string;
|
|
creator_id: string;
|
|
name: string;
|
|
description?: string;
|
|
system_prompt?: string;
|
|
mcp_requirements: MCPRequirement[];
|
|
agentpress_tools: Record<string, any>;
|
|
tags: string[];
|
|
is_public: boolean;
|
|
download_count: number;
|
|
marketplace_published_at?: string;
|
|
created_at: string;
|
|
creator_name?: string;
|
|
icon_name?: string;
|
|
icon_color?: string;
|
|
icon_background?: string;
|
|
is_kortix_team?: boolean;
|
|
usage_examples?: UsageExampleMessage[];
|
|
metadata?: {
|
|
source_agent_id?: string;
|
|
source_version_id?: string;
|
|
source_version_name?: string;
|
|
model?: string;
|
|
};
|
|
config?: {
|
|
triggers?: Array<{
|
|
name: string;
|
|
description?: string;
|
|
trigger_type: string;
|
|
is_active: boolean;
|
|
config: Record<string, any>;
|
|
}>;
|
|
};
|
|
}
|
|
|
|
export interface MCPRequirement {
|
|
qualified_name: string;
|
|
display_name: string;
|
|
enabled_tools: string[];
|
|
required_config: string[];
|
|
custom_type?: 'sse' | 'http'; // For custom MCP servers
|
|
}
|
|
|
|
export interface InstallTemplateRequest {
|
|
template_id: string;
|
|
instance_name?: string;
|
|
custom_system_prompt?: string;
|
|
profile_mappings?: Record<string, string>;
|
|
custom_mcp_configs?: Record<string, Record<string, any>>;
|
|
trigger_configs?: Record<string, Record<string, any>>;
|
|
}
|
|
|
|
export interface InstallationResponse {
|
|
status: 'installed' | 'configs_required';
|
|
instance_id?: string;
|
|
missing_regular_credentials?: {
|
|
qualified_name: string;
|
|
display_name: string;
|
|
required_config: string[];
|
|
}[];
|
|
missing_custom_configs?: {
|
|
qualified_name: string;
|
|
display_name: string;
|
|
custom_type: string;
|
|
required_config: string[];
|
|
}[];
|
|
template?: {
|
|
template_id: string;
|
|
name: string;
|
|
description?: string;
|
|
};
|
|
}
|
|
|
|
export interface CreateTemplateRequest {
|
|
agent_id: string;
|
|
make_public?: boolean;
|
|
tags?: string[];
|
|
usage_examples?: UsageExampleMessage[];
|
|
}
|
|
|
|
// =====================================================
|
|
// CREDENTIAL MANAGEMENT HOOKS
|
|
// =====================================================
|
|
|
|
export function useUserCredentials() {
|
|
return useQuery({
|
|
queryKey: ['secure-mcp', 'credentials'],
|
|
queryFn: async (): Promise<MCPCredential[]> => {
|
|
const supabase = createClient();
|
|
const { data: { session } } = await supabase.auth.getSession();
|
|
|
|
if (!session) {
|
|
throw new Error('You must be logged in to view credentials');
|
|
}
|
|
|
|
const response = await fetch(`${API_URL}/secure-mcp/credentials`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${session.access_token}`,
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({ message: 'Unknown error' }));
|
|
throw new Error(errorData.message || `HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
return response.json();
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useStoreCredential() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: async (request: StoreCredentialRequest): Promise<MCPCredential> => {
|
|
const supabase = createClient();
|
|
const { data: { session } } = await supabase.auth.getSession();
|
|
|
|
if (!session) {
|
|
throw new Error('You must be logged in to store credentials');
|
|
}
|
|
|
|
const response = await fetch(`${API_URL}/secure-mcp/credentials`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${session.access_token}`,
|
|
},
|
|
body: JSON.stringify(request),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({ message: 'Unknown error' }));
|
|
throw new Error(errorData.message || `HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
return response.json();
|
|
},
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['secure-mcp', 'credentials'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useDeleteCredential() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: async (mcp_qualified_name: string): Promise<void> => {
|
|
const supabase = createClient();
|
|
const { data: { session } } = await supabase.auth.getSession();
|
|
|
|
if (!session) {
|
|
throw new Error('You must be logged in to delete credentials');
|
|
}
|
|
|
|
const response = await fetch(`${API_URL}/secure-mcp/credentials/${encodeURIComponent(mcp_qualified_name)}`, {
|
|
method: 'DELETE',
|
|
headers: {
|
|
'Authorization': `Bearer ${session.access_token}`,
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({ message: 'Unknown error' }));
|
|
throw new Error(errorData.message || `HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
},
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['secure-mcp', 'credentials'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export interface MarketplacePaginationInfo {
|
|
current_page: number;
|
|
page_size: number;
|
|
total_items: number;
|
|
total_pages: number;
|
|
has_next: boolean;
|
|
has_previous: boolean;
|
|
}
|
|
|
|
export interface MarketplaceTemplatesResponse {
|
|
templates: AgentTemplate[];
|
|
pagination: MarketplacePaginationInfo;
|
|
}
|
|
|
|
export function useMarketplaceTemplates(params?: {
|
|
page?: number;
|
|
limit?: number;
|
|
search?: string;
|
|
tags?: string;
|
|
is_kortix_team?: boolean;
|
|
mine?: boolean;
|
|
sort_by?: string;
|
|
sort_order?: string;
|
|
}) {
|
|
return useQuery({
|
|
queryKey: ['secure-mcp', 'marketplace-templates', params],
|
|
queryFn: async (): Promise<MarketplaceTemplatesResponse> => {
|
|
const supabase = createClient();
|
|
const { data: { session } } = await supabase.auth.getSession();
|
|
|
|
if (!session) {
|
|
throw new Error('You must be logged in to view marketplace templates');
|
|
}
|
|
|
|
const searchParams = new URLSearchParams();
|
|
if (params?.page) searchParams.set('page', params.page.toString());
|
|
if (params?.limit) searchParams.set('limit', params.limit.toString());
|
|
if (params?.search) searchParams.set('search', params.search);
|
|
if (params?.tags) searchParams.set('tags', params.tags);
|
|
if (params?.is_kortix_team !== undefined) searchParams.set('is_kortix_team', params.is_kortix_team.toString());
|
|
if (params?.mine !== undefined) searchParams.set('mine', params.mine.toString());
|
|
if (params?.sort_by) searchParams.set('sort_by', params.sort_by);
|
|
if (params?.sort_order) searchParams.set('sort_order', params.sort_order);
|
|
|
|
const response = await fetch(`${API_URL}/templates/marketplace?${searchParams}`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${session.access_token}`,
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({ message: 'Unknown error' }));
|
|
throw new Error(errorData.message || `HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
return response.json();
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useTemplateDetails(template_id: string) {
|
|
return useQuery({
|
|
queryKey: ['secure-mcp', 'template', template_id],
|
|
queryFn: async (): Promise<AgentTemplate> => {
|
|
const supabase = createClient();
|
|
const { data: { session } } = await supabase.auth.getSession();
|
|
|
|
if (!session) {
|
|
throw new Error('You must be logged in to view template details');
|
|
}
|
|
|
|
const response = await fetch(`${API_URL}/templates/${template_id}`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${session.access_token}`,
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({ message: 'Unknown error' }));
|
|
throw new Error(errorData.message || `HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
return response.json();
|
|
},
|
|
enabled: !!template_id,
|
|
});
|
|
}
|
|
|
|
export function useCreateTemplate() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: async (request: CreateTemplateRequest): Promise<{ template_id: string; message: string }> => {
|
|
const supabase = createClient();
|
|
const { data: { session } } = await supabase.auth.getSession();
|
|
|
|
if (!session) {
|
|
throw new Error('You must be logged in to create templates');
|
|
}
|
|
|
|
const response = await fetch(`${API_URL}/templates`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${session.access_token}`,
|
|
},
|
|
body: JSON.stringify(request),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({ message: 'Unknown error' }));
|
|
throw new Error(errorData.message || `HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
return response.json();
|
|
},
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['secure-mcp', 'marketplace-templates'] });
|
|
queryClient.invalidateQueries({ queryKey: ['secure-mcp', 'my-templates'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useMyTemplates(params?: {
|
|
page?: number;
|
|
limit?: number;
|
|
search?: string;
|
|
sort_by?: string;
|
|
sort_order?: string;
|
|
}) {
|
|
return useQuery({
|
|
queryKey: ['secure-mcp', 'my-templates', params],
|
|
queryFn: async (): Promise<MarketplaceTemplatesResponse> => {
|
|
const supabase = createClient();
|
|
const { data: { session } } = await supabase.auth.getSession();
|
|
|
|
if (!session) {
|
|
throw new Error('You must be logged in to view your templates');
|
|
}
|
|
|
|
const searchParams = new URLSearchParams();
|
|
if (params?.page) searchParams.set('page', params.page.toString());
|
|
if (params?.limit) searchParams.set('limit', params.limit.toString());
|
|
if (params?.search) searchParams.set('search', params.search);
|
|
if (params?.sort_by) searchParams.set('sort_by', params.sort_by);
|
|
if (params?.sort_order) searchParams.set('sort_order', params.sort_order);
|
|
|
|
const response = await fetch(`${API_URL}/templates/my?${searchParams}`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${session.access_token}`,
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({ message: 'Unknown error' }));
|
|
throw new Error(errorData.message || `HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
return response.json();
|
|
},
|
|
});
|
|
}
|
|
|
|
export function usePublishTemplate() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: async ({
|
|
template_id,
|
|
tags,
|
|
usage_examples
|
|
}: {
|
|
template_id: string;
|
|
tags?: string[];
|
|
usage_examples?: UsageExampleMessage[];
|
|
}): Promise<{ message: string }> => {
|
|
const supabase = createClient();
|
|
const { data: { session } } = await supabase.auth.getSession();
|
|
|
|
if (!session) {
|
|
throw new Error('You must be logged in to publish templates');
|
|
}
|
|
|
|
const response = await fetch(`${API_URL}/templates/${template_id}/publish`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${session.access_token}`,
|
|
},
|
|
body: JSON.stringify({ tags, usage_examples }),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({ message: 'Unknown error' }));
|
|
throw new Error(errorData.message || `HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
return response.json();
|
|
},
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['secure-mcp', 'marketplace-templates'] });
|
|
queryClient.invalidateQueries({ queryKey: ['secure-mcp', 'my-templates'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useUnpublishTemplate() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: async (template_id: string): Promise<{ message: string }> => {
|
|
const supabase = createClient();
|
|
const { data: { session } } = await supabase.auth.getSession();
|
|
|
|
if (!session) {
|
|
throw new Error('You must be logged in to unpublish templates');
|
|
}
|
|
|
|
const response = await fetch(`${API_URL}/templates/${template_id}/unpublish`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${session.access_token}`,
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({ message: 'Unknown error' }));
|
|
throw new Error(errorData.message || `HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
return response.json();
|
|
},
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['secure-mcp', 'marketplace-templates'] });
|
|
queryClient.invalidateQueries({ queryKey: ['secure-mcp', 'my-templates'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useDeleteTemplate() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: async (template_id: string): Promise<{ message: string }> => {
|
|
const supabase = createClient();
|
|
const { data: { session } } = await supabase.auth.getSession();
|
|
|
|
if (!session) {
|
|
throw new Error('You must be logged in to delete templates');
|
|
}
|
|
|
|
const response = await fetch(`${API_URL}/templates/${template_id}`, {
|
|
method: 'DELETE',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${session.access_token}`,
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({ message: 'Unknown error' }));
|
|
throw new Error(errorData.message || `HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
return response.json();
|
|
},
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['secure-mcp', 'marketplace-templates'] });
|
|
queryClient.invalidateQueries({ queryKey: ['secure-mcp', 'my-templates'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useKortixTeamTemplates() {
|
|
return useQuery({
|
|
queryKey: ['secure-mcp', 'kortix-templates-all'],
|
|
queryFn: async (): Promise<MarketplaceTemplatesResponse> => {
|
|
const supabase = createClient();
|
|
const { data: { session } } = await supabase.auth.getSession();
|
|
|
|
if (!session) {
|
|
throw new Error('You must be logged in to view Kortix templates');
|
|
}
|
|
|
|
const response = await fetch(`${API_URL}/templates/kortix-all`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${session.access_token}`,
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({ message: 'Unknown error' }));
|
|
throw new Error(errorData.message || `HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
return response.json();
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useInstallTemplate() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: async (request: InstallTemplateRequest): Promise<InstallationResponse> => {
|
|
const supabase = createClient();
|
|
const { data: { session } } = await supabase.auth.getSession();
|
|
if (!session) {
|
|
throw new Error('You must be logged in to install templates');
|
|
}
|
|
const response = await fetch(`${API_URL}/templates/install`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${session.access_token}`,
|
|
},
|
|
body: JSON.stringify(request),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({ message: 'Unknown error' }));
|
|
const isAgentLimitError = (response.status === 402) && (
|
|
errorData.error_code === 'AGENT_LIMIT_EXCEEDED' ||
|
|
errorData.detail?.error_code === 'AGENT_LIMIT_EXCEEDED'
|
|
);
|
|
|
|
if (isAgentLimitError) {
|
|
const { AgentCountLimitError } = await import('@/lib/api');
|
|
const errorDetail = errorData.detail || errorData;
|
|
throw new AgentCountLimitError(response.status, errorDetail);
|
|
}
|
|
|
|
throw new Error(errorData.message || `HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
return response.json();
|
|
},
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['agents'] });
|
|
queryClient.invalidateQueries({ queryKey: ['marketplace-templates'] });
|
|
queryClient.invalidateQueries({ queryKey: ['templates'] });
|
|
},
|
|
});
|
|
}
|