mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-05 06:42:12 +03:00
Implements transparent memory and skill injection at the proxy layer, enabling AI agents connecting through OmniRoute to automatically inherit memory and tool capabilities without client-side code changes. Memory System: - SQLite-backed memory store with 4 types: factual, episodic, procedural, semantic - Token budget retrieval with configurable max tokens (default 2000) - Memory injection into chat requests (system message + message prefix) - Memory fact extraction from responses - Dashboard page: /dashboard/memory - MCP tools: omniroute_memory_search, omniroute_memory_add, omniroute_memory_clear Skills System: - Skill registry with semver resolution (^, ~, >=, etc.) - Docker sandbox runner with resource constraints (CPU 100ms, RAM 256MB) - Skill executor with timeout handling - Built-in skills: file_read, file_write, http_request, web_search, eval_code, execute_command - Skill schema injection for OpenAI, Claude, Gemini formats - Tool call interception and execution - Dashboard page: /dashboard/skills - MCP tools: omniroute_skills_list, omniroute_skills_enable, omniroute_skills_execute Advanced Features: - Browser automation skill (Playwright-based) - A2A memory-aware routing skill - Hybrid execution mode (direct/sandbox/auto-upgrade) - Custom skill registration API - Integration tests - Memory caching layer - Memory summarization - Performance benchmarks Resolves: GitHub Issue #812
136 lines
3.1 KiB
TypeScript
136 lines
3.1 KiB
TypeScript
import { skillExecutor } from "./executor";
|
|
import { detectProvider } from "./injection";
|
|
|
|
interface ToolCall {
|
|
id: string;
|
|
name: string;
|
|
arguments: Record<string, unknown>;
|
|
}
|
|
|
|
interface ExecutionContext {
|
|
apiKeyId: string;
|
|
sessionId: string;
|
|
requestId: string;
|
|
}
|
|
|
|
export async function interceptToolCalls(
|
|
toolCalls: ToolCall[],
|
|
context: ExecutionContext
|
|
): Promise<{ id: string; result: unknown }[]> {
|
|
const results = await Promise.all(
|
|
toolCalls.map(async (call) => {
|
|
try {
|
|
const [name, version] = call.name.includes("@")
|
|
? call.name.split("@")
|
|
: [call.name, "latest"];
|
|
|
|
const skillName = version === "latest" ? name : `${name}@${version}`;
|
|
|
|
const execution = await skillExecutor.execute(skillName, call.arguments, {
|
|
apiKeyId: context.apiKeyId,
|
|
sessionId: context.sessionId,
|
|
});
|
|
|
|
return {
|
|
id: call.id,
|
|
result: execution.output,
|
|
};
|
|
} catch (err) {
|
|
return {
|
|
id: call.id,
|
|
result: { error: err instanceof Error ? err.message : String(err) },
|
|
};
|
|
}
|
|
})
|
|
);
|
|
|
|
return results;
|
|
}
|
|
|
|
export function extractToolCalls(response: any, modelId: string): ToolCall[] {
|
|
const provider = detectProvider(modelId);
|
|
|
|
switch (provider) {
|
|
case "openai":
|
|
return (response.tool_calls || []).map((tc: any) => ({
|
|
id: tc.id || `call_${Date.now()}`,
|
|
name: tc.function?.name || "",
|
|
arguments: parseArguments(tc.function?.arguments || "{}"),
|
|
}));
|
|
|
|
case "anthropic":
|
|
return (response.content || [])
|
|
.filter((c: any) => c.type === "tool_use")
|
|
.map((tc: any) => ({
|
|
id: tc.id,
|
|
name: tc.name,
|
|
arguments: tc.input || {},
|
|
}));
|
|
|
|
case "google":
|
|
return (response.functionCalls || []).map((fc: any) => ({
|
|
id: `call_${Date.now()}_${Math.random().toString(36).slice(2)}`,
|
|
name: fc.name,
|
|
arguments: fc.args || {},
|
|
}));
|
|
|
|
default:
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function parseArguments(args: string | Record<string, unknown>): Record<string, unknown> {
|
|
if (typeof args === "object") {
|
|
return args;
|
|
}
|
|
|
|
try {
|
|
return JSON.parse(args);
|
|
} catch {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
export async function handleToolCallExecution(
|
|
response: any,
|
|
modelId: string,
|
|
context: ExecutionContext
|
|
): Promise<any> {
|
|
const toolCalls = extractToolCalls(response, modelId);
|
|
|
|
if (toolCalls.length === 0) {
|
|
return response;
|
|
}
|
|
|
|
const results = await interceptToolCalls(toolCalls, context);
|
|
|
|
const provider = detectProvider(modelId);
|
|
|
|
switch (provider) {
|
|
case "openai":
|
|
return {
|
|
...response,
|
|
tool_results: results.map((r) => ({
|
|
tool_call_id: r.id,
|
|
output: JSON.stringify(r.result),
|
|
})),
|
|
};
|
|
|
|
case "anthropic":
|
|
return {
|
|
...response,
|
|
content: [
|
|
...response.content,
|
|
...results.map((r) => ({
|
|
type: "tool_result",
|
|
tool_use_id: r.id,
|
|
content: JSON.stringify(r.result),
|
|
})),
|
|
],
|
|
};
|
|
|
|
default:
|
|
return response;
|
|
}
|
|
}
|