fix(tools): fixed zendesk tools, kb upload failure for md files, stronger typing (#2297)

* fix(tools): fixed zendesk tools, kb upload failure for md files, stronger typing

* ack PR comments
This commit is contained in:
Waleed
2025-12-10 20:42:16 -08:00
committed by GitHub
parent 37d7902fcd
commit 6c99c841f4
51 changed files with 746 additions and 714 deletions

View File

@@ -4151,7 +4151,7 @@ export function DuckDuckGoIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='-108 -108 216 216'>
<circle r='108' fill='#d53' />
<circle r='96' fill='none' stroke='#ffffff' stroke-width='7' />
<circle r='96' fill='none' stroke='#ffffff' strokeWidth={7} />
<path
d='M-32-55C-62-48-51-6-51-6l19 93 7 3M-39-73h-8l11 4s-11 0-11 7c24-1 35 5 35 5'
fill='#ddd'

View File

@@ -40,7 +40,7 @@ Processes a provided thought/instruction, making it available for subsequent ste
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `thought` | string | Yes | The thought process or instruction provided by the user in the Thinking Step block. |
| `thought` | string | Yes | Your internal reasoning, analysis, or thought process. Use this to think through the problem step by step before responding. |
#### Output

View File

@@ -34,38 +34,30 @@ Integrate Translate into the workflow. Can translate text to any language.
## Tools
### `openai_chat`
### `llm_chat`
Send a chat completion request to any supported LLM provider
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `model` | string | Yes | The model to use \(e.g., gpt-4o, claude-sonnet-4-5, gemini-2.0-flash\) |
| `systemPrompt` | string | No | System prompt to set the behavior of the assistant |
| `context` | string | Yes | The user message or context to send to the model |
| `apiKey` | string | No | API key for the provider \(uses platform key if not provided for hosted models\) |
| `temperature` | number | No | Temperature for response generation \(0-2\) |
| `maxTokens` | number | No | Maximum tokens in the response |
| `azureEndpoint` | string | No | Azure OpenAI endpoint URL |
| `azureApiVersion` | string | No | Azure OpenAI API version |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `content` | string | Translated text |
| `model` | string | Model used |
| `tokens` | json | Token usage |
### `anthropic_chat`
### `google_chat`
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `content` | string | Translated text |
| `model` | string | Model used |
| `tokens` | json | Token usage |
| `content` | string | The generated response content |
| `model` | string | The model used for generation |
| `tokens` | object | Token usage information |

View File

@@ -254,8 +254,8 @@ Upload a media file (image, video, document) to WordPress.com
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `siteId` | string | Yes | WordPress.com site ID or domain \(e.g., 12345678 or mysite.wordpress.com\) |
| `file` | string | Yes | Base64 encoded file data or URL to fetch file from |
| `filename` | string | Yes | Filename with extension \(e.g., image.jpg\) |
| `file` | file | No | File to upload \(UserFile object\) |
| `filename` | string | No | Optional filename override \(e.g., image.jpg\) |
| `title` | string | No | Media title |
| `caption` | string | No | Media caption |
| `altText` | string | No | Alternative text for accessibility |

View File

@@ -173,25 +173,6 @@ Get videos from a YouTube playlist.
| --------- | ---- | ----------- |
| `items` | array | Array of videos in the playlist |
### `youtube_related_videos`
Find videos related to a specific YouTube video.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `videoId` | string | Yes | YouTube video ID to find related videos for |
| `maxResults` | number | No | Maximum number of related videos to return \(1-50\) |
| `pageToken` | string | No | Page token for pagination |
| `apiKey` | string | Yes | YouTube API Key |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `items` | array | Array of related videos |
### `youtube_comments`
Get comments from a YouTube video.

View File

@@ -76,8 +76,9 @@ Retrieve a list of tickets from Zendesk with optional filtering
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Tickets data and metadata |
| `tickets` | array | Array of ticket objects |
| `paging` | object | Pagination information |
| `metadata` | object | Operation metadata |
### `zendesk_get_ticket`
@@ -96,8 +97,8 @@ Get a single ticket by ID from Zendesk
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Ticket data |
| `ticket` | object | Ticket object |
| `metadata` | object | Operation metadata |
### `zendesk_create_ticket`
@@ -125,8 +126,8 @@ Create a new ticket in Zendesk with support for custom fields
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Created ticket data |
| `ticket` | object | Created ticket object |
| `metadata` | object | Operation metadata |
### `zendesk_create_tickets_bulk`
@@ -145,8 +146,8 @@ Create multiple tickets in Zendesk at once (max 100)
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Bulk create job status |
| `jobStatus` | object | Job status object |
| `metadata` | object | Operation metadata |
### `zendesk_update_ticket`
@@ -174,8 +175,8 @@ Update an existing ticket in Zendesk with support for custom fields
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Updated ticket data |
| `ticket` | object | Updated ticket object |
| `metadata` | object | Operation metadata |
### `zendesk_update_tickets_bulk`
@@ -199,8 +200,8 @@ Update multiple tickets in Zendesk at once (max 100)
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Bulk update job status |
| `jobStatus` | object | Job status object |
| `metadata` | object | Operation metadata |
### `zendesk_delete_ticket`
@@ -219,8 +220,8 @@ Delete a ticket from Zendesk
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Delete confirmation |
| `deleted` | boolean | Deletion success |
| `metadata` | object | Operation metadata |
### `zendesk_merge_tickets`
@@ -241,8 +242,8 @@ Merge multiple tickets into a target ticket
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Merge job status |
| `jobStatus` | object | Job status object |
| `metadata` | object | Operation metadata |
### `zendesk_get_users`
@@ -264,8 +265,9 @@ Retrieve a list of users from Zendesk with optional filtering
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Users data and metadata |
| `users` | array | Array of user objects |
| `paging` | object | Pagination information |
| `metadata` | object | Operation metadata |
### `zendesk_get_user`
@@ -284,8 +286,8 @@ Get a single user by ID from Zendesk
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | User data |
| `user` | object | User object |
| `metadata` | object | Operation metadata |
### `zendesk_get_current_user`
@@ -303,8 +305,8 @@ Get the currently authenticated user from Zendesk
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Current user data |
| `user` | object | Current user object |
| `metadata` | object | Operation metadata |
### `zendesk_search_users`
@@ -326,8 +328,9 @@ Search for users in Zendesk using a query string
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Users search results |
| `users` | array | Array of user objects |
| `paging` | object | Pagination information |
| `metadata` | object | Operation metadata |
### `zendesk_create_user`
@@ -353,8 +356,8 @@ Create a new user in Zendesk
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Created user data |
| `user` | object | Created user object |
| `metadata` | object | Operation metadata |
### `zendesk_create_users_bulk`
@@ -373,8 +376,8 @@ Create multiple users in Zendesk using bulk import
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Bulk creation job status |
| `jobStatus` | object | Job status object |
| `metadata` | object | Operation metadata |
### `zendesk_update_user`
@@ -401,8 +404,8 @@ Update an existing user in Zendesk
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Updated user data |
| `user` | object | Updated user object |
| `metadata` | object | Operation metadata |
### `zendesk_update_users_bulk`
@@ -421,8 +424,8 @@ Update multiple users in Zendesk using bulk update
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Bulk update job status |
| `jobStatus` | object | Job status object |
| `metadata` | object | Operation metadata |
### `zendesk_delete_user`
@@ -441,8 +444,8 @@ Delete a user from Zendesk
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Deleted user data |
| `deleted` | boolean | Deletion success |
| `metadata` | object | Operation metadata |
### `zendesk_get_organizations`
@@ -462,8 +465,9 @@ Retrieve a list of organizations from Zendesk
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Organizations data and metadata |
| `organizations` | array | Array of organization objects |
| `paging` | object | Pagination information |
| `metadata` | object | Operation metadata |
### `zendesk_get_organization`
@@ -482,8 +486,8 @@ Get a single organization by ID from Zendesk
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Organization data |
| `organization` | object | Organization object |
| `metadata` | object | Operation metadata |
### `zendesk_autocomplete_organizations`
@@ -504,8 +508,9 @@ Autocomplete organizations in Zendesk by name prefix (for name matching/autocomp
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Organizations search results |
| `organizations` | array | Array of organization objects |
| `paging` | object | Pagination information |
| `metadata` | object | Operation metadata |
### `zendesk_create_organization`
@@ -529,8 +534,8 @@ Create a new organization in Zendesk
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Created organization data |
| `organization` | object | Created organization object |
| `metadata` | object | Operation metadata |
### `zendesk_create_organizations_bulk`
@@ -549,8 +554,8 @@ Create multiple organizations in Zendesk using bulk import
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Bulk creation job status |
| `jobStatus` | object | Job status object |
| `metadata` | object | Operation metadata |
### `zendesk_update_organization`
@@ -575,8 +580,8 @@ Update an existing organization in Zendesk
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Updated organization data |
| `organization` | object | Updated organization object |
| `metadata` | object | Operation metadata |
### `zendesk_delete_organization`
@@ -595,8 +600,8 @@ Delete an organization from Zendesk
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Deleted organization data |
| `deleted` | boolean | Deletion success |
| `metadata` | object | Operation metadata |
### `zendesk_search`
@@ -619,8 +624,9 @@ Unified search across tickets, users, and organizations in Zendesk
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Search results |
| `results` | array | Array of result objects |
| `paging` | object | Pagination information |
| `metadata` | object | Operation metadata |
### `zendesk_search_count`
@@ -639,8 +645,8 @@ Count the number of search results matching a query in Zendesk
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Search count result |
| `count` | number | Number of matching results |
| `metadata` | object | Operation metadata |

View File

@@ -0,0 +1,224 @@
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request'
import { createLogger } from '@/lib/logs/console/logger'
import {
getFileExtension,
getMimeTypeFromExtension,
processSingleFileToUserFile,
} from '@/lib/uploads/utils/file-utils'
import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server'
export const dynamic = 'force-dynamic'
const logger = createLogger('WordPressUploadAPI')
const WORDPRESS_COM_API_BASE = 'https://public-api.wordpress.com/wp/v2/sites'
const WordPressUploadSchema = z.object({
accessToken: z.string().min(1, 'Access token is required'),
siteId: z.string().min(1, 'Site ID is required'),
file: z.any().optional().nullable(),
filename: z.string().optional().nullable(),
title: z.string().optional().nullable(),
caption: z.string().optional().nullable(),
altText: z.string().optional().nullable(),
description: z.string().optional().nullable(),
})
export async function POST(request: NextRequest) {
const requestId = generateRequestId()
try {
const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized WordPress upload attempt: ${authResult.error}`)
return NextResponse.json(
{
success: false,
error: authResult.error || 'Authentication required',
},
{ status: 401 }
)
}
logger.info(
`[${requestId}] Authenticated WordPress upload request via ${authResult.authType}`,
{
userId: authResult.userId,
}
)
const body = await request.json()
const validatedData = WordPressUploadSchema.parse(body)
logger.info(`[${requestId}] Uploading file to WordPress`, {
siteId: validatedData.siteId,
filename: validatedData.filename,
hasFile: !!validatedData.file,
})
if (!validatedData.file) {
return NextResponse.json(
{
success: false,
error: 'No file provided. Please upload a file.',
},
{ status: 400 }
)
}
// Process file - convert to UserFile format if needed
const fileData = validatedData.file
let userFile
try {
userFile = processSingleFileToUserFile(fileData, requestId, logger)
} catch (error) {
return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : 'Failed to process file',
},
{ status: 400 }
)
}
logger.info(`[${requestId}] Downloading file from storage`, {
fileName: userFile.name,
key: userFile.key,
size: userFile.size,
})
let fileBuffer: Buffer
try {
fileBuffer = await downloadFileFromStorage(userFile, requestId, logger)
} catch (error) {
logger.error(`[${requestId}] Failed to download file:`, error)
return NextResponse.json(
{
success: false,
error: `Failed to download file: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
{ status: 500 }
)
}
// Use provided filename or fall back to the original file name
const filename = validatedData.filename || userFile.name
const mimeType = userFile.type || getMimeTypeFromExtension(getFileExtension(filename))
logger.info(`[${requestId}] Uploading to WordPress`, {
siteId: validatedData.siteId,
filename,
mimeType,
size: fileBuffer.length,
})
// Upload to WordPress using multipart form data
const formData = new FormData()
// Convert Buffer to Uint8Array for Blob compatibility
const uint8Array = new Uint8Array(fileBuffer)
const blob = new Blob([uint8Array], { type: mimeType })
formData.append('file', blob, filename)
// Add optional metadata
if (validatedData.title) {
formData.append('title', validatedData.title)
}
if (validatedData.caption) {
formData.append('caption', validatedData.caption)
}
if (validatedData.altText) {
formData.append('alt_text', validatedData.altText)
}
if (validatedData.description) {
formData.append('description', validatedData.description)
}
const uploadResponse = await fetch(`${WORDPRESS_COM_API_BASE}/${validatedData.siteId}/media`, {
method: 'POST',
headers: {
Authorization: `Bearer ${validatedData.accessToken}`,
},
body: formData,
})
if (!uploadResponse.ok) {
const errorText = await uploadResponse.text()
let errorMessage = `WordPress API error: ${uploadResponse.statusText}`
try {
const errorJson = JSON.parse(errorText)
errorMessage = errorJson.message || errorJson.error || errorMessage
} catch {
// Use default error message
}
logger.error(`[${requestId}] WordPress API error:`, {
status: uploadResponse.status,
statusText: uploadResponse.statusText,
error: errorText,
})
return NextResponse.json(
{
success: false,
error: errorMessage,
},
{ status: uploadResponse.status }
)
}
const uploadData = await uploadResponse.json()
logger.info(`[${requestId}] File uploaded successfully`, {
mediaId: uploadData.id,
sourceUrl: uploadData.source_url,
})
return NextResponse.json({
success: true,
output: {
media: {
id: uploadData.id,
date: uploadData.date,
slug: uploadData.slug,
type: uploadData.type,
link: uploadData.link,
title: uploadData.title,
caption: uploadData.caption,
alt_text: uploadData.alt_text,
media_type: uploadData.media_type,
mime_type: uploadData.mime_type,
source_url: uploadData.source_url,
media_details: uploadData.media_details,
},
},
})
} catch (error) {
if (error instanceof z.ZodError) {
logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors })
return NextResponse.json(
{
success: false,
error: 'Invalid request data',
details: error.errors,
},
{ status: 400 }
)
}
logger.error(`[${requestId}] Error uploading file to WordPress:`, error)
return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : 'Internal server error',
},
{ status: 500 }
)
}
}

View File

@@ -12,7 +12,6 @@ import {
ModalFooter,
ModalHeader,
} from '@/components/emcn'
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
import { cn } from '@/lib/core/utils/cn'
import { createLogger } from '@/lib/logs/console/logger'
import { formatFileSize, validateKnowledgeBaseFile } from '@/lib/uploads/utils/file-utils'
@@ -53,7 +52,7 @@ export function AddDocumentsModal({
const [dragCounter, setDragCounter] = useState(0)
const [retryingIndexes, setRetryingIndexes] = useState<Set<number>>(new Set())
const { isUploading, uploadProgress, uploadFiles, clearError } = useKnowledgeUpload({
const { isUploading, uploadProgress, uploadFiles, uploadError, clearError } = useKnowledgeUpload({
workspaceId,
onUploadComplete: () => {
logger.info(`Successfully uploaded ${files.length} files`)
@@ -234,11 +233,7 @@ export function AddDocumentsModal({
<div className='min-h-0 flex-1 overflow-y-auto'>
<div className='space-y-[12px]'>
{fileError && (
<Alert variant='destructive'>
<AlertCircle className='h-4 w-4' />
<AlertTitle>Error</AlertTitle>
<AlertDescription>{fileError}</AlertDescription>
</Alert>
<p className='text-[11px] text-[var(--text-error)] leading-tight'>{fileError}</p>
)}
<div className='flex flex-col gap-[8px]'>
@@ -341,24 +336,31 @@ export function AddDocumentsModal({
</div>
</ModalBody>
<ModalFooter>
<Button variant='default' onClick={handleClose} type='button' disabled={isUploading}>
Cancel
</Button>
<Button
variant='primary'
type='button'
onClick={handleUpload}
disabled={files.length === 0 || isUploading}
>
{isUploading
? uploadProgress.stage === 'uploading'
? `Uploading ${uploadProgress.filesCompleted}/${uploadProgress.totalFiles}...`
: uploadProgress.stage === 'processing'
? 'Processing...'
: 'Uploading...'
: 'Upload'}
</Button>
<ModalFooter className='flex-col items-stretch gap-[12px]'>
{uploadError && (
<p className='text-[11px] text-[var(--text-error)] leading-tight'>
{uploadError.message}
</p>
)}
<div className='flex justify-end gap-[8px]'>
<Button variant='default' onClick={handleClose} type='button' disabled={isUploading}>
Cancel
</Button>
<Button
variant='primary'
type='button'
onClick={handleUpload}
disabled={files.length === 0 || isUploading}
>
{isUploading
? uploadProgress.stage === 'uploading'
? `Uploading ${uploadProgress.filesCompleted}/${uploadProgress.totalFiles}...`
: uploadProgress.stage === 'processing'
? 'Processing...'
: 'Uploading...'
: 'Upload'}
</Button>
</div>
</ModalFooter>
</ModalContent>
</Modal>

View File

@@ -17,7 +17,6 @@ import {
ModalHeader,
Textarea,
} from '@/components/emcn'
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
import { cn } from '@/lib/core/utils/cn'
import { createLogger } from '@/lib/logs/console/logger'
import { formatFileSize, validateKnowledgeBaseFile } from '@/lib/uploads/utils/file-utils'
@@ -89,7 +88,7 @@ export function CreateBaseModal({
const scrollContainerRef = useRef<HTMLDivElement>(null)
const { uploadFiles, isUploading, uploadProgress, clearError } = useKnowledgeUpload({
const { uploadFiles, isUploading, uploadProgress, uploadError, clearError } = useKnowledgeUpload({
workspaceId,
onUploadComplete: (uploadedFiles) => {
logger.info(`Successfully uploaded ${uploadedFiles.length} files`)
@@ -280,21 +279,35 @@ export function CreateBaseModal({
const newKnowledgeBase = result.data
if (files.length > 0) {
newKnowledgeBase.docCount = files.length
try {
const uploadedFiles = await uploadFiles(files, newKnowledgeBase.id, {
chunkSize: data.maxChunkSize,
minCharactersPerChunk: data.minChunkSize,
chunkOverlap: data.overlapSize,
recipe: 'default',
})
if (onKnowledgeBaseCreated) {
onKnowledgeBaseCreated(newKnowledgeBase)
logger.info(`Successfully uploaded ${uploadedFiles.length} files`)
logger.info(`Started processing ${uploadedFiles.length} documents in the background`)
newKnowledgeBase.docCount = uploadedFiles.length
if (onKnowledgeBaseCreated) {
onKnowledgeBaseCreated(newKnowledgeBase)
}
} catch (uploadError) {
// If file upload fails completely, delete the knowledge base to avoid orphaned empty KB
logger.error('File upload failed, deleting knowledge base:', uploadError)
try {
await fetch(`/api/knowledge/${newKnowledgeBase.id}`, {
method: 'DELETE',
})
logger.info(`Deleted orphaned knowledge base: ${newKnowledgeBase.id}`)
} catch (deleteError) {
logger.error('Failed to delete orphaned knowledge base:', deleteError)
}
throw uploadError
}
const uploadedFiles = await uploadFiles(files, newKnowledgeBase.id, {
chunkSize: data.maxChunkSize,
minCharactersPerChunk: data.minChunkSize,
chunkOverlap: data.overlapSize,
recipe: 'default',
})
logger.info(`Successfully uploaded ${uploadedFiles.length} files`)
logger.info(`Started processing ${uploadedFiles.length} documents in the background`)
} else {
if (onKnowledgeBaseCreated) {
onKnowledgeBaseCreated(newKnowledgeBase)
@@ -325,14 +338,6 @@ export function CreateBaseModal({
<ModalBody className='!pb-[16px]'>
<div ref={scrollContainerRef} className='min-h-0 flex-1 overflow-y-auto'>
<div className='space-y-[12px]'>
{submitStatus && submitStatus.type === 'error' && (
<Alert variant='destructive'>
<AlertCircle className='h-4 w-4' />
<AlertTitle>Error</AlertTitle>
<AlertDescription>{submitStatus.message}</AlertDescription>
</Alert>
)}
<div className='flex flex-col gap-[8px]'>
<Label htmlFor='name'>Name</Label>
<Input
@@ -498,36 +503,39 @@ export function CreateBaseModal({
)}
{fileError && (
<Alert variant='destructive'>
<AlertCircle className='h-4 w-4' />
<AlertTitle>Error</AlertTitle>
<AlertDescription>{fileError}</AlertDescription>
</Alert>
<p className='text-[11px] text-[var(--text-error)] leading-tight'>{fileError}</p>
)}
</div>
</div>
</ModalBody>
<ModalFooter>
<Button
variant='default'
onClick={() => handleClose(false)}
type='button'
disabled={isSubmitting}
>
Cancel
</Button>
<Button variant='primary' type='submit' disabled={isSubmitting || !nameValue?.trim()}>
{isSubmitting
? isUploading
? uploadProgress.stage === 'uploading'
? `Uploading ${uploadProgress.filesCompleted}/${uploadProgress.totalFiles}...`
: uploadProgress.stage === 'processing'
? 'Processing...'
: 'Creating...'
: 'Creating...'
: 'Create'}
</Button>
<ModalFooter className='flex-col items-stretch gap-[12px]'>
{(submitStatus?.type === 'error' || uploadError) && (
<p className='text-[11px] text-[var(--text-error)] leading-tight'>
{uploadError?.message || submitStatus?.message}
</p>
)}
<div className='flex justify-end gap-[8px]'>
<Button
variant='default'
onClick={() => handleClose(false)}
type='button'
disabled={isSubmitting}
>
Cancel
</Button>
<Button variant='primary' type='submit' disabled={isSubmitting || !nameValue?.trim()}>
{isSubmitting
? isUploading
? uploadProgress.stage === 'uploading'
? `Uploading ${uploadProgress.filesCompleted}/${uploadProgress.totalFiles}...`
: uploadProgress.stage === 'processing'
? 'Processing...'
: 'Creating...'
: 'Creating...'
: 'Create'}
</Button>
</div>
</ModalFooter>
</form>
</ModalContent>

View File

@@ -1,5 +1,6 @@
import { useCallback, useState } from 'react'
import { createLogger } from '@/lib/logs/console/logger'
import { getFileExtension, getMimeTypeFromExtension } from '@/lib/uploads/utils/file-utils'
const logger = createLogger('KnowledgeUpload')
@@ -143,6 +144,17 @@ const calculateThroughputMbps = (bytes: number, durationMs: number) => {
*/
const formatDurationSeconds = (durationMs: number) => Number((durationMs / 1000).toFixed(2))
/**
* Gets the content type for a file, falling back to extension-based lookup if browser doesn't provide one
*/
const getFileContentType = (file: File): string => {
if (file.type?.trim()) {
return file.type
}
const extension = getFileExtension(file.name)
return getMimeTypeFromExtension(extension)
}
/**
* Runs async operations with concurrency limit
*/
@@ -280,7 +292,7 @@ const getPresignedData = async (
},
body: JSON.stringify({
fileName: file.name,
contentType: file.type,
contentType: getFileContentType(file),
fileSize: file.size,
}),
signal: localController.signal,
@@ -529,7 +541,9 @@ export function useKnowledgeUpload(options: UseKnowledgeUploadOptions = {}) {
throughputMbps: calculateThroughputMbps(file.size, durationMs),
status: xhr.status,
})
resolve(createUploadedFile(file.name, fullFileUrl, file.size, file.type, file))
resolve(
createUploadedFile(file.name, fullFileUrl, file.size, getFileContentType(file), file)
)
} else {
logger.error('S3 PUT request failed', {
status: xhr.status,
@@ -597,7 +611,7 @@ export function useKnowledgeUpload(options: UseKnowledgeUploadOptions = {}) {
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
fileName: file.name,
contentType: file.type,
contentType: getFileContentType(file),
fileSize: file.size,
}),
})
@@ -736,7 +750,7 @@ export function useKnowledgeUpload(options: UseKnowledgeUploadOptions = {}) {
const fullFileUrl = path.startsWith('http') ? path : `${window.location.origin}${path}`
return createUploadedFile(file.name, fullFileUrl, file.size, file.type, file)
return createUploadedFile(file.name, fullFileUrl, file.size, getFileContentType(file), file)
} catch (error) {
logger.error(`Multipart upload failed for ${file.name}:`, error)
const durationMs = getHighResTime() - startTime
@@ -800,7 +814,7 @@ export function useKnowledgeUpload(options: UseKnowledgeUploadOptions = {}) {
file.name,
filePath.startsWith('http') ? filePath : `${window.location.origin}${filePath}`,
file.size,
file.type,
getFileContentType(file),
file
)
} finally {
@@ -855,7 +869,7 @@ export function useKnowledgeUpload(options: UseKnowledgeUploadOptions = {}) {
const batchRequest = {
files: batchFiles.map((file) => ({
fileName: file.name,
contentType: file.type,
contentType: getFileContentType(file),
fileSize: file.size,
})),
}

View File

@@ -1,4 +1,6 @@
import type React from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { RepeatIcon, SplitIcon } from 'lucide-react'
import { useShallow } from 'zustand/react/shallow'
import {
Popover,
@@ -349,14 +351,24 @@ const getCaretViewportPosition = (
}
/**
* Renders a tag icon with background color
* Renders a tag icon with background color - can use either a React icon component or a letter
*/
const TagIcon: React.FC<{ icon: string; color: string }> = ({ icon, color }) => (
const TagIcon: React.FC<{
icon: string | React.ComponentType<{ className?: string }>
color: string
}> = ({ icon, color }) => (
<div
className='flex h-[14px] w-[14px] flex-shrink-0 items-center justify-center rounded'
style={{ backgroundColor: color }}
style={{ background: color }}
>
<span className='font-bold text-[10px] text-white'>{icon}</span>
{typeof icon === 'string' ? (
<span className='font-bold text-[10px] text-white'>{icon}</span>
) : (
(() => {
const IconComponent = icon
return <IconComponent className='h-[9px] w-[9px] text-white' />
})()
)}
</div>
)
@@ -1385,7 +1397,17 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
blockColor = BLOCK_COLORS.PARALLEL
}
const tagIcon = group.blockName.charAt(0).toUpperCase()
// Use actual block icon if available, otherwise fall back to special icons for loop/parallel or first letter
let tagIcon: string | React.ComponentType<{ className?: string }> = group.blockName
.charAt(0)
.toUpperCase()
if (blockConfig?.icon) {
tagIcon = blockConfig.icon
} else if (group.blockType === 'loop') {
tagIcon = RepeatIcon
} else if (group.blockType === 'parallel') {
tagIcon = SplitIcon
}
return (
<div key={group.blockId}>

View File

@@ -1,28 +1,15 @@
import { TranslateIcon } from '@/components/icons'
import { isHosted } from '@/lib/core/config/environment'
import { AuthMode, type BlockConfig } from '@/blocks/types'
import {
getAllModelProviders,
getHostedModels,
getProviderIcon,
providers,
} from '@/providers/utils'
import { getHostedModels, getProviderIcon, providers } from '@/providers/utils'
import { useProvidersStore } from '@/stores/providers/store'
const getCurrentOllamaModels = () => {
return useProvidersStore.getState().providers.ollama.models
}
const getTranslationPrompt = (
targetLanguage: string
) => `You are a highly skilled translator. Your task is to translate the given text into ${targetLanguage || 'English'} while:
1. Preserving the original meaning and nuance
2. Maintaining appropriate formality levels
3. Adapting idioms and cultural references appropriately
4. Preserving formatting and special characters
5. Handling technical terms accurately
Only return the translated text without any explanations or notes. The translation should be natural and fluent in ${targetLanguage || 'English'}.`
const getTranslationPrompt = (targetLanguage: string) =>
`Translate the following text into ${targetLanguage || 'English'}. Output ONLY the translated text with no additional commentary, explanations, or notes.`
export const TranslateBlock: BlockConfig = {
type: 'translate',
@@ -123,19 +110,17 @@ export const TranslateBlock: BlockConfig = {
},
],
tools: {
access: ['openai_chat', 'anthropic_chat', 'google_chat'],
access: ['llm_chat'],
config: {
tool: (params: Record<string, any>) => {
const model = params.model || 'gpt-4o'
if (!model) {
throw new Error('No model selected')
}
const tool = getAllModelProviders()[model]
if (!tool) {
throw new Error(`Invalid model selected: ${model}`)
}
return tool
},
tool: () => 'llm_chat',
params: (params: Record<string, any>) => ({
model: params.model,
systemPrompt: getTranslationPrompt(params.targetLanguage || 'English'),
context: params.context,
apiKey: params.apiKey,
azureEndpoint: params.azureEndpoint,
azureApiVersion: params.azureApiVersion,
}),
},
},
inputs: {

View File

@@ -272,22 +272,35 @@ export const WordPressBlock: BlockConfig<WordPressResponse> = {
},
},
// Media Operations
// Media Operations - File upload (basic mode)
{
id: 'fileUpload',
title: 'Upload File',
type: 'file-upload',
canonicalParamId: 'file',
placeholder: 'Upload a media file to WordPress',
condition: { field: 'operation', value: 'wordpress_upload_media' },
mode: 'basic',
multiple: false,
required: false,
},
// Variable reference (advanced mode) - for referencing files from previous blocks
{
id: 'file',
title: 'File',
title: 'File Reference',
type: 'short-input',
placeholder: 'Base64 encoded file data or file URL',
canonicalParamId: 'file',
placeholder: 'Reference file from previous block (e.g., {{block_name.file}})',
condition: { field: 'operation', value: 'wordpress_upload_media' },
required: { field: 'operation', value: 'wordpress_upload_media' },
mode: 'advanced',
required: false,
},
{
id: 'filename',
title: 'Filename',
title: 'Filename Override',
type: 'short-input',
placeholder: 'image.jpg',
placeholder: 'Optional: Override filename (e.g., image.jpg)',
condition: { field: 'operation', value: 'wordpress_upload_media' },
required: { field: 'operation', value: 'wordpress_upload_media' },
},
{
id: 'mediaTitle',
@@ -756,7 +769,7 @@ export const WordPressBlock: BlockConfig<WordPressResponse> = {
case 'wordpress_upload_media':
return {
...baseParams,
file: params.file,
file: params.fileUpload || params.file,
filename: params.filename,
title: params.mediaTitle,
caption: params.caption,
@@ -891,8 +904,9 @@ export const WordPressBlock: BlockConfig<WordPressResponse> = {
parent: { type: 'number', description: 'Parent page ID' },
menuOrder: { type: 'number', description: 'Menu order' },
// Media inputs
file: { type: 'string', description: 'File data (base64) or URL' },
filename: { type: 'string', description: 'Filename with extension' },
fileUpload: { type: 'json', description: 'File to upload (UserFile object)' },
file: { type: 'json', description: 'File reference from previous block' },
filename: { type: 'string', description: 'Optional filename override' },
mediaTitle: { type: 'string', description: 'Media title' },
caption: { type: 'string', description: 'Media caption' },
altText: { type: 'string', description: 'Alt text' },

View File

@@ -26,7 +26,6 @@ export const YouTubeBlock: BlockConfig<YouTubeResponse> = {
{ label: 'Get Channel Videos', id: 'youtube_channel_videos' },
{ label: 'Get Channel Playlists', id: 'youtube_channel_playlists' },
{ label: 'Get Playlist Items', id: 'youtube_playlist_items' },
{ label: 'Get Related Videos', id: 'youtube_related_videos' },
{ label: 'Get Video Comments', id: 'youtube_comments' },
],
value: () => 'youtube_search',
@@ -250,25 +249,6 @@ export const YouTubeBlock: BlockConfig<YouTubeResponse> = {
integer: true,
condition: { field: 'operation', value: 'youtube_playlist_items' },
},
// Get Related Videos operation inputs
{
id: 'videoId',
title: 'Video ID',
type: 'short-input',
placeholder: 'Enter YouTube video ID to find related videos',
required: true,
condition: { field: 'operation', value: 'youtube_related_videos' },
},
{
id: 'maxResults',
title: 'Max Results',
type: 'slider',
min: 1,
max: 50,
step: 1,
integer: true,
condition: { field: 'operation', value: 'youtube_related_videos' },
},
// Get Video Comments operation inputs
{
id: 'videoId',
@@ -317,7 +297,6 @@ export const YouTubeBlock: BlockConfig<YouTubeResponse> = {
'youtube_channel_videos',
'youtube_channel_playlists',
'youtube_playlist_items',
'youtube_related_videos',
'youtube_comments',
],
config: {
@@ -340,8 +319,6 @@ export const YouTubeBlock: BlockConfig<YouTubeResponse> = {
return 'youtube_channel_playlists'
case 'youtube_playlist_items':
return 'youtube_playlist_items'
case 'youtube_related_videos':
return 'youtube_related_videos'
case 'youtube_comments':
return 'youtube_comments'
default:

View File

@@ -504,7 +504,48 @@ export const ZendeskBlock: BlockConfig = {
subdomain: { type: 'string', description: 'Zendesk subdomain' },
},
outputs: {
success: { type: 'boolean', description: 'Operation success status' },
output: { type: 'json', description: 'Operation result data' },
// Ticket operations - list
tickets: { type: 'json', description: 'Array of ticket objects (get_tickets)' },
// Ticket operations - single
ticket: {
type: 'json',
description: 'Single ticket object (get_ticket, create_ticket, update_ticket)',
},
// User operations - list
users: { type: 'json', description: 'Array of user objects (get_users, search_users)' },
// User operations - single
user: {
type: 'json',
description: 'Single user object (get_user, get_current_user, create_user, update_user)',
},
// Organization operations - list
organizations: {
type: 'json',
description: 'Array of organization objects (get_organizations, autocomplete_organizations)',
},
// Organization operations - single
organization: {
type: 'json',
description:
'Single organization object (get_organization, create_organization, update_organization)',
},
// Search operations
results: { type: 'json', description: 'Array of search result objects (search)' },
count: { type: 'number', description: 'Number of matching results (search_count)' },
// Bulk/async operations
jobStatus: {
type: 'json',
description:
'Job status for async operations (create_tickets_bulk, update_tickets_bulk, merge_tickets, create_users_bulk, update_users_bulk, create_organizations_bulk)',
},
// Delete operations
deleted: {
type: 'boolean',
description: 'Deletion confirmation (delete_ticket, delete_user, delete_organization)',
},
// Pagination (shared across list operations)
paging: { type: 'json', description: 'Pagination information for list operations' },
// Metadata (shared across all operations)
metadata: { type: 'json', description: 'Operation metadata including operation type' },
},
}

View File

@@ -133,6 +133,11 @@ export function validateFileType(fileName: string, mimeType: string): FileValida
const baseMimeType = mimeType.split(';')[0].trim()
// Allow empty MIME types if the extension is supported (browsers often don't recognize certain file types)
if (!baseMimeType) {
return null
}
const allowedMimeTypes = SUPPORTED_MIME_TYPES[extension]
if (!allowedMimeTypes.includes(baseMimeType)) {
return {

130
apps/sim/tools/llm/chat.ts Normal file
View File

@@ -0,0 +1,130 @@
import { createLogger } from '@/lib/logs/console/logger'
import { getProviderFromModel } from '@/providers/utils'
import type { ToolConfig, ToolResponse } from '@/tools/types'
const logger = createLogger('LLMChatTool')
interface LLMChatParams {
model: string
systemPrompt?: string
context: string
apiKey?: string
temperature?: number
maxTokens?: number
azureEndpoint?: string
azureApiVersion?: string
}
interface LLMChatResponse extends ToolResponse {
output: {
content: string
model: string
tokens?: {
prompt?: number
completion?: number
total?: number
}
}
}
export const llmChatTool: ToolConfig<LLMChatParams, LLMChatResponse> = {
id: 'llm_chat',
name: 'LLM Chat',
description: 'Send a chat completion request to any supported LLM provider',
version: '1.0.0',
params: {
model: {
type: 'string',
required: true,
description: 'The model to use (e.g., gpt-4o, claude-sonnet-4-5, gemini-2.0-flash)',
},
systemPrompt: {
type: 'string',
required: false,
description: 'System prompt to set the behavior of the assistant',
},
context: {
type: 'string',
required: true,
description: 'The user message or context to send to the model',
},
apiKey: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'API key for the provider (uses platform key if not provided for hosted models)',
},
temperature: {
type: 'number',
required: false,
description: 'Temperature for response generation (0-2)',
},
maxTokens: {
type: 'number',
required: false,
description: 'Maximum tokens in the response',
},
azureEndpoint: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'Azure OpenAI endpoint URL',
},
azureApiVersion: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'Azure OpenAI API version',
},
},
request: {
url: () => '/api/providers',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => {
const provider = getProviderFromModel(params.model)
return {
provider,
model: params.model,
systemPrompt: params.systemPrompt,
context: JSON.stringify([{ role: 'user', content: params.context }]),
apiKey: params.apiKey,
temperature: params.temperature,
maxTokens: params.maxTokens,
azureEndpoint: params.azureEndpoint,
azureApiVersion: params.azureApiVersion,
}
},
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
const errorMessage = errorData.error || `LLM API error: ${response.status}`
logger.error('LLM chat request failed', { error: errorMessage })
throw new Error(errorMessage)
}
const data = await response.json()
return {
success: true,
output: {
content: data.content,
model: data.model,
tokens: data.tokens,
},
}
},
outputs: {
content: { type: 'string', description: 'The generated response content' },
model: { type: 'string', description: 'The model used for generation' },
tokens: { type: 'object', description: 'Token usage information' },
},
}

View File

@@ -0,0 +1 @@
export { llmChatTool } from './chat'

View File

@@ -579,6 +579,7 @@ import {
} from '@/tools/linear'
import { linkedInGetProfileTool, linkedInSharePostTool } from '@/tools/linkedin'
import { linkupSearchTool } from '@/tools/linkup'
import { llmChatTool } from '@/tools/llm'
import {
mailchimpAddMemberTagsTool,
mailchimpAddMemberTool,
@@ -1256,7 +1257,6 @@ import {
youtubeChannelVideosTool,
youtubeCommentsTool,
youtubePlaylistItemsTool,
youtubeRelatedVideosTool,
youtubeSearchTool,
youtubeVideoDetailsTool,
} from '@/tools/youtube'
@@ -1327,6 +1327,7 @@ export const tools: Record<string, ToolConfig> = {
openai_embeddings: openAIEmbeddingsTool,
http_request: httpRequestTool,
huggingface_chat: huggingfaceChatTool,
llm_chat: llmChatTool,
function_execute: functionExecuteTool,
vision_tool: visionTool,
file_parser: fileParseTool,
@@ -1526,7 +1527,6 @@ export const tools: Record<string, ToolConfig> = {
youtube_comments: youtubeCommentsTool,
youtube_channel_videos: youtubeChannelVideosTool,
youtube_channel_playlists: youtubeChannelPlaylistsTool,
youtube_related_videos: youtubeRelatedVideosTool,
notion_read: notionReadTool,
notion_read_database: notionReadDatabaseTool,
notion_write: notionWriteTool,

View File

@@ -12,9 +12,9 @@ export const thinkingTool: ToolConfig<ThinkingToolParams, ThinkingToolResponse>
thought: {
type: 'string',
required: true,
visibility: 'hidden',
visibility: 'llm-only',
description:
'The thought process or instruction provided by the user in the Thinking Step block.',
'Your internal reasoning, analysis, or thought process. Use this to think through the problem step by step before responding.',
},
},

View File

@@ -254,8 +254,8 @@ export interface WordPressListPagesResponse extends ToolResponse {
// Upload Media
export interface WordPressUploadMediaParams extends WordPressBaseParams {
file: string // Base64 encoded file data or URL
filename: string
file: any // UserFile object from file upload
filename?: string // Optional filename override
title?: string
caption?: string
altText?: string

View File

@@ -1,9 +1,8 @@
import { createLogger } from '@/lib/logs/console/logger'
import type { ToolConfig } from '@/tools/types'
import {
WORDPRESS_COM_API_BASE,
type WordPressUploadMediaParams,
type WordPressUploadMediaResponse,
} from './types'
import type { WordPressUploadMediaParams, WordPressUploadMediaResponse } from './types'
const logger = createLogger('WordPressUploadMediaTool')
export const uploadMediaTool: ToolConfig<WordPressUploadMediaParams, WordPressUploadMediaResponse> =
{
@@ -26,16 +25,16 @@ export const uploadMediaTool: ToolConfig<WordPressUploadMediaParams, WordPressUp
description: 'WordPress.com site ID or domain (e.g., 12345678 or mysite.wordpress.com)',
},
file: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Base64 encoded file data or URL to fetch file from',
type: 'file',
required: false,
visibility: 'user-only',
description: 'File to upload (UserFile object)',
},
filename: {
type: 'string',
required: true,
required: false,
visibility: 'user-or-llm',
description: 'Filename with extension (e.g., image.jpg)',
description: 'Optional filename override (e.g., image.jpg)',
},
title: {
type: 'string',
@@ -64,66 +63,37 @@ export const uploadMediaTool: ToolConfig<WordPressUploadMediaParams, WordPressUp
},
request: {
url: (params) => `${WORDPRESS_COM_API_BASE}/${params.siteId}/media`,
url: () => '/api/tools/wordpress/upload',
method: 'POST',
headers: (params) => {
// Determine content type from filename
const ext = params.filename.split('.').pop()?.toLowerCase() || ''
const mimeTypes: Record<string, string> = {
jpg: 'image/jpeg',
jpeg: 'image/jpeg',
png: 'image/png',
gif: 'image/gif',
webp: 'image/webp',
svg: 'image/svg+xml',
pdf: 'application/pdf',
mp4: 'video/mp4',
mp3: 'audio/mpeg',
wav: 'audio/wav',
doc: 'application/msword',
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
}
const contentType = mimeTypes[ext] || 'application/octet-stream'
return {
'Content-Type': contentType,
'Content-Disposition': `attachment; filename="${params.filename}"`,
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => {
// If the file is a base64 string, we need to decode it
// The body function returns the data directly for binary uploads
// In this case, we return the file data as-is and let the executor handle it
return params.file as any
},
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
accessToken: params.accessToken,
siteId: params.siteId,
file: params.file,
filename: params.filename,
title: params.title,
caption: params.caption,
altText: params.altText,
description: params.description,
}),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const error = await response.json().catch(() => ({}))
throw new Error(error.message || `WordPress API error: ${response.status}`)
}
const data = await response.json()
if (!data.success) {
logger.error('Failed to upload media via custom API route', {
error: data.error,
})
throw new Error(data.error || 'Failed to upload media to WordPress')
}
return {
success: true,
output: {
media: {
id: data.id,
date: data.date,
slug: data.slug,
type: data.type,
link: data.link,
title: data.title,
caption: data.caption,
alt_text: data.alt_text,
media_type: data.media_type,
mime_type: data.mime_type,
source_url: data.source_url,
media_details: data.media_details,
},
media: data.output.media,
},
}
},

View File

@@ -3,7 +3,6 @@ import { youtubeChannelPlaylistsTool } from '@/tools/youtube/channel_playlists'
import { youtubeChannelVideosTool } from '@/tools/youtube/channel_videos'
import { youtubeCommentsTool } from '@/tools/youtube/comments'
import { youtubePlaylistItemsTool } from '@/tools/youtube/playlist_items'
import { youtubeRelatedVideosTool } from '@/tools/youtube/related_videos'
import { youtubeSearchTool } from '@/tools/youtube/search'
import { youtubeVideoDetailsTool } from '@/tools/youtube/video_details'
@@ -14,4 +13,3 @@ export { youtubePlaylistItemsTool }
export { youtubeCommentsTool }
export { youtubeChannelVideosTool }
export { youtubeChannelPlaylistsTool }
export { youtubeRelatedVideosTool }

View File

@@ -1,108 +0,0 @@
import type { ToolConfig } from '@/tools/types'
import type {
YouTubeRelatedVideosParams,
YouTubeRelatedVideosResponse,
} from '@/tools/youtube/types'
export const youtubeRelatedVideosTool: ToolConfig<
YouTubeRelatedVideosParams,
YouTubeRelatedVideosResponse
> = {
id: 'youtube_related_videos',
name: 'YouTube Related Videos',
description: 'Find videos related to a specific YouTube video.',
version: '1.0.0',
params: {
videoId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'YouTube video ID to find related videos for',
},
maxResults: {
type: 'number',
required: false,
visibility: 'user-only',
default: 10,
description: 'Maximum number of related videos to return (1-50)',
},
pageToken: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Page token for pagination',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'YouTube API Key',
},
},
request: {
url: (params: YouTubeRelatedVideosParams) => {
let url = `https://www.googleapis.com/youtube/v3/search?part=snippet&type=video&relatedToVideoId=${encodeURIComponent(
params.videoId
)}&key=${params.apiKey}`
url += `&maxResults=${Number(params.maxResults || 10)}`
if (params.pageToken) {
url += `&pageToken=${params.pageToken}`
}
return url
},
method: 'GET',
headers: () => ({
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response): Promise<YouTubeRelatedVideosResponse> => {
const data = await response.json()
const items = (data.items || []).map((item: any) => ({
videoId: item.id?.videoId,
title: item.snippet?.title,
description: item.snippet?.description,
thumbnail:
item.snippet?.thumbnails?.medium?.url ||
item.snippet?.thumbnails?.default?.url ||
item.snippet?.thumbnails?.high?.url ||
'',
channelTitle: item.snippet?.channelTitle || '',
}))
return {
success: true,
output: {
items,
totalResults: data.pageInfo?.totalResults || 0,
nextPageToken: data.nextPageToken,
},
}
},
outputs: {
items: {
type: 'array',
description: 'Array of related videos',
items: {
type: 'object',
properties: {
videoId: { type: 'string', description: 'YouTube video ID' },
title: { type: 'string', description: 'Video title' },
description: { type: 'string', description: 'Video description' },
thumbnail: { type: 'string', description: 'Video thumbnail URL' },
channelTitle: { type: 'string', description: 'Channel name' },
},
},
},
totalResults: {
type: 'number',
description: 'Total number of related videos available',
},
nextPageToken: {
type: 'string',
description: 'Token for accessing the next page of results',
optional: true,
},
},
}

View File

@@ -166,27 +166,6 @@ export interface YouTubeChannelPlaylistsResponse extends ToolResponse {
}
}
export interface YouTubeRelatedVideosParams {
apiKey: string
videoId: string
maxResults?: number
pageToken?: string
}
export interface YouTubeRelatedVideosResponse extends ToolResponse {
output: {
items: Array<{
videoId: string
title: string
description: string
thumbnail: string
channelTitle: string
}>
totalResults: number
nextPageToken?: string
}
}
export type YouTubeResponse =
| YouTubeSearchResponse
| YouTubeVideoDetailsResponse
@@ -195,4 +174,3 @@ export type YouTubeResponse =
| YouTubeCommentsResponse
| YouTubeChannelVideosResponse
| YouTubeChannelPlaylistsResponse
| YouTubeRelatedVideosResponse

View File

@@ -129,16 +129,8 @@ export const zendeskAutocompleteOrganizationsTool: ToolConfig<
},
outputs: {
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Organizations search results',
properties: {
organizations: { type: 'array', description: 'Array of organization objects' },
paging: { type: 'object', description: 'Pagination information' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success' },
},
},
organizations: { type: 'array', description: 'Array of organization objects' },
paging: { type: 'object', description: 'Pagination information' },
metadata: { type: 'object', description: 'Operation metadata' },
},
}

View File

@@ -151,15 +151,7 @@ export const zendeskCreateOrganizationTool: ToolConfig<
},
outputs: {
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Created organization data',
properties: {
organization: { type: 'object', description: 'Created organization object' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success' },
},
},
organization: { type: 'object', description: 'Created organization object' },
metadata: { type: 'object', description: 'Operation metadata' },
},
}

View File

@@ -103,15 +103,7 @@ export const zendeskCreateOrganizationsBulkTool: ToolConfig<
},
outputs: {
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Bulk creation job status',
properties: {
jobStatus: { type: 'object', description: 'Job status object' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success' },
},
},
jobStatus: { type: 'object', description: 'Job status object' },
metadata: { type: 'object', description: 'Operation metadata' },
},
}

View File

@@ -183,15 +183,7 @@ export const zendeskCreateTicketTool: ToolConfig<
},
outputs: {
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Created ticket data',
properties: {
ticket: { type: 'object', description: 'Created ticket object' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success' },
},
},
ticket: { type: 'object', description: 'Created ticket object' },
metadata: { type: 'object', description: 'Operation metadata' },
},
}

View File

@@ -104,15 +104,7 @@ export const zendeskCreateTicketsBulkTool: ToolConfig<
},
outputs: {
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Bulk create job status',
properties: {
jobStatus: { type: 'object', description: 'Job status object' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success' },
},
},
jobStatus: { type: 'object', description: 'Job status object' },
metadata: { type: 'object', description: 'Operation metadata' },
},
}

View File

@@ -163,15 +163,7 @@ export const zendeskCreateUserTool: ToolConfig<ZendeskCreateUserParams, ZendeskC
},
outputs: {
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Created user data',
properties: {
user: { type: 'object', description: 'Created user object' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success' },
},
},
user: { type: 'object', description: 'Created user object' },
metadata: { type: 'object', description: 'Operation metadata' },
},
}

View File

@@ -103,15 +103,7 @@ export const zendeskCreateUsersBulkTool: ToolConfig<
},
outputs: {
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Bulk creation job status',
properties: {
jobStatus: { type: 'object', description: 'Job status object' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success' },
},
},
jobStatus: { type: 'object', description: 'Job status object' },
metadata: { type: 'object', description: 'Operation metadata' },
},
}

View File

@@ -14,7 +14,7 @@ export interface ZendeskDeleteOrganizationParams {
export interface ZendeskDeleteOrganizationResponse {
success: boolean
output: {
organization: any
deleted: boolean
metadata: {
operation: 'delete_organization'
organizationId: string
@@ -82,7 +82,7 @@ export const zendeskDeleteOrganizationTool: ToolConfig<
return {
success: true,
output: {
organization: null,
deleted: true,
metadata: {
operation: 'delete_organization' as const,
organizationId: params?.organizationId || '',
@@ -93,15 +93,7 @@ export const zendeskDeleteOrganizationTool: ToolConfig<
},
outputs: {
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Deleted organization data',
properties: {
organization: { type: 'object', description: 'Deleted organization object' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success' },
},
},
deleted: { type: 'boolean', description: 'Deletion success' },
metadata: { type: 'object', description: 'Operation metadata' },
},
}

View File

@@ -93,15 +93,7 @@ export const zendeskDeleteTicketTool: ToolConfig<
},
outputs: {
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Delete confirmation',
properties: {
deleted: { type: 'boolean', description: 'Deletion success' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success' },
},
},
deleted: { type: 'boolean', description: 'Deletion success' },
metadata: { type: 'object', description: 'Operation metadata' },
},
}

View File

@@ -14,7 +14,7 @@ export interface ZendeskDeleteUserParams {
export interface ZendeskDeleteUserResponse {
success: boolean
output: {
user: any
deleted: boolean
metadata: {
operation: 'delete_user'
userId: string
@@ -80,7 +80,7 @@ export const zendeskDeleteUserTool: ToolConfig<ZendeskDeleteUserParams, ZendeskD
return {
success: true,
output: {
user: null,
deleted: true,
metadata: {
operation: 'delete_user' as const,
userId: params?.userId || '',
@@ -91,15 +91,7 @@ export const zendeskDeleteUserTool: ToolConfig<ZendeskDeleteUserParams, ZendeskD
},
outputs: {
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Deleted user data',
properties: {
user: { type: 'object', description: 'Deleted user object' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success' },
},
},
deleted: { type: 'boolean', description: 'Deletion success' },
metadata: { type: 'object', description: 'Operation metadata' },
},
}

View File

@@ -85,15 +85,7 @@ export const zendeskGetCurrentUserTool: ToolConfig<
},
outputs: {
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Current user data',
properties: {
user: { type: 'object', description: 'Current user object' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success' },
},
},
user: { type: 'object', description: 'Current user object' },
metadata: { type: 'object', description: 'Operation metadata' },
},
}

View File

@@ -92,15 +92,7 @@ export const zendeskGetOrganizationTool: ToolConfig<
},
outputs: {
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Organization data',
properties: {
organization: { type: 'object', description: 'Organization object' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success' },
},
},
organization: { type: 'object', description: 'Organization object' },
metadata: { type: 'object', description: 'Operation metadata' },
},
}

View File

@@ -120,16 +120,8 @@ export const zendeskGetOrganizationsTool: ToolConfig<
},
outputs: {
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Organizations data and metadata',
properties: {
organizations: { type: 'array', description: 'Array of organization objects' },
paging: { type: 'object', description: 'Pagination information' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success' },
},
},
organizations: { type: 'array', description: 'Array of organization objects' },
paging: { type: 'object', description: 'Pagination information' },
metadata: { type: 'object', description: 'Operation metadata' },
},
}

View File

@@ -90,15 +90,7 @@ export const zendeskGetTicketTool: ToolConfig<ZendeskGetTicketParams, ZendeskGet
},
outputs: {
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Ticket data',
properties: {
ticket: { type: 'object', description: 'Ticket object' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success' },
},
},
ticket: { type: 'object', description: 'Ticket object' },
metadata: { type: 'object', description: 'Operation metadata' },
},
}

View File

@@ -199,16 +199,8 @@ export const zendeskGetTicketsTool: ToolConfig<ZendeskGetTicketsParams, ZendeskG
},
outputs: {
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Tickets data and metadata',
properties: {
tickets: { type: 'array', description: 'Array of ticket objects' },
paging: { type: 'object', description: 'Pagination information' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success' },
},
},
tickets: { type: 'array', description: 'Array of ticket objects' },
paging: { type: 'object', description: 'Pagination information' },
metadata: { type: 'object', description: 'Operation metadata' },
},
}

View File

@@ -89,15 +89,7 @@ export const zendeskGetUserTool: ToolConfig<ZendeskGetUserParams, ZendeskGetUser
},
outputs: {
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'User data',
properties: {
user: { type: 'object', description: 'User object' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success' },
},
},
user: { type: 'object', description: 'User object' },
metadata: { type: 'object', description: 'Operation metadata' },
},
}

View File

@@ -133,16 +133,8 @@ export const zendeskGetUsersTool: ToolConfig<ZendeskGetUsersParams, ZendeskGetUs
},
outputs: {
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Users data and metadata',
properties: {
users: { type: 'array', description: 'Array of user objects' },
paging: { type: 'object', description: 'Pagination information' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success' },
},
},
users: { type: 'array', description: 'Array of user objects' },
paging: { type: 'object', description: 'Pagination information' },
metadata: { type: 'object', description: 'Operation metadata' },
},
}

View File

@@ -121,15 +121,7 @@ export const zendeskMergeTicketsTool: ToolConfig<
},
outputs: {
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Merge job status',
properties: {
jobStatus: { type: 'object', description: 'Job status object' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success' },
},
},
jobStatus: { type: 'object', description: 'Job status object' },
metadata: { type: 'object', description: 'Operation metadata' },
},
}

View File

@@ -141,16 +141,8 @@ export const zendeskSearchTool: ToolConfig<ZendeskSearchParams, ZendeskSearchRes
},
outputs: {
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Search results',
properties: {
results: { type: 'array', description: 'Array of result objects' },
paging: { type: 'object', description: 'Pagination information' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success' },
},
},
results: { type: 'array', description: 'Array of result objects' },
paging: { type: 'object', description: 'Pagination information' },
metadata: { type: 'object', description: 'Operation metadata' },
},
}

View File

@@ -99,15 +99,7 @@ export const zendeskSearchCountTool: ToolConfig<
},
outputs: {
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Search count result',
properties: {
count: { type: 'number', description: 'Number of matching results' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success' },
},
},
count: { type: 'number', description: 'Number of matching results' },
metadata: { type: 'object', description: 'Operation metadata' },
},
}

View File

@@ -136,16 +136,8 @@ export const zendeskSearchUsersTool: ToolConfig<
},
outputs: {
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Users search results',
properties: {
users: { type: 'array', description: 'Array of user objects' },
paging: { type: 'object', description: 'Pagination information' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success' },
},
},
users: { type: 'array', description: 'Array of user objects' },
paging: { type: 'object', description: 'Pagination information' },
metadata: { type: 'object', description: 'Operation metadata' },
},
}

View File

@@ -157,15 +157,7 @@ export const zendeskUpdateOrganizationTool: ToolConfig<
},
outputs: {
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Updated organization data',
properties: {
organization: { type: 'object', description: 'Updated organization object' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success' },
},
},
organization: { type: 'object', description: 'Updated organization object' },
metadata: { type: 'object', description: 'Operation metadata' },
},
}

View File

@@ -181,15 +181,7 @@ export const zendeskUpdateTicketTool: ToolConfig<
},
outputs: {
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Updated ticket data',
properties: {
ticket: { type: 'object', description: 'Updated ticket object' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success' },
},
},
ticket: { type: 'object', description: 'Updated ticket object' },
metadata: { type: 'object', description: 'Operation metadata' },
},
}

View File

@@ -141,15 +141,7 @@ export const zendeskUpdateTicketsBulkTool: ToolConfig<
},
outputs: {
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Bulk update job status',
properties: {
jobStatus: { type: 'object', description: 'Job status object' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success' },
},
},
jobStatus: { type: 'object', description: 'Job status object' },
metadata: { type: 'object', description: 'Operation metadata' },
},
}

View File

@@ -170,15 +170,7 @@ export const zendeskUpdateUserTool: ToolConfig<ZendeskUpdateUserParams, ZendeskU
},
outputs: {
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Updated user data',
properties: {
user: { type: 'object', description: 'Updated user object' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success' },
},
},
user: { type: 'object', description: 'Updated user object' },
metadata: { type: 'object', description: 'Operation metadata' },
},
}

View File

@@ -103,15 +103,7 @@ export const zendeskUpdateUsersBulkTool: ToolConfig<
},
outputs: {
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Bulk update job status',
properties: {
jobStatus: { type: 'object', description: 'Job status object' },
metadata: { type: 'object', description: 'Operation metadata' },
success: { type: 'boolean', description: 'Operation success' },
},
},
jobStatus: { type: 'object', description: 'Job status object' },
metadata: { type: 'object', description: 'Operation metadata' },
},
}