Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,5 @@ yarn-error.log*
*.tsbuildinfo

# idea files
.idea
.idea
.trigger
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-popover": "^1.1.14",
"@radix-ui/react-portal": "^1.1.9",
"@radix-ui/react-progress": "^1.1.7",
"@radix-ui/react-scroll-area": "^1.2.9",
"@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slider": "^1.3.5",
Expand All @@ -56,6 +57,8 @@
"@t3-oss/env-nextjs": "^0.12.0",
"@tanstack/react-query": "^5.69.0",
"@tanstack/react-table": "^8.21.3",
"@trigger.dev/react-hooks": "^3.3.17",
"@trigger.dev/sdk": "^3.3.17",
"@trpc/client": "^11.0.0",
"@trpc/react-query": "^11.0.0",
"@trpc/server": "^11.0.0",
Expand Down Expand Up @@ -102,6 +105,7 @@
"devDependencies": {
"@eslint/eslintrc": "^3.3.1",
"@tailwindcss/postcss": "^4.0.15",
"@trigger.dev/build": "^3.3.17",
"@types/node": "^20.14.10",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
Expand Down
998 changes: 990 additions & 8 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

30 changes: 30 additions & 0 deletions prisma/migrations/20250728150706_add_agent_jobs/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
-- CreateTable
CREATE TABLE "AgentJob" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"triggerJobId" TEXT NOT NULL,
"prompt" TEXT NOT NULL,
"model" TEXT NOT NULL,
"toolkits" TEXT[],
"systemPrompt" TEXT,
"status" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"completedAt" TIMESTAMP(3),
"error" TEXT,
"costInCents" INTEGER,
"durationMs" INTEGER,

CONSTRAINT "AgentJob_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "AgentJob_triggerJobId_key" ON "AgentJob"("triggerJobId");

-- CreateIndex
CREATE INDEX "AgentJob_userId_idx" ON "AgentJob"("userId");

-- CreateIndex
CREATE INDEX "AgentJob_triggerJobId_idx" ON "AgentJob"("triggerJobId");

-- AddForeignKey
ALTER TABLE "AgentJob" ADD CONSTRAINT "AgentJob_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
21 changes: 21 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ model User {
images Image[]
userFeatures UserFeature[]
workbenches Workbench[]
agentJobs AgentJob[]
}

enum UserRole {
Expand Down Expand Up @@ -180,4 +181,24 @@ model Tool {
toolkit Toolkit @relation(fields: [toolkitId], references: [id], onDelete: Cascade)

@@id([id, toolkitId])
}

model AgentJob {
id String @id @default(uuid())
userId String
triggerJobId String @unique // The Trigger.dev job/run ID
prompt String @db.Text
model String
toolkits String[] // Array of toolkit IDs used
systemPrompt String? @db.Text
status String // QUEUED, EXECUTING, COMPLETED, FAILED, CANCELED
createdAt DateTime @default(now())
completedAt DateTime?
error String? @db.Text
costInCents Int?
durationMs Int?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)

@@index([userId])
@@index([triggerJobId])
}
189 changes: 189 additions & 0 deletions src/ai/agent-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import { openai } from "@ai-sdk/openai";
import { getServerToolkit } from "@/toolkits/toolkits/server";
import { createServerOnlyCaller } from "@/server/api/root";
import { Toolkits } from "@/toolkits/toolkits/shared";
import { createCoreAgentConfig, type SimpleToolkit, type CoreAgentConfig } from "@/ai/agent-core";

export interface AgentConfigInput {
toolkits: Array<{ id: Toolkits; parameters: Record<string, any> }>;
selectedChatModel: string;
useNativeSearch?: boolean;
systemPrompt?: string;
baseSystemPrompt?: string;
}

export interface AgentConfig extends CoreAgentConfig {}

export async function createAgentConfig({
toolkits,
selectedChatModel,
useNativeSearch = false,
systemPrompt,
baseSystemPrompt,
}: AgentConfigInput): Promise<AgentConfig> {
// Convert server toolkits to simple toolkits
const simpleToolkits: SimpleToolkit[] = await Promise.all(
toolkits.map(async ({ id, parameters }) => {
const toolkit = getServerToolkit(id);
const serverTools = await toolkit.tools(parameters);

const tools = Object.keys(serverTools).map((toolName) => {
const serverTool = serverTools[toolName as keyof typeof serverTools];
return {
name: toolName,
description: serverTool.description,
inputSchema: serverTool.inputSchema,
execute: async (args: any) => {
try {
const result = await serverTool.callback(args);

// Increment tool usage on successful execution
try {
const serverCaller = await createServerOnlyCaller();
await serverCaller.tools.incrementToolUsageServer({
toolkit: id,
tool: toolName,
});
} catch (error) {
console.error("Failed to increment tool usage:", error);
}

if (serverTool.message) {
return {
result,
message:
typeof serverTool.message === "function"
? serverTool.message(result)
: serverTool.message,
};
} else {
return {
result,
};
}
} catch (error) {
console.error(error);
return {
isError: true,
result: {
error:
error instanceof Error
? error.message
: "An error occurred while executing the tool",
},
};
}
},
};
});

return {
id,
systemPrompt: toolkit.systemPrompt,
tools,
};
})
);

// Use core agent config
const coreConfig = createCoreAgentConfig({
toolkits: simpleToolkits,
selectedChatModel,
useNativeSearch,
systemPrompt,
baseSystemPrompt,
});

// Add OpenAI native search if enabled
const isOpenAi = selectedChatModel.startsWith("openai");
if (isOpenAi && useNativeSearch) {
coreConfig.tools.web_search_preview = openai.tools.webSearchPreview();
}

return coreConfig;
}

/**
* Runtime-specific agent config that doesn't track tool usage
* This is designed for use in background tasks/workers where Next.js request context is not available
*/
export async function createRuntimeAgentConfig({
toolkits,
selectedChatModel,
useNativeSearch = false,
systemPrompt,
baseSystemPrompt,
}: AgentConfigInput): Promise<AgentConfig> {
// Convert server toolkits to simple toolkits without usage tracking
const simpleToolkits: SimpleToolkit[] = await Promise.all(
toolkits.map(async ({ id, parameters }) => {
const toolkit = getServerToolkit(id);
const serverTools = await toolkit.tools(parameters);

const tools = Object.keys(serverTools).map((toolName) => {
const serverTool = serverTools[toolName as keyof typeof serverTools];
return {
name: toolName,
description: serverTool.description,
inputSchema: serverTool.inputSchema,
execute: async (args: any) => {
try {
const result = await serverTool.callback(args);

// Skip tool usage tracking in runtime context
// This avoids Next.js context errors when running in background tasks

if (serverTool.message) {
return {
result,
message:
typeof serverTool.message === "function"
? serverTool.message(result)
: serverTool.message,
};
} else {
return {
result,
};
}
} catch (error) {
console.error(error);
return {
isError: true,
result: {
error:
error instanceof Error
? error.message
: "An error occurred while executing the tool",
},
};
}
},
};
});

return {
id,
systemPrompt: toolkit.systemPrompt,
tools,
};
})
);

// Use core agent config
const coreConfig = createCoreAgentConfig({
toolkits: simpleToolkits,
selectedChatModel,
useNativeSearch,
systemPrompt,
baseSystemPrompt,
});

// Add OpenAI native search if enabled
const isOpenAi = selectedChatModel.startsWith("openai");
if (isOpenAi && useNativeSearch) {
coreConfig.tools.web_search_preview = openai.tools.webSearchPreview();
}

return coreConfig;
}
83 changes: 83 additions & 0 deletions src/ai/agent-core.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { tool, type Tool } from "ai";
import { generateUUID } from "@/lib/utils";

// Lightweight agent configuration without server dependencies
export interface CoreAgentConfig {
tools: Record<string, Tool>;
systemPrompt: string;
selectedChatModel: string;
useNativeSearch: boolean;
maxSteps: number;
toolCallStreaming: boolean;
experimental_generateMessageId: () => string;
}

// Default opinionated system prompt
const DEFAULT_BASE_SYSTEM_PROMPT = `You are a helpful assistant. The current date and time is ${new Date().toLocaleString()}. Whenever you are asked to write code, you must include a language with \`\`\``;

// Default AI SDK configuration
const DEFAULT_AGENT_CONFIG = {
maxSteps: 15,
toolCallStreaming: true,
experimental_generateMessageId: generateUUID,
} as const;

export interface SimpleTool {
name: string;
description: string;
inputSchema: any;
execute: (args: any) => Promise<any>;
}

export interface SimpleToolkit {
id: string;
systemPrompt: string;
tools: SimpleTool[];
}

export function createCoreAgentConfig({
toolkits,
selectedChatModel,
useNativeSearch = false,
systemPrompt,
baseSystemPrompt = DEFAULT_BASE_SYSTEM_PROMPT,
}: {
toolkits: SimpleToolkit[];
selectedChatModel: string;
useNativeSearch?: boolean;
systemPrompt?: string;
baseSystemPrompt?: string;
}): CoreAgentConfig {
// Transform simple toolkits into AI SDK tools
const tools: Record<string, Tool> = {};

for (const toolkit of toolkits) {
for (const simpleTool of toolkit.tools) {
const toolName = `${toolkit.id}_${simpleTool.name}`;
tools[toolName] = tool({
description: simpleTool.description,
parameters: simpleTool.inputSchema,
execute: simpleTool.execute,
});
}
}

// Collect toolkit system prompts
const toolkitSystemPrompts = toolkits.map(toolkit => toolkit.systemPrompt);

// Build comprehensive system prompt
const toolkitInstructions =
toolkitSystemPrompts.length > 0
? `\n\n## Available Toolkits\n\nYou have access to the following toolkits and their capabilities:\n\n${toolkitSystemPrompts.join("\n\n---\n\n")}\n\n${systemPrompt ?? ""}`
: "";

const fullSystemPrompt = baseSystemPrompt + toolkitInstructions;

return {
tools,
systemPrompt: fullSystemPrompt,
selectedChatModel,
useNativeSearch,
...DEFAULT_AGENT_CONFIG,
};
}
Loading