mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
improvement(error-messages): make error extraction generalized abstraction (#1676)
* make error extraction generalized abstraction * remove comments * remove console logs
This commit is contained in:
committed by
GitHub
parent
64eee587cd
commit
c1725c1c4b
195
apps/sim/tools/error-extractors.test.ts
Normal file
195
apps/sim/tools/error-extractors.test.ts
Normal file
@@ -0,0 +1,195 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { ErrorExtractorId, type ErrorInfo, extractErrorMessage } from './error-extractors'
|
||||
|
||||
describe('Error Extractors', () => {
|
||||
describe('extractErrorMessage', () => {
|
||||
it('should extract GraphQL error messages', () => {
|
||||
const errorInfo: ErrorInfo = {
|
||||
status: 400,
|
||||
data: {
|
||||
errors: [{ message: 'GraphQL validation error' }],
|
||||
},
|
||||
}
|
||||
expect(extractErrorMessage(errorInfo)).toBe('GraphQL validation error')
|
||||
})
|
||||
|
||||
it('should extract Twitter API error details', () => {
|
||||
const errorInfo: ErrorInfo = {
|
||||
status: 403,
|
||||
data: {
|
||||
errors: [{ detail: 'Rate limit exceeded' }],
|
||||
},
|
||||
}
|
||||
expect(extractErrorMessage(errorInfo)).toBe('Rate limit exceeded')
|
||||
})
|
||||
|
||||
it('should extract Telegram API description', () => {
|
||||
const errorInfo: ErrorInfo = {
|
||||
status: 403,
|
||||
data: {
|
||||
ok: false,
|
||||
error_code: 403,
|
||||
description: "Forbidden: bots can't send messages to bots",
|
||||
},
|
||||
}
|
||||
expect(extractErrorMessage(errorInfo)).toBe("Forbidden: bots can't send messages to bots")
|
||||
})
|
||||
|
||||
it('should extract standard message field', () => {
|
||||
const errorInfo: ErrorInfo = {
|
||||
status: 400,
|
||||
data: {
|
||||
message: 'Invalid request parameters',
|
||||
},
|
||||
}
|
||||
expect(extractErrorMessage(errorInfo)).toBe('Invalid request parameters')
|
||||
})
|
||||
|
||||
it('should extract OAuth error_description', () => {
|
||||
const errorInfo: ErrorInfo = {
|
||||
status: 401,
|
||||
data: {
|
||||
error: 'invalid_grant',
|
||||
error_description: 'The provided authorization grant is invalid',
|
||||
},
|
||||
}
|
||||
expect(extractErrorMessage(errorInfo)).toBe('The provided authorization grant is invalid')
|
||||
})
|
||||
|
||||
it('should extract SOAP fault strings', () => {
|
||||
const errorInfo: ErrorInfo = {
|
||||
status: 500,
|
||||
data: {
|
||||
fault: {
|
||||
faultstring: 'SOAP processing error',
|
||||
},
|
||||
},
|
||||
}
|
||||
expect(extractErrorMessage(errorInfo)).toBe('SOAP processing error')
|
||||
})
|
||||
|
||||
it('should extract nested error object messages', () => {
|
||||
const errorInfo: ErrorInfo = {
|
||||
status: 400,
|
||||
data: {
|
||||
error: {
|
||||
message: 'Resource not found',
|
||||
},
|
||||
},
|
||||
}
|
||||
expect(extractErrorMessage(errorInfo)).toBe('Resource not found')
|
||||
})
|
||||
|
||||
it('should handle string error field', () => {
|
||||
const errorInfo: ErrorInfo = {
|
||||
status: 400,
|
||||
data: {
|
||||
error: 'Bad request',
|
||||
},
|
||||
}
|
||||
expect(extractErrorMessage(errorInfo)).toBe('Bad request')
|
||||
})
|
||||
|
||||
it('should extract errors array with strings', () => {
|
||||
const errorInfo: ErrorInfo = {
|
||||
status: 400,
|
||||
data: {
|
||||
errors: ['Email is required', 'Password is too short'],
|
||||
},
|
||||
}
|
||||
expect(extractErrorMessage(errorInfo)).toBe('Email is required')
|
||||
})
|
||||
|
||||
it('should use HTTP status text as fallback', () => {
|
||||
const errorInfo: ErrorInfo = {
|
||||
status: 404,
|
||||
statusText: 'Not Found',
|
||||
data: {},
|
||||
}
|
||||
expect(extractErrorMessage(errorInfo)).toBe('Not Found')
|
||||
})
|
||||
|
||||
it('should use final fallback when no pattern matches', () => {
|
||||
const errorInfo: ErrorInfo = {
|
||||
status: 500,
|
||||
data: {},
|
||||
}
|
||||
expect(extractErrorMessage(errorInfo)).toBe('Request failed with status 500')
|
||||
})
|
||||
|
||||
it('should handle undefined errorInfo', () => {
|
||||
expect(extractErrorMessage(undefined)).toBe('Request failed with status unknown')
|
||||
})
|
||||
|
||||
it('should handle empty strings gracefully', () => {
|
||||
const errorInfo: ErrorInfo = {
|
||||
status: 400,
|
||||
data: {
|
||||
message: '',
|
||||
},
|
||||
}
|
||||
// Should skip empty message and use fallback
|
||||
expect(extractErrorMessage(errorInfo)).toBe('Request failed with status 400')
|
||||
})
|
||||
})
|
||||
|
||||
describe('extractErrorMessage with explicit extractorId', () => {
|
||||
it('should use specified extractor directly (deterministic)', () => {
|
||||
const errorInfo: ErrorInfo = {
|
||||
status: 403,
|
||||
data: {
|
||||
description: "Forbidden: bots can't send messages to bots",
|
||||
message: 'Some other message',
|
||||
},
|
||||
}
|
||||
|
||||
// With explicit extractor ID, should use Telegram extractor
|
||||
expect(extractErrorMessage(errorInfo, ErrorExtractorId.TELEGRAM_DESCRIPTION)).toBe(
|
||||
"Forbidden: bots can't send messages to bots"
|
||||
)
|
||||
})
|
||||
|
||||
it('should use specified extractor even when other patterns match first', () => {
|
||||
const errorInfo: ErrorInfo = {
|
||||
status: 400,
|
||||
data: {
|
||||
errors: [{ message: 'GraphQL error' }], // This would match first normally
|
||||
message: 'Standard message', // Explicitly request this one
|
||||
},
|
||||
}
|
||||
|
||||
// With explicit ID, should skip GraphQL and use standard message
|
||||
expect(extractErrorMessage(errorInfo, ErrorExtractorId.STANDARD_MESSAGE)).toBe(
|
||||
'Standard message'
|
||||
)
|
||||
})
|
||||
|
||||
it('should fallback when specified extractor does not find message', () => {
|
||||
const errorInfo: ErrorInfo = {
|
||||
status: 404,
|
||||
data: {
|
||||
someOtherField: 'value',
|
||||
},
|
||||
}
|
||||
|
||||
// Telegram extractor won't find anything, should fallback
|
||||
expect(extractErrorMessage(errorInfo, ErrorExtractorId.TELEGRAM_DESCRIPTION)).toBe(
|
||||
'Request failed with status 404'
|
||||
)
|
||||
})
|
||||
|
||||
it('should warn and fallback for non-existent extractor ID', () => {
|
||||
const errorInfo: ErrorInfo = {
|
||||
status: 500,
|
||||
data: {
|
||||
message: 'Error message',
|
||||
},
|
||||
}
|
||||
|
||||
// Non-existent extractor should fallback
|
||||
expect(extractErrorMessage(errorInfo, 'non-existent-extractor')).toBe(
|
||||
'Request failed with status 500'
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
175
apps/sim/tools/error-extractors.ts
Normal file
175
apps/sim/tools/error-extractors.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
/**
|
||||
* Error Extractor Registry
|
||||
*
|
||||
* This module provides a clean, config-based approach to extracting error messages
|
||||
* from diverse API error response formats.
|
||||
*
|
||||
* ## Adding a new extractor
|
||||
*
|
||||
* 1. Add entry to ERROR_EXTRACTORS array below:
|
||||
* ```typescript
|
||||
* {
|
||||
* id: 'stripe-errors',
|
||||
* description: 'Stripe API error format',
|
||||
* examples: ['Stripe API'],
|
||||
* extract: (errorInfo) => errorInfo?.data?.error?.message
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* 2. Add the ID to ErrorExtractorId constant at the bottom of this file
|
||||
*/
|
||||
|
||||
export interface ErrorInfo {
|
||||
status?: number
|
||||
statusText?: string
|
||||
data?: any
|
||||
}
|
||||
|
||||
export type ErrorExtractor = (errorInfo?: ErrorInfo) => string | null | undefined
|
||||
|
||||
export interface ErrorExtractorConfig {
|
||||
/** Unique identifier for this extractor */
|
||||
id: string
|
||||
/** Human-readable description of what API/pattern this handles */
|
||||
description: string
|
||||
/** Example APIs that use this pattern */
|
||||
examples?: string[]
|
||||
/** The extraction function */
|
||||
extract: ErrorExtractor
|
||||
}
|
||||
|
||||
const ERROR_EXTRACTORS: ErrorExtractorConfig[] = [
|
||||
{
|
||||
id: 'graphql-errors',
|
||||
description: 'GraphQL errors array with message field',
|
||||
examples: ['Linear API', 'GitHub GraphQL'],
|
||||
extract: (errorInfo) => errorInfo?.data?.errors?.[0]?.message,
|
||||
},
|
||||
{
|
||||
id: 'twitter-errors',
|
||||
description: 'X/Twitter API error detail field',
|
||||
examples: ['Twitter/X API'],
|
||||
extract: (errorInfo) => errorInfo?.data?.errors?.[0]?.detail,
|
||||
},
|
||||
{
|
||||
id: 'details-array',
|
||||
description: 'Generic details array with message',
|
||||
examples: ['Various REST APIs'],
|
||||
extract: (errorInfo) => errorInfo?.data?.details?.[0]?.message,
|
||||
},
|
||||
{
|
||||
id: 'hunter-errors',
|
||||
description: 'Hunter API error details',
|
||||
examples: ['Hunter.io API'],
|
||||
extract: (errorInfo) => errorInfo?.data?.errors?.[0]?.details,
|
||||
},
|
||||
{
|
||||
id: 'errors-array-string',
|
||||
description: 'Errors array containing strings or objects with messages',
|
||||
examples: ['Various APIs with error arrays'],
|
||||
extract: (errorInfo) => {
|
||||
if (!Array.isArray(errorInfo?.data?.errors)) return undefined
|
||||
const firstError = errorInfo.data.errors[0]
|
||||
if (typeof firstError === 'string') return firstError
|
||||
return firstError?.message
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'telegram-description',
|
||||
description: 'Telegram Bot API description field',
|
||||
examples: ['Telegram Bot API'],
|
||||
extract: (errorInfo) => errorInfo?.data?.description,
|
||||
},
|
||||
{
|
||||
id: 'standard-message',
|
||||
description: 'Standard message field in error response',
|
||||
examples: ['Notion', 'Discord', 'GitHub', 'Twilio', 'Slack'],
|
||||
extract: (errorInfo) => errorInfo?.data?.message,
|
||||
},
|
||||
{
|
||||
id: 'soap-fault',
|
||||
description: 'SOAP/XML fault string patterns',
|
||||
examples: ['SOAP APIs', 'Legacy XML services'],
|
||||
extract: (errorInfo) => errorInfo?.data?.fault?.faultstring || errorInfo?.data?.faultstring,
|
||||
},
|
||||
{
|
||||
id: 'oauth-error-description',
|
||||
description: 'OAuth2 error_description field',
|
||||
examples: ['Microsoft OAuth', 'Google OAuth', 'OAuth2 providers'],
|
||||
extract: (errorInfo) => errorInfo?.data?.error_description,
|
||||
},
|
||||
{
|
||||
id: 'nested-error-object',
|
||||
description: 'Error field containing nested object or string',
|
||||
examples: ['Airtable', 'Google APIs'],
|
||||
extract: (errorInfo) => {
|
||||
const error = errorInfo?.data?.error
|
||||
if (!error) return undefined
|
||||
if (typeof error === 'string') return error
|
||||
if (typeof error === 'object') {
|
||||
return error.message || JSON.stringify(error)
|
||||
}
|
||||
return undefined
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'http-status-text',
|
||||
description: 'HTTP response status text fallback',
|
||||
examples: ['Generic HTTP errors'],
|
||||
extract: (errorInfo) => errorInfo?.statusText,
|
||||
},
|
||||
]
|
||||
|
||||
const EXTRACTOR_MAP = new Map<string, ErrorExtractorConfig>(ERROR_EXTRACTORS.map((e) => [e.id, e]))
|
||||
|
||||
export function extractErrorMessageWithId(
|
||||
errorInfo: ErrorInfo | undefined,
|
||||
extractorId: string
|
||||
): string {
|
||||
const extractor = EXTRACTOR_MAP.get(extractorId)
|
||||
|
||||
if (!extractor) {
|
||||
return `Request failed with status ${errorInfo?.status || 'unknown'}`
|
||||
}
|
||||
|
||||
try {
|
||||
const message = extractor.extract(errorInfo)
|
||||
if (message && message.trim()) {
|
||||
return message
|
||||
}
|
||||
} catch (error) {}
|
||||
|
||||
return `Request failed with status ${errorInfo?.status || 'unknown'}`
|
||||
}
|
||||
|
||||
export function extractErrorMessage(errorInfo?: ErrorInfo, extractorId?: string): string {
|
||||
if (extractorId) {
|
||||
return extractErrorMessageWithId(errorInfo, extractorId)
|
||||
}
|
||||
|
||||
// Backwards compatibility
|
||||
for (const extractor of ERROR_EXTRACTORS) {
|
||||
try {
|
||||
const message = extractor.extract(errorInfo)
|
||||
if (message && message.trim()) {
|
||||
return message
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
return `Request failed with status ${errorInfo?.status || 'unknown'}`
|
||||
}
|
||||
|
||||
export const ErrorExtractorId = {
|
||||
GRAPHQL_ERRORS: 'graphql-errors',
|
||||
TWITTER_ERRORS: 'twitter-errors',
|
||||
DETAILS_ARRAY: 'details-array',
|
||||
HUNTER_ERRORS: 'hunter-errors',
|
||||
ERRORS_ARRAY_STRING: 'errors-array-string',
|
||||
TELEGRAM_DESCRIPTION: 'telegram-description',
|
||||
STANDARD_MESSAGE: 'standard-message',
|
||||
SOAP_FAULT: 'soap-fault',
|
||||
OAUTH_ERROR_DESCRIPTION: 'oauth-error-description',
|
||||
NESTED_ERROR_OBJECT: 'nested-error-object',
|
||||
HTTP_STATUS_TEXT: 'http-status-text',
|
||||
} as const
|
||||
@@ -4,6 +4,8 @@ import { parseMcpToolId } from '@/lib/mcp/utils'
|
||||
import { getBaseUrl } from '@/lib/urls/utils'
|
||||
import { generateRequestId } from '@/lib/utils'
|
||||
import type { ExecutionContext } from '@/executor/types'
|
||||
import type { ErrorInfo } from '@/tools/error-extractors'
|
||||
import { extractErrorMessage } from '@/tools/error-extractors'
|
||||
import type { OAuthTokenPayload, ToolConfig, ToolResponse } from '@/tools/types'
|
||||
import {
|
||||
formatRequestParams,
|
||||
@@ -29,52 +31,12 @@ const MCP_SYSTEM_PARAMETERS = new Set([
|
||||
'blockNameMapping',
|
||||
])
|
||||
|
||||
// Extract a concise, meaningful error message from diverse API error shapes
|
||||
function getDeepApiErrorMessage(errorInfo?: {
|
||||
status?: number
|
||||
statusText?: string
|
||||
data?: any
|
||||
}): string {
|
||||
return (
|
||||
// GraphQL errors (Linear API)
|
||||
errorInfo?.data?.errors?.[0]?.message ||
|
||||
// X/Twitter API specific pattern
|
||||
errorInfo?.data?.errors?.[0]?.detail ||
|
||||
// Generic details array
|
||||
errorInfo?.data?.details?.[0]?.message ||
|
||||
// Hunter API pattern
|
||||
errorInfo?.data?.errors?.[0]?.details ||
|
||||
// Direct errors array (when errors[0] is a string or simple object)
|
||||
(Array.isArray(errorInfo?.data?.errors)
|
||||
? typeof errorInfo.data.errors[0] === 'string'
|
||||
? errorInfo.data.errors[0]
|
||||
: errorInfo.data.errors[0]?.message
|
||||
: undefined) ||
|
||||
// Notion/Discord/GitHub/Twilio pattern
|
||||
errorInfo?.data?.message ||
|
||||
// SOAP/XML fault patterns
|
||||
errorInfo?.data?.fault?.faultstring ||
|
||||
errorInfo?.data?.faultstring ||
|
||||
// Microsoft/OAuth error descriptions
|
||||
errorInfo?.data?.error_description ||
|
||||
// Airtable/Google fallback pattern
|
||||
(typeof errorInfo?.data?.error === 'object'
|
||||
? errorInfo?.data?.error?.message || JSON.stringify(errorInfo?.data?.error)
|
||||
: errorInfo?.data?.error) ||
|
||||
// HTTP status text fallback
|
||||
errorInfo?.statusText ||
|
||||
// Final fallback
|
||||
`Request failed with status ${errorInfo?.status || 'unknown'}`
|
||||
)
|
||||
}
|
||||
|
||||
// Create an Error instance from errorInfo and attach useful context
|
||||
function createTransformedErrorFromErrorInfo(errorInfo?: {
|
||||
status?: number
|
||||
statusText?: string
|
||||
data?: any
|
||||
}): Error {
|
||||
const message = getDeepApiErrorMessage(errorInfo)
|
||||
/**
|
||||
* Create an Error instance from errorInfo and attach useful context
|
||||
* Uses the error extractor registry to find the best error message
|
||||
*/
|
||||
function createTransformedErrorFromErrorInfo(errorInfo?: ErrorInfo, extractorId?: string): Error {
|
||||
const message = extractErrorMessage(errorInfo, extractorId)
|
||||
const transformed = new Error(message)
|
||||
Object.assign(transformed, {
|
||||
status: errorInfo?.status,
|
||||
@@ -506,7 +468,7 @@ async function handleInternalRequest(
|
||||
|
||||
const { isError, errorInfo } = isErrorResponse(response, errorData)
|
||||
if (isError) {
|
||||
const errorToTransform = createTransformedErrorFromErrorInfo(errorInfo)
|
||||
const errorToTransform = createTransformedErrorFromErrorInfo(errorInfo, tool.errorExtractor)
|
||||
|
||||
logger.error(`[${requestId}] Internal API error for ${toolId}:`, {
|
||||
status: errorInfo?.status,
|
||||
@@ -543,7 +505,7 @@ async function handleInternalRequest(
|
||||
|
||||
if (isError) {
|
||||
// Handle error case
|
||||
const errorToTransform = createTransformedErrorFromErrorInfo(errorInfo)
|
||||
const errorToTransform = createTransformedErrorFromErrorInfo(errorInfo, tool.errorExtractor)
|
||||
|
||||
logger.error(`[${requestId}] Internal API error for ${toolId}:`, {
|
||||
status: errorInfo?.status,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type {
|
||||
TelegramDeleteMessageParams,
|
||||
TelegramDeleteMessageResponse,
|
||||
@@ -13,6 +14,7 @@ export const telegramDeleteMessageTool: ToolConfig<
|
||||
description:
|
||||
'Delete messages in Telegram channels or chats through the Telegram Bot API. Requires the message ID of the message to delete.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.TELEGRAM_DESCRIPTION,
|
||||
|
||||
params: {
|
||||
botToken: {
|
||||
@@ -50,10 +52,16 @@ export const telegramDeleteMessageTool: ToolConfig<
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.ok) {
|
||||
const errorMessage = data.description || data.error || 'Failed to delete message'
|
||||
throw new Error(errorMessage)
|
||||
}
|
||||
|
||||
return {
|
||||
success: data.ok,
|
||||
success: true,
|
||||
output: {
|
||||
message: data.ok ? 'Message deleted successfully' : 'Failed to delete message',
|
||||
message: 'Message deleted successfully',
|
||||
data: {
|
||||
ok: data.ok,
|
||||
deleted: data.result,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type {
|
||||
TelegramMessage,
|
||||
TelegramSendMessageParams,
|
||||
@@ -15,6 +16,7 @@ export const telegramMessageTool: ToolConfig<
|
||||
description:
|
||||
'Send messages to Telegram channels or users through the Telegram Bot API. Enables direct communication and notifications with message tracking and chat confirmation.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.TELEGRAM_DESCRIPTION,
|
||||
|
||||
params: {
|
||||
botToken: {
|
||||
@@ -53,12 +55,18 @@ export const telegramMessageTool: ToolConfig<
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.ok) {
|
||||
const errorMessage = data.description || data.error || 'Failed to send message'
|
||||
throw new Error(errorMessage)
|
||||
}
|
||||
|
||||
const result = data.result as TelegramMessage
|
||||
|
||||
return {
|
||||
success: data.ok,
|
||||
success: true,
|
||||
output: {
|
||||
message: data.ok ? 'Message sent successfully' : 'Failed to send message',
|
||||
message: 'Message sent successfully',
|
||||
data: result,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type {
|
||||
TelegramMedia,
|
||||
TelegramSendAnimationParams,
|
||||
@@ -14,6 +15,7 @@ export const telegramSendAnimationTool: ToolConfig<
|
||||
name: 'Telegram Send Animation',
|
||||
description: 'Send animations (GIFs) to Telegram channels or users through the Telegram Bot API.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.TELEGRAM_DESCRIPTION,
|
||||
|
||||
params: {
|
||||
botToken: {
|
||||
@@ -66,11 +68,18 @@ export const telegramSendAnimationTool: ToolConfig<
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.ok) {
|
||||
const errorMessage = data.description || data.error || 'Failed to send animation'
|
||||
throw new Error(errorMessage)
|
||||
}
|
||||
|
||||
const result = data.result as TelegramMedia
|
||||
|
||||
return {
|
||||
success: data.ok,
|
||||
success: true,
|
||||
output: {
|
||||
message: data.ok ? 'Animation sent successfully' : 'Failed to send animation',
|
||||
message: 'Animation sent successfully',
|
||||
data: result,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type {
|
||||
TelegramAudio,
|
||||
TelegramSendAudioParams,
|
||||
@@ -12,6 +13,7 @@ export const telegramSendAudioTool: ToolConfig<TelegramSendAudioParams, Telegram
|
||||
name: 'Telegram Send Audio',
|
||||
description: 'Send audio files to Telegram channels or users through the Telegram Bot API.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.TELEGRAM_DESCRIPTION,
|
||||
|
||||
params: {
|
||||
botToken: {
|
||||
@@ -64,12 +66,18 @@ export const telegramSendAudioTool: ToolConfig<TelegramSendAudioParams, Telegram
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.ok) {
|
||||
const errorMessage = data.description || data.error || 'Failed to send audio'
|
||||
throw new Error(errorMessage)
|
||||
}
|
||||
|
||||
const result = data.result as TelegramAudio
|
||||
|
||||
return {
|
||||
success: data.ok,
|
||||
success: true,
|
||||
output: {
|
||||
message: data.ok ? 'Audio sent successfully' : 'Failed to send audio',
|
||||
message: 'Audio sent successfully',
|
||||
data: result,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type {
|
||||
TelegramPhoto,
|
||||
TelegramSendPhotoParams,
|
||||
@@ -12,6 +13,7 @@ export const telegramSendPhotoTool: ToolConfig<TelegramSendPhotoParams, Telegram
|
||||
name: 'Telegram Send Photo',
|
||||
description: 'Send photos to Telegram channels or users through the Telegram Bot API.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.TELEGRAM_DESCRIPTION,
|
||||
|
||||
params: {
|
||||
botToken: {
|
||||
@@ -64,12 +66,18 @@ export const telegramSendPhotoTool: ToolConfig<TelegramSendPhotoParams, Telegram
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.ok) {
|
||||
const errorMessage = data.description || data.error || 'Failed to send photo'
|
||||
throw new Error(errorMessage)
|
||||
}
|
||||
|
||||
const result = data.result as TelegramPhoto
|
||||
|
||||
return {
|
||||
success: data.ok,
|
||||
success: true,
|
||||
output: {
|
||||
message: data.ok ? 'Photo sent successfully' : 'Failed to send photo',
|
||||
message: 'Photo sent successfully',
|
||||
data: result,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type {
|
||||
TelegramMedia,
|
||||
TelegramSendMediaResponse,
|
||||
@@ -12,6 +13,7 @@ export const telegramSendVideoTool: ToolConfig<TelegramSendVideoParams, Telegram
|
||||
name: 'Telegram Send Video',
|
||||
description: 'Send videos to Telegram channels or users through the Telegram Bot API.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.TELEGRAM_DESCRIPTION,
|
||||
|
||||
params: {
|
||||
botToken: {
|
||||
@@ -64,12 +66,18 @@ export const telegramSendVideoTool: ToolConfig<TelegramSendVideoParams, Telegram
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.ok) {
|
||||
const errorMessage = data.description || data.error || 'Failed to send video'
|
||||
throw new Error(errorMessage)
|
||||
}
|
||||
|
||||
const result = data.result as TelegramMedia
|
||||
|
||||
return {
|
||||
success: data.ok,
|
||||
success: true,
|
||||
output: {
|
||||
message: data.ok ? 'Video sent successfully' : 'Failed to send video',
|
||||
message: 'Video sent successfully',
|
||||
data: result,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -78,6 +78,11 @@ export interface ToolConfig<P = any, R = any> {
|
||||
// OAuth configuration for this tool (if it requires authentication)
|
||||
oauth?: OAuthConfig
|
||||
|
||||
// Error extractor to use for this tool's error responses
|
||||
// If specified, only this extractor will be used (deterministic)
|
||||
// If not specified, will try all extractors in order (fallback)
|
||||
errorExtractor?: string
|
||||
|
||||
// Request configuration
|
||||
request: {
|
||||
url: string | ((params: P) => string)
|
||||
|
||||
Reference in New Issue
Block a user