Files
GoClaw/server/routers.ts

508 lines
17 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { COOKIE_NAME } from "@shared/const";
import { z } from "zod";
import { getSessionCookieOptions } from "./_core/cookies";
import { systemRouter } from "./_core/systemRouter";
import { publicProcedure, router, protectedProcedure } from "./_core/trpc";
import { checkOllamaHealth, listModels, chatCompletion } from "./ollama";
// Shared system user id for non-authenticated agent management
const SYSTEM_USER_ID = 1;
export const appRouter = router({
system: systemRouter,
auth: router({
me: publicProcedure.query(opts => opts.ctx.user),
logout: publicProcedure.mutation(({ ctx }) => {
const cookieOptions = getSessionCookieOptions(ctx.req);
ctx.res.clearCookie(COOKIE_NAME, { ...cookieOptions, maxAge: -1 });
return { success: true } as const;
}),
}),
/**
* Ollama API — серверный прокси для безопасного доступа
*/
ollama: router({
health: publicProcedure.query(async () => {
return checkOllamaHealth();
}),
models: publicProcedure.query(async () => {
try {
const result = await listModels();
return {
success: true as const,
models: result.data ?? [],
};
} catch (err: any) {
return {
success: false as const,
models: [],
error: err.message,
};
}
}),
chat: publicProcedure
.input(
z.object({
model: z.string(),
messages: z.array(
z.object({
role: z.enum(["system", "user", "assistant"]),
content: z.string(),
})
),
temperature: z.number().optional(),
max_tokens: z.number().optional(),
})
)
.mutation(async ({ input }) => {
try {
const result = await chatCompletion(input.model, input.messages, {
temperature: input.temperature,
max_tokens: input.max_tokens,
});
return {
success: true as const,
response: result.choices[0]?.message?.content ?? "",
model: result.model,
usage: result.usage,
};
} catch (err: any) {
return {
success: false as const,
response: "",
error: err.message,
};
}
}),
}),
/**
* Agents — управление AI-агентами (public — внутренний инструмент)
*/
agents: router({
list: publicProcedure.query(async () => {
const { getUserAgents } = await import("./agents");
return getUserAgents(SYSTEM_USER_ID);
}),
get: publicProcedure.input(z.object({ id: z.number() })).query(async ({ input }) => {
const { getAgentById } = await import("./agents");
return getAgentById(input.id);
}),
create: publicProcedure
.input(
z.object({
name: z.string().min(1),
description: z.string().optional(),
role: z.string(),
model: z.string(),
provider: z.string(),
temperature: z.number().min(0).max(2).default(0.7),
maxTokens: z.number().default(2048),
topP: z.number().min(0).max(1).default(1.0),
frequencyPenalty: z.number().min(-2).max(2).default(0.0),
presencePenalty: z.number().min(-2).max(2).default(0.0),
systemPrompt: z.string().optional(),
allowedTools: z.array(z.string()).default([]),
allowedDomains: z.array(z.string()).default([]),
tags: z.array(z.string()).default([]),
})
)
.mutation(async ({ input }) => {
const { createAgent } = await import("./agents");
return createAgent(SYSTEM_USER_ID, {
...input,
temperature: input.temperature.toString(),
topP: input.topP.toString(),
frequencyPenalty: input.frequencyPenalty.toString(),
presencePenalty: input.presencePenalty.toString(),
} as any);
}),
update: publicProcedure
.input(
z.object({
id: z.number(),
name: z.string().optional(),
description: z.string().optional(),
model: z.string().optional(),
provider: z.string().optional(),
temperature: z.number().min(0).max(2).optional(),
maxTokens: z.number().optional(),
topP: z.number().min(0).max(1).optional(),
frequencyPenalty: z.number().min(-2).max(2).optional(),
presencePenalty: z.number().min(-2).max(2).optional(),
systemPrompt: z.string().optional(),
allowedTools: z.array(z.string()).optional(),
allowedDomains: z.array(z.string()).optional(),
isActive: z.boolean().optional(),
tags: z.array(z.string()).optional(),
})
)
.mutation(async ({ input }) => {
const { updateAgent } = await import("./agents");
const { id, temperature, topP, frequencyPenalty, presencePenalty, ...rest } = input;
const updates: Record<string, any> = { ...rest };
if (temperature !== undefined) updates.temperature = temperature.toString();
if (topP !== undefined) updates.topP = topP.toString();
if (frequencyPenalty !== undefined) updates.frequencyPenalty = frequencyPenalty.toString();
if (presencePenalty !== undefined) updates.presencePenalty = presencePenalty.toString();
return updateAgent(id, updates as any);
}),
delete: publicProcedure.input(z.object({ id: z.number() })).mutation(async ({ input }) => {
const { deleteAgent } = await import("./agents");
return deleteAgent(input.id);
}),
stats: publicProcedure.input(z.object({ id: z.number(), hoursBack: z.number().default(24) })).query(async ({ input }) => {
const { getAgentStats } = await import("./agents");
return getAgentStats(input.id, input.hoursBack);
}),
metrics: publicProcedure.input(z.object({ id: z.number(), hoursBack: z.number().default(24) })).query(async ({ input }) => {
const { getAgentMetrics } = await import("./agents");
return getAgentMetrics(input.id, input.hoursBack);
}),
history: publicProcedure.input(z.object({ id: z.number(), limit: z.number().default(50) })).query(async ({ input }) => {
const { getAgentHistory } = await import("./agents");
return getAgentHistory(input.id, input.limit);
}),
accessControl: publicProcedure.input(z.object({ id: z.number() })).query(async ({ input }) => {
const { getAgentAccessControl } = await import("./agents");
return getAgentAccessControl(input.id);
}),
updateToolAccess: publicProcedure
.input(
z.object({
agentId: z.number(),
tool: z.string(),
isAllowed: z.boolean(),
maxExecutionsPerHour: z.number().optional(),
timeoutSeconds: z.number().optional(),
allowedPatterns: z.array(z.string()).optional(),
blockedPatterns: z.array(z.string()).optional(),
})
)
.mutation(async ({ input }) => {
const { updateToolAccess } = await import("./agents");
const { agentId, ...updates } = input;
return updateToolAccess(agentId, input.tool, updates);
}),
/**
* Chat with a specific agent using its configuration
*/
chat: publicProcedure
.input(
z.object({
agentId: z.number(),
message: z.string(),
conversationId: z.string().optional(),
})
)
.mutation(async ({ input }) => {
const { getAgentById, saveHistory } = await import("./agents");
const agent = await getAgentById(input.agentId);
if (!agent) {
return { success: false as const, response: "", error: "Agent not found" };
}
const messages: Array<{ role: "system" | "user" | "assistant"; content: string }> = [];
if (agent.systemPrompt) {
messages.push({ role: "system", content: agent.systemPrompt });
}
messages.push({ role: "user", content: input.message });
const startTime = Date.now();
try {
const result = await chatCompletion(
agent.model,
messages,
{
temperature: agent.temperature ? parseFloat(agent.temperature as string) : 0.7,
max_tokens: agent.maxTokens ?? 2048,
}
);
const processingTimeMs = Date.now() - startTime;
const response = result.choices[0]?.message?.content ?? "";
// Save to history
await saveHistory(input.agentId, {
userMessage: input.message,
agentResponse: response,
conversationId: input.conversationId,
status: "success",
});
return {
success: true as const,
response,
model: result.model,
usage: result.usage,
processingTimeMs,
};
} catch (err: any) {
await saveHistory(input.agentId, {
userMessage: input.message,
agentResponse: null,
conversationId: input.conversationId,
status: "error",
});
return {
success: false as const,
response: "",
error: err.message,
};
}
}),
}),
/**
* Tools — управление инструментами агентов
*/
tools: router({
list: publicProcedure.query(async () => {
const { getAllTools } = await import("./tools");
return getAllTools();
}),
execute: publicProcedure
.input(
z.object({
agentId: z.number(),
tool: z.string(),
params: z.record(z.string(), z.unknown()),
})
)
.mutation(async ({ input }) => {
const { executeTool } = await import("./tools");
return executeTool(input.agentId, input.tool, input.params);
}),
}),
/**
* Browser Agent — управление браузерными сессиями через Puppeteer
*/
browser: router({
createSession: publicProcedure
.input(z.object({ agentId: z.number() }))
.mutation(async ({ input }) => {
const { createBrowserSession } = await import("./browser-agent");
return createBrowserSession(input.agentId);
}),
execute: publicProcedure
.input(
z.object({
sessionId: z.string(),
action: z.object({
type: z.enum(["navigate", "click", "type", "extract", "screenshot", "scroll", "wait", "evaluate", "close"]),
params: z.record(z.string(), z.unknown()),
}),
})
)
.mutation(async ({ input }) => {
const { executeBrowserAction } = await import("./browser-agent");
return executeBrowserAction(input.sessionId, input.action as any);
}),
getSessions: publicProcedure
.input(z.object({ agentId: z.number() }))
.query(async ({ input }) => {
const { getAgentSessions } = await import("./browser-agent");
return getAgentSessions(input.agentId);
}),
closeSession: publicProcedure
.input(z.object({ sessionId: z.string() }))
.mutation(async ({ input }) => {
const { executeBrowserAction } = await import("./browser-agent");
return executeBrowserAction(input.sessionId, { type: "close", params: {} });
}),
closeAllSessions: publicProcedure
.input(z.object({ agentId: z.number() }))
.mutation(async ({ input }) => {
const { closeAllAgentSessions } = await import("./browser-agent");
await closeAllAgentSessions(input.agentId);
return { success: true };
}),
}),
/**
* Tool Builder — генерация и установка новых инструментов через LLM
*/
toolBuilder: router({
generate: publicProcedure
.input(
z.object({
name: z.string().min(1),
description: z.string().min(10),
category: z.string().optional(),
exampleInput: z.string().optional(),
exampleOutput: z.string().optional(),
dangerous: z.boolean().optional(),
})
)
.mutation(async ({ input }) => {
const { generateTool } = await import("./tool-builder");
return generateTool(input);
}),
install: publicProcedure
.input(
z.object({
toolId: z.string(),
name: z.string(),
description: z.string(),
category: z.string(),
dangerous: z.boolean(),
parameters: z.record(z.string(), z.object({
type: z.string(),
description: z.string(),
required: z.boolean().optional(),
})),
implementation: z.string(),
})
)
.mutation(async ({ input }) => {
const { installTool } = await import("./tool-builder");
return installTool(input);
}),
listCustom: publicProcedure.query(async () => {
const { getCustomTools } = await import("./tool-builder");
return getCustomTools();
}),
delete: publicProcedure
.input(z.object({ toolId: z.string() }))
.mutation(async ({ input }) => {
const { deleteTool } = await import("./tool-builder");
return deleteTool(input.toolId);
}),
test: publicProcedure
.input(
z.object({
toolId: z.string(),
params: z.record(z.string(), z.unknown()),
})
)
.mutation(async ({ input }) => {
const { testTool } = await import("./tool-builder");
return testTool(input.toolId, input.params);
}),
}),
/**
* Agent Compiler — компиляция агентов по ТЗ через LLM
*/
agentCompiler: router({
compile: publicProcedure
.input(
z.object({
specification: z.string().min(20),
name: z.string().optional(),
preferredProvider: z.string().optional(),
preferredModel: z.string().optional(),
})
)
.mutation(async ({ input }) => {
const { compileAgentConfig } = await import("./agent-compiler");
return compileAgentConfig({ ...input, userId: SYSTEM_USER_ID });
}),
deploy: publicProcedure
.input(
z.object({
config: z.object({
name: z.string(),
description: z.string(),
role: z.string(),
model: z.string(),
provider: z.string(),
temperature: z.number(),
maxTokens: z.number(),
topP: z.number(),
frequencyPenalty: z.number(),
presencePenalty: z.number(),
systemPrompt: z.string(),
allowedTools: z.array(z.string()),
allowedDomains: z.array(z.string()),
maxRequestsPerHour: z.number(),
tags: z.array(z.string()),
reasoning: z.string(),
}),
})
)
.mutation(async ({ input }) => {
const { deployCompiledAgent } = await import("./agent-compiler");
return deployCompiledAgent(input.config, SYSTEM_USER_ID);
}),
compileAndDeploy: publicProcedure
.input(
z.object({
specification: z.string().min(20),
name: z.string().optional(),
preferredProvider: z.string().optional(),
preferredModel: z.string().optional(),
})
)
.mutation(async ({ input }) => {
const { compileAndDeployAgent } = await import("./agent-compiler");
return compileAndDeployAgent({ ...input, userId: SYSTEM_USER_ID });
}),
}),
/**
* Orchestrator — main AI agent with tool-use loop
*/
orchestrator: router({
// Get orchestrator config from DB (model, systemPrompt, allowedTools)
getConfig: publicProcedure.query(async () => {
const { getOrchestratorConfig } = await import("./orchestrator");
return getOrchestratorConfig();
}),
chat: publicProcedure
.input(
z.object({
messages: z.array(
z.object({
role: z.enum(["user", "assistant", "system"]),
content: z.string(),
})
),
model: z.string().optional(), // override model (optional, uses DB config by default)
maxIterations: z.number().min(1).max(20).optional(),
})
)
.mutation(async ({ input }) => {
const { orchestratorChat } = await import("./orchestrator");
return orchestratorChat(
input.messages,
input.model, // undefined = use DB config model
input.maxIterations ?? 10
);
}),
tools: publicProcedure.query(async () => {
const { ORCHESTRATOR_TOOLS } = await import("./orchestrator");
return ORCHESTRATOR_TOOLS.map((t) => ({
name: t.function.name,
description: t.function.description,
parameters: t.function.parameters,
}));
}),
}),
});
export type AppRouter = typeof appRouter;