feat: add environment variables support for agents and collections
- Add envVars field to Agent and Collection models in Prisma schema - Add envVars to UpdateAgentSchema and UpdateCollectionSchema types - Implement env vars merging logic (agent overrides collection) - Pass env vars through tool executor to sandbox - Add UI for managing env vars in agent and collection settings pages - Update API routes to handle envVars PATCH updates - Refactor discord tool execute functions for biome compatibility This allows users to configure tool-specific environment variables (like DISCORD_BOT_TOKEN) at both the collection and agent level, with agent settings taking precedence over collection settings.
This commit is contained in:
parent
079c328bfc
commit
938a382aab
25 changed files with 2021 additions and 15 deletions
|
|
@ -426,6 +426,10 @@ model Collection {
|
|||
executorType String? @map("executor_type") @db.VarChar(50)
|
||||
executorConfig Json? @map("executor_config") @db.JsonB
|
||||
|
||||
// Tool environment variables (encrypted JSON: { "KEY": "value" })
|
||||
// These are passed to tools when executed
|
||||
envVars Json? @map("env_vars") @db.JsonB
|
||||
|
||||
// Timestamps
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
|
@ -522,6 +526,10 @@ model Agent {
|
|||
executorType String? @map("executor_type") @db.VarChar(50)
|
||||
executorConfig Json? @map("executor_config") @db.JsonB
|
||||
|
||||
// Tool environment variables (encrypted JSON: { "KEY": "value" })
|
||||
// Agent env vars override collection env vars, otherwise merged
|
||||
envVars Json? @map("env_vars") @db.JsonB
|
||||
|
||||
// Timestamps
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
|
|
|||
|
|
@ -34,17 +34,23 @@ export async function executePackage(
|
|||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
||||
|
||||
// Build request body, only include env if provided
|
||||
const requestBody: Record<string, unknown> = {
|
||||
packageName,
|
||||
name: functionName,
|
||||
version: 'latest',
|
||||
params,
|
||||
};
|
||||
if (options.env && Object.keys(options.env).length > 0) {
|
||||
requestBody.env = options.env;
|
||||
}
|
||||
|
||||
const response = await fetch(`${getSandboxUrl()}/execute-tool`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
packageName,
|
||||
name: functionName,
|
||||
version: 'latest',
|
||||
params,
|
||||
}),
|
||||
body: JSON.stringify(requestBody),
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -18,4 +18,6 @@ export interface PackageInfo {
|
|||
export interface ExecutorOptions {
|
||||
timeout?: number; // Milliseconds
|
||||
cacheDir?: string;
|
||||
/** Environment variables to inject during execution */
|
||||
env?: Record<string, string>;
|
||||
}
|
||||
|
|
|
|||
19
packages/tools/discord-post/CHANGELOG.md
Normal file
19
packages/tools/discord-post/CHANGELOG.md
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# @tpmjs/discord-post
|
||||
|
||||
## 0.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Add Discord tools for reading server messages and posting to channels
|
||||
|
||||
- `@tpmjs/discord-read`: Read messages from a Discord server for the past N hours
|
||||
- `@tpmjs/discord-post`: Post messages and rich embeds to a Discord channel
|
||||
|
||||
## 0.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Add Discord tools for reading server messages and posting to channels
|
||||
|
||||
- `@tpmjs/discord-read`: Read messages from a Discord server for the past N hours
|
||||
- `@tpmjs/discord-post`: Post messages and rich embeds to a Discord channel
|
||||
48
packages/tools/discord-post/package.json
Normal file
48
packages/tools/discord-post/package.json
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"name": "@tpmjs/discord-post",
|
||||
"version": "0.2.0",
|
||||
"description": "AI SDK tool to post messages to a Discord channel",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"type-check": "tsc --noEmit"
|
||||
},
|
||||
"keywords": [
|
||||
"tpmjs",
|
||||
"discord",
|
||||
"ai",
|
||||
"communication"
|
||||
],
|
||||
"tpmjs": {
|
||||
"category": "communication",
|
||||
"frameworks": [
|
||||
"vercel-ai"
|
||||
],
|
||||
"env": [
|
||||
{
|
||||
"name": "DISCORD_BOT_TOKEN",
|
||||
"description": "Discord bot token with Send Messages permission",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"tools": [
|
||||
{
|
||||
"name": "discordPostTool",
|
||||
"description": "Post a message or rich embed to a Discord channel. Supports plain text messages and formatted embeds with titles, descriptions, colors, and fields."
|
||||
}
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"ai": "6.0.23"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tpmjs/tsconfig": "workspace:*",
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
213
packages/tools/discord-post/src/index.ts
Normal file
213
packages/tools/discord-post/src/index.ts
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
/**
|
||||
* Discord Post Tool
|
||||
* Posts messages and embeds to a Discord channel
|
||||
*/
|
||||
|
||||
import { jsonSchema, tool } from 'ai';
|
||||
|
||||
// Tool input type
|
||||
type DiscordPostInput = {
|
||||
channelId: string;
|
||||
content?: string;
|
||||
embed?: {
|
||||
title?: string;
|
||||
description?: string;
|
||||
color?: number;
|
||||
fields?: Array<{
|
||||
name: string;
|
||||
value: string;
|
||||
inline?: boolean;
|
||||
}>;
|
||||
footer?: string;
|
||||
timestamp?: string;
|
||||
};
|
||||
};
|
||||
|
||||
const DISCORD_API_BASE = 'https://discord.com/api/v10';
|
||||
|
||||
/**
|
||||
* Discord Post Tool
|
||||
* Posts a message or rich embed to a Discord channel
|
||||
*/
|
||||
export const discordPostTool = tool({
|
||||
description:
|
||||
'Post a message or rich embed to a Discord channel. Supports plain text messages and formatted embeds with titles, descriptions, colors, and fields.',
|
||||
inputSchema: jsonSchema<DiscordPostInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
channelId: {
|
||||
type: 'string',
|
||||
description: 'Discord channel ID to post to',
|
||||
},
|
||||
content: {
|
||||
type: 'string',
|
||||
description: 'Plain text message content (max 2000 characters)',
|
||||
},
|
||||
embed: {
|
||||
type: 'object',
|
||||
description: 'Rich embed object for formatted messages',
|
||||
properties: {
|
||||
title: {
|
||||
type: 'string',
|
||||
description: 'Embed title (max 256 characters)',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
description: 'Embed description (max 4096 characters)',
|
||||
},
|
||||
color: {
|
||||
type: 'number',
|
||||
description: 'Embed color as decimal (e.g., 5793266 for Discord blurple)',
|
||||
},
|
||||
fields: {
|
||||
type: 'array',
|
||||
description: 'Array of field objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
description: 'Field name (max 256 chars)',
|
||||
},
|
||||
value: {
|
||||
type: 'string',
|
||||
description: 'Field value (max 1024 chars)',
|
||||
},
|
||||
inline: { type: 'boolean', description: 'Display inline' },
|
||||
},
|
||||
required: ['name', 'value'],
|
||||
},
|
||||
},
|
||||
footer: {
|
||||
type: 'string',
|
||||
description: 'Footer text (max 2048 characters)',
|
||||
},
|
||||
timestamp: {
|
||||
type: 'string',
|
||||
description: 'ISO8601 timestamp to display',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
required: ['channelId'],
|
||||
additionalProperties: false,
|
||||
}),
|
||||
execute: discordPostExecute,
|
||||
});
|
||||
|
||||
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Discord embed building with validation
|
||||
async function discordPostExecute(input: DiscordPostInput) {
|
||||
// Get token from environment - the executor injects this
|
||||
const token = process.env.DISCORD_BOT_TOKEN;
|
||||
|
||||
if (!token) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'DISCORD_BOT_TOKEN environment variable is required',
|
||||
};
|
||||
}
|
||||
|
||||
// Validate that we have something to post
|
||||
if (!input.content && !input.embed) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Either content or embed must be provided',
|
||||
};
|
||||
}
|
||||
|
||||
// Build the message payload
|
||||
const payload: {
|
||||
content?: string;
|
||||
embeds?: Array<{
|
||||
title?: string;
|
||||
description?: string;
|
||||
color?: number;
|
||||
fields?: Array<{ name: string; value: string; inline?: boolean }>;
|
||||
footer?: { text: string };
|
||||
timestamp?: string;
|
||||
}>;
|
||||
} = {};
|
||||
|
||||
if (input.content) {
|
||||
// Truncate to Discord's limit
|
||||
payload.content = input.content.slice(0, 2000);
|
||||
}
|
||||
|
||||
if (input.embed) {
|
||||
const embed: {
|
||||
title?: string;
|
||||
description?: string;
|
||||
color?: number;
|
||||
fields?: Array<{ name: string; value: string; inline?: boolean }>;
|
||||
footer?: { text: string };
|
||||
timestamp?: string;
|
||||
} = {};
|
||||
|
||||
if (input.embed.title) {
|
||||
embed.title = input.embed.title.slice(0, 256);
|
||||
}
|
||||
|
||||
if (input.embed.description) {
|
||||
embed.description = input.embed.description.slice(0, 4096);
|
||||
}
|
||||
|
||||
if (input.embed.color !== undefined) {
|
||||
embed.color = input.embed.color;
|
||||
}
|
||||
|
||||
if (input.embed.fields && input.embed.fields.length > 0) {
|
||||
embed.fields = input.embed.fields.slice(0, 25).map((field) => ({
|
||||
name: field.name.slice(0, 256),
|
||||
value: field.value.slice(0, 1024),
|
||||
inline: field.inline,
|
||||
}));
|
||||
}
|
||||
|
||||
if (input.embed.footer) {
|
||||
embed.footer = { text: input.embed.footer.slice(0, 2048) };
|
||||
}
|
||||
|
||||
if (input.embed.timestamp) {
|
||||
embed.timestamp = input.embed.timestamp;
|
||||
}
|
||||
|
||||
payload.embeds = [embed];
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${DISCORD_API_BASE}/channels/${input.channelId}/messages`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bot ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
return {
|
||||
success: false,
|
||||
error: `Discord API error (${response.status}): ${errorText}`,
|
||||
};
|
||||
}
|
||||
|
||||
const message = (await response.json()) as {
|
||||
id: string;
|
||||
channel_id: string;
|
||||
timestamp: string;
|
||||
};
|
||||
|
||||
return {
|
||||
success: true,
|
||||
messageId: message.id,
|
||||
channelId: message.channel_id,
|
||||
timestamp: message.timestamp,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error occurred',
|
||||
};
|
||||
}
|
||||
}
|
||||
11
packages/tools/discord-post/tsconfig.json
Normal file
11
packages/tools/discord-post/tsconfig.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"extends": "@tpmjs/tsconfig/base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"declaration": true,
|
||||
"declarationMap": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
19
packages/tools/discord-read/CHANGELOG.md
Normal file
19
packages/tools/discord-read/CHANGELOG.md
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# @tpmjs/discord-read
|
||||
|
||||
## 0.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Add Discord tools for reading server messages and posting to channels
|
||||
|
||||
- `@tpmjs/discord-read`: Read messages from a Discord server for the past N hours
|
||||
- `@tpmjs/discord-post`: Post messages and rich embeds to a Discord channel
|
||||
|
||||
## 0.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Add Discord tools for reading server messages and posting to channels
|
||||
|
||||
- `@tpmjs/discord-read`: Read messages from a Discord server for the past N hours
|
||||
- `@tpmjs/discord-post`: Post messages and rich embeds to a Discord channel
|
||||
48
packages/tools/discord-read/package.json
Normal file
48
packages/tools/discord-read/package.json
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"name": "@tpmjs/discord-read",
|
||||
"version": "0.2.0",
|
||||
"description": "AI SDK tool to read messages from a Discord server",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"type-check": "tsc --noEmit"
|
||||
},
|
||||
"keywords": [
|
||||
"tpmjs",
|
||||
"discord",
|
||||
"ai",
|
||||
"communication"
|
||||
],
|
||||
"tpmjs": {
|
||||
"category": "communication",
|
||||
"frameworks": [
|
||||
"vercel-ai"
|
||||
],
|
||||
"env": [
|
||||
{
|
||||
"name": "DISCORD_BOT_TOKEN",
|
||||
"description": "Discord bot token with MESSAGE_CONTENT intent",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"tools": [
|
||||
{
|
||||
"name": "discordReadTool",
|
||||
"description": "Read messages from a Discord server for the past N hours. Fetches all text channels and their messages, filtering by time and optionally excluding bot messages."
|
||||
}
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"ai": "6.0.23"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tpmjs/tsconfig": "workspace:*",
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
248
packages/tools/discord-read/src/index.ts
Normal file
248
packages/tools/discord-read/src/index.ts
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
/**
|
||||
* Discord Read Tool
|
||||
* Fetches messages from a Discord server for the past N hours
|
||||
*/
|
||||
|
||||
import { jsonSchema, tool } from 'ai';
|
||||
|
||||
// Tool input type
|
||||
type DiscordReadInput = {
|
||||
guildId: string;
|
||||
hours?: number;
|
||||
excludeChannels?: string[];
|
||||
excludeBots?: boolean;
|
||||
};
|
||||
|
||||
const DISCORD_API_BASE = 'https://discord.com/api/v10';
|
||||
|
||||
/**
|
||||
* Fetch messages from a channel, paginating through results
|
||||
*/
|
||||
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Discord API pagination with time filtering
|
||||
async function fetchChannelMessages(
|
||||
headers: Record<string, string>,
|
||||
channelId: string,
|
||||
cutoffTime: number,
|
||||
excludeBots: boolean
|
||||
): Promise<
|
||||
Array<{
|
||||
author: string;
|
||||
content: string;
|
||||
timestamp: string;
|
||||
attachments: number;
|
||||
reactions: number;
|
||||
}>
|
||||
> {
|
||||
const messages: Array<{
|
||||
author: string;
|
||||
content: string;
|
||||
timestamp: string;
|
||||
attachments: number;
|
||||
reactions: number;
|
||||
}> = [];
|
||||
|
||||
let lastMessageId: string | undefined;
|
||||
let hasMore = true;
|
||||
|
||||
while (hasMore) {
|
||||
const url = new URL(`${DISCORD_API_BASE}/channels/${channelId}/messages`);
|
||||
url.searchParams.set('limit', '100');
|
||||
if (lastMessageId) {
|
||||
url.searchParams.set('before', lastMessageId);
|
||||
}
|
||||
|
||||
const response = await fetch(url.toString(), { headers });
|
||||
|
||||
if (!response.ok) {
|
||||
// Channel might not be accessible, skip it
|
||||
break;
|
||||
}
|
||||
|
||||
const batch = (await response.json()) as Array<{
|
||||
id: string;
|
||||
content: string;
|
||||
author: { id: string; username: string; bot?: boolean };
|
||||
timestamp: string;
|
||||
attachments: unknown[];
|
||||
reactions?: unknown[];
|
||||
}>;
|
||||
|
||||
if (batch.length === 0) {
|
||||
hasMore = false;
|
||||
break;
|
||||
}
|
||||
|
||||
for (const msg of batch) {
|
||||
const msgTime = new Date(msg.timestamp).getTime();
|
||||
|
||||
// Stop if we've gone past our time window
|
||||
if (msgTime < cutoffTime) {
|
||||
hasMore = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip bot messages if requested
|
||||
if (excludeBots && msg.author.bot) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip empty messages (system messages, etc.)
|
||||
if (!msg.content && msg.attachments.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
messages.push({
|
||||
author: msg.author.username,
|
||||
content: msg.content || '[attachment]',
|
||||
timestamp: msg.timestamp,
|
||||
attachments: msg.attachments.length,
|
||||
reactions: msg.reactions?.length || 0,
|
||||
});
|
||||
}
|
||||
|
||||
lastMessageId = batch[batch.length - 1]?.id;
|
||||
|
||||
// Rate limit protection
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
}
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discord Read Tool
|
||||
* Reads messages from all text channels in a Discord server for the past N hours
|
||||
*/
|
||||
export const discordReadTool = tool({
|
||||
description:
|
||||
'Read messages from a Discord server for the past N hours. Fetches all text channels and their messages, filtering by time and optionally excluding bot messages.',
|
||||
inputSchema: jsonSchema<DiscordReadInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
guildId: {
|
||||
type: 'string',
|
||||
description: 'Discord server (guild) ID',
|
||||
},
|
||||
hours: {
|
||||
type: 'number',
|
||||
description: 'Number of hours to look back (default: 24)',
|
||||
},
|
||||
excludeChannels: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: 'Channel IDs to exclude from reading',
|
||||
},
|
||||
excludeBots: {
|
||||
type: 'boolean',
|
||||
description: 'Whether to exclude bot messages (default: true)',
|
||||
},
|
||||
},
|
||||
required: ['guildId'],
|
||||
additionalProperties: false,
|
||||
}),
|
||||
execute: discordReadExecute,
|
||||
});
|
||||
|
||||
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Multi-step Discord API workflow
|
||||
async function discordReadExecute(input: DiscordReadInput) {
|
||||
// Get token from environment - the executor injects this
|
||||
const token = process.env.DISCORD_BOT_TOKEN;
|
||||
|
||||
if (!token) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'DISCORD_BOT_TOKEN environment variable is required',
|
||||
};
|
||||
}
|
||||
|
||||
const headers = {
|
||||
Authorization: `Bot ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
||||
const hours = input.hours || 24;
|
||||
const excludeBots = input.excludeBots !== false;
|
||||
const excludeChannels = input.excludeChannels || [];
|
||||
const cutoffTime = Date.now() - hours * 60 * 60 * 1000;
|
||||
|
||||
try {
|
||||
// 1. Get guild info
|
||||
const guildResponse = await fetch(`${DISCORD_API_BASE}/guilds/${input.guildId}`, { headers });
|
||||
|
||||
if (!guildResponse.ok) {
|
||||
const error = await guildResponse.text();
|
||||
return {
|
||||
success: false,
|
||||
error: `Failed to fetch guild: ${error}`,
|
||||
};
|
||||
}
|
||||
|
||||
const guild = (await guildResponse.json()) as { id: string; name: string };
|
||||
|
||||
// 2. Get all channels
|
||||
const channelsResponse = await fetch(`${DISCORD_API_BASE}/guilds/${input.guildId}/channels`, {
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!channelsResponse.ok) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Failed to fetch channels',
|
||||
};
|
||||
}
|
||||
|
||||
const allChannels = (await channelsResponse.json()) as Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
type: number;
|
||||
}>;
|
||||
|
||||
// Filter to text channels only (type 0 = GUILD_TEXT)
|
||||
const textChannels = allChannels.filter((c) => c.type === 0 && !excludeChannels.includes(c.id));
|
||||
|
||||
// 3. Fetch messages from each channel
|
||||
const channelResults: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
messageCount: number;
|
||||
messages: Array<{
|
||||
author: string;
|
||||
content: string;
|
||||
timestamp: string;
|
||||
attachments: number;
|
||||
reactions: number;
|
||||
}>;
|
||||
}> = [];
|
||||
|
||||
for (const channel of textChannels) {
|
||||
const messages = await fetchChannelMessages(headers, channel.id, cutoffTime, excludeBots);
|
||||
|
||||
if (messages.length > 0) {
|
||||
channelResults.push({
|
||||
id: channel.id,
|
||||
name: channel.name,
|
||||
messageCount: messages.length,
|
||||
messages,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const totalMessages = channelResults.reduce((sum, c) => sum + c.messageCount, 0);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
guildName: guild.name,
|
||||
channels: channelResults,
|
||||
totalMessages,
|
||||
timeRange: {
|
||||
start: new Date(cutoffTime).toISOString(),
|
||||
end: new Date().toISOString(),
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error occurred',
|
||||
};
|
||||
}
|
||||
}
|
||||
11
packages/tools/discord-read/tsconfig.json
Normal file
11
packages/tools/discord-read/tsconfig.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"extends": "@tpmjs/tsconfig/base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"declaration": true,
|
||||
"declarationMap": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
|
@ -63,6 +63,8 @@ export const UpdateAgentSchema = z.object({
|
|||
// Executor configuration
|
||||
executorType: ExecutorTypeSchema.nullable().optional(),
|
||||
executorConfig: ExecutorConfigUpdateSchema,
|
||||
// Tool environment variables
|
||||
envVars: z.record(z.string(), z.string()).nullable().optional(),
|
||||
});
|
||||
|
||||
export const AddCollectionToAgentSchema = z.object({
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ export const UpdateCollectionSchema = z.object({
|
|||
// Executor configuration
|
||||
executorType: ExecutorTypeSchema.nullable().optional(),
|
||||
executorConfig: ExecutorConfigUpdateSchema,
|
||||
// Tool environment variables
|
||||
envVars: z.record(z.string(), z.string()).nullable().optional(),
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue