Skip to content

fix(telegram): allow plain URLs in photo/video/audio/animation fields#3797

Open
cyphercodes wants to merge 1 commit intosimstudioai:stagingfrom
cyphercodes:fix/telegram-photo-url
Open

fix(telegram): allow plain URLs in photo/video/audio/animation fields#3797
cyphercodes wants to merge 1 commit intosimstudioai:stagingfrom
cyphercodes:fix/telegram-photo-url

Conversation

@cyphercodes
Copy link
Copy Markdown

Summary

Fixes #3220

The Telegram Send Photo block (and other media blocks) was rejecting valid photo URLs with "Photo is required." error. This happened because the function only accepted JSON-stringified file objects, not plain URLs.

Problem

When users passed a plain URL like to the photo field, the function tried to parse it as JSON, failed, and returned - causing the validation error.

Solution

Updated to detect http/https URLs and pass them through unchanged:

  • Added URL detection at the start of string handling
  • Returns URL strings as-is (or wrapped in array for non-single mode)
  • Still supports JSON-stringified file objects for backward compatibility
  • Updated TypeScript return types to include

Testing

  • ✅ Plain HTTPS URLs:
  • ✅ Plain HTTP URLs:
  • ✅ URLs with whitespace (properly trimmed)
  • ✅ JSON stringified file objects (backward compatible)
  • ✅ Regular file objects (backward compatible)

Impact

This fix affects all Telegram media operations that use :

  • Send Photo
  • Send Video
  • Send Audio
  • Send Animation

Users can now use direct URLs from previous blocks (e.g., Function block output) without workarounds.

Fixes simstudioai#3220

The normalizeFileInput function was rejecting plain URL strings for Telegram
media blocks (send_photo, send_video, etc.) because it only accepted JSON
stringified file objects.

Now it detects http/https URLs and passes them through unchanged, allowing
users to provide direct image URLs like "https://example.com/photo.jpg".

Changes:
- Updated normalizeFileInput to detect and return URL strings as-is
- Updated function return types to include string type
- Added comprehensive unit tests for URL handling

Tested with:
- Plain HTTPS URLs
- Plain HTTP URLs
- URLs with whitespace (trimmed)
- JSON stringified file objects (still work)
- Regular file objects (still work)
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 26, 2026

@cyphercodes is attempting to deploy a commit to the Sim Team on Vercel.

A member of the Team first needs to authorize it.

@cursor
Copy link
Copy Markdown

cursor bot commented Mar 26, 2026

PR Summary

Medium Risk
Changes a widely-used file-normalization helper to return URL strings (and updates its TypeScript overloads), which could affect downstream callers that assumed only object/array outputs. Added tests reduce regression risk, but behavior changes across many blocks that rely on this utility.

Overview
Fixes normalizeFileInput to accept plain http:///https:// strings by trimming and returning them directly (or wrapped in an array when single is false) instead of failing JSON parsing and producing undefined.

Updates the function’s overload return types to include string, and adds a new vitest suite covering URL pass-through, whitespace trimming, JSON-stringified inputs, object/array inputs, and edge cases (including the single-file error).

Written by Cursor Bugbot for commit aa0060e. This will update automatically on new commits. Configure here.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 26, 2026

Greptile Summary

This PR fixes a bug where the Telegram Send Photo/Video/Audio/Animation blocks rejected plain http/https URLs with a "Photo is required." error. The root cause was that normalizeFileInput — which normalizes file params that may arrive as JSON-stringified objects from the advanced-mode template resolver — did not handle raw URL strings, returning undefined for them instead.

Key changes:

  • apps/sim/blocks/utils.ts: Added an early URL-detection branch in normalizeFileInput that trims the string and returns it as-is (for single: true) or wrapped in an array (for single: false), before attempting JSON.parse.
  • apps/sim/blocks/utils.test.ts: New test file covering HTTPS/HTTP URLs, whitespace trimming, JSON objects, plain objects/arrays, and all null/undefined/empty edge cases.

Findings:

  • The fix is logically correct for the Telegram use-case and is backward-compatible with existing JSON-object flows.
  • The test file uses a relative import (./utils) and is missing the @vitest-environment node annotation — both are minor convention violations per project guidelines.
  • Broader concerns about overload return-type accuracy and URL passthrough affecting non-Telegram callers were discussed in prior review threads and are not re-raised here.

Confidence Score: 5/5

Safe to merge; the fix correctly unblocks plain URL inputs for Telegram media blocks with no regression risk to existing file-object paths.

All remaining findings are P2 style/convention issues (relative import, missing Vitest environment annotation) that do not affect runtime behaviour or correctness. The logic change is well-targeted, backward-compatible, and covered by new tests.

apps/sim/blocks/utils.test.ts — minor convention fixes needed (absolute import, @vitest-environment node).

Important Files Changed

Filename Overview
apps/sim/blocks/utils.ts Adds http/https URL passthrough to normalizeFileInput; the fix correctly resolves the Telegram photo/video/audio/animation validation error. Return-type overload accuracy and broader caller impact were discussed in prior review threads.
apps/sim/blocks/utils.test.ts New test file with solid coverage of the URL, JSON, object, and edge-case paths; uses a relative import and is missing the required @vitest-environment node annotation.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[normalizeFileInput called] --> B{fileParam falsy?}
    B -- yes --> C[return undefined]
    B -- no --> D{typeof fileParam === 'string'?}
    D -- no --> G
    D -- yes --> E{starts with http:// or https://?}
    E -- yes --> F{options.single?}
    F -- yes --> F1[return trimmed URL string]
    F -- no --> F2[return trimmed URL wrapped in array]
    E -- no --> J{JSON.parse succeeds?}
    J -- no --> K[return undefined]
    J -- yes --> G{Array.isArray fileParam?}
    G -- yes --> H{length > 0?}
    H -- yes --> I[files = fileParam]
    H -- no --> K2[files = undefined]
    I --> L{options.single?}
    K2 --> M[return undefined]
    G -- no --> N{typeof object and not null?}
    N -- yes --> O[files = fileParam wrapped in array]
    N -- no --> M
    O --> L
    L -- yes --> P{files.length > 1?}
    P -- yes --> Q[throw error]
    P -- no --> R[return files 0]
    L -- no --> S[return files array]
Loading

Reviews (2): Last reviewed commit: "fix(telegram): allow plain URLs in photo..." | Re-trigger Greptile

Comment on lines 330 to +333
export function normalizeFileInput(
fileParam: unknown,
options?: { single?: false }
): object[] | undefined
): object[] | string | undefined
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Non-single overload return type doesn't include string[]

When a URL is passed with single: false (or no options), the implementation returns [trimmed] — a string[]. However, the non-single overload declares object[] | string | undefined as its return type. TypeScript doesn't flag this because string[] is assignable to the implementation's object variant (arrays are objects), but it means callers receive a typed promise of object[] while actually getting string[] at runtime.

Any caller that iterates over the returned array and accesses file-object properties (.name, .url, etc.) would silently get undefined instead of failing loudly with a type error. The telegram_send_document path (normalizeFileInput(params.files)) is one such non-single caller — a URL string passed as params.files would return [url] typed as object[] | string | undefined, which could confuse downstream handlers.

The overload should reflect the actual runtime shape:

export function normalizeFileInput(
  fileParam: unknown,
  options?: { single?: false }
): (object | string)[] | string | undefined

Comment on lines +342 to +345
const trimmed = fileParam.trim()
if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) {
return options?.single ? trimmed : [trimmed]
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 URL passthrough affects all normalizeFileInput callers, not just Telegram

This change is correct for Telegram media blocks, but normalizeFileInput is used by ~15 other blocks (Box, Confluence, Google Drive, Fireflies, Jira, Linear, etc.). Previously, passing a URL string to any of these would silently return undefined, causing a validation error. Now the URL string is returned to the caller.

For upload-oriented blocks (e.g. Box's upload_file, Confluence attachments), the caller assigns the result directly to params.file / baseParams.file and passes it to the tool handler. A URL string where those handlers expect a file object with { name, url, size } will likely cause a confusing downstream error instead of the current clear validation failure.

Consider scoping this change to Telegram-only, for example by adding an allowUrl?: boolean option to normalizeFileInput, so the URL shortcut doesn't silently change behaviour for other integrations:

export function normalizeFileInput(
  fileParam: unknown,
  options: { single: true; allowUrl?: boolean; errorMessage?: string }
): object | string | undefined

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

fileParam: unknown,
options?: { single?: false }
): object[] | undefined
): object[] | string | undefined
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-single overload return type doesn't match actual return

Medium Severity

When single is falsy, the URL path returns [trimmed] which is string[], but the non-single overload declares the return type as object[] | string | undefined. Since string is a primitive and not assignable to object, string[] is neither object[] nor string. A caller using this overload that checks typeof result === 'string' to detect a URL will never match (because it's actually an array), and a caller narrowing to object[] will get string elements incorrectly typed as object, potentially leading to runtime property-access errors on the array items.

Additional Locations (1)
Fix in Cursor Fix in Web

@waleedlatif1 waleedlatif1 deleted the branch simstudioai:staging April 3, 2026 23:01
@waleedlatif1 waleedlatif1 reopened this Apr 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants