@theihtisham/ai-agent-starter-kit 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/.env.example +33 -0
  2. package/Dockerfile +35 -0
  3. package/LICENSE +21 -0
  4. package/README.md +73 -0
  5. package/docker-compose.yml +28 -0
  6. package/next-env.d.ts +5 -0
  7. package/next.config.mjs +17 -0
  8. package/package.json +85 -0
  9. package/postcss.config.js +6 -0
  10. package/prisma/schema.prisma +157 -0
  11. package/prisma/seed.ts +46 -0
  12. package/src/app/(auth)/forgot-password/page.tsx +56 -0
  13. package/src/app/(auth)/layout.tsx +7 -0
  14. package/src/app/(auth)/login/page.tsx +83 -0
  15. package/src/app/(auth)/signup/page.tsx +108 -0
  16. package/src/app/(dashboard)/agents/[id]/edit/page.tsx +68 -0
  17. package/src/app/(dashboard)/agents/[id]/page.tsx +114 -0
  18. package/src/app/(dashboard)/agents/new/page.tsx +43 -0
  19. package/src/app/(dashboard)/agents/page.tsx +63 -0
  20. package/src/app/(dashboard)/api-keys/page.tsx +139 -0
  21. package/src/app/(dashboard)/dashboard/page.tsx +79 -0
  22. package/src/app/(dashboard)/layout.tsx +16 -0
  23. package/src/app/(dashboard)/settings/billing/page.tsx +59 -0
  24. package/src/app/(dashboard)/settings/page.tsx +45 -0
  25. package/src/app/(dashboard)/usage/page.tsx +46 -0
  26. package/src/app/api/agents/[id]/chat/route.ts +100 -0
  27. package/src/app/api/agents/[id]/chats/route.ts +36 -0
  28. package/src/app/api/agents/[id]/route.ts +97 -0
  29. package/src/app/api/agents/route.ts +84 -0
  30. package/src/app/api/api-keys/[id]/route.ts +25 -0
  31. package/src/app/api/api-keys/route.ts +72 -0
  32. package/src/app/api/auth/[...nextauth]/route.ts +5 -0
  33. package/src/app/api/auth/register/route.ts +53 -0
  34. package/src/app/api/health/route.ts +26 -0
  35. package/src/app/api/stripe/checkout/route.ts +37 -0
  36. package/src/app/api/stripe/plans/route.ts +16 -0
  37. package/src/app/api/stripe/portal/route.ts +29 -0
  38. package/src/app/api/stripe/webhook/route.ts +45 -0
  39. package/src/app/api/usage/route.ts +43 -0
  40. package/src/app/globals.css +59 -0
  41. package/src/app/layout.tsx +22 -0
  42. package/src/app/page.tsx +32 -0
  43. package/src/app/pricing/page.tsx +25 -0
  44. package/src/components/agents/agent-form.tsx +137 -0
  45. package/src/components/agents/model-selector.tsx +35 -0
  46. package/src/components/agents/tool-selector.tsx +48 -0
  47. package/src/components/auth-provider.tsx +17 -0
  48. package/src/components/billing/plan-badge.tsx +23 -0
  49. package/src/components/billing/pricing-table.tsx +95 -0
  50. package/src/components/billing/usage-meter.tsx +39 -0
  51. package/src/components/chat/chat-input.tsx +68 -0
  52. package/src/components/chat/chat-interface.tsx +152 -0
  53. package/src/components/chat/chat-message.tsx +50 -0
  54. package/src/components/chat/chat-sidebar.tsx +49 -0
  55. package/src/components/chat/code-block.tsx +38 -0
  56. package/src/components/chat/markdown-renderer.tsx +56 -0
  57. package/src/components/chat/streaming-text.tsx +46 -0
  58. package/src/components/dashboard/agent-card.tsx +52 -0
  59. package/src/components/dashboard/header.tsx +75 -0
  60. package/src/components/dashboard/sidebar.tsx +52 -0
  61. package/src/components/dashboard/stat-card.tsx +42 -0
  62. package/src/components/dashboard/usage-chart.tsx +42 -0
  63. package/src/components/landing/cta.tsx +30 -0
  64. package/src/components/landing/features.tsx +75 -0
  65. package/src/components/landing/hero.tsx +42 -0
  66. package/src/components/landing/pricing.tsx +28 -0
  67. package/src/components/ui/avatar.tsx +24 -0
  68. package/src/components/ui/badge.tsx +24 -0
  69. package/src/components/ui/button.tsx +39 -0
  70. package/src/components/ui/card.tsx +50 -0
  71. package/src/components/ui/dialog.tsx +73 -0
  72. package/src/components/ui/dropdown.tsx +77 -0
  73. package/src/components/ui/input.tsx +23 -0
  74. package/src/components/ui/skeleton.tsx +7 -0
  75. package/src/components/ui/switch.tsx +31 -0
  76. package/src/components/ui/table.tsx +48 -0
  77. package/src/components/ui/tabs.tsx +66 -0
  78. package/src/components/ui/textarea.tsx +20 -0
  79. package/src/hooks/use-agent.ts +44 -0
  80. package/src/hooks/use-streaming.ts +82 -0
  81. package/src/hooks/use-subscription.ts +40 -0
  82. package/src/hooks/use-usage.ts +43 -0
  83. package/src/hooks/use-user.ts +13 -0
  84. package/src/lib/agents/index.ts +60 -0
  85. package/src/lib/agents/memory/long-term.ts +241 -0
  86. package/src/lib/agents/memory/manager.ts +154 -0
  87. package/src/lib/agents/memory/short-term.ts +155 -0
  88. package/src/lib/agents/memory/types.ts +68 -0
  89. package/src/lib/agents/orchestration/debate.ts +170 -0
  90. package/src/lib/agents/orchestration/index.ts +103 -0
  91. package/src/lib/agents/orchestration/parallel.ts +143 -0
  92. package/src/lib/agents/orchestration/router.ts +199 -0
  93. package/src/lib/agents/orchestration/sequential.ts +127 -0
  94. package/src/lib/agents/orchestration/types.ts +68 -0
  95. package/src/lib/agents/tools/calculator.ts +131 -0
  96. package/src/lib/agents/tools/code-executor.ts +191 -0
  97. package/src/lib/agents/tools/file-reader.ts +129 -0
  98. package/src/lib/agents/tools/index.ts +48 -0
  99. package/src/lib/agents/tools/registry.ts +182 -0
  100. package/src/lib/agents/tools/web-search.ts +83 -0
  101. package/src/lib/ai/agent.ts +275 -0
  102. package/src/lib/ai/context.ts +68 -0
  103. package/src/lib/ai/memory.ts +98 -0
  104. package/src/lib/ai/models.ts +80 -0
  105. package/src/lib/ai/streaming.ts +80 -0
  106. package/src/lib/ai/tools.ts +149 -0
  107. package/src/lib/auth/middleware.ts +41 -0
  108. package/src/lib/auth/nextauth.ts +69 -0
  109. package/src/lib/db/client.ts +15 -0
  110. package/src/lib/rate-limit/limiter.ts +93 -0
  111. package/src/lib/rate-limit/rules.ts +38 -0
  112. package/src/lib/stripe/client.ts +25 -0
  113. package/src/lib/stripe/plans.ts +75 -0
  114. package/src/lib/stripe/usage.ts +123 -0
  115. package/src/lib/stripe/webhooks.ts +96 -0
  116. package/src/lib/utils/api-response.ts +85 -0
  117. package/src/lib/utils/errors.ts +73 -0
  118. package/src/lib/utils/helpers.ts +50 -0
  119. package/src/lib/utils/id.ts +21 -0
  120. package/src/lib/utils/logger.ts +38 -0
  121. package/src/lib/utils/validation.ts +44 -0
  122. package/src/middleware.ts +13 -0
  123. package/src/types/agent.ts +31 -0
  124. package/src/types/api.ts +38 -0
  125. package/src/types/billing.ts +35 -0
  126. package/src/types/chat.ts +30 -0
  127. package/src/types/next-auth.d.ts +19 -0
  128. package/tailwind.config.ts +72 -0
  129. package/tsconfig.json +28 -0
  130. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Sequential Orchestrator — Runs agents in a pipeline.
3
+ *
4
+ * Each agent receives the output of the previous agent as its input.
5
+ * Useful for multi-step processing: research -> analysis -> writing -> review.
6
+ *
7
+ * Example: Research Agent -> Summarizer Agent -> Polish Agent
8
+ */
9
+
10
+ import type {
11
+ AgentConfig,
12
+ AgentMessage,
13
+ OrchestrationResult,
14
+ OrchestratorOptions,
15
+ } from './types';
16
+
17
+ export class SequentialOrchestrator {
18
+ private agents: AgentConfig[];
19
+ private options: OrchestratorOptions;
20
+
21
+ constructor(agents: AgentConfig[], options: OrchestratorOptions = {}) {
22
+ if (agents.length === 0) {
23
+ throw new Error('SequentialOrchestrator requires at least one agent');
24
+ }
25
+ this.agents = agents;
26
+ this.options = {
27
+ timeoutMs: 60_000,
28
+ maxRetries: 2,
29
+ ...options,
30
+ };
31
+ }
32
+
33
+ /**
34
+ * Run the sequential pipeline.
35
+ * The initial input is passed to the first agent, then each subsequent
36
+ * agent receives the output of the previous one.
37
+ */
38
+ async run(input: string): Promise<OrchestrationResult> {
39
+ const startTime = Date.now();
40
+ const trace: AgentMessage[] = [];
41
+ let currentContent = input;
42
+
43
+ const timeoutMs = this.options.timeoutMs ?? 60_000;
44
+
45
+ try {
46
+ for (let i = 0; i < this.agents.length; i++) {
47
+ const agent = this.agents[i]!;
48
+
49
+ // Check timeout
50
+ if (Date.now() - startTime > timeoutMs) {
51
+ return {
52
+ content: currentContent,
53
+ agentIds: this.agents.slice(0, i).map((a) => a.id),
54
+ trace,
55
+ durationMs: Date.now() - startTime,
56
+ success: false,
57
+ error: `Timeout after ${timeoutMs}ms during agent: ${agent.name}`,
58
+ };
59
+ }
60
+
61
+ // Build the input message
62
+ const inputMessage: AgentMessage = {
63
+ content: currentContent,
64
+ fromAgent: i === 0 ? 'user' : this.agents[i - 1]!.id,
65
+ toAgent: agent.id,
66
+ timestamp: Date.now(),
67
+ };
68
+
69
+ // Execute with retries
70
+ let result: AgentMessage | undefined;
71
+ let lastError: Error | undefined;
72
+ const maxRetries = this.options.maxRetries ?? 2;
73
+
74
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
75
+ try {
76
+ result = await agent.execute(inputMessage, agent);
77
+ break;
78
+ } catch (err) {
79
+ lastError = err as Error;
80
+ if (attempt < maxRetries) {
81
+ // Brief backoff before retry
82
+ await new Promise((resolve) => setTimeout(resolve, 100 * (attempt + 1)));
83
+ }
84
+ }
85
+ }
86
+
87
+ if (!result) {
88
+ return {
89
+ content: currentContent,
90
+ agentIds: this.agents.slice(0, i).map((a) => a.id),
91
+ trace,
92
+ durationMs: Date.now() - startTime,
93
+ success: false,
94
+ error: `Agent "${agent.name}" failed after ${maxRetries + 1} attempts: ${lastError?.message}`,
95
+ };
96
+ }
97
+
98
+ // Record the interaction
99
+ trace.push(inputMessage);
100
+ trace.push(result);
101
+
102
+ // Notify callback
103
+ this.options.onAgentMessage?.(result);
104
+
105
+ // Pass output to next agent
106
+ currentContent = result.content;
107
+ }
108
+
109
+ return {
110
+ content: currentContent,
111
+ agentIds: this.agents.map((a) => a.id),
112
+ trace,
113
+ durationMs: Date.now() - startTime,
114
+ success: true,
115
+ };
116
+ } catch (err) {
117
+ return {
118
+ content: currentContent,
119
+ agentIds: [],
120
+ trace,
121
+ durationMs: Date.now() - startTime,
122
+ success: false,
123
+ error: (err as Error).message,
124
+ };
125
+ }
126
+ }
127
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Multi-Agent Orchestration Types
3
+ *
4
+ * Defines the types for different orchestration patterns:
5
+ * sequential, parallel, router, and debate.
6
+ */
7
+
8
+ /** Configuration for a single agent in the orchestration */
9
+ export interface AgentConfig {
10
+ id: string;
11
+ name: string;
12
+ description: string;
13
+ systemPrompt: string;
14
+ model?: string;
15
+ /** Called to execute the agent — can be a local function or API call */
16
+ execute: (input: AgentMessage, config: AgentConfig) => Promise<AgentMessage>;
17
+ }
18
+
19
+ /** A message passed between agents */
20
+ export interface AgentMessage {
21
+ content: string;
22
+ fromAgent: string;
23
+ toAgent?: string;
24
+ metadata?: Record<string, unknown>;
25
+ timestamp?: number;
26
+ }
27
+
28
+ /** Result of an orchestration run */
29
+ export interface OrchestrationResult {
30
+ content: string;
31
+ /** Which agents participated */
32
+ agentIds: string[];
33
+ /** Full trace of agent interactions */
34
+ trace: AgentMessage[];
35
+ /** Total execution time in ms */
36
+ durationMs: number;
37
+ /** Success status */
38
+ success: boolean;
39
+ /** Error message if failed */
40
+ error?: string;
41
+ }
42
+
43
+ /** Available orchestration patterns */
44
+ export type OrchestrationPattern = 'sequential' | 'parallel' | 'router' | 'debate';
45
+
46
+ /** A handoff between two agents */
47
+ export interface Handoff {
48
+ fromAgent: string;
49
+ toAgent: string;
50
+ content: string;
51
+ reason?: string;
52
+ }
53
+
54
+ /** Options for configuring an orchestrator */
55
+ export interface OrchestratorOptions {
56
+ /** Maximum total time for the orchestration in ms (default: 60000) */
57
+ timeoutMs?: number;
58
+ /** Maximum retries for individual agent calls (default: 2) */
59
+ maxRetries?: number;
60
+ /** Callback for monitoring agent interactions */
61
+ onAgentMessage?: (message: AgentMessage) => void;
62
+ }
63
+
64
+ /** Function signature for a router classifier */
65
+ export type RouterClassifier = (
66
+ input: string,
67
+ agents: AgentConfig[],
68
+ ) => Promise<AgentConfig | AgentConfig[]>;
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Calculator Tool — Safely evaluate mathematical expressions.
3
+ *
4
+ * Uses a restricted Function constructor with only Math functions available.
5
+ * No filesystem, network, or DOM access. Timeout protection included.
6
+ */
7
+
8
+ import type { ToolDefinition } from './registry';
9
+
10
+ export const calculatorTool: ToolDefinition = {
11
+ name: 'calculator',
12
+ description:
13
+ 'Evaluate mathematical expressions. Supports basic arithmetic, trigonometry, logarithms, and Math functions. Examples: "2 + 2", "Math.sqrt(16)", "Math.PI * 5^2"',
14
+ parameters: {
15
+ expression: {
16
+ type: 'string',
17
+ description: 'Math expression to evaluate (e.g., "2 + 2", "Math.sqrt(16)", "sin(PI/4)")',
18
+ required: true,
19
+ },
20
+ precision: {
21
+ type: 'number',
22
+ description: 'Number of decimal places in the result (default: 6)',
23
+ required: false,
24
+ default: 6,
25
+ },
26
+ },
27
+ handler: async (params): Promise<string> => {
28
+ const expression = (params.expression as string).trim();
29
+ const precision = (params.precision as number) ?? 6;
30
+
31
+ if (!expression) {
32
+ return 'Error: Empty expression';
33
+ }
34
+
35
+ // Validate expression — reject dangerous patterns
36
+ const forbidden = [
37
+ /\brequire\s*\(/,
38
+ /\bimport\s+/,
39
+ /\beval\s*\(/,
40
+ /\bFunction\s*\(/,
41
+ /\bprocess\b/,
42
+ /\bglobal\b/,
43
+ /\bwindow\b/,
44
+ /\bdocument\b/,
45
+ /\bfetch\s*\(/,
46
+ /\b__proto__\b/,
47
+ /\bconstructor\b/,
48
+ /\bprototype\b/,
49
+ ];
50
+
51
+ for (const pattern of forbidden) {
52
+ if (pattern.test(expression)) {
53
+ return `Error: Expression contains forbidden pattern: ${pattern.source}`;
54
+ }
55
+ }
56
+
57
+ try {
58
+ // Create a sandboxed math environment
59
+ const mathEnv: Record<string, unknown> = {
60
+ abs: Math.abs,
61
+ ceil: Math.ceil,
62
+ floor: Math.floor,
63
+ round: Math.round,
64
+ trunc: Math.trunc,
65
+ sqrt: Math.sqrt,
66
+ cbrt: Math.cbrt,
67
+ pow: Math.pow,
68
+ exp: Math.exp,
69
+ log: Math.log,
70
+ log2: Math.log2,
71
+ log10: Math.log10,
72
+ ln: Math.log,
73
+ sin: Math.sin,
74
+ cos: Math.cos,
75
+ tan: Math.tan,
76
+ asin: Math.asin,
77
+ acos: Math.acos,
78
+ atan: Math.atan,
79
+ atan2: Math.atan2,
80
+ sinh: Math.sinh,
81
+ cosh: Math.cosh,
82
+ tanh: Math.tanh,
83
+ min: Math.min,
84
+ max: Math.max,
85
+ random: Math.random,
86
+ sign: Math.sign,
87
+ PI: Math.PI,
88
+ E: Math.E,
89
+ LN2: Math.LN2,
90
+ LN10: Math.LN10,
91
+ SQRT2: Math.SQRT2,
92
+ // Aliases
93
+ pi: Math.PI,
94
+ e: Math.E,
95
+ // Convenience functions
96
+ avg: (...args: number[]) => args.reduce((a, b) => a + b, 0) / args.length,
97
+ sum: (...args: number[]) => args.reduce((a, b) => a + b, 0),
98
+ factorial: (n: number) => {
99
+ if (n < 0 || !Number.isInteger(n)) return NaN;
100
+ if (n <= 1) return 1;
101
+ let result = 1;
102
+ for (let i = 2; i <= n; i++) result *= i;
103
+ return result;
104
+ },
105
+ };
106
+
107
+ const keys = Object.keys(mathEnv);
108
+ const values = Object.values(mathEnv);
109
+
110
+ // Replace ^ with ** for exponentiation
111
+ const processedExpr = expression.replace(/\^/g, '**');
112
+
113
+ const fn = new Function(...keys, `"use strict"; return (${processedExpr});`);
114
+ const result = fn(...values);
115
+
116
+ if (typeof result !== 'number' || isNaN(result)) {
117
+ return `Result: ${result}`;
118
+ }
119
+
120
+ if (!isFinite(result)) {
121
+ return `Result: ${result === Infinity ? 'Infinity' : '-Infinity'}`;
122
+ }
123
+
124
+ // Format with specified precision
125
+ const formatted = Number(result.toFixed(precision));
126
+ return `Result: ${formatted}`;
127
+ } catch (err) {
128
+ return `Error evaluating "${expression}": ${(err as Error).message}`;
129
+ }
130
+ },
131
+ };
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Code Executor Tool — Safe sandboxed JavaScript execution.
3
+ *
4
+ * Runs user code in a restricted Function sandbox with:
5
+ * - No filesystem, network, or DOM access
6
+ * - Configurable timeout
7
+ * - Console output capture
8
+ * - Return value serialization
9
+ */
10
+
11
+ import type { ToolDefinition } from './registry';
12
+
13
+ export interface CodeExecutionResult {
14
+ success: boolean;
15
+ output: string;
16
+ returnValue: unknown;
17
+ consoleOutput: string[];
18
+ durationMs: number;
19
+ error?: string;
20
+ }
21
+
22
+ export const codeExecutorTool: ToolDefinition = {
23
+ name: 'code_executor',
24
+ description:
25
+ 'Execute JavaScript code in a safe sandbox. Supports console.log output capture and return values. No filesystem or network access.',
26
+ parameters: {
27
+ code: {
28
+ type: 'string',
29
+ description: 'JavaScript code to execute. Use return statements to return a value.',
30
+ required: true,
31
+ },
32
+ timeout: {
33
+ type: 'number',
34
+ description: 'Execution timeout in milliseconds (default: 5000, max: 10000)',
35
+ required: false,
36
+ default: 5000,
37
+ },
38
+ },
39
+ handler: async (params): Promise<string> => {
40
+ const code = (params.code as string).trim();
41
+ const timeout = Math.min((params.timeout as number) ?? 5000, 10000);
42
+
43
+ if (!code) {
44
+ return 'Error: No code provided';
45
+ }
46
+
47
+ // Validate code — reject dangerous patterns
48
+ const forbidden = [
49
+ /\brequire\s*\(/,
50
+ /\bimport\s+/,
51
+ /\beval\s*\(/,
52
+ /\bFunction\s*\(/,
53
+ /\bprocess\b/,
54
+ /\bglobal\b/,
55
+ /\bglobalThis\b/,
56
+ /\bwindow\b/,
57
+ /\bdocument\b/,
58
+ /\bfetch\s*\(/,
59
+ /\bXMLHttpRequest\b/,
60
+ /\bWebSocket\b/,
61
+ /\bWorker\b/,
62
+ /\bSharedArrayBuffer\b/,
63
+ /\bAtomics\b/,
64
+ /\b__proto__\b/,
65
+ /\bconstructor\s*\[/,
66
+ /\[\s*\'constructor\'\s*\]/,
67
+ /\.constructor\b/,
68
+ ];
69
+
70
+ for (const pattern of forbidden) {
71
+ if (pattern.test(code)) {
72
+ return `Error: Code contains forbidden pattern for security: ${pattern.source}`;
73
+ }
74
+ }
75
+
76
+ try {
77
+ const consoleOutput: string[] = [];
78
+
79
+ // Create a mock console
80
+ const mockConsole = {
81
+ log: (...args: unknown[]) => {
82
+ consoleOutput.push(args.map(stringify).join(' '));
83
+ },
84
+ error: (...args: unknown[]) => {
85
+ consoleOutput.push(`[ERROR] ${args.map(stringify).join(' ')}`);
86
+ },
87
+ warn: (...args: unknown[]) => {
88
+ consoleOutput.push(`[WARN] ${args.map(stringify).join(' ')}`);
89
+ },
90
+ info: (...args: unknown[]) => {
91
+ consoleOutput.push(`[INFO] ${args.map(stringify).join(' ')}`);
92
+ },
93
+ table: (data: unknown) => {
94
+ consoleOutput.push(stringify(data));
95
+ },
96
+ };
97
+
98
+ // Wrap the code to capture the return value
99
+ const wrappedCode = `
100
+ "use strict";
101
+ const console = arguments[0];
102
+ ${code}
103
+ `;
104
+
105
+ const startTime = Date.now();
106
+
107
+ // Execute with timeout using Promise.race
108
+ const executionPromise = new Promise<unknown>((resolve, reject) => {
109
+ try {
110
+ const fn = new Function(wrappedCode);
111
+ const result = fn(mockConsole);
112
+ resolve(result);
113
+ } catch (err) {
114
+ reject(err);
115
+ }
116
+ });
117
+
118
+ const timeoutPromise = new Promise<never>((_, reject) => {
119
+ setTimeout(() => reject(new Error(`Execution timed out after ${timeout}ms`)), timeout);
120
+ });
121
+
122
+ let returnValue: unknown;
123
+ try {
124
+ returnValue = await Promise.race([executionPromise, timeoutPromise]);
125
+ } catch (err) {
126
+ const durationMs = Date.now() - startTime;
127
+ const result: CodeExecutionResult = {
128
+ success: false,
129
+ output: '',
130
+ returnValue: undefined,
131
+ consoleOutput,
132
+ durationMs,
133
+ error: (err as Error).message,
134
+ };
135
+ return formatResult(result);
136
+ }
137
+
138
+ const durationMs = Date.now() - startTime;
139
+ const result: CodeExecutionResult = {
140
+ success: true,
141
+ output: consoleOutput.join('\n'),
142
+ returnValue,
143
+ consoleOutput,
144
+ durationMs,
145
+ };
146
+
147
+ return formatResult(result);
148
+ } catch (err) {
149
+ return `Error: ${(err as Error).message}`;
150
+ }
151
+ },
152
+ };
153
+
154
+ function formatResult(result: CodeExecutionResult): string {
155
+ const lines: string[] = [];
156
+
157
+ lines.push(`Execution ${result.success ? 'completed' : 'failed'} in ${result.durationMs}ms`);
158
+
159
+ if (result.consoleOutput.length > 0) {
160
+ lines.push('');
161
+ lines.push('Console output:');
162
+ for (const line of result.consoleOutput) {
163
+ lines.push(` ${line}`);
164
+ }
165
+ }
166
+
167
+ if (result.returnValue !== undefined) {
168
+ lines.push('');
169
+ lines.push(`Return value: ${stringify(result.returnValue)}`);
170
+ }
171
+
172
+ if (result.error) {
173
+ lines.push('');
174
+ lines.push(`Error: ${result.error}`);
175
+ }
176
+
177
+ return lines.join('\n');
178
+ }
179
+
180
+ function stringify(value: unknown): string {
181
+ if (value === undefined) return 'undefined';
182
+ if (value === null) return 'null';
183
+ if (typeof value === 'string') return value;
184
+ if (typeof value === 'function') return '[Function]';
185
+ if (typeof value === 'symbol') return value.toString();
186
+ try {
187
+ return JSON.stringify(value, null, 2);
188
+ } catch {
189
+ return String(value);
190
+ }
191
+ }
@@ -0,0 +1,129 @@
1
+ /**
2
+ * File Reader Tool — Read and analyze file contents.
3
+ *
4
+ * Supports text files with line counting, character analysis,
5
+ * and content previewing. Designed for agent use — does not
6
+ * directly access the filesystem for security.
7
+ */
8
+
9
+ import type { ToolDefinition } from './registry';
10
+
11
+ export const fileReaderTool: ToolDefinition = {
12
+ name: 'file_reader',
13
+ description:
14
+ 'Analyze file contents provided as text. Returns statistics (lines, characters, words) and a preview. Does NOT access the filesystem directly — content must be passed in.',
15
+ parameters: {
16
+ content: {
17
+ type: 'string',
18
+ description: 'The file content to analyze',
19
+ required: true,
20
+ },
21
+ filename: {
22
+ type: 'string',
23
+ description: 'Name of the file (for display purposes)',
24
+ required: true,
25
+ },
26
+ analysis: {
27
+ type: 'string',
28
+ description:
29
+ 'Type of analysis: "stats" (line/word/char counts), "preview" (first N lines), "full" (everything), or "summary" (stats + preview)',
30
+ required: false,
31
+ default: 'summary',
32
+ enum: ['stats', 'preview', 'full', 'summary'],
33
+ },
34
+ max_preview_lines: {
35
+ type: 'number',
36
+ description: 'Maximum number of lines to include in preview (default: 50)',
37
+ required: false,
38
+ default: 50,
39
+ },
40
+ },
41
+ handler: async (params): Promise<string> => {
42
+ const content = params.content as string;
43
+ const filename = params.filename as string;
44
+ const analysis = (params.analysis as string) ?? 'summary';
45
+ const maxPreviewLines = (params.max_preview_lines as number) ?? 50;
46
+
47
+ if (!content) {
48
+ return `File "${filename}": Empty or no content provided.`;
49
+ }
50
+
51
+ const lines = content.split('\n');
52
+ const totalLines = lines.length;
53
+ const totalChars = content.length;
54
+ const totalWords = content.split(/\s+/).filter(Boolean).length;
55
+
56
+ // Detect file type
57
+ const extension = filename.split('.').pop()?.toLowerCase() ?? '';
58
+ const fileType = getFileType(extension);
59
+
60
+ // Non-empty lines
61
+ const nonEmptyLines = lines.filter((line) => line.trim().length > 0).length;
62
+
63
+ const linesResult: string[] = [];
64
+
65
+ linesResult.push(`=== File Analysis: ${filename} ===`);
66
+ linesResult.push('');
67
+
68
+ if (analysis === 'stats' || analysis === 'summary') {
69
+ linesResult.push('Statistics:');
70
+ linesResult.push(` Lines: ${totalLines} (${nonEmptyLines} non-empty)`);
71
+ linesResult.push(` Characters: ${totalChars.toLocaleString()}`);
72
+ linesResult.push(` Words: ${totalWords.toLocaleString()}`);
73
+ linesResult.push(` Type: ${fileType}`);
74
+ linesResult.push(` Average line length: ${totalChars > 0 ? Math.round(totalChars / totalLines) : 0} chars`);
75
+ linesResult.push('');
76
+ }
77
+
78
+ if (analysis === 'preview' || analysis === 'summary') {
79
+ const previewLines = lines.slice(0, maxPreviewLines);
80
+ linesResult.push(`Content preview (first ${Math.min(maxPreviewLines, totalLines)} of ${totalLines} lines):`);
81
+ linesResult.push('---');
82
+
83
+ for (let i = 0; i < previewLines.length; i++) {
84
+ const lineNum = String(i + 1).padStart(4, ' ');
85
+ const line = previewLines[i]!;
86
+ linesResult.push(`${lineNum} | ${line}`);
87
+ }
88
+
89
+ if (totalLines > maxPreviewLines) {
90
+ linesResult.push(`\n... ${totalLines - maxPreviewLines} more lines not shown`);
91
+ }
92
+
93
+ linesResult.push('---');
94
+ }
95
+
96
+ if (analysis === 'full') {
97
+ linesResult.push('Full content:');
98
+ linesResult.push(content);
99
+ }
100
+
101
+ return linesResult.join('\n');
102
+ },
103
+ };
104
+
105
+ function getFileType(extension: string): string {
106
+ const types: Record<string, string> = {
107
+ ts: 'TypeScript',
108
+ tsx: 'TypeScript (React)',
109
+ js: 'JavaScript',
110
+ jsx: 'JavaScript (React)',
111
+ json: 'JSON',
112
+ md: 'Markdown',
113
+ css: 'CSS',
114
+ html: 'HTML',
115
+ py: 'Python',
116
+ rs: 'Rust',
117
+ go: 'Go',
118
+ java: 'Java',
119
+ yaml: 'YAML',
120
+ yml: 'YAML',
121
+ xml: 'XML',
122
+ csv: 'CSV',
123
+ sql: 'SQL',
124
+ sh: 'Shell',
125
+ bash: 'Bash',
126
+ txt: 'Plain Text',
127
+ };
128
+ return types[extension] ?? `Unknown (.${extension || 'text'})`;
129
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Agent Tools — Default tool set and registry setup.
3
+ *
4
+ * Re-exports the ToolRegistry, built-in tools, and provides
5
+ * a convenience function to create a registry with all default tools.
6
+ */
7
+
8
+ export { ToolRegistry, getGlobalRegistry } from './registry';
9
+ export type { ToolDefinition, ToolExecutionResult } from './registry';
10
+
11
+ export { webSearchTool } from './web-search';
12
+ export { calculatorTool } from './calculator';
13
+ export { codeExecutorTool } from './code-executor';
14
+ export { fileReaderTool } from './file-reader';
15
+
16
+ import { ToolRegistry } from './registry';
17
+ import { webSearchTool } from './web-search';
18
+ import { calculatorTool } from './calculator';
19
+ import { codeExecutorTool } from './code-executor';
20
+ import { fileReaderTool } from './file-reader';
21
+
22
+ /** All built-in tools as an array */
23
+ export const builtInTools = [webSearchTool, calculatorTool, codeExecutorTool, fileReaderTool];
24
+
25
+ /**
26
+ * Create a ToolRegistry pre-loaded with all built-in tools.
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * const registry = createDefaultRegistry();
31
+ *
32
+ * // Execute a calculation
33
+ * const result = await registry.execute('calculator', { expression: '2 + 2' });
34
+ * console.log(result.result); // "Result: 4"
35
+ *
36
+ * // List available tools
37
+ * console.log(registry.listNames()); // ["web_search", "calculator", "code_executor", "file_reader"]
38
+ * ```
39
+ */
40
+ export function createDefaultRegistry(): ToolRegistry {
41
+ const registry = new ToolRegistry();
42
+
43
+ for (const tool of builtInTools) {
44
+ registry.register(tool);
45
+ }
46
+
47
+ return registry;
48
+ }